Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 33 additions & 64 deletions asap-common/dependencies/rs/asap_types/src/capability_matching.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ use promql_utilities::query_logics::enums::AggregationType;
/// `SketchWarmTier` is the default — every existing `AggregationConfig` and
/// `StreamingConfig` decodes into this variant via `#[serde(default)]`, so
/// pre-Phase-5 deploys keep dispatching to `SimpleEngine` unchanged.
///
/// **Step-1 of the JSONL deprecation refactor** removed the
/// `ColdJsonlFallback` variant. The legacy local-FS JSONL leg
/// (`LocalFsColdStore`, `parse_jsonl`, the §5.2 raw-store
/// fallback) was deleted at the same commit; the surviving
/// failover surface is warm-tier sketch ↔ Gorilla-S3 archive.
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default,
)]
Expand All @@ -38,16 +44,11 @@ pub enum StorageBackend {
#[default]
SketchWarmTier,

/// NEW (Phase 5) — Gorilla-S3 archive. Served by `GorillaQueryEngine`,
/// reading per-hour Gorilla chunks from S3 / MinIO via the Phase-3
/// `GorillaS3ColdStore`.
/// Gorilla-S3 archive. Served by `GorillaQueryEngine`, reading
/// per-hour Gorilla chunks from S3 / MinIO via the
/// `GorillaS3Store`.
GorillaS3Archive,

/// Local-FS JSONL fallback (PR #54 §5.2). Served by the existing
/// cold-fallback path; used when no warm-tier or archive aggregation
/// can answer the query.
ColdJsonlFallback,

/// Double-write: the metric is written to both warm-tier sketches AND the
/// Gorilla-S3 archive. Capability matching surfaces both options and the
/// cost-aware dispatcher picks per query (typically warm-tier for low-
Expand All @@ -63,7 +64,6 @@ impl StorageBackend {
match self {
StorageBackend::SketchWarmTier => "sketch_warm",
StorageBackend::GorillaS3Archive => "gorilla_archive",
StorageBackend::ColdJsonlFallback => "cold_jsonl",
StorageBackend::DoubleWrite => "double_write",
}
}
Expand Down Expand Up @@ -148,8 +148,10 @@ pub fn compatible_agg_types(stat: Statistic) -> &'static [AggregationType] {
///
/// The returned list is **ordered by preference**: the router walks it in
/// order and dispatches to the first backend whose engine is registered.
/// `ColdJsonlFallback` is appended whenever the warm tier is in play so a
/// capability miss falls through to the §5.2 raw-store path before erroring.
///
/// **Step-1 of the JSONL deprecation refactor**: the legacy
/// `ColdJsonlFallback` failover slot was removed. Surviving
/// failover surface is warm-tier sketch ↔ Gorilla-S3 archive.
///
/// Routing rules (mirrors `docs/design-gorilla-s3-cold-engine.md` §8):
///
Expand All @@ -159,48 +161,35 @@ pub fn compatible_agg_types(stat: Statistic) -> &'static [AggregationType] {
/// routes to the archive when the metric is Gorilla-only — there is no
/// warm-tier sketch to fall back to in that deploy shape.
/// * Metric configured for `SketchWarmTier` (or unconfigured / default):
/// `[SketchWarmTier, ColdJsonlFallback]` — warm tier first, raw-store
/// fallback if no compatible aggregation exists.
/// `[SketchWarmTier]`. A capability miss in the warm tier surfaces
/// as a 404 — the previous JSONL fallback path has been deleted.
/// * Metric configured for `DoubleWrite`: head depends on accuracy hint,
/// tail is the failover sequence (the cost-aware `EngineRouter` picks
/// the head, walks the tail on failure):
/// - `Exact` → `[GorillaS3Archive, SketchWarmTier, ColdJsonlFallback]`
/// - `Approximate` → `[SketchWarmTier, GorillaS3Archive, ColdJsonlFallback]`
/// * Metric explicitly configured for `ColdJsonlFallback`: just
/// `[ColdJsonlFallback]`.
/// - `Exact` → `[GorillaS3Archive, SketchWarmTier]`
/// - `Approximate` → `[SketchWarmTier, GorillaS3Archive]`
pub fn compatible_storage_backends(
_stat: Statistic,
accuracy: AccuracyTarget,
metric_storage_config: StorageBackend,
) -> Vec<StorageBackend> {
match metric_storage_config {
StorageBackend::GorillaS3Archive => {
// Exact-on-archive subsumes approximate-on-warm: a Gorilla-only
// metric has no sketch to back-fall to, and the archive can
// always answer exact (and therefore also approximate) queries.
vec![StorageBackend::GorillaS3Archive]
}
StorageBackend::SketchWarmTier => {
vec![
StorageBackend::SketchWarmTier,
StorageBackend::ColdJsonlFallback,
]
vec![StorageBackend::SketchWarmTier]
}
StorageBackend::DoubleWrite => match accuracy {
AccuracyTarget::Exact => vec![
StorageBackend::GorillaS3Archive,
StorageBackend::SketchWarmTier,
StorageBackend::ColdJsonlFallback,
],
AccuracyTarget::Approximate => vec![
StorageBackend::SketchWarmTier,
StorageBackend::GorillaS3Archive,
StorageBackend::ColdJsonlFallback,
],
},
StorageBackend::ColdJsonlFallback => {
vec![StorageBackend::ColdJsonlFallback]
}
}
}

Expand Down Expand Up @@ -1131,18 +1120,15 @@ mod tests {
AccuracyTarget::Approximate,
StorageBackend::SketchWarmTier,
);
assert_eq!(
backends,
vec![
StorageBackend::SketchWarmTier,
StorageBackend::ColdJsonlFallback,
]
);
// Step-1 of the JSONL deprecation: warm-tier only routes
// to itself; the previous `ColdJsonlFallback` failover slot
// has been deleted.
assert_eq!(backends, vec![StorageBackend::SketchWarmTier]);
}

