Dinesh/ Blog
data engineering

Overall Engineering Clarity — Data, Distributed Systems and AI (Deep Dive)

Trace compute, commit, indexing and authorization boundaries across a data/AI platform. Mechanisms, worked examples, failure analysis and complete practice answers.

119 min readChecking device speech…

In this article

  • Trace compute, commit, indexing and authorization boundaries across a data/AI platform
  • Diagnose version-specific Spark, SQL, JVM and Kubernetes failure modes
  • Solve the original quiz bank and execute bounded algorithm and SCD2 fixtures
Background & source details

Level: advanced

Data Platform & Orchestration · Lesson 23 of 23 ↗
Loading this browser’s progress…

Bring with you

Sources checked: . A source check does not change the publication or update date.

Editorial review: · What review means

In this article · 166 sections

Reading contract: this long-form companion retains the original mechanisms, diagrams and complete quiz bank. Spark examples target 3.5.7, Iceberg 1.9.2, PostgreSQL 16, and Java notes name their runtime versions; service APIs below are reference material, not a deployment recipe. Version-specific corrections are made in place; full worked self-check/final-exam answers and bounded local tests follow. Service procedures are explicitly unexecuted.

Overall Engineering Clarity — Data, Distributed Systems and AI

A slow query, a half-visible table update, and an AI answer drawn from the wrong tenant look like different incidents. Each exposes a boundary the system must make explicit: where work moves, when data becomes visible, and which evidence a request is allowed to use. This companion connects those boundaries through a governed data platform, from distributed computation to storage, indexes, and AI services.

Use the diagrams to follow one query or update, then use the decision tables and Q&A to challenge the design. The recurring shape is mental model → flow / diagram → internals → decision tables → code → Q&A with reasoning; the later quiz bank is a separate retrieval-practice pass, not another step in the platform’s runtime flow.

How to read each concept — the "Why → How → Trade-off" framework

Most engineers can recite what a thing is. The clarity gap shows up when you ask why it exists and what breaks. Use this template for every concept:

What it is        →  one sentence definition
Why it exists     →  the problem before this solution
How it works      →  internals / mechanism
When to use       →  correct fit
When NOT to use   →  anti-patterns
Trade-offs        →  latency, cost, consistency, complexity
Failure modes     →  what bites you in production
Concrete example  →  code or architecture you can point at

Intuition shortcut. Every technology in this post solves one of three pains:

  1. One machine isn't enough → distribution, partitioning, replication.
  2. Cheap object storage isn't transactional → lakehouse formats, commit protocols.
  3. Models don't know your private/fresh data, and they hallucinate → embeddings, retrieval, agents, evals.
How to read each concept — the "Why → How → Trade-off" framework
Figure text equivalent — labels and relationships
  • Node “One machine not enough” (square).
  • Node “Spark · Sharding · Replication · Consensus” (square).
  • Node “Object storage not transactional” (square).
  • Node “Iceberg · Delta · Hudi” (square).
  • Node “LLM doesn't know your data + can hallucinate” (square).
  • Node “Embeddings · RAG · Agents · Evals” (square).
  • Node “Reliable, scalable, governed data + AI platform” (square).
  • “One machine not enough” leads to “Spark · Sharding · Replication · Consensus”. Line: normal; ends: arrow point.
  • “Object storage not transactional” leads to “Iceberg · Delta · Hudi”. Line: normal; ends: arrow point.
  • “LLM doesn't know your data + can hallucinate” leads to “Embeddings · RAG · Agents · Evals”. Line: normal; ends: arrow point.
  • “Spark · Sharding · Replication · Consensus” leads to “Reliable, scalable, governed data + AI platform”. Line: normal; ends: arrow point.
  • “Iceberg · Delta · Hudi” leads to “Reliable, scalable, governed data + AI platform”. Line: normal; ends: arrow point.
  • “Embeddings · RAG · Agents · Evals” leads to “Reliable, scalable, governed data + AI platform”. Line: normal; ends: arrow point.

Default for every answer: problem first, mechanism second, trade-off third, failure mode fourth, example fifth.


Part I — Distributed Compute

This part follows query planning into physical work and then uses task evidence to choose a tuning experiment. Keep plan shape, partition data volume and process/container memory distinct: a shuffle change cannot repair every scan or native-memory bottleneck.

1. Apache Spark — Internals That Actually Matter

1.1 Mental model

Spark is a distributed query engine. The single sentence to remember:

Ask where data moves and where time is spent before buying more CPU.

Shuffle moves rows so matching keys meet, and is a frequent bottleneck. Scan I/O, skew, CPU-heavy expressions, Python conversion, scheduling and output can instead dominate; inspect the physical plan and task metrics before choosing a fix.

1.2 Execution flow — what really happens when you call an action

Imagine grouping a day’s playback events by user. The action triggers work, but rows for one user may be scattered across input partitions. Follow the diagram to the shuffle and then into the reducer tasks: that is where local scans become a cross-machine coordination problem. The scheduler and executor boxes explain who arranges the work; the shuffle boundary explains why more CPU alone may not shorten it.

1.2 Execution flow — what really happens when you call an action
Figure text equivalent — labels and relationships
  • Node “Driver (SparkContext)” (square).
  • Node “DAG Scheduler splits at shuffle boundaries” (square).
  • Node “Task Scheduler 1 task per partition per stage” (square).
  • Node “Cluster Manager allocates executor resources” (square).
  • Node “Executor 1 (JVM; Python worker when needed)” (square).
  • Node “Executor 2” (square).
  • Node “Executor N” (square).
  • Node “Shuffle Service (sort-based / push-based)” (square).
  • Node “Reducer tasks next stage” (square).
  • Node “Action result” (square).
  • “Driver (SparkContext)” leads to “DAG Scheduler splits at shuffle boundaries”. Line: normal; ends: arrow point.
  • “DAG Scheduler splits at shuffle boundaries” leads to “Task Scheduler 1 task per partition per stage”. Line: normal; ends: arrow point.
  • “Cluster Manager allocates executor resources” leads to “Executor 1 (JVM; Python worker when needed)”. Line: normal; ends: arrow point.
  • “Task Scheduler 1 task per partition per stage” leads to “Executor 1 (JVM; Python worker when needed)”. Line: normal; ends: arrow point.
  • “Cluster Manager allocates executor resources” leads to “Executor 2”. Line: normal; ends: arrow point.
  • “Cluster Manager allocates executor resources” leads to “Executor N”. Line: normal; ends: arrow point.
  • “Executor 1 (JVM; Python worker when needed)” leads to “Shuffle Service (sort-based / push-based)”. Line: normal; ends: arrow point.
  • “Executor 2” leads to “Shuffle Service (sort-based / push-based)”. Line: normal; ends: arrow point.
  • “Executor N” leads to “Shuffle Service (sort-based / push-based)”. Line: normal; ends: arrow point.
  • “Shuffle Service (sort-based / push-based)” leads to “Reducer tasks next stage”. Line: normal; ends: arrow point.
  • “Reducer tasks next stage” leads to “Action result”. Line: normal; ends: arrow point.

Layers of work:

  • Application → jobs triggered by actions → stages at shuffle dependencies → tasks for stage partitions. One action can generate multiple jobs, especially in SQL/AQE; this hierarchy is not a one-to-one action/job guarantee.
  • DAG Scheduler decides stages; Task Scheduler dispatches tasks; BlockManager stores intermediate data on each executor.
  • Narrow transformations (map, filter, select, mapPartitions, union) keep partitions local.
  • Wide dependencies require cross-partition work. groupBy, distinct and repartitioning commonly shuffle; joins depend on the physical strategy and existing partitioning (a broadcast join need not shuffle the large input).
  • Catalyst rewrites your logical plan; Tungsten-era execution improvements include binary representation and whole-stage code generation; AQE (Adaptive Query Execution) flips plans at runtime once it sees actual partition sizes.

1.3 Catalyst — the optimizer pipeline

The previous diagram describes execution; this one asks which work should be executed at all. If the playback query needs only yesterday’s rows and two columns, filtering and pruning before a shuffle can save more than tuning the shuffle itself. Follow the plan from named columns to physical operators, then use the rule table to identify which unnecessary work can disappear.

1.3 Catalyst — the optimizer pipeline
Figure text equivalent — labels and relationships
  • Node “DataFrame / SQL” (square).
  • Node “Parsed Logical Plan” (square).
  • Node “Analyzed Plan (resolve cols, fns, types)” (square).
  • Node “Optimized Logical Plan (rule-based)” (square).
  • Node “Physical Plans (many candidates)” (square).
  • Node “Strategy selection (rules, hints, statistics; CBO when enabled)” (square).
  • Node “Whole-stage Codegen (Tungsten)” (square).
  • Node “Execute on executors” (square).
  • “DataFrame / SQL” leads to “Parsed Logical Plan”. Line: normal; ends: arrow point.
  • “Parsed Logical Plan” leads to “Analyzed Plan (resolve cols, fns, types)”. Line: normal; ends: arrow point.
  • “Analyzed Plan (resolve cols, fns, types)” leads to “Optimized Logical Plan (rule-based)”. Line: normal; ends: arrow point.
  • “Optimized Logical Plan (rule-based)” leads to “Physical Plans (many candidates)”. Line: normal; ends: arrow point.
  • “Physical Plans (many candidates)” leads to “Strategy selection (rules, hints, statistics; CBO when enabled)”. Line: normal; ends: arrow point.
  • “Strategy selection (rules, hints, statistics; CBO when enabled)” leads to “Whole-stage Codegen (Tungsten)”. Line: normal; ends: arrow point.
  • “Whole-stage Codegen (Tungsten)” leads to “Execute on executors”. Line: normal; ends: arrow point.

Key rules Catalyst applies (you should be able to name them):

RuleWhat it does
Predicate pushdownPush WHERE filters into the file scan (Parquet stats / partition pruning).
Projection pruningRead only the columns the query references.
Constant foldingEvaluate constant expressions at plan time.
Boolean simplification(x AND TRUE) → x; collapse always-false branches.
Join reorderingUse stats to pick the cheapest join order (CBO).
Reorder filters & projectionsPush them as low as possible in the tree.

1.4 Tungsten — what makes Spark fast on the CPU

  • Whole-stage codegen fuses compatible physical operators into generated Java; unsupported operators create boundaries. JVM optimization can reduce dispatch overhead, but full-tree fusion or vectorization is not guaranteed.
  • Binary row representation reduces per-object overhead; storage may be on-heap or explicitly configured off-heap. Tungsten does not mean all execution memory is off-heap.
  • Columnar scans and batches can improve locality and reduce decoding work; generated row operators and columnar boundaries still coexist.

Opaque UDF semantics can limit optimization. Python execution adds serialization/process costs, while Scala/Java UDF and vectorized Python paths differ. Unrelated filters can still be pushed down; inspect the plan rather than claiming every UDF disables all codegen/pushdown.

1.5 AQE — Adaptive Query Execution

Three runtime optimizations that fire during query execution (default on since 3.2):

  1. Coalesce shuffle partitions — small post-shuffle partitions are merged so reducers aren't underutilized.
  2. Skew join handling — a heavily skewed partition is split into sub-partitions and the other side is replicated.
  3. Switch join strategy — if a planned sort-merge join's input turns out small enough at runtime, AQE flips it to broadcast hash.

1.6 Why shuffle is expensive — the full picture

1.6 Why shuffle is expensive — the full picture
Figure text equivalent — labels and relationships
  • Node “Map task partition by hash(key)” (square).
  • Node “Local sort + spill to disk” (square).
  • Node “Map task” (square).
  • Node “Local sort + spill” (square).
  • Node “Network fetch (reducer pulls or push-based shuffle)” (square).
  • Node “Reducer merge + aggregate” (square).
  • Node “Reducer” (square).
  • Node “Sort if needed (SMJ)” (square).
  • “Map task partition by hash(key)” leads to “Local sort + spill to disk”. Line: normal; ends: arrow point.
  • “Map task” leads to “Local sort + spill”. Line: normal; ends: arrow point.
  • “Local sort + spill to disk” leads to “Network fetch (reducer pulls or push-based shuffle)”. Line: normal; ends: arrow point.
  • “Local sort + spill” leads to “Network fetch (reducer pulls or push-based shuffle)”. Line: normal; ends: arrow point.
  • “Network fetch (reducer pulls or push-based shuffle)” leads to “Reducer merge + aggregate”. Line: normal; ends: arrow point.
  • “Network fetch (reducer pulls or push-based shuffle)” leads to “Reducer”. Line: normal; ends: arrow point.
  • “Reducer merge + aggregate” leads to “Sort if needed (SMJ)”. Line: normal; ends: arrow point.
  • “Reducer” leads to “Sort if needed (SMJ)”. Line: normal; ends: arrow point.

Costs that pile up:

  • Encoding/serialization and copying; RDD serializers and SQL binary/shuffle paths differ.
  • Disk spill if shuffle data exceeds memory.
  • Network I/O between executors.
  • Sorting (sort-based shuffle is the default).
  • Reducer pressure — one slow reducer becomes a straggler.

Push-based shuffle in Spark 3.5.7: requires the supported external shuffle-service/YARN arrangement and shuffle mergers. Map output blocks can be merged to reduce small random fetches. It is not the Kubernetes shuffle-tracking feature, and this review claims no universal throughput win.

1.7 Memory model per executor

spark.executor.memory (heap) ├── reserved (300 MiB) ├── remaining heap (~40% of heap minus reserved at default fraction) └── unified region (~60% of heap minus reserved at default fraction) ├── execution (sort, join, agg, shuffle) └── storage (cache, broadcast)spark.executor.memoryOverhead ← JVM/native overhead; Python if not separately limitedspark.memory.offHeap.size ← separately configured execution off-heapspark.executor.pyspark.memory ← separate Python allowance when configured
Executor heap: 300 MiB reserved; the remaining heap is split at the default fraction into about 40% outside the unified region and 60% unified execution/storage memory. Execution covers sort, join, aggregation and shuffle; storage covers cache and broadcast. Separately, spark.executor.memoryOverhead covers JVM/native overhead and Python when not separately limited; spark.memory.offHeap.size configures execution off-heap memory, and spark.executor.pyspark.memory supplies a separate Python allowance when configured.
Figure text and layout
spark.executor.memory (heap)
  ├── reserved (300 MiB)
  ├── remaining heap (~40% of heap minus reserved at default fraction)
  └── unified region (~60% of heap minus reserved at default fraction)
        ├── execution (sort, join, agg, shuffle)
        └── storage   (cache, broadcast)
spark.executor.memoryOverhead   ← JVM/native overhead; Python if not separately limited
spark.memory.offHeap.size       ← separately configured execution off-heap
spark.executor.pyspark.memory   ← separate Python allowance when configured
Text diagram

OOM investigations include: too few partitions, skewed partitions, oversized cache, or memoryOverhead too small for PySpark workers / native libs.

1.8 The five things that make a Spark job slow

Treat the branches below as competing hypotheses, not a checklist of settings to apply together. If most tasks finish quickly and one runs much longer, investigate the straggler branch before increasing every executor’s memory. If all tasks scan large inputs, start with reads and pruning instead. The Spark UI sequence that follows supplies the evidence needed to choose a branch.

1.8 The five things that make a Spark job slow
Figure text equivalent — labels and relationships
  • Node “Slow Spark Job” (square).
  • Node “Reading too much?” (square).
  • Node “Shuffling too much?” (square).
  • Node “One straggler task?” (square).
  • Node “GC / OOM?” (square).
  • Node “Tiny output files?” (square).
  • Node “Predicate pushdown, column pruning, partition design” (square).
  • Node “Broadcast small side, bucketing, AQE” (square).
  • Node “Skew salting, AQE skew join” (square).
  • Node “Increase partitions, tune memoryOverhead, persist DISK_ONLY or MEMORY_AND_DISK_DESER” (square).
  • Node “Repartition before write, compact, target 128–512 MB” (square).
  • “Slow Spark Job” leads to “Reading too much?”. Line: normal; ends: arrow point.
  • “Slow Spark Job” leads to “Shuffling too much?”. Line: normal; ends: arrow point.
  • “Slow Spark Job” leads to “One straggler task?”. Line: normal; ends: arrow point.
  • “Slow Spark Job” leads to “GC / OOM?”. Line: normal; ends: arrow point.
  • “Slow Spark Job” leads to “Tiny output files?”. Line: normal; ends: arrow point.
  • “Reading too much?” leads to “Predicate pushdown, column pruning, partition design”. Line: normal; ends: arrow point.
  • “Shuffling too much?” leads to “Broadcast small side, bucketing, AQE”. Line: normal; ends: arrow point.
  • “One straggler task?” leads to “Skew salting, AQE skew join”. Line: normal; ends: arrow point.
  • “GC / OOM?” leads to “Increase partitions, tune memoryOverhead, persist DISK_ONLY or MEMORY_AND_DISK_DESER”. Line: normal; ends: arrow point.
  • “Tiny output files?” leads to “Repartition before write, compact, target 128–512 MB”. Line: normal; ends: arrow point.

1.9 Spark UI — your diagnostic flow

Always inspect in this order:

  1. SQL tab — physical plan + actual row counts. Spot accidental cartesians, wrong join strategy, missing pushdown.
  2. Stages tab — input size, shuffle read/write, spill, task duration distribution. Median vs max ratio reveals skew.
  3. Executors tab — GC time (a high fraction is a diagnostic signal, not a universal 10% threshold), failed tasks, memoryUsed.
  4. Storage tab — persisted datasets, cached partitions and storage use; derive reuse/eviction behavior from the workload rather than assuming a universal cache-hit-ratio display.
  5. Environment tab — confirm config actually loaded what you set.

1.10 Join strategy cheat sheet

JoinWhen chosenCostFailure mode
Broadcast Hashsmall side < spark.sql.autoBroadcastJoinThreshold (10 MB default)no shuffle on big side, RAM on every executordriver/executor OOM if "small" side is wrong
Shuffle Hashmedium tables, AQE may pickboth sides shuffled, hash table on smaller sideOOM if hash table too big
Sort-Mergeboth large, equi-joinshuffle + sort each sidesort spill, skew
Broadcast Nested Loopnon-equi join, small sidequadraticcatastrophic on big input
Cartesianlast resort, no condition∣A∣×∣B∣\lvert A\rvert \times \lvert B\rvertalmost never acceptable

1.11 Skew — recognize and fix

Symptoms (Spark UI): one task at 30 min while peers finish in 30 s; one partition's shuffle read is 50× the median; max task time ≫ median.

Why it happens: Hash partitioning sends every row with the same key to the same reducer. One hot key = one overloaded reducer.

Fixes, in order of cheapness:

  1. Turn on AQE skew join (spark.sql.adaptive.skewJoin.enabled=true).
  2. Pre-aggregate before the join (combine map-side).
  3. Salt the hot key.

