Þ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:
- HAVE — thund-native (DataFusion + skade/Iceberg) already does it, or gets it for free from the format/engine.
- NEED — genuinely required to be a complete Spark alternative for knut's workloads, and not yet wired. Goes on the worklist.
- OUT-OF-SCOPE — Databricks-proprietary or Delta-format-specific; Iceberg and/or knut's own substrate is the answer, or knut's workloads don't use it.
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.)
- Iceberg write-path via skade for the native backend. Today the native backend materialises to in-process MemTables + file sinks; wire dataset
OutputType+icebergformat 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 (published0.9.8vs the local fork0.9.1) must be resolved before knut-thund can add a skade dep; knut-thund does not depend on skade today (onlygraphar-flightdoes), so this is a clean add once the version is pinned. - Partition-transform write (
bucket/truncate/months/…). The IR + thepartition_transformscapability already model it andcheckgates it; the native writer must honor transforms when landing to Iceberg (the liquid- clustering-equivalent). Depends on (1). - 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).
- Snapshot / time-travel read knob. An IR "read table AS OF snapshot/tag" source, lowering to skade snapshot reads. Cheap once (1) lands.
- 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.
- Table maintenance (compaction / snapshot expiry). OPTIMIZE-equivalent; belongs in skade, scheduled by korp. Not a thund runtime concern.
- 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):
Updatemode is an upsert delta (changed/new rows re-emitted), NOT a retraction stream — the native run/event surface carries no negative-row retract marker. For an updating sink this is the correct changed-row feed; true retract streams are a separate, larger engine feature and are not claimed.ProcessingTimefires one trigger per arriving micro-batch; the wall-clockinterval_msis advisory because the in-process synthetic source has no real-time clock. Against a live Kafka source the trigger cadence would follow real arrivals.- The native backend RUNS these knobs in-process (MemTable/file materialisation). Landing results into an Iceberg table via skade is worklist item (1) above — that is the boundary between "native runs the pipeline" and "native is a full lakehouse sink."