Architecture kata · K—01
Cold-Chain Telemetry and Compliance Platform
Architecture kata · design study on a fictional brief
- Sector
- Pharmaceutical logistics
- Topic
- Reliable data
- Technologies
- EMQX, Apache Kafka, Apache Flink, ClickHouse, Apache Iceberg, Ceph, PostGIS, immudb, OpenBao, Apache APISIX, MapLibre, Trino

In short
20,000 refrigerated vehicles, 72-hour offline gaps, and GDP reports that an auditor can verify instead of having to trust.
Problem
A European pharmaceutical logistics provider runs about 20,000 refrigerated vehicles and 300 depots across 25 countries, moving vaccines and biologics under Good Distribution Practice (GDP). Each vehicle reports position, two temperature probes, door status and humidity every 10 seconds. Three device vendors each have their own data format and portal. Dispatchers switch between three portals. Excursions are usually found after delivery, and each shipment’s compliance report is assembled by hand. Vehicles lose connectivity for up to 72 hours and later upload a backlog. Nothing may be lost, and a report must be verifiable ten years later.
Approach
One tension shapes the design. Vehicles keep losing connectivity, yet no measurement may be lost. An auditor must also be able to confirm the record is complete and unaltered. The loss problem is solved on the device. The proof problem is solved in storage. The device keeps every measurement until the broker acknowledges it, and tags each one with its own timestamp and an increasing sequence number. Everything else builds on that sequence number.
Architecture
- Ingestion: EMQX for MQTT devices and an HTTPS batch API for the vendor that only offers exports. A downlink topic lets the platform ask a device to resend a sequence range.
- Backbone: Kafka with Avro contracts. Reference data from the transport system arrives by Debezium.
- Processing: Flink deduplicates by device and sequence, detects gaps in real time, evaluates excursion and geofence rules, and accepts late data for 72 hours.
- Stores by access pattern: ClickHouse for 90 days of hot analytics. Iceberg on Ceph with Object Lock for the ten-year raw record. PostgreSQL with PostGIS for operational state.
- Proof: each report is generated from raw data, hashed, signed with OpenBao, and its digest appended to an immudb ledger.
- Access: APISIX and Keycloak for dispatchers and customers, MapLibre for the live map.