Salting code (manual, when AQE isn't enough):

from pyspark.sql import functions as F
 
N = 32  # salt fan-out
big = df_big.withColumn("salt", (F.rand() * N).cast("int"))
small = (df_small
         .crossJoin(spark.range(N).withColumnRenamed("id", "salt")))
joined = (big.join(small, ["key", "salt"])
            .drop("salt"))

For this inner equijoin, each left row gets one salt and the right side is replicated N times, preserving the original join multiplicity. This scatters work statistically, not necessarily across exactly N distinct physical reducers. It also replicates all right rows here, not just hot keys; restrict salting to measured hot keys where possible. Outer/semi/anti joins need separate proofs.

1.12 Coding patterns you should know cold

Top-K per group with a window:

from pyspark.sql import functions as F, Window
 
w = Window.partitionBy("category").orderBy(F.col("score").desc(), F.col("id").asc())
top3 = (df.withColumn("rk", F.row_number().over(w))
          .where("rk <= 3")
          .drop("rk"))

Dedupe — keep the latest record per key:

w = Window.partitionBy("id").orderBy(F.col("event_ts").desc(), F.col("version").desc())
latest = (df.withColumn("rk", F.row_number().over(w))
            .where("rk = 1")
            .drop("rk"))

Sessionization with watermark (Spark 3.5.7 reference, not executed):

A 15-minute session gap joins adjacent events; the 30-minute watermark delays state cleanup and bounds some late-data handling. It is not a guarantee that every later event is always discarded, and output mode/operator support matters.

sessions = (events
    .withWatermark("ts", "30 minutes")
    .groupBy(F.session_window("ts", "15 minutes"), "user_id")
    .agg(F.count("*").alias("hits")))

Per-column profile in one pass (governance use case):

agg_exprs = []
for c in df.columns:
    agg_exprs += [
        F.sum(F.col(c).isNull().cast("int")).alias(f"{c}_nulls"),
        F.approx_count_distinct(c).alias(f"{c}_ndv"),
    ]
profile = df.agg(*agg_exprs)   # one aggregate plan; inspect exchanges and sketch memory

Version-aware upsert into Iceberg (Spark extensions required; unexecuted reference):

Deduplicate updates to one row per key before MERGE. The table retains a monotone source version and tombstone; otherwise an older replay can overwrite or resurrect a newer row. The generic UPDATE/INSERT-star scaffold below is valid only for aligned schemas and prefiltered replay-safe source rows; it does not make arbitrary CDC idempotent.

MERGE INTO catalog.db.dim_customer t
USING updates u
ON t.cust_id = u.cust_id
WHEN MATCHED AND u.version > t.version THEN UPDATE SET *
WHEN NOT MATCHED THEN INSERT *;

1.13 Spark on Kubernetes — what's different

  • Each executor is a pod; the driver pod creates them.
  • Dynamic allocation + shuffle tracking (available since Spark 3.0) means you don't need an external shuffle service — executors are kept around if they hold shuffle data still in use.
  • Select executor node pools with node selectors/affinity plus compatible taints/tolerations; spot pools for cost; non-spot pool for the driver.

1.14 Q&A with reasoning

Q1. Why is reduceByKey better than groupByKey().mapValues(_.sum)? Because reduceByKey performs a map-side combine before the shuffle. The reducer receives one partial aggregate per partition per key instead of every raw value. Less network, less spill, less reducer memory.

Q2. A task takes 30 min while peers finish in 30 s. What's happening? Possible data skew — one key may have many more rows. Also check slow disks, retries, remote fetch and garbage collection before attributing the straggler. Spark UI shows max ≫ median task time and one task's shuffle read is huge. Fix with AQE skew join or manual salting; pre-aggregate where possible.

Q3. Why do Python UDFs hurt performance? Scalar Python UDFs cross the JVM/Python boundary and hide their internal semantics from Catalyst. Transfer may be batched; optimizer restrictions depend on which expressions reference the UDF. Use SQL functions or Pandas UDFs (Arrow batches; columnar, vectorized) instead.

Q4. cache() vs checkpoint() — when to use which? cache keeps lineage; if a partition is lost, Spark recomputes from the lineage chain. checkpoint writes to reliable storage and truncates lineage. Use checkpoint to break long iterative chains (graph algorithms, ML loops) where lineage recomputation would be catastrophic on failure.

Q5. repartition vs coalesce? repartition(N) shuffles to redistribute (approximately evenly for round-robin rows, not necessarily bytes or cost). coalesce(N) (where N < current partitions) merges existing partitions without shuffle, but may produce unbalanced output. repartitionByRange(N, col) partitions by range using sampling — useful before sorted writes.

Q6. PySpark task crashes with Container killed by YARN, exceeding memory limits. Heap is fine. Why? Python workers and native allocations consume process/container memory outside the JVM heap. Check separately configured PySpark/off-heap allowances and the remaining overhead budget against RSS; choose measured headroom, not a universal percentage. Avoid loading large local objects.

Q7. Why does broadcast join risk OOM? The small side is collected to the driver, then sent to every executor. if the serialized side is 500 MB and there are 200 executor copies, raw replication alone is about 100 GB; deserialized/hash-table overhead and driver work can be larger.

Q8. What does df.explain(True) show, and what would you look for? Parsed → Analyzed → Optimized logical plans + physical plan. You scan for: chosen join type, presence of BroadcastExchange, HashAggregate vs SortAggregate, predicate pushdown into FileScan, partition filters, and AQE rules applied.


2. Spark Tuning — Diagnostic Recipes

These are the recipes you should rehearse aloud.

2.1 Tuning checklist (memorize)

The sizes below are starting hypotheses, not acceptance thresholds. Validate them on the actual engine/storage version and representative data; no Spark service or benchmark is run here.

  1. Right file format: Parquet/ORC with snappy/zstd; target file size 128–512 MB.
  2. Partitioning on disk: low-cardinality cols (date, region). NOT high-cardinality (user_id).
  3. Bucketing when you join the same large tables repeatedly on the same key.
  4. spark.sql.shuffle.partitions: tune so each shuffle partition processes ~128–256 MB.
  5. AQE on: spark.sql.adaptive.enabled=true, plus coalesce + skew-join.
  6. Broadcast the small side; hint broadcast(df) if Catalyst doesn't pick it up.
  7. Avoid UDFs when SQL functions exist; if needed, Pandas UDFs.
  8. Predicate pushdown: filter before join; let Parquet stats prune.
  9. Column pruning: select only what you need, early.
  10. Cache reused DataFrames only after measuring benefit. PySpark 3.5.7 DataFrame persistence defaults to MEMORY_AND_DISK_DESER; do not copy a Scala RDD storage constant into a Python API.
  11. Dynamic Partition Pruning for star-schema joins.
  12. Speculation can duplicate unusually slow task attempts; it does not divide deterministic hot-key work and requires a safe output commit protocol.
  13. spark.executor.memoryOverhead: measure native/Python RSS and separately configured PySpark/off-heap allowances; no fixed percentage guarantees safety.
  14. Avoid collect() on big DFs; use toLocalIterator() only when the largest partition fits driver memory, or write out.
  15. Z-order / sort within partitions for selective queries (Iceberg/Delta).

2.2 The "this job is slow" design review answer

1. I would not guess. I'd open Spark UI.
2. SQL tab → physical plan, join strategy, scan size, partition filters.
3. Stages tab → shuffle read/write, spill, GC time, max vs median task time.
4. Executors tab → GC %, OOM, memory used, failed tasks.
5. Map symptom to fix:
     huge scan      → pushdown / partitioning / column pruning
     huge shuffle   → broadcast / bucket / AQE / skew
     stragglers     → AQE skew, salting, speculation
     GC time high   → inspect heap allocation/cache and partition work
     native RSS high → measure Python/native usage and memoryOverhead
     small files    → repartition before write, compaction
6. I'd validate the fix by re-checking the same UI metrics.

2.3 Q&A with reasoning

Q. Why does Dynamic Partition Pruning help star schemas? A fact-dim equi-join with a filter on the dim can be optimized: Spark first runs the dim filter, collects the matching dim keys, and pushes them as a partition filter on the fact scan. The fact scan only reads partitions that survived. Saves enormous I/O on large facts.

Q. Why are small files harmful on object stores? Object request/open/planning overhead and metadata can dominate tiny files. S3 prefix request-rate guidance is provider-specific, not a law of all object stores. A 10 TB table in 1 KB files is unusable. Target 128–512 MB; compact regularly.

Q. How does AQE detect skew at runtime? After each shuffle stage, AQE looks at partition sizes. For supported skew joins, a partition must exceed both the configured median factor and absolute-byte threshold; it splits that partition into sub-partitions and replicates the matching rows from the other side of the join.


Part II — Storage and Lakehouse

Object APIs define individual object visibility, while table formats define which set of files constitutes a committed snapshot. Follow the format/catalog commit and retention rules before reasoning about compaction, concurrent writes or deletion.

3. Distributed Storage — Why Object Stores Need Lakehouses

A distributed job finishing its tasks does not yet mean readers can safely see its output. Suppose half the new files have arrived while the remaining tasks are retrying: a reader needs either the old table or the committed new table, not an accidental mixture. The diagram introduces the gap between individual object operations and a table-wide commit. Provider-specific capabilities differ, as the comparison below shows; the common requirement is a consistent published table state.

3. Distributed Storage — Why Object Stores Need Lakehouses
Figure text equivalent — labels and relationships
  • Node “Compute Spark / Trino / Flink” (square).
  • Node “Object Store S3 / ADLS / GCS” (square).
  • Node “Provider-specific request-rate guidance measure workload and quotas” (square).
  • Node “Namespace/API-specific rename; not a table transaction” (square).
  • Node “Strong consistency now, but not multi-object atomic” (square).
  • Node “Lakehouse commit protocol fixes this” (square).
  • “Compute Spark / Trino / Flink” leads to “Object Store S3 / ADLS / GCS”: GET / PUT / LIST. Line: normal; ends: arrow point.
  • “Object Store S3 / ADLS / GCS” leads to “Provider-specific request-rate guidance measure workload and quotas”. Line: normal; ends: arrow point.
  • “Object Store S3 / ADLS / GCS” leads to “Namespace/API-specific rename; not a table transaction”. Line: normal; ends: arrow point.
  • “Object Store S3 / ADLS / GCS” leads to “Strong consistency now, but not multi-object atomic”. Line: normal; ends: arrow point.
  • “Namespace/API-specific rename; not a table transaction” leads to “Lakehouse commit protocol fixes this”. Line: normal; ends: arrow point.
  • “Strong consistency now, but not multi-object atomic” leads to “Lakehouse commit protocol fixes this”. Line: normal; ends: arrow point.

3.1 HDFS vs object stores at a glance

HDFSS3ADLS Gen2GCS
Hierarchyfilesystemgeneral-purpose key namespace; directory buckets differoptional hierarchical namespaceflat or hierarchical-namespace bucket
Atomic renamefilesystem operationgeneral-purpose: copy/delete; S3 Express directory buckets have a separate rename APIdirectory rename with HNScapabilities depend on bucket namespace/API
Appendsupported with filesystem rulesAPI/bucket-specific, not a general-purpose object overwrite guaranteeappend/flush via Data Lake APIsAPI/object-type-specific; not a table transaction
Multi-regionnowith replicationwith GRSwith multi-region

3.2 Why "no atomic rename" matters

Rename-based Hadoop committers stage task attempts before publication. HDFS rename is a namespace operation; general-purpose S3 uses copy/delete, with work proportional to copied bytes plus per-object operations. Attempt selection and job publication must tolerate retries/speculation. This does not imply every classic-committer configuration duplicates output, and S3 Express RenameObject is a different API.

That's why we have:

  • S3A magic/staging committers use pending multipart uploads and job/task commit protocols to avoid rename-based publication. Completing each object is atomic; completing an entire multi-file job is not one all-object atomic operation.
  • Lakehouse table formats (Iceberg/Delta/Hudi) — table commit visibility is controlled by the format and catalog/log protocol, not by assuming a multi-file directory rename. Iceberg uses atomic metadata replacement; Delta uses its transaction log; Hudi has a timeline protocol.

3.3 File format quick-take

FormatCompressionSplittableSchema evolutionPrimary use
CSV / JSONpoorpartialsloppylanding only
Avrogoodyesrow-based, schema registryKafka topics, streaming
Parquetgreat (snappy/zstd)yescolumn add/drop, by nameanalytical default
ORCgreatyesyesHive ecosystem

3.4 Performance levers

  • Avoid small files — compact to 128–512 MB Parquet.
  • Partition layout: tenant=.../date=.../hour=... for time-aligned scans.
  • Column projection + predicate pushdown with Parquet/ORC stats.
  • Z-order / Hilbert for multi-column locality (Iceberg, Delta).
  • Caching layer (Alluxio, EMRFS S3 cache) for repeated reads.

4. Lakehouse and Apache Iceberg — Tables on Top of Object Storage

4.1 The problem in one paragraph

Raw Parquet on S3/ADLS/GCS gives cheap storage. It does not give you atomic multi-file writes, safe schema evolution, row-level updates/deletes, time travel/audit, or fast pruning at petabyte scale. Iceberg, Delta, and Hudi all add a metadata layer that turns folders of files into transactional tables.

4.2 Iceberg layout — every level matters

Iceberg answers that visibility problem by making table membership explicit in metadata. Read this hierarchy from the catalog down: a reader selects a snapshot and follows its file references rather than guessing membership from whichever objects happen to be present. The same hierarchy also carries statistics, which is why it helps both correctness and query planning.

4.2 Iceberg layout — every level matters
Figure text equivalent — labels and relationships
  • Node “Catalog (REST / Glue / HMS / Nessie) atomic CAS pointer” (square).
  • Node “metadata/v3.metadata.json (current snapshot pointer)” (square).
  • Node “Manifest list (snap-xxx.avro) + partition summary stats” (square).
  • Node “Manifest 1 + per-file stats” (square).
  • Node “Manifest 2” (square).
  • Node “data/part=2026-04-30/file-1.parquet” (square).
  • Node “data/part=2026-04-30/file-2.parquet” (square).
  • Node “data/part=2026-04-29/file-3.parquet” (square).
  • “Catalog (REST / Glue / HMS / Nessie) atomic CAS pointer” leads to “metadata/v3.metadata.json (current snapshot pointer)”. Line: normal; ends: arrow point.
  • “metadata/v3.metadata.json (current snapshot pointer)” leads to “Manifest list (snap-xxx.avro) + partition summary stats”. Line: normal; ends: arrow point.
  • “Manifest list (snap-xxx.avro) + partition summary stats” leads to “Manifest 1 + per-file stats”. Line: normal; ends: arrow point.
  • “Manifest list (snap-xxx.avro) + partition summary stats” leads to “Manifest 2”. Line: normal; ends: arrow point.
  • “Manifest 1 + per-file stats” leads to “data/part=2026-04-30/file-1.parquet”. Line: normal; ends: arrow point.
  • “Manifest 1 + per-file stats” leads to “data/part=2026-04-30/file-2.parquet”. Line: normal; ends: arrow point.
  • “Manifest 2” leads to “data/part=2026-04-29/file-3.parquet”. Line: normal; ends: arrow point.
  • Catalog/commit implementation resolves current table metadata and supplies atomic replacement/commit validation. REST, metastore and filesystem implementations differ; branches/tags can retain other snapshots.
  • metadata.json records schema (with column IDs), partition spec, sort order, and snapshots.
  • Manifest list holds per-manifest partition-summary stats so a planner can prune whole manifests.
  • Manifests list data files plus per-file stats: row counts, null counts, min/max per column.
  • Data files may be Parquet, ORC or Avro according to the format and engine support.

4.3 Read path — why Iceberg scales beyond Hive

4.3 Read path — why Iceberg scales beyond Hive
Figure text equivalent — labels and relationships
  • Node “Query” (square).
  • Node “Catalog: current metadata.json” (square).
  • Node “Manifest list” (square).
  • Node “Surviving manifests” (square).
  • Node “Surviving data files” (square).
  • Node “Read” (square).
  • “Query” leads to “Catalog: current metadata.json”. Line: normal; ends: arrow point.
  • “Catalog: current metadata.json” leads to “Manifest list”. Line: normal; ends: arrow point.
  • “Manifest list” leads to “Surviving manifests”: partition prune. Line: normal; ends: arrow point.
  • “Surviving manifests” leads to “Surviving data files”: file-level prune (min/max, nulls). Line: normal; ends: arrow point.
  • “Surviving data files” leads to “Read”: column projection + row-group pruning. Line: normal; ends: arrow point.

Older Hive-style layouts relied on directory listings to discover files; historical S3 consistency limitations added difficulty to that approach. Iceberg reads a small set of metadata files that already enumerate data files plus stats — most pruning happens before any data file is opened. For a query limited to one day, follow partition pruning to the surviving manifests, then file statistics to the surviving files. Strong object-store consistency alone does not provide that snapshot membership or eliminate the cost of discovering and planning a large file set.

4.4 Write path and commit protocol

  1. Each task writes Parquet to data/.
  2. On task completion, manifest entries are assembled.
  3. Driver builds a new metadata.json referencing a new snapshot.
  4. Driver requests atomic replacement of the observed metadata with the new metadata. Conceptual CAS, not a literal universal commit(currentSnap, newMetadata) API; catalog implementations define the request and validation.
  5. On conflict, validate whether the operation remains safe on the new state. Compatible appends may retry metadata assembly; conflicting row-level operations may fail and need recomputation. Blind retry is not correct for every write.

That's optimistic concurrency. The atomic replacement establishes visibility; operation-specific conflict checks establish whether concurrent changes are compatible.

4.5 Killer features and why they matter

FeatureWhat it gives you
Hidden partitioningDeclare PARTITIONED BY (days(ts), bucket(16, user_id)); users query natural columns and Iceberg rewrites predicates.
Partition evolutionChange strategy without rewriting old data.
Schema evolution by column IDRenames are safe — identity stays the same.
Time travelFOR VERSION AS OF / FOR TIMESTAMP AS OF for audit and rollback.
Row-level opsMerge-on-read (delete files, position deletes, equality deletes) or copy-on-write.
Multi-engineMultiple engines support Iceberg, but catalog, credentials, spec version, delete types and read/write capabilities must be checked per integration.

4.6 Iceberg vs Delta vs Hudi

NeedCandidate to evaluate
Multi-engine (Spark + Trino + Flink + Snowflake)Iceberg
Tightly coupled with Databricks ecosystemDelta (UniForm makes it Iceberg-readable too)
High-velocity streaming upserts (CDC)Hudi (or Iceberg + Flink)
Time-travel + branch/tag for data scienceIceberg with Nessie or native tags

Hudi has two flavors:

  • Copy-on-Write rewrites whole files on update — fast reads, expensive writes.
  • Merge-on-Read appends log/delta records merged with base files at read — fast writes, slower reads until compaction.

4.7 Maintenance you cannot skip

JobWhy
expire_snapshotsBound metadata growth, free old data files.
rewrite_data_filesCompact small files into 128–512 MB chunks.
rewrite_manifestsKeep manifest count low for fast planning.
remove_orphan_filesClean up files left by aborted writes.

Skip these and the table eventually slows to a crawl.

4.8 Governance hooks (why this matters for catalogs)

Iceberg metadata is itself queryable:

SELECT * FROM catalog.db.table.snapshots;
SELECT * FROM catalog.db.table.files;
SELECT * FROM catalog.db.table.partitions;
SELECT * FROM catalog.db.table.history;

That supports snapshot/file audits and retention operations; it is not complete business lineage or a legal-erasure proof. Deletion must also account for branches/tags, copies, backups, object versions, caches and retention/legal holds.

4.9 Q&A with reasoning

Q1. Parquet vs Iceberg — same thing? No. Parquet is a file format (columnar layout inside one file). Iceberg is a table format (which files belong to the table at which snapshot, with what schema). Iceberg tables contain Parquet files.

Q2. Why does Iceberg scale where Hive directory listing doesn't? Hive reads tables by listing directories. Iceberg reads metadata files that already enumerate files plus min/max stats, so most pruning happens before any data file is opened.

Q3. How does Iceberg handle two writers at once? Optimistic concurrency: each writer prepares new metadata referencing the snapshot it read, then performs a single catalog-level CAS. A conflicting writer validates against the new state; some operations can retry while incompatible updates fail and require recomputation.

Q4. Equality deletes vs position deletes? Position deletes identify a data-file path and row position. Equality deletes match values in a specified field-ID set, not arbitrary SQL predicates. Readers reconcile applicable deletes under spec/sequence rules; implementation, sort order and workload determine relative cost. Engine/spec support must be checked.

Q5. How would you do GDPR delete on a 10 PB table?

  1. Authorize and record deletion scope. 2) Execute supported DELETE with the engine/table copy-on-write or merge-on-read mode. 3) Rewrite affected files where needed and verify current reads. 4) Expire eligible snapshots only after checking branch/tag references, legal retention and in-flight jobs. 5) Safely remove unreferenced/orphan files with an age margin. 6) Reconcile object versions, replicas, backups and derived indexes under policy. Snapshot expiry does not guarantee global unrecoverability or legal compliance.

Graph and search indexes optimize different questions over authoritative data. Trace edge direction and bounded traversal for lineage, then analyzed terms, refresh and exact-value authorization filters for discovery; both indexes need rebuild and freshness contracts.

5. Graph Databases — Why Lineage Is Naturally a Graph

Some questions are fundamentally about relationships: "what depends on this column, 5 hops downstream?" Recursive SQL and graph traversal both pay for visited edges, filtering and result size. Neo4j-style native adjacency can make neighbor access efficient once an indexed start node is found; no database family is universally faster for every lineage graph.

5.1 Property graph model for lineage

5.1 Property graph model for lineage
Figure text equivalent — labels and relationships
  • Node “Job :Job” (square).
  • Node “Dataset orders :Dataset” (square).
  • Node “Column total :Column” (square).
  • Node “Dataset orders_eu :Dataset” (square).
  • Node “Column eu_total :Column” (square).
  • Node “Tag PII :Tag” (square).
  • Node “User Alice :User” (square).
  • Node “Policy mask_eu :Policy” (square).
  • “Job :Job” leads to “Dataset orders :Dataset”: WRITES. Line: normal; ends: arrow point.
  • “Dataset orders :Dataset” leads to “Column total :Column”: HAS. Line: normal; ends: arrow point.
  • “Dataset orders_eu :Dataset” leads to “Column eu_total :Column”: HAS. Line: normal; ends: arrow point.
  • “Column eu_total :Column” leads to “Column total :Column”: DERIVED_FROM. Line: normal; ends: arrow point.
  • “Column eu_total :Column” leads to “Tag PII :Tag”: TAGGED_AS. Line: normal; ends: arrow point.
  • “User Alice :User” leads to “Dataset orders :Dataset”: OWNS. Line: normal; ends: arrow point.
  • “Policy mask_eu :Policy” leads to “Column eu_total :Column”: GOVERNS. Line: normal; ends: arrow point.

