Skip to content

Commit 6a19e5c

Browse files
cadonnaclaude
andauthored
vector: add optional buffer consumer to ingest from buffer queue (opendata-oss#435)
Adds a `buffer` cargo feature that wires an `opendata-buffer` Consumer into the vector server. When configured, the server runs a background task that polls the queue, decodes each entry as a protobuf `WriteRequest` (with a 2-byte version/encoding prefix), and writes through `VectorDb`. The consumer starts before the HTTP listener and is shut down before the server exits. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 5cd5224 commit 6a19e5c

8 files changed

Lines changed: 684 additions & 5 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vector/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ required-features = ["http-server"]
2323
default = []
2424
http-server = ["dep:axum", "dep:tower", "dep:clap", "dep:prost", "dep:serde_json", "dep:serde_with"]
2525
embedded-reader = ["dep:reqwest", "dep:serde_json"]
26+
buffer = ["http-server", "dep:buffer"]
2627
bench-internals = []
2728
avx512 = []
2829

@@ -60,6 +61,9 @@ serde_with = { workspace = true, optional = true }
6061
# Embedded reader dependencies (optional, enabled by embedded-reader feature)
6162
reqwest = { workspace = true, optional = true, features = ["json"] }
6263

64+
# Buffer consumer dependencies (optional, enabled by buffer feature)
65+
buffer = { workspace = true, optional = true }
66+
6367
[[bin]]
6468
name = "gen_sift100k_groundtruth"
6569
path = "src/bin/gen_sift100k_groundtruth.rs"

vector/src/bin/server.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,23 @@ async fn main() {
5959
let vector_config = load_vector_config(&config);
6060
tracing::info!("Opening vector database with config: {:?}", vector_config);
6161
let metadata_fields = vector_config.metadata_fields.clone();
62+
#[cfg(feature = "buffer")]
63+
let buffer_consumer_config = vector_config.buffer_consumer.clone();
6264

63-
let db = VectorDb::open(vector_config)
64-
.await
65-
.expect("Failed to open vector database");
65+
let db = Arc::new(
66+
VectorDb::open(vector_config)
67+
.await
68+
.expect("Failed to open vector database"),
69+
);
70+
71+
let server = VectorServer::new(db, server_config, metadata_fields);
72+
73+
#[cfg(feature = "buffer")]
74+
let server = match buffer_consumer_config {
75+
Some(buffer_config) => server.with_buffer_consumer(buffer_config),
76+
None => server,
77+
};
6678

67-
let server = VectorServer::new(Arc::new(db), server_config, metadata_fields);
6879
server.run().await;
6980
}
7081
Command::Reader { config } => {

vector/src/model.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
use std::collections::HashMap;
88
use std::time::Duration;
99

10+
#[cfg(feature = "buffer")]
11+
use common::ObjectStoreConfig;
1012
use common::StorageConfig;
1113
use serde::{Deserialize, Serialize};
1214

@@ -245,6 +247,12 @@ pub struct Config {
245247
/// attribute names or type mismatches will fail. If empty, any attribute
246248
/// names are accepted with types inferred from the first write.
247249
pub metadata_fields: Vec<MetadataFieldSpec>,
250+
251+
/// Buffer consumer configuration. When `Some`, the server starts a
252+
/// background task that ingests vectors from an `opendata-buffer` queue.
253+
#[cfg(feature = "buffer")]
254+
#[serde(default)]
255+
pub buffer_consumer: Option<BufferConsumerConfig>,
248256
}
249257

250258
impl Default for Config {
@@ -263,10 +271,68 @@ impl Default for Config {
263271
chunk_target: 4096,
264272
query_pruning_factor: None,
265273
metadata_fields: Vec::new(),
274+
#[cfg(feature = "buffer")]
275+
buffer_consumer: None,
266276
}
267277
}
268278
}
269279

280+
/// Configuration for the embedded buffer consumer task.
281+
///
282+
/// Mirrors [`buffer::ConsumerConfig`], plus a poll interval used by the
283+
/// vector consumer when the queue is empty.
284+
#[cfg(feature = "buffer")]
285+
#[derive(Debug, Clone, Serialize, Deserialize)]
286+
pub struct BufferConsumerConfig {
287+
/// Object store where the buffer queue lives. Must match the producer.
288+
pub object_store: ObjectStoreConfig,
289+
290+
/// Path to the queue manifest in object storage. Must match the producer.
291+
#[serde(default = "default_buffer_manifest_path")]
292+
pub manifest_path: String,
293+
294+
/// Path prefix for data batch objects. Must match the producer.
295+
#[serde(default = "default_buffer_data_path_prefix")]
296+
pub data_path_prefix: String,
297+
298+
/// Poll interval when the queue is empty.
299+
#[serde(default = "default_buffer_poll_interval", with = "duration_secs")]
300+
pub poll_interval: Duration,
301+
302+
/// How often the garbage collector runs.
303+
#[serde(default = "default_buffer_gc_interval", with = "duration_secs")]
304+
pub gc_interval: Duration,
305+
306+
/// Minimum age before an unreferenced batch file is deleted.
307+
#[serde(default = "default_buffer_gc_grace_period", with = "duration_secs")]
308+
pub gc_grace_period: Duration,
309+
}
310+
311+
#[cfg(feature = "buffer")]
312+
fn default_buffer_manifest_path() -> String {
313+
"ingest/manifest".to_string()
314+
}
315+
316+
#[cfg(feature = "buffer")]
317+
fn default_buffer_data_path_prefix() -> String {
318+
"ingest".to_string()
319+
}
320+
321+
#[cfg(feature = "buffer")]
322+
fn default_buffer_poll_interval() -> Duration {
323+
Duration::from_millis(100)
324+
}
325+
326+
#[cfg(feature = "buffer")]
327+
fn default_buffer_gc_interval() -> Duration {
328+
Duration::from_secs(300)
329+
}
330+
331+
#[cfg(feature = "buffer")]
332+
fn default_buffer_gc_grace_period() -> Duration {
333+
Duration::from_secs(600)
334+
}
335+
270336
/// Options for search operations.
271337
#[derive(Debug, Clone, Default)]
272338
pub struct SearchOptions {

0 commit comments

Comments
 (0)