Serving Layer¶
Gold-layer Iceberg tables are queried directly by DuckDB — no dedicated query server, no data movement. Analysts get sub-second cold-start times and the full power of SQL. When concurrency or scan volume outgrows DuckDB, the documented migration path is AWS Athena: same Iceberg tables, same S3 storage, zero pipeline changes.
DuckDB Catalog¶
serving/duckdb/catalog.py — Connection factory and Glue table registration.
import duckdb, os
def get_connection() -> duckdb.DuckDBPyConnection:
con = duckdb.connect()
con.execute("INSTALL iceberg; LOAD iceberg;")
con.execute("INSTALL aws; LOAD aws;")
if os.getenv("LAKEHOUSE_ENV") == "aws":
con.execute("CALL load_aws_credentials();") # CREDENTIAL_CHAIN auth
_register_glue_tables(con)
else:
con.execute(f"SET s3_region='{os.getenv('S3_REGION', 'us-east-1')}';")
return con
def _register_glue_tables(con: duckdb.DuckDBPyConnection) -> None:
tables = {
"order_daily_summary": "s3://chakra-lakehouse/gold/order_daily_summary/",
"silver_orders": "s3://chakra-lakehouse/silver/orders/orders/",
"orders_dlq": "s3://chakra-lakehouse/dlq/orders/",
}
for alias, path in tables.items():
con.execute(f"""
CREATE OR REPLACE VIEW {alias} AS
SELECT * FROM iceberg_scan('{path}', allow_moved_files=true)
""")
The CREDENTIAL_CHAIN approach (load_aws_credentials()) picks up IAM role credentials automatically in ECS/EKS — no hardcoded keys. Locally, S3_REGION + boto-style env vars are used instead.
Analyst Queries¶
serving/duckdb/queries/order_analytics.sql
Revenue Trend¶
SELECT
order_date,
SUM(total_revenue_cents) / 100.0 AS revenue_usd,
SUM(unique_customers) AS customers,
ROUND(SUM(total_revenue_cents) * 1.0
/ NULLIF(SUM(total_orders), 0) / 100, 2) AS avg_order_usd
FROM iceberg_scan('s3://chakra-lakehouse/gold/order_daily_summary/')
WHERE order_date >= current_date - INTERVAL 30 DAYS
GROUP BY order_date
ORDER BY order_date;
Time Travel — Point-in-Time Audit¶
-- Query Gold table state as of a specific timestamp
SELECT order_date, total_revenue_cents
FROM iceberg_scan(
's3://chakra-lakehouse/gold/order_daily_summary/',
version = '2024-03-14T00:00:00'
)
WHERE order_date = '2024-03-13';
Iceberg snapshot IDs or timestamps can be passed directly. This powers post-incident audits and backfill validation without needing a separate audit table.
Freshness Check¶
-- Alerts if Gold data is stale beyond the 5-minute SLA
SELECT
MAX(updated_at) AS last_updated,
NOW() - MAX(updated_at) AS staleness,
CASE WHEN NOW() - MAX(updated_at) > INTERVAL 5 MINUTES
THEN 'SLA_BREACH' ELSE 'OK' END AS status
FROM iceberg_scan('s3://chakra-lakehouse/gold/order_daily_summary/');
DLQ Audit¶
-- Hourly DLQ failure counts by stage and reason — surfaced in Grafana
SELECT
DATE_TRUNC('hour', failed_at) AS hour,
failure_stage,
failure_reason,
COUNT(*) AS event_count
FROM iceberg_scan('s3://chakra-lakehouse/dlq/orders/')
WHERE failed_at >= NOW() - INTERVAL 24 HOURS
GROUP BY 1, 2, 3
ORDER BY 1 DESC, 4 DESC;
Production-Scale Path: AWS Athena¶
DuckDB is the right choice for the reference implementation: zero infrastructure, instant startup, full Iceberg support. When usage patterns cross any of these thresholds, Athena is the documented production path.
| Threshold | Reason to migrate |
|---|---|
| > 10 concurrent users | DuckDB is single-process; Athena scales query workers horizontally |
| > 50 GB per query scan | DuckDB loads data into memory; Athena pushes predicates to S3 Select |
| BI tool JDBC/ODBC required | DuckDB has no persistent server; Athena exposes a JDBC endpoint |
| Audit logging required | Athena logs every query to CloudTrail; DuckDB has none |
Migration Procedure¶
The tables are already in Iceberg on S3. Migration is a registration step, not a data movement step.
# 1. Register Gold tables in AWS Glue (one-time)
aws glue create-table \
--database-name chakra_lakehouse \
--table-input '{
"Name": "order_daily_summary",
"StorageDescriptor": {
"Location": "s3://chakra-lakehouse/gold/order_daily_summary/",
"InputFormat": "org.apache.iceberg.mr.mapred.IcebergInputFormat"
},
"Parameters": { "table_type": "ICEBERG" }
}'
# 2. Update analyst queries: replace iceberg_scan() with table name
-- BEFORE: FROM iceberg_scan('s3://chakra-lakehouse/gold/order_daily_summary/')
-- AFTER: FROM chakra_lakehouse.order_daily_summary
# 3. Grant Athena workgroup access via IAM
aws iam attach-role-policy \
--role-name analysts \
--policy-arn arn:aws:iam::aws:policy/AmazonAthenaFullAccess
The Flink pipeline and dbt models are unchanged. The schema contract in contracts/data-products/orders-analytics.yaml is unchanged. The Gold Iceberg table is unchanged. Only the query engine changes.
Serving Architecture¶
flowchart TD
GOLD["Iceberg Gold Tables\ns3://chakra-lakehouse/gold/"]
DDB["DuckDB\niceberg_scan() · in-process\nAnalyst notebooks · local scripts"]
ATH["AWS Athena\nGlue catalog · distributed\nBI tools · high concurrency"]
GOLD --> DDB
GOLD -.->|"production scale\n(ADR-0005 thresholds)"| ATH
DDB --> NB["Jupyter / Python\nnotebooks"]
ATH --> BI["Tableau · Power BI\nLooker · Redash"]
style GOLD fill:#14532d,color:#fff
style DDB fill:#0f766e,color:#fff
style ATH fill:#374151,color:#fff
ADR-0005: DuckDB Serving Layer — Full decision rationale, Trino comparison, and Athena migration criteria.