Analytics Infrastructure and Query Performance Questions
Designing analytics data infrastructure and optimizing query performance for analytics workloads. Includes data modeling for analytics, columnar versus row storage trade offs, clustering and partitioning strategies, indexing and materialized views, caching and result reuse, profiling and tuning slow queries, cost and latency trade offs for large scale analytics, and considerations for ingest pipelines and analytical storage choices.
HardTechnical
18 practiced
You are the principal data engineer deciding between adopting a managed analytics warehouse vs building a self-managed open-source stack. Outline an evaluation framework covering TCO, vendor-lock risk, operational effort, feature parity, performance targets, and migration plan for stakeholders. Describe a 3-phase pilot to validate the decision.
Sample Answer
Evaluation framework (objective criteria + stakeholders, weighted scoring):- Total Cost of Ownership (5yr): include cloud infra, licences, labor (SRE/DevOps), networking, backups, DR, security, storage egress; model best/worst/cashflow. Compare NPV and cost per TB / cost per query.- Vendor-lock risk: assess data portability (standard formats, connectors), API dependency, proprietary features, exit cost (egress, re-architecture). Score likelihood × impact and mitigation (open export formats, dual-write).- Operational effort: FTEs required (on-call, upgrades, config, capacity planning), mean time to recover, patching cadence, training cost. Estimate ops-hours/month.- Feature parity: analytics SQL features, UDFs, materialized views, security (RBAC, encryption), governance (data catalog, lineage), integrations (BI, streaming). Triage must-have vs nice-to-have.- Performance targets: SLA for query latency, concurrency, ingestion lag, SLA percentiles (P50/P95/P99). Run benchmarks with representative queries and data shapes; include autoscaling behavior.- Migration plan & risk: discovery, data model compatibility, cutover strategy (dual-write, backfill), validation tests, rollback plan, expected downtime, cost of parallel run.Decision artifacts:- Weighted decision matrix, NPV, risk register, runbook, SLA comparisons, demo reports.3-phase pilot (6–12 weeks):Phase 1 — Discovery & Fit (2 weeks)- Export sample schemas, representative datasets (1–10% scale), key queries/ETL jobs, security and compliance checklist.- Success: all required connectors work; initial cost estimate within X% of model; stakeholders sign-off.Phase 2 — Performance & Reliability POC (3–6 weeks)- Deploy either managed instance and/or open-source stack in sandbox; run ingestion at target throughput, run concurrency/query suite, simulate failures, measure latency, cost, ops-hours.- Metrics: P95 query latency, ingestion lag, cost/TB, ops-time. Success thresholds defined beforehand (e.g., P95 < target, ops < Y hrs/week).Phase 3 — Migration Dry-run & Business Validation (2–4 weeks)- Run dual-write/replication for selected pipelines; sync historical backfill; validate data quality with reconciliations; have BI users run dashboards; test cutover and rollback.- Deliverables: migration runbook, rollback steps, monitoring dashboards, training docs, final cost/risk comparison, executive recommendation.Stakeholder plan:- Weekly demos to engineering leads, security, finance, analytics power users.- Decision checkpoint after Phase 2 with go/no-go criteria; final sign-off after Phase 3.If managed wins: negotiate SLAs, egress clauses, export tooling. If self-managed wins: budget for 12–18 months of ramp-up, hire/skill-up ops, automate upgrades and disaster recovery.
HardTechnical
26 practiced
Design a streaming deduplication and watermarking solution in Spark Structured Streaming for events with at-least-once delivery from Kafka. Provide pseudocode or structured steps for state management, windowing, watermark configuration, and how to ensure idempotent writes to downstream Delta/iceberg tables.
Sample Answer
Approach (brief): Use event_id (or natural key) + event_timestamp to deduplicate with Spark state + watermark to bound state retention. Use structured streaming with checkpointing; perform windowed/stateful deduplication or use dropDuplicates(withWatermark) then write via foreachBatch using idempotent MERGE into Delta/Iceberg keyed by event_id and last_processed_ts. Maintain consumer offsets via checkpointing to ensure exactly-once semantics to sink.Key concepts:- Watermark (withWatermark) bounds how late an event can be and lets Spark purge state after that lateness (e.g., 30m).- dropDuplicates with watermark uses state under the hood; state TTL ~= watermark + extra cleanup.- State management: ensure checkpointLocation is durable; tune spark.sql.streaming.stateStore.maintenance.interval and spark.sql.streaming.stateStore.rocksdb.* if large.- Idempotent sink: MERGE into Delta/Iceberg keyed by event_id + event_ts ensures at-least-once Kafka delivery doesn't create duplicates. Alternatively use write with precomputed dedupe keys and transactional sink (Delta's atomic commits).- Exactly-once to sink: Kafka -> Spark Structured Streaming + Delta MERGE with checkpointing gives end-to-end idempotence.Edge cases & tuning:- Late-but-valid data arriving after watermark: will be dropped; choose watermark based on SLAs.- High cardinality of keys: increase state store resources, set shorter watermark, or use external dedupe store (Redis/Cassandra) with TTL.- Large out-of-order skew: consider buffering windowing by event_time and using session/window aggregations.- Fault tolerance: durable checkpointing, idempotent MERGE prevents duplicate inserts on retries.Trade-offs:- Larger watermark tolerates lateness but increases state size and memory.- MERGE provides correctness but can be slower than append; tune partitioning and concurrency for throughput.
python
# Pseudocode (PySpark)
from pyspark.sql import SparkSession
from pyspark.sql.functions import col, expr
spark = SparkSession.builder.appName("dedup-watermark").getOrCreate()
kafka_df = (spark.readStream.format("kafka")
.option("kafka.bootstrap.servers","...").option("subscribe","topic")
.option("startingOffsets","earliest").load())
events = (kafka_df.selectExpr("CAST(value AS STRING) as json")
.selectExpr("from_json(json, 'event_id STRING, payload STRUCT<...>, event_ts TIMESTAMP') as t")
.select("t.*"))
# 1) Watermark + dropDuplicates on event_id (keeps earliest within watermark)
deduped = (events
.withWatermark("event_ts", "30 minutes") # watermark bounds late data
.dropDuplicates(["event_id", "event_ts"])) # keeps one event_id per event_ts within watermark
def foreach_batch(df, epoch_id):
# df is micro-batch deterministic subset; idempotent write using MERGE
# Use Delta or Iceberg MERGE on primary key event_id and use event_ts to keep latest
(df.createOrReplaceTempView("batch"))
spark.sql("""
MERGE INTO target_table t
USING batch s
ON t.event_id = s.event_id
WHEN MATCHED AND s.event_ts > t.event_ts THEN UPDATE SET *
WHEN NOT MATCHED THEN INSERT *
""")
# commit is atomic for Delta/Iceberg
(deduped.writeStream
.foreachBatch(foreach_batch)
.option("checkpointLocation","/mnt/checkpoints/dedup")
.start())MediumTechnical
24 practiced
Describe strategies for schema enforcement and governance in ingestion pipelines: schema registry patterns, contract tests, automated validation, and how to handle breaking changes in a backward-compatible way. Include tooling and processes you would adopt.
Sample Answer
Approach: I’d enforce schemas via a combination of a central registry, automated validation & contract testing, robust versioning rules and governed rollout processes so producers and consumers stay decoupled but safe.Practical pattern:- Schema registry: use Avro/Protobuf with Confluent or AWS Glue Schema Registry (or Apicurio for REST). Store schema metadata, compatibility mode (BACKWARD, FORWARD, FULL) and enforce at broker/ingest time.- Producer-side validation: libraries validate payloads against the registry before publish; reject invalid messages early.- Consumer-side validation: schema-aware deserializers and a lightweight validation layer to fail-fast and log bad records to a Dead Letter Queue (DLQ).- Contract tests: implement consumer-driven contract tests (Pact-like or custom pytest suites). Run producer and consumer contracts in CI; test matrix ensures new producer versions satisfy all consumer expectations.- Automated validation in pipelines: integrate Great Expectations / Deequ for row-level and column-level checks; embed schema validation in Spark/Flink ingestion jobs to drop/tag/route malformed records.Handling breaking changes:- Prefer additive, backward-compatible changes (new optional fields, enums with new values only if consumers can ignore).- When breaking changes are necessary: - Publish new schema version with compatibility set to allow co-existence (e.g., new subject name or major version tag). - Use feature-flagged rollout / canary producers writing to new topic or subject; run consumers in dual-read mode (read both old and new) during migration. - Maintain a deprecation window and changelog; notify consumers via governance Slack/email and an API catalog (Data Catalog / Schema Registry UI). - Use automated migration jobs to transform old records to new schema where feasible.Processes & tooling:- CI gates: contract tests, linting (avro-tools / buf), and schema-change approval step in PRs.- Governance: a lightweight schema review board, documented compatibility policy, and a registry-backed ACL model.- Observability: schema-change alerts, metrics on DLQ rates, contract test coverage reports.Example flow: developer proposes schema PR → CI runs schema linter + contract tests against consumer stubs → schema registry approval workflow (manual for major changes) → canary deploy writing to new subject → consumers migrate → deprecate old subject after window.This combination reduces runtime surprises, ensures traceability, and provides safe paths for evolution without breaking downstream consumers.
MediumTechnical
25 practiced
You need to ingest transactional changes from operational DBs into an analytics lakehouse using Debezium + Kafka and process them into delta tables. Describe how you'd handle schema evolution, deduplication, ordering (transaction boundaries), and ensuring idempotency in downstream consumers.
Sample Answer
Situation: Ingesting Debezium CDC into Kafka and producing Delta tables for analytics while handling evolving schemas, duplicates, transaction boundaries, ordering, and idempotency.Approach (high level):- Use Debezium -> Kafka Connect with Avro/JSON Schema and a schema registry. Partition Kafka topics by the source primary key to preserve per-entity ordering.- Use Spark Structured Streaming (or Flink) consumers that write to Delta Lake using idempotent upserts (MERGE).Schema evolution:- Store schemas in a Schema Registry (Avro/JSON Schema). Enforce compatibility rules (BACKWARD/SECONDARY) so producers can add nullable fields.- Enable Debezium to include schema metadata in events. On the consumer, use Delta’s mergeSchema=true or explicit ALTER TABLE to add new nullable columns; map unknown fields to json struct if you need flexible columns.- For breaking changes (rename/type change), run a controlled migration: transform events in a schema-normalization layer (small ETL job) before landing.Deduplication & idempotency:- Rely on a stable event id from Debezium’s source envelope: combine source.partition info + source.lsn/txId + source.row/seq (or binlog filename+pos in MySQL/Postgres) to form deterministic event_id.- Use Delta MERGE keyed by primary key + event_id (or by primary key and last_applied_lsn). Implementation pattern: in each micro-batch, dedupe incoming events by event_id, then MERGE into target table using event’s op timestamp/lsn to only apply newer events.- Maintain a small processed-events index (or use Delta’s transaction metadata) to detect replayed events. Alternatively, store last_applied_lsn per primary key and ignore older lsn events.Ordering & transaction boundaries:- Partition Kafka by primary key to preserve order per entity. Debezium emits transaction metadata (txId, txCommit) — consume transaction topic or use envelope.transaction to group events.- Consumer should buffer/hold events until transaction commit seen (if strict transactional semantics required). With high throughput, rely on ordering+lsn: apply only events with increasing lsn/timestamp per key.- For multi-row transactions that must be atomic, use Debezium’s "transactional" mode which writes a transaction marker; consumers use that to apply the whole group in a single Delta transaction (Spark foreachBatch + Delta Transaction) so either all rows commit or none do.Implementation pattern (example):- Spark Structured Streaming: - Read Kafka Avro, parse envelope, extract event_id, lsn/txId, op, payload. - Drop duplicates in batch by event_id. - For each micro-batch, perform a Delta MERGE: - WHEN MATCHED AND incoming_lsn > target_last_lsn THEN UPDATE - WHEN NOT MATCHED THEN INSERT - WHEN MATCHED AND op='d' THEN DELETE (or soft-delete) - Set mergeSchema=true for controlled schema additions, but prefer explicit schema migrations for production.Other best practices:- Use tombstone messages for deletes; configure Debezium to emit them.- Monitor lag, out-of-order events, and schema evolution warnings.- Use transactional writes where supported (Kafka EOS + idempotent Delta writes) and retention of Kafka compacted topics for replayability.- Test migrations on shadow tables, and implement alerting for schema incompatibility or repeated replays.Why this works:- Schema registry + controlled Delta schema evolution keeps analytics stable.- Deterministic event_id + lsn-based MERGE provides idempotency and deduplication.- Kafka partitioning + Debezium transaction metadata preserve ordering and allow atomic application of transaction-scoped changes.
HardSystem Design
26 practiced
Design a petabyte-scale analytics lakehouse that supports: 1) raw event ingestion at 5M events/sec, 2) interactive SQL for analysts (sub-5s for common queries), 3) time travel and data lineage, and 4) separation of compute and storage. Sketch architecture components, technology choices (e.g., object store, metadata layer, query engines), and how you'd achieve scalability and cost control.
Sample Answer
Requirements clarification:- Ingest 5M events/sec (raw), petabyte storage, sub-5s interactive SQL for common queries, time-travel + lineage, separation of compute/storage.High-level architecture (data flow):Producers → Ingestion (Kafka/Kinesis) → Stream processing (Flink) → Object store (S3/GS/Azure Blob) with table format (Apache Iceberg/Delta Lake) + Metadata service (Nessie/Glue Catalog) → Query engines (Trino for interactive, Spark/Presto for heavy BI/ETL) → Catalog & governance (OpenLineage/Apache Atlas) → BI/analytics clientsComponent choices & rationale:- Object store: S3 (or GCS/Blob) — cheap, durable, supports large objects and lifecycle policies.- Table format/Time travel: Apache Iceberg (or Delta Lake). Iceberg gives true snapshot isolation, partition evolution, fast time-travel and efficient metadata for petabyte scale.- Metadata layer: Project Nessie or Hive Metastore backed by Glue/Azure Data Catalog to provide consistent catalog + multi-engine support.- Ingestion: Kafka (self-managed/MSK) or Kinesis; provision partitions to sustain 5M eps. Use compression (snappy), batching, and partition keys.- Stream processing: Apache Flink for low-latency exactly-once writes to Iceberg (using Iceberg's streaming writers) to convert events to parquet/ORC and manage partitioning.- Query engines: Trino for sub-5s interactive queries (with pushdown, columnar formats, vectorized execution); support cached results; Spark SQL for heavy transforms.- Lineage & governance: OpenLineage + Apache Atlas integrated into pipelines and metadata commits.- Orchestration: Airflow / Dagster for batch/ETL jobs and compaction.How to handle 5M events/sec:- Kafka with >1000 partitions (estimate based on throughput per partition); autoscale brokers and use multi-az.- Producers batch & compress; use schema registry (Confluent/Apicurio) for compact schemas.- Flink consumers read partitions in parallel and write micro-batches to Iceberg using manifest lists to keep small-file problem under control.Time-travel & lineage:- Iceberg snapshots provide time-travel; retain snapshots per SLA and use object-store lifecycle to tier older data to Glacier/Archive.- Commit metadata records include provenance (job id, run id); emit OpenLineage events at each pipeline step. Use Atlas for lineage UI and compliance queries.Separation of compute and storage:- Storage = S3; compute = ephemeral Trino/Spark/Flink clusters. Autoscale compute clusters (Kubernetes/EMR EKS/GKE) and terminate idle nodes. Use query federation via catalog.Scalability & performance for interactive queries:- Partitioning strategy: hybrid partitioning (time + hashed user-id) and Z-order / sort-on-hot-columns to reduce scan.- Use columnar format (Parquet/ORC), compression, and per-file bloom filters/MinMax via Iceberg predicates for fast pruning.- Maintain materialized views / pre-aggregations for common queries; cache them in a fast store (Redis or materialized Parquet on S3).- Use Trino worker fleet sized for concurrency; enable result caching and CPU-efficient instance types (C5/C6i).- Pushdown predicate, projection, vectorized read.Cost control:- Tiered storage lifecycle (hot S3 -> infrequent -> Glacier).- Compact small files periodically (compaction jobs) to reduce per-file overhead.- Use spot/spot-fleet or preemptible nodes for batch processing.- Autoscaling compute with query concurrency limits and queuing; enforce cost quotas by workspace.- Use cold summary tables for old data; keep raw hot for limited retention.Reliability & consistency:- Exactly-once semantics using Flink + Iceberg atomic commits.- Backups of metadata and cross-region replication for critical datasets.- Monitoring: Prometheus/Grafana, Kafka/cluster health, SLOs on ingestion lag and query latencies.Trade-offs:- Iceberg + S3 scales but small-file management requires compaction work.- Trino gives low-latency but complex for highly concurrent complex queries—mitigate with materialized views and caches.- Using managed cloud services reduces ops but increases cost.Example sizing rough (simple):- 5M eps * 100 bytes/event ≈ 500 MB/s ≈ ~43 TB/day compressed; plan ~1–2 PB/year depending on retention and expansion — design partition retention and archive accordingly.This architecture provides petabyte-scale storage, sub-5s interactive responses for common queries via pruning, materialized views and Trino, robust time-travel through Iceberg snapshots, lineage via OpenLineage/Atlas, and clear compute/storage separation with autoscaling and cost controls.
Unlock Full Question Bank
Get access to hundreds of Analytics Infrastructure and Query Performance interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.