5.2 Cypher essentials

These Neo4j 5 reference queries assume a single authorized tenant graph and indexed unique asset IDs. In a shared graph, bind tenant scope from authenticated context and constrain every traversed node/edge; filtering only the start node or trusting a caller-supplied tenant is insufficient. The final shortest-path example permits any relationship type deliberately; restrict types for lineage-only semantics.

// Anchor on an indexed node, bound depth
MATCH (start:Column {id:'sales.amount'})
MATCH (start)<-[:DERIVED_FROM*1..5]-(down:Column)
RETURN DISTINCT down.id;
 
// Temporal lineage — what was upstream on a date?
MATCH (a:Dataset {id:$id})-[r:DERIVED_FROM]->(b)
WHERE r.valid_from <= datetime($asOf)
  AND (r.valid_to IS NULL OR datetime($asOf) < r.valid_to)
RETURN b;
 
// Shortest path between two assets
MATCH p = shortestPath(
  (a:Dataset {id:'src'})-[*1..5]->(b:Dataset {id:'dst'}))
RETURN p;

5.3 Things that explode at scale

  • Unanchored traversals — start from indexed nodes, always.
  • Supernodes — a Tag connected to millions of columns will blow up any traversal that touches it. Split by tenant/domain/time, or filter relationship types early.
  • Unbounded depth — always cap with *1..N.

5.4 Q&A with reasoning

Q. Why is index-free adjacency fast? Once the start node is found via index, each hop follows stored relationship pointers. This can avoid repeated global lookup work; relational adjacency with suitable indexes can also be efficient. Output size and supernodes still dominate some queries.

Q. How do you model temporal lineage? Put valid_from and valid_to on relationships, or version snapshots. Then "what was upstream on 2026-04-30?" becomes a predicate filter.

Q. When is a graph DB the wrong choice? When the workload is mostly aggregations, transactional records, or wide tabular reporting. Use relational/Iceberg for source of truth and graph as a traversal index that can be rebuilt from the source of truth.

Q. What is a supernode and why is it bad? A node with extremely high degree (tag connected to millions of columns). Traversals through it explode. Mitigations: filter by relationship type at the supernode, partition the supernode by tenant/domain, or skip it in path expansions.


6.1 Why Elasticsearch exists

Full-text discovery needs ranking, tokenization, typo tolerance, filters, facets, and low latency across millions or billions of documents. An unindexed substring scan is costly and lacks full-text ranking semantics; PostgreSQL full-text/trigram indexes and other engines are alternatives, not inherently incapable.

6.2 Architecture

6.2 Architecture
Figure text equivalent — labels and relationships
  • Node “Document” (square).
  • Node “Shard (primary)” (square).
  • Node “Lucene segment 1 (immutable)” (square).
  • Node “Lucene segment 2” (square).
  • Node “Lucene segment 3” (square).
  • Node “Larger segment” (square).
  • Node “Shard (replica)” (square).
  • “Document” leads to “Shard (primary)”: index. Line: normal; ends: arrow point.
  • “Shard (primary)” leads to “Lucene segment 1 (immutable)”. Line: normal; ends: arrow point.
  • “Shard (primary)” leads to “Lucene segment 2”. Line: normal; ends: arrow point.
  • “Shard (primary)” leads to “Lucene segment 3”. Line: normal; ends: arrow point.
  • “Lucene segment 1 (immutable)” leads to “Larger segment”: merge. Line: dotted; ends: arrow point.
  • “Lucene segment 2” leads to “Larger segment”: merge. Line: dotted; ends: arrow point.
  • “Shard (primary)” leads to “Shard (replica)”: replicate. Line: normal; ends: arrow point.
  • A shard is a Lucene index. Segments are immutable inverted-index files; refresh creates new segments; merge consolidates.
  • Primary shards scale indexing throughput; replicas scale read throughput and provide HA.

6.3 Inverted index + scoring

"customer" → [(d1, tf=3, pos=[1,5,10]), (d4, tf=1, pos=[2]), ...]
Term customer maps to posting entries (d1, tf=3, positions 1/5/10) and (d4, tf=1, position 2), followed by an ellipsis.
Figure text and layout
"customer" → [(d1, tf=3, pos=[1,5,10]), (d4, tf=1, pos=[2]), ...]
Text diagram
  • BM25 scoring (default since 5.x).
  • Analyzer = tokenizer + filters (lowercase, stemmers, synonyms).
  • Mapping: text (analyzed for full-text) vs keyword (exact for filters/aggregations/sort).

6.4 Document lifecycle

  1. Index → translog + in-memory buffer.
  2. Refresh → newly indexed data becomes searchable. Elastic Stack commonly defaults to 1 s for recently searched indices; serverless defaults and activity rules differ. Refresh is not the durability/fsync boundary.
  3. Flush creates a Lucene commit and starts a new translog generation; translog durability and fsync policies are separate from refresh.
  4. Merge → fewer larger segments. force_merge on read-only indexes.

6.5 Query DSL — what to actually write

{
  "query": {
    "bool": {
      "must":   [{"match": {"name": "customer"}}],
      "should": [{"match": {"description": "PII"}}],
      "filter": [{"term": {"tenant": "acme"}}, {"term": {"sensitivity": "high"}}],
      "must_not": [{"term": {"deleted": true}}]
    }
  },
  "aggs": {"by_tag": {"terms": {"field": "tags.keyword"}}}
}
  • filter doesn't score and is cacheable — put exact-match conditions there.
  • match is analyzed; term is exact (use on appropriately mapped exact-value fields, including keyword/numeric/date/boolean as applicable).
  • Multi-match for cross-field; phrase with slop; fuzzy for typo tolerance; k-NN for vector search (8.x+).

6.6 Capacity planning rules

  • Shard size sweet spot 10–50 GB. Too small → overhead. Too large → slow recovery, slow merges.
  • Heap sizing: follow version-specific defaults and keep below the actual compressed-oops threshold (Elastic documents 26 GB as generally safe, sometimes about 30 GB), with room for OS file cache/native memory. 32 GB is not a universal boundary.
  • Index lifecycle (ILM) — hot/warm/cold/frozen tiers.

6.7 Operational pitfalls

  • Mapping explosion — dynamic mapping on user JSON creating thousands of fields. Use dynamic: strict or dynamic: false.
  • Deep pagination (from + size > 10k) — use search_after with PIT.
  • Refresh interval of 1 s is wasteful for bulk loads — set to -1 then back.
  • Hot shards — node-level imbalance; use shard allocation awareness.

6.8 Q&A with reasoning

Q. Why limit JVM heap to 32 GB? The compressed-oops threshold depends on JVM/platform; verify it in node logs. Elastic recommends leaving substantial memory for filesystem cache/native uses and warns that oversized heaps can hurt pauses.

Q. match vs term? match analyzes the query text (lowercases, stems) and searches the analyzed field; term is exact byte match against the indexed token. Use term on keyword fields, not on text.

Q. What is hybrid retrieval? BM25 (keyword) + dense vector (semantic), fused with Reciprocal Rank Fusion (RRF). BM25 catches exact terms; dense catches paraphrases. They can complement one another; measure retrieval recall, relevance and latency on the target queries.


Part IV — AI / ML

Attention, vector retrieval and tool orchestration introduce different compute and trust boundaries. Derive the attention/cache costs, evaluate retrieval evidence, and keep model proposals separate from authorized actions and verified answers.

7. LLMs — What's Actually Inside the Box

7.1 The honest definition

An LLM is a neural network trained to predict the next token given previous tokens. Many chat LLMs use next-token pretraining plus post-training; architectures/objectives and tool/retrieval augmentation vary. This description is not a mechanistic explanation of every capability. It is not a database. It compresses patterns into weights — that's why it can hallucinate.

7.2 Transformer architecture

7.2 Transformer architecture
Figure text equivalent — labels and relationships
  • Node “Tokens” (square).
  • Node “Token embeddings” (square).
  • Node “Layer 1: Attention with position handling + FFN + Norm (architecture-specific)” (square).
  • Node “Layer 2 ... N” (square).
  • Node “Output projection then softmax then next token” (square).
  • Node “KV cache (per layer)” (square).
  • “Tokens” leads to “Token embeddings”. Line: normal; ends: arrow point.
  • “Token embeddings” leads to “Layer 1: Attention with position handling + FFN + Norm (architecture-specific)”. Line: normal; ends: arrow point.
  • “Layer 1: Attention with position handling + FFN + Norm (architecture-specific)” leads to “Layer 2 ... N”. Line: normal; ends: arrow point.
  • “Layer 2 ... N” leads to “Output projection then softmax then next token”. Line: normal; ends: arrow point.
  • “Output projection then softmax then next token” leads to “Tokens”: append, repeat. Line: normal; ends: arrow point.
  • “Layer 1: Attention with position handling + FFN + Norm (architecture-specific)” leads to “KV cache (per layer)”: cache K, V. Line: dotted; ends: arrow point.
  • “Layer 2 ... N” leads to “KV cache (per layer)”. Line: dotted; ends: arrow point.
  • Self-attention: softmax(QKT/dk)V\text{softmax}(QK^T / \sqrt{d_k}) V. Cost is O(n2⋅d)O(n^2 \cdot d) in sequence length.
  • Multi-head: project into h heads, run attention in parallel, concatenate.
  • Decoder-only (for example published Llama/Mistral architectures) — generation; do not infer undisclosed proprietary internals. Encoder-only (BERT) — embeddings/classification. Encoder-decoder (T5) — translation/summarization.
  • Modern: RMSNorm, SwiGLU activation, RoPE position encoding, GQA (grouped-query attention) to shrink KV cache.

7.3 Why long context is expensive — and what saves it

  • Attention is O(n2)O(n^2) in sequence length, so a 128k-token prompt costs ~16k× more than a 1k prompt for the attention matrix alone.
  • KV cache stores prior keys/values so each new query attends to n cached positions without recomputing all prior projections/states. Dense attention for that decode step is O(n d); total generation across n new tokens still accumulates quadratic attention work. Cache memory grows with retained context.
  • FlashAttention is an IO-aware kernel that tiles attention to keep working sets in SRAM and avoids materializing the full n×nn \times n matrix.
  • RoPE scaling / NTK / YaRN alter position handling; usable length and retained quality require model-specific training/evaluation, not only a configuration change.
  • Mixture-of-Experts (MoE): only a subset of expert FFNs activate per token (e.g., 2 of 8). Active FFN compute can fall relative to a dense model with the same total parameter count; routing, communication and memory determine actual throughput.

An embedding maps text to a dense vector (typically 768–3072 dimensions). Similarity is cosine (after normalization, equivalent to dot product). Searching billions of vectors fast uses approximate indexes:

IndexIdeaNotes
HNSWhierarchical navigable small-world graphone ANN option in several libraries/services; pgvector defaults to exact nearest-neighbor search unless an index is created
IVF-PQcoarse cluster + product quantizationmemory-efficient
ScaNNpartition + quantizationGoogle's

The trade-off triangle: recall ↔ latency ↔ memory. Tune efSearch/nprobe to move along that triangle.

7.5 Inference optimizations

  • Quantization — INT8/INT4 reduce weight representation size relative to float formats; scale/metadata, kernels, KV cache and quality constraints determine real memory/latency savings.
  • Speculative decoding — a small draft model proposes tokens; the big model verifies in parallel.
  • Continuous batching admits new requests as others finish; PagedAttention manages KV storage. They are related serving mechanisms, not a universal 5–20× benchmark.
  • Distillation — train a smaller student from a larger teacher.

7.6 Fine-tuning vs RAG

GoalTool
Inject up-to-date private knowledgeRAG
Change tone, format, domain styleLoRA / QLoRA
Teach a brand-new skill where prompting failsfull SFT
Align with human preferencesRLHF / DPO

Choose the intervention from the failure: retrieval for missing authorized facts, prompting/schema/tool changes for workflow, and evaluated fine-tuning for learned behavior. LoRA is a parameter-efficient training method and can be used for SFT; these are not mutually exclusive product tiers.

7.7 Q&A with reasoning

Q. Why is attention O(n2)O(n^2)? Every token computes a similarity score against every other token. n queries × n keys = n2n^2 pairs.

Q. Why does the KV cache help? In a causal decoder, cached prior K/V remain valid for a fixed prefix. The new query attends to n retained positions in O(n d), instead of recomputing full-prefix attention; growing-context generation still accumulates quadratic attention work.

Q. What does FlashAttention break? The memory wall — it avoids writing the full attention matrix to HBM, instead streaming tiles through fast SRAM. Same math, much less DRAM traffic.

Q. Why do tokenizers matter for cost? Token-based context limits and tariffs count the selected model tokenizer’s output. Count customer_account_id_v2 with that tokenizer; character length or rarity alone does not determine a fixed token count or price.

Q. Why does normalization matter for cosine vs dot product? Dot product equals the product of the two norms times cosine; magnitude and direction both matter. Increasing magnitude makes a negative dot product more negative, not a better high-score match. Normalize nonzero vectors to unit length and dot equals cosine.

Q. RLHF vs DPO? Both align the model to preferences over candidate pairs. A common RLHF pipeline fits a reward model and optimizes a policy, often with PPO; RLHF is broader than that one optimizer. DPO directly optimizes a preference objective without an explicit separately trained reward model in that recipe. Stability/quality are empirical, not guaranteed.


8. RAG and Agentic Systems — Models That Reach for Data and Tools

8.1 Vanilla RAG pipeline

The model’s weights are not the platform’s current catalogue. To answer “Which dataset contains customer contact details?”, the service must first find relevant, authorized records. Follow the lower indexing branch into the query-time retrieval path, then watch where retrieved material enters the prompt. That join is the crucial boundary: a fluent answer cannot compensate for missing evidence or a tenant filter applied too late.

8.1 Vanilla RAG pipeline
Figure text equivalent — labels and relationships
  • Node “User query” (square).
  • Node “Query rewrite / HyDE expansion” (square).
  • Node “Hybrid retrieval BM25 + dense + tenant filter” (square).
  • Node “Reranker (cross-encoder)” (square).
  • Node “Prompt assembly system + chunks + query” (square).
  • Node “LLM” (square).
  • Node “Answer + citations” (square).
  • Node “Vector index + keyword index” (cylinder).
  • Node “Source docs / metadata” (square).
  • “User query” leads to “Query rewrite / HyDE expansion”. Line: normal; ends: arrow point.
  • “Query rewrite / HyDE expansion” leads to “Hybrid retrieval BM25 + dense + tenant filter”. Line: normal; ends: arrow point.
  • “Hybrid retrieval BM25 + dense + tenant filter” leads to “Reranker (cross-encoder)”. Line: normal; ends: arrow point.
  • “Reranker (cross-encoder)” leads to “Prompt assembly system + chunks + query”. Line: normal; ends: arrow point.
  • “Prompt assembly system + chunks + query” leads to “LLM”. Line: normal; ends: arrow point.
  • “LLM” leads to “Answer + citations”. Line: normal; ends: arrow point.
  • “Vector index + keyword index” leads to “Hybrid retrieval BM25 + dense + tenant filter”. Line: normal; ends: arrow point.
  • “Source docs / metadata” leads to “Vector index + keyword index”: chunk + embed. Line: normal; ends: arrow point.

8.2 Quality levers, in order of impact

The ordering is a diagnostic starting point, not a universal ranking; evaluate ablations for the actual corpus and task.

  1. Chunking — semantic, with 10–20 % overlap, with metadata (source, owner, tenant, sensitivity, timestamp). For tabular metadata, chunk = a dataset card with name, description, key columns, owner, tags.
  2. Hybrid retrieval — BM25 + dense, fused via Reciprocal Rank Fusion.
  3. Query rewriting — HyDE (have the LLM imagine an answer, embed it, retrieve real docs by similarity), multi-query, decomposition.
  4. Reranker — cross-encoder (bge-reranker, Cohere rerank) over top-50 → top-K.
  5. Pre-retrieval metadata filters — tenant, time, sensitivity. Authorize the retrieval universe and enforce again before exposure. Some ANN engines apply filters after approximate scanning internally; use supported filtered/exact/iterative strategies and do not expose unauthorized intermediate results.
  6. Prompt — citations format, structured output via JSON schema or function calling.
  7. Caching answers by tenant, authorization/audience version, query/context, content and model/prompt version. Stale-while-revalidate is inappropriate where stale permissions could expose revoked content.

8.3 Why hybrid retrieval can complement either alone

  • Dense embeddings nail semantic paraphrase ("client identifier" → customer_id).
  • BM25 nails exact identifiers, code symbols, rare terms (PII_CUSTOMER_EMAIL_V2).
  • RRF avoids directly comparing incompatible score scales, but has parameters (rank constant k, candidate windows and possibly weights):
score(d) = Σ over rankers   1 / (k + rank_r(d))         # k often 60

8.4 Evaluation (treat it as a test suite)

LayerMetrics
Retrievalrecall@k, precision@k, MRR, NDCG
Generationfaithfulness, answer relevance, context precision/recall
Agenttask success, tool error rate, iterations, cost per task

Tools: Ragas, TruLens, DeepEval, Langfuse. Use LLM-as-judge with a calibration set, plus periodic human review.

8.5 Agentic loop

8.5 Agentic loop
Figure text equivalent — labels and relationships
  • Node “Plan” (square).
  • Node “Act (call a tool)” (square).
  • Node “Observe (tool result)” (square).
  • Node “Reflect done? need more?” (square).
  • Node “Final answer + trace” (square).
  • “Plan” leads to “Act (call a tool)”. Line: normal; ends: arrow point.
  • “Act (call a tool)” leads to “Observe (tool result)”. Line: normal; ends: arrow point.
  • “Observe (tool result)” leads to “Reflect done? need more?”. Line: normal; ends: arrow point.
  • “Reflect done? need more?” leads to “Plan”: continue. Line: normal; ends: arrow point.
  • “Reflect done? need more?” leads to “Final answer + trace”: stop. Line: normal; ends: arrow point.

Useful agent = LLM + a small set of typed tools + a loop with hard limits:

  • max tool calls per task,
  • token + dollar budget,
  • wall-clock timeout,
  • deterministic fallbacks for parts that should never depend on an LLM.

8.6 Worked example — autonomous PII classifier

A new column gives the agent loop a concrete task: decide whether the column needs a sensitive-data tag, with evidence a steward can inspect. Trace a clearly formatted email through the early rule branch, then contrast an ambiguous identifier that needs profiling and similar-column retrieval. The extra tool calls are justified only when they resolve uncertainty; a confidence score must not bypass tool authorization or the review policy.

8.6 Worked example — autonomous PII classifier
Figure text equivalent — labels and relationships
  • Node “New column” (square).
  • Node “Regex (SSN, email, IBAN)” (square).
  • Node “Proposed tag + evidence and policy gate” (square).
  • Node “Dictionary (cities, names, codes)” (square).
  • Node “ML / NER classifier” (square).
  • Node “profile_column (Spark sample)” (square).
  • Node “vector_search (similar past columns)” (square).
  • Node “LLM Judge: aggregate + rationale + confidence” (square).
  • Node “Authorized proposal commit plus replayable index update” (square).
  • Node “Human steward review” (square).
  • Node “labeled example weekly retrain” (square).
  • “New column” leads to “Regex (SSN, email, IBAN)”. Line: normal; ends: arrow point.
  • “Regex (SSN, email, IBAN)” leads to “Proposed tag + evidence and policy gate”: hit. Line: normal; ends: arrow point.
  • “Regex (SSN, email, IBAN)” leads to “Dictionary (cities, names, codes)”: miss. Line: normal; ends: arrow point.
  • “Dictionary (cities, names, codes)” leads to “Proposed tag + evidence and policy gate”: hit. Line: normal; ends: arrow point.
  • “Dictionary (cities, names, codes)” leads to “ML / NER classifier”: miss. Line: normal; ends: arrow point.
  • “ML / NER classifier” leads to “Proposed tag + evidence and policy gate”: high conf. Line: normal; ends: arrow point.
  • “ML / NER classifier” leads to “profile_column (Spark sample)”: ambiguous. Line: normal; ends: arrow point.
  • “profile_column (Spark sample)” leads to “vector_search (similar past columns)”. Line: normal; ends: arrow point.
  • “vector_search (similar past columns)” leads to “LLM Judge: aggregate + rationale + confidence”. Line: normal; ends: arrow point.
  • “LLM Judge: aggregate + rationale + confidence” leads to “Authorized proposal commit plus replayable index update”: conf >= threshold. Line: normal; ends: arrow point.
  • “LLM Judge: aggregate + rationale + confidence” leads to “Human steward review”: conf < threshold. Line: normal; ends: arrow point.
  • “Human steward review” leads to “labeled example weekly retrain”. Line: normal; ends: arrow point.
  • “labeled example weekly retrain” leads to “ML / NER classifier”. Line: normal; ends: arrow point.