#[test]
fn double_write_metric_returns_both_options() {
// Exact: archive head, warm-tier failover, then JSONL.
// Exact: archive head, warm-tier failover.
let exact = compatible_storage_backends(
Statistic::Sum,
AccuracyTarget::Exact,
Expand All @@ -1153,11 +1139,10 @@ mod tests {
vec![
StorageBackend::GorillaS3Archive,
StorageBackend::SketchWarmTier,
StorageBackend::ColdJsonlFallback,
]
);
// Approximate: warm-tier head (cheaper for ε/δ-bounded answers),
// archive failover, then JSONL.
// Approximate: warm-tier head (cheaper for ε/δ-bounded
// answers), archive failover.
let approx = compatible_storage_backends(
Statistic::Quantile,
AccuracyTarget::Approximate,
Expand All @@ -1168,7 +1153,6 @@ mod tests {
vec![
StorageBackend::SketchWarmTier,
StorageBackend::GorillaS3Archive,
StorageBackend::ColdJsonlFallback,
]
);
}
Expand All @@ -1187,16 +1171,6 @@ mod tests {
assert_eq!(backends, vec![StorageBackend::GorillaS3Archive]);
}

#[test]
fn cold_jsonl_only_metric_routes_to_jsonl() {
let backends = compatible_storage_backends(
Statistic::Sum,
AccuracyTarget::Exact,
StorageBackend::ColdJsonlFallback,
);
assert_eq!(backends, vec![StorageBackend::ColdJsonlFallback]);
}

#[test]
fn storage_backend_default_is_warm_tier() {
// `#[serde(default)]` on `StreamingConfig.storage_backend` (and on
Expand All @@ -1215,21 +1189,18 @@ mod tests {
"gorilla_archive",
);
assert_eq!(
StorageBackend::ColdJsonlFallback.data_source_id(),
"cold_jsonl",
StorageBackend::DoubleWrite.data_source_id(),
"double_write",
);
}

/// Source-of-truth agreement check, mirrors
/// `capability_canonical_map_agreement` for the storage axis.
///
/// For every `(Statistic, AccuracyTarget, StorageBackend)` triple:
/// 1. The returned backend list is non-empty.
/// 2. The first element matches the expected head per the routing matrix
/// in `compatible_storage_backends`'s docstring (kept sync'd by hand).
/// 3. Every list ends in something the router can dispatch — either the
/// archive (Gorilla-only deploys) or `ColdJsonlFallback` (every
/// other deploy shape).
/// For every `(Statistic, AccuracyTarget, StorageBackend)` triple
/// the returned backend list must be non-empty and its head must
/// match the routing matrix in `compatible_storage_backends`'s
/// docstring.
#[test]
fn capability_storage_backend_agreement() {
let stats = [
Expand All @@ -1247,7 +1218,6 @@ mod tests {
let configs = [
StorageBackend::SketchWarmTier,
StorageBackend::GorillaS3Archive,
StorageBackend::ColdJsonlFallback,
StorageBackend::DoubleWrite,
];

Expand All @@ -1262,16 +1232,15 @@ mod tests {
);
let last = *backends.last().unwrap();
assert!(
last == StorageBackend::ColdJsonlFallback
last == StorageBackend::SketchWarmTier
|| last == StorageBackend::GorillaS3Archive,
"backend list for ({stat:?}, {acc:?}, {cfg:?}) must terminate in a \
dispatchable failover (ColdJsonlFallback or GorillaS3Archive); got {last:?}",
dispatchable failover (SketchWarmTier or GorillaS3Archive); got {last:?}",
);
// The expected head is determined by `(metric_storage_config, accuracy)`:
let expected_head = match (cfg, acc) {
(StorageBackend::GorillaS3Archive, _) => StorageBackend::GorillaS3Archive,
(StorageBackend::SketchWarmTier, _) => StorageBackend::SketchWarmTier,
(StorageBackend::ColdJsonlFallback, _) => StorageBackend::ColdJsonlFallback,
(StorageBackend::DoubleWrite, AccuracyTarget::Exact) => {
StorageBackend::GorillaS3Archive
}
Expand Down
47 changes: 18 additions & 29 deletions asap-query-engine/src/bin/precompute_engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,16 +96,11 @@ struct Args {
#[arg(long)]
persistence_part_cache_mb: Option<u64>,

/// Root of the §5.2 cold-tier raw-sample store. When set,
/// capability-miss queries first try the hour-bucketed JSONL
/// layout under this root. Combine with
/// `--forward-unsupported-queries` to keep Prometheus as the
/// tail of the fallback chain. Reads from `ASAP_COLD_STORE_ROOT`
/// so containerised deploys can wire it via env (matches the
/// backend Docker image's environment in
/// `deploy/docker-compose/base.yml`).
#[arg(long, env = "ASAP_COLD_STORE_ROOT")]
cold_store_root: Option<std::path::PathBuf>,
// Step-1 of the JSONL deprecation refactor removed the
// `--cold-store-root` / `ASAP_COLD_STORE_ROOT` flag. The §5.2
// local-FS JSONL fallback was deleted at the same commit; the
// surviving fallback chain is just Prometheus (gated by
// `--forward-unsupported-queries`).

/// Upstream Prometheus URL for the tail of the fallback chain.
/// Only consulted when `--forward-unsupported-queries` is set.
Expand Down Expand Up @@ -273,17 +268,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
args.prometheus_scrape_interval, // default 30s (matches e2e window size)
QueryLanguage::promql,
));
if let Some(root) = args.cold_store_root.as_deref() {
info!(
cold_store_root = %root.display(),
prom_tail = args.forward_unsupported_queries,
"Cold-tier fallback enabled (§5.2 cold store)",
);
}
let adapter_config = AdapterConfig::from_prom_with_optional_cold(
// Step-1 of the JSONL deprecation: the only surviving
// fallback path is Prometheus (gated by
// `--forward-unsupported-queries`).
let adapter_config = AdapterConfig::prometheus_promql(
args.prometheus_server.clone(),
args.forward_unsupported_queries,
args.cold_store_root.as_deref(),
);
let http_config = HttpServerConfig {
port: args.query_port,
Expand Down Expand Up @@ -343,32 +333,31 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// Mirrors the registration block in `src/main.rs` so the
// `precompute_engine` binary (used by the deploy/docker image)
// matches the full backend's behaviour.
match query_engine_rust::drivers::query::fallback::cold_store::GorillaS3Config::from_env() {
match query_engine_rust::engines::gorilla::GorillaS3Config::from_env() {
Ok(s3_cfg) => {
match query_engine_rust::drivers::query::fallback::cold_store::GorillaS3ColdStore::with_default_backend(s3_cfg) {
Ok(cold_store) => {
use query_engine_rust::engines::{
GorillaEngineConfig, GorillaQueryEngine, QueryEngine,
};
match query_engine_rust::engines::gorilla::GorillaS3Store::with_default_backend(s3_cfg) {
Ok(store) => {
use query_engine_rust::engines::{GorillaEngineConfig, GorillaQueryEngine};
use query_engine_rust::routing::QueryEngine;
let gorilla = Arc::new(GorillaQueryEngine::with_gorilla_s3(
Arc::new(cold_store),
Arc::new(store),
GorillaEngineConfig::default(),
));
info!(
"Phase-6: registering GorillaQueryEngine on the capability router (data_source_id=gorilla_archive)",
"Registering GorillaQueryEngine on the capability router (data_source_id=gorilla_archive)",
);
http_server = http_server.with_query_engine(gorilla as Arc<dyn QueryEngine>);
}
Err(e) => {
warn!(
"ASAP_GORILLA_S3_* env vars present but GorillaS3ColdStore failed to build ({e}); router will not have a cold-archive engine",
"ASAP_GORILLA_S3_* env vars present but GorillaS3Store failed to build ({e}); router will not have an archive engine",
);
}
}
}
Err(_) => {
info!(
"ASAP_GORILLA_S3_* env vars not configured — router serves warm-tier metrics only (set ASAP_GORILLA_S3_BUCKET + ASAP_GORILLA_S3_REGION to enable cold-archive routing)",
"ASAP_GORILLA_S3_* env vars not configured — router serves warm-tier metrics only (set ASAP_GORILLA_S3_BUCKET + ASAP_GORILLA_S3_REGION to enable archive routing)",
);
}
}
Expand Down
2 changes: 1 addition & 1 deletion asap-query-engine/src/bin/show_logical_plans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use datafusion_summary_library::{PrecomputedSummaryRead, SummaryInfer, SummaryMe
use promql_utilities::data_model::KeyByLabelNames;
use promql_utilities::query_logics::enums::{AggregationType, Statistic};
use query_engine_rust::data_model::AggregationIdInfo;
use query_engine_rust::engines::simple_engine::{
use query_engine_rust::engines::simple::engine::{
QueryExecutionContext, QueryMetadata, StoreQueryParams, StoreQueryPlan,
};
use std::collections::HashMap;
Expand Down
11 changes: 9 additions & 2 deletions asap-query-engine/src/data_model/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
pub mod aggregation_config;
pub mod aggregation_reference;
pub mod backend_storage_routing;
pub mod enums;
pub mod hot_reload_config;
pub mod inference_config;
Expand All @@ -14,7 +13,6 @@ pub mod traits;

pub use aggregation_config::*;
pub use aggregation_reference::*;
pub use backend_storage_routing::*;
pub use enums::*;
pub use hot_reload_config::*;
pub use inference_config::*;
Expand All @@ -25,3 +23,12 @@ pub use promql_schema::*;
pub use query_config::*;
pub use streaming_config::*;
pub use traits::*;

// Step-1 of the JSONL deprecation refactor moved
// `backend_storage_routing` into the new `crate::routing` module
// alongside the engine router. Re-export here to keep
// `crate::data_model::BackendStorageRouting` compiling for any
// transitive caller that hasn't been migrated yet.
pub use crate::routing::{
classify_query_shape, BackendStorageRouting, QueryShape, RoutingTarget,
};
Loading