Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/knut

crates/knut-thund/.nornir/thund-vs-spark-gap.md

Þund vs Spark — the definitive scope of "complete thund"

The question this doc answers: what does it take for knut-thund's NATIVE backend (DataFusion + skade/Iceberg) to be a COMPLETE replacement for Spark on knut's workloads — so a user can drop Spark entirely for these pipelines.

Two gap axes are catalogued here:

  • GAP 1 — SDP-over-Connect drop list (streaming/quality knobs Spark Declarative Pipelines cannot express). Status: closed — see the last section. Every item on SparkBackend::lowering_report's drop list is now executed by the native backend with a real row-level test.
  • GAP 2 — Databricks + Delta Lake features BEYOND OSS Spark + Iceberg. This is the forward-looking scope: for each proprietary/Delta feature, does thund-native already have it, NEED it, or is it OUT-OF-SCOPE (Databricks- proprietary, not required for knut's workloads)? Iceberg (via skade / iceberg-rust) already has equivalents for most of Delta's table features — we build on those, we do not reinvent Delta.

Date: 2026-07-29. Sources: Delta Lake docs (delta.io), Apache Iceberg docs (iceberg.apache.org), Databricks platform docs + 2025/2026 platform notes.


0. What "complete" means here

"Complete" is not "reimplement Databricks." Databricks is a managed platform whose value is largely operational (a cluster manager, a governance plane, a proprietary vectorized engine). knut already has its own substrate (skade's all-core gatling data plane, an embedded Iceberg catalog, korp's orchestration). So "complete thund" = the pipeline-authoring + execution surface knut needs is fully served by the native backend, with Iceberg as the table format. Anything that is a managed-platform concern, or a Delta-format-lock-in concern, is out-of-scope by construction — Iceberg is the chosen format.

Three verdicts are used below:


1. OSS Spark surface (the baseline thund must match)

These are the Spark capabilities knut's pipelines actually lean on. This is the floor — below "complete" is not negotiable.

Spark capability thund-native Status Notes
Batch SQL (SELECT/join/agg/window fns) DataFusion HAVE The native backend runs the IR's SQL on DataFusion for real.
Column pruning + predicate pushdown DataFusion scan HAVE Typed projection/filter IR nodes push into the scan (see native_projection_pushdown.rs).
Structured Streaming (micro-batch) recompute + incremental paths HAVE Kafka/CDC/file-drop micro-batch driving; watermark, windows, triggers, output modes.
Event-time watermark + allowed lateness WatermarkTracker HAVE min-across-inputs, idle-timeout, late-row eviction (GAP-1).
Tumbling / hopping / session windows incremental window operator HAVE Close/evict on watermark; incremental keyed aggregation.
Output modes Append / Update / Complete recompute delta / full HAVE Complete = full table/trigger; Update = changed-row upsert delta (not a retraction stream — honest).
APPLY CHANGES / CDC merge (SCD1 + SCD2) apply_cdc_merge HAVE INSERT/UPDATE_AFTER upsert, DELETE + apply_as_deletes predicate, UPDATE_BEFORE pre-image drop, SCD2 __start_at/__end_at.
Data-quality expectations (EXPECT/ON VIOLATION) apply_expectations HAVE Warn (keep+record) / Drop (filter) / Fail (abort).
Checkpoint + crash recovery barrier/epoch store HAVE Resumes without double-emit (checkpoint.rs).
File I/O (parquet/csv/arrow/json), object-store DataFusion + object_store HAVE File source + sink, partitioned writes.
Iceberg read/write skade HAVE (via skade) skade is knut's Iceberg stack; the IR's iceberg source/format lowers to it.
Broad connector ecosystem (JDBC, Kafka, many sinks) partial NEED knut needs the connectors its workloads use (Kafka, object-store, Iceberg); the long tail of Spark connectors is not a goal. Scope to knut's actual sources.

Verdict on the OSS baseline: thund-native already covers it for knut's workloads. The one real gap is connector breadth, and that is scoped to the sources knut actually ingests, not Spark's entire connector zoo.


2. Databricks features BEYOND OSS Spark

These are the proprietary, managed-platform features. Almost all are OUT-OF-SCOPE — they are either a managed service, or the exact thing knut's own substrate replaces.

Databricks feature What it is thund verdict Why
Photon Proprietary C++ vectorized engine OUT-OF-SCOPE knut's engine is DataFusion (vectorized Arrow) + skade's gatling all-core data plane. That IS knut's Photon-equivalent; it is not a feature to import.
Unity Catalog (governance) Metastore, access control, lineage, discovery OUT-OF-SCOPE (mostly) knut has an embedded Iceberg catalog (skade). Governance/lineage is a korp/nornir concern, not a thund concern. The catalog need is HAVE (skade); the governance plane is not thund's job.
Delta Live Tables / DLT Declarative pipeline framework HAVE (this IS thund) thund's IR + ExecBackend + expectations + streaming tables ARE the DLT-equivalent, backend-portable to native OR Spark-SDP. This is the whole point of the crate.
Databricks SQL (DBSQL) / serverless warehouses Managed SQL endpoints, autoscaling OUT-OF-SCOPE A managed-service/ops concern. Query execution itself is HAVE (DataFusion).
Predictive Optimization Auto OPTIMIZE/VACUUM/clustering OUT-OF-SCOPE / future An automation layer over table maintenance. The underlying maintenance ops (compaction, expiry) are Iceberg/skade table-maintenance — see §3. Automation is a korp scheduling concern if ever needed.
Predictive I/O / IWM Proprietary caching/autoscaling OUT-OF-SCOPE Managed-runtime performance features.
Mosaic AI / MLflow (managed) ML platform OUT-OF-SCOPE Not a data-pipeline-engine concern.

Verdict on Databricks-proprietary: nothing here is a thund gap. The one that looks like a feature — DLT — is precisely what thund already is. Everything else is a managed service or a proprietary engine that knut's own substrate (DataFusion + skade) already stands in for.


3. Delta Lake table features vs Iceberg (via skade) — the real catalogue

This is the substantive part. Delta and Iceberg are competing table formats; knut has chosen Iceberg (skade). For each Delta feature, the question is: does Iceberg have the equivalent (so skade gives it to us), does thund-native need to WIRE it, or is it Delta-lock-in we don't want?

Delta feature What it does Iceberg equivalent thund verdict
Time travel Query an older table version Snapshots + AS OF HAVE (skade) — skade tracks snapshots; wire an IR "as-of" read knob if a knut workload needs it.
MERGE (upsert) Join-based insert/update/delete Iceberg row-level MERGE / equality+position deletes HAVE (thund CDC) — thund's apply_cdc_merge IS the keyed merge; on Iceberg it lands via skade's delete surface (below).
Deletion vectors Mark deleted rows w/o rewriting file (MoR) Position deletes (merge-on-read) HAVE (Iceberg/skade) — position deletes are the deletion-vector analog. Iceberg had this first; do NOT reinvent DV. Wire an IR delete/MERGE-emits-deletes path onto skade if needed.
Change Data Feed (CDF) Read row-level changes between versions Incremental append scan + snapshot diff HAVE-ish (Iceberg/skade) — Iceberg incremental scans read appended data since a snapshot; full row-level CDF (updates/deletes as a changelog) is thinner. NEED if a knut workload consumes downstream CDC out of an Iceberg table (thund currently consumes CDC in).
Liquid clustering Auto multi-dim clustering, no fixed partitions Hidden partitioning + sort orders HAVE (Iceberg/skade) — Iceberg hidden partitioning (bucket/truncate/months…) + sort order is the non-Delta answer. thund's IR already carries partition transforms (partition_transforms capability); native must honor them on write (NEED to wire transform-write to skade).
OPTIMIZE / ZORDER Compaction + Z-order data layout Compaction (rewrite_data_files) + sort order / Z-order NEED (skade maintenance) — table maintenance op; belongs in skade, invoked by thund/korp. Z-order is a sort-order variant.
Generated columns Column value computed from an expression Iceberg has no generated cols; do it in the flow query HAVE (via query) — a thund flow's SELECT computes derived columns; no format feature needed.
Identity columns Auto-increment surrogate key No Iceberg equivalent (disables concurrency in Delta anyway) OUT-OF-SCOPE — generate keys in the flow (monotonic/uuid) if a workload needs one; Delta's identity col is a concurrency-hostile anti-pattern.
Column mapping Rename/special-char cols via metadata Iceberg tracks columns by field-id natively HAVE (Iceberg) — Iceberg's field-id schema evolution is strictly better; no mapping layer needed.
Constraints (NOT NULL / CHECK) Enforce row predicates on write Iceberg has required fields; CHECK is engine-side HAVE (thund expectations) — thund's Expectation + ON VIOLATION Fail IS the CHECK constraint; NOT NULL is a schema property.
Schema evolution Add/rename/drop columns safely Iceberg field-id schema evolution HAVE (Iceberg/skade)
Table branching / tags (WAP) Isolated write-audit-publish branches Iceberg branches + tags HAVE (Iceberg/skade) — skade tracks branches/tags; wire an IR write-to-branch knob if a knut WAP workflow needs it.

Verdict on Delta-vs-Iceberg: Iceberg (skade) already provides equivalents for essentially every Delta table feature — often the original of it (position deletes predate deletion vectors; hidden partitioning predates liquid clustering; field-id evolution beats column mapping). The remaining thund work is wiring the IR to skade's existing Iceberg surface, not building new format features and emphatically not adopting Delta.


4. Prioritized worklist — what remains for "complete thund"

Ordered by leverage for knut's actual workloads. (GAP-1 items are DONE — §5.)

  1. Iceberg write-path via skade for the native backend. Today the native backend materialises to in-process MemTables + file sinks; wire dataset OutputType + iceberg format to a real skade table write (append + snapshot commit). This is the single biggest lever — it turns "runs the computation" into "runs the computation AND lands it in the lakehouse." Blocker to note: the skade version collision (published 0.9.8 vs the local fork 0.9.1) must be resolved before knut-thund can add a skade dep; knut-thund does not depend on skade today (only graphar-flight does), so this is a clean add once the version is pinned.
  2. Partition-transform write (bucket/truncate/months/…). The IR + the partition_transforms capability already model it and check gates it; the native writer must honor transforms when landing to Iceberg (the liquid- clustering-equivalent). Depends on (1).
  3. Position-delete / MERGE-emits-deletes onto Iceberg. thund's CDC merge currently produces a merged snapshot; to be a true incremental lakehouse sink it should emit Iceberg position/equality deletes (the deletion-vector analog) via skade rather than rewriting. Depends on (1).
  4. Snapshot / time-travel read knob. An IR "read table AS OF snapshot/tag" source, lowering to skade snapshot reads. Cheap once (1) lands.
  5. Change Data Feed OUT. If a knut workload needs to emit a row-level changelog from an Iceberg table (not just consume CDC in), add an incremental changelog scan over skade snapshot diffs. Demand-driven — only if a workload needs it.
  6. Table maintenance (compaction / snapshot expiry). OPTIMIZE-equivalent; belongs in skade, scheduled by korp. Not a thund runtime concern.
  7. Connector breadth — scope strictly to knut's real sources (Kafka, object-store, Iceberg). Not a goal to match Spark's connector zoo.

Explicitly OUT-OF-SCOPE (do not build): Photon, Unity Catalog governance, DBSQL/serverless, Predictive Optimization/IO, identity columns, column mapping, and the Delta format itself. Iceberg + skade + DataFusion is the substrate.


5. GAP-1 status — SDP drop list, now run by Native (DONE)

SparkBackend::lowering_report enumerates every knob SDP-over-Connect drops. Each is now executed by the native backend with a real, passing, row-level test. The report strings themselves now name the native counterpart ("RUN BY NATIVE: …"), so the "dropped by SDP / run by Native" status is self-documenting.

SDP drop Native runs it? Proof (test)
watermark (allowed-lateness late-row eviction) YES iceberg_spark_gap::native_runs_what_sdp_drops_watermark_and_window; streaming_microbatch::incremental_watermark_drops_and_counts_late_rows
event-time window close/evict (tumbling/hopping/session) YES streaming_microbatch::windowed_*; iceberg_spark_gap
ProcessingTime trigger YES (per micro-batch; wall-clock advisory) sdp_drop_coverage::processing_time_trigger_runs_native_and_sdp_drops_it
Complete output mode YES (full table each trigger) sdp_drop_coverage::complete_mode_emits_full_table_each_trigger_distinct_from_update
Update output mode YES (changed-row upsert delta) sdp_drop_coverage::update_mode_emits_only_changed_rows_and_sdp_drops_it
CDC apply-changes (dropped entirely on 4.1) YES streaming_microbatch::cdc_scd_type1_upserts_and_deletes; iceberg_spark_gap::cdc_gap_*
CDC apply_as_deletes predicate YES sdp_drop_coverage::cdc_apply_as_deletes_predicate_removes_row_native
CDC UPDATE_BEFORE pre-image drop YES sdp_drop_coverage::cdc_update_before_preimage_is_dropped_native
SCD Type2 history (dropped on 4.2) YES streaming_microbatch::cdc_scd_type2_keeps_version_history; iceberg_spark_gap::cdc_gap_*
expectations Warn / Drop / Fail YES sdp_drop_coverage::warn_expectation_keeps_rows_and_records_native; streaming_microbatch::{drop,fail}_expectation_*

Honest caveats (not overclaimed):