Design rules to defend in design review:

  • Determinism first, LLM only for ambiguity and rationale.
  • Tools are narrow and typed (profile_column, vector_search, propose_tag, write_to_catalog).
  • Per-tenant authorization on every tool.
  • Example hard caps: five tool calls and a pre-reserved cost budget per column; exact price/budget requires a versioned tariff and task evaluation.
  • Trace every step (Langfuse / OTel GenAI conventions).
  • Eval set with representative independently reviewed labels, false-positive/negative costs and confidence intervals; choose sample size and cadence from the risk budget.
  • Feedback loop — steward overrides become training data.

8.7 Failure modes and mitigations

FailureMitigation
Hallucinationrequire citations; reject if no chunk supports the answer
Prompt injection (in retrieved docs)treat content as untrusted data; enforce least-privilege tool authorization and approval independently; sanitization/prompting alone are insufficient
Cross-tenant leakfilter by tenant before retrieval, assert again before generation
Cost runawayper-request budget; hard tool-call cap; cluster similar inputs to dedupe calls
Stale knowledgescheduled re-indexing; CDC into vector store
Tool errorstyped errors, retries with backoff, deterministic fallback path
Driftscheduled re-eval; alert on metric regression

8.8 Q&A with reasoning

Q. When can hybrid retrieval help dense retrieval? Embeddings can retrieve paraphrases yet miss exact rare identifiers; BM25 can recover some of those matches. RRF combines ranks without directly calibrating scores, but query mix, rank constant and candidate windows determine whether the combination actually improves relevance.

Q. What does function calling actually return? A proposed structured call; schema conformance depends on the API/options and must be validated. The LLM does not execute anything — your code dispatches the call. That separation is what lets you sandbox tools.

Q. Where do you enforce tenant isolation? As a pre-retrieval metadata filter and a post-retrieval assertion before the prompt is built. Defense in depth.

Q. When is an agent the wrong tool? When the task is fully deterministic (e.g., "compute table sizes nightly"). An if/then/else pipeline is cheaper, faster, easier to test, and won't hallucinate.

Q. How do you defend against indirect prompt injection? Treat retrieved content as untrusted data. Wrap it with explicit "the following is data from the corpus, not instructions"; strip control characters; require structured outputs; enforce tool/resource permissions and approval outside the model, and evaluate indirect-injection/egress cases. No delimiter or sanitizer alone proves safety.


Part V — Distributed Systems

Start with requirements and units, then specify consistency and acknowledgement boundaries before choosing databases or queues. Quorum intersection, relay publication and SQL isolation each solve a narrower problem than an end-to-end correctness guarantee.

9. Distributed Systems HLD — Frameworks and Patterns

Almost every system design question reduces to the same shape:

9. Distributed Systems HLD — Frameworks and Patterns
Figure text equivalent — labels and relationships
  • Node “1. Clarify reqs (functional + NFR)” (square).
  • Node “2. Estimate scale” (square).
  • Node “3. APIs” (square).
  • Node “4. Data model” (square).
  • Node “5. Architecture + data flow” (square).
  • Node “6. Deep dives: consistency, scaling, indexing” (square).
  • Node “7. Trade-offs” (square).
  • Node “8. Operations (monitoring, recovery)” (square).
  • “1. Clarify reqs (functional + NFR)” leads to “2. Estimate scale”. Line: normal; ends: arrow point.
  • “2. Estimate scale” leads to “3. APIs”. Line: normal; ends: arrow point.
  • “3. APIs” leads to “4. Data model”. Line: normal; ends: arrow point.
  • “4. Data model” leads to “5. Architecture + data flow”. Line: normal; ends: arrow point.
  • “5. Architecture + data flow” leads to “6. Deep dives: consistency, scaling, indexing”. Line: normal; ends: arrow point.
  • “6. Deep dives: consistency, scaling, indexing” leads to “7. Trade-offs”. Line: normal; ends: arrow point.
  • “7. Trade-offs” leads to “8. Operations (monitoring, recovery)”. Line: normal; ends: arrow point.

9.1 Capacity math you should know cold

  • 1 day ≈ 86,400 s ≈ ~10⁵.
  • 1 M req/day ≈ 12 RPS; 1 B/day ≈ 12 k RPS.
  • 10 Gbit/s is 1.25 GB/s raw decimal line rate before protocol overhead.
  • SSD/NVMe IOPS, PostgreSQL QPS, Kafka events/s and LLM tokens/s depend on hardware, request size, durability, model and concurrency. Measure them; the original universal per-node ranges are withdrawn.
  • S3 request-rate guidance is bucket/API-specific and may scale with prefixes; it is not a capacity guarantee for another object store.
  • For a time-based SLO over a 30-day window: 99.9% allows 43.2 minutes unavailable; 99.99% allows 4.32 minutes. A request-based error budget is a fraction of eligible requests, not a downtime conversion.

9.2 Consistency, replication, consensus

  • CAP under partition: pick C or A. Real systems live on PACELC — Else trade Latency vs Consistency.
  • Quorum: R+W>NR + W > N proves intersection for fixed replica sets, not linearizability by itself. Version/order and failure rules still matter.
  • Replication models:
    • Leader-follower — one write authority, but read consistency depends on replica lag, routing and replication/acknowledgement settings.
    • Multi-leader — write anywhere, conflicts must be resolved (CRDT, last-writer-wins, application logic).
    • Leaderless (Dynamo / Cassandra) — quorum reads/writes, hinted handoff.
  • Consensus — Raft (for example etcd), Paxos-family protocols and ZooKeeper's distinct Zab protocol coordinate replicated state; do not call all three the same algorithm. Need 3 nodes to tolerate 1 failure, 5 to tolerate 2.
  • Time — NTP/PTP, hybrid logical clocks, vector clocks, Lamport timestamps, TrueTime (Spanner).

9.3 Building blocks (2026 default picks)

Interpret these as examples to evaluate, not a current market-ranking or default-vendor mandate.

BlockTech
Load balancerEnvoy, ALB, GCLB
API gatewayKong, Apigee, AWS API GW
CacheRedis Cluster, Memcached
Pub/subKafka, Pulsar, Kinesis, Pub/Sub
QueueSQS, RabbitMQ, Service Bus
OLTP DBPostgres / Aurora / Cloud SQL / Spanner / CockroachDB
Wide-column / KVDynamoDB, Cassandra, ScyllaDB, Bigtable
SearchElasticsearch / OpenSearch
Vectorpgvector, Pinecone, Weaviate, Elastic k-NN
Lake / LakehouseS3 + Iceberg + Trino / Spark
DWSnowflake / BigQuery / Redshift
Stream procSpark Structured Streaming, Flink
ObservabilityProm + Grafana + OTel + Loki/Tempo
Coordinationetcd, ZK, Consul

9.4 Patterns you'll reach for

  • Outbox pattern — atomically persist business row plus outbox row, then relay at least once. It closes a local lost-event gap; publication and external effects still need retries/deduplication.
  • Saga — cross-service transactions with compensating actions when 2PC isn't viable.
  • Idempotency keys — bind principal, operation and request payload to a durably recorded outcome; atomically dedup with the effect. A header alone cannot make retries safe.
  • CQRS / event sourcing — separate write model from read model.
  • Backpressure + bounded queues.
  • Retry + exponential backoff + jitter + circuit breaker + bulkhead.
  • Cache stampede mitigation: request coalescing, jittered TTL, stale-while-revalidate.
  • Hot key — salt, secondary indexes, replicate.
  • Schema migration without downtime — expand → migrate → contract.
  • Backfills without overload — rate-limited, idempotent, resumable.

9.5 The outbox pattern in detail

An order saved without its notification event is a useful failure to reason about: the database says it happened, but downstream services never hear of it. The diagram puts both durable records inside one transaction and moves publication to a separate relay. Follow that relay carefully—it closes the lost-event gap, but retries still require the idempotent consumers shown on the right.

9.5 The outbox pattern in detail
Figure text equivalent — labels and relationships
  • Node “Service” (square).
  • Node “Postgres” (cylinder).
  • Node “Relay / Debezium (reads outbox table)” (square).
  • Node “Kafka topic” (square).
  • Node “Consumer A (idempotent)” (square).
  • Node “Consumer B (idempotent)” (square).
  • “Service” leads to “Postgres”: single tx: insert order + insert outbox row. Line: normal; ends: arrow point.
  • “Postgres” leads to “Relay / Debezium (reads outbox table)”. Line: normal; ends: arrow point.
  • “Relay / Debezium (reads outbox table)” leads to “Kafka topic”: publish. Line: normal; ends: arrow point.
  • “Kafka topic” leads to “Consumer A (idempotent)”. Line: normal; ends: arrow point.
  • “Kafka topic” leads to “Consumer B (idempotent)”. Line: normal; ends: arrow point.

What it solves: the dual-write problem. If you write to the DB and then publish to Kafka as two separate calls, a crash between them leaves the system inconsistent. Outbox writes the event row in the same DB transaction as the business row. A separate relay reads outbox entries and publishes them; consumers must be idempotent (dedup by event id).

9.6 Worked example — multi-tenant lineage service

Apply that reliability question to lineage: a completed Spark job must eventually be visible in both the audit history and the dependency graph. These stores serve different readers, so the two arrows from ingestion are not an atomic transaction. Read the architecture with a failure in mind: if the graph update fails after durable capture, the service needs replay and a way to expose freshness rather than pretending the two views changed together.

9.6 Worked example — multi-tenant lineage service
Figure text equivalent — labels and relationships
  • Node “Producers Spark / Airflow / dbt” (square).
  • Node “Kafka (topic per tenant)” (square).
  • Node “Streaming ingestor validate, dedupe, enrich” (square).
  • Node “Iceberg raw run events (audit + replay)” (cylinder).
  • Node “Graph store Neo4j / Neptune” (cylinder).
  • Node “Query API” (square).
  • Node “UI / agents upstream/downstream, column-level diff, impact analysis” (square).
  • “Producers Spark / Airflow / dbt” leads to “Kafka (topic per tenant)”: OpenLineage RunEvents. Line: normal; ends: arrow point.
  • “Kafka (topic per tenant)” leads to “Streaming ingestor validate, dedupe, enrich”. Line: normal; ends: arrow point.
  • “Streaming ingestor validate, dedupe, enrich” leads to “Iceberg raw run events (audit + replay)”. Line: normal; ends: arrow point.
  • “Streaming ingestor validate, dedupe, enrich” leads to “Graph store Neo4j / Neptune”. Line: normal; ends: arrow point.
  • “Iceberg raw run events (audit + replay)” leads to “Query API”. Line: normal; ends: arrow point.
  • “Graph store Neo4j / Neptune” leads to “Query API”. Line: normal; ends: arrow point.
  • “Query API” leads to “UI / agents upstream/downstream, column-level diff, impact analysis”. Line: normal; ends: arrow point.

Why two stores? Iceberg is the durable source of truth (cheap, replayable, auditable). The graph store is a traversal index that can be rebuilt from Iceberg if it corrupts. Writes go to both; the graph is eventually consistent with Iceberg.

9.7 Common HLD designs to be ready for

  1. Distributed metadata catalog (Google-scale).
  2. Lineage service spanning lakehouse + DW + dashboards.
  3. PII discovery / classification platform.
  4. Real-time data quality scoring on streams.
  5. Multi-tenant search service.
  6. Vector search at scale (multi-tenant RAG).
  7. Idempotent ingestion service with exactly-once into Iceberg.
  8. Job orchestrator for Spark DAGs with retries and SLA.
  9. Distributed rate limiter / API gateway.
  10. CDC pipeline Postgres → Kafka → Iceberg → DW.
  11. Feature store for ML.
  12. Observability platform.

9.8 Q&A with reasoning

Q. What does the outbox pattern solve? The dual-write problem. You want to update a DB row and publish an event. If you do them as two separate calls, a crash between them leaves the system inconsistent. Outbox writes the event row in the same DB transaction, then a relay reads the outbox table and publishes. Consumers must be idempotent.

Q. At-least-once vs exactly-once? At-least-once may deliver duplicates after retry. Exactly-once semantics have a specified scope: coordinate input progress with output or use a durable dedup/effect transaction. A MERGE statement alone does not prove exactly-once for arbitrary non-idempotent updates or stale CDC.

Q. Why use consistent hashing? When a node is added or removed, an expected roughly K/(N+1) keys move on adding a balanced node to N existing nodes (about K/N on removing one), instead of remapping nearly everything. Virtual nodes smooth load distribution.

