1use std::convert::Infallible;
2use std::sync::Arc;
3use std::time::Duration;
4
5use crate::metrics::MetricOutcomes;
6use crate::services::autoscaling::{AutoscalingMetricService, AutoscalingMetrics};
7use crate::services::buffer::{
8 ObservableEnvelopeBuffer, PartitionedEnvelopeBuffer, ProjectKeyPair,
9};
10use crate::services::cogs::{CogsService, CogsServiceRecorder};
11use crate::services::config_reload::ConfigReloadService;
12use crate::services::global_config::{
13 GlobalConfigHandle, GlobalConfigManager, GlobalConfigService,
14};
15use crate::services::health_check::{HealthCheck, HealthCheckService};
16use crate::services::metrics::RouterService;
17#[cfg(feature = "processing")]
18use crate::services::objectstore::{self, Objectstore};
19use crate::services::outcome::{
20 ClientReportOutcomeProducerService, NullOutcomeProducerService, OutcomeProducerService,
21 TrackOutcome,
22};
23use crate::services::processor::{
24 self, EnvelopeProcessor, EnvelopeProcessorService, EnvelopeProcessorServicePool,
25};
26use crate::services::projects::cache::{ProjectCacheHandle, ProjectCacheService};
27use crate::services::projects::source::ProjectSource;
28use crate::services::proxy_processor::{ProxyAddrs, ProxyProcessorService};
29use crate::services::relays::{RelayCache, RelayCacheService};
30use crate::services::stats::RelayStats;
31#[cfg(feature = "processing")]
32use crate::services::store::{StoreService, StoreServicePool};
33use crate::services::upload::{self, Upload};
34use crate::services::upstream::{UpstreamRelay, UpstreamRelayService};
35use crate::utils::{MemoryChecker, MemoryStat, ThreadKind};
36#[cfg(feature = "processing")]
37use anyhow::Context;
38use anyhow::Result;
39use axum::extract::FromRequestParts;
40use axum::http::request::Parts;
41use relay_cogs::Cogs;
42use relay_config::{Config, ConfigSnapshot, EmitOutcomes, RelayMode};
43#[cfg(feature = "processing")]
44use relay_config::{RedisConfigRef, RedisConfigsRef};
45#[cfg(feature = "processing")]
46use relay_redis::AsyncRedisClient;
47#[cfg(feature = "processing")]
48use relay_redis::redis::Script;
49#[cfg(feature = "processing")]
50use relay_redis::{RedisClients, RedisError, RedisScripts};
51use relay_system::{Addr, Service, ServiceSpawn, ServiceSpawnExt as _, channel};
52
53#[derive(Debug, thiserror::Error)]
55pub enum ServiceError {
56 #[error("could not load the Geoip Db")]
58 GeoIp,
59
60 #[cfg(feature = "processing")]
62 #[error("could not initialize kafka producer: {0}")]
63 Kafka(String),
64
65 #[cfg(feature = "processing")]
67 #[error("could not initialize redis client during startup")]
68 Redis,
69}
70
71#[derive(Clone, Debug)]
72pub struct Registry {
73 pub cogs: Cogs,
74 pub health_check: Addr<HealthCheck>,
75 pub outcome_aggregator: Addr<TrackOutcome>,
76 pub processor: Addr<EnvelopeProcessor>,
77 pub relay_cache: Addr<RelayCache>,
78 pub global_config: Addr<GlobalConfigManager>,
79 pub upstream_relay: Addr<UpstreamRelay>,
80 pub envelope_buffer: Arc<PartitionedEnvelopeBuffer>,
81 pub project_cache_handle: ProjectCacheHandle,
82 pub autoscaling: Option<Addr<AutoscalingMetrics>>,
83 #[cfg(feature = "processing")]
84 pub objectstore: Option<Addr<Objectstore>>,
85 pub upload: Addr<Upload>,
86 pub global_config_handle: GlobalConfigHandle,
87}
88
89pub fn create_runtime(name: &'static str, threads: usize) -> relay_system::Runtime {
91 relay_system::Runtime::builder(name)
92 .worker_threads(threads)
93 .max_blocking_threads(150)
104 .thread_keep_alive(Duration::from_secs(1))
108 .build()
109}
110
111fn create_processor_pool(config: &ConfigSnapshot) -> Result<EnvelopeProcessorServicePool> {
112 let thread_count = match config.cpu_concurrency() {
115 conc @ 0..=2 => conc.max(1),
116 conc @ 3..=4 => conc - 1,
117 conc => conc - 2,
118 };
119 relay_log::info!("starting {thread_count} envelope processing workers");
120
121 let pool = crate::utils::ThreadPoolBuilder::new("processor", tokio::runtime::Handle::current())
122 .num_threads(thread_count)
123 .max_concurrency(config.pool_concurrency())
124 .thread_kind(ThreadKind::Worker)
125 .build()?;
126
127 Ok(pool)
128}
129
130#[cfg(feature = "processing")]
131fn create_store_pool(config: &ConfigSnapshot) -> Result<StoreServicePool> {
132 let thread_count = config.cpu_concurrency().div_ceil(8);
138 relay_log::info!("starting {thread_count} store workers");
139
140 let pool = crate::utils::ThreadPoolBuilder::new("store", tokio::runtime::Handle::current())
141 .num_threads(thread_count)
142 .max_concurrency(config.pool_concurrency())
143 .build()?;
144
145 Ok(pool)
146}
147
148#[derive(Debug)]
149struct StateInner {
150 config: Arc<Config>,
151 memory_checker: MemoryChecker,
152 registry: Registry,
153}
154
155#[derive(Clone, Debug)]
157pub struct ServiceState {
158 inner: Arc<StateInner>,
159}
160
161impl ServiceState {
162 pub async fn start(
164 handle: &relay_system::Handle,
165 services: &dyn ServiceSpawn,
166 config: Arc<Config>,
167 ) -> Result<Self> {
168 let upstream_relay = services.start(UpstreamRelayService::new(config.clone()));
169 let current_config = config.current();
170
171 #[cfg(feature = "processing")]
172 let redis_clients = current_config
173 .redis()
174 .filter(|_| current_config.processing_enabled())
175 .map(create_redis_clients)
176 .transpose()
177 .context(ServiceError::Redis)?;
178
179 #[cfg(feature = "processing")]
184 if let Some(redis_clients) = &redis_clients {
185 initialize_redis_scripts_for_client(redis_clients)
186 .await
187 .context(ServiceError::Redis)?;
188 }
189
190 let memory_stat = MemoryStat::new(current_config.memory_stat_refresh_frequency_ms());
193
194 let (processor, processor_rx) = match current_config.relay_mode() {
197 RelayMode::Proxy => channel(ProxyProcessorService::name()),
198 RelayMode::Managed => channel(EnvelopeProcessorService::name()),
199 };
200
201 let (aggregator, aggregator_rx) = channel(RouterService::name());
202
203 let outcome_aggregator = match current_config.emit_outcomes() {
204 EmitOutcomes::None => services.start(NullOutcomeProducerService::new()),
205 _ => match current_config.relay_mode() {
206 RelayMode::Proxy => services.start(ClientReportOutcomeProducerService::new(
207 ¤t_config,
208 processor.clone(),
209 )),
210 RelayMode::Managed => services.start(OutcomeProducerService::new(
211 Arc::clone(&config),
212 aggregator.clone(),
213 )),
214 },
215 };
216
217 let (global_config, global_config_rx) =
218 GlobalConfigService::new(config.clone(), upstream_relay.clone());
219 let global_config_handle = global_config.handle();
220 let global_config = services.start(global_config);
224
225 let project_source = ProjectSource::start_in(
226 services,
227 Arc::clone(&config),
228 upstream_relay.clone(),
229 #[cfg(feature = "processing")]
230 redis_clients.clone(),
231 )
232 .await;
233 let project_cache_handle =
234 ProjectCacheService::new(Arc::clone(&config), project_source).start_in(services);
235
236 let cogs = CogsService::new(¤t_config);
237 let cogs = Cogs::new(CogsServiceRecorder::new(
238 ¤t_config,
239 services.start(cogs),
240 ));
241
242 let metric_outcomes = MetricOutcomes::new(outcome_aggregator.clone());
243
244 #[cfg(feature = "processing")]
245 let store_pool = create_store_pool(¤t_config)?;
246 #[cfg(feature = "processing")]
249 let mut kafka_client = None;
250 #[cfg(feature = "processing")]
251 let store = current_config
252 .processing_enabled()
253 .then(|| {
254 StoreService::create(
255 store_pool.clone(),
256 config.clone(),
257 global_config_handle.clone(),
258 metric_outcomes.clone(),
259 )
260 .map(|s| {
261 kafka_client = Some(s.kafka_client());
262 services.start(s)
263 })
264 })
265 .transpose()?;
266
267 #[cfg(feature = "processing")]
268 let objectstore = objectstore::create_service(current_config.objectstore(), store.clone())?
269 .map(|s| services.start(s));
270
271 let envelope_buffer = PartitionedEnvelopeBuffer::create(
272 current_config.spool_partitions(),
273 config.clone(),
274 memory_stat.clone(),
275 global_config_rx.clone(),
276 project_cache_handle.clone(),
277 processor.clone(),
278 outcome_aggregator.clone(),
279 services,
280 );
281
282 let (processor_pool, aggregator_handle, autoscaling) = match current_config.relay_mode() {
283 RelayMode::Proxy => {
284 services.start_with(
285 ProxyProcessorService::new(
286 config.clone(),
287 project_cache_handle.clone(),
288 ProxyAddrs {
289 outcome_aggregator: outcome_aggregator.clone(),
290 upstream_relay: upstream_relay.clone(),
291 },
292 ),
293 processor_rx,
294 );
295 (None, None, None)
296 }
297 RelayMode::Managed => {
298 let processor_pool = create_processor_pool(¤t_config)?;
299
300 let router = RouterService::new(
301 handle.clone(),
302 current_config.default_aggregator_config().clone(),
303 current_config.secondary_aggregator_configs().clone(),
304 Some(processor.clone().recipient()),
305 project_cache_handle.clone(),
306 );
307 let router_handle = router.handle();
308 services.start_with(router, aggregator_rx);
309
310 services.start_with(
311 EnvelopeProcessorService::new(
312 processor_pool.clone(),
313 config.clone(),
314 global_config_handle.clone(),
315 project_cache_handle.clone(),
316 cogs.clone(),
317 #[cfg(feature = "processing")]
318 redis_clients.clone(),
319 processor::Addrs {
320 outcome_aggregator: outcome_aggregator.clone(),
321 upstream_relay: upstream_relay.clone(),
322 #[cfg(feature = "processing")]
323 objectstore: objectstore.clone(),
324 #[cfg(feature = "processing")]
325 store_forwarder: store,
326 aggregator: aggregator.clone(),
327 },
328 metric_outcomes.clone(),
329 ),
330 processor_rx,
331 );
332
333 let autoscaling = services.start(AutoscalingMetricService::new(
334 memory_stat.clone(),
335 envelope_buffer.clone(),
336 handle.clone(),
337 processor_pool.clone(),
338 ));
339
340 (Some(processor_pool), Some(router_handle), Some(autoscaling))
341 }
342 };
343
344 let health_check = services.start(HealthCheckService::new(
345 config.clone(),
346 MemoryChecker::new(memory_stat.clone(), config.clone()),
347 aggregator_handle,
348 upstream_relay.clone(),
349 envelope_buffer.clone(),
350 ));
351
352 services.start(RelayStats::new(
353 config.clone(),
354 handle.clone(),
355 upstream_relay.clone(),
356 #[cfg(feature = "processing")]
357 redis_clients.clone(),
358 processor_pool,
359 #[cfg(feature = "processing")]
360 store_pool,
361 ));
362
363 let relay_cache = services.start(RelayCacheService::new(
364 config.clone(),
365 upstream_relay.clone(),
366 ));
367
368 let upload = services.start(upload::create_service(
369 &config,
370 &upstream_relay,
371 #[cfg(feature = "processing")]
372 &objectstore,
373 ));
374
375 let _ = services.start(ConfigReloadService::new(
376 config.clone(),
377 #[cfg(feature = "processing")]
378 kafka_client,
379 ));
380
381 let registry = Registry {
382 cogs,
383 processor,
384 health_check,
385 outcome_aggregator,
386 relay_cache,
387 global_config,
388 project_cache_handle,
389 upstream_relay,
390 envelope_buffer,
391 autoscaling,
392 #[cfg(feature = "processing")]
393 objectstore,
394 upload,
395 global_config_handle,
396 };
397
398 let state = StateInner {
399 config: config.clone(),
400 memory_checker: MemoryChecker::new(memory_stat, config.clone()),
401 registry,
402 };
403
404 Ok(ServiceState {
405 inner: Arc::new(state),
406 })
407 }
408
409 pub fn config(&self) -> ConfigSnapshot {
411 self.inner.config.current()
412 }
413
414 pub fn cogs(&self) -> &Cogs {
416 &self.inner.registry.cogs
417 }
418
419 pub fn memory_checker(&self) -> &MemoryChecker {
423 &self.inner.memory_checker
424 }
425
426 pub fn autoscaling(&self) -> Option<&Addr<AutoscalingMetrics>> {
427 self.inner.registry.autoscaling.as_ref()
428 }
429
430 pub fn envelope_buffer_is_empty(&self) -> bool {
432 self.inner.registry.envelope_buffer.is_empty()
433 }
434
435 pub fn envelope_buffer(&self, project_key_pair: ProjectKeyPair) -> &ObservableEnvelopeBuffer {
437 self.inner.registry.envelope_buffer.buffer(project_key_pair)
438 }
439
440 pub fn project_cache_handle(&self) -> &ProjectCacheHandle {
442 &self.inner.registry.project_cache_handle
443 }
444
445 pub fn relay_cache(&self) -> &Addr<RelayCache> {
447 &self.inner.registry.relay_cache
448 }
449
450 pub fn health_check(&self) -> &Addr<HealthCheck> {
452 &self.inner.registry.health_check
453 }
454
455 pub fn upstream_relay(&self) -> &Addr<UpstreamRelay> {
457 &self.inner.registry.upstream_relay
458 }
459
460 pub fn processor(&self) -> &Addr<EnvelopeProcessor> {
462 &self.inner.registry.processor
463 }
464
465 pub fn global_config(&self) -> &Addr<GlobalConfigManager> {
467 &self.inner.registry.global_config
468 }
469
470 pub fn outcome_aggregator(&self) -> &Addr<TrackOutcome> {
472 &self.inner.registry.outcome_aggregator
473 }
474
475 #[cfg(feature = "processing")]
476 pub fn objectstore(&self) -> Option<&Addr<Objectstore>> {
478 self.inner.registry.objectstore.as_ref()
479 }
480
481 pub fn upload(&self) -> &Addr<Upload> {
483 &self.inner.registry.upload
484 }
485
486 pub fn global_config_handle(&self) -> &GlobalConfigHandle {
487 &self.inner.registry.global_config_handle
488 }
489}
490
491#[cfg(feature = "processing")]
499pub fn create_redis_clients(configs: RedisConfigsRef<'_>) -> Result<RedisClients, RedisError> {
500 const PROJECT_CONFIG_REDIS_CLIENT: &str = "projectconfig";
501 const QUOTA_REDIS_CLIENT: &str = "quotas";
502 const UNIFIED_REDIS_CLIENT: &str = "unified";
503
504 match configs {
505 RedisConfigsRef::Unified(unified) => {
506 let client = create_async_redis_client(UNIFIED_REDIS_CLIENT, &unified)?;
507
508 Ok(RedisClients {
509 project_configs: client.clone(),
510 quotas: client,
511 })
512 }
513 RedisConfigsRef::Individual {
514 project_configs,
515 quotas,
516 } => {
517 let project_configs =
518 create_async_redis_client(PROJECT_CONFIG_REDIS_CLIENT, &project_configs)?;
519 let quotas = create_async_redis_client(QUOTA_REDIS_CLIENT, "as)?;
520
521 Ok(RedisClients {
522 project_configs,
523 quotas,
524 })
525 }
526 }
527}
528
529#[cfg(feature = "processing")]
530fn create_async_redis_client(
531 name: &'static str,
532 config: &RedisConfigRef<'_>,
533) -> Result<AsyncRedisClient, RedisError> {
534 match config {
535 RedisConfigRef::Cluster {
536 cluster_nodes,
537 options,
538 } => AsyncRedisClient::cluster(name, cluster_nodes.iter().map(|s| s.as_str()), options),
539 RedisConfigRef::Single { server, options } => {
540 AsyncRedisClient::single(name, server, options)
541 }
542 }
543}
544
545#[cfg(feature = "processing")]
546async fn initialize_redis_scripts_for_client(
547 redis_clients: &RedisClients,
548) -> Result<(), RedisError> {
549 let scripts = RedisScripts::all();
550
551 let RedisClients {
552 project_configs,
553 quotas,
554 } = redis_clients;
555
556 initialize_redis_scripts(project_configs, &scripts).await?;
557 initialize_redis_scripts(quotas, &scripts).await?;
558
559 Ok(())
560}
561
562#[cfg(feature = "processing")]
563async fn initialize_redis_scripts(
564 client: &AsyncRedisClient,
565 scripts: &[&Script],
566) -> Result<(), RedisError> {
567 let mut connection = client.get_connection().await?;
568
569 for script in scripts {
570 script
573 .prepare_invoke()
574 .load_async(&mut connection)
575 .await
576 .map_err(RedisError::Redis)?;
577 }
578
579 Ok(())
580}
581
582impl FromRequestParts<Self> for ServiceState {
583 type Rejection = Infallible;
584
585 async fn from_request_parts(_: &mut Parts, state: &Self) -> Result<Self, Self::Rejection> {
586 Ok(state.clone())
587 }
588}