pub struct BulkInserter<T> /* private fields */ Shard-aware high-performance bulk inserter.
Routes records to the correct worker based on shard key computation, enabling true multi-head ingest. Uses serde_avro_fast for fast Avro encoding and rayon for parallel processing.
It also provides:
- Support for insert options (update_on_existing_pk, etc.)
- Permission checking before insertion
- Table replication detection (replicated tables use head node only)
- Multi-head ingest configuration
- Retry logic with configurable max retries
HA Failover Safety
This implementation uses ArcSwap for lock-free atomic updates of routing state during HA failover. This ensures:
- No race conditions when routing table changes during insert operations
- No out-of-bounds indexing from stale routing data
- Records are always routed using consistent, up-to-date routing information
Implementations
impl<T: Serialize + KineticaRecord + ShardKeyExtractor + Send + Sync + ‘static + Default> BulkInserter<T>
Creates a new shard-aware BulkInserter instance with default options.
This is a convenience method that calls with_options with None for options. For more control over insert behavior, use with_options directly.
kinetica- Shared Kinetica clienttable_name- Target table for inserts (including schema, e.g., “my_schema.my_table”)batch_size- Records per batch (recommended: 50000)num_workers- Number of flush workers, i.e. of batches sent concurrently; used exactly as given.0sizes the pool at one worker per available CPU.
pub async fn with_options( kinetica: Arc<Kinetica>, table_name: String, batch_size: usize, num_workers: usize, options: Option<BulkInserterOptions>, ) -> Result<Arc<Self>, KineticaError>
Creates a new shard-aware BulkInserter instance with the specified options.
This constructor:
- Validates user permissions for the table
- Validates batch size
- Checks if the table is replicated
- Configures multi-head ingest (if available and table is not replicated)
- Sets up worker queues and routing
kinetica- Shared Kinetica clienttable_name- Target table for inserts (including schema, e.g., “my_schema.my_table”)batch_size- Records per batch (recommended: 50000); must be > 0num_workers- Number of flush workers, i.e. of batches sent concurrently; used exactly as given.0sizes the pool at one worker per available CPU.options- Optional BulkInserterOptions for configuring insert behavior
Returns an error if:
- User doesn’t have required permissions (insert, and update if using upsert)
- Table doesn’t exist
- Batch size is less than 1
- Unable to configure multi-head ingest
impl<T: Serialize + KineticaRecord + ShardKeyExtractor + Send + Sync + ‘static> BulkInserter<T>
pub async fn with_schema( kinetica: Arc<Kinetica>, table_name: String, batch_size: usize, num_workers: usize, options: Option<BulkInserterOptions>, schema_str: &str, ) -> Result<Arc<Self>, KineticaError>
Creates a new shard-aware BulkInserter instance with an explicit schema.
This constructor is useful for types that cannot implement Default, such as GenericRecord which requires a Type reference at construction time.
kinetica- Shared Kinetica clienttable_name- Target table for inserts (including schema, e.g., “my_schema.my_table”)batch_size- Records per batch (recommended: 50000); must be > 0num_workers- Number of flush workers, i.e. of batches sent concurrently; used exactly as given.0sizes the pool at one worker per available CPU.options- Optional BulkInserterOptions for configuring insert behaviorschema_str- The Avro schema JSON string for the record type
Each record is one JSON object, as text. Records are sent as JSON arrays to the ranks’ /insert/records/json, each to the rank of a random shard: the client does not read their keys. A batch the server fails is reported as any other, through drain_errors, and one that meets a connection failure is retried and failed over like any other.
Creates a JSON inserter with the default JsonOptions and options.
num_workers is as for BulkInserter::with_schema.
pub async fn with_json_options( kinetica: Arc<Kinetica>, table_name: String, batch_size: usize, num_workers: usize, options: Option<BulkInserterOptions>, json_options: JsonOptions, ) -> Result<Arc<Self>, KineticaError>
Creates a JSON inserter. With JsonOptions::is_validate_json, each record inserted must be a JSON object.
impl<T: Serialize + ShardKeyExtractor + Send + Sync + ‘static> BulkInserter<T>
Inserts a single record with shard-aware routing.
The record’s shard key is computed using ShardKeyExtractor::shard_key_values(), and the record is routed to the worker that owns the corresponding shard.
Returns an error if the record cannot be routed — see Self::route_to_worker. The record is not queued in that case, and comes back in the QueueError.
Also returns an error after close when this record fills a batch: the batch cannot be sent, and its records come back.
This method uses ArcSwap::load() to get the current routing state, which provides:
- Lock-free atomic read of the routing configuration
- Consistent routing even during HA failover (no torn reads)
- No race conditions with concurrent routing updates
A JSON inserter whose JsonOptions ask for it first checks that the record is a JSON object.
pub async fn insert_with_backpressure( &self, record: Arc<T>, ) -> Result<(), KineticaError>
Inserts a single record with backpressure control.
This async method will wait if the maximum in-flight batches limit is reached, providing memory-bounded operation for high-throughput scenarios.
This method uses ArcSwap::load() to get the current routing state atomically.
pub fn insert_batch(&self, records: Vec<T>) -> Result<(), QueueError<T>>
Inserts a batch of records with shard-aware routing.
This method is more efficient than calling insert() in a loop because:
- Worker indices are computed in parallel using rayon
- Records are grouped by destination before any lock is taken
- Each destination’s queue is locked once per call, not once per record
The records go into the calling thread’s stripe of each queue, as insert puts them, and a batch is sent each time that stripe reaches the batch size. No batch is larger than the batch size.
records- A vector of records to insert. Each record will be wrapped in Arc internally.
The records before the first that cannot be routed are queued, and that record and every one after it come back in the QueueError with its error. A list whose JSON does not validate is not queued at all, and comes back whole.
pub fn insert_batch_arc( &self, records: Vec<Arc<T>>, ) -> Result<(), QueueError<T>>
Inserts a batch of records that are already wrapped in Arc.
This is useful when records are shared or when the caller already has Arc-wrapped records.
records- A vector of Arc-wrapped records to insert.
pub fn count_inserted(&self) -> i64
Returns the total number of records successfully inserted.
pub fn count_updated(&self) -> i64
Returns the total number of records successfully updated (for upserts).
pub fn table_name(&self) -> &str
Returns the table name.
pub fn batch_size(&self) -> usize
Returns the batch size.
pub fn is_table_replicated(&self) -> bool
Returns whether the target table is replicated.
pub fn is_multi_head_enabled(&self) -> bool
Returns whether multi-head ingest is enabled.
pub fn is_using_head_node(&self) -> bool
Returns whether the head node is being used (replicated table or multi-head disabled).
pub fn max_retries(&self) -> usize
Returns the current maximum retries for failed insertions.
pub fn set_max_retries(&self, max_retries: usize)
Sets the maximum retries for failed insertions.
Returns a reference to the insert options.
pub fn json_options(&self) -> Option<JsonOptions>
The JSON options of a JSON inserter, or those set since.
pub fn set_json_options(&self, json_options: JsonOptions)
Replaces the JSON options, which a JSON inserter reads for each record it is given. They do not make an inserter of Avro records a JSON one.
pub fn flush_options(&self) -> FlushOptions
Returns the flush options.
pub fn timed_flush_options(&self) -> FlushOptions
The timed flush options.
pub fn set_timed_flush_options( self: &Arc<Self>, flush_options: Option<FlushOptions>, )
Replaces the timed flush options, None restoring the defaults (no timed flush), and restarts the timed flush with them. A scheduled timed flush is stopped first, and a new one starts if the new interval is positive, its first run one interval from now.
pub fn is_return_individual_errors(&self) -> bool
Returns whether return_individual_errors mode is active.
pub fn buffered_record_count(&self) -> usize
Returns the number of records currently buffered in worker queues.
These are records that have been queued via insert() but not yet sent to the flush workers. Call flush() to send these records.
pub fn in_flight_record_estimate(&self) -> i64
Returns the estimated number of records in-flight (sent but not yet confirmed).
This is an estimate based on pending_batches * batch_size. The actual number may be slightly different due to partial batches.
pub fn drain_errors(&self) -> Vec<InsertError<T>>
Drains and returns all queued errors. This clears the error queue, so subsequent calls return new errors only.
pub fn error_count(&self) -> usize
Returns the number of queued errors without draining.
pub fn failed_record_count(&self) -> usize
Returns the total number of records that failed to insert. This sums the record_count from all errors in the queue.
pub fn drain_warnings(&self) -> Vec<String>
Drains and returns all queued warnings.
pub fn warning_count(&self) -> usize
Returns the number of queued warnings without draining.
pub fn is_timed_flush_running(&self) -> bool
Returns whether timed flush is currently running.
pub fn stop_timed_flush(&self)
Stops the timed flush task.
pub fn max_in_flight_batches(&self) -> usize
Returns the maximum number of in-flight batches configured.
pub fn available_in_flight_permits(&self) -> usize
Returns the number of available in-flight permits.
pub fn backpressure_metrics(&self) -> BackpressureMetrics
Returns backpressure metrics for monitoring.
This provides visibility into the backpressure state of the inserter, useful for monitoring and tuning max_in_flight_batches.
Flushes all buffered records to the server and waits until they have been acknowledged.
When this returns Ok(()), every record queued before the call has either been inserted or recorded in the error queue — so a subsequent read is guaranteed to see them.
Check error_count or drain_errors afterwards to see whether any records failed; a returned Ok means “all batches finished”, not “all batches succeeded”.
Use flush_no_wait if you deliberately want to hand the batches off without waiting.
pub async fn flush_no_wait(&self) -> Result<(), KineticaError>
Hands all buffered records to the flush workers without waiting for them to complete.
Prefer flush — after this returns, the records have not necessarily reached the server, so reading the table back may not see them. This exists for background flushing (the timed-flush task uses it) where blocking would stall the caller.
pub fn pending_batches(&self) -> i64
Returns the number of pending batches (sent but not yet completed).
Closes the BulkInserter, flushing all records and waiting for completion.
This method:
- Flushes all buffered records to the flush workers
- Waits for all pending batches to complete
- Stops the flush workers and the timed flush
The inserter cannot send anything afterwards: an insert still buffers, but one that fills a batch, or a flush() with records buffered, fails.
IMPORTANT: This method will wait indefinitely until all pending batches complete. It only returns when:
- All records have been successfully inserted, OR
- All records have either succeeded or failed (with errors queued)
Failed records are tracked in the error queue and can be retrieved via drain_errors(). The method guarantees that inserted + updated + failed = queued.
Returns Ok(()) when all batches have completed (success or failure). Check error_count() or drain_errors() to see if any records failed.
pub async fn close_with_timeout( &self, timeout: Duration, ) -> Result<(), KineticaError>
Closes the BulkInserter with an optional timeout.
If timeout is reached, returns an error but records may still be in-flight. Prefer using close() without timeout to ensure all records are flushed.
timeout- Maximum time to wait for pending batches to complete
Returns Ok(()) if all batches completed, or Err if timeout was reached.
pub fn shard_column_count(&self) -> usize
Returns the number of shard key columns used for routing.
pub fn shard_column_names(&self) -> Vec<String>
Returns the names of shard key columns.
pub fn routing_table_size(&self) -> usize
Returns the routing table size (number of shards).
pub fn worker_count(&self) -> usize
Returns the number of worker queues.
pub fn num_cluster_switches(&self) -> usize
Returns the number of times the connection has switched clusters due to failover.
pub fn ha_ring_size(&self) -> usize
Returns the current HA ring size (number of clusters).
pub fn current_head_node_url(&self) -> Option<Url>
Returns the current head node URL.
Trait Implementations
impl<T> Drop for BulkInserter<T>
Dropping an inserter stops its background tasks (they hold only Weak references, and the shutdown sender goes with it), so nothing leaks. It cannot flush, since that is asynchronous: records still buffered, or queued but not yet sent, are lost. Say so, rather than lose them silently.