Search before asking
Motivation
Native query engines need a Paimon writer to run within the query's resource limits. Today the Rust writer and C FFI do not provide a way to connect the write path to an engine memory tracker, spill memory-heavy work to disk, stop promptly when a query is cancelled, or report writer resource metrics.
The existing write_buffer_size option is a flush threshold, not a total writer memory limit. Writer memory also includes:
- Arrow
RecordBatch values retained by primary-key writers;
- active partition and bucket writers;
- file encoder and row-group buffers;
- sorting, deduplication, merging, serialization, and index-generation state;
- file writers being closed by background tasks.
Changing write_buffer_size cannot account for all of this memory or reserve bytes from the embedding query engine before an allocation. An engine such as Apache Doris therefore cannot reliably enforce its query memory limit around a Paimon Rust write.
The C FFI also has no cancellation handle or writer metrics API. A cancelled query may continue CPU work or I/O until the current operation finishes, and the engine cannot report the memory retained or data spilled by the writer.
Solution
This requires changes in both the Rust writer core and the C FFI. FFI callbacks alone are insufficient because the allocations and long-running work occur inside the Rust writer.
Rust writer core
Add an optional resource context shared by all writers in one logical write. Existing callers should retain the current behavior when no context is configured.
The context should support:
- reserving memory before retaining buffers or starting memory-heavy work;
- automatically releasing reservations when the memory is freed;
- returning a distinct resource-exhausted error when memory cannot be reserved;
- checking a cancellation signal;
- configuring a spill directory and spill budget;
- collecting writer metrics.
Memory accounting should initially cover retained Arrow batches, active file/row-group buffers, primary-key sort and merge state, index state, and background file-close tasks. Reservations should use an ownership or guard model so error and cancellation paths release them reliably.
When memory cannot be reserved, the writer should flush safe state or spill intermediate data. Primary-key sorting, deduplication, and merging need a bounded-memory spill path rather than only a lower file flush threshold. If an operation cannot spill safely, it should return a resource-exhausted error.
Spill files should be scoped to one writer or query and removed after success, error, abort, or cancellation. The spilled path must produce the same logical result as the in-memory path.
Long-running operations should check cancellation at useful boundaries, including batch ingestion, partition/bucket dispatch, sort and merge loops, file encoding, background close waits, and prepare_commit. Cancellation should return a distinct error and clean up reservations, tasks, open writers, and spill files without publishing a commit.
Expose a metrics snapshot containing at least:
- current and peak accounted memory;
- input rows and bytes;
- output files and bytes;
- spill operations, files, and bytes;
- flush count and time;
- sort/merge and
prepare_commit time;
- cancellation and resource-exhausted counts.
The API should document which values are exact and which are estimates.
C FFI
Expose the resource context through a versioned C structure or an opaque thread-safe handle. An embedding engine needs to:
- provide memory reserve/release callbacks or a fixed memory budget;
- provide spill configuration;
- cancel a writer while another FFI call may be running;
- retrieve a metrics snapshot;
- distinguish cancellation and resource exhaustion from other errors.
A possible callback shape is:
typedef struct paimon_write_resource_callbacks_v1 {
void *context;
bool (*try_reserve)(void *context, uint64_t bytes);
void (*release)(void *context, uint64_t bytes);
bool (*is_cancelled)(void *context);
} paimon_write_resource_callbacks_v1;
The final design must define callback lifetime, thread-safety, ownership, and whether callbacks can be invoked from Rust runtime threads. A separate resource handle may be preferable because cancellation must work while a write call is blocked in the FFI.
Suggested implementation phases
- Add the resource context, memory reservations, cancellation, and metrics to the Rust core.
- Add the C FFI resource handle and error/metrics APIs.
- Add bounded-memory primary-key sort/merge with spill.
- Extend accounting and spill coverage to the remaining writer and index paths.
The implementation should demonstrate that:
- a configured writer stays within its accounted budget or returns a resource-exhausted error;
- a low-memory primary-key write spills and produces the same logical result as the in-memory path;
- cancellation stops a long-running write and removes reservations, tasks, and spill files;
- an embedding engine can attach its query memory tracker and read writer metrics.
Anything else?
The closest existing issues found during the search are:
These issues concern scan, SQL runtime, or metadata paths rather than table writers.
Open design questions:
- Should the core abstraction expose reserve/release callbacks, a Rust memory-pool trait, or both?
- Which writer allocations can be accounted exactly in the first phase?
- Should spill be limited to primary-key sort/merge initially?
- What cancellation latency should the API guarantee?
Willingness to contribute
Search before asking
Motivation
Native query engines need a Paimon writer to run within the query's resource limits. Today the Rust writer and C FFI do not provide a way to connect the write path to an engine memory tracker, spill memory-heavy work to disk, stop promptly when a query is cancelled, or report writer resource metrics.
The existing
write_buffer_sizeoption is a flush threshold, not a total writer memory limit. Writer memory also includes:RecordBatchvalues retained by primary-key writers;Changing
write_buffer_sizecannot account for all of this memory or reserve bytes from the embedding query engine before an allocation. An engine such as Apache Doris therefore cannot reliably enforce its query memory limit around a Paimon Rust write.The C FFI also has no cancellation handle or writer metrics API. A cancelled query may continue CPU work or I/O until the current operation finishes, and the engine cannot report the memory retained or data spilled by the writer.
Solution
This requires changes in both the Rust writer core and the C FFI. FFI callbacks alone are insufficient because the allocations and long-running work occur inside the Rust writer.
Rust writer core
Add an optional resource context shared by all writers in one logical write. Existing callers should retain the current behavior when no context is configured.
The context should support:
Memory accounting should initially cover retained Arrow batches, active file/row-group buffers, primary-key sort and merge state, index state, and background file-close tasks. Reservations should use an ownership or guard model so error and cancellation paths release them reliably.
When memory cannot be reserved, the writer should flush safe state or spill intermediate data. Primary-key sorting, deduplication, and merging need a bounded-memory spill path rather than only a lower file flush threshold. If an operation cannot spill safely, it should return a resource-exhausted error.
Spill files should be scoped to one writer or query and removed after success, error, abort, or cancellation. The spilled path must produce the same logical result as the in-memory path.
Long-running operations should check cancellation at useful boundaries, including batch ingestion, partition/bucket dispatch, sort and merge loops, file encoding, background close waits, and
prepare_commit. Cancellation should return a distinct error and clean up reservations, tasks, open writers, and spill files without publishing a commit.Expose a metrics snapshot containing at least:
prepare_committime;The API should document which values are exact and which are estimates.
C FFI
Expose the resource context through a versioned C structure or an opaque thread-safe handle. An embedding engine needs to:
A possible callback shape is:
The final design must define callback lifetime, thread-safety, ownership, and whether callbacks can be invoked from Rust runtime threads. A separate resource handle may be preferable because cancellation must work while a write call is blocked in the FFI.
Suggested implementation phases
The implementation should demonstrate that:
Anything else?
The closest existing issues found during the search are:
These issues concern scan, SQL runtime, or metadata paths rather than table writers.
Open design questions:
Willingness to contribute