This final chapter is a field guide. It tells you where to start, what is solid, what is evolving, and how to keep your mental model synchronized with the code.
Sail moves quickly, so treat exact capability claims as snapshots. The stable part is the architecture: front doors converge to spec, spec resolves to DataFusion, DataFusion plans run locally or through the distributed job runner, and Arrow batches move through every layer.
| Goal | Start here |
|---|---|
| Understand a PySpark query | crates/sail-spark-connect/src/service/plan_executor.rs |
| Understand SQL parsing | crates/sail-sql-parser/src/parser.rs |
| Understand SQL to spec | crates/sail-sql-analyzer/src/statement.rs |
| Understand spec to logical plan | crates/sail-plan/src/resolver/ |
| Understand session setup | crates/sail-session/src/session_factory/server.rs |
| Understand physical planning | crates/sail-session/src/planner.rs |
| Understand local execution | crates/sail-execution/src/job_runner.rs |
| Understand distributed execution | crates/sail-execution/src/driver/ |
| Understand job graph splitting | crates/sail-execution/src/job_graph/ |
| Understand shuffles | crates/sail-execution/src/plan/shuffle_*.rs and
crates/sail-execution/src/stream/ |
| Understand Python UDFs | crates/sail-python-udf/src/ |
| Understand catalogs | crates/sail-catalog/ and
crates/sail-session/src/catalog.rs |
| Understand table formats | crates/sail-common-datafusion/src/datasource.rs and
crates/sail-session/src/formats.rs |
| Understand Delta and Iceberg | crates/sail-delta-lake/,
crates/sail-iceberg/, and
crates/sail-session/src/planner.rs |
| Understand extensions | crates/sail-session, crates/sail-plan,
crates/sail-execution/src/codec.rs |
| Symptom | Likely area |
|---|---|
| PySpark API call fails before planning | Spark Connect service/proto conversion |
| SQL text parses incorrectly | SQL parser or analyzer |
| Column cannot be resolved | resolver state or attribute resolution |
| Function gives Spark-incompatible output | function registry or implementation |
| Works in SQL but not DataFrame API | protocol-to-spec conversion mismatch |
| Works locally but not in cluster | codec, worker session, shuffle, or task execution |
| Table name resolves incorrectly | catalog manager or namespace handling |
| File scan has wrong schema | table format or source option resolution |
| Merge/delete fails | lakehouse optimizer/planner path |
| Streaming query starts but never finishes/stops | streaming query manager or background task |
The following areas are central and well established architecturally:
RecordBatch execution,JobRunner,Mature does not mean bug-free. It means the architecture is settled enough that new work should usually fit into the existing pattern.
Several areas are active design surfaces:
When working in these areas, prefer small changes that preserve future extension options.
At the time this guide was prepared, the important capability shape is:
| Area | Current shape |
|---|---|
| PySpark/DataFrame | Primary target through Spark Connect |
| SQL | Custom parser/analyzer, Spark syntax focus |
| Flight SQL | Secondary SQL front door |
| Arrow | Core memory and wire model |
| DataFusion | Query kernel |
| Local execution | Direct DataFusion stream execution |
| Cluster execution | Driver/worker/job graph/task stream architecture |
| Python UDFs | Multiple PySpark UDF/UDTF paths |
| Catalogs | Memory, Glue, HMS, Iceberg REST, Unity, OneLake, system |
| Formats | Listing file formats, Delta, Iceberg, Python data sources |
| Delta | Reads, append/overwrite writes, row-level paths for MERGE/DELETE, variant-related work |
| Iceberg | Read path, write support evolving |
| Streaming | Architecture present, feature coverage evolving |
Always verify exact support in the current repository before documenting a public claim. Capability surfaces move faster than architecture chapters.
The crate graph has a useful directional shape:
protocol crates
-> spec/resolver/session crates
-> DataFusion extension crates
-> execution/storage/support crates
The most important rule is separation:
sail-spark-connect should know about Spark
protobufs.sail-plan should know about Sail spec and DataFusion
logical plans.sail-session should assemble DataFusion session state
and physical planners.sail-execution should execute physical plans without
caring which protocol produced them.When a change violates that separation, pause. Sometimes it is necessary, but it usually signals that a boundary type or extension point is missing.
Bookmark these first:
crates/sail-spark-connect/src/service/plan_executor.rscrates/sail-common/src/spec/plan.rscrates/sail-plan/src/lib.rscrates/sail-plan/src/resolver/plan.rscrates/sail-plan/src/resolver/query/mod.rscrates/sail-session/src/session_factory/server.rscrates/sail-session/src/planner.rscrates/sail-execution/src/job_runner.rscrates/sail-execution/src/job_graph/planner.rscrates/sail-execution/src/codec.rscrates/sail-common-datafusion/src/session/job.rscrates/sail-common-datafusion/src/datasource.rsThese files are not the whole system. They are the quickest route back to the architecture when you feel lost.
The entire guide can be compressed to one path:
client intent
-> protocol-specific message or SQL
-> Sail spec
-> DataFusion logical plan with Sail extensions
-> optimized logical plan
-> physical plan with Sail extensions
-> local stream or distributed job graph
-> Arrow RecordBatch stream
-> protocol-specific response
And one warning:
If a feature cannot survive every boundary it crosses, it is not complete.
Sail is interesting because it is not merely a Rust rewrite of Spark. It is a careful compatibility layer over a modern Rust query stack. The best way to learn it is to follow conversions: protobuf to spec, SQL to spec, spec to logical plan, logical plan to physical plan, physical plan to job graph, job graph to tasks, tasks to Arrow streams, Arrow streams back to the client.
That is the shape to preserve as Sail grows.
Navigation: Previous: Chapter 18, Feature Playbooks | Reader Guide