Flink's Role in Real-Time Data Warehouses: Handling Late Data, Exactly-Once & Recovery
This article details how to build a production-ready real-time data warehouse using Flink, covering CDC ingestion, event-time processing, dimension joins, upsert aggregation, state TTL, checkpoint tuning, exactly-once verification, and a pre-launch checklist for reliability.
Introduction: Flink as the Continuous Computation Hub
A reliable real-time data warehouse is judged not by sub-second dashboard refreshes but by whether data remains correct after late arrivals, duplicates, failures, and business rollbacks. Kafka handles ingestion and buffering, the lakehouse/OLAP stores and serves results, and Flink transforms continuous business changes into correctable, recoverable, reusable data products.
1. Define Targets and Calibers Before Choosing Flink
For an order operations dashboard showing per-minute GMV, paid orders, refund amount, store ranking, and channel conversion, the product requirement "see changes within 10 seconds" is not yet actionable. Five concrete engineering goals must be set:
Payment events update the dashboard within 10 seconds after entering the message system.
Allow 30 seconds of event-time disorder; later events go to a late-data process.
Refunds and order-status rollbacks must update already-emitted minute-level metrics.
Job failures recover from the latest successful checkpoint without double-counting amounts.
Daily reconciliation against offline or lakehouse detail data.
These goals directly drive technical choices: event time + watermark for disorder, upsert-capable sinks for corrections, replayable sources + state recovery + idempotent writes for exactly-once, and raw detail retention for reconciliation.
A real-time data warehouse does not pursue "all data forever correct on first pass"; it designs separate mechanisms for first output, late correction, and eventual consistency.
2. Building an Order Real-Time Data Warehouse Pipeline
Step 1: Ingest Changes Without Premature Cleansing
Orders, payments, and refunds originate from business databases; capture inserts, updates, deletes via CDC into Kafka ODS. Preserve raw changelog, business primary key, event time, database timestamp, source table, and operation type. This enables replay when Flink jobs fail and re-computation when business definitions change without re-reading production databases.
CREATE TABLE ods_order_cdc (
order_id STRING,
user_id STRING,
shop_id STRING,
status STRING,
amount DECIMAL(18, 2),
event_time TIMESTAMP_LTZ(3),
update_time TIMESTAMP_LTZ(3),
PRIMARY KEY (order_id) NOT ENFORCED,
WATERMARK FOR event_time AS event_time - INTERVAL '30' SECOND
) WITH (
'connector' = 'kafka',
'topic' = 'ods_order_cdc',
'properties.bootstrap.servers' = 'kafka:9092',
'value.format' = 'debezium-json',
'scan.startup.mode' = 'group-offsets'
);Do not keep only "latest order state"; the original changelog (insert, before-update, after-update, delete) is the basis for later correction and replay. ODS aims for completeness and traceability; business cleansing moves to the next layer.
Step 2: Standardize Caliber and Primary Keys at DWD
DWD produces a reusable business fact table. For paid orders: normalize status, validate amounts, filter test orders, deduplicate by business primary key, unify time fields, and divert dirty data. Example using ROW_NUMBER to keep latest version per order:
CREATE VIEW dwd_paid_order AS
SELECT order_id, user_id, shop_id, amount, event_time, update_time
FROM (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY order_id
ORDER BY update_time DESC
) AS rn
FROM ods_order_cdc
WHERE status = 'PAID'
AND amount > 0
)
WHERE rn = 1;Key pitfall: confusing "message deduplication" with "business state update". Duplicate delivery of the same message should be eliminated, but a genuine transition from paid to refunded must propagate as an update or retraction. Dirty data should not be dropped silently; write to an error topic/quality table with original content, failed rule, job version, and detection time.
Step 3: Handle Disorder and Late Data with Event Time
Orders generated at 10:00 may arrive later due to mobile networks, backlogs, upstream retries. Processing-time windows yield different results on replay. Set watermark = event_time - 30 seconds, meaning the system tolerates common disorder up to 30 seconds. This value should come from latency distribution analysis, not guesswork.
Pre-launch: collect 7-day arrival-delay percentiles. If 99.9% of payments arrive within 25 seconds, 30 seconds is a reasonable initial watermark. Extending to 10 minutes for rare outliers would degrade dashboard freshness.
Late events (past watermark) need explicit handling: (1) correct old results via upsert sink, (2) write to side output and trigger补算 (recalculation), or (3) drop and record metrics if business permits. Default silent dropping causes long-term metric drift.
Step 4: Dimension Joins Must Reflect "State at Event Time"
Order facts often join store, region, product, channel dimensions. Per-event database lookups become bottlenecks. Strategies:
Low-frequency small dimensions: cache or broadcast state.
Larger online dimensions: async lookup with timeout, capacity, fallback.
Historical-caliber dimensions: build versioned tables and use Temporal Join.
CREATE VIEW dwd_paid_order_dim AS
SELECT
o.order_id,
o.shop_id,
d.region_name,
o.amount,
o.event_time
FROM dwd_paid_order AS o
LEFT JOIN dim_shop FOR SYSTEM_TIME AS OF o.event_time AS d
ON o.shop_id = d.shop_id;Business must decide: join "store region at order time" (needs versioned table + event time) or "current latest region" (latest snapshot). Technology follows caliber, not vice versa.
Step 5: Aggregation Results Must Upsert by Primary Key
With standardized details, compute per-store per-minute payment count and amount using Flink SQL window TVF:
INSERT INTO ads_shop_minute
SELECT
window_start,
window_end,
shop_id,
COUNT(*) AS pay_order_cnt,
SUM(amount) AS pay_amount
FROM TABLE(
TUMBLE(
TABLE dwd_paid_order_dim,
DESCRIPTOR(event_time),
INTERVAL '1' MINUTE
)
)
GROUP BY window_start, window_end, shop_id;Downstream table should use (window_start, shop_id) as composite primary key with an upsert-capable connector/storage. Append-only sinks produce duplicate rows on late events, refunds, or replay. DWS/ADS layering should aim for reuse and caliber governance, not blindly replicate all offline layers.
3. Flink Core Capabilities to Production Configuration
Capability 1: State Is Governed Data, Not Just Cache
Deduplication, windows, joins, TopN all generate state. Unbounded state turns the job into an ever-growing database. State TTL must exceed max disorder + max retry + compensation window. Example: orders late up to 30 min, upstream retry up to 10 min → TTL ≥ 40 min + safety margin. Too short loses events; too long increases checkpoint/recovery cost.
Capability 2: Checkpoint Is a Recovery Mechanism, Not a Decorative Switch
Baseline test-environment parameters (not universal optima):
SET 'execution.checkpointing.interval' = '60 s';
SET 'execution.checkpointing.timeout' = '10 min';
SET 'execution.checkpointing.min-pause' = '20 s';
SET 'table.exec.state.ttl' = '2 h';Monitor success rate, end-to-end duration, alignment time, persisted data volume, failure causes. If checkpoint duration approaches interval, don't just increase timeout; investigate backpressure, hot keys, state growth, remote storage jitter. For upgrades: assign stable UIDs to stateful operators, generate savepoint before changes, verify restore compatibility when types, state schema, parallelism, or topology change.
Capability 3: Exactly-Once Requires End-to-End Verification
Flink's exactly-once guarantees each event affects state once on recovery, but end-to-end no-duplicate/no-loss needs replayable sources and transactional/idempotent sinks. Practical verification: continuously write test orders with unique IDs, kill TaskManager mid-aggregation, wait for recovery, then compare input primary key count, DWD primary key count, and ADS aggregated amount. If sink lacks transactions, design deterministic primary keys and idempotent upserts. Irreversible side effects (external API calls, SMS) must not be placed in regular streaming operators as if they were database writes.
4. Ecosystem Selection and Reusable Scenarios
A Common (Not Exclusive) Stack
Ingestion: Flink CDC for MySQL/PostgreSQL changes; tracking/logs to Kafka or Pulsar.
Compute: Flink SQL primary; complex state logic, async access, custom rules via DataStream API.
Storage: Details/intermediates to Paimon/Iceberg/Hudi lake tables; high-concurrency query results to Doris/StarRocks/ClickHouse.
Deployment: Kubernetes or YARN; monitoring via Prometheus/Grafana; add logging, alerting, lineage, quality, release tooling.
Consumers: Operations dashboards, data APIs, user profiles, recommendation features, risk rules, real-time alerts.
Selection criteria: verify three things — change semantics flow through, failures can replay, final results correctable by primary key. If any breaks, the system "looks real-time but is actually unrecoverable".
Five Directly Applicable Scenarios
Real-time operations analysis: reuse order/payment/refund DWD, minute aggregates by store/channel/product; focus on rollback & reconciliation.
Real-time user profiling: aggregate click/search/favorite/purchase; control high-cardinality state TTL and feature freshness.
Real-time risk control: combine current event, historical state, rule version for decisions; ensure low latency, explainability, rule traceability.
CDC into lake: continuously write database changes to primary-key lake tables; handle schema evolution, delete semantics, full/incremental sync.
IoT alerting: aggregate metrics by device/time window; handle hot devices, idle partitions, abnormal retries, alert noise reduction.
Don't rush to Flink if: business only needs hourly/next-day results, data volume is tiny, existing DB incremental jobs are stable, or team lacks stateful streaming ops capability. The standard is not "everyone uses it" but whether the business truly needs continuous computation, event time, stateful processing, and failure recovery. Simple data movement can use CDC tools or scheduled jobs.
5. Pre-Launch Checklist & Failure Diagnosis
Pre-Launch Verification (10 Items)
Every fact table has stable business primary key; update/delete semantics defined.
Event time sourced from business fields; timezone, precision, null handling unified.
Watermark derived from real latency distribution; late-data handling strategy exists.
All dedup/window/join states have explainable lifecycles (TTL).
Source replayable; sink supports transactions or deterministic-primary-key idempotent writes.
Checkpoint storage on reliable distributed FS or object store.
Job passed TaskManager failure, downstream timeout, message backlog drills.
Savepoint-based release/rollback plan; stateful operator UIDs stable.
Monitoring covers latency, throughput, backpressure, watermark, checkpoint, state size, restart count.
Raw data replayable; real-time results have offline/lakehouse reconciliation baseline.
Troubleshooting Paths
Dashboard latency up: check Kafka lag → Flink backpressure → downstream write latency. Don't blindly increase parallelism.
Checkpoint frequent timeout: check alignment time & state size → hot keys, backpressure, remote storage. Merely raising timeout delays failure detection.
Watermark stuck: check idle partitions, source partition with no data, or one input stream lagging in multi-input operators.
Amount double-counting: verify sink not append-only, business PK stable, no duplicate commit on recovery, update-before/delete messages not incorrectly dropped.
Dimension join mass timeouts: check cache hit rate, async request capacity, external DB pool, fallback logic; prevent single dimension jitter from stalling main pipeline.
Real-time pipeline stability comes from replayability, observability, correctability — not from "job has run for many days".
Conclusion: Treat Flink as a Continuous Computation System, Not a SQL Executor
In a real-time data warehouse, Flink's key role is not "runs fast" but continuously understanding business changes: it knows when events happened, remembers what occurred before, recovers from failure, and reflects late arrivals, refunds, and dimension changes back into results.
A production-ready solution typically starts from one scenario: define SLA and business PK, keep replayable ODS, build reusable DWD, then window aggregation with upsert output, finally add checkpointing, monitoring, reconciliation, and failure drills.
If you remember one sentence: design time, primary key, state, and replay first — then write the first line of Flink SQL.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Niu Liu
A slightly rustic name 🤠 A tech veteran navigating the internet wave Hardcore tech: fixing all bugs and tough challenges
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
