Legacy POS sync without stopping operations
A brewery needed to show consumption rankings on the bar TV. Data came from the POS — the establishment’s legacy system. The hardware was an Android TV Box running 24/7; the internet dropped; the POS restarted daily. Shutting everything down to migrate was risky — the bar could not stop.
This post describes what I did on Batalha dos Barris: instead of replacing the system in one go, I swapped parts gradually, with a separate sync agent.
Context
- Where data originates: MySQL on the POS (legacy system; we don’t control deploys).
- Where data lands: PostgreSQL + Flask API + Angular player (PWA) on the TV Box.
- What the business expected: rankings updated regularly, without someone at the TV tweaking code.
If sync stops, the ranking freezes — and customers in the bar can see it.
Architecture
textPOS MySQL → Python agent (SSH) → PostgreSQL → Flask API → Angular app on TV Box
- The player on the TV does not touch legacy MySQL directly.
- The agent handles SSH, POS restarts, and the sync cursor.
- The API uses cache to avoid hammering the player with requests.
We chose fixed-interval sync instead of a live connection (websocket). On a TV Box with unstable internet, that is simpler and more reliable.
Problems we hit
Internet drops on the agent: sync must resume from the last confirmed ID or timestamp — without reprocessing everything or skipping sales.
POS restarts: the agent reconnects and validates the resume point; the player shows the last cached snapshot.
Data lag: today we find out via logs; if I did it again, I’d add alerts when lag exceeds an acceptable threshold.
Lessons for other projects
- Resume from where you left off should be a requirement from day one, not a later optimization.
- Incremental sync with an interval that tolerates short network failures.
- Isolate failures: legacy, agent, API, and player should not take each other down.
- Next step: measure lag and failures per stage.




Comments
Sign in with your GitHub account to comment. Comments are public, stored as Discussions on the blog's comments repository.