Documentation Style Guide#
This guide is for anyone who writes or reviews Comet documentation: pages under docs/source,
README.md, and the user-facing .doc(...) strings in CometConf.scala and in contrib config
classes. It defines the words Comet’s docs use for how a query runs, and the conventions every page
follows. The vocabulary rules (T, N, O) and the accuracy rules (P13, P16) also apply to doc
comments, AGENTS.md, and the agent skills under .ai/skills, with the contributor guide’s
allowances. The rules apply to new and changed text. Existing pages move over as they are edited, so
a review does not ask a change to fix text it leaves alone, unless the change makes that text wrong
(P16).
The sections cover how Comet runs a query, the vocabulary, the rule for “native”, operator names, page conventions, and generated content.
Each rule has an ID, such as N1. Reviews cite the ID, so IDs are never renumbered or reused.
Where this guide and the code disagree, the code is right and the guide needs a fix. The
background for the vocabulary is in #4419.
How Comet Runs a Query#
The terms below describe this model. Comet Plugin Architecture has the details.
Comet’s planner rules replace the Spark operators Comet supports with Comet operators, most of them named
Comet*, and serialize their expressions to protobuf. An operator Comet does not support stays a Spark operator.Most Comet operators run in Comet’s Rust code, which is built on Apache DataFusion and is called from the JVM through JNI. Some do their own work on the JVM, such as broadcast exchanges.
Comet’s query operators usually pass data to each other as Apache Arrow batches. Some expressions run Spark’s own generated JVM code over those batches.
Vocabulary#
Use the terms in the first column. They answer three separate questions: does Comet handle the work, which language runs it, and what format is the data in. A term from one question says nothing about the other two.
ID |
Use |
For |
Instead of |
|---|---|---|---|
T1 |
Comet pipeline, runs in Comet |
Work that a Comet operator handles, whatever the language |
“the native path”, “runs natively” |
T2 |
falls back to Spark, Spark fallback |
Work that stays in a Spark operator |
— |
T3 |
Rust-implemented, runs in Rust, native Rust |
Work done by Comet’s Rust code, usually through DataFusion |
“native” on its own |
T4 |
JVM-side, runs on the JVM |
A Comet operator whose own work runs on the JVM, such as |
“non-native Comet operator” |
T5 |
codegen dispatch |
Spark’s generated JVM code for an expression, run over Arrow batches from inside a Rust-implemented plan |
“falls back” (it does not fall back) |
T6 |
Native, Codegen dispatch, Hybrid |
The expression kinds in the Implementation legend |
Any other meaning of “Hybrid” |
T7 |
Arrow-native, Arrow batches, Arrow columnar |
Claims about data format: the data is held in Arrow columnar format |
“vectorized” as a synonym for columnar (T10) |
T8 |
Arrow IPC |
The format shuffle and broadcast write |
— |
T9 |
Spark rows ( |
Spark’s row format, or a Spark |
“row format”, “JVM format” without saying which |
T10 |
vectorized |
Proper names, such as Spark’s vectorized Parquet reader, and batch-at-a-time kernels contrasted with row-by-row loops |
“vectorized” as a synonym for columnar |
Notes on these terms:
Hybrid (T6) describes an expression that has both a Rust path and a Spark-compatible path, usually codegen dispatch. Each use of the expression in a plan takes one path. The term comes from
GenerateDocs, which writes the Implementation column ofexpressions.md. Do not use “hybrid” for a plan that mixes Comet and Spark operators. That is a plan with fallback (T2).Arrow-native (T7) is about format. It does not mean that no per-row work happens: codegen dispatch evaluates each row, and JVM shuffle buffers
UnsafeRows before encoding them to Arrow IPC.Spark rows (T9) can appear inside the Comet pipeline. JVM shuffle, for example, buffers
UnsafeRows. The format alone does not say whether Spark or Comet handles the work.Expressions are not plan nodes. An expression runs inside an operator such as
CometProjectorCometFilter. A codegen-dispatched expression leaves its operator Rust-implemented.
The Rule for “Native”#
“Native” has several meanings in Comet’s docs, so on its own it does not tell the reader which one is meant. Name the meaning instead.
N1. Do not use runs natively, natively in Comet, the native path, the native pipeline, native execution, native operator, or native Comet operator, in any tense or form, such as ran natively, go native, a native path, or Comet-native. Write in the Comet pipeline (T1) or Rust-implemented (T3). When “native” means “did not fall back to Spark”, write T1, because codegen dispatch (T5) is not a fallback. When the sentence is about Spark’s own behavior, N5 applies.
N2. These compounds are allowed everywhere, because the second part fixes the meaning: native Rust; native shuffle and the native shuffle path (the counterpart of JVM shuffle, which need not appear beside it); “native”, optionally followed by a format, followed by scan, reader, write, or writer, only for Comet’s Rust-implemented scans and writers (native scan, native Parquet reader, native Iceberg write), not for a JVM-side scan such as
CometInMemoryTableScanExec; native memory and native library (the terms Comet’s own log lines use); Arrow-native (T7); Native as the expression kind (T6); and native implementation of an expression, meaning its Rust implementation.N3. In the contributor guide, “native” before a code noun is also allowed when it means the crates under
native/or the JNI boundary: native code, native side (and native-side logs), native plan, native kernel, and JVM/native boundary. The noun must name something undernative/, such as a crate, file, type, function, or plan, or the JNI boundary. Pages outside the contributor guide, includinguser-guide/,about/,index.md,README.md, and.doc(...)strings, allow only the N2 compounds and write Rust instead. N1 applies everywhere. Any other use of “native” is a finding: name the meaning with a term from the vocabulary above.N4. Do not use “native” as a noun. Write the Rust side or name the component: “hands each block to the Rust side”, not “hands each block to native”.
N5. Do not call Spark’s own behavior native. Write Spark’s built-in shuffle, not Spark’s native shuffle.
N6. Quote identifiers, config keys and values, API names, log lines, and plan output exactly as they are, in code font.
CometNativeScan,CometNativeExec, andspark.comet.shuffle.mode=nativeare not prose and do not break N1. Text that quotes a phrase in order to discuss it, such as the “Before” column below, does not break these rules either.
Examples:
Before |
After |
|---|---|
Runs your Spark queries on the DataFusion native engine. |
Speeds up your existing Spark queries with Apache DataFusion. |
The surrounding native operators keep running natively. |
The surrounding operators stay in the Comet pipeline. |
Keeps evaluation inside the native pipeline. |
Keeps evaluation inside the Comet pipeline. |
Takes columnar input directly from Comet native operators. |
Takes Arrow batches directly from Comet operators. |
Operators, Plan Names, and Expressions#
Understanding Comet Plans describes the nodes a plan can contain, grouped by role. Its Comet Operator Reference lists them. Link to it rather than copying it.
Do not infer an operator’s kind from its class hierarchy. For example,
CometTakeOrderedAndProjectExec runs Rust plans without extending CometNativeExec. Most Comet
operators print a Comet prefix, but not all: IcebergWrite and IcebergCommit are Comet
operators too.
O1. In prose about plans, use the name that plan output prints. Spark drops the
Execsuffix from most names (CometProjectExecprints asCometProject), but some operators keep it, such asCometWindowExec, andCometShuffleExchangeExecprints asCometExchangeorCometColumnarExchange. This is thenodeName, which formatted explain output and the Spark UI print. Some classes also overridesimpleString, which changes the linedf.explain()prints:CometTakeOrderedAndProjectExecprints asCometTakeOrderedAndProjectExec(...)there. Quote plan output exactly as it was printed (N6), and check the operator reference when unsure.O2. Use the class name, with
Exec, only when the text is about the code, including text about how a planner rule rewrites a plan.O3. The current names are the correct names. Do not describe renames that have not happened.
O4. Expressions are not operators. Describe an expression by its kind (T6), not as a node in the plan.
Page Conventions#
P1. Every new Markdown file starts with the ASF license header, as in this file.
P2. A page has one H1, and heading levels are not skipped.
P3. The first paragraph says who the page is for and what it covers. A long page then gives a short overview of its sections.
P4. On pages under
docs/source, link to another page by its path, either relative to the current page or starting with/for thedocs/sourceroot, not by the published docs URL, because the site’s address can change.README.mdis rendered by GitHub, so it links to the published docs.P5. Do not hand-edit generated content. Change the source it comes from instead. See Generated Content.
P6. Every new page is added to a toctree. Pages for users go under
docs/source/user-guide/ordocs/source/about/, and pages for contributors go underdocs/source/contributor-guide.P7. The user guide describes what Comet does today, in the present tense. Plans go in the roadmap. Do not leave
TODOorXXXmarkers in published pages.P8. A claim that depends on a version, such as “Spark 3.5 and later”, “Spark 3.5+”, or “Spark 4.0 only”, matches the versions the code supports. The same applies to other dependencies, such as Iceberg.
P9. Prefer the active voice, and address the reader as “you”.
P10. Do not use marketing words such as “blazing fast”, “seamless”, or “powerful”. State the measured result, or leave the claim out.
P11. Use a table to compare several items, such as operators, configs, or support levels.
P12. Plan output in a page is real output from
explain, in Spark’s tree format with+-connectors, and is shown in a code block.P13. Config keys, class names, and file paths named in a page exist in the code. Code examples are valid in the language their block declares. Follow Configuration Conventions for new configs.
P14. A new user guide page includes at least one example a reader can run and links to related pages. A new contributor guide page that describes a procedure says how to check that it worked.
P15. A change that adds an operator updates the Comet Operator Reference and Supported Spark Operators in the same pull request. A change that adds an expression updates its row in Spark Expression Support.
P16. Describe the code as it is now. Remove text about code that has been removed, keep sample output, such as explain output and messages, in step with the code that produces it, and keep each page consistent with itself.
P17. A change that moves, renames, or removes a page adds an entry to
redirectsindocs/source/conf.py, so that existing links keep working.P18. Text, diagrams, and images taken from another project or website say where they came from, and their license is compatible with the Apache License. See the ASF’s third-party licensing policy.
Generated Content#
These parts of the docs are written by tools. Edit the source they come from instead. The rest of
each file, including the prose around the markers and the index.md pages, is written by hand.
Config tables, the text between
<!--BEGIN:CONFIG_TABLE[...]-->and<!--END:CONFIG_TABLE-->.GenerateDocsbuilds them from the config entries inCometConf.scalaand from the contrib config classes registered withCometConfigProvider. Theenable_exprandenable_agg_exprtables come from the expression serde maps inQueryPlanSerdeinstead.Expression compatibility sections, the text between
<!--BEGIN:EXPR_COMPAT[...]-->and<!--END:EXPR_COMPAT-->incompatibility/expressions/_category_template/.GenerateDocsbuilds them from the expression serdes.Cast tables, the text between the
CAST_*_TABLEmarkers.GenerateDocsbuilds them fromCometCast.supportedTypes,CometCast.isSupported, and Spark’sCast.canCast.Per-version expression pages built from
_category_template/. Onmain, the docs build creates them in a temporary tree. Release branches commit a generated copy, made withdev/generate-release-docs.sh.The Implementation column in
expressions.md, built byGenerateDocsfrom the expression serdes inQueryPlanSerde.changelog/, written by the release process.
The expression audits, data source audits, and benchmark results under contributor-guide/ are
records of past work, not pages to restyle.
Changing This Guide#
Propose changes in a pull request. Add new rules with new IDs, and do not renumber or reuse an ID, even when a rule is removed.