Skip to content

Commit 1d061b7

Browse files
committed
chore(coprocessor): observe query timings in tfhe-worker
- add --dcid_ttl_sec config - add otel traces for dcid
1 parent be21946 commit 1d061b7

6 files changed

Lines changed: 100 additions & 25 deletions

File tree

coprocessor/fhevm-engine/tfhe-worker/benches/utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ async fn start_coprocessor(rx: Receiver<bool>, app_port: u16, db_url: &str) {
109109
metric_rerand_batch_latency: MetricsConfig::default(),
110110
metric_fhe_batch_latency: MetricsConfig::default(),
111111
worker_id: None,
112+
dcid_ttl_sec: 30,
112113
};
113114

114115
std::thread::spawn(move || {

coprocessor/fhevm-engine/tfhe-worker/src/daemon_cli.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@ pub struct Args {
9090
#[arg(long, value_parser = clap::value_parser!(Uuid))]
9191
pub worker_id: Option<Uuid>,
9292

93+
/// Time-to-live in seconds for dependence chain locks
94+
/// Defaults to 30 seconds if not provided
95+
#[arg(long, value_parser = clap::value_parser!(u32), default_value_t = 30)]
96+
pub dcid_ttl_sec: u32,
97+
9398
/// Log level for the application
9499
#[arg(
95100
long,

coprocessor/fhevm-engine/tfhe-worker/src/dependence_chain.rs

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,37 @@
11
use chrono::{DateTime, Utc};
2+
use prometheus::{register_histogram, register_int_counter, Histogram, IntCounter};
23
use sqlx::Postgres;
3-
use std::fmt;
4+
use std::{fmt, sync::LazyLock, time::SystemTime};
45
use tracing::{debug, error, info, warn};
56
use uuid::Uuid;
67

8+
pub(crate) static ACQUIRED_DEPENDENCE_CHAIN_ID_COUNTER: LazyLock<IntCounter> =
9+
LazyLock::new(|| {
10+
register_int_counter!(
11+
"coprocessor_tfhe_worker_dcid_counter",
12+
"Number of acquired dependence chain IDs in tfhe-worker"
13+
)
14+
.unwrap()
15+
});
16+
17+
pub static ACQUIRE_DEPENDENCE_CHAIN_ID_QUERY_HISTOGRAM: LazyLock<Histogram> = LazyLock::new(|| {
18+
register_histogram!(
19+
"coprocessor_tfhe_worker_query_acquire_dcid_seconds",
20+
"Histogram of query-time spent acquiring dependence chain IDs in tfhe-worker",
21+
vec![0.001, 0.005, 0.01, 0.05, 0.1, 0.25, 0.5, 0.75, 1.0, 2.0, 5.0, 10.0]
22+
)
23+
.unwrap()
24+
});
25+
26+
pub static EXTEND_DEPENDENCE_CHAIN_ID_QUERY_HISTOGRAM: LazyLock<Histogram> = LazyLock::new(|| {
27+
register_histogram!(
28+
"coprocessor_tfhe_worker_query_extend_dcid_seconds",
29+
"Histogram of query-time spent extending dependence_chain lock in tfhe-worker",
30+
vec![0.001, 0.005, 0.01, 0.05, 0.1, 0.25, 0.5, 0.75, 1.0, 2.0, 5.0, 10.0]
31+
)
32+
.unwrap()
33+
});
34+
735
#[derive(Debug, Clone, PartialEq, Eq)]
836
pub enum LockingReason {
937
UpdatedUnowned, // Normal lock acquisition
@@ -30,7 +58,7 @@ pub struct LockMngr {
3058
pool: sqlx::Pool<Postgres>,
3159
worker_id: Uuid,
3260
lock: Option<DatabaseChainLock>,
33-
expiration_duration_secs: i64,
61+
lock_ttl_sec: i64,
3462
}
3563

3664
/// Dependence chain lock data
@@ -68,17 +96,13 @@ impl LockMngr {
6896
worker_id,
6997
pool,
7098
lock: None,
71-
expiration_duration_secs: 30,
99+
lock_ttl_sec: 30,
72100
}
73101
}
74102

75-
pub fn new_with_expiry(
76-
worker_id: Uuid,
77-
pool: sqlx::Pool<Postgres>,
78-
expiration_duration_secs: i64,
79-
) -> Self {
103+
pub fn new_with_ttl(worker_id: Uuid, pool: sqlx::Pool<Postgres>, lock_ttl_sec: u32) -> Self {
80104
let mut mgr = Self::new(worker_id, pool);
81-
mgr.expiration_duration_secs = expiration_duration_secs;
105+
mgr.lock_ttl_sec = lock_ttl_sec as i64;
82106
mgr
83107
}
84108

@@ -88,6 +112,7 @@ impl LockMngr {
88112
pub async fn acquire_next_lock(
89113
&mut self,
90114
) -> Result<(Option<Vec<u8>>, LockingReason), sqlx::Error> {
115+
let started_at = SystemTime::now();
91116
let row = sqlx::query_as::<_, DatabaseChainLock>(
92117
r#"
93118
WITH candidate AS (
@@ -124,7 +149,7 @@ impl LockMngr {
124149
"#,
125150
)
126151
.bind(self.worker_id)
127-
.bind(self.expiration_duration_secs)
152+
.bind(self.lock_ttl_sec)
128153
.fetch_optional(&self.pool)
129154
.await?;
130155

@@ -135,7 +160,15 @@ impl LockMngr {
135160
};
136161

137162
self.lock.replace(row.clone());
138-
info!(?row, "Acquired lock");
163+
164+
ACQUIRED_DEPENDENCE_CHAIN_ID_COUNTER.inc();
165+
166+
let elapsed = started_at.elapsed().map(|d| d.as_secs_f64()).unwrap_or(0.0);
167+
if elapsed > 0.0 {
168+
ACQUIRE_DEPENDENCE_CHAIN_ID_QUERY_HISTOGRAM.observe(elapsed);
169+
}
170+
171+
info!(?row, query_elapsed = %elapsed, "Acquired lock");
139172

140173
Ok((
141174
Some(row.dependence_chain_id),
@@ -253,6 +286,7 @@ impl LockMngr {
253286
pub async fn extend_current_lock(
254287
&mut self,
255288
) -> Result<Option<(Vec<u8>, LockingReason)>, sqlx::Error> {
289+
let started_at = SystemTime::now();
256290
let dependence_chain_id = match &self.lock {
257291
Some(lock) => lock.dependence_chain_id.clone(),
258292
None => {
@@ -272,14 +306,15 @@ impl LockMngr {
272306
"#,
273307
dependence_chain_id,
274308
self.worker_id,
275-
self.expiration_duration_secs as f64
309+
self.lock_ttl_sec as f64
276310
)
277311
.fetch_optional(&self.pool)
278312
.await?;
279313

280314
let lock_expires_at = match row {
281315
Some(r) => r,
282316
None => {
317+
self.lock.take();
283318
error!(dcid = %hex::encode(&dependence_chain_id), "No lock extended");
284319
return Ok(None);
285320
}
@@ -291,6 +326,11 @@ impl LockMngr {
291326
info!(dcid = %hex::encode(&dependence_chain_id), expires_at = ?lock.lock_expires_at, "Extended lock");
292327
}
293328

329+
let elapsed = started_at.elapsed().map(|d| d.as_secs_f64()).unwrap_or(0.0);
330+
if elapsed > 0.0 {
331+
EXTEND_DEPENDENCE_CHAIN_ID_QUERY_HISTOGRAM.observe(elapsed);
332+
}
333+
294334
Ok(Some((dependence_chain_id, LockingReason::ExtendedLock)))
295335
}
296336

coprocessor/fhevm-engine/tfhe-worker/src/tests/dependence_chain.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ async fn test_acquire_next_lock() {
2020

2121
for dependence_chain_id in dependence_chain_ids.iter() {
2222
info!(target: "deps_chain", ?dependence_chain_id, "Testing acquire_next_lock");
23-
let mut mgr = LockMngr::new_with_expiry(Uuid::new_v4(), pool.clone(), 3600);
23+
let mut mgr = LockMngr::new_with_ttl(Uuid::new_v4(), pool.clone(), 3600);
2424

2525
let (acquired, locking) = mgr.acquire_next_lock().await.unwrap();
2626
assert_eq!(acquired, Some(dependence_chain_id.clone()));
@@ -65,7 +65,7 @@ async fn test_work_stealing() {
6565
info!(?dependence_chain_id, "Testing acquire_next_lock");
6666

6767
let worker = Uuid::new_v4();
68-
let mut mgr = LockMngr::new_with_expiry(worker, pool.clone(), expiration_duration_secs);
68+
let mut mgr = LockMngr::new_with_ttl(worker, pool.clone(), expiration_duration_secs);
6969
let acquired = mgr.acquire_next_lock().await.unwrap().0;
7070
assert_eq!(acquired, Some(dependence_chain_id.clone()));
7171

coprocessor/fhevm-engine/tfhe-worker/src/tests/utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ async fn start_coprocessor(rx: Receiver<bool>, app_port: u16, db_url: &str) {
118118
metric_rerand_batch_latency: MetricsConfig::default(),
119119
metric_fhe_batch_latency: MetricsConfig::default(),
120120
worker_id: None,
121+
dcid_ttl_sec: 30,
121122
};
122123

123124
std::thread::spawn(move || {

coprocessor/fhevm-engine/tfhe-worker/src/tfhe_worker.rs

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ use itertools::Itertools;
88
use lazy_static::lazy_static;
99
use opentelemetry::trace::{Span, TraceContextExt, Tracer};
1010
use opentelemetry::KeyValue;
11-
use prometheus::{register_int_counter, IntCounter};
11+
use prometheus::{register_histogram, register_int_counter, Histogram, IntCounter};
1212
use scheduler::dfg::types::{DFGTxInput, SchedulerError};
1313
use scheduler::dfg::{build_component_nodes, ComponentNode, DFComponentGraph, DFGOp};
1414
use scheduler::dfg::{scheduler::Scheduler, types::DFGTaskInput};
1515
use sqlx::types::Uuid;
1616
use sqlx::Postgres;
1717
use sqlx::{postgres::PgListener, query, Acquire};
18+
use std::time::SystemTime;
1819
use std::{
1920
collections::{BTreeSet, HashMap},
2021
num::NonZeroUsize,
@@ -55,12 +56,20 @@ lazy_static! {
5556
"work items successfully processed and stored in the database"
5657
)
5758
.unwrap();
59+
static ref WORK_ITEMS_QUERY_HISTOGRAM: Histogram = register_histogram!(
60+
"coprocessor_tfhe_worker_query_work_items_seconds",
61+
"Histogram of time spent querying work items in tfhe-worker",
62+
vec![0.001, 0.005, 0.01, 0.05, 0.1, 0.25, 0.5, 0.75, 1.0, 2.0, 5.0, 10.0]
63+
)
64+
.unwrap();
5865
}
5966

6067
pub async fn run_tfhe_worker(
6168
args: crate::daemon_cli::Args,
6269
health_check: crate::health_check::HealthCheck,
6370
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
71+
// Determine worker ID to use for the lifetime of this process
72+
// In case of a failure in tfhe_worker_cycle, the same id must be reused to quickly unlock any held locks
6473
let worker_id = args.worker_id.unwrap_or(Uuid::new_v4());
6574
info!(target: "tfhe_worker", worker_id = %worker_id, "Starting tfhe-worker service");
6675
loop {
@@ -92,10 +101,11 @@ async fn tfhe_worker_cycle(
92101
let mut listener = PgListener::connect_with(&pool).await?;
93102
listener.listen("work_available").await?;
94103

95-
let mut deps_chain_mngr = dependence_chain::LockMngr::new(worker_id, pool.clone());
104+
let mut dcid_mngr =
105+
dependence_chain::LockMngr::new_with_ttl(worker_id, pool.clone(), args.dcid_ttl_sec);
96106

97107
// Release all owned locks on startup to avoid stale locks
98-
deps_chain_mngr.release_all_owned_locks().await?;
108+
dcid_mngr.release_all_owned_locks().await?;
99109

100110
#[cfg(feature = "bench")]
101111
populate_cache_with_tenant_keys(vec![1i32], &pool, &tenant_key_cache).await?;
@@ -131,18 +141,26 @@ async fn tfhe_worker_cycle(
131141
args,
132142
&health_check,
133143
&mut trx,
134-
&mut deps_chain_mngr,
144+
&mut dcid_mngr,
135145
&tracer,
136146
&loop_ctx,
137147
)
138148
.await?;
139149
if transactions.is_empty() {
140-
deps_chain_mngr.release_current_lock().await?;
150+
dcid_mngr.release_current_lock().await?;
141151

142152
// Lock another dependence chain if available and
143153
// continue processing without waiting for notification
144-
let (lock, _) = deps_chain_mngr.acquire_next_lock().await?;
145-
immedially_poll_more_work = lock.is_some();
154+
let mut s = tracer.start_with_context("query_dependence_chain", &loop_ctx);
155+
156+
let (dependence_chain_id, _) = dcid_mngr.acquire_next_lock().await?;
157+
immedially_poll_more_work = dependence_chain_id.is_some();
158+
159+
s.set_attribute(KeyValue::new(
160+
"dependence_chain_id",
161+
format!("{:?}", dependence_chain_id.as_ref().map(hex::encode)),
162+
));
163+
s.end();
146164

147165
continue;
148166
} else {
@@ -161,7 +179,12 @@ async fn tfhe_worker_cycle(
161179

162180
// Execute transactions segregated by tenant
163181
for (tenant_id, ref mut tenant_txs) in transactions.iter_mut() {
164-
deps_chain_mngr.extend_current_lock().await?;
182+
if dcid_mngr.extend_current_lock().await?.is_none() {
183+
// best-effort attempt to extend the lock and prevent other replicas from trying to lock the same DCID.
184+
// Worst-case scenario, it returns None if the lock has expired.
185+
// However, the worker has already secured exclusive access to the txn computations in the Computations table.
186+
warn!(target: "tfhe_worker", tenant_id = %tenant_id, "Lost dcid lock while processing transactions");
187+
}
165188

166189
let mut tx_graph = build_transaction_graph_and_execute(
167190
tenant_id,
@@ -178,7 +201,7 @@ async fn tfhe_worker_cycle(
178201
&mut tx_graph,
179202
&mut unneeded_handles,
180203
&mut trx,
181-
&mut deps_chain_mngr,
204+
&mut dcid_mngr,
182205
&tracer,
183206
&loop_ctx,
184207
)
@@ -324,8 +347,7 @@ async fn query_for_work<'a>(
324347
(Vec<(i32, Vec<ComponentNode>)>, Vec<(Handle, Handle)>),
325348
Box<dyn std::error::Error + Send + Sync>,
326349
> {
327-
// This query locks our work items so other worker doesn't select them.
328-
let mut s = tracer.start_with_context("query_work_items", loop_ctx);
350+
let mut s = tracer.start_with_context("query_dependence_chain", loop_ctx);
329351

330352
// Lock dependence chain
331353
let (dependence_chain_id, locking_reason) = match deps_chain_mngr.extend_current_lock().await? {
@@ -343,7 +365,11 @@ async fn query_for_work<'a>(
343365
"dependence_chain_id",
344366
format!("{:?}", dependence_chain_id.as_ref().map(hex::encode)),
345367
));
368+
s.end();
346369

370+
// This query locks our work items so other worker doesn't select them.
371+
let mut s = tracer.start_with_context("query_work_items", loop_ctx);
372+
let started_at = SystemTime::now();
347373
let the_work = query!(
348374
"
349375
WITH selected_computations AS (
@@ -398,6 +424,8 @@ FOR UPDATE SKIP LOCKED ",
398424
error!(target: "tfhe_worker", { error = %err }, "error while querying work items");
399425
err
400426
})?;
427+
428+
WORK_ITEMS_QUERY_HISTOGRAM.observe(started_at.elapsed().unwrap_or_default().as_secs_f64());
401429
s.set_attribute(KeyValue::new("count", the_work.len() as i64));
402430
s.end();
403431
health_check.update_db_access();

0 commit comments

Comments
 (0)