Why these components
| Need | Choice | Why |
|---|---|---|
| Device ingest | EMQX with MQTT 5 | MQTT fits devices: tens of thousands of persistent connections, small messages, QoS 1 acknowledgements and sessions for offline clients. EMQX clusters and checks an X.509 certificate per device. Mosquitto and HiveMQ CE do not cluster. |
| Bridge from broker to log | EMQX Kafka data integration | Writes to Kafka with acks=all and acknowledges the device only after Kafka confirms, so no MQTT offset can be lost. An Enterprise feature; the open alternative is Kafka Connect MQTT source, at least once. |
| Sources without MQTT | Ingestion API, HTTPS batch with an idempotency key | The third vendor only offers portal exports. A second door into the same raw topic, where the idempotency key makes a resend harmless. |
| Event backbone | Apache Kafka with KRaft on Strimzi | An MQTT broker is a distribution point, not a durable log. Kafka gives a replayable log ordered by device, and three replicas with acks=all prevent loss. Redpanda was ruled out over its Business Source License. |
| Schema contract | Apicurio Registry with Avro | A versioned contract for three vendor formats and future ones. Incompatible changes fail in CI, not in production. Confluent Schema Registry was ruled out because of its Community License. |
| Transport reference data | Debezium change data capture | Reads the shipment, vehicle and driver mapping from the transport system’s log, with no query load and no change to that system. Flink uses it as broadcast state. |
| Stream processing | Apache Flink with the Kubernetes operator | Event time, watermarks, 72 hours of allowed lateness, stateful joins and rules in one place. Deduplication uses the monotonic sequence: the last number plus a small gap set per device, so 20,000 keys, not billions. |
| Hot analytics | ClickHouse with the Altinity operator | Sub-second time series queries over 90 days, with idempotent writes through ReplacingMergeTree on device and sequence. TimescaleDB was ruled out: its compression and continuous aggregates sit under the Timescale License. |
| Evidence archive | Apache Iceberg on Ceph RGW with Rook | Cheap object storage for ten years of raw data, an open table format with ACID snapshots, and write-once retention through S3 Object Lock. Ceph, because the MinIO community edition narrowed in 2025. |
| Operational state | PostgreSQL with PostGIS on CloudNativePG | Shipments, geofences, alert state and last position: relational and geographic queries in one place. A boring and correct choice for small, hot, often-updated data. |
| Cache and pub/sub | Valkey | Redis-compatible and BSD-licensed under the Linux Foundation, for the live position cache and WebSocket fan-out. Redis 8 returned to AGPL, which would also be acceptable; Valkey stays out of the licence debate. |
| Immutable ledger | immudb | Report digest, signature, Iceberg snapshot ID and report version go into an append-only ledger. Merkle proofs let an auditor verify independently that nothing changed. Codenotary offers enterprise support. |
| Secrets and keys | OpenBao | Device PKI, a tokenisation key per driver and transit signing. The Linux Foundation fork under MPL 2.0, since HashiCorp Vault moved to the Business Source License. |
| API gateway | Apache APISIX | OAuth2 validation, quotas per customer, versioning and WebSocket proxying. APISIX under the Apache licence, because Kong OSS moved enterprise features into its closed edition. |
| Identity | Keycloak | OpenID Connect federated to the corporate identity provider for staff, and client credentials for business customers. Support comes through the Red Hat build of Keycloak. |
| Map and UI | React, MapLibre GL and Martin vector tiles | An own tile server means no fee per map call. MapLibre is the BSD-licensed fork made after Mapbox GL v2 went closed. Enough for 500 dispatchers. |
| Analytical access | Trino, Apache Superset, JupyterHub | Data scientists query Iceberg with SQL through Trino, dashboards run in Superset and notebooks in JupyterHub. The evidence archive doubles as the analytics source, so no copy is needed. |
| Platform | Kubernetes or OpenShift, Argo CD, cert-manager, step-ca, Cilium | Every workload is operator-managed, and GitOps makes the setup reproducible. step-ca issues device certificates; Cilium enforces mTLS and network policy. |
| Observability | OpenTelemetry, Prometheus, Grafana, Loki, Tempo | Device liveness, consumer lag, checkpoint health, alert delay from device time to delivery, and report backlog in one stack. |
Decisions and trade-offs
- Acknowledge the device only after Kafka confirms. With EMQX Kafka integration, no MQTT offset can be lost, and the device keeps each sample until then. A sequence gap triggers a “resend range” command on the downlink topic. The cost is the EMQX Enterprise licence, a named exception to the open-source rule. Without that budget, I would use Kafka Connect MQTT source: at least once, with the offset in the connector.
- At-least-once plus idempotent writes. The chain is QoS 1 and sequence numbers on the device, acknowledgement after Kafka, Flink checkpoints for state and offsets, and two-phase commit or idempotent upserts in the stores. The result is effectively once, with deduplication at read. It is not a claim of exactly-once. The cost: dashboards must query ClickHouse with FINAL or argMax.
- Two stores by access pattern. A dispatcher needs sub-second queries over 90 days. An auditor needs untouched raw data ten years later. ClickHouse is hot and fast; Iceberg is cheap and locked. The raw layer is the source of truth, and ClickHouse can be rebuilt from it. The cost is running two stores.
- Verify, do not trust. Three layers: Object Lock makes raw objects undeletable, OpenBao signs the report digest, and immudb records it with a Merkle proof. The auditor hashes the PDF again and asks for the proof. The cost: no compaction under Object Lock, so file size is controlled through a 10-minute checkpoint interval.
- Driver privacy versus ten-year evidence. Telemetry belongs to the vehicle. The driver-to-vehicle mapping is the only personal data, tokenised with a per-driver key. Erasure deletes the mapping and destroys the key, so every copy of the token becomes meaningless and the evidence stays intact. The cost: driver monitoring is employee data and needs works council consultation before go-live.
- Late data creates a new report version. A late sample goes through the same rules and is flagged as retrospective: no SMS, but the quality manager is notified and the product is quarantined. Report version 2 is issued, and the digest of version 1 stays in the ledger. The cost is that customers receive report revisions.

What I would revisit
- Real device capabilities. Can every vendor’s device buffer for 72 hours, load certificates and honour a resend command? If not, late data comes only from portal exports, which also decides whether the third vendor stays on the batch API.
- EMQX licence budget. If there is none, the bridge becomes Kafka Connect MQTT source, at least once.
- Retention mode. Is ten years the same in every country, and will legal accept compliance mode, where nobody can delete, or only governance mode?
- Transport system access. If its database is not open to change data capture, reference data comes from API polling, with a freshness cost.
Outcome notes (design assumptions, not measurements)
This is a design exercise, not a deployed platform. The output is a four-page diagram set, a one-page whiteboard version and an 18-page Turkish PDF with capacity arithmetic and open questions. The alert latency estimate (about 8–10 seconds against a 30-second target) is a design estimate.
Sources
- Architecture PDF (Turkish, 18 pages).
- draw.io diagrams (English labels): four-page set and a one-page 30-minute version.
Real projects on this topic
P—01
Completed
Featured
Enterprise Data Warehouse Migration to Google BigQuery
80 TB warehouse moved from SQL Server and a 15-node Cloudera cluster to BigQuery: $550K annual savings, 75% lower infrastructure cost, 7x faster workflows.
Role
Design and lead · Senior Data Engineering & Data Architecture Group Manager
P—08
Completed
Data Governance, Metadata Management and MDM
Data governance, metadata management and master data management (MDM) across 100+ source systems, with data catalog and lineage tooling.
Role
Implementation · Big Data Management & Data Architecture Manager