1use std::sync::Arc;
2use std::time::Duration;
3
4use log::{info, warn};
5use parking_lot::RwLock;
6
7#[cfg(feature = "vector")]
8use linkrs_api::api_core::VectorApi;
9use linkrs_api::api_core::{QueryApi, SyncApi};
10use linkrs_core::event_dispatch::EventSubscriptions;
11use linkrs_core::metadata::SchemaManager;
12use linkrs_metrics::StatsManager;
13use linkrs_query::query_manager::QueryManager;
14use linkrs_query::SessionEvent;
15#[cfg(feature = "vector")]
16use linkrs_sync::backend::VectorBackend;
17use linkrs_transaction::TransactionManager;
18
19use super::GraphService;
20use crate::auth::AuthenticatorFactory;
21use crate::config::Config;
22use crate::permission::{PermissionManager, GOD_SPACE_ID};
23use crate::query::executor::streaming::pool::SharedScheduler;
24use crate::query::executor::streaming::query_registry::QueryRegistry;
25use crate::query::optimizer::PartitioningConfig;
26use crate::session::GraphSessionManager;
27use crate::storage::{
28 StorageClient, StorageOperationContextOps, StorageSchemaContextOps, StorageSyncContextOps,
29};
30
31impl<
32 S: StorageClient
33 + StorageSchemaContextOps
34 + StorageSyncContextOps
35 + StorageOperationContextOps
36 + Clone
37 + 'static,
38 > GraphService<S>
39{
40 pub async fn new(config: Config, storage: Arc<S>) -> Arc<Self> {
42 #[cfg(feature = "vector")]
43 return Self::create_service(config, storage, None, true, None, None).await;
44 #[cfg(not(feature = "vector"))]
45 return Self::create_service(config, storage, None, true, None).await;
46 }
47
48 pub async fn new_for_test(config: Config, storage: Arc<S>) -> Arc<Self> {
50 #[cfg(feature = "vector")]
51 return Self::create_service(config, storage, None, false, None, None).await;
52 #[cfg(not(feature = "vector"))]
53 return Self::create_service(config, storage, None, false, None).await;
54 }
55
56 pub async fn new_with_transaction_manager(
58 config: Config,
59 storage: Arc<S>,
60 transaction_manager: Arc<TransactionManager>,
61 ) -> Arc<Self> {
62 #[cfg(feature = "vector")]
63 return Self::create_service(config, storage, Some(transaction_manager), true, None, None)
64 .await;
65 #[cfg(not(feature = "vector"))]
66 return Self::create_service(config, storage, Some(transaction_manager), true, None).await;
67 }
68
69 pub async fn new_with_transaction_manager_and_stats(
71 config: Config,
72 storage: Arc<S>,
73 transaction_manager: Arc<TransactionManager>,
74 stats_manager: Arc<StatsManager>,
75 ) -> Arc<Self> {
76 #[cfg(feature = "vector")]
77 {
78 Self::create_service(
79 config,
80 storage,
81 Some(transaction_manager),
82 true,
83 Some(stats_manager),
84 None,
85 )
86 .await
87 }
88 #[cfg(not(feature = "vector"))]
89 {
90 Self::create_service(
91 config,
92 storage,
93 Some(transaction_manager),
94 true,
95 Some(stats_manager),
96 )
97 .await
98 }
99 }
100
101 #[cfg(feature = "vector")]
103 pub async fn with_shared_vector_backend(
104 config: Config,
105 storage: Arc<S>,
106 transaction_manager: Arc<TransactionManager>,
107 stats_manager: Arc<StatsManager>,
108 backend: VectorBackend,
109 ) -> Arc<Self> {
110 Self::create_service(
111 config,
112 storage,
113 Some(transaction_manager),
114 true,
115 Some(stats_manager),
116 Some(backend),
117 )
118 .await
119 }
120
121 async fn create_service(
127 config: Config,
128 storage: Arc<S>,
129 transaction_manager: Option<Arc<TransactionManager>>,
130 start_cleanup_task: bool,
131 external_stats_manager: Option<Arc<StatsManager>>,
132 #[cfg(feature = "vector")] shared_vector_backend: Option<VectorBackend>,
133 ) -> Arc<Self> {
134 crate::query::executor::streaming::operators::source_operator::set_column_block_enabled(
135 config.common.columnar.column_block_enabled,
136 );
137
138 let session_idle_timeout =
139 Duration::from_secs(config.server.auth.session_idle_timeout_secs.max(1));
140 let session_events = Arc::new(EventSubscriptions::<SessionEvent>::new());
141 let query_manager = Arc::new(QueryManager::new_with_shared(session_events.clone()));
142 query_manager.set_slow_query_threshold_ms(config.monitoring.slow_query_threshold_ms as i64);
143 let session_manager = GraphSessionManager::new_with_shared(
144 format!("{}:{}", config.database.host, config.database.port),
145 config.database.max_sessions,
146 session_idle_timeout,
147 session_events.clone(),
148 );
149 session_manager.set_default_auto_commit(config.common.transaction.auto_commit);
150
151 if start_cleanup_task {
152 session_manager.start_cleanup_task().await;
153 }
154
155 let schema_manager: Option<Arc<SchemaManager>> = storage.get_schema_manager();
156
157 let stats_manager = if let Some(ext_stats) = external_stats_manager {
158 ext_stats
159 } else {
160 let slow_query_config = config.to_slow_query_config();
161 let m = &config.monitoring;
162 Arc::new(
163 StatsManager::with_slow_query_logger(
164 m.enabled,
165 m.memory_cache_size,
166 m.slow_query_threshold_ms * 1000,
167 slow_query_config,
168 )
169 .expect("Failed to create StatsManager with slow query logger"),
170 )
171 };
172
173 let runtime = crate::config::RuntimeConfig::file(config.storage_path());
174 let cost_profile = config
175 .common
176 .optimizer
177 .storage_cost_profile
178 .resolve_for_runtime(&runtime);
179 let cost_config = linkrs_api::api_core::cost_config_for_profile(cost_profile);
180 info!(
181 "Optimizer cost profile resolved to {:?} (random_page_cost={})",
182 cost_profile, cost_config.random_page_cost,
183 );
184 let mut optimizer_engine = crate::query::OptimizerEngine::new(cost_config);
185 optimizer_engine.set_partitioning_config(Self::partitioning_config_from(&config));
186 {
187 let rules = &config.common.optimizer.rules;
188 for name in rules
189 .disabled_rules
190 .iter()
191 .chain(rules.enabled_rules.iter())
192 {
193 if !Self::is_known_optimizer_rule(name) {
194 log::warn!("Unknown optimizer rule name '{name}' in [optimizer.rules]");
195 }
196 }
197 optimizer_engine.set_rule_selection(&rules.disabled_rules, &rules.enabled_rules);
198 }
199 let shared_scheduler = Arc::new(SharedScheduler::new(
200 optimizer_engine.partitioning_config().max_workers.max(1),
201 ));
202 let optimizer_engine = Arc::new(optimizer_engine);
203 let query_registry = Arc::new(QueryRegistry::new());
204 query_manager.set_query_registry(Arc::clone(&query_registry));
205 info!(
206 "Shared query scheduler created with {} worker(s)",
207 shared_scheduler.max_workers()
208 );
209
210 #[cfg(feature = "vector")]
211 let (query_api, vector_api) = if config.is_vector_enabled() {
212 let backend = match shared_vector_backend {
213 Some(backend) => backend,
214 None => {
215 let engine = simvec::LocalVectorEngine::open(config.vector_data_dir())
216 .unwrap_or_else(|_| panic!("Failed to open local vector engine"));
217 if let Some(hnsw) =
218 linkrs_api::vector_config::local_hnsw_config(&config.vector_config().local)
219 {
220 engine.set_default_hnsw_config(hnsw);
221 }
222 if let Some(ivf) =
223 linkrs_api::vector_config::local_ivf_config(&config.vector_config().local)
224 {
225 engine.set_default_ivf_config(ivf);
226 }
227 if let Some(quant) = linkrs_api::vector_config::local_quantization_config(
228 &config.vector_config().local,
229 ) {
230 engine.set_default_quantization_config(quant);
231 }
232 linkrs_sync::backend::VectorBackend::local(engine)
233 }
234 };
235
236 match QueryApi::with_vector_backend(
237 Arc::new(RwLock::new((*storage).clone())),
238 stats_manager.clone(),
239 backend.clone(),
240 schema_manager.clone(),
241 )
242 .await
243 {
244 Ok(mut api) => {
245 api = api
246 .with_statistics_settings(
247 config.common.optimizer.statistics_sample_limit,
248 config.common.optimizer.statistics_min_epoch_delta,
249 )
250 .with_default_cost_profile_label(format!("{cost_profile:?}"))
251 .with_space_cost_profiles(
252 &config.common.optimizer.space_cost_profiles,
253 &runtime,
254 )
255 .with_password_history_depth(
256 config.server.security.password_policy.history_size,
257 );
258 api.install_shared_scheduler(shared_scheduler.clone(), query_registry.clone());
259 api.install_query_manager(Arc::clone(&query_manager));
260 let vector_api = storage.get_sync_manager().and_then(|sync_manager| {
266 sync_manager
267 .vector_coordinator()
268 .cloned()
269 .map(|coordinator| {
270 Arc::new(VectorApi::new(backend, coordinator, sync_manager))
271 })
272 });
273 (Arc::new(RwLock::new(api)), vector_api)
274 }
275 Err(e) => {
276 warn!(
277 "Failed to initialize vector search, falling back to basic QueryApi: {}",
278 e
279 );
280 let mut api = Self::build_query_api(
281 &storage,
282 &stats_manager,
283 schema_manager.as_ref(),
284 optimizer_engine.clone(),
285 &config.common.optimizer,
286 cost_profile,
287 &runtime,
288 config.server.security.password_policy.history_size,
289 );
290 api.install_shared_scheduler(shared_scheduler.clone(), query_registry.clone());
291 api.install_query_manager(Arc::clone(&query_manager));
292 (Arc::new(RwLock::new(api)), None)
293 }
294 }
295 } else {
296 let mut api = Self::build_query_api(
297 &storage,
298 &stats_manager,
299 schema_manager.as_ref(),
300 optimizer_engine.clone(),
301 &config.common.optimizer,
302 cost_profile,
303 &runtime,
304 config.server.security.password_policy.history_size,
305 );
306 api.install_shared_scheduler(shared_scheduler.clone(), query_registry.clone());
307 api.install_query_manager(Arc::clone(&query_manager));
308 (Arc::new(RwLock::new(api)), None)
309 };
310
311 #[cfg(not(feature = "vector"))]
312 let query_api = {
313 let mut api = Self::build_query_api(
314 &storage,
315 &stats_manager,
316 schema_manager.as_ref(),
317 optimizer_engine.clone(),
318 &config.common.optimizer,
319 cost_profile,
320 &runtime,
321 config.server.security.password_policy.history_size,
322 );
323 api.install_shared_scheduler(shared_scheduler.clone(), query_registry.clone());
324 api.install_query_manager(Arc::clone(&query_manager));
325 Arc::new(RwLock::new(api))
326 };
327
328 if start_cleanup_task {
329 Self::spawn_startup_statistics_load(query_api.clone(), storage.clone());
330 }
331
332 let permission_manager = Arc::new(PermissionManager::new());
333 Self::ensure_admin_seed_and_rebuild(&storage, &permission_manager, &config);
334
335 let authenticator =
336 AuthenticatorFactory::create_with_storage(&config.server.auth, Arc::clone(&storage));
337
338 let sync_api = storage
339 .get_sync_manager()
340 .map(|sync_manager| Arc::new(SyncApi::new(sync_manager)));
341
342 let service = Self {
343 session_manager,
344 query_api,
345 authenticator,
346 permission_manager,
347 stats_manager,
348 storage,
349 #[cfg(feature = "vector")]
350 vector_api,
351 sync_api,
352 transaction_manager,
353 shared_scheduler,
354 query_registry,
355 query_manager,
356 progress_rows_interval: config.monitoring.progress_report_rows_interval,
357 next_query_id: std::sync::atomic::AtomicU64::new(1),
358 bootstrap_config: config.server.bootstrap.clone(),
359 security_config: config.server.security.clone(),
360 };
361 Arc::new(service)
362 }
363
364 fn build_query_api(
367 storage: &Arc<S>,
368 stats_manager: &Arc<StatsManager>,
369 schema_manager: Option<&Arc<SchemaManager>>,
370 optimizer_engine: Arc<crate::query::OptimizerEngine>,
371 optimizer_config: &crate::config::OptimizerConfig,
372 cost_profile: crate::config::StorageCostProfile,
373 runtime: &crate::config::RuntimeConfig,
374 password_history_depth: usize,
375 ) -> QueryApi<S> {
376 let inner = Arc::new(RwLock::new((**storage).clone()));
377 QueryApi::with_optimizer_engine(
378 inner,
379 stats_manager.clone(),
380 optimizer_engine,
381 schema_manager.cloned(),
382 )
383 .with_statistics_settings(
384 optimizer_config.statistics_sample_limit,
385 optimizer_config.statistics_min_epoch_delta,
386 )
387 .with_default_cost_profile_label(format!("{cost_profile:?}"))
388 .with_space_cost_profiles(&optimizer_config.space_cost_profiles, runtime)
389 .with_password_history_depth(password_history_depth)
390 }
391
392 fn is_known_optimizer_rule(name: &str) -> bool {
397 let trimmed = name.strip_suffix("Rule").unwrap_or(name);
398 let registry = crate::query::optimizer::heuristic::rule_enum::RuleRegistry::default();
399 let known = registry
400 .iter()
401 .any(|rule| rule.name().strip_suffix("Rule") == Some(trimmed));
402 known
403 }
404
405 fn partitioning_config_from(config: &Config) -> PartitioningConfig {
408 let parallel = &config.common.parallel;
409 PartitioningConfig {
410 enabled: parallel.enabled,
411 min_rows_per_partition: parallel.min_rows_per_partition,
412 max_partitions: parallel.max_partitions,
413 vertex_id_range: parallel.vertex_id_range(),
414 max_workers: parallel.workers,
415 max_buffered_chunks: parallel.max_buffered_chunks,
416 fixed_overhead_rows: 0,
419 per_partition_rows: 0,
420 exchange_row_per_mille: 0,
421 }
422 }
423
424 fn spawn_startup_statistics_load(query_api: Arc<RwLock<QueryApi<S>>>, storage: Arc<S>) {
427 tokio::spawn(async move {
428 let spaces = match storage.list_spaces() {
429 Ok(spaces) => spaces,
430 Err(error) => {
431 warn!("Startup statistics load: failed to list spaces: {}", error);
432 return;
433 }
434 };
435 for space in spaces {
436 let result = query_api
437 .read()
438 .collect_statistics(&space.space_name, false);
439 match result {
440 Ok(()) => info!("Startup statistics loaded for space '{}'", space.space_name),
441 Err(error) => warn!(
442 "Startup statistics load failed for space '{}': {}",
443 space.space_name, error
444 ),
445 }
446 }
447 });
448 }
449
450 fn ensure_admin_seed_and_rebuild(
462 storage: &Arc<S>,
463 permission_manager: &Arc<PermissionManager>,
464 config: &Config,
465 ) {
466 let mut handle = (**storage).clone();
467 if handle.list_users().is_empty() {
468 let username = config.server.auth.default_username.clone();
469 let password = config.server.auth.bootstrap_password.clone();
470 if !username.is_empty() && !password.is_empty() {
471 match linkrs_core::types::UserInfo::new(username.clone(), password) {
472 Ok(info) => {
473 if let Err(error) = handle.create_user(&info) {
474 warn!("Admin seed failed for '{}': {}", username, error);
475 } else if let Err(error) =
476 handle.grant_role(&username, GOD_SPACE_ID, linkrs_core::RoleType::God)
477 {
478 warn!("Admin seed grant failed for '{}': {}", username, error);
479 } else {
480 info!("Seeded default admin '{}'", username);
481 }
482 }
483 Err(error) => warn!("Admin seed hashing failed: {}", error),
484 }
485 }
486 }
487 let roles = handle.list_all_user_roles();
488 if !roles.is_empty() {
489 permission_manager.rebuild_from_snapshot(roles);
490 }
491 }
492}