Synchronous vs. queue-based message processing for IoT: when each one wins

Martin Vanco
Martin Vanco
Our work
March 23, 2026
5 min read

The synchronous path: simple, fast to build, and honest about its limits

The inline model is straightforward. A device sends an HTTPS message to your API endpoint. The API authenticates the request, validates the payload, writes raw and normalized data to the database, updates the latest device state, and appends telemetry history. One request, one flow, done.

In AWS terms, this is a relatively small surface: an ALB in front of ECS Fargate running your API, Aurora PostgreSQL as your database, and standard networking and secrets management around it. If you're familiar with building web applications, this looks exactly like building a web application. That's the point.

Why this works well early on

Debugging is linear. A request comes in, you can trace it through auth, validation, and storage in a single service. There's no message bus to inspect, no worker logs to correlate, no queue depth to monitor. When something breaks, you know where to look.

Deployment is simple. One service, one database, one set of infrastructure. Your team doesn't need to understand queue semantics, dead-letter handling, or eventual consistency. For a small fleet sending periodic telemetry, this is more than enough.

Where it starts to hurt

The problems are structural, not incidental. Ingest and processing are tightly coupled, which means API latency and failure rate increase directly with database pressure. If your processing layer gets slow (complex validation, heavy writes, schema changes), your ingestion layer gets slow too. Devices start timing out.

There's no burst buffer. If 500 devices report at the same time (and they will, especially on timed intervals), every message hits your API and database simultaneously. You can't absorb the spike and drain it gradually.

Scaling is blunt. When processing is saturated, you have to scale the full API stack, even though the bottleneck is only the write path. And adding downstream consumers (alerts, analytics, integrations) means changing your API logic every time. Fanout gets messy fast.

The queue-based path: more moving parts, but each one scales independently

The async model splits the work. A device sends a message (HTTPS or MQTT) to an ingestion layer. The ingestion layer authenticates the device, enforces topic policy, and drops the message onto a queue. A separate worker picks up the message, parses it, validates the schema, and writes to the state store and telemetry store. If processing fails, the message goes to a dead-letter queue for replay.

In AWS, this adds IoT Core (with custom authorizers and topic rules), SQS for the main queue and DLQ, and separate ECS Fargate workers for processing. The ingestion layer and the processing layer are now independent services that scale independently.

What you gain

Burst absorption. The queue is the buffer. A thousand devices reporting at once don't hammer your database directly. They fill the queue, and workers drain it at a sustainable rate. Your ingest latency stays stable because the ingestion layer's job is just "accept and enqueue."

Protocol flexibility. With an ingestion layer in front, you're not locked into HTTPS. MQTT support means persistent connections, lower overhead, topic-based routing, and bi-directional communication for things like firmware and configuration updates. Devices publish to devices/{deviceUid} and the platform handles routing from there.

Failure isolation. If a worker crashes or a database migration goes sideways, messages stay in the queue. Nothing is lost. The DLQ catches messages that fail repeatedly, and you can replay them once the issue is fixed. Compare this to the synchronous model, where a processing failure means the device has to retry (assuming it even knows something went wrong).

Independent scaling. Ingestion can handle 10x the traffic without touching the workers. Workers can be tuned for throughput without affecting ingest latency. You scale what's actually saturated, not the entire stack.

Clean fanout. Need to add an alerting system? A real-time dashboard? An integration with an external service? Subscribe to the topic or add a consumer to the queue. The ingestion path doesn't change.

What it costs you

Complexity is real and it's not optional. You now need to think about things that didn't exist in the synchronous model:

Idempotency. At-least-once delivery means a message can arrive twice. Your processing logic needs to handle duplicates gracefully. If a device sends the same status update and your worker writes it twice, is your state still correct?

Ordering and staleness. Messages can arrive out of order. A status message from 10:01 might be processed before one from 10:00. If your state store blindly writes the latest message it receives, you'll overwrite newer data with older data. Timestamp freshness rules are the answer: only update state if the incoming timestamp is newer than what's stored.

Driver-based parsing. Different device types send different payload formats. Different firmware versions of the same device type might send different schemas. You need driver-based parsers that route each message to the right parser based on device type and version. Schema validation per event type keeps garbage data out of your stores.

DLQ management. Dead-letter queues are great until they fill up and nobody looks at them. You need monitoring, alerting on DLQ depth, and a replay mechanism. A DLQ without a process around it is just a place where problems go to hide.

How to decide

The honest answer is that this is a staging decision, not a philosophical one.

If you're validating a product concept with a fleet of 50–200 devices, go synchronous. You'll move faster, debug easier, and spend less on infrastructure. The limitations won't bite you yet. Your biggest risk at this stage is building the wrong product, not building the wrong architecture.

If you're heading toward production with a growing fleet, multiple device types, or any need for downstream consumers beyond a backoffice dashboard, go queue-based. The upfront investment in queue semantics, idempotency, and worker design pays for itself the first time you survive a burst without downtime or data loss.

The worst outcome is building the queue-based system when you only need the synchronous one. You'll spend weeks on infrastructure that doesn't make your product better. The second worst is staying on the synchronous path too long and discovering its limits during a production incident at 2 AM.

The migration path matters

One thing worth planning for: if you start synchronous, design your API so that the ingest endpoint is a clean surface. Don't scatter validation and business logic across controllers. Keep the "accept message, validate, process, store" flow in a single pipeline that you can later split at the "process" step by dropping a queue in between.

The teams that have the hardest time migrating are the ones that interleaved ingest and processing so deeply that there's no clean cut point. The teams that migrate cleanly are the ones that wrote their synchronous path as if the queue was coming eventually. Because it usually is.

This article is based on a real IoT platform architecture designed for connected field devices with periodic and event-driven telemetry, including asset trackers, telematics units, and industrial monitoring nodes. The patterns described here have been tested across both small-fleet validation and production-scale deployments.

Martin Vanco

Read what's next

Synchronous vs. queue-based message processing for IoT: when each one wins

A practical breakdown of two IoT platform architectures, the trade-offs nobody warns you about, and how to pick the right one for your stage.

MQTT and HTTPS in the same IoT platform: when to use which and why

A developer's guide to running both protocols side by side, with practical patterns for auth, topic design, and the flows that make each one the right choice.

The Hidden 80% Tax on Every Company That Builds Native for iOS and Android

The real business case for Flutter, including when it is not the right call. Two native apps means double the cost, double the coordination, and double the bugs. Here is the math most companies skip.