Internet connectivity in a busy Lebanese restaurant cannot be assumed. A POS system that stops working when the connection drops is an operational liability. Here is how to design sync architecture that keeps restaurants running and keeps data consistent whether the network is present or not.
Internet connectivity in a busy Lebanese restaurant cannot be assumed. Power interruptions, router failures, and ISP outages in Lebanon and across much of the MENA region make offline operation a hard requirement, not a nice-to-have. A POS system that stops functioning when the network drops is an operational liability that restaurant owners in Beirut and Riyadh both understand from direct experience. This is how to design the sync architecture that keeps operations running regardless of connectivity.
Why POS sync is harder than it looks
At first glance, POS synchronization looks straightforward: terminals write data locally, upload it to a central server when connectivity returns, and the server reconciles. In practice, several properties of restaurant operations make this significantly harder.
Multiple terminals operate simultaneously. A restaurant with five terminals processes concurrent orders. Each terminal must see an accurate picture of table status, available inventory, and current promotions. A table that is marked as occupied by terminal one should not appear available on terminal three a second later.
Partial network availability is common. A restaurant chain in Lebanon may have reliable fiber at its Ashrafieh location and intermittent 4G at a mall branch in Tripoli. The sync architecture must handle terminals that have been offline for varying durations, from seconds to hours.
Order modifications compound the conflict problem. A waiter adds items to an order on terminal two. The manager applies a discount on terminal four. The kitchen printer is connected to terminal one. All three terminals have the same order ID and are in different states of partial connectivity when network returns.
The local-first architecture
The correct architecture for POS systems in Lebanon and the MENA region is local-first: each terminal has a complete local database that can process all operations independently, and the sync layer runs as a background process.
SQLite is the standard choice for the terminal-side database. It is embedded, requires no server process, supports transactions, and handles the read/write patterns of POS operations cleanly. For a Go-based POS terminal, the modernc.org/sqlite package provides a pure-Go SQLite driver that eliminates CGO dependencies and simplifies cross-compilation for POS hardware.
The local schema mirrors the server-side schema with two additions: a sync_status column on every table that participates in sync (values: pending, synced, conflict), and a local_version integer that increments on every local write.
Change capture and queue-based sync
When a terminal is online, changes flow to the central server in near real-time. When offline, changes accumulate in a local outbox table. On reconnection, the outbox is replayed in order.
A Go struct for an outbox entry:
type OutboxEntry struct {
ID uuid.UUID `db:"id"`
TableName string `db:"table_name"`
RecordID uuid.UUID `db:"record_id"`
Operation string `db:"operation"` // insert, update, delete
Payload []byte `db:"payload"` // JSON snapshot of changed record
CreatedAt time.Time `db:"created_at"`
SyncedAt *time.Time `db:"synced_at"`
}
Every write operation on the terminal (new order, order modification, payment, table status change) first writes to the target table, then writes an outbox entry in the same SQLite transaction. The outbox is the durable record of everything that needs to reach the server.
The sync worker runs as a goroutine on the terminal, checking connectivity every few seconds and draining the outbox when online:
func (s *SyncWorker) drainOutbox(ctx context.Context) error {
entries, err := s.db.GetPendingOutboxEntries(ctx, 100)
if err != nil {
return err
}
for _, entry := range entries {
if err := s.sendToServer(ctx, entry); err != nil {
return err // will retry next cycle
}
s.db.MarkOutboxEntrySynced(ctx, entry.ID)
}
return nil
}
Conflict resolution in a multi-terminal environment
Conflicts arise when two terminals modify the same record while one or both are offline. The server receives two versions of the record with different content but the same record ID.
For most POS conflicts, a last-write-wins strategy with a server-side clock is sufficient. The server compares updated_at timestamps and accepts the most recent. This handles the common case of two waiters updating the same table status.
For order modifications, last-write-wins is not safe. If terminal one adds a dish and terminal two removes a dish from the same order, and the server accepts only one, the customer gets either an extra dish or a removed one that was already prepared. The correct resolution for order line items is set-union: merge the changes rather than selecting one.
The merge strategy per record type:
- Table status (available, occupied, reserved): last-write-wins by server timestamp
- Order header (discount, status, payment): last-write-wins
- Order line items (items added or removed): set-union with conflict flag if an item was simultaneously modified
- Inventory adjustments: accumulate deltas, apply sum
A conflict flag on a record surfaces in the manager's dashboard for manual review. In practice, the vast majority of conflicts in restaurant operations are resolvable by automated strategy, with manual review needed only for edge cases.
Server-side reconciliation and broadcast
After the server processes incoming changes from a terminal, it must broadcast the reconciled state back to all other terminals. In a restaurant chain with ten branches across Lebanon and the Gulf, each branch's terminals subscribe to updates relevant to their branch. Cross-branch data sharing is minimal: promotions, menu updates, and aggregate reporting flow from server to terminals on a slower schedule.
For real-time intra-branch sync, WebSocket connections from each terminal to the server are the practical choice. The server maintains a connection registry per branch and broadcasts reconciled record updates to all connected terminals in that branch.
For branches currently offline, the server queues pending updates. When the branch reconnects, it pulls the queued updates before broadcasting any local changes. This ordering ensures that the terminal receives server state before applying its own accumulated changes, which reduces the conflict surface.
Inventory sync as a special case
Inventory sync is the most operationally sensitive component of a restaurant POS. A dish sold at terminal one decrements inventory. The same dish ordered at terminal three while terminal one is offline creates an oversell risk if both decrements apply.
The correct model for inventory in an offline-first POS is reservation-based, not immediate decrement. When a dish is ordered, the terminal creates an inventory reservation record with a status of pending. The server processes reservations in order and either confirms or denies them based on actual available inventory. If a reservation is denied (the item was already sold out), the kitchen receives an alert and the waiter is notified.
For Lebanese and Gulf restaurant chains where menu items go out of stock frequently during peak periods (specific cuts of meat, seasonal items), the reservation model is operationally important. The alternative (allowing oversell and dealing with it manually) degrades the dining experience and creates operational friction.
Key lessons from production
Local-first with SQLite on the terminal is the correct architecture for restaurant POS systems in MENA. Assume no connectivity. Design for sync as background work, not as a dependency for terminal function.
The outbox pattern gives you durable change capture without complex event sourcing infrastructure. It is the right level of complexity for a POS product.
Conflict resolution strategy must be defined per record type. A single last-write-wins policy is insufficient for order line items. Define the merge strategy for each entity before you start building, not after you hit your first conflict in production.
Inventory reservations prevent oversell in offline scenarios. Implement reservation-based inventory from the start rather than retrofitting it after the first complaint from a kitchen manager.
Enjoying this article?
Enter your email and get a clean, formatted PDF of this article - free, no spam.
Not sure where to start?
Voxire builds POS and restaurant management systems for Lebanese and MENA restaurant chains, including offline-first terminal architecture, multi-branch sync infrastructure, and inventory management systems. If you are building or upgrading a restaurant POS system, we can help you design the sync layer correctly from the start.
https://voxire.com/get-a-quote/


