mkv

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit 689b85e6f2981a3b642783b458e5d67d89269fa7
parent 71abb1ed7d3271e4fe1650c51455eb9ced93d204
Author: Silas Brack <silas@teton.ai>
Date:   Sat,  7 Mar 2026 17:14:54 +0100

Fixes

Diffstat:
MREADME.md | 2+-
Msrc/db.rs | 45++++++++++++++++++++++++++++++++++++++++-----
Msrc/lib.rs | 12++++++++++++
3 files changed, 53 insertions(+), 6 deletions(-)

diff --git a/README.md b/README.md @@ -27,7 +27,7 @@ curl http://localhost:3000/?prefix=path/to/ ### Operations ```bash -# Rebuild index by scanning all volumes (disaster recovery) +# Rebuild index by scanning all volumes (stop the server first) mkv -d /tmp/index.db -v http://vol1:8080,http://vol2:8080,http://vol3:8080 -r 2 rebuild # Rebalance after adding/removing volumes (preview with --dry-run) diff --git a/src/db.rs b/src/db.rs @@ -76,13 +76,45 @@ impl Db { pub async fn list_keys(&self, prefix: &str) -> Result<Vec<String>, AppError> { let conn = self.conn.clone(); - let pattern = format!("{prefix}%"); + let prefix = prefix.to_string(); tokio::task::spawn_blocking(move || { let conn = conn.lock().unwrap(); - let mut stmt = conn.prepare_cached("SELECT key FROM kv WHERE key LIKE ?1 ORDER BY key")?; - let keys = stmt - .query_map(params![pattern], |row| row.get(0))? - .collect::<Result<Vec<String>, _>>()?; + if prefix.is_empty() { + let mut stmt = conn.prepare_cached("SELECT key FROM kv ORDER BY key")?; + let keys = stmt + .query_map([], |row| row.get(0))? + .collect::<Result<Vec<String>, _>>()?; + return Ok(keys); + } + // Compute exclusive upper bound: increment last non-0xFF byte + let upper = { + let mut bytes = prefix.as_bytes().to_vec(); + let mut result = None; + while let Some(last) = bytes.pop() { + if last < 0xFF { + bytes.push(last + 1); + result = Some(String::from_utf8_lossy(&bytes).into_owned()); + break; + } + } + result + }; + let keys = match &upper { + Some(end) => { + let mut stmt = conn.prepare_cached( + "SELECT key FROM kv WHERE key >= ?1 AND key < ?2 ORDER BY key", + )?; + stmt.query_map(params![prefix, end], |row| row.get(0))? + .collect::<Result<Vec<String>, _>>()? + } + None => { + let mut stmt = conn.prepare_cached( + "SELECT key FROM kv WHERE key >= ?1 ORDER BY key", + )?; + stmt.query_map(params![prefix], |row| row.get(0))? + .collect::<Result<Vec<String>, _>>()? + } + }; Ok(keys) }) .await @@ -120,6 +152,7 @@ impl Db { let conn = self.conn.clone(); tokio::task::spawn_blocking(move || { let conn = conn.lock().unwrap(); + conn.execute_batch("BEGIN")?; let mut stmt = conn.prepare_cached( "INSERT INTO kv (key, volumes, size) VALUES (?1, ?2, ?3) ON CONFLICT(key) DO UPDATE SET volumes = ?2, size = ?3", @@ -127,6 +160,8 @@ impl Db { for (key, volumes, size) in &records { stmt.execute(params![key, encode_volumes(volumes), size])?; } + drop(stmt); + conn.execute_batch("COMMIT")?; Ok(()) }) .await diff --git a/src/lib.rs b/src/lib.rs @@ -7,6 +7,8 @@ pub mod rebuild; use std::sync::Arc; +const DEFAULT_BODY_LIMIT: usize = 256 * 1024 * 1024; // 256 MB + pub struct Args { pub db_path: String, pub volumes: Vec<String>, @@ -14,6 +16,15 @@ pub struct Args { } pub fn build_app(args: &Args) -> axum::Router { + if args.replicas > args.volumes.len() { + eprintln!( + "Error: replication factor ({}) exceeds number of volumes ({})", + args.replicas, + args.volumes.len() + ); + std::process::exit(1); + } + if let Some(parent) = std::path::Path::new(&args.db_path).parent() { std::fs::create_dir_all(parent).unwrap_or_else(|e| { eprintln!("Failed to create database directory: {e}"); @@ -37,5 +48,6 @@ pub fn build_app(args: &Args) -> axum::Router { .delete(server::delete_key) .head(server::head_key), ) + .layer(axum::extract::DefaultBodyLimit::max(DEFAULT_BODY_LIMIT)) .with_state(state) }