Why AppSync integration into a mobile app breaks under real-world loads
Integrating AppSync into a mobile app almost always goes smoothly on a staging environment and starts falling apart in the field.
The reason is not the service itself, but the fact that a mobile client lives under conditions that server-side code doesn't have: the network drops mid-request, the app is backgrounded, the device switches from Wi-Fi to cellular.
Data synchronization in a mobile app is not a single request, but a constant exchange between the local cache, the queue of unsent changes, and the server.
The first typical failure is local cache desync. The client wrote a change offline, sent it later, and by that time the server had already received an edit from another device.
Without record versioning and a conflict resolution strategy, the last response silently overwrites fresher data, and the user sees a stale state.
A mobile app's offline mode rests not on "saving to a database," but on a strict order of applying mutations and an explicit rule for whose record version wins.
The second is duplicate real-time subscriptions. A screen is recreated on rotation or when returning from the background, opening a new connection each time, and the old ones are not closed.
The client receives the same event two or three times, counters double up, and the channel holds extra sockets and drains the battery.
The fix is tying the subscription to the screen's lifecycle and having a single subscription point at the store level, not at the level of an individual widget.
The third is degradation when the network switches. The connection breaks, the client goes into reconnect with increasing backoff, and without proper handling of the disconnect, events that arrived during the transition are lost. The user sees a "frozen" list, even though the data on the server has already been updated.
All three failures don't show up immediately, but under real load and on a weak network — so below we break down how this is addressed at the client architecture level.
What correct AppSync integration delivers: sync modes and their effect
Sync modes: what changes in the numbers
Real-time mode keeps an open channel to the service: a change on the server reaches the device in fractions of a second, and the write-back goes out immediately.
This is what chats, order tracking, and collaborative lists need — data update latency stops being noticeable to the eye.
The price is a persistent connection and careful handling of drops: without resubscription after a network loss, the interface quietly "freezes" and shows stale data.
Periodic synchronization polls the server on a schedule and costs less in resources, but it introduces a desync window — on slow channels it grows.
An offline change queue covers the third scenario: edits accumulate locally and go out in a batch once the network appears, so the app stays functional in the subway and in a warehouse.
The key question here is conflict resolution: a "last write wins" strategy is simple but loses others' edits, while field-level versioned merging preserves both sides.
| Option | When it fits | Limitations |
|---|---|---|
| Real time | chats, statuses, collaboration | requires a stable network, harder rollback |
| Periodic polling | catalogs, reports, infrequent updates | desync window grows with the interval |
| Offline queue + merging | field work, warehouses, traveling employees | an explicit conflict strategy is required |
In practice, modes are combined: reference data is pulled by polling, critical entities — by a stream, and writes always go through a queue with versioning.
This arrangement eliminates a whole class of failures — lost edits during simultaneous editing and "stuck" screens after a connection drop. We set this up on AWS AppSync, where subscriptions, offline storage, and merge rules are described declaratively.
AWS AppSync
Implementation stages: from data schema audit to documentation handover
Implementing synchronization starts not with code, but with a data schema audit: which types and fields already exist, how relationships are structured, where clients write directly to the database, and where they read outdated snapshots.
At this step, we map out queries and subscriptions so that new operations don't duplicate existing ones and don't pull unnecessary data onto the mobile device.
- Data schema and current flow audit. We cross-check models, per-operation access rights, and analyze which app screens read and write which entities. The output is a list of conflicts and places where synchronization will break existing logic.
- Target schema design. We define types, partition and sort keys, a conflict resolution strategy for simultaneous edits from multiple devices, and authorization rules based on record ownership.
- Synchronization and offline mode setup. We connect local storage on the device, configure queues for deferred changes and resending after connectivity is restored, so the app works without a network and doesn't lose edits.
- Mobile app load testing. We run scenarios with a large number of simultaneous subscriptions, frequent network switches, and bulk edits. We monitor how response time, volume of transmitted data, and battery consumption behave.
- Optimization based on measurement results. We remove redundant fields from responses, split heavy queries, and limit update frequency where the interface can get by with periodic polling.
- Documentation handover. We deliver the schema description, access rules, synchronization and offline behavior scenarios, environment deployment instructions, and a checklist for the client's team.
This sequence reduces the risk that synchronization will have to be redone after the first user complaints about data discrepancies. Describe your task — we'll come back with an estimate and a plan for each stage.
How does offline mode with a local database differ from working directly with the server?
Working directly with the server means that every screen of the application waits for a network response. While there is a connection, it looks simple: a request goes out, data comes back, the interface updates.
But as soon as the connection drops — subway, basement, roaming, an overloaded cell tower — requests start to hang, screens show emptiness, and the user either waits or leaves.
Integrating AppSync into a mobile application is built precisely around making this gap invisible to the person.
Offline mode with a local database flips the scheme: the application writes to and reads from storage on the device, and the network becomes a background synchronizer.
The user works instantly, regardless of connection quality, and changes go to the server when the channel appears. The price is a synchronization layer that must be designed: an operation queue, retries, and the order of application.
Data conflict resolution is the main area where these two approaches diverge in complexity. With direct requests to the server, there is almost no conflict: the server is the single source of truth, and the last write wins.
In offline mode, two devices could have edited the same record while they were without a network, and during synchronization it is necessary to decide whose version is correct: by modification time, by record version, or through field merging.
The difference in traffic volume is also noticeable. The direct model pulls data every time a screen is opened — on a weak channel this is noticeable both in speed and in consumption.
The local database serves data from storage, and only changes go over the network — delta synchronization instead of a full fetch.
The choice is not binary: usually a local database is used for user data and direct requests for what must be fresh — statuses, payment, access rights.
Case study: how we eliminated data desynchronization in an app with thousands of active users
The client is a delivery service with hundreds of couriers per shift and tens of thousands of active users. The app ran on REST requests: order status was pulled by polling every few seconds, and changes from different devices on the same account overwrote each other.
Under peak loads, the share of conflicts reached 18% of all status changes, and after a network loss the client showed stale data until a manual restart — recovery took minutes and required user action.
What we reworked in the synchronization layer
We moved data exchange to a managed GraphQL layer with subscriptions: the client no longer polls the server but receives changes over a channel.
Every mutation goes through a resolver with a record version check — if a device sends data based on a stale state, the server rejects the operation and returns the current version, and the client applies it without user involvement. This eliminated the "last writer wins" class of failures that caused lost edits.
Metrics before and after
| Metric | Before | After |
|---|---|---|
| Share of change conflicts | up to 18% | below 1% |
| Recovery after network loss | minutes, manual | seconds, automatic |
| "Lost edits" class of failures | regular | eliminated |
| Server load from polling | constant | only actual changes |
We also set up an offline queue separately: operations made without a network are stored locally and applied when the connection is restored with a version check. This closed the scenario where a courier entered a status in a basement and lost it.
AWS AppSync provided a managed layer of subscriptions and resolvers without our own real-time infrastructure — the team did not spend time maintaining connection servers.
After the rework, the share of conflicts dropped by more than twenty times, recovery after network loss no longer required user action, and the load from constant polling was replaced by a stream of only actual changes. Describe your task — we will get back to you with an estimate and a work plan.
Technical concerns before the start: compatibility, migration, rollback, access
Before starting AppSync integration, customers most often ask the same question: what will happen to the already running application and its data.
Let us answer specifically — AppSync integration does not require rewriting the client entirely: existing REST calls and the local cache continue to work in parallel, and the GraphQL layer is connected on top, as screens become ready.
Data migration follows the "dual write → reconciliation → read switchover" scheme, so there is a rollback point at every step.
What if a data discrepancy is detected after the switchover?
Rollback of changes is built into the scheme itself: reads return to the previous source with a single configuration change, without an app release. Dual write is retained, so the discrepancy is closed by re-reconciliation rather than manual recovery.
Access and roles are described by separate policies — each environment has its own set of permissions, and production is isolated from test environments.
What if the team has no experience with AppSync? We hand over integration documentation: the type schema, examples of queries and mutations, and a breakdown of role-based authorization. The customer's engineers join the work at the stage of configuring resolvers, not after handover.
What if the authorization provider needs to be changed? The access layer is separated from business logic — changing the provider affects configuration, not the screen code.
Upon handover, the customer receives:
- a data schema and type descriptions with query examples;
- environment configurations with separation of access rights and roles;
- a data migration procedure with reconciliation points and rollback conditions;
- instructions for rolling back changes without an app release;
- integration documentation for the customer's team;
- a set of checks for acceptance — which scenarios to test and how.
Describe your task — we will come back with an estimate and a plan: we will review the current stack, determine the migration order, and lock in rollback points before work begins.
Implementation phase timelines: what happens and in what sequence
The order of phases is arranged so that each subsequent step builds on a verified previous one: first we lock down the data model and access rules, then we bring up synchronization, and only after that do we migrate live screens onto it.
This integration plan allows you to stop at any step and still have a working application, rather than a set of half-finished modules.
Phases 3–5 run with partial overlap: while one engineer handles synchronization, a second prepares the screen migration — this cuts the overall implementation timeline by roughly a third without losing control.
The approximate timelines are given for an application of medium complexity; the analysis is affected by the number of screens and the depth of offline scenarios, so we finalize the plan after the first week of work, when the data schema is already fully visible.
We set aside a separate load and failure phase: it shows how the application behaves when the connection drops and comes back, and it often reveals write conflicts that are not visible on test data.
Based on the results of this phase, changes to the schema are made before handover, not after going into production.
AWS AppSync







