Skip to content

Commit 253d0be

Browse files
committed
feat: add support for EXPLAIN logging of slow queries
- Introduced a new static variable `SLOW_QUERY_EXPLAIN` to enable capturing EXPLAIN output for slow queries. - Implemented `set_slow_query_explain` and `slow_query_explain_enabled` functions to manage this feature. - Updated the query logging to include EXPLAIN output when slow query logging is enabled, enhancing debugging and performance analysis capabilities.
1 parent 5fbde6c commit 253d0be

3 files changed

Lines changed: 49 additions & 0 deletions

File tree

src/bin/rillflow.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,9 @@ struct Cli {
146146
/// Slow query logging threshold in milliseconds (default 500)
147147
#[arg(long, default_value_t = 500)]
148148
slow_query_ms: u64,
149+
/// Also capture EXPLAIN (FORMAT TEXT) for slow queries
150+
#[arg(long, default_value_t = false)]
151+
slow_query_explain: bool,
149152

150153
#[command(subcommand)]
151154
command: Commands,
@@ -534,6 +537,7 @@ async fn main() -> rillflow::Result<()> {
534537
rillflow::metrics::set_slow_query_threshold(std::time::Duration::from_millis(
535538
cli.slow_query_ms,
536539
));
540+
rillflow::metrics::set_slow_query_explain(cli.slow_query_explain);
537541
let mgr = store.schema();
538542

539543
let tenant_helper = TenantHelper::new(

src/metrics.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ static METRICS: OnceLock<Metrics> = OnceLock::new();
5555
static TENANT_METRICS: OnceLock<Mutex<HashMap<String, TenantCounters>>> = OnceLock::new();
5656
static QUERY_DURATIONS: OnceLock<Mutex<HashMap<String, (u64, u64)>>> = OnceLock::new();
5757
static SLOW_QUERY_THRESHOLD_MS: OnceLock<std::sync::atomic::AtomicU64> = OnceLock::new();
58+
static SLOW_QUERY_EXPLAIN: OnceLock<std::sync::atomic::AtomicBool> = OnceLock::new();
5859
static PROJECTION_STATS: OnceLock<Mutex<HashMap<String, (u64, u64, u64)>>> = OnceLock::new();
5960

6061
#[derive(Default, Clone)]
@@ -409,6 +410,16 @@ pub fn slow_query_threshold() -> Duration {
409410
Duration::from_millis(atom.load(std::sync::atomic::Ordering::Relaxed))
410411
}
411412

413+
pub fn set_slow_query_explain(enabled: bool) {
414+
let atom = SLOW_QUERY_EXPLAIN.get_or_init(|| std::sync::atomic::AtomicBool::new(false));
415+
atom.store(enabled, std::sync::atomic::Ordering::Relaxed);
416+
}
417+
418+
pub fn slow_query_explain_enabled() -> bool {
419+
let atom = SLOW_QUERY_EXPLAIN.get_or_init(|| std::sync::atomic::AtomicBool::new(false));
420+
atom.load(std::sync::atomic::Ordering::Relaxed)
421+
}
422+
412423
pub fn record_projection_batch(name: &str, events_processed: u64, dur: Duration) {
413424
if let Ok(mut map) = PROJECTION_STATS.get_or_init(Default::default).lock() {
414425
let entry = map.entry(name.to_string()).or_insert((0, 0, 0));

src/query/mod.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,6 +1100,18 @@ where
11001100
// slow query logging
11011101
if start.elapsed() > crate::metrics::slow_query_threshold() {
11021102
tracing::warn!(target: "rillflow::slow_query", elapsed_ms = start.elapsed().as_millis() as u64, sql = %sql_captured, "slow document query");
1103+
if crate::metrics::slow_query_explain_enabled() {
1104+
// Best-effort EXPLAIN (without executing)
1105+
let explain = format!("EXPLAIN (VERBOSE, FORMAT TEXT) {}", sql_captured);
1106+
let rows = sqlx::query_scalar::<_, String>(&explain)
1107+
.fetch_all(&pool)
1108+
.await;
1109+
if let Ok(lines) = rows {
1110+
for line in lines {
1111+
tracing::warn!(target: "rillflow::slow_query", plan = %line);
1112+
}
1113+
}
1114+
}
11031115
}
11041116
rows.into_iter()
11051117
.map(|(value,)| serde_json::from_value(value).map_err(Into::into))
@@ -1115,6 +1127,17 @@ where
11151127
crate::metrics::record_query_duration("docs_fetch_optional", start.elapsed());
11161128
if start.elapsed() > crate::metrics::slow_query_threshold() {
11171129
tracing::warn!(target: "rillflow::slow_query", elapsed_ms = start.elapsed().as_millis() as u64, sql = %sql_captured, "slow document query");
1130+
if crate::metrics::slow_query_explain_enabled() {
1131+
let explain = format!("EXPLAIN (VERBOSE, FORMAT TEXT) {}", sql_captured);
1132+
let rows = sqlx::query_scalar::<_, String>(&explain)
1133+
.fetch_all(&pool)
1134+
.await;
1135+
if let Ok(lines) = rows {
1136+
for line in lines {
1137+
tracing::warn!(target: "rillflow::slow_query", plan = %line);
1138+
}
1139+
}
1140+
}
11181141
}
11191142
match row {
11201143
Some((value,)) => Ok(Some(serde_json::from_value(value)?)),
@@ -1131,6 +1154,17 @@ where
11311154
crate::metrics::record_query_duration("docs_fetch_one", start.elapsed());
11321155
if start.elapsed() > crate::metrics::slow_query_threshold() {
11331156
tracing::warn!(target: "rillflow::slow_query", elapsed_ms = start.elapsed().as_millis() as u64, sql = %sql_captured, "slow document query");
1157+
if crate::metrics::slow_query_explain_enabled() {
1158+
let explain = format!("EXPLAIN (VERBOSE, FORMAT TEXT) {}", sql_captured);
1159+
let rows = sqlx::query_scalar::<_, String>(&explain)
1160+
.fetch_all(&pool)
1161+
.await;
1162+
if let Ok(lines) = rows {
1163+
for line in lines {
1164+
tracing::warn!(target: "rillflow::slow_query", plan = %line);
1165+
}
1166+
}
1167+
}
11341168
}
11351169
Ok(serde_json::from_value(value)?)
11361170
}

0 commit comments

Comments
 (0)