Q. How do you avoid hot-key on a global counter? Bucket the key into many shards (counter:42#0..M-1), increment a random shard, sum periodically. Trade read cost for write scalability.

Q. What does R + W > N prove? Fixed read/write replica sets intersect. That alone is not linearizability: concurrent writes, failed/partial writes, version resolution and sloppy quorums require additional protocol rules. A timestamp tie-breaker is not a proof.

Q. Saga vs 2PC? Prepared 2PC participants can block awaiting a durable coordinator decision. A saga commits local steps and compensates on failure; orchestration may use synchronous or asynchronous calls. Compensation is a business action, not guaranteed rollback, and may itself need retries/manual repair. Choose from atomicity and failure requirements, not a microservice label.


10. SQL and RDBMS — Indexes, Plans, Isolation

10.1 Mental model

A database must answer three hard questions:

  1. How do I store data safely? → transactions, WAL, durability.
  2. How do I find data quickly? → indexes, statistics, optimizer.
  3. How do I handle many users at once? → isolation, locks, MVCC.

Most SQL performance reduces to: how many rows must we touch, and can an index reduce that?

10.2 ACID + isolation levels

LevelDirtyNon-repeatablePhantom
Read Uncommittedyesyesyes
Read Committed (PG default)noyesyes
Repeatable ReadnonoSQL standard may allow; PostgreSQL snapshot isolation prevents visible phantoms but can permit serialization anomalies
Serializablenonono
  • MVCC (Postgres, Oracle, InnoDB) — ordinary snapshot reads and writes can coexist via tuple versions; row/table locks and DDL can still block. The visibility map supports vacuum/index-only scans, not the entire visibility mechanism.
  • Long-running transactions prevent vacuum cleanup → table bloat.

10.3 Indexes — what they're for

IndexUse
B-Treerange + equality
Hashequality only
GIN/GiST (PG)full-text, JSONB, geospatial
BRINblock summaries; useful when values correlate with physical location
Bitmaplow cardinality (warehouse OLAP)
Covering / INCLUDEincludes payload; PostgreSQL still needs visibility-map checks and sometimes heap fetches
Compositeleftmost-prefix rule

There is no universal 5–10% cutoff. Correlation, covering/visibility, random I/O cost, table size and cache state affect index versus sequential scan cost.

10.4 Why WHERE date_trunc('day', ts) = '2026-04-30' is slow

Applying a function to the indexed column prevents direct B-tree lookup. Rewrite as a range:

WHERE ts >= '2026-04-30' AND ts < '2026-05-01'

This enables an ordinary B-tree range predicate; the optimizer may still choose a sequential scan. An expression index can support the matching date expression where its immutability/time-zone semantics are valid.

10.5 Reading EXPLAIN ANALYZE

Operators to recognize:

  • Seq Scan — scans the relation; can be optimal even for large tables when many rows are needed.
  • Index Scan / Index-Only Scan — compare selected rows, random I/O and visibility heap fetches, not just the operator name.
  • Bitmap Heap Scan — combine multiple indexes, then fetch.
  • Hash Join — build hash on smaller side.
  • Merge Join — both sides sorted on join key.
  • Nested Loop — fast when outer is small and inner is indexed.

Compare estimated versus actual rows, loops and buffers. Large errors suggest stale statistics, correlation, skew or parameter-sensitive plans; ANALYZE may help but is not a universal repair. EXPLAIN ANALYZE executes the statement, including writes, so use a safe fixture or an explicitly planned transaction.

10.6 Online schema migration — expand / contract

10.6 Online schema migration — expand / contract
Figure text equivalent — labels and relationships
  • Node “Add new column (compatible schema)” (square).
  • Node “App writes BOTH old + new columns” (square).
  • Node “Backfill and reconcile (rate-limited, resumable)” (square).
  • Node “App reads NEW (verify parity)” (square).
  • Node “Drop old column” (square).
  • “Add new column (compatible schema)” leads to “App writes BOTH old + new columns”. Line: normal; ends: arrow point.
  • “App writes BOTH old + new columns” leads to “Backfill and reconcile (rate-limited, resumable)”. Line: normal; ends: arrow point.
  • “Backfill and reconcile (rate-limited, resumable)” leads to “App reads NEW (verify parity)”. Line: normal; ends: arrow point.
  • “App reads NEW (verify parity)” leads to “Drop old column”. Line: normal; ends: arrow point.

PostgreSQL 11+ can add a column with a constant nonvolatile default without rewriting every row, but ALTER TABLE still needs locks and validation may scan. Deploy dual writes before/during backfill, reconcile concurrent changes, validate parity, then switch reads and retire the old column under a rollback plan.

10.7 Q&A with reasoning

Q. How does a composite index (a, b, c) help queries? Usable for filters on a, (a, b), or (a, b, c) — a condition on b alone may still use the index but usually cannot narrow the scanned range effectively in PostgreSQL 16. Check the version and plan; this is not absolute unusability. Order columns by selectivity and common filter shape.

Q. Why is UUID a worse PK than BIGINT for clustered indexes? UUIDv4 values are random → inserts can hit scattered pages → page splits, fragmentation, poor cache locality. ULID/UUIDv7 improve temporal locality but do not guarantee strictly sequential order across generators. PostgreSQL heap storage is not an InnoDB-style permanently clustered primary index.

Q. Hash join vs merge join vs nested loop — when each?

  • Nested loop — small outer + indexed inner; roughly O(outer × log inner + output) for that access path.
  • Hash join — equi-joins; expected O(N+M+output) with adequate hash memory and ordinary hashing; skew/spill add cost.
  • Merge join — ordered equijoin inputs; O(N+M+output), excluding input sorts. Duplicate-key runs still require buffering/revisiting matches.

Q. Why does VACUUM FULL block writers? It rewrites the table to reclaim space and takes an ACCESS EXCLUSIVE lock. An alternative such as pg_repack has its own locks, privileges, storage headroom and compatibility requirements; no live maintenance is performed here.


Part VI — Modeling and Governance

Define the fact grain and history semantics before choosing a star, vault or graph representation. Governance joins identity, ownership, quality, lineage and access policy; a tag proposal is not automatically an enforceable or legally sufficient decision.

11. Data Modeling — Choose by Query Shape

The right model depends on who asks what, not on which model is fashionable.

11.1 OLTP vs OLAP

OLTPOLAP
Goaltransactionsanalytics
Schema3NF, normalizeddenormalized (star/snowflake)
Workloadsmall point reads/writeswide scans, aggregations
EnginePG / MySQL / OracleSnowflake / BigQuery / Iceberg

11.2 Dimensional (Kimball) modeling

  • Fact tables — measurements + FKs to dimensions; e.g. sales_fact (sale_id, date_key, product_key, store_key, qty, amount).
  • Dimension tables — descriptive context (product, customer, date, store).
  • Star = fact + flat dims. Snowflake = normalized dims (more joins, less duplication).
  • Grain — the most atomic level a fact row represents. Define grain first.
  • SCD types:
    • Type 1 — overwrite, no history.
    • Type 2 — new row + effective_from/to, is_current.
    • Type 3 — column for previous value.
    • Type 4 — terminology varies; Kimball uses a mini-dimension pattern, while some tutorials use a separate history-table label. State the chosen taxonomy.
    • Type 6 — Type 2 history plus current attributes overwritten across historical rows (the conventional 1+2+3 hybrid).

11.3 Other modeling approaches

  • Data Vault 2.0 — Hubs (business keys), Links (relationships), Satellites (descriptive + history). Insert-only, auditable; great for raw enterprise lakes feeding star marts downstream.
  • One Big Table (OBT) — wide tables on Iceberg/BigQuery for ML/feature engineering.
  • Graph modeling — for lineage, entity resolution, knowledge graphs.

11.4 Modeling decision table

NeedChoose
Auditable raw historyData Vault / event log
BI dashboards (fast, simple)Star schema
ML feature engineeringOBT / feature store
Lineage queriesGraph
Governance metadataHybrid: relational core + graph for lineage

11.5 Q&A with reasoning

Q. What is grain and why does it matter? Grain is what one fact row represents. If grain is unclear, aggregations either double-count or lose meaning. Always define grain first.

Q. SCD Type 2 implementation in Spark + Iceberg? A matched MERGE source row does not automatically both update and insert. Stage change rows or use a transaction that closes the prior interval and inserts the new version atomically. Keep one current row per natural key, nonoverlapping effective intervals, stable version/surrogate identity and retry deduplication; handle late changes explicitly. The local worked program below demonstrates the transaction.

Q. When is Data Vault the right answer vs Kimball? Data Vault for raw, auditable, source-system-faithful layers in large enterprise lakes where multiple inconsistent sources land. Build Kimball star marts on top for analyst consumption.


12. Data Governance — Catalog, Quality, Lineage, Privacy

12.1 The six questions a governed platform must answer

  1. What data do we have? — catalog
  2. Who owns it? — stewardship
  3. Can I trust it? — quality
  4. Where did it come from? — lineage
  5. Who can access it? — policy / RBAC / ABAC
  6. Is it sensitive? — classification / PII detection

12.2 Architecture of a modern catalog

The lineage service answers dependency questions, but a catalogue also has to answer “what is this asset?” and “how do I find it?” Follow one new dataset through ingestion into the entity, search, and graph stores below. They are different views of the same asset, not three competing authorities; stable identity and visible update lag are what let the service combine their answers responsibly.

12.2 Architecture of a modern catalog
Figure text equivalent — labels and relationships
  • Node “Sources Iceberg / Snowflake / RDBMS / Kafka / BI tools / Spark + Airflow runs” (square).
  • Node “Ingest schema, stats, runs, lineage” (square).
  • Node “Entity store Iceberg / RDBMS” (cylinder).
  • Node “Search index Elasticsearch” (cylinder).
  • Node “Graph Neo4j / Neptune” (cylinder).
  • Node “Services: Search · Lineage · Quality · Classification · Policy · Glossary · Stewardship” (square).
  • Node “UI / API / AI agents” (square).
  • “Sources Iceberg / Snowflake / RDBMS / Kafka / BI tools / Spark + Airflow runs” leads to “Ingest schema, stats, runs, lineage”: crawlers + OpenLineage. Line: normal; ends: arrow point.
  • “Ingest schema, stats, runs, lineage” leads to “Entity store Iceberg / RDBMS”. Line: normal; ends: arrow point.
  • “Ingest schema, stats, runs, lineage” leads to “Search index Elasticsearch”. Line: normal; ends: arrow point.
  • “Ingest schema, stats, runs, lineage” leads to “Graph Neo4j / Neptune”. Line: normal; ends: arrow point.
  • “Entity store Iceberg / RDBMS” leads to “Services: Search · Lineage · Quality · Classification · Policy · Glossary · Stewardship”. Line: normal; ends: arrow point.
  • “Search index Elasticsearch” leads to “Services: Search · Lineage · Quality · Classification · Policy · Glossary · Stewardship”. Line: normal; ends: arrow point.
  • “Graph Neo4j / Neptune” leads to “Services: Search · Lineage · Quality · Classification · Policy · Glossary · Stewardship”. Line: normal; ends: arrow point.
  • “Services: Search · Lineage · Quality · Classification · Policy · Glossary · Stewardship” leads to “UI / API / AI agents”. Line: normal; ends: arrow point.

Why three indexes? Because three different access patterns:

  • Entity store for canonical "what is this asset?"
  • Search index for "find me anything about X."
  • Graph for "what's upstream/downstream of X?"

12.3 Metadata types (memorize)

  • Technical — schema, types, partitioning, formats, sizes.
  • Business — glossary terms, domains, owners, descriptions.
  • Operational — ingestion times, freshness, last-updated, run logs.
  • Social — usage, ratings, certified status.
  • Lineage — who reads / writes / derives.
  • Quality — rule definitions, scores, incidents.

12.4 Data quality dimensions

DimensionExample check
Completeness% non-null on critical columns
Validityvalues match regex / domain / enum
Accuracymatches a reference (gold) source
UniquenessPK has no duplicates
Timelinessdata freshness < SLA
Consistencycross-table referential integrity
Reasonablenessdistribution / volume in expected range

12.5 Lineage granularity

  • Table-level — jobs read/write tables (cheap, coarse).
  • Column-level — which output column derives from which input columns. Built by parsing SQL or capturing Spark logical plans (OpenLineage's Spark integration).
  • Row-level — full provenance per record. Expensive; rarely worth it.

12.6 OpenLineage — the standard

Three core entities: Job, Run, Dataset (inputs/outputs). RunEvents emit start/complete/fail. Spark / Airflow / Flink / dbt have integrations. Backends: Marquez, Datahub, OpenMetadata.

12.7 PII classification — layered, not "just call the LLM"

12.7 PII classification — layered, not "just call the LLM"
Figure text equivalent — labels and relationships
  • Node “Column sample” (square).
  • Node “Regex (SSN, email)” (square).
  • Node “Policy-approved tag proposal” (square).
  • Node “Dictionary (cities, names)” (square).
  • Node “ML / NER classifier” (square).
  • Node “LLM judge + rationale” (square).
  • Node “Human steward review” (square).
  • “Column sample” leads to “Regex (SSN, email)”. Line: normal; ends: arrow point.
  • “Regex (SSN, email)” leads to “Policy-approved tag proposal”: hit. Line: normal; ends: arrow point.
  • “Regex (SSN, email)” leads to “Dictionary (cities, names)”: miss. Line: normal; ends: arrow point.
  • “Dictionary (cities, names)” leads to “Policy-approved tag proposal”: hit. Line: normal; ends: arrow point.
  • “Dictionary (cities, names)” leads to “ML / NER classifier”: miss. Line: normal; ends: arrow point.
  • “ML / NER classifier” leads to “Policy-approved tag proposal”: high conf. Line: normal; ends: arrow point.
  • “ML / NER classifier” leads to “LLM judge + rationale”: ambiguous. Line: normal; ends: arrow point.
  • “LLM judge + rationale” leads to “Human steward review”: low conf. Line: normal; ends: arrow point.
  • “Human steward review” leads to “Policy-approved tag proposal”. Line: normal; ends: arrow point.

Deterministic layers can handle well-defined patterns, but their coverage must be measured; the LLM is only invoked for ambiguity, and a human approves low-confidence cases. This keeps cost, latency, and audit trails sane.

12.8 Policy enforcement

  • Tag-based access — "if tagged PII and user not in privacy_role, mask."
  • Row-level filters — tenant/region.
  • Column masking — hash, redact, tokenize.
  • Enforced in compute (Snowflake / Databricks / Trino) via integrations or proxies (Immuta, Privacera).

12.9 Q&A with reasoning

Q. RBAC vs ABAC — what's the practical difference? RBAC assigns permissions to roles; users inherit role permissions. ABAC evaluates attributes of subject, resource, and environment in a policy expression. ABAC can reduce role explosion when permissions depend on tenant, region, sensitivity, time, etc.

Q. What is a "data contract"? A versioned, testable agreement between producer and consumer covering schema, semantics, freshness, and SLAs. Enforced in CI: schema changes that break the contract fail the build.

Q. Why graph for lineage rather than recursive SQL? Multi-hop relationship traversal is what graph databases are built for. Both recursive SQL and graph traversal must visit relevant edges; benchmark representative depth/degree/filter/selectivity and cycle handling rather than assuming a universal winner.

Q. Where does OpenLineage capture column-level lineage? OpenLineage integrations can derive column dependencies from supported Spark plan expressions and facets. SQL parsing and runtime capture have different coverage; opaque UDFs, dynamic sources and unsupported operators can still leave unknown lineage. Record uncertainty rather than claiming complete fidelity.


Part VII — Algorithms, Languages, Platform

Use explicit invariants to select algorithms, runtime memory/concurrency behavior and deployment controls. The local algorithms have independent tests; JVM, Spark and Kubernetes procedures remain version-specific service references rather than reproduced operations.

13. Algorithms and Data Structures — The Patterns That Repeat

Don't memorize 200 problems. Learn the recognition table.

13.1 Pattern recognition

Signal in the problemPattern
contiguous subarray / substringsliding window, prefix sum
sorted input or sorting helpstwo pointers, binary search
top-K / Kth largest / smallestheap
relationships / connectivityBFS, DFS, Union-Find, topological sort
optimal value with overlapping subproblemsdynamic programming
enumerate all valid configurationsbacktracking
huge stream, bounded memoryreservoir sampling, Bloom filter, HLL, Count-Min Sketch

13.2 Big-data sketches

  • HyperLogLog — approximate distinct count using registers; the relative standard error is approximately 1.04/sqrt(m), with memory depending on representation and corrections. Used by Spark's approx_count_distinct.
  • Count-Min Sketch — approximate frequency. Heavy-hitter detection.
  • Bloom filter — probabilistic set membership. False positives are possible; a correctly maintained insertion-only filter with consistent hashing has no false negatives for inserted keys. Deletion bugs, stale filters and inconsistent encodings invalidate that guarantee. Used inside Parquet row groups, RocksDB, ad-blocking.
  • MinHash + LSH — near-duplicate detection at scale (entity resolution).

13.3 Patterns in code

Contracts: strings are iterated as Python Unicode code points; top-K values must be mutually orderable; DSU expects integer vertices in [0,n); topo expects finite reusable adjacency lists and includes neighbor-only vertices. LRU accepts hashable keys and uses -1 for misses (ambiguous if -1 is a stored value). Top-K costs O(n log k) plus its O(k log k) sorted output for k≥2; k=1 is a linear scan and k=0 returns immediately.

Sliding window — longest substring without repeating chars (O(n))

def length_of_longest(s):
    seen = {}
    left = best = 0
    for right, c in enumerate(s):
        if c in seen and seen[c] >= left:
            left = seen[c] + 1
        seen[c] = right
        best = max(best, right - left + 1)
    return best

Top-K with a min-heap of size K (O(n log k))

import heapq
 
def top_k(stream, k):
    if type(k) is not int or k < 0:
        raise ValueError("nonnegative integer k required")
    if k == 0:
        return []
    h = []
    for x in stream:
        if len(h) < k:
            heapq.heappush(h, x)
        elif x > h[0]:
            heapq.heapreplace(h, x)
    return sorted(h, reverse=True)

Why a min-heap of size k? The smallest of the top-K is on top; you only displace it if a new element is larger.

Union-Find with path compression + union by rank (~O(α(n)) per op)

class DSU:
    def __init__(self, n):
        self.p = list(range(n))
        self.r = [0] * n
 
    def find(self, x):
        while self.p[x] != x:
            self.p[x] = self.p[self.p[x]]   # path compression
            x = self.p[x]
        return x
 
    def union(self, a, b):
        a, b = self.find(a), self.find(b)
        if a == b:
            return False
        if self.r[a] < self.r[b]:
            a, b = b, a
        self.p[b] = a
        if self.r[a] == self.r[b]:
            self.r[a] += 1
        return True

Used for connectivity (lineage clusters, account merging, friend graphs).

Topological sort — Kahn's algorithm (O(V+E))

from collections import deque
 
def topo(graph):                      # graph: {u: [v, ...]}
    indeg = {u: 0 for u in graph}
    for u in graph:
        for v in graph[u]:
            indeg[v] = indeg.get(v, 0) + 1
            indeg.setdefault(u, 0)
    q = deque([u for u, d in indeg.items() if d == 0])
    order = []
    while q:
        u = q.popleft()
        order.append(u)
        for v in graph.get(u, []):
            indeg[v] -= 1
            if indeg[v] == 0:
                q.append(v)
    if len(order) != len(indeg):
        raise ValueError("cycle detected")
    return order

Used for job DAGs, compile dependencies, lineage recompute order.

LRU Cache (OrderedDict)

from collections import OrderedDict
 
class LRU:
    def __init__(self, cap):
        if type(cap) is not int or cap < 0:
            raise ValueError("nonnegative capacity required")
        self.cap = cap
        self.d = OrderedDict()
 
    def get(self, k):
        if k not in self.d:
            return -1
        self.d.move_to_end(k)
        return self.d[k]
 
    def put(self, k, v):
        if k in self.d:
            self.d.move_to_end(k)
        self.d[k] = v
        if len(self.d) > self.cap:
            self.d.popitem(last=False)

13.4 Q&A with reasoning

Q. Why is heapifying a list O(n), not O(n log n)? Sift-down from the bottom up — leaves are already heaps, and most nodes are near the bottom (cheap sift). The geometric series sums to O(n).

Q. When does a Bloom filter return a false negative? Not for correctly maintained insertion-only filters with matching encodings/hash functions. It can if state is stale/corrupted or deletion clears shared bits. That's why it's used as a negative cache — a "no" is reliable, a "yes" requires confirmation.

Q. BFS vs DFS for shortest unweighted path? BFS — it expands by distance layer, so the first time you reach the target you've used the fewest hops.

Q. Why does Dijkstra fail on negative edges? It commits to the shortest known distance once a node is finalized; a later negative edge could discover a shorter path. Use Bellman-Ford for negatives (and detect negative cycles).

Q. How does HyperLogLog estimate distinct counts? It hashes each element, looks at the run of leading zeros in the hash, and tracks the maximum across many "registers" partitioned by hash prefix. The harmonic mean of 2^max_zeros across registers estimates cardinality. For m=16,384 registers the asymptotic relative standard error is about 0.8125%; storage depends on register encoding and estimator implementation, not a universal 16 KB allocation.


14. Java and the JVM — What Production Engineers Must Know

14.1 Memory model

The diagram describes a traditional generational HotSpot layout, not every collector. Region-based/concurrent collectors organize memory differently; pause/heap ranges below are workload hypotheses rather than service guarantees.

JVM Process├── Method Area / Metaspace (class metadata, off-heap)├── Heap│ ├── Young Gen (Eden + 2 Survivors)│ └── Old Gen (Tenured)├── Per-thread: PC reg, JVM stack, native stack└── Direct memory (NIO, off-heap caches like Tungsten, Arrow)
JVM process contains metaspace, heap (young: Eden/two survivors; old/tenured), per-thread PC/JVM/native stacks, and direct memory. Metaspace and direct caches are off-heap.
Figure text and layout
JVM Process
├── Method Area / Metaspace  (class metadata, off-heap)
├── Heap
│   ├── Young Gen (Eden + 2 Survivors)
│   └── Old Gen (Tenured)
├── Per-thread: PC reg, JVM stack, native stack
└── Direct memory (NIO, off-heap caches like Tungsten, Arrow)
Text diagram
  • Objects are born in Eden; survive minor GC → Survivor → eventually Tenured.
  • Metaspace replaces PermGen (Java 8+); it grows in native memory. Bound it with -XX:MaxMetaspaceSize.

14.2 Garbage collectors

GCPauseThroughputHeap sweet spotUse case
SerialHighLow<100 MBTiny / CLI
Parallel (Throughput)HighWorkload-dependent<8 GBBatch / Spark executors
G1GCPredictableGood4–32 GBGeneral services; default since Java 9
ZGClow-pause design goal, not a deadline guaranteeGood8 GB – 16 TBLow-latency services, very large heaps
Shenandoahlow-pause design goalGoodLargeLatency-critical

Key flags: -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:InitiatingHeapOccupancyPercent=35.

14.3 Java Memory Model — happens-before

  • volatile → visibility + ordering, not atomicity. Use AtomicLong for exact atomic counter operations; LongAdder is a high-contention accumulator whose concurrent sum is not an atomic snapshot.
  • synchronized → mutual exclusion + happens-before.
  • final → special initialization visibility for correctly constructed objects without leaking this; it does not make referenced mutable objects immutable or eliminate publication discipline.
  • Locks — ReentrantLock (fairness, tryLock), ReadWriteLock, StampedLock.

14.4 Concurrency toolkit

  • ConcurrentHashMap — striped locking pre-Java 8, CAS + bin-level sync from Java 8.
  • CompletableFuture — async pipelines (thenCompose, thenCombine).
  • Executors — newFixedThreadPool, ForkJoinPool (work-stealing), and virtual threads (Project Loom, Java 21) for massive I/O concurrency without callback hell.
  • Backpressure — bounded queues + RejectedExecutionHandler (CallerRuns can slow a producer but can block event-loop/request threads; choose an explicit rejection/backpressure policy).

14.5 Common pitfalls

  • Unbounded Executors.newCachedThreadPool → OOM under burst.
  • String.intern() in a hot path can add table/contention and retained-heap cost; ordinary interned String objects are not stored in metaspace in modern HotSpot.
  • synchronized on autoboxed Integer / Long → cross-thread aliasing bug.
  • Humongous allocations in G1 (objects ≥ 50% of region size) bypass Eden, hurt collection.
  • try-with-resources automatically closes resources in reverse declaration order; leaked descriptors can exhaust process limits and fail I/O.
  • Virtual threads: JDK 21 can pin carrier threads when blocking while holding monitors; JEP 491 removes this monitor-related pinning in JDK 24. Native/foreign calls and other constraints still require version-specific diagnosis.

14.6 Q&A with reasoning

Q. Why does GC affect p99 latency more than average? Stop-the-world pauses are visible only to requests unlucky enough to hit them. Heap/collector/workload choices affect pause distribution; a larger heap does not universally increase full-GC duration or guarantee fewer pauses. Low-pause collectors have throughput/headroom tradeoffs, not a hard sub-ms application SLO.

Q. Why is volatile not enough for a counter? volatile gives visibility but not atomicity — count++ is read-modify-write, three steps, not atomic. Use AtomicLong for an exact counter; use LongAdder for high-contention statistics only when a non-atomic concurrent sum is acceptable.

Q. PySpark executor is killed by YARN/K8s but heap looks fine. Why? Python/native RSS is outside the JVM heap. Measure it against the whole container budget, including separate PySpark/off-heap allowances, before changing overhead or partition/batch sizes.

Q. What's a humongous object in G1? Object ≥ 50 % of region size. Allocated directly into old gen → may fragment regions, may trigger long full collections. Avoid by tuning region size or rewriting to chunked processing.


15. Python — What Matters for Data Engineering

15.1 Object model

In CPython, almost everything is an object. Variables hold references, not values. b = a copies the reference, not the underlying object — that's why mutation is visible through both names.

15.2 The GIL

In ordinary GIL-enabled CPython builds, only one thread executes Python bytecode at a time in a given interpreter. Python 3.13 supports optional free-threaded builds; extension compatibility and synchronization still matter.

WorkloadTool
CPU-bound mathmultiprocessing.Pool, NumPy/Numba where the selected kernel/build releases the GIL
I/O bound (HTTP, DB)asyncio + aiohttp/asyncpg
Mixedconcurrent.futures.ThreadPoolExecutor for simplicity
Big dataPySpark (Python driver, JVM executors)

15.3 Iterators, generators, async

from itertools import islice
 
def chunks(iterable, n):
    if type(n) is not int or n < 1:
        raise ValueError("positive chunk size required")
    it = iter(iterable)
    while True:
        chunk = list(islice(it, n))
        if not chunk:
            return
        yield chunk

Generators avoid materializing the whole stream; this chunks generator uses O(n) batch memory plus upstream state, not a universal constant-space guarantee. asyncio is great for I/O concurrency; never call blocking I/O inside an async function without run_in_executor.

15.4 PySpark specifics

  • Driver in Python ↔ JVM via Py4J. Executors run Python workers per task.
  • Python UDFs may add cross-language serialization and inhibit optimizer opportunities; measure rather than assume a universal slowdown.
  • Pandas UDFs (vectorized) use Arrow batches and can reduce overhead; speedup depends on computation, conversion, batch size and memory.
  • Avoid collect() on big DFs. toLocalIterator() still needs memory for the largest partition (up to two with prefetch); do not accumulate the yielded rows.

15.5 Idiomatic performance

  • Vectorize with NumPy/Pandas; avoid per-row Python loops.
  • dict.get, setdefault, defaultdict, Counter.
  • __slots__ to remove __dict__ overhead for millions of objects.
  • functools.lru_cache for memoization; cached_property for instance-level.

15.6 Q&A with reasoning

Q. Why does len({1, True, 1.0}) == 1? 1 == True == 1.0 and their hashes are equal, so the set treats them as the same key.

Q. Why is mutable default arg def f(x=[]) dangerous? The default list is created once at function definition time. Each call mutating it accumulates state across calls. Use x=None and if x is None: x = [] inside, preserving an intentionally supplied empty container.

Q. Why are dicts and sets average O(1)? Hash-table implementations with load management make typical lookup expected O(1); collision-heavy inputs can be worse. CPython probing/layout is an implementation detail, not simply textbook quadratic probing shared by dict and set.

Q. Does pandas.DataFrame.apply release the GIL? With the default Python execution path, Python bytecode remains GIL-bound in a GIL-enabled build. axis=0 applies by column (the default); axis=1 applies by row. User functions may call kernels that release the GIL, and supported JIT engines differ, so neither row-by-row execution nor GIL retention is universal.


16. Cloud, Containers, Kubernetes — Just Enough

16. Cloud, Containers, Kubernetes — Just Enough
Figure text equivalent — labels and relationships
  • Node “Code” (square).
  • Node “Container image (Docker / OCI)” (square).
  • Node “Registry” (square).
  • Node “Kubernetes cluster” (square).
  • Node “Deployment (replicas, rolling update)” (square).
  • Node “Pod 1 (1+ containers)” (square).
  • Node “Pod 2” (square).
  • Node “Service (stable virtual IP)” (square).
  • Node “Ingress / Gateway API (L7 routing)” (square).
  • “Code” leads to “Container image (Docker / OCI)”. Line: normal; ends: arrow point.
  • “Container image (Docker / OCI)” leads to “Registry”. Line: normal; ends: arrow point.
  • “Registry” leads to “Kubernetes cluster”. Line: normal; ends: arrow point.
  • “Kubernetes cluster” leads to “Deployment (replicas, rolling update)”. Line: normal; ends: arrow point.
  • “Deployment (replicas, rolling update)” leads to “Pod 1 (1+ containers)”. Line: normal; ends: arrow point.
  • “Deployment (replicas, rolling update)” leads to “Pod 2”. Line: normal; ends: arrow point.
  • “Service (stable virtual IP)” leads to “Pod 1 (1+ containers)”. Line: normal; ends: arrow point.
  • “Service (stable virtual IP)” leads to “Pod 2”. Line: normal; ends: arrow point.
  • “Ingress / Gateway API (L7 routing)” leads to “Service (stable virtual IP)”. Line: normal; ends: arrow point.

16.1 What bites engineers in production

  • Requests vs limits — requests guide scheduling and resource allocation; CPU limits throttle and memory limits are enforced reactively, commonly by OOM kill. Requests are not a guarantee of unused dedicated physical capacity. Set both, especially on memory.
  • Liveness vs readiness probes — readiness gates traffic; liveness failure can trigger container restart according to policy. Mixing them up kills healthy pods under load. Add a startup probe for slow-starting apps to avoid liveness false-positives during boot.
  • Spark on K8s — each executor is a pod; use dynamic allocation with shuffle tracking. Select node pools with affinity/selectors plus compatible tolerations; spot for cost.
  • Secrets — don't bake them into images; mount via Secret/ExternalSecret/CSI driver.
  • Workload identity — prefer per-workload cloud identities (IRSA / Azure WI / GCP WIF) over long-lived keys.
  • Image supply chain — multi-stage builds, distroless base, SBOM (Syft), signing (cosign), admission control.

16.2 Cloud building blocks (cross-cloud)

LayerAWSAzureGCP
Object storageS3ADLS Gen2 / BlobGCS
Compute (VM)EC2VMCompute Engine
ContainersEKS / ECS / FargateAKS / ACA / ACIGKE / Cloud Run
FunctionLambdaFunctionsCloud Functions
SparkEMR / Glue / DatabricksHDInsight / Synapse / DatabricksDataproc / Databricks
DWRedshiftSynapseBigQuery
StreamingKinesis / MSKEvent Hubs / Service BusPub/Sub
CatalogGlue Data CatalogPurviewDataplex
VectorOpenSearch / Aurora pgvectorAI SearchVertex AI Search / AlloyDB

16.3 Observability trinity

  • Metrics — Prometheus + Grafana (or Cloud Monitoring).
  • Logs — Loki / OpenSearch / Cloud Logging.
  • Traces — OpenTelemetry SDKs → Jaeger / Tempo / Cloud Trace.
  • GenAI semantic conventions (OTel) for tracing prompts/retrievals/tool calls.

17. CI/CD and Testing — Confidence to Ship

A complete test pyramid for a data service:

17. CI/CD and Testing — Confidence to Ship
Figure text equivalent — labels and relationships
  • Node “Unit tests (fast, pure)” (square).
  • Node “Integration tests (Testcontainers: Postgres, Kafka, S3-mock, Spark)” (square).
  • Node “Contract tests (producer ↔ consumer, Pact)” (square).
  • Node “Data quality tests (Great Expectations, Soda)” (square).
  • Node “E2E + smoke (against staging)” (square).
  • Node “AI / agent evals (Ragas, golden sets, LLM-as-judge)” (square).
  • “Unit tests (fast, pure)” leads to “Integration tests (Testcontainers: Postgres, Kafka, S3-mock, Spark)”. Line: normal; ends: arrow point.
  • “Integration tests (Testcontainers: Postgres, Kafka, S3-mock, Spark)” leads to “Contract tests (producer ↔ consumer, Pact)”. Line: normal; ends: arrow point.
  • “Contract tests (producer ↔ consumer, Pact)” leads to “Data quality tests (Great Expectations, Soda)”. Line: normal; ends: arrow point.
  • “Data quality tests (Great Expectations, Soda)” leads to “E2E + smoke (against staging)”. Line: normal; ends: arrow point.
  • “E2E + smoke (against staging)” leads to “AI / agent evals (Ragas, golden sets, LLM-as-judge)”. Line: normal; ends: arrow point.

17.1 What each layer guards against

  • Unit — pure logic regressions. Fast feedback.
  • Integration — real DB, Spark, Kafka, S3 (LocalStack/MinIO). Most production bugs live at boundaries.
  • Contract — schema and semantic agreements between services.
  • Data quality — datasets failing even when code passes (schema drift, missing rows, distribution change).
  • E2E — system behavior under realistic flows.
  • AI evals — faithfulness, recall@k, tool-call correctness; gate merges on regression.

17.2 CI/CD patterns worth defending

  • Trunk-based development + short-lived PR branches.
  • Caching of Maven/Gradle/uv lockfile-based downloads (saves minutes).
  • Reproducible builds — pinned base images, lockfiles, SLSA provenance.
  • Security gates — SAST (Semgrep), dependency scan (Dependabot/Renovate), secrets scan (gitleaks), container scan (Trivy/Grype), SBOM (Syft) + signing (cosign).
  • Deploy — GitOps (Argo CD / Flux); progressive delivery (Argo Rollouts canary/blue-green); auto-rollback on SLO regression.

17.3 Quality gates worth setting

These are example policy targets to negotiate, not evidence-based universal thresholds. Coverage/mutation scores do not prove correctness; vulnerability handling needs exploitability, exception ownership and patch deadlines.

  • ≥ 80 % coverage on changed files (not absolute).
  • 0 critical CVEs in shipped image.
  • Mutation score on critical modules ≥ 60 %.
  • Performance regression budget on integration tests.
  • Contract test on every consumer/provider before deploy.

Part VIII — Putting It Together

Compose the platform by tracing one durable update through derived indexes and one authorized query back to its evidence. The self-check and full final-exam solutions below test boundary reasoning, not memorization of vendor performance slogans.

18. One Coherent Mental Map

Follow one new dataset through this final map rather than reading it as a product shopping list. Ingestion captures the update; the lakehouse retains replayable history; graph, search, and vector indexes make different questions cheap. The serving layer combines those views for a request, while governance constrains which data and actions are allowed. The earlier shuffle, commit, and retrieval examples explain why each boundary exists.

18. One Coherent Mental Map
Figure text equivalent — labels and relationships
  • Node “Kafka / CDC” (square).
  • Node “Spark / Flink streaming ingest” (square).
  • Node “Iceberg lakehouse (audit / replay)” (cylinder).
  • Node “Graph (lineage)” (cylinder).
  • Node “Search index” (cylinder).
  • Node “Vector index” (cylinder).
  • Node “Query / API” (square).
  • Node “RAG / Agents” (square).
  • Node “Catalog · DQ · Classification · Lineage · Policy · Stewardship” (square).
  • Node “UI · Notebooks · Apps” (square).
  • Group “Ingest” contains “Spark / Flink streaming ingest”, “Kafka / CDC”.
  • Group “Storage” contains “Iceberg lakehouse (audit / replay)”, “Graph (lineage)”, “Search index”, “Vector index”.
  • Group “Serve” contains “Query / API”, “RAG / Agents”.
  • Group “Govern” contains “Catalog · DQ · Classification · Lineage · Policy · Stewardship”.
  • “Kafka / CDC” leads to “Spark / Flink streaming ingest”. Line: normal; ends: arrow point.
  • “Spark / Flink streaming ingest” leads to “Iceberg lakehouse (audit / replay)”. Line: normal; ends: arrow point.
  • “Spark / Flink streaming ingest” leads to “Graph (lineage)”. Line: normal; ends: arrow point.
  • “Spark / Flink streaming ingest” leads to “Search index”. Line: normal; ends: arrow point.
  • “Spark / Flink streaming ingest” leads to “Vector index”. Line: normal; ends: arrow point.
  • “Iceberg lakehouse (audit / replay)” leads to “Query / API”. Line: normal; ends: arrow point.
  • “Graph (lineage)” leads to “Query / API”. Line: normal; ends: arrow point.
  • “Search index” leads to “Query / API”. Line: normal; ends: arrow point.
  • “Vector index” leads to “RAG / Agents”. Line: normal; ends: arrow point.
  • “Query / API” leads to “RAG / Agents”. Line: normal; ends: arrow point.
  • “Iceberg lakehouse (audit / replay)” leads to “Catalog · DQ · Classification · Lineage · Policy · Stewardship”. Line: normal; ends: arrow point.
  • “Graph (lineage)” leads to “Catalog · DQ · Classification · Lineage · Policy · Stewardship”. Line: normal; ends: arrow point.
  • “Search index” leads to “Catalog · DQ · Classification · Lineage · Policy · Stewardship”. Line: normal; ends: arrow point.
  • “Catalog · DQ · Classification · Lineage · Policy · Stewardship” leads to “RAG / Agents”. Line: normal; ends: arrow point.
  • “RAG / Agents” leads to “UI · Notebooks · Apps”. Line: normal; ends: arrow point.
  • “Query / API” leads to “UI · Notebooks · Apps”. Line: normal; ends: arrow point.

If an index is lost, the durable history should let you rebuild it; if a policy changes, rebuilding later is not a substitute for enforcing access now. The drawing shows responsibilities, not atomicity across stores or a complete authorization protocol. Those are design obligations to test using the self-check questions that follow.

One sentence per layer:

  • Ingest — absorb data with backpressure and idempotent / exactly-once semantics.
  • Storage — keep cheap durable truth in a lakehouse; add specialized indexes (graph, search, vector) that can be rebuilt from the lake.
  • Serve — expose query APIs and AI features that combine retrieval, traversal, and generation.
  • Govern — make every asset discoverable, trusted, and auditable; let humans approve what models can't.

19. The Ten Self-Check Questions

Answer each out loud using the What → Why → How → Trade-off → Failure → Example framework. If you can't, that's the topic to revisit.

  1. Walk a Spark query through Catalyst → Tungsten → AQE → physical execution. Where do plans change at runtime, and why?
  2. When does shuffle dominate distributed query cost, and which supported workloads can push-based shuffle help?
  3. Explain Iceberg's commit protocol and how two concurrent writers reconcile.
  4. Compare column-level lineage in a graph DB vs recursive SQL at 100 M edges. Where does each break?
  5. Derive the cost of self-attention and explain KV cache + FlashAttention.
  6. Design a multi-tenant RAG system: where do you enforce tenant isolation, and how do you defend against indirect prompt injection?
  7. Sketch an idempotent CDC pipeline Postgres → Kafka → Iceberg with exactly-once semantics. Where can duplicates appear, and how do you dedupe?
  8. Choose between broadcast hash, shuffle hash, and sort-merge join — what stats and runtime signals drive AQE's decision?
  9. Explain the outbox pattern and why dual-write is unsafe without it.
  10. Layered PII classification: what's deterministic, what's ML, what's an LLM, and where does a human approve?

20. Closing — How to Compound These Ideas

The point of going this deep isn't trivia. Once you internalize why each component exists, you can compose them. A new architecture is just an old set of trade-offs in a new shape. If you can articulate the original problem, the rest is engineering.

Three habits make this compound:

  1. Speak every concept out loud with the What → Why → How → Trade-off → Failure → Example framework. Silent reading creates a false sense of mastery.
  2. For each topic, draw the flow before answering. The diagram forces you to commit to a structure; the words follow.
  3. For each "why," chase one more "why." "Shuffle is expensive." → Why? "Network + sort + spill + reducer pressure." → Why does that hurt p99? "One straggler bounds stage completion." → How do I see it? "Spark UI: max vs median task time." That fourth-level answer is what senior reviewers listen for.

Anything weaker than that, treat as a draft.


Part IX — In-Depth Quiz Bank with Reasoning

Each quiz item below is answer + reasoning, not just the answer. Speak each one out loud. If you only know the answer but not the why, you don't know it yet.

21. Spark Core — Quiz With Reasoning

  1. Default spark.sql.shuffle.partitions is 200 in Spark 3.5.7. It is a default, not a safety or optimality guarantee. Start with enough partitions for measured shuffle volume; configured AQE can coalesce small partitions. A 128–256 MB target is a workload hypothesis.
  2. Wide vs narrow. groupByKey is wide because rows for the same key must meet on one reducer, requiring shuffle. map, filter, mapPartitions are narrow because each output partition derives from one input partition.
  3. reduceByKey vs groupByKey().mapValues(_.sum). reduceByKey does map-side combine — it sends only partial sums across the network. groupByKey ships every value, blowing up shuffle and reducer memory. Same mathematical sum under associative/commutative assumptions, potentially different floating-point rounding and very different cost.
  4. spark.sql.adaptive.enabled defaults to true in 3.5. AQE coalesces tiny partitions, switches sort-merge to broadcast when actual stats are small, and splits skewed partitions. Default-on enables runtime adaptation without manual hints.
  5. MEMORY_ONLY storage level. RDD/SQL and Python/JVM representations differ. PySpark 3.5.7 DataFrame persistence defaults to MEMORY_AND_DISK_DESER; MEMORY_ONLY may recompute evicted partitions. Choose from measured reuse and memory, not an unavailable Python MEMORY_AND_DISK_SER constant.
  6. Unit of parallelism = partition. One task attempt processes a stage partition. Concurrent tasks are bounded by runnable partitions and available task slots, approximately min(partitions, executor cores / task CPUs), not their product; retries/speculation add attempts.
  7. persist vs checkpoint. persist keeps lineage; if a partition is lost, Spark recomputes from lineage. checkpoint writes to reliable storage and truncates lineage — critical for very long chains and iterative algorithms where recomputation would be catastrophic.
  8. Broadcast variable vs collect. Broadcast distributes a value once per executor; tasks read it locally. collect returns the entire DataFrame to the driver, paying full network cost and risking driver OOM.
  9. df.explain(True). Shows Parsed, Analyzed, Optimized logical plans and the Physical plan. Use it to confirm predicate pushdown, projection pruning, partition filters, and join strategy.
  10. Whole-stage codegen disabled when. Unsupported physical operators, configured field/size limits, fallback rules or spark.sql.codegen.wholeStage=false create boundaries. Do not generalize one 100-field default into a universal UDF rule; inspect the executed plan and version.

22. Spark Tuning — Quiz With Reasoning

  1. autoBroadcastJoinThreshold default = 10 MB in Spark 3.5.7. This is a planning threshold, not an OOM safety guarantee; materialized size can exceed estimates. BROADCAST hints and adaptive thresholds affect strategy, so inspect the final plan and driver/executor memory.
  2. Disable broadcast for one join. Spark 3.5.7 documents BROADCAST, MERGE, SHUFFLE_HASH and SHUFFLE_REPLICATE_NL strategy hints, not the asserted NO_BROADCAST_HASH hint. Disable automatic broadcast with the static and relevant adaptive thresholds and inspect the final plan; a MERGE hint is advisory and explicit BROADCAST can override thresholds.
  3. spark.sql.adaptive.skewJoin.enabled. Splits a skewed partition into sub-partitions and replicates the other side, shifting work from one slow reducer to several fast ones — task runtime is bounded by the slowest task, so cutting stragglers wins.
  4. Small-files threshold. There is no universal 64 MB boundary. Compare request/open/planning cost, pruning, parallelism and compaction cost; 128–512 MB targets are workload starting points.
  5. Default SQL data-source format = Parquet in the pinned Spark version; it is configurable. Columnar, predicate pushdown, footer stats, splittable.
  6. coalesce(1) vs repartition(1). Both leave one output partition/task. Coalesce can collapse upstream narrow work onto one task; repartition adds a shuffle so upstream tasks can remain parallel before one reducer. Neither makes a large single-file output scalable.
  7. Best storage for a very large reused DF. PySpark 3.5.7 supports MEMORY_AND_DISK_DESER by default for DataFrame.persist. Cache only useful reused data and compare against recomputation; disk spill is not a guarantee against every OOM.
  8. GC time signal in Spark UI. Inspect stage/executor GC and task metrics together. A high fraction motivates allocation/cache/partition investigation; raising non-heap overhead does not directly repair heap-GC churn.
  9. Approximate distinct. Spark approx_count_distinct uses HyperLogLog++; its default requested relative standard deviation is 0.05, not a universal 1%. Exact distinct and sketch merge costs depend on physical aggregation and data distribution.
  10. Bucketing vs broadcasting. Compatible bucket metadata/partitioning can avoid exchanges for repeated joins, but engine settings and matching layouts matter. Broadcast targets a sufficiently small side; neither eliminates every shuffle by name alone.

23. SQL / RDBMS — Quiz With Reasoning

  1. Defaults: PostgreSQL 16 = Read Committed; MySQL InnoDB = Repeatable Read. PG uses a fresh committed snapshot per statement. InnoDB ordinary consistent reads use snapshots; locking reads/range updates may use next-key/gap locks. Snapshot reads and locking reads are not the same mechanism.
  2. Covering index. The index contains needed columns, but PostgreSQL index-only scans also need all-visible heap pages to avoid visibility heap fetches. Included columns do not by themselves guarantee zero heap I/O.
  3. LEFT JOIN vs LEFT SEMI JOIN. LEFT JOIN keeps both tables' columns and emits NULLs for non-matches. LEFT SEMI returns only the left rows where a match exists; useful for existence filters where you don't need right-side columns.
  4. VACUUM FULL vs VACUUM. Regular VACUUM marks dead tuples reusable. VACUUM FULL rewrites the table and indexes to reclaim space; takes ACCESS EXCLUSIVE lock and rewrites blocks.
  5. Prefix search index in Postgres. B-tree with text_pattern_ops for LIKE 'foo%' because it sorts lexicographically; trigram GIN supports more flexible patterns.
  6. MySQL InnoDB clustered index. Leaf records contain row data in clustered-key order, not necessarily contiguous disk order. An explicit primary key is preferred; without one InnoDB selects a suitable unique non-null index or generates an internal key. Wide/random keys add space/locality costs.
  7. Phantom-read prevention. PostgreSQL 16 Repeatable Read uses snapshot isolation and prevents visible phantoms but can allow serialization anomalies. Serializable adds SSI conflict detection and may abort; retry the complete transaction.
  8. Leftmost-prefix rule. In PostgreSQL 16, equality on leading B-tree columns and an inequality on the first non-equality column bound the scanned range. A later-column condition can still be checked by the index; b-only is not an absolute unusability rule.
  9. BIGINT vs UUID PK. BIGINT is compact; only its allocation policy makes it sequential. UUIDv4 avoids a central allocator with probabilistic uniqueness but scatters clustered-index inserts. UUIDv7/ULID improve temporal locality, not universal cross-generator monotonicity.
  10. EXPLAIN (ANALYZE, BUFFERS) in PostgreSQL 16. Executes the statement and reports runtime rows/loops plus buffer hits/reads/dirtied/written blocks. Without ANALYZE, BUFFERS does not supply the execution profile; buffer reads are not proof of physical disk access past the OS cache. Use a safe fixture for writes.

24. Distributed Storage — Quiz With Reasoning

  1. S3 max object size. The accessed multipart limits page specifies 48.8 TiB and up to 10,000 parts of 5 MiB–5 GiB (last part may be smaller). The old 5 TB blanket limit is stale; verify bucket/API and current limits before an upload. This is documentation, not an upload test.
  2. HDFS default block size = 128 MB. Large blocks reduce NameNode metadata pressure and amortize seek cost over reads.
  3. Spark on S3 committer. Hadoop 3.3.6 S3A magic/staging committers coordinate pending multipart uploads and task/job commits without relying on rename. Individual object completion is atomic; a whole multi-file dataset needs its table/reader commit contract. Choose the committer supported by the distribution, not a universal 2026 default.
  4. LIST consistency on S3. Strongly consistent since Dec 2020. Before that, eventual consistency caused stale listings; lakehouse formats sidestep listings via metadata files.
  5. Archival tier. S3 Glacier Deep Archive targets infrequently accessed long-term data with retrieval delay and minimum-duration charges. Total cost depends on region, retrieval and object size; it is not universally cheapest for every workload.
  6. Speculative execution risk on S3. Multiple attempts require an attempt-aware commit protocol. Avoid unsupported rename assumptions; validate the selected S3A committer and downstream table visibility under retries rather than asserting all classic-committer configurations duplicate output.
  7. HDFS Erasure Coding RS-6-3. Stores 6 data + 3 parity blocks; effective replication ratio = 1.5×. Saves storage at CPU cost for encoding/decoding.
  8. ADLS protocol prefix in Spark. abfss:// (secure). Use it instead of older wasbs:// Blob URIs.
  9. Parquet has native column-level encryption (Modular Encryption since 1.12). Allows per-column keys and integrity checks for governance.
  10. Best partition column for daily logs. date (low cardinality, time-aligned). Avoid high-cardinality columns like user_id to prevent partition explosion.

25. Iceberg / Lakehouse — Quiz With Reasoning

  1. Iceberg atomic commit mechanism. Catalog-level atomic swap (HMS / Glue / REST / Nessie). The catalog provides compare-and-set on the table pointer; readers see only committed snapshots.
  2. Manifest list contents. Pointers to manifest files for a snapshot, with partition-summary stats used for partition-level pruning before reading the manifests themselves.
  3. Iceberg schema evolution by column ID, not name. Renames preserve identity; old data still maps to the right logical column.
  4. expire_snapshots. Removes old snapshots beyond retention and unreferenced data files. Without it, table metadata grows indefinitely and storage cost balloons.
  5. Hudi CoW vs MoR. Copy-on-Write rewrites whole files on update; reads stay simple but writes are heavier. Merge-on-Read appends delta files merged at read; faster writes, slower reads, periodic compaction needed.
  6. Delta transaction log location. _delta_log/ directory at table root holding JSON commits and Parquet checkpoints.
  7. Iceberg REST catalog. REST is a standardized integration option, not a proved universal 2026 default. Verify engine/catalog/auth support and atomic commit semantics.
  8. Iceberg time-travel SQL. FOR VERSION AS OF <id> or FOR TIMESTAMP AS OF <ts>. Engines resolve via snapshot history.
  9. Z-order improves multi-column data skipping by spatial locality. Records with similar values land in the same files, so predicate pruning skips more files.
  10. Equality vs position deletes. Position deletes address file and row position; equality deletes match values for specified field IDs. They are not arbitrary predicates. Read cost and supported forms depend on engine/spec, sequence rules and file layout.

26. Data Modeling — Quiz With Reasoning

  1. Grain. The level of detail one fact-table row represents. Grain decisions drive every aggregation. Mixing grains causes silent double counting.
  2. Star vs snowflake. Snowflake normalizes dimensions into multiple tables; star keeps them flat. Star = simpler, fewer joins, faster scans. Snowflake = less duplication, harder reads.
  3. SCD Type 2. Track full history with effective_from, effective_to, is_current. Each change creates a new row; old rows remain for point-in-time queries.
  4. Surrogate keys. Insulate fact from natural-key changes, allow SCD2, enable smaller integer joins. Natural keys are still stored for traceability.
  5. Bridge table. Resolves many-to-many between fact and dimension. Example: a healthcare fact links to multiple diagnoses through a bridge.
  6. Additive measures sum correctly across all dimensions. Semi-additive (e.g., balance) sums across some dimensions; non-additive (e.g., ratios) needs special handling.
  7. Junk dimension. Combines low-cardinality flags into one dim to avoid many tiny dims and reduce fact-table key counts.
  8. Conformed dimension. Same dim used identically across multiple fact tables, enabling cross-process analysis.
  9. Factless fact table. Records events with no measure column. Example: student-attendance event used for counts.
  10. Late-arriving dimension fix. Insert "Unknown" placeholder in the dim immediately, update the fact with the proper key when the dim arrives, or look up the natural key with a backfill job.

27. Graph Databases — Quiz With Reasoning

  1. Index-free adjacency. Each node stores direct pointers to its relationships. Once you anchor a query at a node, traversal is pointer-following, not global index lookup.
  2. OPTIONAL MATCH returns NULLs when patterns are absent, similar to LEFT OUTER JOIN.
  3. Uniqueness constraint. CREATE CONSTRAINT FOR (d:Dataset) REQUIRE d.id IS UNIQUE. Enforces a single dataset per id and creates a backing index.
  4. *1..5. Variable-length path of 1 to 5 hops. Always bound length to avoid traversal explosion.
  5. Relationship direction. Stored directed; queries can ignore direction with -[r]-.
  6. Neptune query languages. openCypher and Gremlin; SPARQL via a separate endpoint.
  7. One-hop neighbors. O(degree). For supernodes this is huge; bound traversal or filter by relationship type.
  8. Plan inspection. EXPLAIN (no execution) and PROFILE (execution + DB hits).
  9. Supernode. A node with millions of relationships; traversals through it explode. Mitigate by tag/tenant partitioning, indexed predicates, or relationship-level filters.
  10. In-memory graph choice. Compare Memgraph, Neo4j and other engines on the actual graph, working set, traversal, durability and operational needs. There is no source-backed universal best engine here.

28. Elasticsearch — Quiz With Reasoning

  1. Default scoring = BM25 since 5.x. Improves over TF-IDF by saturating term frequency and normalizing for document length.
  2. text vs keyword. text is analyzed (tokenized) for full-text search; keyword stored as-is for exact match, sorting, aggregations. Most catalog fields need both via multi-field mapping.
  3. JVM heap sizing. Follow Elastic/version/JVM guidance and verify compressed-oops status. The threshold is environment dependent (26 GB generally safe in current guidance, sometimes about 30 GB), not a universal 32 GB cliff; retain OS/native headroom.
  4. Translog. Per-shard recovery log with configured durability/fsync behavior. Refresh makes data searchable; flush creates a Lucene commit and a new translog generation. These are distinct boundaries.
  5. Refresh interval. Elastic Stack commonly uses one second for recently searched indices; serverless and inactive-index behavior differ. Bulk-load changes need a restore/visibility plan, not an unconditional live setting change.
  6. Change primary shards after creation? Not directly. Use reindex into a new index with desired shards, or shrink/split.
  7. Phased rollover indexes. Index Lifecycle Management (ILM) with rollover by size/age/docs.
  8. Cheap top-K aggregations. terms aggregation with size and shard_size tuned. Be careful with high-cardinality fields; use composite aggregation for paging.
  9. Vector search type. Dense vector with k-NN (HNSW indexing, since 8.x).
  10. match vs term. match analyzes the query text (tokenizes) before matching. term does exact match on the indexed token; use on appropriately mapped exact-value fields, including keyword/numeric/date/boolean as applicable.

29. LLMs — Quiz With Reasoning

  1. Self-attention complexity. O(n2⋅d)O(n^2 \cdot d). Each token attends to every other token, and each attention head computes a dd-dimensional dot product. Long context blows up compute and memory.
  2. Llama tokenizers. Tokenization is model-version-specific; earlier Llama and Llama 3 use different vocabularies/implementations. Inspect the published tokenizer for the selected checkpoint; never assume one SentencePiece recipe for the whole family.
  3. Decoder-only models. Published GPT-style/Llama/Mistral causal models generate autoregressively. Do not assert undisclosed Claude/Gemini internals from a product name alone.
  4. Cosine vs dot. For nonzero vectors normalized to unit length, dot product equals cosine. Magnitude otherwise affects dot product; inspect the selected embedding contract instead of assuming normalization.
  5. ANN choice. HNSW is a widely implemented graph index, not a verified most-popular-2026 ranking. Compare exact search, HNSW and IVF/quantization options under recall, filter, memory and latency requirements.
  6. Continuous batching tool. vLLM can admit requests as others finish; PagedAttention manages KV storage. Measure throughput and tail latency for the workload rather than assuming a dramatic universal gain.
  7. SFT vs RLHF/DPO. SFT imitates reference outputs; preference training uses comparative feedback. Better helpfulness/safety is an evaluation goal, not a guarantee of either objective.
  8. System prompt. Top-of-conversation instruction defining role, constraints, format. Treated as trusted context; never let retrieved data override it.
  9. KV cache contents. Per-layer attention K and V tensors for all already-generated tokens. Memory grows linearly with batch × layers × seq length.
  10. 70B Q4 sizing. Nominal 4-bit weights alone cost 35 billion bytes (about 32.6 GiB). Quantization scales/metadata, unquantized tensors, runtime workspace and KV cache add memory. A 40 GB device is not a guaranteed fit for an arbitrary context/batch, and offload/tensor parallelism change the performance contract.

30. RAG and Agents — Quiz With Reasoning

  1. RRF = Reciprocal Rank Fusion. Combines two ranked lists by summing 1/(k+rank)1/(k + \text{rank}). Good when scores are not directly comparable (BM25 vs cosine).
  2. HyDE = Hypothetical Document Embeddings. Have the LLM imagine an answer, embed it, and retrieve real docs by similarity to that. Helps when queries are short and ambiguous.
  3. Reranker purpose. Improves top-K precision by re-scoring candidates with a more expensive cross-encoder.
  4. Vector graph index. HNSW is one graph ANN family; a vector database may use exact search, IVF or other indexes. Select and tune explicitly.
  5. Long-context failure. Retrieval can reduce irrelevant context and position sensitivity, but may miss evidence and does not eliminate lost-in-the-middle behavior. Evaluate retrieval and answer support separately.
  6. Faithfulness metric. Estimates whether answer claims are supported by retrieved context. Low support flags grounding problems; even high support does not prove the context true, authorized or complete. Calibrate the selected Ragas metric/judge.
  7. ReAct = Reasoning + Acting interleaved with tools. The model reasons, calls a tool, observes the result, and continues.
  8. Function-calling output. A proposed structured call whose schema/values must be validated under the selected API contract. Application code authorizes and executes it; formatting does not grant permission.
  9. Tenant filters in RAG. Apply pre-retrieval as a metadata filter; assert again pre-generation. Post-only filtering can leak counts/snippets and waste compute.
  10. Chunk size. 500–1,000 tokens and 10–20% overlap are candidate settings, not universal optima. Evaluate boundary preservation, redundancy, retrieval recall and context budget for the document types.

31. Distributed Systems HLD — Quiz With Reasoning

  1. CAP under partition. You must choose C or A. Partition tolerance is mandatory for distributed systems. Real systems also obey PACELC: when no partition, choose Latency vs Consistency.
  2. Quorum for strong consistency in N=5. R+W>NR + W > N. Examples: R=3,W=3R=3, W=3 or R=2,W=4R=2, W=4. Overlapping fixed replica sets supply a shared witness; the protocol must still establish ordering and handle concurrent/partial writes.
  3. Consistent hashing benefit. In a balanced model adding one node to N remaps about K/(N+1) keys in expectation; removing one remaps about K/N. Virtual nodes smooth placement, not arbitrary hot-key load.
  4. Outbox pattern. Atomically write DB row + outbox event in same transaction; relay publishes events to broker. Solves dual-write inconsistency.
  5. At-least-once vs exactly-once. Replays are possible; exactly-once effects require a defined atomic input/output or durable deduplication boundary. Idempotence on one sink does not prove a multi-service workflow exactly-once.
  6. 2PC vs Saga. 2PC coordinates an atomic commit but prepared participants can block. A saga commits local steps and compensates; calls may be synchronous or async, and irreversible effects require an explicit recovery policy.
  7. Postgres CDC standard. Logical replication / WAL via Debezium emitting Avro/JSON/Protobuf.
  8. Avoid hot key on a global counter. Bucket/salt the key, distribute across many partitions, aggregate periodically.
  9. Raft fault tolerance. A majority is floor(N/2)+1. Three voters can make progress with one unavailable under Raft network/timing assumptions; safety and liveness are separate, and membership changes need the protocol.
  10. Idempotency-key location. Header or body, validated server-side; persisted with the record for dedupe across retries.

32. Algorithms and Data Structures — Quiz With Reasoning

  1. HLL precision. Relative standard error is approximately 1.04/sqrt(m) for m registers; m=16,384 gives about 0.8125%. Packed/sparse/dense register representation determines memory, so 16 KB is not universal.
  2. Bloom filter and false negatives. An insertion-only correctly updated filter with consistent hashes has none for inserted keys. False positives require authoritative confirmation; stale state, corruption or unsafe deletions invalidate the premise.
  3. Heap from list complexity. O(n) using heapify (sift-down from middle). Pushing one-by-one is O(n log n).
  4. Nearly-sorted data. Insertion sort costs O(n+inversions), so it is linear only when inversions are O(n). Python Timsort exploits runs; no single sort wins every nearly-sorted definition.
  5. Path compression cost. Effectively O(α(n))O(\alpha(n)) ≈ constant. Combined with union-by-rank.
  6. Trie space for 10k words avg length 8. ~80k nodes worst case, often less with shared prefixes. Use compact trie / DAWG to save more.
  7. Dijkstra failure. Negative edges invalidate its label-setting proof. Bellman-Ford supports negative edges and detects reachable negative cycles; finite shortest paths require no relevant reachable negative cycle, not absence of all cycles.
  8. Shortest unweighted path = BFS. Each level expansion equals one hop, so first time you reach a node is via the shortest path.
  9. HLL notation. Define precision p and register count m=2^p rather than using k ambiguously. With p=14 there are 16,384 registers; error follows the square-root law under hash/estimator assumptions.
  10. Quickselect. Average O(n), worst O(n²). Median-of-medians gives O(n) worst case.

33. Java / JVM — Quiz With Reasoning

  1. Java 11 GC default. G1 is the default on server-class HotSpot configurations; ergonomics/platform flags can choose differently. G1 targets pauses rather than guaranteeing them. Compare Parallel, G1 and low-pause collectors with the actual heap, allocation rate, CPU budget and tail-latency requirement.
  2. Publication without synchronized. Volatile writes/reads establish happens-before for preceding writes; AtomicReference offers atomic reference operations. Final-field initialization has special guarantees only for correctly constructed objects and does not make mutable contents immutable.
  3. -XX:+HeapDumpOnOutOfMemoryError writes a .hprof when the JVM throws OutOfMemoryError. Heap dumps captured at the moment of failure expose the real retained set, hard to reconstruct later.
  4. ConcurrentHashMap.size() in OpenJDK 21. It sums baseCount and counter cells via sumCount, not all n map entries or one counter per bin. Under concurrent updates the result is a transient estimate, not a transactionally coherent snapshot.
  5. Runnable vs Callable. Runnable.run() returns void and cannot throw checked exceptions. Callable<T>.call() returns T and can throw. Executors use Callable and surface results via Future.
  6. Humongous region in G1. A region holding an object ≥ 50 % of region size, allocated directly into old gen. Many humongous allocations indicate big arrays/strings that should be streamed.
  7. LongAdder vs AtomicLong. LongAdder reduces update contention with cells, but sum() is not an atomic snapshot during concurrent updates. Use AtomicLong/CAS or a lock/transaction when an exact linearizable read-modify-write invariant is required.
  8. Effectively immutable class. All fields final, no leaking this from constructor, defensive copies of mutable refs, no mutator methods. JMM safe-publication guarantees other threads see constructor-completed state.
  9. Finalizer thread. A JVM-internal thread that processes Object.finalize() queue. Deprecated for removal because finalization is unpredictable, can resurrect objects, and runs on a single bottleneck thread. Prefer try-with-resources and Cleaner.
  10. Print all GC events with timestamps (Java 9+). -Xlog:gc*=info:file=gc.log:time,uptime,level,tags. Unified logging replaces fragmented Java 8 flags.

34. Python — Quiz With Reasoning

  1. bool([]) is False. Empty containers are falsy; __bool__ falls back to __len__, which returns 0. That's why if not items: is idiomatic.
  2. Integer identity versus equality. Equal values need not be identical, and compilation/interning may reuse even large constants. Small-int caching is CPython-specific, not a Python language guarantee. Use == for values and is for identity/sentinels.
  3. *args, **kwargs. Desugar to a positional tuple and keyword dict. Useful in decorators with functools.wraps.
  4. Hashable class. Implement __hash__ and __eq__ consistently. Hashable classes should be effectively immutable; mutating after insertion into a set/dict corrupts the structure.
  5. Shallow vs deep copy. Shallow copy.copy duplicates the outer container, sharing inner references. Deep copy.deepcopy recursively duplicates. Choose based on whether mutation of inner objects must be isolated.
  6. yield from delegates iteration, exceptions, send, and return-value of a sub-generator. Composes generators without re-implementing the protocol.
  7. Mutable default argument trap. Defaults evaluate once at definition. Use a None sentinel and an explicit if x is None: x = []; x or [] replaces a deliberately supplied empty object and can change aliasing semantics.
  8. len({1, True, 1.0}) == 1. 1 == True == 1.0, and their hashes match (hash(1) == hash(1.0) == hash(True)), so the set collapses them.
  9. Membership: list vs set. set is O(1) average via hashing; list is O(n).
  10. @functools.wraps. Preserves wrapped function's __name__, __doc__, __qualname__, __module__, __wrapped__. Without it, decorators hide identities and break introspection.

35. OOP / LLD Patterns — Quiz With Reasoning

  1. Type-dispatch cascade. Repeated type checks may make extension require editing central code (OCP concern). They do not automatically violate LSP; substitutability depends on behavioral pre/postconditions. A closed sum type with exhaustive dispatch can be deliberate.
  2. Strategy vs State. Strategy = caller picks an algorithm and swaps it. State = the object's behavior changes due to internal lifecycle. Strategy fits pluggable PII classifiers; State fits a job FSM (PENDING → RUNNING → SUCCEEDED).
  3. Add tracing without modifying repositories. Decorator pattern wraps a repository implementing the same interface. Proxy is similar but typically adds control or remote concerns.
  4. Singleton trade-off. Hidden global state, hard to mock, tests interfere across cases, parallel tests become unsafe. Mitigate by injecting via a DI container with proper scoping.
  5. DTO placement. Keep transport/persistence DTOs at their application/adapter boundaries and map into domain types. Exact package boundaries depend on the architecture; a shared kernel is a deliberate coupling, not a default dumping ground.
  6. Aggregate vs Entity. An aggregate is a cluster of entities and value objects with one root that enforces invariants. An entity has identity but no transactional boundary by itself.
  7. Visitor adds operations easily but resists adding new types. True. New operation = new Visitor; new type forces editing every Visitor.
  8. Build a complex immutable object with optional fields → Builder. Telescoping constructors and many setters break immutability and readability; Builder validates and constructs once with .build().
  9. CoR vs Pipeline. Chain of Responsibility selects/short-circuits handlers; a pipeline composes transformations. Real pipelines can also stop on errors or policy, so always passing every stage is not a defining safety guarantee.
  10. Composition over inheritance. Composition gives flexibility, supports delegation, avoids fragile-base-class problems, and simplifies testing because dependencies can be swapped.

36. Cloud & Kubernetes — Quiz With Reasoning

  1. K8s scheduler factors. Resource requests + node fit + affinity + taints/tolerations + topology spread. Scheduling failures show in Events.
  2. Requests vs limits. Requests guide scheduling and CPU shares; limits enforce CPU throttling and reactive memory boundaries. A request is not a guarantee that memory remains physically unused for you.
  3. Memory limit exceeded. OOMKilled by kernel cgroups; Kubernetes restarts the container per restart policy.
  4. PodDisruptionBudget. Bounds disruptions through the eviction API for selected pods. It does not prevent involuntary node failure or every deletion/update; controller rollout availability has separate controls.
  5. Run-to-completion = Job (and CronJob for scheduled). Track succeeded/failed counts; clean up with TTL controller.
  6. Mounted secrets. Control permissions, RBAC, storage encryption and application reload. Volume updates are eventually propagated; subPath mounts do not receive automatic updates. Files/environment can both leak via privileged access or application logs/dumps.
  7. In-cluster service type = ClusterIP (default).
  8. AWS pod-to-IAM mapping = IRSA (IAM Roles for Service Accounts) using OIDC trust between EKS and IAM.
  9. Image layer storage. Tar layers stored content-addressed by sha256 digest; identical layers shared between images.
  10. kubectl drain. Cordon and eviction can respect PDBs and graceful termination, but flags/workload exceptions and blocked evictions matter. It is an operational procedure to plan/test, not a universally safe command to run automatically.

37. CI/CD & Testing — Quiz With Reasoning

  1. JUnit 5 lifecycle. @BeforeAll usually requires a static method; PER_CLASS test-instance lifecycle allows nonstatic methods. Shared resources need isolation/cleanup and parallel-test discipline.
  2. @Mock vs @Spy. @Mock returns defaults for unstubbed methods; @Spy wraps a real object so unstubbed methods run real code. Spies are useful for partial mocking but easy to misuse.
  3. Pytest fixture scopes. function (default), class, module, package and session. Wider scopes amortize setup but share state; scope is not a substitute for teardown/isolation.
  4. Selenium W3C standard. Standardized WebDriver protocol; replaced JSON Wire and unified browser drivers.
  5. Timing tests. Inject clocks/schedulers for deterministic logic tests; bounded waits may still be needed for integration behavior. Arbitrary fixed sleeps are brittle and slow, not proof of synchronization.
  6. Spark assertDataFrameEqual. Row-order checking defaults false in the referenced testing API; use checkRowOrder=True only with explicitly deterministic input order. Tolerances/nulls/schema also need a stated contract.
  7. SBOM tool = Syft (or Trivy SBOM, Anchore). SBOMs enable vulnerability scanning and supply-chain audits.
  8. SAST vs DAST. SAST analyzes source code at rest; DAST tests a running app. Both are needed; neither alone catches all classes of bugs.
  9. Flaky-test budget. Allowed % of intermittent failures before a test is auto-quarantined. Forces teams to fix flakes rather than ignore them.
  10. Trunk-based development. Faster integration, fewer merge conflicts, continuous delivery friendly. Long-lived branches drift and break.

38. Data Governance — Quiz With Reasoning

  1. OpenLineage core entities = Job, Run, Dataset. Lineage must represent what code (Job) ran in a specific Run, reading/writing which Datasets.
  2. Lineage granularity = table + column level. Column-level requires SQL parsing or runtime plan capture; table-level is the fallback.
  3. Catalog vs metastore. Catalog = governance/business metadata + UX. Metastore = technical schema registry used by query engines (Hive metastore, Glue).
  4. Current vs historical state. Both. Current state for query; snapshots/history for audit and time-travel.
  5. Glossary term vs tag. Glossary terms are curated business vocabulary with definitions and ownership. Tags are lightweight labels.
  6. Lineage edge storage. Graph DB (Neo4j/Neptune) for traversal; or columnar with adjacency for very large scale; often both with replay from raw events.
  7. DQ rule types. Completeness, uniqueness, validity, freshness, referential integrity, reasonableness.
  8. RBAC vs ABAC. RBAC assigns by role; ABAC evaluates subject/resource/environment attributes and supports tag-based rules. It can reduce role explosion but adds attribute freshness, policy-testing and enforcement complexity.
  9. Data contract. Producer-consumer agreement (schema, semantics, SLAs) version-controlled and tested. Breaks the silent-coupling problem.
  10. PII propagation. Tag at source, propagate through lineage automatically; downstream columns derived from a PII column inherit sensitivity until proven otherwise.

39. Agentic AI Design — Quiz With Reasoning

  1. ReAct. Interleaves reasoning-oriented decisions and actions/tool observations. Retain structured action/provenance traces for debugging; do not require exposing private model chain-of-thought.
  2. Separate judge. A second model can provide an additional assessment, but shared training/bias can correlate errors. Calibrate against independent labels and human review instead of assuming independence.
  3. Safe reuse across LLM calls. Similar inputs do not prove equivalent authorized answers. Include tenant, permission/document version, context and model/prompt settings; exact or semantic caching needs task-specific correctness tests.
  4. Trace store. Structured traces can use a dedicated or general observability backend. Redact/scope sensitive content, sample appropriately and define retention; a brand name does not ensure sufficient or lawful telemetry.
  5. Anthropic-style "tool use". Model emits structured tool_use blocks; orchestrator executes and feeds results back.
  6. Bound max iterations. Cost + safety; prevent infinite loops, runaway tool use, and budget overrun.
  7. RAG vs fine-tune for changing data → RAG. Fine-tuning is for stable behavior, not freshness.
  8. GraphRAG. Retrieval that traverses a knowledge graph, expanding context via edges (lineage, ownership, policy).
  9. Agent failure modes. Tool/schema errors, unauthorized actions, injection, wrong results, loops and cost overruns are candidates to measure. No universal ranking is established here.
  10. Tenant filters. Enforce pre-retrieval as filter and pre-generation as assert.

40. Coding-Round Pattern Recognition — Quiz With Reasoning

  1. FIFO queue in Python = collections.deque because popleft is O(1). list.pop(0) is O(n).
  2. Top-K largest streaming numbers. Min-heap of size K. New element compared to heap top; replace if larger. O(n log k).
  3. Subarray sum equals K. Prefix sum + hash map. Tracks how many prefix sums equal current_prefix - K.
  4. Dependencies and ordering = topological sort. Detects cycles when nodes never reach in-degree 0.
  5. Connected components / repeated merges = Union-Find. With path compression + union by rank, operations are nearly O(1).
  6. Next greater element. Scanning left-to-right, keep unresolved indices in nonincreasing value order; pop strictly smaller values and assign the current value as their next greater. Equal values stay for a strictly-greater answer.
  7. Shortest unweighted path = BFS. Each level is one hop, so first reach is shortest.
  8. Shortest weighted path with non-negative weights = Dijkstra. Heap maintains current best; popped distance is final because edge weights are non-negative.
  9. Minimum speed/capacity/time = binary search on answer. Requires monotonic feasibility: if X works, all values ≥ X work.
  10. All combinations = backtracking. Grow a partial solution; backtrack on invalid branches.
  11. Comparison-sort lower bound. Worst-case comparison sorting of arbitrary distinct keys needs Ω(n log n) comparisons (n! possible orders). Timsort has O(n log n) worst-case time and exploits ordered runs; restricted keys or already sorted input have different bounds.
  12. Heap push/pop = O(log n). Sift-up/down along height.
  13. bisect_left returns the first index where the value can be inserted to keep sorted order; equivalent to first index with arr[i] >= target.
  14. Recursion on deep graph. Stack-overflow risk with deep recursion. Use iterative DFS or raise recursion limit cautiously.
  15. BFS vs DFS memory. BFS frontier size follows width; recursive DFS frames follow depth. Graph traversal also needs visited state, often O(V); an iterative DFS that pushes all neighbors can retain siblings too. Both are O(V) worst case on ordinary adjacency-list traversal.
  16. State in DP is a representation of the subproblem whose answer composes the bigger answer. State must capture all decisions affecting the future.
  17. Monotonic feasibility. Property where if X works, every X' on one side also works (or fails). Required for binary search on the answer.
  18. No false negatives, possible false positives = Bloom filter. For a correctly maintained insertion-only filter with consistent hashing, an unset required bit rules out prior insertion; collisions cause false positives. Stale or corrupted filters void that premise.
  19. Approximate distinct count = HyperLogLog. Counts via leading-zero patterns of hashes.
  20. Spark top-N per group. row_number over a partition/order window gives at most N rows per group. Add a unique deterministic tie-breaker; sorting/partition exchange costs and skew still matter.

41. Final Exam — Mixed Drill

Twenty rapid questions to take cold; force yourself to answer each in two sentences using the What → Why → How shape.

  1. Why does AQE flip a sort-merge join to broadcast at runtime?
  2. What's the difference between predicate pushdown and dynamic partition pruning?
  3. Why does Iceberg use column IDs for schema evolution instead of names?
  4. How does an outbox row + Debezium relay deliver effectively-once?
  5. Why is ts >= '...' AND ts < '...' indexable but date_trunc('day', ts) = '...' is not?
  6. Why does a heap of size kk beat sorting for top-K?
  7. Why does volatile not make count++ thread-safe?
  8. Why is BFS the right algorithm for shortest unweighted path?
  9. When can hybrid retrieval improve on dense-only, and how would you test that?
  10. Why does a KV cache reduce dense attention work to O(nd)O(n d) for one new token without making the whole generation linear?
  11. Why is MEMORY_AND_DISK_DESER often safer than MEMORY_ONLY for big DataFrames?
  12. Why is index-free adjacency fast for graph traversal but irrelevant for aggregations?
  13. Why is partition-by-user_id a bad idea for daily logs?
  14. Why does Iceberg need maintenance jobs (expire_snapshots, rewrite_data_files)?
  15. When can ABAC reduce role explosion for multi-tenant access, and what policy complexity does it add?
  16. Why do agents need hard caps on iterations and tokens?
  17. Why does groupByKey().mapValues(_.sum) lose to reduceByKey?
  18. How do you determine a safe Elasticsearch heap size, and why is 32 GB not a universal boundary?
  19. Why does R+W>NR + W > N prove intersection but not, by itself, linearizability in a leaderless store?
  20. Which small-file costs differ between object stores and HDFS, and when does each dominate?

If you can answer all twenty in 60 seconds each with confidence, you're ready.

Worked SCD2 transaction: close and insert are one change

A changed dimension value needs two effects: close the old interval and insert the new version. A single matched MERGE source row does not generally perform both actions. This complete SQLite fixture owns its in-memory connection/transaction, uses a one-current-row partial unique index, a stable (customer,start) dimension identity and half-open intervals. An accepted_change ledger records even unchanged-city events: otherwise a no-op at time 30 could be forgotten and a conflicting replay or late change at 25 accepted. Replays of accepted identities are no-ops; new identities older than the accepted high-water mark are rejected. It does not solve arbitrary bitemporal correction.

import sqlite3
 
db = sqlite3.connect(":memory:")
db.executescript("""
CREATE TABLE dim(customer TEXT, start INTEGER, end INTEGER, city TEXT,
  PRIMARY KEY(customer,start), CHECK(end IS NULL OR end > start));
CREATE UNIQUE INDEX one_current ON dim(customer) WHERE end IS NULL;
CREATE TABLE accepted_change(customer TEXT NOT NULL, ts INTEGER NOT NULL,
  city TEXT NOT NULL, PRIMARY KEY(customer,ts));
""")
 
def change(customer, ts, city):
    with db:
        if not isinstance(customer, str) or not customer or type(ts) is not int or not isinstance(city, str):
            raise ValueError("nonempty customer, integer timestamp and string city required")
        prior = db.execute("SELECT city FROM accepted_change WHERE customer=? AND ts=?",(customer,ts)).fetchone()
        if prior:
            if prior[0] != city:
                raise ValueError("conflicting change identity")
            return
        latest_ts = db.execute("SELECT MAX(ts) FROM accepted_change WHERE customer=?",(customer,)).fetchone()[0]
        if latest_ts is not None and ts <= latest_ts:
            raise ValueError("late correction needs a separate interval-repair protocol")
        db.execute("INSERT INTO accepted_change VALUES (?,?,?)",(customer,ts,city))
        current = db.execute("SELECT start,city FROM dim WHERE customer=? AND end IS NULL",(customer,)).fetchone()
        if current:
            if city == current[1]:
                return
            db.execute("UPDATE dim SET end=? WHERE customer=? AND end IS NULL",(ts,customer))
        db.execute("INSERT INTO dim VALUES (?,?,NULL,?)",(customer,ts,city))
 
def at(customer, ts):
    return db.execute("""SELECT city FROM dim WHERE customer=? AND start<=?
      AND (end IS NULL OR ?<end)""",(customer,ts,ts)).fetchall()
 
change("a",10,"Paris")
change("a",20,"Rome")
change("a",20,"Rome")
assert at("a",9) == []
assert at("a",19) == [("Paris",)]
assert at("a",20) == [("Rome",)]
assert db.execute("SELECT COUNT(*) FROM dim").fetchone()[0] == 2
print("SCD2 half-open intervals, atomic close/insert and retry identity PASS")

In a concurrent production database, serialize changes for a natural key with a lock/conditional transaction and retry conflicts. SQLite memory here establishes local semantics only. For Iceberg/Spark, use engine-supported staged change rows/transactional commit patterns with deduplicated source versions and validate concurrent writers; do not translate the function into two independently committed jobs. Late-arriving dimensions require a fact-key reconciliation policy, while late effective-date corrections may split historical intervals.

Complete reference procedures and execution boundaries

The Spark, Iceberg, Cypher, Elasticsearch, JVM and Kubernetes snippets earlier are reference procedures, not executed services. The following closes their setup/observation/recovery contracts without introducing an installation or deployment:

  • Spark 3.5.7: use a configured session with df_big/df_small having compatible key, and window data with unique id/version tie-breaks. Inspect schemas/null semantics, deterministic expected row multiplicities, explain(True) and actual task/exchange/skew metrics. Session windows require timestamp ts, user identity, a supported output mode and a checkpoint location for streaming. Stop a disposable query and restart from its checkpoint to test state/late-event behavior; a watermark is not a universal discard threshold. No Spark run was performed here.
  • Iceberg 1.9.2: choose a catalog with supported atomic commits, matching Spark extensions and the table schema/version/delete mode. Stage one highest accepted source version per key, including tombstones; execute the version-aware MERGE, compare snapshot rows and retry the same batch. Concurrent incompatible updates must fail/recompute. For maintenance, capture branch/tag references, active readers and retention holds first; expire only eligible snapshots and remove orphans older than the longest plausible writer lifetime. Inspect retained object versions/backups separately. No maintenance command was run.
  • Graph lineage: define derived_column -DERIVED_FROM-> source_column, a tenant-scoped identity constraint/index and half-open validity intervals. Upstream follows the arrow; downstream reverses it. Bind tenant/start/depth/time parameters, inspect EXPLAIN, and only PROFILE a bounded disposable fixture. Test cycles, supernodes, absent nodes and an edge ending exactly at the query timestamp. Unknown/UDF lineage stays marked unknown.
  • Search: create an explicit test mapping (analyzed text versus exact-value fields), ingest a bounded fixture, compare visibility before/after refresh, and distinguish that from durability after process loss. Test tenant/deleted filters and score behavior, PIT/search_after pagination, mapping limits and shard/heap metrics. Bulk refresh changes must be restored. This lesson does not start Elasticsearch or assert its timing.
  • JVM/Python: pin runtime/build and collector; distinguish heap, native/Python and container RSS; capture approved sanitized metrics before changing limits. Java 21 versus 24 monitor pinning and GIL-enabled versus free-threaded CPython need different tests. Heap dumps can contain secrets and require protected handling; no dump/host tuning was performed.
  • Kubernetes/CI: inspect requests/limits, selectors/tolerations, probes, disruption/rollout policy and secret reload semantics in a disposable deployment. Verify readiness removal, liveness restart, startup allowance and eviction behavior independently. Unit/schema/contract tests do not replace workload or cloud integration tests. Build/deploy/drain commands are not executed by this review.

Ten self-check questions — worked answers

  1. Spark path: parse and analyze names/types, optimize expressions, choose a physical plan, then execute stages/tasks. AQE observes shuffle statistics and can coalesce partitions, split supported skew joins or change strategy; the initial plan is not always the final plan.
  2. Shuffle: partitioning, serialization, network fetch, sorting and spill can dominate, but scans/CPU/skew may dominate instead. Push-based shuffle in the pinned version uses supported shuffle mergers/external-service infrastructure; do not confuse it with Kubernetes shuffle tracking.
  3. Iceberg concurrency: write immutable data/metadata, validate against the observed state and atomically replace current metadata. Compatible changes can retry; conflicting row updates cannot be made correct by blindly swapping a pointer.
  4. Graph versus SQL: start from a selective authorized anchor, bound depth and visited work, and compare the same result semantics on realistic degree distributions. A 100M-edge total alone predicts neither engine's latency; one supernode may dominate both.
  5. Attention: n queries × n keys create n² scores in dense full attention. Cached decode computes the new query against prior keys/values, while FlashAttention tiles computation to avoid materializing the full score matrix in HBM. Neither removes all quadratic full-context compute.
  6. Tenant RAG: authorize before exposing retrieval candidates, enforce source access again before prompt/output, scope caches, and treat snippets as untrusted data. Typed tools need independent permission/approval; a citation or model confidence score is not authorization.
  7. CDC: durable source change identity/version, broker progress and sink commit must be coordinated. A crash after sink commit can replay; dedup/version-aware tombstones prevent duplicate increments or resurrection. Generic MERGE alone is not an exactly-once proof.
  8. Join selection: compare estimated/actual bytes, keys, ordering/partitioning and memory. Broadcast only within a safe materialized-size budget; shuffle hash needs build-side memory; sort-merge handles large equijoins with sort/shuffle costs. AQE decisions are constrained by supported strategies and configuration.
  9. Outbox: business row and event row share one transaction; a relay may publish twice after an ambiguous ACK. Stable event identity and idempotent consumers close the duplicate-effect side, not an imagined cross-system atomic transaction.
  10. PII: regex/dictionary and learned classifiers produce evidence-backed proposals, not legal determinations. Evaluate false positives/negatives, minimize sampled sensitive values, enforce tool/catalog permissions and human review where policy requires it; record provenance and overrides.

Final exam — full worked answers

  1. AQE switch: runtime materialized statistics can show a join side is small enough for a supported broadcast strategy. Check final plan/thresholds rather than claiming any small side must broadcast.
  2. Pushdown versus DPP: pushdown sends eligible predicates/projections into a scan; dynamic partition pruning derives a runtime partition filter from another relation's filtered keys. They can complement one another.
  3. Column IDs: identity survives rename/reorder, so old files retain the right meaning. Safe evolution still requires type/engine compatibility and rejects reuse of a dropped field's identity.
  4. Outbox/Debezium: transactionally store event with business state, relay with stable identity, and atomically dedup effects at the sink. Debezium publication alone does not atomically commit an external email/payment.
  5. Range predicates: ts >= start AND ts < end can use a normal ordered index while preserving a half-open day boundary. A function expression may need a matching expression index and correct time-zone semantics. Indexability does not force the optimizer to choose an index.
  6. Top-K: a size-k min-heap keeps only the current winners, costing O(n log k) and O(k) space; sorting all n costs O(n log n). If k≈n or input is already sorted, the comparison changes; k=0 must be handled.
  7. Volatile counter: visibility/order do not make read-modify-write atomic. Use AtomicLong CAS/increment or a lock for an exact invariant; LongAdder's concurrent sum is not an atomic snapshot.
  8. BFS: a FIFO frontier explores unit-edge distances in layers, so first discovery gives the fewest hops. Weighted paths need different assumptions/algorithms.
  9. Hybrid retrieval: keyword matching finds exact rare identifiers while embeddings can find paraphrases. RRF combines ranks with a rank constant/window; test whether the combination improves the target metric rather than assuming it always does.
  10. KV cache: retain prior K/V and compute the new token's attention against them in O(n d); full generation still sums over growing n. It reduces recomputation, not all quadratic workload or cache memory growth.
  11. Persistence: the pinned PySpark DataFrame default MEMORY_AND_DISK_DESER can retain partitions on disk rather than recomputing every evicted one. It can still lose to recomputation and does not cure skew/OOM by itself.
  12. Adjacency: native neighbor access reduces repeated lookup overhead after anchoring. Aggregation over many vertices/edges still scans/combines data; pointers cannot remove output-size costs.
  13. Partitions: a user-ID directory per user can explode metadata/tiny files for daily scans. Date partitioning/bucketing can suit the query, but very high daily volume or tenant isolation may need another layout. Hidden partition transforms make this an engine-level choice rather than a hard universal ban.
  14. Maintenance: rewrite small files/manifests for planning/read efficiency; expire eligible snapshots to release unreferenced history. Preserve branches, holds and active-writer safety margins, and audit actual deleted objects/versions.
  15. ABAC/RBAC: ABAC evaluates subject/resource/environment attributes and may avoid a combinatorial role set; RBAC remains simpler for stable roles. Both need default denial, consistent enforcement and tested policy changes.
  16. Agent caps: bound tool/model iterations, time, tokens and side effects before admission. A loop that fails to finish returns a truthful bounded failure, not an automatic permission to expand access or spend.
  17. Map-side combine: associative/commutative aggregation reduces values before transfer; groupByKey must retain more raw values. Floating-point order and nonassociative operations may change exact results, so the equivalence has assumptions.
  18. Heap sizing: verify the actual compressed-oops threshold and reserve filesystem/native memory. Current Elastic guidance cites 26 GB as generally safe, sometimes ~30 GB; 32 GB is not a universal law.
  19. Quorum: R+W>N establishes fixed-set intersection. Concurrent/partial writes, stale/sloppy quorums and conflict rules still require an ordering protocol; LWW timestamps alone do not establish linearizability.
  20. Small files: object requests and planning/listing overhead are expensive relative to tiny payloads; HDFS also pays NameNode/file metadata and open costs. Compare workload/engine/storage evidence, not a blanket claim that one is always worse.

Bloom-filter billing exercise

A Bloom filter says an invoice event has been seen. May the consumer drop it? No: a positive may be a false positive, so check the authoritative deduplication ledger and atomically record identity with the billing effect. A negative is useful only if the filter is current and correctly maintained with consistent encoding/hashes; it is not a permission or financial-authority decision.

Versioned primary sources and local proof

The earlier review checked these references on 8 October 2026. Exact saved governing passages were reread and hash-verified; reuse of version-pinned evidence is not relabeled as new HTTP research. The 9 October body review freshly rechecked the mutable S3, GCS, Elastic, Kubernetes, OpenLineage, Iceberg specification and OWASP documentation, plus pandas apply, PostgreSQL 16 EXPLAIN and Spark iterator memory. References include Spark 3.5.7 configuration, SQL tuning, DataFrame persistence, Iceberg 1.9.2 reliability, writes, maintenance, PostgreSQL 16 isolation, indexes, S3 current multipart limits, Hadoop 3.3.6 committers, Elastic JVM settings, JEP 491, Python 3.13 free threading, Kubernetes resource management, OpenLineage model and pgvector v0.8.0.

The standard-library algorithms and SQLite program have bounded independent tests. No Spark/Iceberg/graph/search service, cloud operation, JVM benchmark, Kubernetes deployment, trained model or LLM/agent call was run. The local checks establish example behavior, not production integration or deployment readiness.

Pause / Recall / Apply

Can you explain it without the page?

Close the example. Reconstruct the core idea, then change one assumption. Mark complete when you’re ready; you can always undo it.

Stored in this browser only. No account, no sync. Clearing browser data removes your record.