11# vortex-java
22
3- > ** Experimental ** — not production-ready. APIs will change without notice.
3+ > ** Alpha ** — not production-ready. APIs will change without notice.
44
55Pure-Java reader/writer for the [ Vortex] ( https://github.com/spiraldb/vortex ) columnar file format.
66
7- ## Performance
8-
9- ` RustVsJavaReadBenchmark ` — 10M OHLC rows, JMH throughput (higher = better), Apple M5 / 32 GB, Java 25:
10-
11- | Column | Encoding | vortex-jni | vortex-java | ratio |
12- | ------------------| ------------------| -------------| --------------| ---------------|
13- | ` volume ` (I64) | primitive | 51.9 ops/s | ~ 115 ops/s | ** Java 2.2×** |
14- | ` close ` (F64) | ALP (multi-walk) | re-measure | 62 ops/s | re-measure |
15- | ` symbol ` (UTF-8) | dict + FSST | re-measure | 7.6 ops/s | re-measure |
16-
17- The ` close ` and ` symbol ` numbers regressed from earlier snapshots (118 and 27.8 respectively)
18- because commit ` 347bc34 ` made the bench data realistic: 30 Nasdaq tickers with independent per-ticker
19- price walks instead of a single global walker and a constant symbol. Rust's writer responds by
20- picking heavier encodings — ` vortex.dict ` over ` vortex.fsst ` for ` symbol ` , and an ALP sub-encoding
21- that no longer fits a single tight reference for ` close ` . Decode work goes up on both sides;
22- JNI numbers above are stale and need re-measurement on the new workload.
23-
24- Reproduce: ` ./benchmark.sh RustVsJavaReadBenchmark ` . Hardware / JDK build / commit SHA used to produce
25- this snapshot should be captured alongside any update (see TODO #10c).
26-
27- ` RustWritesJavaReadsBigFileBenchmark ` — 3 GB file, 4 × I64 columns of random data (defeats
28- bit-packing so segments stay large), projection on ` c0 ` , JMH throughput:
29-
30- | Reader | Throughput | ms/op | Decode rate |
31- | -------------| ----------------| -------| -------------|
32- | vortex-jni | 5.9 ops/s | ~ 170 | ~ 4.7 GB/s |
33- | vortex-java | ** 19.7 ops/s** | ~ 51 | ~ 15.7 GB/s — ** Java 3.4×** |
34-
35- Memory-bandwidth bound on Apple M5 / 32 GB. Reproduce: ` ./benchmark.sh RustWritesJavaReadsBigFileBenchmark `
36- (adds a ~ 30 s fixture build for the JNI write). Pass ` -Dvortex.bench.bigfile=<path> ` to reuse an existing
37- fixture between runs.
38-
39- All columns decoded faster in pure Java than via JNI + Apache Arrow. Key optimisations:
40- ` static final ` ValueLayout constants (JIT constant-folding), aligned arena allocation
41- (` allocate(n, alignment) ` ), ` getAtIndex() ` /` setAtIndex() ` (clearer stride for the
42- auto-vectoriser), and O(1) bytes allocation for constant strings (alternating offsets
43- into a single shared byte buffer).
7+ - ✅ Pure-Java reader for primitive, sequence, ALP, dict, FSST
8+ - ✅ Local (mmap) or Remote (HTTPS, single read of last 65K)
9+ - 🚧 Writer (in progress)
10+ - 🚧 Benchmark against Rust+JNI (in progress)
11+ - 🚧 Full encoding coverage (in progress)
12+ - 🚧 Vectorized decode paths (Panama Vector API)
13+ - ❌ No Iceberg/Spark/Flink integration yet
4414
4515## Motivation
4616
@@ -56,6 +26,13 @@ reads, making it easier to:
5626- build and test on any platform with a standard JDK
5727- debug and profile with standard JVM tooling
5828
29+ ## Who is this for?
30+
31+ - JVM analytics engines
32+ - JVM-based OLAP systems
33+ - Anyone who wants mmap‑backed, zero‑copy columnar reads without first decompressing
34+ the whole file (or row chunk)
35+
5936### Why fewer layers = faster
6037
6138```
@@ -66,10 +43,10 @@ reads, making it easier to:
6643 │ (BigIntVector.get(i)) │ │ (buffer.getAtIndex) │
6744 └────────────┬─────────────┘ └──────────┬───────────┘
6845 │ Arrow Java API │ FFM API
69- ┌────────────▼─────────────┐ │ (MemorySegment,
70- │ Apache Arrow (Java) │ │ zero-copy slice)
71- │ VectorSchemaRoot, │ │
72- │ BigIntVector, … │ │
46+ ┌────────────▼─────────────┐ │ (MemorySegment,
47+ │ Apache Arrow (Java) │ │ zero-copy slice)
48+ │ VectorSchemaRoot, │ │
49+ │ BigIntVector, … │ │
7350 └────────────┬─────────────┘ ┌──────────▼───────────┐
7451 │ Arrow C Data Interface │ OS mmap region │
7552 │ (ArrowArray/ArrowSchema) │ (file on disk) │
@@ -101,28 +78,21 @@ file occupies. Decoding reads bytes directly from that view with no copies, no i
10178Arrow format, and no boundary crossings. The JIT sees the full decode path as ordinary Java
10279bytecode.
10380
104- ## Why Vortex instead of Parquet
81+ ## Design principles
10582
106- | | Parquet | Vortex |
107- | ------------------------| ----------------------------------------| ------------------------------------|
108- | Max column-chunk size | 2 GB (` int32 ` page offsets) | 4 GB per segment (` uint32 ` length) |
109- | Max file offset | 2 GB (` int32 ` on some implementations) | 16 EB (` uint64 ` in footer) |
110- | Encoding extensibility | Fixed codec set | Plugin registry, any encoding |
111-
112- Files larger than 2 GB are a practical problem with Parquet: the ` int32 ` data-page size field in the page header caps
113- individual column chunks at 2 GB.
114- Vortex uses ` uint64 ` offsets throughout the footer, and this library maps files with ` MemorySegment ` (Java FFM API),
115- which has no per-mapping size limit — unlike the legacy ` FileChannel.map() ` API that caps each mapping at 2 GB.
83+ - Zero-copy as much as possible
84+ - No JNI
85+ - No Unsafe -- don't remove safety to gain few %
86+ - Align with vortex-rust and Vortex-go semantics
87+ - Make the JIT happy (constant layouts, predictable strides, no virtual dispatch in hot loops)
88+ - Prepare for the Vector API / Valhalla
11689
11790## Prior art and inspiration
11891
11992| Project | Language | Notes |
12093| -------------------------------------------------------------| ----------| ---------------------------------------------------------------------|
12194| [ spiraldb/vortex] ( https://github.com/spiraldb/vortex ) | Rust | Reference implementation + JNI bindings |
12295| [ spiraldb/vortex-go] ( https://github.com/spiraldb/vortex-go ) | Go | Pure-language port; primary inspiration for this project's approach |
123-
124- ### Further reading
125-
12696- [ FFM vs Unsafe] ( https://inside.java/2025/06/12/ffm-vs-unsafe/ ) — Maurizio Cimadamore's deep-dive on why FFM (` MemorySegment ` /` Arena ` ) supersedes ` sun.misc.Unsafe ` : safety, performance, and the JVM's path forward
12797
12898## Serialization formats
0 commit comments