At Gruppo Mai Più, we build digital products for real businesses. One of them is HotelManager, a hotel management system where the front desk works in a local app and the owner can view part of the operation from the cloud.
A seemingly small decision turned out to matter: how often should we sync the hotel with the cloud without slowing down local operations?
At first, both ends of the range seemed debatable. Too short could become expensive at scale. Too long could make the web dashboard feel sluggish.
The number we landed on was 5 seconds. Not by magic, but as a product decision: accept a few seconds of delay in exchange for a system that is simple, resilient, and easy to operate.
In one sentence: the front desk works locally in real time; the cloud updates within a few seconds.
The principle
HotelManager is designed around the front desk first.
A check-in, checkout, payment, or room change cannot depend on an internet connection. That is why the core system runs inside the hotel.
The desktop app talks to a local service, which writes to an encrypted local database1. The cloud is not on the critical path of day-to-day operations.
The cloud has a different role: giving the owner visibility, remote configuration, auditing, sync health, and management tools. We are not chasing perfect millisecond latency; we want frictionless local operations and cloud data fresh enough to make decisions.
The sync intervals
When we say “5 seconds,” we are simplifying. The system runs on several clocks.
| Layer | Idle | While active | Key detail |
|---|---|---|---|
| Hotel-to-cloud events | 5s | 100ms2 | Batches of up to 100 events |
| Cloud-to-hotel commands | 3s | 100ms2 | Batches of up to 50 commands |
| Desktop app | 8s | When it becomes visible again | Does not refresh while the window is hidden |
| Web dashboard | 20s | 3s | Runs quickly while work is pending |
| Sync failures | Progressive backoff3 | Up to 5 min | Prevents retry storms |
The most important detail is in the first row: when the hotel has no new events, the publishing process does not send an empty request to the cloud. It only checks the local database and goes back to sleep.
That clears up a common misconception. It does not mean every hotel sends data every 5 seconds forever. If nothing changed, there is nothing to publish.
What happens when something changes
Synchronization runs in both directions.
When the local server saves a change, it also stores an event in an outbox4.
A background process checks that queue. If it finds events, it sends them to the cloud. When it finds work, it checks again in 100ms to drain the queue quickly.
In the other direction, the cloud uses commands. When the owner changes something from the web dashboard, the cloud queues a pending command5. The hotel checks the cloud every 3 seconds, fetches that command, applies it locally, and reports whether it succeeded or failed.
The rule is simple: the cloud requests, but the hotel applies.
There is also deduplication6. If a command is applied but its confirmation fails, the next attempt does not apply it twice. It only confirms the result again.
There is one special case: login. Before validating the PIN, the system tries to fetch recent changes with a 2-second timeout. If the cloud is slow, we do not leave the front desk staff locked out.
The real-world experience
We do not sell this as “perfect real time.”
The accurate description is: changes usually propagate within a few seconds, and the hotel keeps working even when the cloud is unavailable.
For a remote action, the path is: the owner changes something, the cloud creates a command, the hotel fetches and applies it, generates an event, and that event returns to the cloud.
That is why the interface shows states such as pending, applied, or failed. It is better to tell the truth than pretend a remote action has already happened.
Why we did not start with persistent connections
WebSockets7 or SSE8 can reduce latency. They are not a bad idea, but they do not eliminate the hard problems: queues, retries, deduplication, pending states, and recovery after being offline.
HotelManager also runs in real hotels: Windows PCs, ordinary routers, imperfect connections, restarts, and little technical supervision.
Periodic polling gave us a simpler operational surface:
- Each attempt is an HTTP request9.
- Reconnecting means trying again.
- Errors can be logged and displayed.
- Retries have a limit.
- Commands can be retried without duplicating the change.
- Support can tell whether something is pending, applied, or failed.
In this product, that clarity matters. A hotel does not grind to a halt because the dashboard sees a value 2 seconds late. It does if the front desk cannot operate or nobody understands what happened when the internet came back.
The question of scale
Poorly designed polling can be expensive. That is why the outbox matters: the hotel-to-cloud side sends no empty requests when there are no events.
Today, the largest fixed cost is on the cloud-to-hotel side. A connected hotel checks for commands or configuration snapshots10, which also acts as a heartbeat.
For a pilot and an initial production stage, that cost is acceptable: it is simple, observable, and predictable. If it grows too much, we can improve the transport without changing the model:
- Combine configuration and command checks.
- Add jitter11 so hotels do not all check at the same second.
- Increase the interval when there is no work.
- Move commands to long polling12.
- Enable an SSE channel8.
The important architecture is not the polling itself. It is an outbox plus a command queue, with safe application and confirmation.
The transport can evolve.
The actual decision
We chose 5 seconds for publishing events because it felt fast, was inexpensive at our initial scale, and was easy to debug.
We combined it with commands every 3 seconds, fast draining at 100ms while there is work, and backoff up to 5 minutes when failures occur.
We kept local operations as the source of truth because the front desk is the center of the product.
We do not poll just for the sake of it. It is a deliberate way to buy simplicity without giving up an experience measured in seconds.
For anyone who wants to dig deeper
The Stripe documentation on webhooks and its guide to PaymentIntents explain why polling public APIs is best avoided when asynchronous events are available.
The Dropbox engineering post on long polling shows an interesting alternative when the real goal becomes reducing latency or idle traffic.
Notes
- An encrypted database protects data so it cannot be read without the correct key.
- 100ms is one tenth of a second. It is used once work appears and the queue should be drained quickly.
- Progressive backoff means waiting increasingly longer before retrying after a failure.
- An outbox is the local list of changes waiting to be sent to the cloud.
- A pending command is an instruction the cloud leaves ready for the hotel to apply.
- Deduplication allows retries without applying the same change twice.
- A WebSocket is a persistent connection between two systems.
- SSE is a channel through which the server can push updates to the client.
- HTTP is the standard web protocol; here it is a simple call between the hotel and the cloud.
- A configuration snapshot is a complete copy of a given configuration.
- Jitter is a small random delay that keeps every hotel from checking at the same second.
- Long polling is a request that remains open until something new appears or the request times out.