When Your Events Arrive Like a Firehose: Streaming Architectures That Don’t Lose (or Lie About) Data
Designing high-velocity streaming systems is less about picking Kafka vs Kinesis and more about building reliability and quality guarantees you can explain to customers, auditors, and investors.
High-velocity streaming isn’t hard because Kafka is hard. It’s hard because reliability and truth are hard—and your business runs on both.Back to all posts
The incident you don’t forget: “Why are dashboards flat?”
I’ve watched teams ship a shiny streaming pipeline, celebrate “real-time analytics,” and then get blindsided two weeks later: dashboards go flat during peak traffic, nobody gets paged, and the VP Sales is making decisions off stale numbers. The postmortem usually reads the same:
- A high-velocity source (mobile events, CDC from Postgres, or payment webhooks) spiked.
- Consumers fell behind (lag climbed), then someone “fixed it” by redeploying.
- Offsets got reset, duplicates poured in, and the data team quietly patched numbers in a notebook.
High-velocity streaming is easy to demo and brutally hard to operate. The goal isn’t “streaming.” The goal is reliable, high-quality data that drives business decisions—with behavior you can predict under load and during failure.
A reference architecture that survives high velocity
At high level, you want a design that assumes things will break and makes recovery boring:
- Producers: apps, devices, CDC (
Debezium), third-party webhooks - Durable log:
Kafka/Kinesis(this is your replay buffer) - Stream processing:
Flink/Spark Structured Streaming(state + windows + enrichment) - Quality gates: schema registry + contract checks + quarantine/DLQ
- Sinks: lakehouse (
Iceberg/Delta Lake/Hudi) + OLAP (ClickHouse/Druid) when needed - Observability: lag, throughput, drop/duplicate rates, DLQ volume, freshness SLOs
The key design choice is treating the log as the system of record for events (within a retention window). That replay capability is what turns “we lost data” into “we’re behind, but recoverable.”
Here’s a concrete Kafka topic setup that’s friendly to high velocity and recovery (tune to your workload, but don’t leave this to defaults):
# Example: create a topic for high-velocity clickstream
kafka-topics \
--bootstrap-server kafka-1:9092 \
--create \
--topic clickstream.v1 \
--partitions 48 \
--replication-factor 3 \
--config retention.ms=604800000 \
--config min.insync.replicas=2 \
--config cleanup.policy=deleteMeasurable outcome targets (what “good” looks like):
- P95 end-to-end freshness < 60s during peak (or whatever the business needs)
- Data loss rate = 0 (enforced by design + monitoring)
- Duplicate rate < 0.1% (and decreasing)
- MTTR for pipeline incidents < 30 minutes with replay-based recovery
Reliability mechanics: backpressure, replay, idempotency (the holy trinity)
Most streaming failures aren’t exotic. They’re basic physics: producers can outpace consumers, and sinks slow down at the worst time.
1) Backpressure is a feature, not a bug
If your sink (S3, Iceberg, warehouse) slows down, your processing layer must apply backpressure instead of dropping messages or OOM-ing.
In Flink, you can design for this by keeping state bounded (windows + TTL), and by sizing parallelism with realistic sink throughput.
2) Replay changes the whole game
Set log retention based on how long it realistically takes you to detect and fix issues.
- If your on-call coverage is thin, 7–14 days retention is often cheaper than rebuilding trust after a silent drop.
- For regulated domains, retention also supports auditability (within reason—talk to legal).
3) Idempotency prevents “double counting” during recovery
Exactly-once is a loaded term. In practice, you want effectively-once outcomes:
- Every event has an immutable
event_id - Your sink supports upsert/merge keyed by
event_id(or a deterministic composite key) - Your processing job can be restarted without changing the final result
Example: Spark Structured Streaming with watermarking + dedup (common for mobile events):
from pyspark.sql.functions import col
events = (
spark.readStream.format("kafka")
.option("kafka.bootstrap.servers", "kafka:9092")
.option("subscribe", "clickstream.v1")
.load()
)
parsed = events.selectExpr("CAST(value AS STRING) as json")
# assume you parse into columns: event_id, event_time, user_id, ...
deduped = (
parsed
.withWatermark("event_time", "10 minutes")
.dropDuplicates(["event_id"])
)
# write to a sink that supports upserts/merge (e.g., Delta)
(
deduped.writeStream
.format("delta")
.option("checkpointLocation", "s3://checkpoints/clickstream/")
.outputMode("append")
.start("s3://lake/clickstream/")
)If you can’t do deterministic dedup, you don’t have a streaming system—you have a duplicate generator with a dashboard.
Quality at speed: schemas, contracts, and quarantine lanes
“Data quality” usually gets hand-waved until the CFO asks why revenue numbers changed.
Plain-English definition: data quality is whether the data is fit for its intended use (correct types, valid ranges, consistent meaning). In streaming, quality problems multiply because bad events arrive continuously.
Schema registry + compatibility rules
Don’t let producers ship whatever JSON they feel like today. Use Schema Registry (Avro/Protobuf/JSON Schema) and enforce compatibility.
Example: Confluent Schema Registry compatibility config (backward is a common default):
curl -X PUT \
-H "Content-Type: application/vnd.schemaregistry.v1+json" \
--data '{"compatibility":"BACKWARD"}' \
http://schema-registry:8081/config/clickstream-valueData contracts that engineering can actually follow
A practical contract includes:
- Required fields:
event_id,event_time,producer,schema_version - Allowed nullability + ranges (e.g.,
price >= 0) - Semantic meaning (what does
user_idrepresent? hashed? stable?)
Enforce contracts in CI for producers (yes, even if the producer is a “quick” microservice). If your events are partially AI-generated code, I’ve seen schemas drift weekly because nobody owns the contract.
Quarantine / DLQ to keep the pipeline flowing
If you hard-fail on every malformed event, you’ll page yourself into oblivion. If you accept everything, you’ll corrupt downstream truth.
Use a DLQ (dead-letter queue) or quarantine table:
- Route invalid events (schema violation, impossible timestamps) to
clickstream.dlq - Alert on DLQ rate spikes
- Build a fast remediation loop (top error types, top producers)
Measurable outcomes:
- DLQ rate < 0.5% steady-state
- Time-to-fix for top DLQ cause < 1 day
- Schema break incidents drop to near-zero after compatibility enforcement
Serving business value: stop overbuilding, start landing trustworthy data
A common failure mode is building a complicated real-time serving layer before you’ve stabilized ingestion + quality. The fastest path to business value for many teams is:
- Land curated streaming data into a lakehouse table (
Iceberg/Delta/Hudi). - Provide “near real-time” analytics via
Trino,Databricks SQL, orSnowflakeexternal tables. - Add OLAP (
ClickHouse,Druid) only when you can prove query latency or concurrency needs.
Example: Flink SQL writing to an Iceberg table (common pattern for scalable append + compaction workflows):
CREATE TABLE clickstream_iceberg (
event_id STRING,
event_time TIMESTAMP(3),
user_id STRING,
page STRING,
producer STRING,
schema_version STRING,
WATERMARK FOR event_time AS event_time - INTERVAL '5' SECOND
) WITH (
'connector'='iceberg',
'catalog-name'='prod',
'catalog-type'='hadoop',
'warehouse'='s3://lake/warehouse',
'format-version'='2'
);Business framing that founders and execs care about:
- Faster feedback loops: product experiments measured in minutes, not days
- Lower churn risk: fewer “your numbers are wrong” customer escalations
- Lower diligence risk: you can explain lineage, retention, and recovery to investors
- Lower hiring drag: new data engineers aren’t spending their first month firefighting
Operability: define data SLOs, then page the right humans
Plain-English definition: an SLO (Service Level Objective) is a measurable reliability target. For data, it’s usually about freshness and completeness—not “the job ran.”
Data SLO examples that work in the real world:
- Freshness SLO: P95
event_time → available_in_table< 120 seconds - Completeness SLO: ≥ 99.9% of expected partitions arrive within 15 minutes
- Correctness SLO: ≥ 99.99% events conform to schema (DLQ < 0.01%)
Instrument the pipeline end-to-end:
- Broker: partition throughput, ISR count (
min.insync.replicasviolations) - Consumers: consumer lag by group + partition
- Processor: checkpoint duration, backpressure time, restart count
- Sink: commit latency, small-file count (lakehouse), compaction backlog
Example: Prometheus alert on Kafka consumer lag (illustrative):
groups:
- name: streaming-alerts
rules:
- alert: KafkaConsumerLagHigh
expr: kafka_consumergroup_lag{group="clickstream-processor"} > 500000
for: 10m
labels:
severity: page
annotations:
summary: "High consumer lag for clickstream-processor"
description: "Lag > 500k messages for 10m. Risk of freshness SLO breach."This is where teams win or lose credibility: not whether the pipeline is “real-time,” but whether it’s predictable and explainable when it isn’t.
The pragmatic build plan (and where GitPlumbers fits)
Here’s what actually works when you’re moving fast and don’t want to rebuild later:
- Stabilize ingestion: durable log, retention, partitioning, producer acks, DLQ.
- Make correctness cheap: schema registry + compatibility + producer CI checks.
- Design for replay: idempotent processing + deterministic keys + checkpointing.
- Deliver value early: land to lakehouse + a small set of trusted metrics.
- Operationalize: data SLOs + alerting + on-call runbooks + game days.
If your current streaming stack is a mix of legacy glue plus AI-assisted “looks right” code, don’t guess where the cracks are.
- Run GitPlumbers Automated Insights (GitHub-integrated analysis) to quickly surface structural risks: unsafe retry loops, missing idempotency, schema drift hazards, insecure connectors, and observability gaps.
- Or book a GitPlumbers code audit for a deeper read: we’ll map your event contracts, replay story, state management, and failure modes—and give you a remediation plan with ROI (what to fix now vs later).
- If you need to execute fast without a risky hiring spree, we can assemble a fractional team for remediation (streaming + platform + data modeling specialists) matched to what the audit finds.
The honest truth: high-velocity streaming isn’t “set and forget.” But with replay, idempotency, and contracts, it becomes “boring and bankable.”
Key takeaways
- High-velocity streaming systems fail in predictable ways: silent drops, duplicates, schema drift, and unbounded lag—design for replay, idempotency, and backpressure from day one.
- Reliability isn’t a vibe: define **data SLOs** (freshness, completeness, correctness) and instrument lag, drop rate, and DLQ volume like you would error budgets.
- Quality at speed requires **contracts** (schemas + compatibility), **quarantine paths** (DLQ), and **deterministic dedup** (event keys + watermarks).
- A lakehouse sink (Iceberg/Delta/Hudi) with partitioning + compaction is the fastest route to analytics value without building a bespoke serving layer too early.
- If your pipeline was stitched together from “helpful” AI suggestions, run GitPlumbers **Automated Insights** or book a **code audit** before it becomes investor diligence material.
Implementation checklist
- Define data SLOs: freshness (P95), completeness (%), correctness (schema conformance).
- Choose an ingestion contract: `event_id`, `event_time`, `producer`, `schema_version`, and a partition key you can defend.
- Enable replay: durable log (Kafka/Kinesis), retention aligned to incident response (>= 7 days for most teams).
- Implement idempotent processing: dedup keys + sink upserts/merge semantics.
- Add a DLQ/quarantine path and alert on it (volume + top error types).
- Enforce schema compatibility in CI/CD (backward/forward rules).
- Instrument lag and throughput end-to-end; set paging thresholds tied to SLO breach risk.
- Load-test peak velocity and failure modes (broker restart, consumer rebalance, sink slowdown).
Questions we hear from teams
- Kafka vs Kinesis: which is better for high-velocity streaming?
- Either can work. The deciding factors are operational model and ecosystem. Kafka often wins when you need rich consumer patterns, on-prem/hybrid flexibility, and mature tooling (Schema Registry, Connect, Flink). Kinesis can be great when you want a managed AWS-native path. In both cases, reliability comes from replay retention, idempotent processing, and observability—not the logo on the box.
- Do I really need a schema registry if we use JSON?
- Yes, if you care about quality and stability. JSON without enforced schemas is how you get silent breaking changes (“string” becomes “object”), dashboard drift, and incident archaeology. A registry + compatibility rules turns schema evolution into an intentional process.
- What’s the minimum viable set of guarantees for a startup?
- Replay (retention + offsets), idempotency (event IDs + dedup/upserts), and a DLQ/quarantine path. Add data SLOs and lag alerting before you scale usage of the metrics across the company.
- How do we know if we should fix our pipeline or rebuild it?
- Fix when the core model (events + contracts + sinks) is sound but implementation is brittle (missing idempotency, poor alerting, bad partitioning). Rebuild when you can’t define a stable event contract, you can’t replay, or you’re locked into a sink/processor that can’t meet SLOs without heroic effort. A code audit makes this decision explicit and defensible.
Ready to modernize your codebase?
Let GitPlumbers help you transform AI-generated chaos into clean, scalable applications.
