Support the Vortex file format - #112950
Conversation
Add `Vortex` input and output formats for reading and writing Vortex files (https://github.com/vortex-data/vortex), an extensible columnar file format for compressed Apache Arrow-compatible data. The implementation uses the Rust `vortex` crate through a new C FFI crate `rust/workspace/vortex` (`_ch_rust_vortex`). Data crosses the FFI boundary through the Arrow C Data Interface and is converted with the same `ArrowColumnToCHColumn`/`CHColumnToArrowColumn` code as the `Arrow` format. IO is delegated back to ClickHouse through callbacks, so reads go through ClickHouse's own buffers (with range reads for seekable inputs and whole-file buffering otherwise), and writes stream into the output buffer. All work is driven by a single-threaded runtime on the calling thread: the library spawns no threads. The reader supports projection pushdown (only the requested columns are read), count-only queries from file metadata, and schema inference. The writer uses the library's default adaptive compression (BtrBlocks-style cascading encodings plus zstd). Closes #87327 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Preview deployment for your docs. Learn more about Mintlify Previews.
|
|
Workflow [PR], commit [e5634f1] Summary: ✅
AI ReviewSummaryThis PR adds Findings
Final Verdict
LLVM Coverage Report
Changed lines: Changed C/C++ lines covered: 296/353 (83.85%) · Uncovered code |
# Conflicts: # src/Formats/registerFormats.cpp
Address the AI review of #112950 and the `arm_tidy` build failure. - `VortexBlockInputFormat::prepareReader` pruned the scanned columns to the exact top-level names of the requested header. A header column addressing a subcolumn, such as `t.a`, dropped the parent field `t` from the scan, and `ArrowColumnToCHColumn` then silently filled the column with default values. Keep `Nested::extractTableName` of every header column, the same way the `ArrowIPC` reader does. Covered by a new case in `04669_vortex_format`. - `VortexBlockOutputFormat` did not override `resetFormatterImpl`, so a formatter reused by `MessageQueueSink` kept the finished Rust writer and the next message failed with `writer is already finished`. Free the writer and the conversion state on reset, like `Arrow`, `ORC`, and `Parquet` do. - Value-initialize the `ArrowArray` and `ArrowSchema` C structures, fixing the `cppcoreguidelines-pro-type-member-init` errors in `Build (arm_tidy)`: https://s3.amazonaws.com/clickhouse-test-reports/json.html?PR=112950&sha=d4debff97a3088dde960cc32e1d0ce8af1c997b6&name_0=PR&name_1=Build%20%28arm_tidy%29
The MSan build failed to link unit_tests_dbms with relocation R_X86_64_PC32 out of range errors: the MSan-instrumented Vortex Rust library (with origin tracking) adds so much code that the binary exceeds the 2 GiB range of the small code model. Disable Vortex under MSan, following the precedent of wasmtime and delta-kernel-rs. Consequently, exclude Vortex from the format enumeration in 02187_async_inserts_all_formats (its availability now varies by build, so it cannot appear in the static reference; this also fixes the reference mismatch this PR introduced there), and tag 04669_vortex_format with no-msan. CI: https://s3.amazonaws.com/clickhouse-test-reports/json.html?PR=112950&sha=645a6771def9e396863d6401ca67230109b220dd Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Master removed the legacy Docusaurus tree (docs/en), which conflicted with the Vortex doc added there. The Vortex format page is ported to the Mintlify tree: docs/reference/formats/Vortex.mdx (body matches the embedded documentation registered via FormatFactory::setDocumentation), a row in docs/reference/formats/index.mdx, and an entry in docs/reference/navigation.json.
|
🕵 @groeneai, investigate the two
Both hangs are unrelated to this PR: the hung queries do not involve the |
|
Both hangs are the same defect, and it is not the one the
and its stack ( The So both rows are Cost vs interruptibility are two separate things, and only the first has a fix. Cost: your #113140 is the right fix for it, and the Interruptibility: #113140 does not address it. Its 87 added lines contain no cancellation check (0 occurrences of The phase counters confirm the hang is in the still-running phase rather than anything already finished: recorded phases sum to 6.6 s of 1757 s ( I am authoring the cancellation-checkpoint fix and will link the PR here when it opens. #112203 is a different mechanism (uninterruptible evaluation of one large value); this is uninterruptible plan construction, closer to #113003 and covered in general by #47272. Unrelated to this PR, confirmed rather than assumed. Your MSan argument holds: |
The `Vortex` output format inherited the generic `CHColumnToArrowColumn` mapping that serializes `DateTime` as plain Arrow `UINT32`, so the temporal type was silently lost on round-trip: `DESC file(...)` of a written file inferred `UInt32`. Add an `output_datetime_as_timestamp` setting to `CHColumnToArrowColumn` (following the existing `output_date_as_uint16` pattern) that exports `DateTime` as Arrow `TIMESTAMP` with second precision and the column's time zone, and enable it for `Vortex`. Such files are read back as `DateTime64(0)`. `Arrow` and `Parquet` keep the old behavior. Update the format documentation accordingly and add a test with schema-inferred readback of `DateTime`, including `Nullable` and `Array` nesting. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Fixing PR for the I had a fix for this on a local branch, but @alexey-milovidov opened #113415 with the identical |
| arrow_settings.output_fixed_string_as_fixed_byte_array = false; | ||
| /// Write `DateTime` as `vortex.timestamp` with second precision instead of the generic `U32`, | ||
| /// so the temporal type is preserved on round-trip (it is read back as `DateTime64(0)`). | ||
| arrow_settings.output_datetime_as_timestamp = true; |
There was a problem hiding this comment.
Vortex still inherits the generic CHColumnToArrowColumn mapping for IPv4, which emits plain Arrow UINT32. On the read side ArrowColumnToCHColumn only reconstructs IPv4 when it gets an explicit type hint (or the ORC-specific INT32 carrier), so schema-inferred Vortex readback still turns SELECT toIPv4(...) FORMAT Vortex into UInt32 on DESC file(...) / plain SELECT * FROM file(...).
This needs a Vortex-specific logical mapping for IPv4 (or an explicit rejection + documented limitation) plus a small stateless inferred-schema test.
There was a problem hiding this comment.
🕵 Vortex has no logical type for IP addresses, so a lossless Vortex-specific mapping is not possible — IPv4 goes through the generic Arrow UINT32 carrier, exactly like in Parquet and Arrow (where schema inference also reads it back as UInt32). Rejecting the write would make Vortex stricter than the sibling formats for no benefit, so I took the documented-limitation route: the format documentation now states that IPv4 is written as U32, that inference reads it back as UInt32, and shows the explicit-schema way to get IPv4 back (SELECT * FROM file('data.vortex', Vortex, 'ip IPv4')). Added the stateless test 04812_vortex_ipv4.sh covering inference (UInt32/Nullable(UInt32)), the plain and explicit-schema round trips (values verified), and the documented IPv6 write rejection.
…ex` format Vortex has no type for IP addresses, so `IPv4` columns are written through the generic Arrow `UINT32` carrier (the same as in `Parquet` and `Arrow`), and schema inference reads them back as `UInt32`. Document this explicitly, including the explicit-schema way to read the column back as `IPv4`, and add a stateless test covering inference, both round trips, and the documented `IPv6` write rejection. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
🕵 CI triage for the reds on
|
| arrow_settings.output_datetime_as_timestamp = true; | ||
|
|
||
| ch_column_to_arrow_column | ||
| = std::make_unique<CHColumnToArrowColumn>(getPort(PortKind::Main).getHeader(), "Vortex", arrow_settings); |
There was a problem hiding this comment.
Vortex now inherits the generic CHColumnToArrowColumn mapping here, but that mapper still has no TypeIndex::Nothing handling in either getArrowType or fillArrowArray. The advertised Null/Nothing support therefore does not actually work: SELECT NULL FORMAT Vortex (or any Nullable(Nothing) field) still throws UNKNOWN_TYPE, and a Null field cannot be schema-inferred on read either because VortexSchemaReader goes through arrowSchemaToCHHeader with allow_arrow_null_type = false.
Upstream vortex-arrow does support Arrow DataType::Null, so this needs either explicit Nothing/Nullable(Nothing) support on both paths or the supported-types contract/docs pared back, plus a focused stateless test for the chosen behavior.
|
The AST fuzzer STID 2270-2caa failure on I confirmed that on your exact query rather than by inference. A pre-fix binary reproduces your log line byte for byte ( Mechanism: The crash is the visible half. When no constant survives there is no error and rows are silently dropped instead: over two shards of 1000 rows,
113469 covers this carrier; I am not claiming it clears the whole message class. Agreed that 96656 and 103695 are the same family with different stack IDs. One caveat on keying: within this family a single STID pools more than one query shape, so the shape identifies a carrier more reliably than the STID does. |
Closes: #87327
Related: ClickHouse/rust_vendor#74
Changelog category (leave one):
Changelog entry (a user-readable short description of the changes that goes into CHANGELOG.md):
Added support for reading and writing the Vortex columnar file format (the
Vortexinput and output format). This closes #87327.Documentation entry for user-facing changes
The implementation uses the Rust
vortexcrate (v0.83.0) through a new C FFI craterust/workspace/vortex(_ch_rust_vortex), following the same pattern asprqlandpolyglot. Data crosses the FFI boundary through the Arrow C Data Interface and is converted with the sameArrowColumnToCHColumn/CHColumnToArrowColumncode as theArrowformat. IO is delegated back to ClickHouse through callbacks: reads go through ClickHouse's own read buffers (range reads for seekable inputs, whole-file buffering otherwise), and the produced file is streamed into the output buffer. All work is driven by a single-threaded runtime on the calling thread — the library spawns no threads, and Rust panics are caught at the FFI boundary and turned into exceptions.Features:
count()-only queries answered from file metadata without reading data.Limitations (documented in
docs/reference/formats/Vortex.mdx):Map,Int128/UInt128/Int256/UInt256,IPv6, andIntervalcolumns cannot be written (no corresponding Vortex type).StringandFixedStringare written as VortexBinary(ClickHouse strings are arbitrary bytes, while Vortex requiresUtf8to be valid UTF-8).unit_tests_dbmsoverflows the 2 GiBR_X86_64_PC32relocation range (same approach aswasmtimeanddelta-kernel-rs).Parquet(~0.5 s), while writes are noticeably slower (the adaptive compressor samples many encodings per column) — parallelism can be added later.The 127 new vendored Rust crates are added in ClickHouse/rust_vendor#74 (the
contrib/rust_vendorsubmodule is bumped to that branch).