From IoT Core to AppSync
Re-platforming real-time audience interaction for scale
Options on the table
Background
Nova Dynamic Media runs large-scale enterprise live events — webcasts, conferences and hybrid productions where the audience isn't just watching, they're participating. During a session, attendees submit and upvote questions, answer live polls and quizzes, respond to surveys, and follow slides that advance in lockstep with the speaker, all on top of an ultra-low-latency Amazon IVS video stream.
Every one of those interactions has to reach thousands of devices in near real time, and stay correct when someone's phone drops off the venue Wi-Fi and reconnects. The original platform delivered this real-time layer with AWS IoT Core over MQTT — a Pub/Sub design borrowed from the IoT world. It worked at small scale, but as audiences and feature complexity grew, the seams started to show.
Why IoT Core fell short
IoT Core is purpose-built for device telemetry — millions of sensors publishing small messages. A user-facing, interactive event is a different problem, and three mismatches kept surfacing:
- Heavy client connections: Web browsers had to hold an MQTT client connection open. On mobile and flaky venue networks that meant extra weight, reconnection edge cases, and battery/perf overhead just to receive a poll update.
- No schema, hand-rolled everything: Raw Pub/Sub has no strict data contract, so the team manually handled serialization, payload validation and parsing on both the backend and every client — error-prone boilerplate that grew with each new feature.
- Delivery without state: MQTT delivers a message but stores nothing. To show the current poll result or Q&A thread to someone who just refreshed, the app had to separately query DynamoDB — racing the live Pub/Sub stream against the state database and occasionally showing stale or missed data.
Why AppSync
AWS AppSync collapses the real-time problem and the state problem into one managed GraphQL backend, which is exactly the shape of this domain.
Subscriptions ride standard WebSockets under the hood — no MQTT wrapper for the browser to manage. A single strongly-typed schema unifies mutations (writing data), queries (fetching current state) and subscriptions (listening for live updates), so validation and parsing come for free. And AppSync resolves directly against DynamoDB and Lambda, so one operation can persist a change and broadcast it to every connected attendee in a single round-trip.
We considered building our own WebSocket layer, but that meant owning scaling, auth, reconnection and schema ourselves — undifferentiated heavy lifting AppSync already does well.
How we re-engineered the pipeline
The migration turned a fragmented messaging system into one strongly-typed real-time data graph, with DynamoDB as the single source of truth and AppSync owning the WebSocket fan-out. Design goals: keep the hot path resolver-only (no Lambda latency), scope every broadcast to its event, and make state idempotent so reconnects and retries can't corrupt it.
- Schema & subscription filtering: One typed schema models the domain — mutations (publishPoll, advanceSlide, submitQuestion, upvote), queries (getEventState), and subscriptions (onEventUpdate(eventId)). AppSync enhanced subscription filters bind each subscription to its eventId, so a mutation fans out only to that event's devices instead of every connected client.
- Resolver-only hot path: Most operations use AppSync resolvers (VTL/JS) mapped straight to DynamoDB — no Lambda in the critical path, removing cold-start and invocation latency. Multi-step flows use pipeline resolvers; Lambda data sources are reserved for genuinely complex cases.
- DynamoDB single-table design: A single table with composite keys (PK EVENT#{id}, SK POLL#/QUESTION#/SLIDE#) serves every access pattern, with GSIs for upvote-ordered question feeds. Atomic counters (ADD) handle upvotes; conditional writes give optimistic concurrency so duplicate or out-of-order mutations can't clobber state.
- Mutation→subscription loop: A slide change is one mutation: the resolver persists new state to DynamoDB and AppSync broadcasts the result to every subscribed device in fractions of a second — one round-trip, no separate publish step, no drift between what's stored and what's shown.
- Grounded state on reconnect: Because the store sits directly behind the graph, a device back from a network drop just runs getEventState and gets the canonical current state — no fragile replay of a Pub/Sub stream it missed offline.
- Tiered auth & Mission Control: Cognito authorizes moderators with field-level rules (only staff can publish/advance); attendees subscribe under a read-only tier. Mission Control is the single surface issuing those privileged mutations — live upvoting, nested replies, and the speaker teleprompter channel, all kept in step with the ultra-low-latency Amazon IVS stream.
The impact
Across 50+ enterprise live events, the AppSync architecture delivered wins that were both technical and felt by the business:
- High-concurrency scale: Absorbed massive spikes when thousands of attendees voted or asked questions at the same moment during peak sessions.
- Correctness by design: With DynamoDB behind the GraphQL interface, state is always grounded — a brief disconnect no longer means lost or stale data; the client just re-fetches the truth.
- Perfect sync: Slides, live captions and engagement elements stay locked to the ultra-low-latency IVS stream, so what the audience sees and what they interact with never drift apart.
- Developer velocity: Built-in resolvers and schema validation removed a layer of backend boilerplate, so the team shipped new event features faster.
