From 7c4c198902a40336137e42f971c32246c424fb40 Mon Sep 17 00:00:00 2001 From: Jim Ezesinachi Date: Mon, 1 Jun 2026 19:15:34 +0100 Subject: [PATCH 1/5] add db cluster logic and tests Signed-off-by: Jim Ezesinachi --- ahnlich/Cargo.lock | 3 + ahnlich/db/Cargo.toml | 5 +- ahnlich/db/src/cli/server.rs | 55 +++ ahnlich/db/src/lib.rs | 1 + ahnlich/db/src/replication/mod.rs | 329 +++++++++++++++ ahnlich/db/src/server/cluster.rs | 244 +++++++++++ ahnlich/db/src/server/cluster_mutations.rs | 65 +++ ahnlich/db/src/server/cluster_queries.rs | 118 ++++++ ahnlich/db/src/server/cluster_tasks.rs | 212 ++++++++++ ahnlich/db/src/server/handler.rs | 381 +++++++++++++----- ahnlich/db/src/server/mod.rs | 4 + ahnlich/db/src/tests/cluster_tests.rs | 319 +++++++++++++++ ahnlich/db/src/tests/mod.rs | 2 + .../db/src/tests/replication_store_tests.rs | 185 +++++++++ .../replication/src/storage/state_machine.rs | 5 + ahnlich/replication/src/types.rs | 15 +- ahnlich/types/build.rs | 4 + ahnlich/types/src/db/pipeline.rs | 8 +- ahnlich/types/src/services/db_service.rs | 72 ++++ ahnlich/types/src/shared/info.rs | 2 +- ahnlich/utils/src/server.rs | 8 +- protos/db/pipeline.proto | 3 + protos/services/db_service.proto | 2 + .../grpc/db/pipeline/pipeline.pb.go | 346 +++++++++------- .../grpc/services/db_service/db_service.pb.go | 228 ++++++----- .../services/db_service/db_service_grpc.pb.go | 39 ++ .../grpc/db/pipeline_pb.ts | 17 + .../grpc/services/db_service_connect.ts | 10 + .../grpc/db/pipeline/__init__.py | 7 + .../grpc/services/db_service/__init__.py | 37 ++ 30 files changed, 2352 insertions(+), 374 deletions(-) create mode 100644 ahnlich/db/src/replication/mod.rs create mode 100644 ahnlich/db/src/server/cluster.rs create mode 100644 ahnlich/db/src/server/cluster_mutations.rs create mode 100644 ahnlich/db/src/server/cluster_queries.rs create mode 100644 ahnlich/db/src/server/cluster_tasks.rs create mode 100644 ahnlich/db/src/tests/cluster_tests.rs create mode 100644 ahnlich/db/src/tests/replication_store_tests.rs diff --git a/ahnlich/Cargo.lock b/ahnlich/Cargo.lock index 44561e5e9..f5857ea91 100644 --- a/ahnlich/Cargo.lock +++ b/ahnlich/Cargo.lock @@ -1243,6 +1243,7 @@ name = "db" version = "0.1.0" dependencies = [ "ahash 0.8.12", + "ahnlich-replication", "ahnlich_client_rs", "ahnlich_similarity", "ahnlich_types", @@ -1259,8 +1260,10 @@ dependencies = [ "log", "ndarray", "once_cell", + "openraft", "papaya", "pretty_assertions", + "prost 0.13.5", "pulp", "rand 0.8.5", "rayon", diff --git a/ahnlich/db/Cargo.toml b/ahnlich/db/Cargo.toml index 97a8fbedf..c87079309 100644 --- a/ahnlich/db/Cargo.toml +++ b/ahnlich/db/Cargo.toml @@ -42,6 +42,10 @@ pulp.workspace = true papaya.workspace = true ahnlich_types = { path = "../types", version = "*" } tonic.workspace = true +prost.workspace = true +openraft.workspace = true +ahnlich-replication = { path = "../replication", version = "*" } +bitcode = { version = "0.6", features = ["serde"] } [dev-dependencies] futures.workspace = true @@ -54,7 +58,6 @@ dhat = "0.3" rcgen = "0.13" tempfile = "3" ahnlich_client_rs = { path = "../client", version = "*" } -bitcode = { version = "0.6", features = ["serde"] } chrono = { version = "0.4", default-features = false, features = ["std", "clock"] } [[bench]] diff --git a/ahnlich/db/src/cli/server.rs b/ahnlich/db/src/cli/server.rs index dfa8b3ae5..75f7e456e 100644 --- a/ahnlich/db/src/cli/server.rs +++ b/ahnlich/db/src/cli/server.rs @@ -1,3 +1,4 @@ +use ahnlich_replication::config::RaftStorageEngine; use clap::{Args, Parser, Subcommand}; use utils::cli::CommandLineConfig; @@ -18,6 +19,20 @@ pub enum Commands { pub struct ServerConfig { #[arg(long, default_value_t = 1369)] pub port: u16, + #[arg(long)] + pub cluster_addr: Option, + #[arg(long, default_value_t = false, conflicts_with = "cluster_join")] + pub cluster_bootstrap: bool, + #[arg(long, conflicts_with = "cluster_bootstrap")] + pub cluster_join: Option, + #[arg(long, value_enum, default_value_t = RaftStorageEngine::RocksDb)] + pub cluster_storage: RaftStorageEngine, + #[arg(long, requires_if("rocksdb", "cluster_storage"))] + pub cluster_data_dir: Option, + #[arg(long, default_value_t = 1000)] + pub cluster_snapshot_logs: u64, + #[arg(long, default_value_t = 300_000)] + pub cluster_snapshot_interval: u64, #[clap(flatten)] pub common: CommandLineConfig, } @@ -26,6 +41,13 @@ impl Default for ServerConfig { fn default() -> Self { Self { port: 1369, + cluster_addr: None, + cluster_bootstrap: false, + cluster_join: None, + cluster_storage: RaftStorageEngine::RocksDb, + cluster_data_dir: None, + cluster_snapshot_logs: 1000, + cluster_snapshot_interval: 300_000, common: CommandLineConfig::default(), } } @@ -38,6 +60,39 @@ impl ServerConfig { self } + pub fn is_clustered(&self) -> bool { + self.cluster_addr.is_some() + } + + pub fn cluster_addr(mut self, addr: std::net::SocketAddr) -> Self { + self.cluster_addr = Some(addr); + self + } + + pub fn cluster_bootstrap(mut self, addr: std::net::SocketAddr) -> Self { + self.cluster_addr = Some(addr); + self.cluster_bootstrap = true; + self.cluster_join = None; + self + } + + pub fn cluster_join(mut self, addr: std::net::SocketAddr, join: std::net::SocketAddr) -> Self { + self.cluster_addr = Some(addr); + self.cluster_bootstrap = false; + self.cluster_join = Some(join); + self + } + + pub fn cluster_data_dir(mut self, dir: std::path::PathBuf) -> Self { + self.cluster_data_dir = Some(dir); + self + } + + pub fn cluster_storage(mut self, storage: RaftStorageEngine) -> Self { + self.cluster_storage = storage; + self + } + pub fn persist_location(mut self, location: std::path::PathBuf) -> Self { self.common.persist_location = Some(location); self diff --git a/ahnlich/db/src/lib.rs b/ahnlich/db/src/lib.rs index 266a38f21..a6279c3a3 100644 --- a/ahnlich/db/src/lib.rs +++ b/ahnlich/db/src/lib.rs @@ -3,6 +3,7 @@ mod algorithm; pub mod cli; pub mod engine; pub mod errors; +pub mod replication; pub mod server; #[cfg(test)] diff --git a/ahnlich/db/src/replication/mod.rs b/ahnlich/db/src/replication/mod.rs new file mode 100644 index 000000000..b271240ba --- /dev/null +++ b/ahnlich/db/src/replication/mod.rs @@ -0,0 +1,329 @@ +#![allow(clippy::result_large_err)] + +use std::io::Cursor; +use std::sync::Arc; +use std::sync::atomic::AtomicBool; + +use ahnlich_replication::node::ReplicationNode; +use ahnlich_replication::storage::StateMachineHandler; +use ahnlich_replication::types::{DbCommand, DbResponse}; +use ahnlich_types::db::query; +use openraft::{StorageError, StorageIOError}; +use prost::Message; +use serde::Serialize; +use utils::persistence::AhnlichPersistenceUtils; + +use crate::engine::operations; +use crate::engine::store::{StoreHandler, Stores}; +use crate::errors::ServerError; + +openraft::declare_raft_types!( + pub DbTypeConfig: + D = DbCommand, + R = DbResponse, + Node = ReplicationNode, + SnapshotData = Cursor> +); + +#[derive(Debug)] +pub struct DbStateMachine { + store_handler: StoreHandler, +} + +impl DbStateMachine { + pub fn new(write_flag: Arc) -> Self { + Self { + store_handler: StoreHandler::new(write_flag), + } + } + + pub fn from_store_handler(store_handler: StoreHandler) -> Self { + Self { store_handler } + } + + pub fn store_handler(&self) -> &StoreHandler { + &self.store_handler + } +} + +fn decode_payload( + command_name: &str, + payload: &[u8], +) -> Result> { + M::decode(payload).map_err(|err| StorageError::IO { + source: StorageIOError::read_state_machine(&std::io::Error::other(format!( + "failed to decode {command_name} raft payload: {err}", + ))), + }) +} + +fn encode_raw_result( + command_name: &str, + result: &T, +) -> Result> { + bitcode::serialize(result) + .map(DbResponse::Bytes) + .map_err(|err| StorageError::IO { + source: StorageIOError::write_state_machine(&std::io::Error::other(format!( + "failed to encode {command_name} raw result: {err}", + ))), + }) +} + +fn operation_error(command_name: &str, err: ServerError) -> StorageError { + StorageError::IO { + source: StorageIOError::write_state_machine(&std::io::Error::other(format!( + "{command_name} apply failed: {err}", + ))), + } +} + +impl StateMachineHandler for DbStateMachine { + type Snapshot = Stores; + + fn apply(&mut self, data: &DbCommand) -> Result> { + match data { + DbCommand::CreateStore(payload) => { + let params = decode_payload::("CreateStore", payload)?; + operations::create_store(&self.store_handler, params) + .map(|_| DbResponse::Unit) + .map_err(|err| operation_error("CreateStore", err)) + } + DbCommand::CreatePredIndex(payload) => { + let params = decode_payload::("CreatePredIndex", payload)?; + let created = operations::create_pred_index(&self.store_handler, params) + .map_err(|err| operation_error("CreatePredIndex", err))?; + + encode_raw_result("CreatePredIndex", &created) + } + DbCommand::CreateNonLinearAlgorithmIndex(payload) => { + let params = decode_payload::( + "CreateNonLinearAlgorithmIndex", + payload, + )?; + let created = + operations::create_non_linear_algorithm_index(&self.store_handler, params) + .map_err(|err| operation_error("CreateNonLinearAlgorithmIndex", err))?; + + encode_raw_result("CreateNonLinearAlgorithmIndex", &created) + } + DbCommand::Set(payload) => { + let params = decode_payload::("Set", payload)?; + let upsert = operations::set(&self.store_handler, params) + .map_err(|err| operation_error("Set", err))?; + + encode_raw_result("Set", &upsert) + } + DbCommand::DelKey(payload) => { + let params = decode_payload::("DelKey", payload)?; + let deleted = operations::del_key(&self.store_handler, params) + .map_err(|err| operation_error("DelKey", err))?; + + encode_raw_result("DelKey", &deleted) + } + DbCommand::DelPred(payload) => { + let params = decode_payload::("DelPred", payload)?; + let deleted = operations::del_pred(&self.store_handler, params) + .map_err(|err| operation_error("DelPred", err))?; + + encode_raw_result("DelPred", &deleted) + } + DbCommand::DropPredIndex(payload) => { + let params = decode_payload::("DropPredIndex", payload)?; + let deleted = operations::drop_pred_index(&self.store_handler, params) + .map_err(|err| operation_error("DropPredIndex", err))?; + + encode_raw_result("DropPredIndex", &deleted) + } + DbCommand::DropNonLinearAlgorithmIndex(payload) => { + let params = decode_payload::( + "DropNonLinearAlgorithmIndex", + payload, + )?; + let deleted = + operations::drop_non_linear_algorithm_index(&self.store_handler, params) + .map_err(|err| operation_error("DropNonLinearAlgorithmIndex", err))?; + + encode_raw_result("DropNonLinearAlgorithmIndex", &deleted) + } + DbCommand::DropStore(payload) => { + let params = decode_payload::("DropStore", payload)?; + let dropped = operations::drop_store(&self.store_handler, params) + .map_err(|err| operation_error("DropStore", err))?; + + encode_raw_result("DropStore", &dropped) + } + } + } + + fn get_snapshot(&self) -> Self::Snapshot { + self.store_handler.get_snapshot() + } + + fn restore_snapshot(&mut self, snapshot: Self::Snapshot) { + self.store_handler.use_snapshot(snapshot); + } +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + use std::sync::Arc; + use std::sync::atomic::AtomicBool; + + use ahnlich_types::keyval::{DbStoreEntry, StoreKey, StoreValue}; + use ahnlich_types::metadata::MetadataValue; + + use super::*; + + fn store_value(label: &str) -> StoreValue { + let mut value = HashMap::new(); + value.insert( + "label".to_owned(), + MetadataValue { + value: Some(ahnlich_types::metadata::metadata_value::Value::RawString( + label.to_owned(), + )), + }, + ); + StoreValue { value } + } + + fn create_store_query(store: &str, dimension: u32) -> query::CreateStore { + query::CreateStore { + store: store.to_owned(), + dimension, + create_predicates: Vec::new(), + non_linear_indices: Vec::new(), + error_if_exists: true, + } + } + + fn set_query(store: &str) -> query::Set { + query::Set { + store: store.to_owned(), + inputs: vec![DbStoreEntry { + key: Some(StoreKey { + key: vec![1.0, 2.0], + }), + value: Some(store_value("alpha")), + }], + } + } + + fn drop_store_query(store: &str) -> query::DropStore { + query::DropStore { + store: store.to_owned(), + error_if_not_exists: true, + } + } + + fn encode_command(query: &M, wrap: impl FnOnce(Vec) -> DbCommand) -> DbCommand { + wrap(query.encode_to_vec()) + } + + fn decode_set_response(response: DbResponse) -> ahnlich_types::shared::info::StoreUpsert { + match response { + DbResponse::Bytes(bytes) => { + bitcode::deserialize(bytes.as_slice()).expect("decode set response") + } + DbResponse::Unit => panic!("expected byte response"), + } + } + + fn decode_count_response(response: DbResponse) -> usize { + match response { + DbResponse::Bytes(bytes) => { + bitcode::deserialize(bytes.as_slice()).expect("decode count response") + } + DbResponse::Unit => panic!("expected byte response"), + } + } + + #[test] + fn apply_create_store_and_set_dispatches_and_mutates_state() { + let mut state_machine = DbStateMachine::new(Arc::new(AtomicBool::new(false))); + + let create_response = state_machine + .apply(&encode_command( + &create_store_query("products", 2), + DbCommand::CreateStore, + )) + .expect("create store should succeed"); + assert!(matches!(create_response, DbResponse::Unit)); + + let set_response = state_machine + .apply(&encode_command(&set_query("products"), DbCommand::Set)) + .expect("set should succeed"); + let set = decode_set_response(set_response); + assert_eq!( + set, + ahnlich_types::shared::info::StoreUpsert { + inserted: 1, + updated: 0, + } + ); + + let stores = operations::list_stores(state_machine.store_handler()); + assert_eq!(stores.stores.len(), 1); + assert_eq!(stores.stores[0].name, "products"); + assert_eq!(stores.stores[0].len, 1); + assert_eq!(stores.stores[0].dimension, 2); + assert!(stores.stores[0].predicate_indices.is_empty()); + assert!(stores.stores[0].non_linear_indices.is_empty()); + assert!( + state_machine + .store_handler() + .write_flag + .load(std::sync::atomic::Ordering::SeqCst) + ); + } + + #[test] + fn apply_drop_store_dispatches_and_returns_deleted_count() { + let mut state_machine = DbStateMachine::new(Arc::new(AtomicBool::new(false))); + + state_machine + .apply(&encode_command( + &create_store_query("products", 2), + DbCommand::CreateStore, + )) + .expect("create store should succeed"); + + let response = state_machine + .apply(&encode_command( + &drop_store_query("products"), + DbCommand::DropStore, + )) + .expect("drop store should succeed"); + let deleted_count = decode_count_response(response); + assert_eq!(deleted_count, 1); + assert!( + operations::list_stores(state_machine.store_handler()) + .stores + .is_empty() + ); + } + + #[test] + fn apply_rejects_malformed_payload_without_mutating_state() { + let mut state_machine = DbStateMachine::new(Arc::new(AtomicBool::new(false))); + + let err = state_machine + .apply(&DbCommand::CreateStore(vec![0xff, 0x01, 0x02])) + .expect_err("malformed payload should fail"); + + assert!(matches!(err, StorageError::IO { .. })); + assert!( + operations::list_stores(state_machine.store_handler()) + .stores + .is_empty() + ); + assert!( + !state_machine + .store_handler() + .write_flag + .load(std::sync::atomic::Ordering::SeqCst) + ); + } +} diff --git a/ahnlich/db/src/server/cluster.rs b/ahnlich/db/src/server/cluster.rs new file mode 100644 index 000000000..39f4bc12e --- /dev/null +++ b/ahnlich/db/src/server/cluster.rs @@ -0,0 +1,244 @@ +use crate::cli::ServerConfig; +use crate::replication::{DbStateMachine, DbTypeConfig}; +use ahnlich_replication::config::{ClusterLifecycle, RaftConfig, RaftStorageEngine}; +use ahnlich_replication::network::GrpcRaftNetworkFactory; +use ahnlich_replication::node::ReplicationNode; +use ahnlich_replication::proto::cluster_admin::cluster_admin_service_client::ClusterAdminServiceClient; +use ahnlich_replication::proto::cluster_admin::{ + AddLearnerRequest, ChangeMembershipRequest, GetLeaderRequest, GetMetricsRequest, NodeInfo, +}; +use ahnlich_replication::storage::{ReplicationFailureState, StateMachineStore}; +use openraft::{Config as OpenRaftConfig, Membership, Raft, SnapshotPolicy, StoredMembership}; +use std::collections::BTreeMap; +use std::hash::{DefaultHasher, Hash, Hasher}; +use std::io::Result as IoResult; +use std::net::SocketAddr; +use std::sync::Arc; +use std::sync::Mutex; +use std::sync::atomic::AtomicBool; +use utils::server::ListenerStreamOrAddress; + +const SERVICE_NAME: &str = "ahnlich-db"; + +pub(crate) type DbRaft = Raft; + +pub(crate) struct ClusterRuntime { + pub(crate) node_id: u64, + pub(crate) raft_addr: SocketAddr, + pub(crate) raft: Arc, + pub(crate) state_machine: StateMachineStore, + pub(crate) failure_state: Arc, + cluster_listener: Mutex>, +} + +impl std::fmt::Debug for ClusterRuntime { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ClusterRuntime") + .field("node_id", &self.node_id) + .field("raft_addr", &self.raft_addr) + .field("failure_state", &self.failure_state) + .finish_non_exhaustive() + } +} + +impl ClusterRuntime { + fn new( + node_id: u64, + raft_addr: SocketAddr, + raft: Arc, + state_machine: StateMachineStore, + failure_state: Arc, + cluster_listener: ListenerStreamOrAddress, + ) -> Self { + Self { + node_id, + raft_addr, + raft, + state_machine, + failure_state, + cluster_listener: Mutex::new(Some(cluster_listener)), + } + } + + pub(crate) fn take_cluster_listener(&self) -> IoResult { + self.cluster_listener + .lock() + .expect("cluster listener mutex poisoned") + .take() + .ok_or_else(|| std::io::Error::other("cluster listener already taken")) + } + + fn node_info(&self, service_addr: SocketAddr) -> NodeInfo { + NodeInfo { + id: self.node_id, + raft_addr: self.raft_addr.to_string(), + service_addr: service_addr.to_string(), + } + } +} + +fn hash_node_id(addr: SocketAddr) -> u64 { + let mut hasher = DefaultHasher::new(); + addr.hash(&mut hasher); + hasher.finish() +} + +fn normalize_cluster_target(addr: SocketAddr) -> String { + format!("http://{addr}") +} + +fn build_raft_config( + config: &ServerConfig, + service_addr: SocketAddr, + raft_addr: SocketAddr, +) -> RaftConfig { + RaftConfig { + node_id: hash_node_id(raft_addr), + raft_addr, + service_addr, + storage: config.cluster_storage, + data_dir: config.cluster_data_dir.clone(), + snapshot_logs: config.cluster_snapshot_logs, + snapshot_interval_ms: config.cluster_snapshot_interval, + lifecycle: if config.cluster_bootstrap { + ClusterLifecycle::Bootstrap + } else if let Some(join) = config.cluster_join { + ClusterLifecycle::Join(join) + } else { + ClusterLifecycle::Existing + }, + } +} + +async fn resolve_join_target(join_addr: SocketAddr) -> SocketAddr { + let target = normalize_cluster_target(join_addr); + let mut client = match ClusterAdminServiceClient::connect(target.clone()).await { + Ok(client) => client, + Err(err) => { + log::warn!("Failed to connect to join target {join_addr}: {err}"); + return join_addr; + } + }; + + match client + .get_leader(tonic::Request::new(GetLeaderRequest {})) + .await + { + Ok(response) => response + .into_inner() + .leader_addr + .parse() + .unwrap_or(join_addr), + Err(_) => join_addr, + } +} + +pub(crate) async fn initialize_cluster_runtime( + config: &ServerConfig, + service_addr: SocketAddr, + cluster: &ClusterRuntime, +) -> IoResult<()> { + match build_raft_config(config, service_addr, cluster.raft_addr).lifecycle { + ClusterLifecycle::Bootstrap => { + let node = ReplicationNode { + raft_addr: cluster.raft_addr.to_string(), + service_addr: service_addr.to_string(), + }; + cluster + .raft + .initialize(BTreeMap::from([(cluster.node_id, node)])) + .await + .map_err(|err| std::io::Error::other(err.to_string()))?; + } + ClusterLifecycle::Join(join_addr) => { + let target = resolve_join_target(join_addr).await; + let mut client = ClusterAdminServiceClient::connect(normalize_cluster_target(target)) + .await + .map_err(|err| std::io::Error::other(err.to_string()))?; + let node = cluster.node_info(service_addr); + + client + .add_learner(tonic::Request::new(AddLearnerRequest { + node: Some(node.clone()), + })) + .await + .map_err(|err| std::io::Error::other(err.to_string()))?; + + let metrics = client + .get_metrics(tonic::Request::new(GetMetricsRequest {})) + .await + .map_err(|err| std::io::Error::other(err.to_string()))? + .into_inner(); + + let mut node_ids = metrics.voter_ids; + if !node_ids.contains(&node.id) { + node_ids.push(node.id); + } + client + .change_membership(tonic::Request::new(ChangeMembershipRequest { node_ids })) + .await + .map_err(|err| std::io::Error::other(err.to_string()))?; + } + ClusterLifecycle::Existing => {} + } + + Ok(()) +} + +pub(crate) async fn build_cluster_runtime( + config: &ServerConfig, + service_addr: SocketAddr, + cluster_listener: ListenerStreamOrAddress, +) -> IoResult { + let raft_addr = cluster_listener.local_addr()?; + let raft_config = build_raft_config(config, service_addr, raft_addr); + let failure_state = Arc::new(ReplicationFailureState::default()); + let state_machine = StateMachineStore::new( + DbStateMachine::new(Arc::new(AtomicBool::new(false))), + StoredMembership::new(None, Membership::new(vec![], BTreeMap::new())), + failure_state.clone(), + ); + + let openraft_config = OpenRaftConfig { + cluster_name: SERVICE_NAME.to_owned(), + snapshot_policy: SnapshotPolicy::LogsSinceLast(raft_config.snapshot_logs), + ..Default::default() + } + .validate() + .map_err(|err| std::io::Error::other(err.to_string()))?; + + let raft = match raft_config.storage { + RaftStorageEngine::RocksDb => { + let data_dir = raft_config.data_dir.ok_or_else(|| { + std::io::Error::other("cluster_data_dir is required when cluster_storage=rocksdb") + })?; + let log_store = ahnlich_replication::storage::RocksLogStore::::open( + data_dir.join("raft"), + ) + .map_err(|err| std::io::Error::other(err.to_string()))?; + Raft::new( + raft_config.node_id, + Arc::new(openraft_config), + GrpcRaftNetworkFactory::::default(), + log_store, + state_machine.clone(), + ) + .await + .map_err(|err| std::io::Error::other(err.to_string()))? + } + RaftStorageEngine::Memory => { + return Err(std::io::Error::other( + "cluster_storage=memory is not supported by the DB server runtime", + )); + } + }; + + Ok(ClusterRuntime::new( + raft_config.node_id, + raft_addr, + Arc::new(raft), + state_machine, + failure_state, + cluster_listener, + )) +} diff --git a/ahnlich/db/src/server/cluster_mutations.rs b/ahnlich/db/src/server/cluster_mutations.rs new file mode 100644 index 000000000..36e4ce4ca --- /dev/null +++ b/ahnlich/db/src/server/cluster_mutations.rs @@ -0,0 +1,65 @@ +use crate::server::cluster::ClusterRuntime; +use ahnlich_replication::node::ReplicationNode; +use ahnlich_replication::types::{DbCommand, DbResponse}; +use openraft::error::RaftError; +use serde::de::DeserializeOwned; +use std::any::{Any, TypeId}; + +pub(crate) fn map_client_write_error( + command_name: &str, + err: RaftError>, +) -> tonic::Status { + if let Some(forward) = err.forward_to_leader() { + let leader = forward + .leader_node + .as_ref() + .map(|node| node.service_addr.as_str()) + .unwrap_or("unknown leader"); + tonic::Status::failed_precondition(format!( + "{command_name} requires leader routing; retry against {leader}", + )) + } else { + tonic::Status::internal(format!("{command_name} raft write failed: {err}")) + } +} + +async fn submit_raw_db_command( + cluster: Option<&ClusterRuntime>, + command_name: &str, + command: DbCommand, +) -> Result { + let cluster = cluster.ok_or_else(|| { + tonic::Status::failed_precondition("DB server is not running in cluster mode") + })?; + + cluster + .raft + .client_write(command) + .await + .map(|response| response.data) + .map_err(|err| map_client_write_error(command_name, err)) +} + +pub(crate) async fn submit_db_command( + cluster: Option<&ClusterRuntime>, + command_name: &str, + command: DbCommand, +) -> Result +where + T: DeserializeOwned + 'static, +{ + match submit_raw_db_command(cluster, command_name, command).await? { + DbResponse::Bytes(bytes) => bitcode::deserialize(bytes.as_slice()).map_err(|err| { + tonic::Status::internal(format!("failed to decode {command_name} raw result: {err}",)) + }), + DbResponse::Unit if TypeId::of::() == TypeId::of::<()>() => { + let unit: Box = Box::new(()); + Ok(*unit + .downcast::() + .expect("unit type checked before downcast")) + } + DbResponse::Unit => Err(tonic::Status::internal(format!( + "{command_name} unexpectedly returned unit response", + ))), + } +} diff --git a/ahnlich/db/src/server/cluster_queries.rs b/ahnlich/db/src/server/cluster_queries.rs new file mode 100644 index 000000000..26755ee28 --- /dev/null +++ b/ahnlich/db/src/server/cluster_queries.rs @@ -0,0 +1,118 @@ +use crate::engine::operations; +use crate::engine::store::StoreHandler; +use crate::errors::ServerError; +use crate::server::cluster::ClusterRuntime; +use ahnlich_replication::cluster_info; +use ahnlich_replication::node::ReplicationNode; +use ahnlich_types::db::server; +use ahnlich_types::shared::cluster::{ + ClusterInfoResponse, ClusterNode, NodeHealthStatus as PublicNodeHealthStatus, + NodeRole as PublicNodeRole, +}; +use openraft::error::RaftError; +use std::io::Result as IoResult; +use std::net::SocketAddr; +use std::sync::Arc; + +fn map_cluster_role(role: cluster_info::NodeRole) -> i32 { + match role { + cluster_info::NodeRole::Leader => PublicNodeRole::Leader as i32, + cluster_info::NodeRole::Follower => PublicNodeRole::Follower as i32, + cluster_info::NodeRole::Learner => PublicNodeRole::Learner as i32, + } +} + +fn map_cluster_health(health: cluster_info::NodeHealthStatus) -> i32 { + match health { + cluster_info::NodeHealthStatus::Healthy => PublicNodeHealthStatus::Healthy as i32, + cluster_info::NodeHealthStatus::Unreachable => PublicNodeHealthStatus::Unreachable as i32, + } +} + +pub(crate) fn map_storage_error(context: &str, err: openraft::StorageError) -> tonic::Status { + tonic::Status::internal(format!("{context}: {err}")) +} + +pub(crate) fn map_linearizable_error( + err: RaftError>, +) -> tonic::Status { + if let Some(forward) = err.forward_to_leader() { + let leader = forward + .leader_node + .as_ref() + .map(|node| node.service_addr.as_str()) + .unwrap_or("unknown leader"); + tonic::Status::failed_precondition(format!( + "ListStores must be served by the leader in cluster mode; retry against {leader}", + )) + } else { + tonic::Status::internal(format!("failed to linearize ListStores: {err}")) + } +} + +#[allow(clippy::result_large_err)] +pub(crate) fn read_store_handler( + cluster: Option<&ClusterRuntime>, + store_handler: &Arc, + f: impl FnOnce(&StoreHandler) -> Result, +) -> Result { + if let Some(cluster) = cluster { + cluster + .state_machine + .with_handler(|handler| f(handler.store_handler())) + .map_err(|err| map_storage_error("failed to access clustered state machine", err))? + .map_err(Into::into) + } else { + f(store_handler).map_err(Into::into) + } +} + +pub(crate) async fn list_stores_response( + cluster: Option<&ClusterRuntime>, + store_handler: &Arc, +) -> Result { + if let Some(cluster) = cluster { + cluster + .raft + .ensure_linearizable() + .await + .map_err(map_linearizable_error)?; + } + + read_store_handler(cluster, store_handler, |store_handler| { + Ok(operations::list_stores(store_handler)) + }) +} + +pub(crate) async fn cluster_info_response( + listener_addr: IoResult, + cluster: Option<&ClusterRuntime>, +) -> Result { + if let Some(cluster) = cluster { + let nodes = cluster_info::cluster_topology(cluster.raft.as_ref()) + .await + .into_iter() + .map(|node| ClusterNode { + node_id: node.node_id, + addr: node.service_addr, + role: map_cluster_role(node.role), + health: map_cluster_health(node.health), + term: node.term, + commit_index: node.commit_index, + }) + .collect(); + + Ok(ClusterInfoResponse { nodes }) + } else { + Ok(ClusterInfoResponse { + nodes: vec![ClusterNode { + node_id: 1, + addr: listener_addr?.to_string(), + role: PublicNodeRole::Leader as i32, + health: PublicNodeHealthStatus::Healthy as i32, + term: None, + commit_index: None, + }], + }) + } +} diff --git a/ahnlich/db/src/server/cluster_tasks.rs b/ahnlich/db/src/server/cluster_tasks.rs new file mode 100644 index 000000000..5d4db6183 --- /dev/null +++ b/ahnlich/db/src/server/cluster_tasks.rs @@ -0,0 +1,212 @@ +use crate::cli::ServerConfig; +use crate::engine::store::StoresSnapshot; +use crate::replication::DbStateMachine; +use crate::server::cluster::{ClusterRuntime, DbRaft}; +use ahnlich_replication::admin::ClusterAdmin; +use ahnlich_replication::network::GrpcRaftService; +use ahnlich_replication::proto::cluster_admin::cluster_admin_service_server::ClusterAdminServiceServer; +use ahnlich_replication::proto::raft_internal::raft_internal_service_server::RaftInternalServiceServer; +use ahnlich_replication::storage::StateMachineStore; +use std::future::Future; +use std::io::Result as IoResult; +use std::sync::Arc; +use std::sync::Mutex; +use task_manager::{BlockingTask, Task, TaskManager, TaskState}; +use tokio::time::{Duration, sleep}; +use utils::connection_layer::trace_with_parent; +use utils::server::ListenerStreamOrAddress; +use utils::size_calculation::SizeCalculationHandler; + +struct ClusterServiceTask { + listener: ListenerStreamOrAddress, + raft: Arc, +} + +impl std::fmt::Debug for ClusterServiceTask { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ClusterServiceTask").finish_non_exhaustive() + } +} + +#[async_trait::async_trait] +impl BlockingTask for ClusterServiceTask { + fn task_name(&self) -> String { + "ahnlich-db-cluster".to_owned() + } + + async fn run( + mut self, + shutdown_signal: std::pin::Pin + Send + Sync + 'static>>, + ) { + let listener_stream = if let ListenerStreamOrAddress::ListenerStream(stream) = self.listener + { + stream + } else { + panic!("cluster listener must be of type listener stream"); + }; + + self.listener = ListenerStreamOrAddress::Address( + listener_stream + .as_ref() + .local_addr() + .expect("Could not get cluster local address"), + ); + + let raft_service = RaftInternalServiceServer::new(GrpcRaftService::new(self.raft.clone())); + let admin_service = ClusterAdminServiceServer::new(ClusterAdmin::new(self.raft.clone())); + + let _ = tonic::transport::Server::builder() + .trace_fn(trace_with_parent) + .add_service(raft_service) + .add_service(admin_service) + .serve_with_incoming_shutdown(listener_stream, shutdown_signal) + .await; + } +} + +#[derive(Debug, Clone)] +struct ClusterSizeCalculationTask { + state_machine: StateMachineStore, + interval_ms: u64, +} + +#[async_trait::async_trait] +impl Task for ClusterSizeCalculationTask { + fn task_name(&self) -> String { + "cluster_size_calculation".to_owned() + } + + async fn run(&self) -> TaskState { + sleep(Duration::from_millis(self.interval_ms)).await; + if let Err(err) = self.state_machine.with_handler(|handler| { + StoresSnapshot::new(handler.store_handler().get_stores()).recalculate_all_sizes(); + }) { + log::error!("Failed to recalculate clustered store sizes: {err}"); + return TaskState::Break; + } + TaskState::Continue + } +} + +#[derive(Debug, Clone)] +struct ReplicationFailureWatcher { + failure_state: Arc, + task_manager: Arc, +} + +#[async_trait::async_trait] +impl Task for ReplicationFailureWatcher { + fn task_name(&self) -> String { + "db_replication_failure_watcher".to_owned() + } + + async fn run(&self) -> TaskState { + sleep(Duration::from_millis(100)).await; + if self.failure_state.failed() { + log::error!( + "Replication failure detected, shutting down DB server: {:?}", + self.failure_state.reason() + ); + self.task_manager.cancel_all(); + TaskState::Break + } else { + TaskState::Continue + } + } +} + +struct SnapshotTriggerTask { + raft: Arc, + interval_ms: u64, + last_seen_applied: Mutex>, +} + +impl std::fmt::Debug for SnapshotTriggerTask { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SnapshotTriggerTask") + .field("interval_ms", &self.interval_ms) + .finish_non_exhaustive() + } +} + +impl SnapshotTriggerTask { + fn new(raft: Arc, interval_ms: u64) -> Self { + Self { + raft, + interval_ms, + last_seen_applied: Mutex::new(None), + } + } +} + +#[async_trait::async_trait] +impl Task for SnapshotTriggerTask { + fn task_name(&self) -> String { + "db_snapshot_trigger".to_owned() + } + + async fn run(&self) -> TaskState { + sleep(Duration::from_millis(self.interval_ms)).await; + + let last_applied = self + .raft + .metrics() + .borrow() + .last_applied + .map(|log_id| log_id.index); + let Some(last_applied) = last_applied else { + return TaskState::Continue; + }; + + let should_snapshot = { + let mut guard = self + .last_seen_applied + .lock() + .expect("snapshot trigger mutex poisoned"); + let should = guard.is_none_or(|seen| seen != last_applied); + if should { + *guard = Some(last_applied); + } + should + }; + + if should_snapshot && let Err(err) = self.raft.trigger().snapshot().await { + log::warn!("Failed to trigger DB snapshot: {err}"); + } + + TaskState::Continue + } +} + +pub(crate) async fn spawn_cluster_tasks( + config: &ServerConfig, + task_manager: &Arc, + cluster: &ClusterRuntime, +) -> IoResult<()> { + task_manager + .spawn_task_loop(ClusterSizeCalculationTask { + state_machine: cluster.state_machine.clone(), + interval_ms: config.common.size_calculation_interval, + }) + .await; + task_manager + .spawn_task_loop(ReplicationFailureWatcher { + failure_state: cluster.failure_state.clone(), + task_manager: task_manager.clone(), + }) + .await; + task_manager + .spawn_task_loop(SnapshotTriggerTask::new( + cluster.raft.clone(), + config.cluster_snapshot_interval, + )) + .await; + task_manager + .spawn_blocking(ClusterServiceTask { + listener: cluster.take_cluster_listener()?, + raft: cluster.raft.clone(), + }) + .await; + + Ok(()) +} diff --git a/ahnlich/db/src/server/handler.rs b/ahnlich/db/src/server/handler.rs index 2c5169484..a1c19dc72 100644 --- a/ahnlich/db/src/server/handler.rs +++ b/ahnlich/db/src/server/handler.rs @@ -1,15 +1,25 @@ use crate::cli::ServerConfig; use crate::engine::operations; use crate::engine::store::StoreHandler; +use crate::engine::store::StoresSnapshot; use crate::errors::ServerError; +use crate::server::cluster::{ClusterRuntime, build_cluster_runtime, initialize_cluster_runtime}; +use crate::server::cluster_mutations::submit_db_command; +use crate::server::cluster_queries::{ + cluster_info_response, list_stores_response, read_store_handler, +}; +use crate::server::cluster_tasks::spawn_cluster_tasks; +use ahnlich_replication::types::DbCommand; use ahnlich_types::db::pipeline::db_query::Query; use ahnlich_types::db::server::GetSimNEntry; use ahnlich_types::keyval::{DbStoreEntry, StoreKey, StoreName}; use ahnlich_types::services::db_service::db_service_server::{DbService, DbServiceServer}; +use ahnlich_types::shared::cluster::{ClusterInfoQuery, ClusterInfoResponse}; use ahnlich_types::shared::info::ErrorResponse; use ahnlich_types::db::{pipeline, query, server}; use ahnlich_types::{client as types_client, utils as types_utils}; +use prost::Message; use std::future::Future; use std::io::Result as IoResult; use std::net::SocketAddr; @@ -36,6 +46,7 @@ pub struct Server { client_handler: Arc, task_manager: Arc, config: ServerConfig, + cluster: Option, } #[tonic::async_trait] @@ -45,9 +56,20 @@ impl DbService for Server { &self, request: tonic::Request, ) -> std::result::Result, tonic::Status> { - operations::create_store(&self.store_handler, request.into_inner()) - .map(|_| tonic::Response::new(server::Unit {})) - .map_err(|err| err.into()) + let params = request.into_inner(); + + if self.cluster.is_some() { + let (): () = submit_db_command( + self.cluster.as_ref(), + "CreateStore", + DbCommand::CreateStore(params.encode_to_vec()), + ) + .await?; + } else { + operations::create_store(&self.store_handler, params)?; + } + + Ok(tonic::Response::new(server::Unit {})) } #[tracing::instrument(skip_all)] @@ -62,22 +84,27 @@ impl DbService for Server { .map(|key| StoreKey { key: key.key }) .collect(); - let entries: Vec = self - .store_handler - .get_key_in_store( - &StoreName { - value: params.store, - }, - keys, - )? - .into_iter() - .map(|(embedding_key, store_value)| DbStoreEntry { - key: Some(StoreKey { - key: embedding_key.as_slice().to_vec(), - }), - value: Some(Arc::unwrap_or_clone(store_value)), - }) - .collect(); + let entries: Vec = read_store_handler( + self.cluster.as_ref(), + &self.store_handler, + |store_handler| { + Ok(store_handler + .get_key_in_store( + &StoreName { + value: params.store, + }, + keys, + )? + .into_iter() + .map(|(embedding_key, store_value)| DbStoreEntry { + key: Some(StoreKey { + key: embedding_key.as_slice().to_vec(), + }), + value: Some(Arc::unwrap_or_clone(store_value)), + }) + .collect()) + }, + )?; Ok(tonic::Response::new(server::Get { entries })) } @@ -92,22 +119,27 @@ impl DbService for Server { let condition = ahnlich_types::unwrap_or_invalid!(params.condition, "Predicate Condition is required"); - let entries = self - .store_handler - .get_pred_in_store( - &StoreName { - value: params.store, - }, - &condition, - )? - .into_iter() - .map(|(embedding_key, store_value)| DbStoreEntry { - key: Some(StoreKey { - key: embedding_key.as_slice().to_vec(), - }), - value: Some(Arc::unwrap_or_clone(store_value)), - }) - .collect(); + let entries = read_store_handler( + self.cluster.as_ref(), + &self.store_handler, + |store_handler| { + Ok(store_handler + .get_pred_in_store( + &StoreName { + value: params.store, + }, + &condition, + )? + .into_iter() + .map(|(embedding_key, store_value)| DbStoreEntry { + key: Some(StoreKey { + key: embedding_key.as_slice().to_vec(), + }), + value: Some(Arc::unwrap_or_clone(store_value)), + }) + .collect()) + }, + )?; Ok(tonic::Response::new(server::Get { entries })) } @@ -128,27 +160,33 @@ impl DbService for Server { let algorithm = ahnlich_types::algorithm::algorithms::Algorithm::try_from(params.algorithm) .map_err(|err| tonic::Status::invalid_argument(err.to_string()))?; - let entries = self - .store_handler - .get_sim_in_store( - &StoreName { - value: params.store, - }, - search_input, - types_utils::convert_to_nonzerousize(params.closest_n) - .map_err(tonic::Status::invalid_argument)?, - algorithm, - params.condition, - )? - .into_iter() - .map(|(embedding_key, store_value, sim)| GetSimNEntry { - key: Some(StoreKey { - key: embedding_key.as_slice().to_vec(), - }), - value: Some(Arc::unwrap_or_clone(store_value)), - similarity: Some(sim), - }) - .collect(); + let closest_n = types_utils::convert_to_nonzerousize(params.closest_n) + .map_err(tonic::Status::invalid_argument)?; + let entries = read_store_handler( + self.cluster.as_ref(), + &self.store_handler, + |store_handler| { + Ok(store_handler + .get_sim_in_store( + &StoreName { + value: params.store, + }, + search_input, + closest_n, + algorithm, + params.condition, + )? + .into_iter() + .map(|(embedding_key, store_value, sim)| GetSimNEntry { + key: Some(StoreKey { + key: embedding_key.as_slice().to_vec(), + }), + value: Some(Arc::unwrap_or_clone(store_value)), + similarity: Some(sim), + }) + .collect()) + }, + )?; Ok(tonic::Response::new(server::GetSimN { entries })) } @@ -166,10 +204,21 @@ impl DbService for Server { &self, request: tonic::Request, ) -> std::result::Result, tonic::Status> { - let created = operations::create_pred_index(&self.store_handler, request.into_inner())?; + let params = request.into_inner(); + + let created_indexes = (if self.cluster.is_some() { + submit_db_command( + self.cluster.as_ref(), + "CreatePredIndex", + DbCommand::CreatePredIndex(params.encode_to_vec()), + ) + .await? + } else { + operations::create_pred_index(&self.store_handler, params)? + }) as u64; Ok(tonic::Response::new(server::CreateIndex { - created_indexes: created as u64, + created_indexes, })) } @@ -178,13 +227,21 @@ impl DbService for Server { &self, request: tonic::Request, ) -> std::result::Result, tonic::Status> { - let created = operations::create_non_linear_algorithm_index( - &self.store_handler, - request.into_inner(), - )?; + let params = request.into_inner(); + + let created_indexes = (if self.cluster.is_some() { + submit_db_command( + self.cluster.as_ref(), + "CreateNonLinearAlgorithmIndex", + DbCommand::CreateNonLinearAlgorithmIndex(params.encode_to_vec()), + ) + .await? + } else { + operations::create_non_linear_algorithm_index(&self.store_handler, params)? + }) as u64; Ok(tonic::Response::new(server::CreateIndex { - created_indexes: created as u64, + created_indexes, })) } @@ -193,11 +250,20 @@ impl DbService for Server { &self, request: tonic::Request, ) -> std::result::Result, tonic::Status> { - let del = operations::drop_pred_index(&self.store_handler, request.into_inner())?; + let params = request.into_inner(); - Ok(tonic::Response::new(server::Del { - deleted_count: del as u64, - })) + let deleted_count = (if self.cluster.is_some() { + submit_db_command( + self.cluster.as_ref(), + "DropPredIndex", + DbCommand::DropPredIndex(params.encode_to_vec()), + ) + .await? + } else { + operations::drop_pred_index(&self.store_handler, params)? + }) as u64; + + Ok(tonic::Response::new(server::Del { deleted_count })) } #[tracing::instrument(skip_all)] @@ -205,12 +271,20 @@ impl DbService for Server { &self, request: tonic::Request, ) -> std::result::Result, tonic::Status> { - let del = - operations::drop_non_linear_algorithm_index(&self.store_handler, request.into_inner())?; + let params = request.into_inner(); - Ok(tonic::Response::new(server::Del { - deleted_count: del as u64, - })) + let deleted_count = (if self.cluster.is_some() { + submit_db_command( + self.cluster.as_ref(), + "DropNonLinearAlgorithmIndex", + DbCommand::DropNonLinearAlgorithmIndex(params.encode_to_vec()), + ) + .await? + } else { + operations::drop_non_linear_algorithm_index(&self.store_handler, params)? + }) as u64; + + Ok(tonic::Response::new(server::Del { deleted_count })) } #[tracing::instrument(skip_all)] @@ -218,11 +292,20 @@ impl DbService for Server { &self, request: tonic::Request, ) -> std::result::Result, tonic::Status> { - let del = operations::del_key(&self.store_handler, request.into_inner())?; + let params = request.into_inner(); - Ok(tonic::Response::new(server::Del { - deleted_count: del as u64, - })) + let deleted_count = (if self.cluster.is_some() { + submit_db_command( + self.cluster.as_ref(), + "DelKey", + DbCommand::DelKey(params.encode_to_vec()), + ) + .await? + } else { + operations::del_key(&self.store_handler, params)? + }) as u64; + + Ok(tonic::Response::new(server::Del { deleted_count })) } #[tracing::instrument(skip_all)] @@ -230,11 +313,20 @@ impl DbService for Server { &self, request: tonic::Request, ) -> std::result::Result, tonic::Status> { - let del = operations::del_pred(&self.store_handler, request.into_inner())?; + let params = request.into_inner(); - Ok(tonic::Response::new(server::Del { - deleted_count: del as u64, - })) + let deleted_count = (if self.cluster.is_some() { + submit_db_command( + self.cluster.as_ref(), + "DelPred", + DbCommand::DelPred(params.encode_to_vec()), + ) + .await? + } else { + operations::del_pred(&self.store_handler, params)? + }) as u64; + + Ok(tonic::Response::new(server::Del { deleted_count })) } #[tracing::instrument(skip_all)] @@ -242,11 +334,20 @@ impl DbService for Server { &self, request: tonic::Request, ) -> std::result::Result, tonic::Status> { - let dropped = operations::drop_store(&self.store_handler, request.into_inner())?; + let params = request.into_inner(); - Ok(tonic::Response::new(server::Del { - deleted_count: dropped as u64, - })) + let deleted_count = (if self.cluster.is_some() { + submit_db_command( + self.cluster.as_ref(), + "DropStore", + DbCommand::DropStore(params.encode_to_vec()), + ) + .await? + } else { + operations::drop_store(&self.store_handler, params)? + }) as u64; + + Ok(tonic::Response::new(server::Del { deleted_count })) } #[tracing::instrument(skip_all)] @@ -272,9 +373,8 @@ impl DbService for Server { &self, _request: tonic::Request, ) -> std::result::Result, tonic::Status> { - Ok(tonic::Response::new(operations::list_stores( - &self.store_handler, - ))) + let store_list = list_stores_response(self.cluster.as_ref(), &self.store_handler).await?; + Ok(tonic::Response::new(store_list)) } #[tracing::instrument(skip_all)] @@ -283,9 +383,15 @@ impl DbService for Server { request: tonic::Request, ) -> std::result::Result, tonic::Status> { let params = request.into_inner(); - let store_info = self.store_handler.get_store(&StoreName { - value: params.store, - })?; + let store_info = read_store_handler( + self.cluster.as_ref(), + &self.store_handler, + |store_handler| { + store_handler.get_store(&StoreName { + value: params.store, + }) + }, + )?; Ok(tonic::Response::new(store_info)) } @@ -326,11 +432,32 @@ impl DbService for Server { &self, request: tonic::Request, ) -> std::result::Result, tonic::Status> { - let set = operations::set(&self.store_handler, request.into_inner())?; + let params = request.into_inner(); + + let set = if self.cluster.is_some() { + submit_db_command( + self.cluster.as_ref(), + "Set", + DbCommand::Set(params.encode_to_vec()), + ) + .await? + } else { + operations::set(&self.store_handler, params)? + }; Ok(tonic::Response::new(server::Set { upsert: Some(set) })) } + #[tracing::instrument(skip_all)] + async fn cluster_info( + &self, + _request: tonic::Request, + ) -> std::result::Result, tonic::Status> { + Ok(tonic::Response::new( + cluster_info_response(self.listener.local_addr(), self.cluster.as_ref()).await?, + )) + } + #[tracing::instrument(skip_all)] async fn pipeline( &self, @@ -611,6 +738,22 @@ impl DbService for Server { } } } + + Query::ClusterInfo(params) => { + match self.cluster_info(tonic::Request::new(params)).await { + Ok(res) => response_vec.push( + pipeline::db_server_response::Response::ClusterInfo(res.into_inner()), + ), + Err(err) => { + response_vec.push(pipeline::db_server_response::Response::Error( + ErrorResponse { + message: err.message().to_string(), + code: err.code().into(), + }, + )); + } + } + } } } @@ -654,19 +797,28 @@ impl AhnlichServerUtils for Server { self.task_manager.clone() } + fn should_spawn_persistence(&self) -> bool { + !self.config.is_clustered() + } + async fn spawn_tasks_before_server( &self, task_manager: &Arc, ) -> std::io::Result<()> { - use crate::engine::store::StoresSnapshot; - use utils::size_calculation::SizeCalculation; + if let Some(cluster) = &self.cluster { + spawn_cluster_tasks(&self.config, task_manager, cluster).await?; + tokio::time::sleep(std::time::Duration::from_millis(100)).await; + initialize_cluster_runtime(&self.config, self.listener.local_addr()?, cluster).await?; + } else { + use utils::size_calculation::SizeCalculation; - let size_calculation_task = SizeCalculation::task( - self.write_flag(), - self.config.common.size_calculation_interval, - StoresSnapshot::new(self.store_handler.get_stores()), - ); - task_manager.spawn_task_loop(size_calculation_task).await; + let size_calculation_task = SizeCalculation::task( + self.write_flag(), + self.config.common.size_calculation_interval, + StoresSnapshot::new(self.store_handler.get_stores()), + ); + task_manager.spawn_task_loop(size_calculation_task).await; + } Ok(()) } } @@ -680,9 +832,10 @@ impl Server { client_handler.clone(), ) .await?; - let write_flag = Arc::new(AtomicBool::new(false)); - let mut store_handler = StoreHandler::new(write_flag.clone()); - if let Some(persist_location) = &config.common.persist_location { + let mut store_handler = StoreHandler::new(Arc::new(AtomicBool::new(false))); + if !config.is_clustered() + && let Some(persist_location) = &config.common.persist_location + { match Persistence::load_snapshot(persist_location, config.common.enable_mmap) { Err(e) => { log::error!("Failed to load snapshot from persist location {e}"); @@ -695,13 +848,29 @@ impl Server { } } }; - Ok(Self { + let mut server = Self { listener, store_handler: Arc::new(store_handler), client_handler, task_manager: Arc::new(TaskManager::new()), config: config.clone(), - }) + cluster: None, + }; + + if config.is_clustered() { + let cluster_client_handler = Arc::new(ClientHandler::new(1024)); + let cluster_addr = config + .cluster_addr + .ok_or_else(|| std::io::Error::other("cluster_addr is required in cluster mode"))?; + let cluster_listener = + ListenerStreamOrAddress::new(cluster_addr.to_string(), cluster_client_handler) + .await?; + let service_addr = server.listener.local_addr()?; + server.cluster = + Some(build_cluster_runtime(config, service_addr, cluster_listener).await?); + } + + Ok(server) } pub fn client_handler(&self) -> Arc { @@ -712,6 +881,10 @@ impl Server { self.listener.local_addr() } + pub fn cluster_local_addr(&self) -> Option { + self.cluster.as_ref().map(|cluster| cluster.raft_addr) + } + /// initializes a server using server configuration pub async fn new(config: &ServerConfig) -> IoResult { // Enable log and tracing diff --git a/ahnlich/db/src/server/mod.rs b/ahnlich/db/src/server/mod.rs index 062ae9d9b..e07b998c0 100644 --- a/ahnlich/db/src/server/mod.rs +++ b/ahnlich/db/src/server/mod.rs @@ -1 +1,5 @@ +pub mod cluster; +pub mod cluster_mutations; +pub mod cluster_queries; +pub mod cluster_tasks; pub mod handler; diff --git a/ahnlich/db/src/tests/cluster_tests.rs b/ahnlich/db/src/tests/cluster_tests.rs new file mode 100644 index 000000000..593608aef --- /dev/null +++ b/ahnlich/db/src/tests/cluster_tests.rs @@ -0,0 +1,319 @@ +use crate::cli::ServerConfig; +use crate::server::handler::Server; +use ahnlich_types::db::{ + pipeline::{DbQuery, DbRequestPipeline, db_query::Query, db_server_response::Response}, + query as db_query_types, +}; +use ahnlich_types::keyval::{DbStoreEntry, StoreKey, StoreValue}; +use ahnlich_types::metadata::MetadataValue; +use ahnlich_types::metadata::metadata_value::Value as MetadataValueEnum; +use ahnlich_types::services::db_service::db_service_client::DbServiceClient; +use ahnlich_types::shared::cluster::{ClusterInfoQuery, NodeRole}; +use pretty_assertions::assert_eq; +use std::collections::HashMap; +use std::path::PathBuf; +use tempfile::TempDir; +use tokio::task::JoinHandle; +use tokio::time::{Duration, sleep}; +use tonic::Code; +use tonic::transport::Channel; +use utils::server::AhnlichServerUtils; + +struct SpawnedServer { + addr: std::net::SocketAddr, + _data_dir: TempDir, + task: JoinHandle<()>, +} + +impl Drop for SpawnedServer { + fn drop(&mut self) { + self.task.abort(); + } +} + +fn cluster_data_dir(tempdir: &TempDir) -> PathBuf { + let path = tempdir.path().join("raft-data"); + std::fs::create_dir_all(&path).expect("create raft data dir"); + path +} + +fn store_value(label: &str) -> StoreValue { + let mut value = HashMap::new(); + value.insert( + "label".to_owned(), + MetadataValue { + value: Some(MetadataValueEnum::RawString(label.to_owned())), + }, + ); + StoreValue { value } +} + +fn store_entry(label: &str) -> DbStoreEntry { + DbStoreEntry { + key: Some(StoreKey { + key: vec![0.1, 0.2, 0.3], + }), + value: Some(store_value(label)), + } +} + +async fn connect_client(addr: std::net::SocketAddr) -> DbServiceClient { + DbServiceClient::connect(format!("http://{addr}")) + .await + .expect("connect db client") +} + +async fn wait_for_ping(addr: std::net::SocketAddr) { + for _ in 0..50 { + if let Ok(mut client) = DbServiceClient::connect(format!("http://{addr}")).await + && client + .ping(tonic::Request::new(db_query_types::Ping {})) + .await + .is_ok() + { + return; + } + sleep(Duration::from_millis(100)).await; + } + panic!("db server at {addr} did not become ready"); +} + +async fn spawn_clustered_server( + config: ServerConfig, + data_dir: TempDir, +) -> (SpawnedServer, std::net::SocketAddr) { + let server = Server::new(&config) + .await + .expect("create clustered db server"); + let addr = server.local_addr().expect("db local addr"); + let cluster_addr = server.cluster_local_addr().expect("cluster local addr"); + let task = tokio::spawn(async move { + let _ = server.start().await; + }); + wait_for_ping(addr).await; + ( + SpawnedServer { + addr, + _data_dir: data_dir, + task, + }, + cluster_addr, + ) +} + +async fn wait_for_cluster_size(client: &mut DbServiceClient, expected_nodes: usize) { + for _ in 0..50 { + let response = client + .cluster_info(tonic::Request::new(ClusterInfoQuery {})) + .await + .expect("cluster info query should succeed") + .into_inner(); + if response.nodes.len() == expected_nodes { + return; + } + sleep(Duration::from_millis(200)).await; + } + panic!("cluster did not reach {expected_nodes} visible nodes in time"); +} + +async fn wait_for_get_key_count( + client: &mut DbServiceClient, + store: &str, + expected_entries: usize, +) { + for _ in 0..50 { + let response = client + .get_key(tonic::Request::new(db_query_types::GetKey { + store: store.to_owned(), + keys: vec![StoreKey { + key: vec![0.1, 0.2, 0.3], + }], + })) + .await; + + if let Ok(response) = response + && response.into_inner().entries.len() == expected_entries + { + return; + } + + sleep(Duration::from_millis(200)).await; + } + panic!("replicated entry did not appear in follower in time"); +} + +#[tokio::test] +async fn test_single_node_clustered_pipeline_and_cluster_info() { + let data_dir = tempfile::tempdir().expect("tempdir"); + let config = ServerConfig::default() + .os_select_port() + .cluster_bootstrap(std::net::SocketAddr::from(([127, 0, 0, 1], 0))) + .cluster_data_dir(cluster_data_dir(&data_dir)); + + let (server, _) = spawn_clustered_server(config, data_dir).await; + let mut client = connect_client(server.addr).await; + + let pipeline = DbRequestPipeline { + queries: vec![ + DbQuery { + query: Some(Query::CreateStore(db_query_types::CreateStore { + store: "clustered-main".to_owned(), + dimension: 3, + create_predicates: vec![], + non_linear_indices: vec![], + error_if_exists: true, + })), + }, + DbQuery { + query: Some(Query::Set(db_query_types::Set { + store: "clustered-main".to_owned(), + inputs: vec![store_entry("first")], + })), + }, + DbQuery { + query: Some(Query::ListStores(db_query_types::ListStores {})), + }, + DbQuery { + query: Some(Query::ClusterInfo(ClusterInfoQuery {})), + }, + ], + }; + + let response = client + .pipeline(tonic::Request::new(pipeline)) + .await + .expect("clustered pipeline should succeed") + .into_inner(); + + assert_eq!(response.responses.len(), 4); + assert!(matches!( + response.responses[0].response, + Some(Response::Unit(_)) + )); + let set_response = match response.responses[1].response.clone() { + Some(Response::Set(set)) => set, + other => panic!("expected Set response, got {other:?}"), + }; + assert_eq!( + set_response.upsert, + Some(ahnlich_types::shared::info::StoreUpsert { + inserted: 1, + updated: 0, + }) + ); + + let store_list = match response.responses[2].response.clone() { + Some(Response::StoreList(store_list)) => store_list, + other => panic!("expected StoreList response, got {other:?}"), + }; + assert_eq!(store_list.stores.len(), 1); + let info = &store_list.stores[0]; + assert_eq!(info.name, "clustered-main"); + assert_eq!(info.dimension, 3); + assert_eq!(info.len, 1); + assert!(info.size_in_bytes > 0); + assert!(info.non_linear_indices.is_empty()); + assert!(info.predicate_indices.is_empty()); + + let cluster_info = match response.responses[3].response.clone() { + Some(Response::ClusterInfo(cluster_info)) => cluster_info, + other => panic!("expected ClusterInfo response, got {other:?}"), + }; + assert_eq!(cluster_info.nodes.len(), 1); + assert_eq!(cluster_info.nodes[0].role, NodeRole::Leader as i32); +} + +#[tokio::test] +async fn test_three_node_cluster_replication_and_follower_list_stores_error() { + let leader_dir = tempfile::tempdir().expect("leader tempdir"); + let follower_one_dir = tempfile::tempdir().expect("follower one tempdir"); + let follower_two_dir = tempfile::tempdir().expect("follower two tempdir"); + + let (leader, leader_cluster_addr) = spawn_clustered_server( + ServerConfig::default() + .os_select_port() + .cluster_bootstrap(std::net::SocketAddr::from(([127, 0, 0, 1], 0))) + .cluster_data_dir(cluster_data_dir(&leader_dir)), + leader_dir, + ) + .await; + + let (follower_one, _) = spawn_clustered_server( + ServerConfig::default() + .os_select_port() + .cluster_join( + std::net::SocketAddr::from(([127, 0, 0, 1], 0)), + leader_cluster_addr, + ) + .cluster_data_dir(cluster_data_dir(&follower_one_dir)), + follower_one_dir, + ) + .await; + + let (follower_two, _) = spawn_clustered_server( + ServerConfig::default() + .os_select_port() + .cluster_join( + std::net::SocketAddr::from(([127, 0, 0, 1], 0)), + leader_cluster_addr, + ) + .cluster_data_dir(cluster_data_dir(&follower_two_dir)), + follower_two_dir, + ) + .await; + + let mut leader_client = connect_client(leader.addr).await; + let mut follower_one_client = connect_client(follower_one.addr).await; + let mut follower_two_client = connect_client(follower_two.addr).await; + + wait_for_cluster_size(&mut leader_client, 3).await; + + leader_client + .create_store(tonic::Request::new(db_query_types::CreateStore { + store: "replicated-store".to_owned(), + dimension: 3, + create_predicates: vec![], + non_linear_indices: vec![], + error_if_exists: true, + })) + .await + .expect("leader create_store should succeed"); + + leader_client + .set(tonic::Request::new(db_query_types::Set { + store: "replicated-store".to_owned(), + inputs: vec![store_entry("replicated")], + })) + .await + .expect("leader set should succeed"); + + wait_for_get_key_count(&mut follower_one_client, "replicated-store", 1).await; + wait_for_get_key_count(&mut follower_two_client, "replicated-store", 1).await; + + let cluster_info = leader_client + .cluster_info(tonic::Request::new(ClusterInfoQuery {})) + .await + .expect("leader cluster_info should succeed") + .into_inner(); + assert_eq!(cluster_info.nodes.len(), 3); + assert_eq!( + cluster_info + .nodes + .iter() + .filter(|node| node.role == NodeRole::Leader as i32) + .count(), + 1 + ); + + let list_stores_error = follower_one_client + .list_stores(tonic::Request::new(db_query_types::ListStores {})) + .await + .expect_err("follower ListStores should error in M2"); + assert_eq!(list_stores_error.code(), Code::FailedPrecondition); + + let second_list_stores_error = follower_two_client + .list_stores(tonic::Request::new(db_query_types::ListStores {})) + .await + .expect_err("second follower ListStores should error in M2"); + assert_eq!(second_list_stores_error.code(), Code::FailedPrecondition); +} diff --git a/ahnlich/db/src/tests/mod.rs b/ahnlich/db/src/tests/mod.rs index e6e5e2f49..56de61430 100644 --- a/ahnlich/db/src/tests/mod.rs +++ b/ahnlich/db/src/tests/mod.rs @@ -1,4 +1,6 @@ mod auth_tests; +mod cluster_tests; +mod replication_store_tests; mod server_tests; use ahnlich_types::keyval::StoreKey; diff --git a/ahnlich/db/src/tests/replication_store_tests.rs b/ahnlich/db/src/tests/replication_store_tests.rs new file mode 100644 index 000000000..806cdbb2f --- /dev/null +++ b/ahnlich/db/src/tests/replication_store_tests.rs @@ -0,0 +1,185 @@ +use std::collections::{BTreeMap, BTreeSet, HashMap}; +use std::sync::Arc; +use std::sync::atomic::AtomicBool; + +use ahnlich_replication::node::ReplicationNode; +use ahnlich_replication::storage::{ReplicationFailureState, StateMachineStore}; +use ahnlich_replication::types::DbCommand; +use ahnlich_types::db::query; +use ahnlich_types::keyval::{DbStoreEntry, StoreKey, StoreName, StoreValue}; +use ahnlich_types::metadata::MetadataValue; +use futures::executor::block_on; +use openraft::RaftSnapshotBuilder; +use openraft::storage::RaftStateMachine; +use openraft::{CommittedLeaderId, Entry, EntryPayload, LogId, Membership, StoredMembership}; +use prost::Message; + +use crate::replication::{DbStateMachine, DbTypeConfig}; + +fn replication_node(id: u64) -> ReplicationNode { + ReplicationNode { + raft_addr: format!("127.0.0.1:{}", 9_000 + id), + service_addr: format!("127.0.0.1:{}", 8_000 + id), + } +} + +fn membership(voters: &[u64]) -> Membership { + let voter_set = voters.iter().copied().collect::>(); + let nodes = voters + .iter() + .copied() + .map(|id| (id, replication_node(id))) + .collect::>(); + Membership::new(vec![voter_set], nodes) +} + +fn log_id(term: u64, node_id: u64, index: u64) -> LogId { + LogId::new(CommittedLeaderId::new(term, node_id), index) +} + +fn normal_entry(term: u64, node_id: u64, index: u64, data: DbCommand) -> Entry { + Entry { + log_id: log_id(term, node_id, index), + payload: EntryPayload::Normal(data), + } +} + +fn store_value(label: &str) -> StoreValue { + let mut value = HashMap::new(); + value.insert( + "label".to_owned(), + MetadataValue { + value: Some(ahnlich_types::metadata::metadata_value::Value::RawString( + label.to_owned(), + )), + }, + ); + StoreValue { value } +} + +fn create_store_query(store: &str, dimension: u32) -> query::CreateStore { + query::CreateStore { + store: store.to_owned(), + dimension, + create_predicates: Vec::new(), + non_linear_indices: Vec::new(), + error_if_exists: true, + } +} + +fn set_query(store: &str) -> query::Set { + query::Set { + store: store.to_owned(), + inputs: vec![DbStoreEntry { + key: Some(StoreKey { + key: vec![1.0, 2.0], + }), + value: Some(store_value("alpha")), + }], + } +} + +fn drop_store_query(store: &str) -> query::DropStore { + query::DropStore { + store: store.to_owned(), + error_if_not_exists: true, + } +} + +fn encode_command(query: &M, wrap: impl FnOnce(Vec) -> DbCommand) -> DbCommand { + wrap(query.encode_to_vec()) +} + +fn decode_count_response(response: ahnlich_replication::types::DbResponse) -> usize { + match response { + ahnlich_replication::types::DbResponse::Bytes(bytes) => { + bitcode::deserialize(bytes.as_slice()).expect("decode count response") + } + ahnlich_replication::types::DbResponse::Unit => panic!("expected byte response"), + } +} + +#[test] +fn snapshot_round_trip_restores_store_state() { + let failure_state = Arc::new(ReplicationFailureState::default()); + let mut store = StateMachineStore::::new( + DbStateMachine::new(Arc::new(AtomicBool::new(false))), + StoredMembership::new(None, membership(&[1])), + failure_state, + ); + + block_on(store.apply(vec![ + normal_entry( + 1, + 1, + 1, + encode_command(&create_store_query("products", 2), DbCommand::CreateStore), + ), + normal_entry( + 1, + 1, + 2, + encode_command(&set_query("products"), DbCommand::Set), + ), + ])) + .expect("apply should succeed"); + + let mut snapshot_builder = block_on(store.get_snapshot_builder()); + let snapshot = block_on(snapshot_builder.build_snapshot()).expect("build snapshot"); + + let mut target = StateMachineStore::::new( + DbStateMachine::new(Arc::new(AtomicBool::new(false))), + StoredMembership::new(None, membership(&[1])), + Arc::new(ReplicationFailureState::default()), + ); + + let openraft::Snapshot { meta, snapshot } = snapshot; + block_on(target.install_snapshot(&meta, snapshot)).expect("install snapshot"); + let restored_entries = target + .with_handler(|handler| { + handler.store_handler().get_key_in_store( + &StoreName { + value: "products".to_owned(), + }, + vec![StoreKey { key: vec![1.0, 2.0] }], + ) + }) + .expect("state machine access should succeed") + .expect("restored lookup should succeed"); + assert_eq!(restored_entries.len(), 1); + + let response = block_on(target.apply(vec![normal_entry( + 1, + 1, + 3, + encode_command(&drop_store_query("products"), DbCommand::DropStore), + )])) + .expect("restored state should accept drop"); + let deleted_count = decode_count_response(response.into_iter().next().expect("one response")); + assert_eq!(deleted_count, 1); +} + +#[test] +fn apply_operation_failure_marks_replication_failure_state() { + let failure_state = Arc::new(ReplicationFailureState::default()); + let mut store = StateMachineStore::::new( + DbStateMachine::new(Arc::new(AtomicBool::new(false))), + StoredMembership::new(None, membership(&[1])), + failure_state.clone(), + ); + + let err = block_on(store.apply(vec![normal_entry( + 1, + 1, + 1, + encode_command(&set_query("missing-store"), DbCommand::Set), + )])) + .expect_err("apply should fail"); + + assert!(matches!(err, openraft::StorageError::IO { .. })); + assert!(failure_state.failed()); + + let reason = failure_state.reason().expect("failure reason should exist"); + assert!(reason.contains("state machine apply failed")); + assert!(reason.contains("Set apply failed")); +} diff --git a/ahnlich/replication/src/storage/state_machine.rs b/ahnlich/replication/src/storage/state_machine.rs index b8542c831..6ad7b88bf 100644 --- a/ahnlich/replication/src/storage/state_machine.rs +++ b/ahnlich/replication/src/storage/state_machine.rs @@ -142,6 +142,11 @@ impl> StateMachineStore { self.failure_state.clone() } + pub fn with_handler(&self, f: impl FnOnce(&H) -> R) -> Result> { + let inner = self.lock_inner()?; + Ok(f(&inner.handler)) + } + fn lock_inner( &self, ) -> Result>, StorageError> { diff --git a/ahnlich/replication/src/types.rs b/ahnlich/replication/src/types.rs index bf0419794..119dc67d8 100644 --- a/ahnlich/replication/src/types.rs +++ b/ahnlich/replication/src/types.rs @@ -45,21 +45,22 @@ pub enum AiCommand { } /// Successful response from applying a DB Raft command. `Unit` covers -/// mutations whose protocol response is empty (DropStore, DropPredIndex, -/// etc.); `Bytes` carries a protobuf-encoded response message for mutations -/// that produce one (Set's StoreUpsert counts, etc.). Decoding the bytes -/// against the right protobuf type is the gRPC handler's responsibility: -/// it knows what response shape the originating request expects. -#[derive(Debug, Clone, Serialize, Deserialize)] +/// mutations whose protocol response is empty; `Bytes` carries an encoded raw +/// operation result for mutations that return a value. The gRPC handler is +/// responsible for decoding those bytes into the expected operation result +/// type, then shaping them into public response messages. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] pub enum DbResponse { + #[default] Unit, Bytes(Vec), } /// Successful response from applying an AI Raft command. Same shape as /// [`DbResponse`]. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, Serialize, Deserialize)] pub enum AiResponse { + #[default] Unit, Bytes(Vec), } diff --git a/ahnlich/types/build.rs b/ahnlich/types/build.rs index 6a49f6ae2..8c58d2e63 100644 --- a/ahnlich/types/build.rs +++ b/ahnlich/types/build.rs @@ -84,6 +84,10 @@ fn main() -> Result<()> { "keyval.StoreValue", "#[derive(serde::Serialize, serde::Deserialize)]", ) + .type_attribute( + "shared.info.StoreUpsert", + "#[derive(serde::Serialize, serde::Deserialize)]", + ) .type_attribute( "metadata.MetadataValue", "#[derive(PartialOrd, Ord, Hash, Eq)]", diff --git a/ahnlich/types/src/db/pipeline.rs b/ahnlich/types/src/db/pipeline.rs index 8fc6ece59..f98cab8d6 100644 --- a/ahnlich/types/src/db/pipeline.rs +++ b/ahnlich/types/src/db/pipeline.rs @@ -3,7 +3,7 @@ pub struct DbQuery { #[prost( oneof = "db_query::Query", - tags = "1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17" + tags = "1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18" )] pub query: ::core::option::Option, } @@ -45,6 +45,8 @@ pub mod db_query { Ping(super::super::query::Ping), #[prost(message, tag = "17")] GetStore(super::super::query::GetStore), + #[prost(message, tag = "18")] + ClusterInfo(super::super::super::shared::cluster::ClusterInfoQuery), } } #[derive(Clone, PartialEq, ::prost::Message)] @@ -56,7 +58,7 @@ pub struct DbRequestPipeline { pub struct DbServerResponse { #[prost( oneof = "db_server_response::Response", - tags = "1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12" + tags = "1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13" )] pub response: ::core::option::Option, } @@ -88,6 +90,8 @@ pub mod db_server_response { Error(super::super::super::shared::info::ErrorResponse), #[prost(message, tag = "12")] StoreInfo(super::super::server::StoreInfo), + #[prost(message, tag = "13")] + ClusterInfo(super::super::super::shared::cluster::ClusterInfoResponse), } } #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/ahnlich/types/src/services/db_service.rs b/ahnlich/types/src/services/db_service.rs index 5f1f02ab3..c73d1ecf5 100644 --- a/ahnlich/types/src/services/db_service.rs +++ b/ahnlich/types/src/services/db_service.rs @@ -391,6 +391,26 @@ pub mod db_service_client { )); self.inner.unary(req, path, codec).await } + pub async fn cluster_info( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner.ready().await.map_err(|e| { + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = + http::uri::PathAndQuery::from_static("/services.db_service.DBService/ClusterInfo"); + let mut req = request.into_request(); + req.extensions_mut().insert(GrpcMethod::new( + "services.db_service.DBService", + "ClusterInfo", + )); + self.inner.unary(req, path, codec).await + } pub async fn ping( &mut self, request: impl tonic::IntoRequest, @@ -535,6 +555,13 @@ pub mod db_service_server { tonic::Response, tonic::Status, >; + async fn cluster_info( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; async fn ping( &self, request: tonic::Request, @@ -1295,6 +1322,51 @@ pub mod db_service_server { }; Box::pin(fut) } + "/services.db_service.DBService/ClusterInfo" => { + #[allow(non_camel_case_types)] + struct ClusterInfoSvc(pub Arc); + impl + tonic::server::UnaryService< + super::super::super::shared::cluster::ClusterInfoQuery, + > for ClusterInfoSvc + { + type Response = super::super::super::shared::cluster::ClusterInfoResponse; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request< + super::super::super::shared::cluster::ClusterInfoQuery, + >, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::cluster_info(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = ClusterInfoSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } "/services.db_service.DBService/Ping" => { #[allow(non_camel_case_types)] struct PingSvc(pub Arc); diff --git a/ahnlich/types/src/shared/info.rs b/ahnlich/types/src/shared/info.rs index b152b3a47..82e6fd675 100644 --- a/ahnlich/types/src/shared/info.rs +++ b/ahnlich/types/src/shared/info.rs @@ -12,7 +12,7 @@ pub struct ServerInfo { #[prost(uint64, tag = "5")] pub remaining: u64, } -#[derive(Clone, Copy, PartialEq, ::prost::Message)] +#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, PartialEq, ::prost::Message)] pub struct StoreUpsert { #[prost(uint64, tag = "1")] pub inserted: u64, diff --git a/ahnlich/utils/src/server.rs b/ahnlich/utils/src/server.rs index e27f7e1a5..2087a1c4f 100644 --- a/ahnlich/utils/src/server.rs +++ b/ahnlich/utils/src/server.rs @@ -55,6 +55,10 @@ pub trait AhnlichServerUtils: BlockingTask + Sized + Send + Sync + 'static + Deb fn task_manager(&self) -> Arc; + fn should_spawn_persistence(&self) -> bool { + true + } + /// Spawn background tasks before server starts (e.g., model threads, size calculation) /// Returns error to fail fast if critical initialization fails async fn spawn_tasks_before_server(&self, _task_manager: &Arc) -> IoResult<()> { @@ -93,7 +97,9 @@ pub trait AhnlichServerUtils: BlockingTask + Sized + Send + Sync + 'static + Deb // install panic hook task_manager.install_panic_hook(); - if let Some(persist_location) = self.config().persist_location { + if self.should_spawn_persistence() + && let Some(persist_location) = self.config().persist_location + { let persistence_task = Persistence::task( self.write_flag(), self.config().persistence_interval, diff --git a/protos/db/pipeline.proto b/protos/db/pipeline.proto index 1d3e813eb..49bc3a332 100644 --- a/protos/db/pipeline.proto +++ b/protos/db/pipeline.proto @@ -4,6 +4,7 @@ package db.pipeline; import "db/query.proto"; import "db/server.proto"; +import "shared/cluster.proto"; import "shared/info.proto"; option go_package = "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/pipeline;pipeline"; @@ -31,6 +32,7 @@ message DBQuery { db.query.ListClients list_clients = 15; db.query.Ping ping = 16; db.query.GetStore get_store = 17; + shared.cluster.ClusterInfoQuery cluster_info = 18; } } @@ -53,6 +55,7 @@ message DBServerResponse { shared.info.ErrorResponse error = 11; db.server.StoreInfo store_info = 12; + shared.cluster.ClusterInfoResponse cluster_info = 13; } } diff --git a/protos/services/db_service.proto b/protos/services/db_service.proto index 830244898..de83615ac 100644 --- a/protos/services/db_service.proto +++ b/protos/services/db_service.proto @@ -5,6 +5,7 @@ package services.db_service; import "db/pipeline.proto"; import "db/query.proto"; import "db/server.proto"; +import "shared/cluster.proto"; option go_package = "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/services/db_service;db_service"; @@ -34,6 +35,7 @@ service DBService { rpc ListClients(db.query.ListClients) returns (db.server.ClientList); rpc ListStores(db.query.ListStores) returns (db.server.StoreList); rpc InfoServer(db.query.InfoServer) returns (db.server.InfoServer); + rpc ClusterInfo(shared.cluster.ClusterInfoQuery) returns (shared.cluster.ClusterInfoResponse); rpc Ping(db.query.Ping) returns (db.server.Pong); /** Pipeline method for all methods **/ diff --git a/sdk/ahnlich-client-go/grpc/db/pipeline/pipeline.pb.go b/sdk/ahnlich-client-go/grpc/db/pipeline/pipeline.pb.go index b3e096428..10f681e68 100644 --- a/sdk/ahnlich-client-go/grpc/db/pipeline/pipeline.pb.go +++ b/sdk/ahnlich-client-go/grpc/db/pipeline/pipeline.pb.go @@ -15,6 +15,7 @@ import ( query "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" server "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/server" + cluster "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/shared/cluster" info "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/shared/info" ) @@ -49,6 +50,7 @@ type DBQuery struct { // *DBQuery_ListClients // *DBQuery_Ping // *DBQuery_GetStore + // *DBQuery_ClusterInfo Query isDBQuery_Query `protobuf_oneof:"query"` } @@ -210,6 +212,13 @@ func (x *DBQuery) GetGetStore() *query.GetStore { return nil } +func (x *DBQuery) GetClusterInfo() *cluster.ClusterInfoQuery { + if x, ok := x.GetQuery().(*DBQuery_ClusterInfo); ok { + return x.ClusterInfo + } + return nil +} + type isDBQuery_Query interface { isDBQuery_Query() } @@ -282,6 +291,10 @@ type DBQuery_GetStore struct { GetStore *query.GetStore `protobuf:"bytes,17,opt,name=get_store,json=getStore,proto3,oneof"` } +type DBQuery_ClusterInfo struct { + ClusterInfo *cluster.ClusterInfoQuery `protobuf:"bytes,18,opt,name=cluster_info,json=clusterInfo,proto3,oneof"` +} + func (*DBQuery_CreateStore) isDBQuery_Query() {} func (*DBQuery_GetKey) isDBQuery_Query() {} @@ -316,6 +329,8 @@ func (*DBQuery_Ping) isDBQuery_Query() {} func (*DBQuery_GetStore) isDBQuery_Query() {} +func (*DBQuery_ClusterInfo) isDBQuery_Query() {} + type DBRequestPipeline struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -382,6 +397,7 @@ type DBServerResponse struct { // *DBServerResponse_CreateIndex // *DBServerResponse_Error // *DBServerResponse_StoreInfo + // *DBServerResponse_ClusterInfo Response isDBServerResponse_Response `protobuf_oneof:"response"` } @@ -508,6 +524,13 @@ func (x *DBServerResponse) GetStoreInfo() *server.StoreInfo { return nil } +func (x *DBServerResponse) GetClusterInfo() *cluster.ClusterInfoResponse { + if x, ok := x.GetResponse().(*DBServerResponse_ClusterInfo); ok { + return x.ClusterInfo + } + return nil +} + type isDBServerResponse_Response interface { isDBServerResponse_Response() } @@ -560,6 +583,10 @@ type DBServerResponse_StoreInfo struct { StoreInfo *server.StoreInfo `protobuf:"bytes,12,opt,name=store_info,json=storeInfo,proto3,oneof"` } +type DBServerResponse_ClusterInfo struct { + ClusterInfo *cluster.ClusterInfoResponse `protobuf:"bytes,13,opt,name=cluster_info,json=clusterInfo,proto3,oneof"` +} + func (*DBServerResponse_Unit) isDBServerResponse_Response() {} func (*DBServerResponse_Pong) isDBServerResponse_Response() {} @@ -584,6 +611,8 @@ func (*DBServerResponse_Error) isDBServerResponse_Response() {} func (*DBServerResponse_StoreInfo) isDBServerResponse_Response() {} +func (*DBServerResponse_ClusterInfo) isDBServerResponse_Response() {} + type DBResponsePipeline struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -638,125 +667,136 @@ var file_db_pipeline_proto_rawDesc = []byte{ 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x64, 0x62, 0x2e, 0x70, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x1a, 0x0e, 0x64, 0x62, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0f, 0x64, 0x62, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x1a, 0x11, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2f, 0x69, 0x6e, 0x66, 0x6f, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x89, 0x08, 0x0a, 0x07, 0x44, 0x42, 0x51, 0x75, 0x65, 0x72, 0x79, - 0x12, 0x3a, 0x0a, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, - 0x79, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x48, 0x00, 0x52, - 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x2b, 0x0a, 0x07, - 0x67, 0x65, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, - 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x48, - 0x00, 0x52, 0x06, 0x67, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x2e, 0x0a, 0x08, 0x67, 0x65, 0x74, - 0x5f, 0x70, 0x72, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x64, 0x62, - 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x64, 0x48, 0x00, - 0x52, 0x07, 0x67, 0x65, 0x74, 0x50, 0x72, 0x65, 0x64, 0x12, 0x2f, 0x0a, 0x09, 0x67, 0x65, 0x74, - 0x5f, 0x73, 0x69, 0x6d, 0x5f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x64, - 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x69, 0x6d, 0x4e, 0x48, - 0x00, 0x52, 0x07, 0x67, 0x65, 0x74, 0x53, 0x69, 0x6d, 0x4e, 0x12, 0x47, 0x0a, 0x11, 0x63, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x72, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, - 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, - 0x48, 0x00, 0x52, 0x0f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x64, 0x49, 0x6e, - 0x64, 0x65, 0x78, 0x12, 0x73, 0x0a, 0x21, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x6f, - 0x6e, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x61, 0x72, 0x5f, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, - 0x68, 0x6d, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, - 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x4e, 0x6f, 0x6e, 0x4c, 0x69, 0x6e, 0x65, 0x61, 0x72, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, - 0x68, 0x6d, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x48, 0x00, 0x52, 0x1d, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x4e, 0x6f, 0x6e, 0x4c, 0x69, 0x6e, 0x65, 0x61, 0x72, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, - 0x74, 0x68, 0x6d, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x41, 0x0a, 0x0f, 0x64, 0x72, 0x6f, 0x70, - 0x5f, 0x70, 0x72, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x17, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x44, 0x72, 0x6f, - 0x70, 0x50, 0x72, 0x65, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x48, 0x00, 0x52, 0x0d, 0x64, 0x72, - 0x6f, 0x70, 0x50, 0x72, 0x65, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x6d, 0x0a, 0x1f, 0x64, - 0x72, 0x6f, 0x70, 0x5f, 0x6e, 0x6f, 0x6e, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x61, 0x72, 0x5f, 0x61, - 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, - 0x44, 0x72, 0x6f, 0x70, 0x4e, 0x6f, 0x6e, 0x4c, 0x69, 0x6e, 0x65, 0x61, 0x72, 0x41, 0x6c, 0x67, - 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x48, 0x00, 0x52, 0x1b, 0x64, - 0x72, 0x6f, 0x70, 0x4e, 0x6f, 0x6e, 0x4c, 0x69, 0x6e, 0x65, 0x61, 0x72, 0x41, 0x6c, 0x67, 0x6f, - 0x72, 0x69, 0x74, 0x68, 0x6d, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x21, 0x0a, 0x03, 0x73, 0x65, - 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, - 0x72, 0x79, 0x2e, 0x53, 0x65, 0x74, 0x48, 0x00, 0x52, 0x03, 0x73, 0x65, 0x74, 0x12, 0x2b, 0x0a, - 0x07, 0x64, 0x65, 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, - 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x4b, 0x65, 0x79, - 0x48, 0x00, 0x52, 0x06, 0x64, 0x65, 0x6c, 0x4b, 0x65, 0x79, 0x12, 0x2e, 0x0a, 0x08, 0x64, 0x65, - 0x6c, 0x5f, 0x70, 0x72, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x64, - 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x50, 0x72, 0x65, 0x64, 0x48, - 0x00, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x50, 0x72, 0x65, 0x64, 0x12, 0x34, 0x0a, 0x0a, 0x64, 0x72, - 0x6f, 0x70, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, - 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x44, 0x72, 0x6f, 0x70, 0x53, 0x74, - 0x6f, 0x72, 0x65, 0x48, 0x00, 0x52, 0x09, 0x64, 0x72, 0x6f, 0x70, 0x53, 0x74, 0x6f, 0x72, 0x65, - 0x12, 0x37, 0x0a, 0x0b, 0x69, 0x6e, 0x66, 0x6f, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, - 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, - 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x48, 0x00, 0x52, 0x0a, 0x69, - 0x6e, 0x66, 0x6f, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x37, 0x0a, 0x0b, 0x6c, 0x69, 0x73, - 0x74, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, - 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, - 0x6f, 0x72, 0x65, 0x73, 0x48, 0x00, 0x52, 0x0a, 0x6c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x72, - 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0c, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, - 0x74, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, - 0x65, 0x72, 0x79, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x48, - 0x00, 0x52, 0x0b, 0x6c, 0x69, 0x73, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x24, - 0x0a, 0x04, 0x70, 0x69, 0x6e, 0x67, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x64, - 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x48, 0x00, 0x52, 0x04, - 0x70, 0x69, 0x6e, 0x67, 0x12, 0x31, 0x0a, 0x09, 0x67, 0x65, 0x74, 0x5f, 0x73, 0x74, 0x6f, 0x72, - 0x65, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, - 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x48, 0x00, 0x52, 0x08, 0x67, - 0x65, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x42, 0x07, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, - 0x22, 0x43, 0x0a, 0x11, 0x44, 0x42, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x50, 0x69, 0x70, - 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x64, 0x62, 0x2e, 0x70, 0x69, 0x70, 0x65, - 0x6c, 0x69, 0x6e, 0x65, 0x2e, 0x44, 0x42, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x07, 0x71, 0x75, - 0x65, 0x72, 0x69, 0x65, 0x73, 0x22, 0xdd, 0x04, 0x0a, 0x10, 0x44, 0x42, 0x53, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x04, 0x75, 0x6e, - 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x2e, 0x55, 0x6e, 0x69, 0x74, 0x48, 0x00, 0x52, 0x04, 0x75, 0x6e, 0x69, - 0x74, 0x12, 0x25, 0x0a, 0x04, 0x70, 0x6f, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x0f, 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x50, 0x6f, 0x6e, 0x67, - 0x48, 0x00, 0x52, 0x04, 0x70, 0x6f, 0x6e, 0x67, 0x12, 0x38, 0x0a, 0x0b, 0x63, 0x6c, 0x69, 0x65, - 0x6e, 0x74, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, - 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, - 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0a, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4c, 0x69, - 0x73, 0x74, 0x12, 0x35, 0x0a, 0x0a, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x6c, 0x69, 0x73, 0x74, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x09, - 0x73, 0x74, 0x6f, 0x72, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x0b, 0x69, 0x6e, 0x66, - 0x6f, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, - 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x53, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x48, 0x00, 0x52, 0x0a, 0x69, 0x6e, 0x66, 0x6f, 0x53, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x03, 0x73, 0x65, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x0e, 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x74, - 0x48, 0x00, 0x52, 0x03, 0x73, 0x65, 0x74, 0x12, 0x22, 0x0a, 0x03, 0x67, 0x65, 0x74, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x2e, 0x47, 0x65, 0x74, 0x48, 0x00, 0x52, 0x03, 0x67, 0x65, 0x74, 0x12, 0x30, 0x0a, 0x09, 0x67, - 0x65, 0x74, 0x5f, 0x73, 0x69, 0x6d, 0x5f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, - 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x69, - 0x6d, 0x4e, 0x48, 0x00, 0x52, 0x07, 0x67, 0x65, 0x74, 0x53, 0x69, 0x6d, 0x4e, 0x12, 0x22, 0x0a, - 0x03, 0x64, 0x65, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x64, 0x62, 0x2e, - 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x44, 0x65, 0x6c, 0x48, 0x00, 0x52, 0x03, 0x64, 0x65, - 0x6c, 0x12, 0x3b, 0x0a, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, - 0x78, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x48, - 0x00, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x32, - 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x69, 0x6e, 0x66, 0x6f, 0x2e, 0x45, 0x72, 0x72, 0x6f, - 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x12, 0x35, 0x0a, 0x0a, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x69, 0x6e, 0x66, 0x6f, - 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x48, 0x00, 0x52, 0x09, - 0x73, 0x74, 0x6f, 0x72, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x0a, 0x0a, 0x08, 0x72, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x51, 0x0a, 0x12, 0x44, 0x42, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x50, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x3b, 0x0a, 0x09, 0x72, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, - 0x2e, 0x64, 0x62, 0x2e, 0x70, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x2e, 0x44, 0x42, 0x53, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x09, 0x72, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x42, 0x4c, 0x5a, 0x4a, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x64, 0x65, 0x76, 0x65, 0x6e, 0x39, 0x36, 0x2f, 0x61, - 0x68, 0x6e, 0x6c, 0x69, 0x63, 0x68, 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x61, 0x68, 0x6e, 0x6c, 0x69, - 0x63, 0x68, 0x2d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2d, 0x67, 0x6f, 0x2f, 0x67, 0x72, 0x70, - 0x63, 0x2f, 0x64, 0x62, 0x2f, 0x70, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x3b, 0x70, 0x69, - 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6f, 0x1a, 0x14, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, + 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x11, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2f, + 0x69, 0x6e, 0x66, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd0, 0x08, 0x0a, 0x07, 0x44, + 0x42, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x3a, 0x0a, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x64, + 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x74, + 0x6f, 0x72, 0x65, 0x48, 0x00, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x74, 0x6f, + 0x72, 0x65, 0x12, 0x2b, 0x0a, 0x07, 0x67, 0x65, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x47, + 0x65, 0x74, 0x4b, 0x65, 0x79, 0x48, 0x00, 0x52, 0x06, 0x67, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x12, + 0x2e, 0x0a, 0x08, 0x67, 0x65, 0x74, 0x5f, 0x70, 0x72, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x11, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, + 0x50, 0x72, 0x65, 0x64, 0x48, 0x00, 0x52, 0x07, 0x67, 0x65, 0x74, 0x50, 0x72, 0x65, 0x64, 0x12, + 0x2f, 0x0a, 0x09, 0x67, 0x65, 0x74, 0x5f, 0x73, 0x69, 0x6d, 0x5f, 0x6e, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x47, 0x65, + 0x74, 0x53, 0x69, 0x6d, 0x4e, 0x48, 0x00, 0x52, 0x07, 0x67, 0x65, 0x74, 0x53, 0x69, 0x6d, 0x4e, + 0x12, 0x47, 0x0a, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x72, 0x65, 0x64, 0x5f, + 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x64, 0x62, + 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, + 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x48, 0x00, 0x52, 0x0f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x50, 0x72, 0x65, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x73, 0x0a, 0x21, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x6f, 0x6e, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x61, 0x72, 0x5f, 0x61, + 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x6e, 0x4c, 0x69, 0x6e, 0x65, 0x61, 0x72, 0x41, + 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x48, 0x00, 0x52, + 0x1d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x6e, 0x4c, 0x69, 0x6e, 0x65, 0x61, 0x72, + 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x41, + 0x0a, 0x0f, 0x64, 0x72, 0x6f, 0x70, 0x5f, 0x70, 0x72, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, + 0x78, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, + 0x72, 0x79, 0x2e, 0x44, 0x72, 0x6f, 0x70, 0x50, 0x72, 0x65, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, + 0x48, 0x00, 0x52, 0x0d, 0x64, 0x72, 0x6f, 0x70, 0x50, 0x72, 0x65, 0x64, 0x49, 0x6e, 0x64, 0x65, + 0x78, 0x12, 0x6d, 0x0a, 0x1f, 0x64, 0x72, 0x6f, 0x70, 0x5f, 0x6e, 0x6f, 0x6e, 0x5f, 0x6c, 0x69, + 0x6e, 0x65, 0x61, 0x72, 0x5f, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x5f, 0x69, + 0x6e, 0x64, 0x65, 0x78, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x64, 0x62, 0x2e, + 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x44, 0x72, 0x6f, 0x70, 0x4e, 0x6f, 0x6e, 0x4c, 0x69, 0x6e, + 0x65, 0x61, 0x72, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x49, 0x6e, 0x64, 0x65, + 0x78, 0x48, 0x00, 0x52, 0x1b, 0x64, 0x72, 0x6f, 0x70, 0x4e, 0x6f, 0x6e, 0x4c, 0x69, 0x6e, 0x65, + 0x61, 0x72, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x49, 0x6e, 0x64, 0x65, 0x78, + 0x12, 0x21, 0x0a, 0x03, 0x73, 0x65, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, + 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x53, 0x65, 0x74, 0x48, 0x00, 0x52, 0x03, + 0x73, 0x65, 0x74, 0x12, 0x2b, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, + 0x44, 0x65, 0x6c, 0x4b, 0x65, 0x79, 0x48, 0x00, 0x52, 0x06, 0x64, 0x65, 0x6c, 0x4b, 0x65, 0x79, + 0x12, 0x2e, 0x0a, 0x08, 0x64, 0x65, 0x6c, 0x5f, 0x70, 0x72, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x44, 0x65, + 0x6c, 0x50, 0x72, 0x65, 0x64, 0x48, 0x00, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x50, 0x72, 0x65, 0x64, + 0x12, 0x34, 0x0a, 0x0a, 0x64, 0x72, 0x6f, 0x70, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x0c, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, + 0x44, 0x72, 0x6f, 0x70, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x48, 0x00, 0x52, 0x09, 0x64, 0x72, 0x6f, + 0x70, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x37, 0x0a, 0x0b, 0x69, 0x6e, 0x66, 0x6f, 0x5f, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x64, 0x62, + 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x53, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x48, 0x00, 0x52, 0x0a, 0x69, 0x6e, 0x66, 0x6f, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, + 0x37, 0x0a, 0x0b, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x18, 0x0e, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x48, 0x00, 0x52, 0x0a, 0x6c, 0x69, + 0x73, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0c, 0x6c, 0x69, 0x73, 0x74, + 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, + 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x73, 0x48, 0x00, 0x52, 0x0b, 0x6c, 0x69, 0x73, 0x74, 0x43, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x73, 0x12, 0x24, 0x0a, 0x04, 0x70, 0x69, 0x6e, 0x67, 0x18, 0x10, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x50, 0x69, + 0x6e, 0x67, 0x48, 0x00, 0x52, 0x04, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x31, 0x0a, 0x09, 0x67, 0x65, + 0x74, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, + 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x6f, 0x72, + 0x65, 0x48, 0x00, 0x52, 0x08, 0x67, 0x65, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x45, 0x0a, + 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x12, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x63, 0x6c, 0x75, + 0x73, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, + 0x51, 0x75, 0x65, 0x72, 0x79, 0x48, 0x00, 0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, + 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x07, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x43, 0x0a, + 0x11, 0x44, 0x42, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x50, 0x69, 0x70, 0x65, 0x6c, 0x69, + 0x6e, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x64, 0x62, 0x2e, 0x70, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, + 0x65, 0x2e, 0x44, 0x42, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x07, 0x71, 0x75, 0x65, 0x72, 0x69, + 0x65, 0x73, 0x22, 0xa7, 0x05, 0x0a, 0x10, 0x44, 0x42, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x04, 0x75, 0x6e, 0x69, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x2e, 0x55, 0x6e, 0x69, 0x74, 0x48, 0x00, 0x52, 0x04, 0x75, 0x6e, 0x69, 0x74, 0x12, 0x25, + 0x0a, 0x04, 0x70, 0x6f, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x64, + 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x50, 0x6f, 0x6e, 0x67, 0x48, 0x00, 0x52, + 0x04, 0x70, 0x6f, 0x6e, 0x67, 0x12, 0x38, 0x0a, 0x0b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, + 0x6c, 0x69, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x64, 0x62, 0x2e, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4c, 0x69, 0x73, + 0x74, 0x48, 0x00, 0x52, 0x0a, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x12, + 0x35, 0x0a, 0x0a, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, + 0x53, 0x74, 0x6f, 0x72, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x09, 0x73, 0x74, 0x6f, + 0x72, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x0b, 0x69, 0x6e, 0x66, 0x6f, 0x5f, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x64, 0x62, + 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x48, 0x00, 0x52, 0x0a, 0x69, 0x6e, 0x66, 0x6f, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x12, 0x22, 0x0a, 0x03, 0x73, 0x65, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, + 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x74, 0x48, 0x00, 0x52, + 0x03, 0x73, 0x65, 0x74, 0x12, 0x22, 0x0a, 0x03, 0x67, 0x65, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x0e, 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x47, 0x65, + 0x74, 0x48, 0x00, 0x52, 0x03, 0x67, 0x65, 0x74, 0x12, 0x30, 0x0a, 0x09, 0x67, 0x65, 0x74, 0x5f, + 0x73, 0x69, 0x6d, 0x5f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x64, 0x62, + 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x69, 0x6d, 0x4e, 0x48, + 0x00, 0x52, 0x07, 0x67, 0x65, 0x74, 0x53, 0x69, 0x6d, 0x4e, 0x12, 0x22, 0x0a, 0x03, 0x64, 0x65, + 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x2e, 0x44, 0x65, 0x6c, 0x48, 0x00, 0x52, 0x03, 0x64, 0x65, 0x6c, 0x12, 0x3b, + 0x0a, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x48, 0x00, 0x52, 0x0b, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x32, 0x0a, 0x05, 0x65, + 0x72, 0x72, 0x6f, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x73, 0x68, 0x61, + 0x72, 0x65, 0x64, 0x2e, 0x69, 0x6e, 0x66, 0x6f, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, + 0x35, 0x0a, 0x0a, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x0c, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, + 0x53, 0x74, 0x6f, 0x72, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x48, 0x00, 0x52, 0x09, 0x73, 0x74, 0x6f, + 0x72, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x48, 0x0a, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, + 0x72, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x73, + 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x6c, + 0x75, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x48, 0x00, 0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, + 0x42, 0x0a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x51, 0x0a, 0x12, + 0x44, 0x42, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x50, 0x69, 0x70, 0x65, 0x6c, 0x69, + 0x6e, 0x65, 0x12, 0x3b, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x64, 0x62, 0x2e, 0x70, 0x69, 0x70, 0x65, 0x6c, + 0x69, 0x6e, 0x65, 0x2e, 0x44, 0x42, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x42, + 0x4c, 0x5a, 0x4a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x64, 0x65, + 0x76, 0x65, 0x6e, 0x39, 0x36, 0x2f, 0x61, 0x68, 0x6e, 0x6c, 0x69, 0x63, 0x68, 0x2f, 0x73, 0x64, + 0x6b, 0x2f, 0x61, 0x68, 0x6e, 0x6c, 0x69, 0x63, 0x68, 0x2d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x2d, 0x67, 0x6f, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x64, 0x62, 0x2f, 0x70, 0x69, 0x70, 0x65, + 0x6c, 0x69, 0x6e, 0x65, 0x3b, 0x70, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -794,18 +834,20 @@ var file_db_pipeline_proto_goTypes = []any{ (*query.ListClients)(nil), // 18: db.query.ListClients (*query.Ping)(nil), // 19: db.query.Ping (*query.GetStore)(nil), // 20: db.query.GetStore - (*server.Unit)(nil), // 21: db.server.Unit - (*server.Pong)(nil), // 22: db.server.Pong - (*server.ClientList)(nil), // 23: db.server.ClientList - (*server.StoreList)(nil), // 24: db.server.StoreList - (*server.InfoServer)(nil), // 25: db.server.InfoServer - (*server.Set)(nil), // 26: db.server.Set - (*server.Get)(nil), // 27: db.server.Get - (*server.GetSimN)(nil), // 28: db.server.GetSimN - (*server.Del)(nil), // 29: db.server.Del - (*server.CreateIndex)(nil), // 30: db.server.CreateIndex - (*info.ErrorResponse)(nil), // 31: shared.info.ErrorResponse - (*server.StoreInfo)(nil), // 32: db.server.StoreInfo + (*cluster.ClusterInfoQuery)(nil), // 21: shared.cluster.ClusterInfoQuery + (*server.Unit)(nil), // 22: db.server.Unit + (*server.Pong)(nil), // 23: db.server.Pong + (*server.ClientList)(nil), // 24: db.server.ClientList + (*server.StoreList)(nil), // 25: db.server.StoreList + (*server.InfoServer)(nil), // 26: db.server.InfoServer + (*server.Set)(nil), // 27: db.server.Set + (*server.Get)(nil), // 28: db.server.Get + (*server.GetSimN)(nil), // 29: db.server.GetSimN + (*server.Del)(nil), // 30: db.server.Del + (*server.CreateIndex)(nil), // 31: db.server.CreateIndex + (*info.ErrorResponse)(nil), // 32: shared.info.ErrorResponse + (*server.StoreInfo)(nil), // 33: db.server.StoreInfo + (*cluster.ClusterInfoResponse)(nil), // 34: shared.cluster.ClusterInfoResponse } var file_db_pipeline_proto_depIdxs = []int32{ 4, // 0: db.pipeline.DBQuery.create_store:type_name -> db.query.CreateStore @@ -825,25 +867,27 @@ var file_db_pipeline_proto_depIdxs = []int32{ 18, // 14: db.pipeline.DBQuery.list_clients:type_name -> db.query.ListClients 19, // 15: db.pipeline.DBQuery.ping:type_name -> db.query.Ping 20, // 16: db.pipeline.DBQuery.get_store:type_name -> db.query.GetStore - 0, // 17: db.pipeline.DBRequestPipeline.queries:type_name -> db.pipeline.DBQuery - 21, // 18: db.pipeline.DBServerResponse.unit:type_name -> db.server.Unit - 22, // 19: db.pipeline.DBServerResponse.pong:type_name -> db.server.Pong - 23, // 20: db.pipeline.DBServerResponse.client_list:type_name -> db.server.ClientList - 24, // 21: db.pipeline.DBServerResponse.store_list:type_name -> db.server.StoreList - 25, // 22: db.pipeline.DBServerResponse.info_server:type_name -> db.server.InfoServer - 26, // 23: db.pipeline.DBServerResponse.set:type_name -> db.server.Set - 27, // 24: db.pipeline.DBServerResponse.get:type_name -> db.server.Get - 28, // 25: db.pipeline.DBServerResponse.get_sim_n:type_name -> db.server.GetSimN - 29, // 26: db.pipeline.DBServerResponse.del:type_name -> db.server.Del - 30, // 27: db.pipeline.DBServerResponse.create_index:type_name -> db.server.CreateIndex - 31, // 28: db.pipeline.DBServerResponse.error:type_name -> shared.info.ErrorResponse - 32, // 29: db.pipeline.DBServerResponse.store_info:type_name -> db.server.StoreInfo - 2, // 30: db.pipeline.DBResponsePipeline.responses:type_name -> db.pipeline.DBServerResponse - 31, // [31:31] is the sub-list for method output_type - 31, // [31:31] is the sub-list for method input_type - 31, // [31:31] is the sub-list for extension type_name - 31, // [31:31] is the sub-list for extension extendee - 0, // [0:31] is the sub-list for field type_name + 21, // 17: db.pipeline.DBQuery.cluster_info:type_name -> shared.cluster.ClusterInfoQuery + 0, // 18: db.pipeline.DBRequestPipeline.queries:type_name -> db.pipeline.DBQuery + 22, // 19: db.pipeline.DBServerResponse.unit:type_name -> db.server.Unit + 23, // 20: db.pipeline.DBServerResponse.pong:type_name -> db.server.Pong + 24, // 21: db.pipeline.DBServerResponse.client_list:type_name -> db.server.ClientList + 25, // 22: db.pipeline.DBServerResponse.store_list:type_name -> db.server.StoreList + 26, // 23: db.pipeline.DBServerResponse.info_server:type_name -> db.server.InfoServer + 27, // 24: db.pipeline.DBServerResponse.set:type_name -> db.server.Set + 28, // 25: db.pipeline.DBServerResponse.get:type_name -> db.server.Get + 29, // 26: db.pipeline.DBServerResponse.get_sim_n:type_name -> db.server.GetSimN + 30, // 27: db.pipeline.DBServerResponse.del:type_name -> db.server.Del + 31, // 28: db.pipeline.DBServerResponse.create_index:type_name -> db.server.CreateIndex + 32, // 29: db.pipeline.DBServerResponse.error:type_name -> shared.info.ErrorResponse + 33, // 30: db.pipeline.DBServerResponse.store_info:type_name -> db.server.StoreInfo + 34, // 31: db.pipeline.DBServerResponse.cluster_info:type_name -> shared.cluster.ClusterInfoResponse + 2, // 32: db.pipeline.DBResponsePipeline.responses:type_name -> db.pipeline.DBServerResponse + 33, // [33:33] is the sub-list for method output_type + 33, // [33:33] is the sub-list for method input_type + 33, // [33:33] is the sub-list for extension type_name + 33, // [33:33] is the sub-list for extension extendee + 0, // [0:33] is the sub-list for field type_name } func init() { file_db_pipeline_proto_init() } @@ -919,6 +963,7 @@ func file_db_pipeline_proto_init() { (*DBQuery_ListClients)(nil), (*DBQuery_Ping)(nil), (*DBQuery_GetStore)(nil), + (*DBQuery_ClusterInfo)(nil), } file_db_pipeline_proto_msgTypes[2].OneofWrappers = []any{ (*DBServerResponse_Unit)(nil), @@ -933,6 +978,7 @@ func file_db_pipeline_proto_init() { (*DBServerResponse_CreateIndex)(nil), (*DBServerResponse_Error)(nil), (*DBServerResponse_StoreInfo)(nil), + (*DBServerResponse_ClusterInfo)(nil), } type x struct{} out := protoimpl.TypeBuilder{ diff --git a/sdk/ahnlich-client-go/grpc/services/db_service/db_service.pb.go b/sdk/ahnlich-client-go/grpc/services/db_service/db_service.pb.go index 7b02e59dd..1248b6836 100644 --- a/sdk/ahnlich-client-go/grpc/services/db_service/db_service.pb.go +++ b/sdk/ahnlich-client-go/grpc/services/db_service/db_service.pb.go @@ -15,6 +15,7 @@ import ( pipeline "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/pipeline" query "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" server "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/server" + cluster "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/shared/cluster" ) const ( @@ -33,78 +34,85 @@ var file_services_db_service_proto_rawDesc = []byte{ 0x1a, 0x11, 0x64, 0x62, 0x2f, 0x70, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0e, 0x64, 0x62, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0f, 0x64, 0x62, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x96, 0x08, 0x0a, 0x09, 0x44, 0x42, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x12, 0x35, 0x0a, 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x74, 0x6f, 0x72, - 0x65, 0x12, 0x15, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x1a, 0x0f, 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x2e, 0x55, 0x6e, 0x69, 0x74, 0x12, 0x44, 0x0a, 0x0f, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x19, 0x2e, 0x64, - 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, - 0x65, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x1a, 0x16, 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, - 0x60, 0x0a, 0x1d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x6e, 0x4c, 0x69, 0x6e, 0x65, - 0x61, 0x72, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x49, 0x6e, 0x64, 0x65, 0x78, - 0x12, 0x27, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x4e, 0x6f, 0x6e, 0x4c, 0x69, 0x6e, 0x65, 0x61, 0x72, 0x41, 0x6c, 0x67, 0x6f, 0x72, - 0x69, 0x74, 0x68, 0x6d, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x1a, 0x16, 0x2e, 0x64, 0x62, 0x2e, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x64, 0x65, - 0x78, 0x12, 0x2a, 0x0a, 0x06, 0x47, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x10, 0x2e, 0x64, 0x62, - 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x1a, 0x0e, 0x2e, - 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x12, 0x2c, 0x0a, - 0x07, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x64, 0x12, 0x11, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, - 0x65, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x64, 0x1a, 0x0e, 0x2e, 0x64, 0x62, - 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x12, 0x30, 0x0a, 0x07, 0x47, - 0x65, 0x74, 0x53, 0x69, 0x6d, 0x4e, 0x12, 0x11, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, - 0x79, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x69, 0x6d, 0x4e, 0x1a, 0x12, 0x2e, 0x64, 0x62, 0x2e, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x69, 0x6d, 0x4e, 0x12, 0x34, 0x0a, - 0x08, 0x47, 0x65, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x12, 0x2e, 0x64, 0x62, 0x2e, 0x71, - 0x75, 0x65, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x1a, 0x14, 0x2e, - 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x49, - 0x6e, 0x66, 0x6f, 0x12, 0x24, 0x0a, 0x03, 0x53, 0x65, 0x74, 0x12, 0x0d, 0x2e, 0x64, 0x62, 0x2e, - 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x53, 0x65, 0x74, 0x1a, 0x0e, 0x2e, 0x64, 0x62, 0x2e, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x74, 0x12, 0x38, 0x0a, 0x0d, 0x44, 0x72, 0x6f, - 0x70, 0x50, 0x72, 0x65, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x17, 0x2e, 0x64, 0x62, 0x2e, - 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x44, 0x72, 0x6f, 0x70, 0x50, 0x72, 0x65, 0x64, 0x49, 0x6e, - 0x64, 0x65, 0x78, 0x1a, 0x0e, 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, - 0x44, 0x65, 0x6c, 0x12, 0x54, 0x0a, 0x1b, 0x44, 0x72, 0x6f, 0x70, 0x4e, 0x6f, 0x6e, 0x4c, 0x69, - 0x6e, 0x65, 0x61, 0x72, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x49, 0x6e, 0x64, - 0x65, 0x78, 0x12, 0x25, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x44, 0x72, - 0x6f, 0x70, 0x4e, 0x6f, 0x6e, 0x4c, 0x69, 0x6e, 0x65, 0x61, 0x72, 0x41, 0x6c, 0x67, 0x6f, 0x72, - 0x69, 0x74, 0x68, 0x6d, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x1a, 0x0e, 0x2e, 0x64, 0x62, 0x2e, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x44, 0x65, 0x6c, 0x12, 0x2a, 0x0a, 0x06, 0x44, 0x65, 0x6c, - 0x4b, 0x65, 0x79, 0x12, 0x10, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x44, - 0x65, 0x6c, 0x4b, 0x65, 0x79, 0x1a, 0x0e, 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x2e, 0x44, 0x65, 0x6c, 0x12, 0x2c, 0x0a, 0x07, 0x44, 0x65, 0x6c, 0x50, 0x72, 0x65, 0x64, - 0x12, 0x11, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x50, - 0x72, 0x65, 0x64, 0x1a, 0x0e, 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, - 0x44, 0x65, 0x6c, 0x12, 0x30, 0x0a, 0x09, 0x44, 0x72, 0x6f, 0x70, 0x53, 0x74, 0x6f, 0x72, 0x65, - 0x12, 0x13, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x44, 0x72, 0x6f, 0x70, - 0x53, 0x74, 0x6f, 0x72, 0x65, 0x1a, 0x0e, 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x2e, 0x44, 0x65, 0x6c, 0x12, 0x3b, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6c, 0x69, - 0x65, 0x6e, 0x74, 0x73, 0x12, 0x15, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x1a, 0x15, 0x2e, 0x64, 0x62, - 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4c, 0x69, - 0x73, 0x74, 0x12, 0x38, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x73, - 0x12, 0x14, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x53, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x1a, 0x14, 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x39, 0x0a, 0x0a, - 0x49, 0x6e, 0x66, 0x6f, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x14, 0x2e, 0x64, 0x62, 0x2e, - 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x1a, 0x15, 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x49, 0x6e, 0x66, - 0x6f, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x27, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, - 0x0e, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x1a, - 0x0f, 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x50, 0x6f, 0x6e, 0x67, - 0x12, 0x4b, 0x0a, 0x08, 0x50, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x1e, 0x2e, 0x64, - 0x62, 0x2e, 0x70, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x2e, 0x44, 0x42, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x50, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x1a, 0x1f, 0x2e, 0x64, - 0x62, 0x2e, 0x70, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x2e, 0x44, 0x42, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x50, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x42, 0x56, 0x5a, - 0x54, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x64, 0x65, 0x76, 0x65, - 0x6e, 0x39, 0x36, 0x2f, 0x61, 0x68, 0x6e, 0x6c, 0x69, 0x63, 0x68, 0x2f, 0x73, 0x64, 0x6b, 0x2f, - 0x61, 0x68, 0x6e, 0x6c, 0x69, 0x63, 0x68, 0x2d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2d, 0x67, - 0x6f, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, - 0x64, 0x62, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x3b, 0x64, 0x62, 0x5f, 0x73, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x14, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2f, 0x63, 0x6c, 0x75, + 0x73, 0x74, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0xec, 0x08, 0x0a, 0x09, 0x44, + 0x42, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x35, 0x0a, 0x0b, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x15, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, + 0x72, 0x79, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x1a, 0x0f, + 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x55, 0x6e, 0x69, 0x74, 0x12, + 0x44, 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x64, 0x49, 0x6e, 0x64, + 0x65, 0x78, 0x12, 0x19, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x1a, 0x16, 0x2e, + 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x60, 0x0a, 0x1d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, + 0x6f, 0x6e, 0x4c, 0x69, 0x6e, 0x65, 0x61, 0x72, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, + 0x6d, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x27, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, + 0x79, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x6e, 0x4c, 0x69, 0x6e, 0x65, 0x61, + 0x72, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x1a, + 0x16, 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x2a, 0x0a, 0x06, 0x47, 0x65, 0x74, 0x4b, 0x65, + 0x79, 0x12, 0x10, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, + 0x4b, 0x65, 0x79, 0x1a, 0x0e, 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, + 0x47, 0x65, 0x74, 0x12, 0x2c, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, 0x64, 0x12, 0x11, + 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x65, + 0x64, 0x1a, 0x0e, 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x47, 0x65, + 0x74, 0x12, 0x30, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x53, 0x69, 0x6d, 0x4e, 0x12, 0x11, 0x2e, 0x64, + 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x69, 0x6d, 0x4e, 0x1a, + 0x12, 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x53, + 0x69, 0x6d, 0x4e, 0x12, 0x34, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, + 0x12, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, + 0x6f, 0x72, 0x65, 0x1a, 0x14, 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, + 0x53, 0x74, 0x6f, 0x72, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x24, 0x0a, 0x03, 0x53, 0x65, 0x74, + 0x12, 0x0d, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x53, 0x65, 0x74, 0x1a, + 0x0e, 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x74, 0x12, + 0x38, 0x0a, 0x0d, 0x44, 0x72, 0x6f, 0x70, 0x50, 0x72, 0x65, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, + 0x12, 0x17, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x44, 0x72, 0x6f, 0x70, + 0x50, 0x72, 0x65, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x1a, 0x0e, 0x2e, 0x64, 0x62, 0x2e, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x44, 0x65, 0x6c, 0x12, 0x54, 0x0a, 0x1b, 0x44, 0x72, 0x6f, + 0x70, 0x4e, 0x6f, 0x6e, 0x4c, 0x69, 0x6e, 0x65, 0x61, 0x72, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, + 0x74, 0x68, 0x6d, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x25, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, + 0x65, 0x72, 0x79, 0x2e, 0x44, 0x72, 0x6f, 0x70, 0x4e, 0x6f, 0x6e, 0x4c, 0x69, 0x6e, 0x65, 0x61, + 0x72, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x1a, + 0x0e, 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x44, 0x65, 0x6c, 0x12, + 0x2a, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x4b, 0x65, 0x79, 0x12, 0x10, 0x2e, 0x64, 0x62, 0x2e, 0x71, + 0x75, 0x65, 0x72, 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x4b, 0x65, 0x79, 0x1a, 0x0e, 0x2e, 0x64, 0x62, + 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x44, 0x65, 0x6c, 0x12, 0x2c, 0x0a, 0x07, 0x44, + 0x65, 0x6c, 0x50, 0x72, 0x65, 0x64, 0x12, 0x11, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, + 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x50, 0x72, 0x65, 0x64, 0x1a, 0x0e, 0x2e, 0x64, 0x62, 0x2e, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x44, 0x65, 0x6c, 0x12, 0x30, 0x0a, 0x09, 0x44, 0x72, 0x6f, + 0x70, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x13, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, + 0x79, 0x2e, 0x44, 0x72, 0x6f, 0x70, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x1a, 0x0e, 0x2e, 0x64, 0x62, + 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x44, 0x65, 0x6c, 0x12, 0x3b, 0x0a, 0x0b, 0x4c, + 0x69, 0x73, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x15, 0x2e, 0x64, 0x62, 0x2e, + 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x73, 0x1a, 0x15, 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x43, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, + 0x53, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x12, 0x14, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, + 0x79, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x1a, 0x14, 0x2e, 0x64, + 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x4c, 0x69, + 0x73, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x49, 0x6e, 0x66, 0x6f, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x12, 0x14, 0x2e, 0x64, 0x62, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x66, 0x6f, + 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x1a, 0x15, 0x2e, 0x64, 0x62, 0x2e, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x54, 0x0a, + 0x0b, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x20, 0x2e, 0x73, + 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x6c, + 0x75, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x51, 0x75, 0x65, 0x72, 0x79, 0x1a, 0x23, + 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, + 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x0e, 0x2e, 0x64, 0x62, + 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x1a, 0x0f, 0x2e, 0x64, 0x62, + 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x50, 0x6f, 0x6e, 0x67, 0x12, 0x4b, 0x0a, 0x08, + 0x50, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x1e, 0x2e, 0x64, 0x62, 0x2e, 0x70, 0x69, + 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x2e, 0x44, 0x42, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x50, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x1a, 0x1f, 0x2e, 0x64, 0x62, 0x2e, 0x70, 0x69, + 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x2e, 0x44, 0x42, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x50, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x42, 0x56, 0x5a, 0x54, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x64, 0x65, 0x76, 0x65, 0x6e, 0x39, 0x36, 0x2f, + 0x61, 0x68, 0x6e, 0x6c, 0x69, 0x63, 0x68, 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x61, 0x68, 0x6e, 0x6c, + 0x69, 0x63, 0x68, 0x2d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2d, 0x67, 0x6f, 0x2f, 0x67, 0x72, + 0x70, 0x63, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x64, 0x62, 0x5f, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x3b, 0x64, 0x62, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var file_services_db_service_proto_goTypes = []any{ @@ -124,20 +132,22 @@ var file_services_db_service_proto_goTypes = []any{ (*query.ListClients)(nil), // 13: db.query.ListClients (*query.ListStores)(nil), // 14: db.query.ListStores (*query.InfoServer)(nil), // 15: db.query.InfoServer - (*query.Ping)(nil), // 16: db.query.Ping - (*pipeline.DBRequestPipeline)(nil), // 17: db.pipeline.DBRequestPipeline - (*server.Unit)(nil), // 18: db.server.Unit - (*server.CreateIndex)(nil), // 19: db.server.CreateIndex - (*server.Get)(nil), // 20: db.server.Get - (*server.GetSimN)(nil), // 21: db.server.GetSimN - (*server.StoreInfo)(nil), // 22: db.server.StoreInfo - (*server.Set)(nil), // 23: db.server.Set - (*server.Del)(nil), // 24: db.server.Del - (*server.ClientList)(nil), // 25: db.server.ClientList - (*server.StoreList)(nil), // 26: db.server.StoreList - (*server.InfoServer)(nil), // 27: db.server.InfoServer - (*server.Pong)(nil), // 28: db.server.Pong - (*pipeline.DBResponsePipeline)(nil), // 29: db.pipeline.DBResponsePipeline + (*cluster.ClusterInfoQuery)(nil), // 16: shared.cluster.ClusterInfoQuery + (*query.Ping)(nil), // 17: db.query.Ping + (*pipeline.DBRequestPipeline)(nil), // 18: db.pipeline.DBRequestPipeline + (*server.Unit)(nil), // 19: db.server.Unit + (*server.CreateIndex)(nil), // 20: db.server.CreateIndex + (*server.Get)(nil), // 21: db.server.Get + (*server.GetSimN)(nil), // 22: db.server.GetSimN + (*server.StoreInfo)(nil), // 23: db.server.StoreInfo + (*server.Set)(nil), // 24: db.server.Set + (*server.Del)(nil), // 25: db.server.Del + (*server.ClientList)(nil), // 26: db.server.ClientList + (*server.StoreList)(nil), // 27: db.server.StoreList + (*server.InfoServer)(nil), // 28: db.server.InfoServer + (*cluster.ClusterInfoResponse)(nil), // 29: shared.cluster.ClusterInfoResponse + (*server.Pong)(nil), // 30: db.server.Pong + (*pipeline.DBResponsePipeline)(nil), // 31: db.pipeline.DBResponsePipeline } var file_services_db_service_proto_depIdxs = []int32{ 0, // 0: services.db_service.DBService.CreateStore:input_type -> db.query.CreateStore @@ -156,28 +166,30 @@ var file_services_db_service_proto_depIdxs = []int32{ 13, // 13: services.db_service.DBService.ListClients:input_type -> db.query.ListClients 14, // 14: services.db_service.DBService.ListStores:input_type -> db.query.ListStores 15, // 15: services.db_service.DBService.InfoServer:input_type -> db.query.InfoServer - 16, // 16: services.db_service.DBService.Ping:input_type -> db.query.Ping - 17, // 17: services.db_service.DBService.Pipeline:input_type -> db.pipeline.DBRequestPipeline - 18, // 18: services.db_service.DBService.CreateStore:output_type -> db.server.Unit - 19, // 19: services.db_service.DBService.CreatePredIndex:output_type -> db.server.CreateIndex - 19, // 20: services.db_service.DBService.CreateNonLinearAlgorithmIndex:output_type -> db.server.CreateIndex - 20, // 21: services.db_service.DBService.GetKey:output_type -> db.server.Get - 20, // 22: services.db_service.DBService.GetPred:output_type -> db.server.Get - 21, // 23: services.db_service.DBService.GetSimN:output_type -> db.server.GetSimN - 22, // 24: services.db_service.DBService.GetStore:output_type -> db.server.StoreInfo - 23, // 25: services.db_service.DBService.Set:output_type -> db.server.Set - 24, // 26: services.db_service.DBService.DropPredIndex:output_type -> db.server.Del - 24, // 27: services.db_service.DBService.DropNonLinearAlgorithmIndex:output_type -> db.server.Del - 24, // 28: services.db_service.DBService.DelKey:output_type -> db.server.Del - 24, // 29: services.db_service.DBService.DelPred:output_type -> db.server.Del - 24, // 30: services.db_service.DBService.DropStore:output_type -> db.server.Del - 25, // 31: services.db_service.DBService.ListClients:output_type -> db.server.ClientList - 26, // 32: services.db_service.DBService.ListStores:output_type -> db.server.StoreList - 27, // 33: services.db_service.DBService.InfoServer:output_type -> db.server.InfoServer - 28, // 34: services.db_service.DBService.Ping:output_type -> db.server.Pong - 29, // 35: services.db_service.DBService.Pipeline:output_type -> db.pipeline.DBResponsePipeline - 18, // [18:36] is the sub-list for method output_type - 0, // [0:18] is the sub-list for method input_type + 16, // 16: services.db_service.DBService.ClusterInfo:input_type -> shared.cluster.ClusterInfoQuery + 17, // 17: services.db_service.DBService.Ping:input_type -> db.query.Ping + 18, // 18: services.db_service.DBService.Pipeline:input_type -> db.pipeline.DBRequestPipeline + 19, // 19: services.db_service.DBService.CreateStore:output_type -> db.server.Unit + 20, // 20: services.db_service.DBService.CreatePredIndex:output_type -> db.server.CreateIndex + 20, // 21: services.db_service.DBService.CreateNonLinearAlgorithmIndex:output_type -> db.server.CreateIndex + 21, // 22: services.db_service.DBService.GetKey:output_type -> db.server.Get + 21, // 23: services.db_service.DBService.GetPred:output_type -> db.server.Get + 22, // 24: services.db_service.DBService.GetSimN:output_type -> db.server.GetSimN + 23, // 25: services.db_service.DBService.GetStore:output_type -> db.server.StoreInfo + 24, // 26: services.db_service.DBService.Set:output_type -> db.server.Set + 25, // 27: services.db_service.DBService.DropPredIndex:output_type -> db.server.Del + 25, // 28: services.db_service.DBService.DropNonLinearAlgorithmIndex:output_type -> db.server.Del + 25, // 29: services.db_service.DBService.DelKey:output_type -> db.server.Del + 25, // 30: services.db_service.DBService.DelPred:output_type -> db.server.Del + 25, // 31: services.db_service.DBService.DropStore:output_type -> db.server.Del + 26, // 32: services.db_service.DBService.ListClients:output_type -> db.server.ClientList + 27, // 33: services.db_service.DBService.ListStores:output_type -> db.server.StoreList + 28, // 34: services.db_service.DBService.InfoServer:output_type -> db.server.InfoServer + 29, // 35: services.db_service.DBService.ClusterInfo:output_type -> shared.cluster.ClusterInfoResponse + 30, // 36: services.db_service.DBService.Ping:output_type -> db.server.Pong + 31, // 37: services.db_service.DBService.Pipeline:output_type -> db.pipeline.DBResponsePipeline + 19, // [19:38] is the sub-list for method output_type + 0, // [0:19] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name diff --git a/sdk/ahnlich-client-go/grpc/services/db_service/db_service_grpc.pb.go b/sdk/ahnlich-client-go/grpc/services/db_service/db_service_grpc.pb.go index f662a9953..ce4b12bea 100644 --- a/sdk/ahnlich-client-go/grpc/services/db_service/db_service_grpc.pb.go +++ b/sdk/ahnlich-client-go/grpc/services/db_service/db_service_grpc.pb.go @@ -16,6 +16,7 @@ import ( pipeline "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/pipeline" query "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/query" server "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/db/server" + cluster "github.com/deven96/ahnlich/sdk/ahnlich-client-go/grpc/shared/cluster" ) // This is a compile-time assertion to ensure that this generated file @@ -40,6 +41,7 @@ const ( DBService_ListClients_FullMethodName = "/services.db_service.DBService/ListClients" DBService_ListStores_FullMethodName = "/services.db_service.DBService/ListStores" DBService_InfoServer_FullMethodName = "/services.db_service.DBService/InfoServer" + DBService_ClusterInfo_FullMethodName = "/services.db_service.DBService/ClusterInfo" DBService_Ping_FullMethodName = "/services.db_service.DBService/Ping" DBService_Pipeline_FullMethodName = "/services.db_service.DBService/Pipeline" ) @@ -69,6 +71,7 @@ type DBServiceClient interface { ListClients(ctx context.Context, in *query.ListClients, opts ...grpc.CallOption) (*server.ClientList, error) ListStores(ctx context.Context, in *query.ListStores, opts ...grpc.CallOption) (*server.StoreList, error) InfoServer(ctx context.Context, in *query.InfoServer, opts ...grpc.CallOption) (*server.InfoServer, error) + ClusterInfo(ctx context.Context, in *cluster.ClusterInfoQuery, opts ...grpc.CallOption) (*cluster.ClusterInfoResponse, error) Ping(ctx context.Context, in *query.Ping, opts ...grpc.CallOption) (*server.Pong, error) // * Pipeline method for all methods * Pipeline(ctx context.Context, in *pipeline.DBRequestPipeline, opts ...grpc.CallOption) (*pipeline.DBResponsePipeline, error) @@ -242,6 +245,16 @@ func (c *dBServiceClient) InfoServer(ctx context.Context, in *query.InfoServer, return out, nil } +func (c *dBServiceClient) ClusterInfo(ctx context.Context, in *cluster.ClusterInfoQuery, opts ...grpc.CallOption) (*cluster.ClusterInfoResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(cluster.ClusterInfoResponse) + err := c.cc.Invoke(ctx, DBService_ClusterInfo_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *dBServiceClient) Ping(ctx context.Context, in *query.Ping, opts ...grpc.CallOption) (*server.Pong, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(server.Pong) @@ -287,6 +300,7 @@ type DBServiceServer interface { ListClients(context.Context, *query.ListClients) (*server.ClientList, error) ListStores(context.Context, *query.ListStores) (*server.StoreList, error) InfoServer(context.Context, *query.InfoServer) (*server.InfoServer, error) + ClusterInfo(context.Context, *cluster.ClusterInfoQuery) (*cluster.ClusterInfoResponse, error) Ping(context.Context, *query.Ping) (*server.Pong, error) // * Pipeline method for all methods * Pipeline(context.Context, *pipeline.DBRequestPipeline) (*pipeline.DBResponsePipeline, error) @@ -348,6 +362,9 @@ func (UnimplementedDBServiceServer) ListStores(context.Context, *query.ListStore func (UnimplementedDBServiceServer) InfoServer(context.Context, *query.InfoServer) (*server.InfoServer, error) { return nil, status.Errorf(codes.Unimplemented, "method InfoServer not implemented") } +func (UnimplementedDBServiceServer) ClusterInfo(context.Context, *cluster.ClusterInfoQuery) (*cluster.ClusterInfoResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ClusterInfo not implemented") +} func (UnimplementedDBServiceServer) Ping(context.Context, *query.Ping) (*server.Pong, error) { return nil, status.Errorf(codes.Unimplemented, "method Ping not implemented") } @@ -663,6 +680,24 @@ func _DBService_InfoServer_Handler(srv interface{}, ctx context.Context, dec fun return interceptor(ctx, in, info, handler) } +func _DBService_ClusterInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(cluster.ClusterInfoQuery) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DBServiceServer).ClusterInfo(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: DBService_ClusterInfo_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DBServiceServer).ClusterInfo(ctx, req.(*cluster.ClusterInfoQuery)) + } + return interceptor(ctx, in, info, handler) +} + func _DBService_Ping_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(query.Ping) if err := dec(in); err != nil { @@ -770,6 +805,10 @@ var DBService_ServiceDesc = grpc.ServiceDesc{ MethodName: "InfoServer", Handler: _DBService_InfoServer_Handler, }, + { + MethodName: "ClusterInfo", + Handler: _DBService_ClusterInfo_Handler, + }, { MethodName: "Ping", Handler: _DBService_Ping_Handler, diff --git a/sdk/ahnlich-client-node/grpc/db/pipeline_pb.ts b/sdk/ahnlich-client-node/grpc/db/pipeline_pb.ts index 767c27864..77685eee3 100644 --- a/sdk/ahnlich-client-node/grpc/db/pipeline_pb.ts +++ b/sdk/ahnlich-client-node/grpc/db/pipeline_pb.ts @@ -31,6 +31,7 @@ import { Ping, Set, } from "./query_pb.js"; +import { ClusterInfoQuery, ClusterInfoResponse } from "../shared/cluster_pb.js"; import { ClientList, CreateIndex, @@ -173,6 +174,13 @@ export class DBQuery extends Message { value: GetStore; case: "getStore"; } + | { + /** + * @generated from field: shared.cluster.ClusterInfoQuery cluster_info = 18; + */ + value: ClusterInfoQuery; + case: "clusterInfo"; + } | { case: undefined; value?: undefined } = { case: undefined }; constructor(data?: PartialMessage) { @@ -212,6 +220,7 @@ export class DBQuery extends Message { { no: 15, name: "list_clients", kind: "message", T: ListClients, oneof: "query" }, { no: 16, name: "ping", kind: "message", T: Ping, oneof: "query" }, { no: 17, name: "get_store", kind: "message", T: GetStore, oneof: "query" }, + { no: 18, name: "cluster_info", kind: "message", T: ClusterInfoQuery, oneof: "query" }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): DBQuery { @@ -366,6 +375,13 @@ export class DBServerResponse extends Message { value: StoreInfo; case: "storeInfo"; } + | { + /** + * @generated from field: shared.cluster.ClusterInfoResponse cluster_info = 13; + */ + value: ClusterInfoResponse; + case: "clusterInfo"; + } | { case: undefined; value?: undefined } = { case: undefined }; constructor(data?: PartialMessage) { @@ -388,6 +404,7 @@ export class DBServerResponse extends Message { { no: 10, name: "create_index", kind: "message", T: CreateIndex, oneof: "response" }, { no: 11, name: "error", kind: "message", T: ErrorResponse, oneof: "response" }, { no: 12, name: "store_info", kind: "message", T: StoreInfo, oneof: "response" }, + { no: 13, name: "cluster_info", kind: "message", T: ClusterInfoResponse, oneof: "response" }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): DBServerResponse { diff --git a/sdk/ahnlich-client-node/grpc/services/db_service_connect.ts b/sdk/ahnlich-client-node/grpc/services/db_service_connect.ts index 8c40a4de9..358a86e0b 100644 --- a/sdk/ahnlich-client-node/grpc/services/db_service_connect.ts +++ b/sdk/ahnlich-client-node/grpc/services/db_service_connect.ts @@ -36,6 +36,7 @@ import { Unit, } from "../db/server_pb.js"; import { MethodKind } from "@bufbuild/protobuf"; +import { ClusterInfoQuery, ClusterInfoResponse } from "../shared/cluster_pb.js"; import { DBRequestPipeline, DBResponsePipeline } from "../db/pipeline_pb.js"; /** @@ -198,6 +199,15 @@ export const DBService = { O: InfoServer$1, kind: MethodKind.Unary, }, + /** + * @generated from rpc services.db_service.DBService.ClusterInfo + */ + clusterInfo: { + name: "ClusterInfo", + I: ClusterInfoQuery, + O: ClusterInfoResponse, + kind: MethodKind.Unary, + }, /** * @generated from rpc services.db_service.DBService.Ping */ diff --git a/sdk/ahnlich-client-py/ahnlich_client_py/grpc/db/pipeline/__init__.py b/sdk/ahnlich-client-py/ahnlich_client_py/grpc/db/pipeline/__init__.py index 8a779d940..51a8d190d 100644 --- a/sdk/ahnlich-client-py/ahnlich_client_py/grpc/db/pipeline/__init__.py +++ b/sdk/ahnlich-client-py/ahnlich_client_py/grpc/db/pipeline/__init__.py @@ -8,6 +8,7 @@ import betterproto +from ...shared import cluster as __shared_cluster__ from ...shared import info as __shared_info__ from .. import query as _query__ from .. import server as _server__ @@ -40,6 +41,9 @@ class DbQuery(betterproto.Message): list_clients: "_query__.ListClients" = betterproto.message_field(15, group="query") ping: "_query__.Ping" = betterproto.message_field(16, group="query") get_store: "_query__.GetStore" = betterproto.message_field(17, group="query") + cluster_info: "__shared_cluster__.ClusterInfoQuery" = betterproto.message_field( + 18, group="query" + ) @dataclass(eq=False, repr=False) @@ -65,6 +69,9 @@ class DbServerResponse(betterproto.Message): 11, group="response" ) store_info: "_server__.StoreInfo" = betterproto.message_field(12, group="response") + cluster_info: "__shared_cluster__.ClusterInfoResponse" = betterproto.message_field( + 13, group="response" + ) @dataclass(eq=False, repr=False) diff --git a/sdk/ahnlich-client-py/ahnlich_client_py/grpc/services/db_service/__init__.py b/sdk/ahnlich-client-py/ahnlich_client_py/grpc/services/db_service/__init__.py index c9b9d55eb..cc8a071fc 100644 --- a/sdk/ahnlich-client-py/ahnlich_client_py/grpc/services/db_service/__init__.py +++ b/sdk/ahnlich-client-py/ahnlich_client_py/grpc/services/db_service/__init__.py @@ -13,6 +13,7 @@ from ...db import pipeline as __db_pipeline__ from ...db import query as __db_query__ from ...db import server as __db_server__ +from ...shared import cluster as __shared_cluster__ if TYPE_CHECKING: import grpclib.server @@ -293,6 +294,23 @@ async def info_server( metadata=metadata, ) + async def cluster_info( + self, + shared_cluster_cluster_info_query: "__shared_cluster__.ClusterInfoQuery", + *, + timeout: Optional[float] = None, + deadline: Optional["Deadline"] = None, + metadata: Optional["MetadataLike"] = None + ) -> "__shared_cluster__.ClusterInfoResponse": + return await self._unary_unary( + "/services.db_service.DBService/ClusterInfo", + shared_cluster_cluster_info_query, + __shared_cluster__.ClusterInfoResponse, + timeout=timeout, + deadline=deadline, + metadata=metadata, + ) + async def ping( self, db_query_ping: "__db_query__.Ping", @@ -410,6 +428,11 @@ async def info_server( ) -> "__db_server__.InfoServer": raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED) + async def cluster_info( + self, shared_cluster_cluster_info_query: "__shared_cluster__.ClusterInfoQuery" + ) -> "__shared_cluster__.ClusterInfoResponse": + raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED) + async def ping(self, db_query_ping: "__db_query__.Ping") -> "__db_server__.Pong": raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED) @@ -540,6 +563,14 @@ async def __rpc_info_server( response = await self.info_server(request) await stream.send_message(response) + async def __rpc_cluster_info( + self, + stream: "grpclib.server.Stream[__shared_cluster__.ClusterInfoQuery, __shared_cluster__.ClusterInfoResponse]", + ) -> None: + request = await stream.recv_message() + response = await self.cluster_info(request) + await stream.send_message(response) + async def __rpc_ping( self, stream: "grpclib.server.Stream[__db_query__.Ping, __db_server__.Pong]" ) -> None: @@ -653,6 +684,12 @@ def __mapping__(self) -> Dict[str, grpclib.const.Handler]: __db_query__.InfoServer, __db_server__.InfoServer, ), + "/services.db_service.DBService/ClusterInfo": grpclib.const.Handler( + self.__rpc_cluster_info, + grpclib.const.Cardinality.UNARY_UNARY, + __shared_cluster__.ClusterInfoQuery, + __shared_cluster__.ClusterInfoResponse, + ), "/services.db_service.DBService/Ping": grpclib.const.Handler( self.__rpc_ping, grpclib.const.Cardinality.UNARY_UNARY, From 2ed4e19a052a25960828477dbd7ca4f6ca0aec87 Mon Sep 17 00:00:00 2001 From: Jim Ezesinachi Date: Mon, 1 Jun 2026 19:57:23 +0100 Subject: [PATCH 2/5] formatting fixes Signed-off-by: Jim Ezesinachi --- ahnlich/db/src/tests/replication_store_tests.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ahnlich/db/src/tests/replication_store_tests.rs b/ahnlich/db/src/tests/replication_store_tests.rs index 806cdbb2f..7a7fb94a0 100644 --- a/ahnlich/db/src/tests/replication_store_tests.rs +++ b/ahnlich/db/src/tests/replication_store_tests.rs @@ -141,7 +141,9 @@ fn snapshot_round_trip_restores_store_state() { &StoreName { value: "products".to_owned(), }, - vec![StoreKey { key: vec![1.0, 2.0] }], + vec![StoreKey { + key: vec![1.0, 2.0], + }], ) }) .expect("state machine access should succeed") From 94cf264e2cb3db38be40844e39ceb0a2608cdd9e Mon Sep 17 00:00:00 2001 From: Jim Ezesinachi Date: Wed, 10 Jun 2026 10:44:48 +0100 Subject: [PATCH 3/5] add StoreRuntime enum for store_handler management in the different modes Signed-off-by: Jim Ezesinachi --- ahnlich/db/src/server/cluster_queries.rs | 31 +- ahnlich/db/src/server/handler.rs | 399 ++++++++++++----------- ahnlich/db/src/server/mod.rs | 1 + ahnlich/db/src/server/store_runtime.rs | 50 +++ ahnlich/db/src/tests/cluster_tests.rs | 15 + 5 files changed, 276 insertions(+), 220 deletions(-) create mode 100644 ahnlich/db/src/server/store_runtime.rs diff --git a/ahnlich/db/src/server/cluster_queries.rs b/ahnlich/db/src/server/cluster_queries.rs index 26755ee28..7cb00a3f4 100644 --- a/ahnlich/db/src/server/cluster_queries.rs +++ b/ahnlich/db/src/server/cluster_queries.rs @@ -1,7 +1,7 @@ use crate::engine::operations; use crate::engine::store::StoreHandler; use crate::errors::ServerError; -use crate::server::cluster::ClusterRuntime; +use crate::server::store_runtime::StoreRuntime; use ahnlich_replication::cluster_info; use ahnlich_replication::node::ReplicationNode; use ahnlich_types::db::server; @@ -12,7 +12,6 @@ use ahnlich_types::shared::cluster::{ use openraft::error::RaftError; use std::io::Result as IoResult; use std::net::SocketAddr; -use std::sync::Arc; fn map_cluster_role(role: cluster_info::NodeRole) -> i32 { match role { @@ -29,10 +28,6 @@ fn map_cluster_health(health: cluster_info::NodeHealthStatus) -> i32 { } } -pub(crate) fn map_storage_error(context: &str, err: openraft::StorageError) -> tonic::Status { - tonic::Status::internal(format!("{context}: {err}")) -} - pub(crate) fn map_linearizable_error( err: RaftError>, ) -> tonic::Status { @@ -52,26 +47,16 @@ pub(crate) fn map_linearizable_error( #[allow(clippy::result_large_err)] pub(crate) fn read_store_handler( - cluster: Option<&ClusterRuntime>, - store_handler: &Arc, + runtime: &StoreRuntime, f: impl FnOnce(&StoreHandler) -> Result, ) -> Result { - if let Some(cluster) = cluster { - cluster - .state_machine - .with_handler(|handler| f(handler.store_handler())) - .map_err(|err| map_storage_error("failed to access clustered state machine", err))? - .map_err(Into::into) - } else { - f(store_handler).map_err(Into::into) - } + runtime.with_store_handler(f) } pub(crate) async fn list_stores_response( - cluster: Option<&ClusterRuntime>, - store_handler: &Arc, + runtime: &StoreRuntime, ) -> Result { - if let Some(cluster) = cluster { + if let Some(cluster) = runtime.cluster() { cluster .raft .ensure_linearizable() @@ -79,16 +64,16 @@ pub(crate) async fn list_stores_response( .map_err(map_linearizable_error)?; } - read_store_handler(cluster, store_handler, |store_handler| { + read_store_handler(runtime, |store_handler| { Ok(operations::list_stores(store_handler)) }) } pub(crate) async fn cluster_info_response( listener_addr: IoResult, - cluster: Option<&ClusterRuntime>, + runtime: &StoreRuntime, ) -> Result { - if let Some(cluster) = cluster { + if let Some(cluster) = runtime.cluster() { let nodes = cluster_info::cluster_topology(cluster.raft.as_ref()) .await .into_iter() diff --git a/ahnlich/db/src/server/handler.rs b/ahnlich/db/src/server/handler.rs index a1c19dc72..205e1e8e9 100644 --- a/ahnlich/db/src/server/handler.rs +++ b/ahnlich/db/src/server/handler.rs @@ -3,12 +3,13 @@ use crate::engine::operations; use crate::engine::store::StoreHandler; use crate::engine::store::StoresSnapshot; use crate::errors::ServerError; -use crate::server::cluster::{ClusterRuntime, build_cluster_runtime, initialize_cluster_runtime}; +use crate::server::cluster::{build_cluster_runtime, initialize_cluster_runtime}; use crate::server::cluster_mutations::submit_db_command; use crate::server::cluster_queries::{ cluster_info_response, list_stores_response, read_store_handler, }; use crate::server::cluster_tasks::spawn_cluster_tasks; +use crate::server::store_runtime::StoreRuntime; use ahnlich_replication::types::DbCommand; use ahnlich_types::db::pipeline::db_query::Query; use ahnlich_types::db::server::GetSimNEntry; @@ -42,11 +43,10 @@ const SERVICE_NAME: &str = "ahnlich-db"; #[derive(Debug)] pub struct Server { listener: ListenerStreamOrAddress, - store_handler: Arc, + runtime: StoreRuntime, client_handler: Arc, task_manager: Arc, config: ServerConfig, - cluster: Option, } #[tonic::async_trait] @@ -58,15 +58,18 @@ impl DbService for Server { ) -> std::result::Result, tonic::Status> { let params = request.into_inner(); - if self.cluster.is_some() { - let (): () = submit_db_command( - self.cluster.as_ref(), - "CreateStore", - DbCommand::CreateStore(params.encode_to_vec()), - ) - .await?; - } else { - operations::create_store(&self.store_handler, params)?; + match &self.runtime { + StoreRuntime::Cluster(cluster) => { + let (): () = submit_db_command( + Some(cluster), + "CreateStore", + DbCommand::CreateStore(params.encode_to_vec()), + ) + .await?; + } + StoreRuntime::Standalone(store_handler) => { + operations::create_store(store_handler, params)?; + } } Ok(tonic::Response::new(server::Unit {})) @@ -84,27 +87,23 @@ impl DbService for Server { .map(|key| StoreKey { key: key.key }) .collect(); - let entries: Vec = read_store_handler( - self.cluster.as_ref(), - &self.store_handler, - |store_handler| { - Ok(store_handler - .get_key_in_store( - &StoreName { - value: params.store, - }, - keys, - )? - .into_iter() - .map(|(embedding_key, store_value)| DbStoreEntry { - key: Some(StoreKey { - key: embedding_key.as_slice().to_vec(), - }), - value: Some(Arc::unwrap_or_clone(store_value)), - }) - .collect()) - }, - )?; + let entries: Vec = read_store_handler(&self.runtime, |store_handler| { + Ok(store_handler + .get_key_in_store( + &StoreName { + value: params.store, + }, + keys, + )? + .into_iter() + .map(|(embedding_key, store_value)| DbStoreEntry { + key: Some(StoreKey { + key: embedding_key.as_slice().to_vec(), + }), + value: Some(Arc::unwrap_or_clone(store_value)), + }) + .collect()) + })?; Ok(tonic::Response::new(server::Get { entries })) } @@ -119,27 +118,23 @@ impl DbService for Server { let condition = ahnlich_types::unwrap_or_invalid!(params.condition, "Predicate Condition is required"); - let entries = read_store_handler( - self.cluster.as_ref(), - &self.store_handler, - |store_handler| { - Ok(store_handler - .get_pred_in_store( - &StoreName { - value: params.store, - }, - &condition, - )? - .into_iter() - .map(|(embedding_key, store_value)| DbStoreEntry { - key: Some(StoreKey { - key: embedding_key.as_slice().to_vec(), - }), - value: Some(Arc::unwrap_or_clone(store_value)), - }) - .collect()) - }, - )?; + let entries = read_store_handler(&self.runtime, |store_handler| { + Ok(store_handler + .get_pred_in_store( + &StoreName { + value: params.store, + }, + &condition, + )? + .into_iter() + .map(|(embedding_key, store_value)| DbStoreEntry { + key: Some(StoreKey { + key: embedding_key.as_slice().to_vec(), + }), + value: Some(Arc::unwrap_or_clone(store_value)), + }) + .collect()) + })?; Ok(tonic::Response::new(server::Get { entries })) } @@ -162,31 +157,27 @@ impl DbService for Server { let closest_n = types_utils::convert_to_nonzerousize(params.closest_n) .map_err(tonic::Status::invalid_argument)?; - let entries = read_store_handler( - self.cluster.as_ref(), - &self.store_handler, - |store_handler| { - Ok(store_handler - .get_sim_in_store( - &StoreName { - value: params.store, - }, - search_input, - closest_n, - algorithm, - params.condition, - )? - .into_iter() - .map(|(embedding_key, store_value, sim)| GetSimNEntry { - key: Some(StoreKey { - key: embedding_key.as_slice().to_vec(), - }), - value: Some(Arc::unwrap_or_clone(store_value)), - similarity: Some(sim), - }) - .collect()) - }, - )?; + let entries = read_store_handler(&self.runtime, |store_handler| { + Ok(store_handler + .get_sim_in_store( + &StoreName { + value: params.store, + }, + search_input, + closest_n, + algorithm, + params.condition, + )? + .into_iter() + .map(|(embedding_key, store_value, sim)| GetSimNEntry { + key: Some(StoreKey { + key: embedding_key.as_slice().to_vec(), + }), + value: Some(Arc::unwrap_or_clone(store_value)), + similarity: Some(sim), + }) + .collect()) + })?; Ok(tonic::Response::new(server::GetSimN { entries })) } @@ -206,15 +197,18 @@ impl DbService for Server { ) -> std::result::Result, tonic::Status> { let params = request.into_inner(); - let created_indexes = (if self.cluster.is_some() { - submit_db_command( - self.cluster.as_ref(), - "CreatePredIndex", - DbCommand::CreatePredIndex(params.encode_to_vec()), - ) - .await? - } else { - operations::create_pred_index(&self.store_handler, params)? + let created_indexes = (match &self.runtime { + StoreRuntime::Cluster(cluster) => { + submit_db_command( + Some(cluster), + "CreatePredIndex", + DbCommand::CreatePredIndex(params.encode_to_vec()), + ) + .await? + } + StoreRuntime::Standalone(store_handler) => { + operations::create_pred_index(store_handler, params)? + } }) as u64; Ok(tonic::Response::new(server::CreateIndex { @@ -229,15 +223,18 @@ impl DbService for Server { ) -> std::result::Result, tonic::Status> { let params = request.into_inner(); - let created_indexes = (if self.cluster.is_some() { - submit_db_command( - self.cluster.as_ref(), - "CreateNonLinearAlgorithmIndex", - DbCommand::CreateNonLinearAlgorithmIndex(params.encode_to_vec()), - ) - .await? - } else { - operations::create_non_linear_algorithm_index(&self.store_handler, params)? + let created_indexes = (match &self.runtime { + StoreRuntime::Cluster(cluster) => { + submit_db_command( + Some(cluster), + "CreateNonLinearAlgorithmIndex", + DbCommand::CreateNonLinearAlgorithmIndex(params.encode_to_vec()), + ) + .await? + } + StoreRuntime::Standalone(store_handler) => { + operations::create_non_linear_algorithm_index(store_handler, params)? + } }) as u64; Ok(tonic::Response::new(server::CreateIndex { @@ -252,15 +249,18 @@ impl DbService for Server { ) -> std::result::Result, tonic::Status> { let params = request.into_inner(); - let deleted_count = (if self.cluster.is_some() { - submit_db_command( - self.cluster.as_ref(), - "DropPredIndex", - DbCommand::DropPredIndex(params.encode_to_vec()), - ) - .await? - } else { - operations::drop_pred_index(&self.store_handler, params)? + let deleted_count = (match &self.runtime { + StoreRuntime::Cluster(cluster) => { + submit_db_command( + Some(cluster), + "DropPredIndex", + DbCommand::DropPredIndex(params.encode_to_vec()), + ) + .await? + } + StoreRuntime::Standalone(store_handler) => { + operations::drop_pred_index(store_handler, params)? + } }) as u64; Ok(tonic::Response::new(server::Del { deleted_count })) @@ -273,15 +273,18 @@ impl DbService for Server { ) -> std::result::Result, tonic::Status> { let params = request.into_inner(); - let deleted_count = (if self.cluster.is_some() { - submit_db_command( - self.cluster.as_ref(), - "DropNonLinearAlgorithmIndex", - DbCommand::DropNonLinearAlgorithmIndex(params.encode_to_vec()), - ) - .await? - } else { - operations::drop_non_linear_algorithm_index(&self.store_handler, params)? + let deleted_count = (match &self.runtime { + StoreRuntime::Cluster(cluster) => { + submit_db_command( + Some(cluster), + "DropNonLinearAlgorithmIndex", + DbCommand::DropNonLinearAlgorithmIndex(params.encode_to_vec()), + ) + .await? + } + StoreRuntime::Standalone(store_handler) => { + operations::drop_non_linear_algorithm_index(store_handler, params)? + } }) as u64; Ok(tonic::Response::new(server::Del { deleted_count })) @@ -294,15 +297,16 @@ impl DbService for Server { ) -> std::result::Result, tonic::Status> { let params = request.into_inner(); - let deleted_count = (if self.cluster.is_some() { - submit_db_command( - self.cluster.as_ref(), - "DelKey", - DbCommand::DelKey(params.encode_to_vec()), - ) - .await? - } else { - operations::del_key(&self.store_handler, params)? + let deleted_count = (match &self.runtime { + StoreRuntime::Cluster(cluster) => { + submit_db_command( + Some(cluster), + "DelKey", + DbCommand::DelKey(params.encode_to_vec()), + ) + .await? + } + StoreRuntime::Standalone(store_handler) => operations::del_key(store_handler, params)?, }) as u64; Ok(tonic::Response::new(server::Del { deleted_count })) @@ -315,15 +319,16 @@ impl DbService for Server { ) -> std::result::Result, tonic::Status> { let params = request.into_inner(); - let deleted_count = (if self.cluster.is_some() { - submit_db_command( - self.cluster.as_ref(), - "DelPred", - DbCommand::DelPred(params.encode_to_vec()), - ) - .await? - } else { - operations::del_pred(&self.store_handler, params)? + let deleted_count = (match &self.runtime { + StoreRuntime::Cluster(cluster) => { + submit_db_command( + Some(cluster), + "DelPred", + DbCommand::DelPred(params.encode_to_vec()), + ) + .await? + } + StoreRuntime::Standalone(store_handler) => operations::del_pred(store_handler, params)?, }) as u64; Ok(tonic::Response::new(server::Del { deleted_count })) @@ -336,15 +341,18 @@ impl DbService for Server { ) -> std::result::Result, tonic::Status> { let params = request.into_inner(); - let deleted_count = (if self.cluster.is_some() { - submit_db_command( - self.cluster.as_ref(), - "DropStore", - DbCommand::DropStore(params.encode_to_vec()), - ) - .await? - } else { - operations::drop_store(&self.store_handler, params)? + let deleted_count = (match &self.runtime { + StoreRuntime::Cluster(cluster) => { + submit_db_command( + Some(cluster), + "DropStore", + DbCommand::DropStore(params.encode_to_vec()), + ) + .await? + } + StoreRuntime::Standalone(store_handler) => { + operations::drop_store(store_handler, params)? + } }) as u64; Ok(tonic::Response::new(server::Del { deleted_count })) @@ -373,7 +381,7 @@ impl DbService for Server { &self, _request: tonic::Request, ) -> std::result::Result, tonic::Status> { - let store_list = list_stores_response(self.cluster.as_ref(), &self.store_handler).await?; + let store_list = list_stores_response(&self.runtime).await?; Ok(tonic::Response::new(store_list)) } @@ -383,15 +391,11 @@ impl DbService for Server { request: tonic::Request, ) -> std::result::Result, tonic::Status> { let params = request.into_inner(); - let store_info = read_store_handler( - self.cluster.as_ref(), - &self.store_handler, - |store_handler| { - store_handler.get_store(&StoreName { - value: params.store, - }) - }, - )?; + let store_info = read_store_handler(&self.runtime, |store_handler| { + store_handler.get_store(&StoreName { + value: params.store, + }) + })?; Ok(tonic::Response::new(store_info)) } @@ -434,15 +438,12 @@ impl DbService for Server { ) -> std::result::Result, tonic::Status> { let params = request.into_inner(); - let set = if self.cluster.is_some() { - submit_db_command( - self.cluster.as_ref(), - "Set", - DbCommand::Set(params.encode_to_vec()), - ) - .await? - } else { - operations::set(&self.store_handler, params)? + let set = match &self.runtime { + StoreRuntime::Cluster(cluster) => { + submit_db_command(Some(cluster), "Set", DbCommand::Set(params.encode_to_vec())) + .await? + } + StoreRuntime::Standalone(store_handler) => operations::set(store_handler, params)?, }; Ok(tonic::Response::new(server::Set { upsert: Some(set) })) @@ -454,7 +455,7 @@ impl DbService for Server { _request: tonic::Request, ) -> std::result::Result, tonic::Status> { Ok(tonic::Response::new( - cluster_info_response(self.listener.local_addr(), self.cluster.as_ref()).await?, + cluster_info_response(self.listener.local_addr(), &self.runtime).await?, )) } @@ -790,7 +791,9 @@ impl AhnlichServerUtils for Server { } fn store_handler(&self) -> &Arc { - &self.store_handler + self.runtime + .standalone_store_handler() + .expect("store_handler is only available in standalone mode") } fn task_manager(&self) -> Arc { @@ -805,17 +808,21 @@ impl AhnlichServerUtils for Server { &self, task_manager: &Arc, ) -> std::io::Result<()> { - if let Some(cluster) = &self.cluster { + if let Some(cluster) = self.runtime.cluster() { spawn_cluster_tasks(&self.config, task_manager, cluster).await?; tokio::time::sleep(std::time::Duration::from_millis(100)).await; initialize_cluster_runtime(&self.config, self.listener.local_addr()?, cluster).await?; } else { use utils::size_calculation::SizeCalculation; + let store_handler = self + .runtime + .standalone_store_handler() + .expect("standalone runtime must expose store handler"); let size_calculation_task = SizeCalculation::task( self.write_flag(), self.config.common.size_calculation_interval, - StoresSnapshot::new(self.store_handler.get_stores()), + StoresSnapshot::new(store_handler.get_stores()), ); task_manager.spawn_task_loop(size_calculation_task).await; } @@ -832,32 +839,7 @@ impl Server { client_handler.clone(), ) .await?; - let mut store_handler = StoreHandler::new(Arc::new(AtomicBool::new(false))); - if !config.is_clustered() - && let Some(persist_location) = &config.common.persist_location - { - match Persistence::load_snapshot(persist_location, config.common.enable_mmap) { - Err(e) => { - log::error!("Failed to load snapshot from persist location {e}"); - if config.common.fail_on_startup_if_persist_load_fails { - return Err(std::io::Error::other(e.to_string())); - } - } - Ok(snapshot) => { - store_handler.use_snapshot(snapshot); - } - } - }; - let mut server = Self { - listener, - store_handler: Arc::new(store_handler), - client_handler, - task_manager: Arc::new(TaskManager::new()), - config: config.clone(), - cluster: None, - }; - - if config.is_clustered() { + let runtime = if config.is_clustered() { let cluster_client_handler = Arc::new(ClientHandler::new(1024)); let cluster_addr = config .cluster_addr @@ -865,12 +847,35 @@ impl Server { let cluster_listener = ListenerStreamOrAddress::new(cluster_addr.to_string(), cluster_client_handler) .await?; - let service_addr = server.listener.local_addr()?; - server.cluster = - Some(build_cluster_runtime(config, service_addr, cluster_listener).await?); - } + let service_addr = listener.local_addr()?; + StoreRuntime::Cluster( + build_cluster_runtime(config, service_addr, cluster_listener).await?, + ) + } else { + let mut store_handler = StoreHandler::new(Arc::new(AtomicBool::new(false))); + if let Some(persist_location) = &config.common.persist_location { + match Persistence::load_snapshot(persist_location, config.common.enable_mmap) { + Err(e) => { + log::error!("Failed to load snapshot from persist location {e}"); + if config.common.fail_on_startup_if_persist_load_fails { + return Err(std::io::Error::other(e.to_string())); + } + } + Ok(snapshot) => { + store_handler.use_snapshot(snapshot); + } + } + }; + StoreRuntime::Standalone(Arc::new(store_handler)) + }; - Ok(server) + Ok(Self { + listener, + runtime, + client_handler, + task_manager: Arc::new(TaskManager::new()), + config: config.clone(), + }) } pub fn client_handler(&self) -> Arc { @@ -882,7 +887,7 @@ impl Server { } pub fn cluster_local_addr(&self) -> Option { - self.cluster.as_ref().map(|cluster| cluster.raft_addr) + self.runtime.cluster_local_addr() } /// initializes a server using server configuration diff --git a/ahnlich/db/src/server/mod.rs b/ahnlich/db/src/server/mod.rs index e07b998c0..8e5e3b2fc 100644 --- a/ahnlich/db/src/server/mod.rs +++ b/ahnlich/db/src/server/mod.rs @@ -3,3 +3,4 @@ pub mod cluster_mutations; pub mod cluster_queries; pub mod cluster_tasks; pub mod handler; +pub mod store_runtime; diff --git a/ahnlich/db/src/server/store_runtime.rs b/ahnlich/db/src/server/store_runtime.rs new file mode 100644 index 000000000..95219bb1a --- /dev/null +++ b/ahnlich/db/src/server/store_runtime.rs @@ -0,0 +1,50 @@ +use crate::engine::store::StoreHandler; +use crate::errors::ServerError; +use crate::server::cluster::ClusterRuntime; +use std::net::SocketAddr; +use std::sync::Arc; + +#[derive(Debug)] +pub(crate) enum StoreRuntime { + Standalone(Arc), + Cluster(ClusterRuntime), +} + +impl StoreRuntime { + pub(crate) fn cluster(&self) -> Option<&ClusterRuntime> { + match self { + Self::Standalone(_) => None, + Self::Cluster(cluster) => Some(cluster), + } + } + + pub(crate) fn standalone_store_handler(&self) -> Option<&Arc> { + match self { + Self::Standalone(store_handler) => Some(store_handler), + Self::Cluster(_) => None, + } + } + + #[allow(clippy::result_large_err)] + pub(crate) fn with_store_handler( + &self, + f: impl FnOnce(&StoreHandler) -> Result, + ) -> Result { + match self { + Self::Standalone(store_handler) => f(store_handler).map_err(Into::into), + Self::Cluster(cluster) => cluster + .state_machine + .with_handler(|handler| f(handler.store_handler())) + .map_err(|err| { + tonic::Status::internal(format!( + "failed to access clustered state machine: {err}" + )) + })? + .map_err(Into::into), + } + } + + pub(crate) fn cluster_local_addr(&self) -> Option { + self.cluster().map(|cluster| cluster.raft_addr) + } +} diff --git a/ahnlich/db/src/tests/cluster_tests.rs b/ahnlich/db/src/tests/cluster_tests.rs index 593608aef..dd0346688 100644 --- a/ahnlich/db/src/tests/cluster_tests.rs +++ b/ahnlich/db/src/tests/cluster_tests.rs @@ -305,6 +305,21 @@ async fn test_three_node_cluster_replication_and_follower_list_stores_error() { 1 ); + let leader_store_list = leader_client + .list_stores(tonic::Request::new(db_query_types::ListStores {})) + .await + .expect("leader ListStores should succeed") + .into_inner(); + assert_eq!(leader_store_list.stores.len(), 1); + + let replicated_store = &leader_store_list.stores[0]; + assert_eq!(replicated_store.name, "replicated-store"); + assert_eq!(replicated_store.dimension, 3); + assert_eq!(replicated_store.len, 1); + assert!(replicated_store.size_in_bytes > 0); + assert!(replicated_store.non_linear_indices.is_empty()); + assert!(replicated_store.predicate_indices.is_empty()); + let list_stores_error = follower_one_client .list_stores(tonic::Request::new(db_query_types::ListStores {})) .await From 368eb093463bc6327ec9550b37739d4159c871ed Mon Sep 17 00:00:00 2001 From: Jim Ezesinachi Date: Tue, 16 Jun 2026 12:16:59 +0100 Subject: [PATCH 4/5] used macros for mutation calls Signed-off-by: Jim Ezesinachi --- ahnlich/Cargo.toml | 1 + ahnlich/db/Cargo.toml | 2 +- ahnlich/db/src/replication/mod.rs | 124 +++++++++++---------- ahnlich/db/src/server/cluster_mutations.rs | 26 ++++- ahnlich/db/src/server/handler.rs | 61 +++++----- ahnlich/replication/Cargo.toml | 2 +- 6 files changed, 120 insertions(+), 96 deletions(-) diff --git a/ahnlich/Cargo.toml b/ahnlich/Cargo.toml index 85ba5c2b2..ed3c836fe 100644 --- a/ahnlich/Cargo.toml +++ b/ahnlich/Cargo.toml @@ -54,6 +54,7 @@ prost = "0.13" pulp = "0.21.4" smallvec = "1.13" openraft = { version = "0.9", features = ["serde", "storage-v2"] } +bitcode = { version = "0.6", features = ["serde"] } [profile.release] lto = true diff --git a/ahnlich/db/Cargo.toml b/ahnlich/db/Cargo.toml index c87079309..2bc1ce194 100644 --- a/ahnlich/db/Cargo.toml +++ b/ahnlich/db/Cargo.toml @@ -45,7 +45,7 @@ tonic.workspace = true prost.workspace = true openraft.workspace = true ahnlich-replication = { path = "../replication", version = "*" } -bitcode = { version = "0.6", features = ["serde"] } +bitcode.workspace = true [dev-dependencies] futures.workspace = true diff --git a/ahnlich/db/src/replication/mod.rs b/ahnlich/db/src/replication/mod.rs index b271240ba..550417cd3 100644 --- a/ahnlich/db/src/replication/mod.rs +++ b/ahnlich/db/src/replication/mod.rs @@ -10,12 +10,10 @@ use ahnlich_replication::types::{DbCommand, DbResponse}; use ahnlich_types::db::query; use openraft::{StorageError, StorageIOError}; use prost::Message; -use serde::Serialize; use utils::persistence::AhnlichPersistenceUtils; use crate::engine::operations; use crate::engine::store::{StoreHandler, Stores}; -use crate::errors::ServerError; openraft::declare_raft_types!( pub DbTypeConfig: @@ -46,36 +44,50 @@ impl DbStateMachine { } } -fn decode_payload( - command_name: &str, - payload: &[u8], -) -> Result> { - M::decode(payload).map_err(|err| StorageError::IO { - source: StorageIOError::read_state_machine(&std::io::Error::other(format!( - "failed to decode {command_name} raft payload: {err}", - ))), - }) +macro_rules! command_name { + ($ty:ty) => {{ + let full = stringify!($ty); + full.rsplit("::").next().unwrap_or(full) + }}; } -fn encode_raw_result( - command_name: &str, - result: &T, -) -> Result> { - bitcode::serialize(result) - .map(DbResponse::Bytes) - .map_err(|err| StorageError::IO { - source: StorageIOError::write_state_machine(&std::io::Error::other(format!( - "failed to encode {command_name} raw result: {err}", +macro_rules! decode_payload { + ($ty:ty, $payload:expr) => {{ + let command_name = command_name!($ty); + + <$ty>::decode($payload.as_slice()).map_err(|err| StorageError::IO { + source: StorageIOError::read_state_machine(&std::io::Error::other(format!( + "failed to decode {command_name} raft payload: {err}", ))), }) + }}; } -fn operation_error(command_name: &str, err: ServerError) -> StorageError { - StorageError::IO { - source: StorageIOError::write_state_machine(&std::io::Error::other(format!( - "{command_name} apply failed: {err}", - ))), - } +macro_rules! encode_raw_result { + ($ty:ty, $result:expr) => {{ + let command_name = command_name!($ty); + + bitcode::serialize($result) + .map(DbResponse::Bytes) + .map_err(|err| StorageError::IO { + source: StorageIOError::write_state_machine(&std::io::Error::other(format!( + "failed to encode {command_name} raw result: {err}", + ))), + }) + }}; +} + +macro_rules! operation_error { + ($ty:ty, $err:expr) => {{ + let command_name = command_name!($ty); + + StorageError::IO { + source: StorageIOError::write_state_machine(&std::io::Error::other(format!( + "{command_name} apply failed: {}", + $err, + ))), + } + }}; } impl StateMachineHandler for DbStateMachine { @@ -84,74 +96,70 @@ impl StateMachineHandler for DbStateMachine { fn apply(&mut self, data: &DbCommand) -> Result> { match data { DbCommand::CreateStore(payload) => { - let params = decode_payload::("CreateStore", payload)?; + let params = decode_payload!(query::CreateStore, payload)?; operations::create_store(&self.store_handler, params) .map(|_| DbResponse::Unit) - .map_err(|err| operation_error("CreateStore", err)) + .map_err(|err| operation_error!(query::CreateStore, err)) } DbCommand::CreatePredIndex(payload) => { - let params = decode_payload::("CreatePredIndex", payload)?; + let params = decode_payload!(query::CreatePredIndex, payload)?; let created = operations::create_pred_index(&self.store_handler, params) - .map_err(|err| operation_error("CreatePredIndex", err))?; + .map_err(|err| operation_error!(query::CreatePredIndex, err))?; - encode_raw_result("CreatePredIndex", &created) + encode_raw_result!(query::CreatePredIndex, &created) } DbCommand::CreateNonLinearAlgorithmIndex(payload) => { - let params = decode_payload::( - "CreateNonLinearAlgorithmIndex", - payload, - )?; + let params = decode_payload!(query::CreateNonLinearAlgorithmIndex, payload)?; let created = operations::create_non_linear_algorithm_index(&self.store_handler, params) - .map_err(|err| operation_error("CreateNonLinearAlgorithmIndex", err))?; + .map_err(|err| { + operation_error!(query::CreateNonLinearAlgorithmIndex, err) + })?; - encode_raw_result("CreateNonLinearAlgorithmIndex", &created) + encode_raw_result!(query::CreateNonLinearAlgorithmIndex, &created) } DbCommand::Set(payload) => { - let params = decode_payload::("Set", payload)?; + let params = decode_payload!(query::Set, payload)?; let upsert = operations::set(&self.store_handler, params) - .map_err(|err| operation_error("Set", err))?; + .map_err(|err| operation_error!(query::Set, err))?; - encode_raw_result("Set", &upsert) + encode_raw_result!(query::Set, &upsert) } DbCommand::DelKey(payload) => { - let params = decode_payload::("DelKey", payload)?; + let params = decode_payload!(query::DelKey, payload)?; let deleted = operations::del_key(&self.store_handler, params) - .map_err(|err| operation_error("DelKey", err))?; + .map_err(|err| operation_error!(query::DelKey, err))?; - encode_raw_result("DelKey", &deleted) + encode_raw_result!(query::DelKey, &deleted) } DbCommand::DelPred(payload) => { - let params = decode_payload::("DelPred", payload)?; + let params = decode_payload!(query::DelPred, payload)?; let deleted = operations::del_pred(&self.store_handler, params) - .map_err(|err| operation_error("DelPred", err))?; + .map_err(|err| operation_error!(query::DelPred, err))?; - encode_raw_result("DelPred", &deleted) + encode_raw_result!(query::DelPred, &deleted) } DbCommand::DropPredIndex(payload) => { - let params = decode_payload::("DropPredIndex", payload)?; + let params = decode_payload!(query::DropPredIndex, payload)?; let deleted = operations::drop_pred_index(&self.store_handler, params) - .map_err(|err| operation_error("DropPredIndex", err))?; + .map_err(|err| operation_error!(query::DropPredIndex, err))?; - encode_raw_result("DropPredIndex", &deleted) + encode_raw_result!(query::DropPredIndex, &deleted) } DbCommand::DropNonLinearAlgorithmIndex(payload) => { - let params = decode_payload::( - "DropNonLinearAlgorithmIndex", - payload, - )?; + let params = decode_payload!(query::DropNonLinearAlgorithmIndex, payload)?; let deleted = operations::drop_non_linear_algorithm_index(&self.store_handler, params) - .map_err(|err| operation_error("DropNonLinearAlgorithmIndex", err))?; + .map_err(|err| operation_error!(query::DropNonLinearAlgorithmIndex, err))?; - encode_raw_result("DropNonLinearAlgorithmIndex", &deleted) + encode_raw_result!(query::DropNonLinearAlgorithmIndex, &deleted) } DbCommand::DropStore(payload) => { - let params = decode_payload::("DropStore", payload)?; + let params = decode_payload!(query::DropStore, payload)?; let dropped = operations::drop_store(&self.store_handler, params) - .map_err(|err| operation_error("DropStore", err))?; + .map_err(|err| operation_error!(query::DropStore, err))?; - encode_raw_result("DropStore", &dropped) + encode_raw_result!(query::DropStore, &dropped) } } } diff --git a/ahnlich/db/src/server/cluster_mutations.rs b/ahnlich/db/src/server/cluster_mutations.rs index 36e4ce4ca..85e17d657 100644 --- a/ahnlich/db/src/server/cluster_mutations.rs +++ b/ahnlich/db/src/server/cluster_mutations.rs @@ -5,6 +5,22 @@ use openraft::error::RaftError; use serde::de::DeserializeOwned; use std::any::{Any, TypeId}; +macro_rules! command_name { + ($ty:ty) => {{ + let full = stringify!($ty); + full.rsplit("::").next().unwrap_or(full) + }}; +} + +macro_rules! submit_db_command { + ($cluster:expr, $query_ty:ty, $params:expr, $builder:path) => {{ + crate::server::cluster_mutations::submit_db_command_inner::<$query_ty, _>( + $cluster, $params, $builder, + ) + }}; +} +pub(crate) use submit_db_command; + pub(crate) fn map_client_write_error( command_name: &str, err: RaftError>, @@ -40,14 +56,18 @@ async fn submit_raw_db_command( .map_err(|err| map_client_write_error(command_name, err)) } -pub(crate) async fn submit_db_command( +pub(crate) async fn submit_db_command_inner( cluster: Option<&ClusterRuntime>, - command_name: &str, - command: DbCommand, + params: Q, + command_builder: impl FnOnce(Vec) -> DbCommand, ) -> Result where + Q: prost::Message, T: DeserializeOwned + 'static, { + let command_name = command_name!(Q); + let command = command_builder(params.encode_to_vec()); + match submit_raw_db_command(cluster, command_name, command).await? { DbResponse::Bytes(bytes) => bitcode::deserialize(bytes.as_slice()).map_err(|err| { tonic::Status::internal(format!("failed to decode {command_name} raw result: {err}",)) diff --git a/ahnlich/db/src/server/handler.rs b/ahnlich/db/src/server/handler.rs index 205e1e8e9..83e70fe4f 100644 --- a/ahnlich/db/src/server/handler.rs +++ b/ahnlich/db/src/server/handler.rs @@ -20,7 +20,6 @@ use ahnlich_types::shared::info::ErrorResponse; use ahnlich_types::db::{pipeline, query, server}; use ahnlich_types::{client as types_client, utils as types_utils}; -use prost::Message; use std::future::Future; use std::io::Result as IoResult; use std::net::SocketAddr; @@ -60,10 +59,11 @@ impl DbService for Server { match &self.runtime { StoreRuntime::Cluster(cluster) => { - let (): () = submit_db_command( + let (): () = submit_db_command!( Some(cluster), - "CreateStore", - DbCommand::CreateStore(params.encode_to_vec()), + query::CreateStore, + params, + DbCommand::CreateStore ) .await?; } @@ -199,10 +199,11 @@ impl DbService for Server { let created_indexes = (match &self.runtime { StoreRuntime::Cluster(cluster) => { - submit_db_command( + submit_db_command!( Some(cluster), - "CreatePredIndex", - DbCommand::CreatePredIndex(params.encode_to_vec()), + query::CreatePredIndex, + params, + DbCommand::CreatePredIndex ) .await? } @@ -225,10 +226,11 @@ impl DbService for Server { let created_indexes = (match &self.runtime { StoreRuntime::Cluster(cluster) => { - submit_db_command( + submit_db_command!( Some(cluster), - "CreateNonLinearAlgorithmIndex", - DbCommand::CreateNonLinearAlgorithmIndex(params.encode_to_vec()), + query::CreateNonLinearAlgorithmIndex, + params, + DbCommand::CreateNonLinearAlgorithmIndex ) .await? } @@ -251,10 +253,11 @@ impl DbService for Server { let deleted_count = (match &self.runtime { StoreRuntime::Cluster(cluster) => { - submit_db_command( + submit_db_command!( Some(cluster), - "DropPredIndex", - DbCommand::DropPredIndex(params.encode_to_vec()), + query::DropPredIndex, + params, + DbCommand::DropPredIndex ) .await? } @@ -275,10 +278,11 @@ impl DbService for Server { let deleted_count = (match &self.runtime { StoreRuntime::Cluster(cluster) => { - submit_db_command( + submit_db_command!( Some(cluster), - "DropNonLinearAlgorithmIndex", - DbCommand::DropNonLinearAlgorithmIndex(params.encode_to_vec()), + query::DropNonLinearAlgorithmIndex, + params, + DbCommand::DropNonLinearAlgorithmIndex ) .await? } @@ -299,12 +303,7 @@ impl DbService for Server { let deleted_count = (match &self.runtime { StoreRuntime::Cluster(cluster) => { - submit_db_command( - Some(cluster), - "DelKey", - DbCommand::DelKey(params.encode_to_vec()), - ) - .await? + submit_db_command!(Some(cluster), query::DelKey, params, DbCommand::DelKey).await? } StoreRuntime::Standalone(store_handler) => operations::del_key(store_handler, params)?, }) as u64; @@ -321,12 +320,8 @@ impl DbService for Server { let deleted_count = (match &self.runtime { StoreRuntime::Cluster(cluster) => { - submit_db_command( - Some(cluster), - "DelPred", - DbCommand::DelPred(params.encode_to_vec()), - ) - .await? + submit_db_command!(Some(cluster), query::DelPred, params, DbCommand::DelPred) + .await? } StoreRuntime::Standalone(store_handler) => operations::del_pred(store_handler, params)?, }) as u64; @@ -343,10 +338,11 @@ impl DbService for Server { let deleted_count = (match &self.runtime { StoreRuntime::Cluster(cluster) => { - submit_db_command( + submit_db_command!( Some(cluster), - "DropStore", - DbCommand::DropStore(params.encode_to_vec()), + query::DropStore, + params, + DbCommand::DropStore ) .await? } @@ -440,8 +436,7 @@ impl DbService for Server { let set = match &self.runtime { StoreRuntime::Cluster(cluster) => { - submit_db_command(Some(cluster), "Set", DbCommand::Set(params.encode_to_vec())) - .await? + submit_db_command!(Some(cluster), query::Set, params, DbCommand::Set).await? } StoreRuntime::Standalone(store_handler) => operations::set(store_handler, params)?, }; diff --git a/ahnlich/replication/Cargo.toml b/ahnlich/replication/Cargo.toml index 1de1cd568..5dc96873d 100644 --- a/ahnlich/replication/Cargo.toml +++ b/ahnlich/replication/Cargo.toml @@ -11,7 +11,7 @@ clap.workspace = true serde.workspace = true serde_json.workspace = true thiserror.workspace = true -bitcode = { version = "0.6", features = ["serde"] } +bitcode.workspace = true utils = { path = "../utils", version = "*" } tokio.workspace = true futures.workspace = true From 2c9610e7126544eaef0e297410fa2df9c0d50c7a Mon Sep 17 00:00:00 2001 From: Diretnan Domnan Date: Wed, 17 Jun 2026 02:17:52 +0200 Subject: [PATCH 5/5] fix: simplify Claude code review to use @claude mention trigger Change from automatic PR event trigger to comment-based trigger. This is the documented, working pattern for Claude Code Action. Users comment '@claude' on a PR to trigger a review. Claude will read CLAUDE.md for review guidelines. Testing this simpler approach first to verify CLAUDE.md integration works. --- .github/workflows/claude-code-review.yml | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 09add27cf..08cee9cf2 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -1,30 +1,24 @@ name: Claude Code Review on: - pull_request: - types: [opened, synchronize, ready_for_review, reopened] + issue_comment: + types: [created] permissions: contents: read pull-requests: write + issues: write id-token: write jobs: claude-review: - # Skip draft PRs - if: github.event.pull_request.draft == false + # Only run when @claude is mentioned in a comment on a PR + if: | + github.event.issue.pull_request && + contains(github.event.comment.body, '@claude') runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - id-token: write steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - fetch-depth: 0 - - uses: anthropics/claude-code-action@v1 with: github_token: ${{ secrets.GITHUB_TOKEN }}