Skip to content

Commit dfe209a

Browse files
dbeglordclaude
andauthored
[ENH](worker): Widen async fn windows to the furthest fitting compaction boundary (#7621)
## Why An async attached function drains its input collection one compaction window at a time. Each run reads the records between the function's completion offset and one compaction boundary, sends them to the external generation service, waits for it to finish, and advances the offset to that boundary. When a tenant backfills a large history, the collection accumulates dozens of small compactions — and the function replays them one by one, paying a full generation round-trip (minutes each, dominated by remote wall time) for every ~200-record window. The resolver currently picks the *nearest* boundary above the completion offset, so the number of round-trips equals the number of compactions, no matter how small each one is. In production we have observed exactly this shape: a backfill of tens of thousands of records drains in hundreds of serial multi-minute round-trips, taking the better part of a day for a single collection. ## What The boundary resolver now targets the *furthest* live compaction boundary whose window still fits within `max_compaction_size`, and falls back to the nearest boundary (preserving the existing oversized-window error) when none fits. One run then covers every compaction between the completion offset and that boundary. With a window cap of 10,000 records, a dense backlog of ~200-record compactions drains in up to ~50× fewer generation round-trips. No other component changes: - The plan already only carries an upper bound for the log read and the as-of record segment at the completion offset; nothing downstream requires the target to be the immediately-next version (`log_fetch_orchestrator` sets `pulled_log_offset`/`log_upper_bound_offset` from the plan, and the completion offset advances to exactly that target via `resolve_pulled_log_offset`). - Skipped intermediate boundaries retire safely in the work queue: `CheckInvocationStatus` marks any queued entry done once the function's completion offset passes it. - Log retention already holds back garbage collection to the minimum attached-function completion offset (`fetch_min_attached_function_completion_offset` in the garbage collector), and a widened window reads no older logs than today — same start, further end. - The HTTP generation executor already splits any window into multiple requests capped at 16MB / `batch_size` (`batch_requests` in `http_generate.rs`), so window size and request payload size stay decoupled. Changed: `rust/worker/src/execution/orchestration/async_function_boundary.rs` (`resolve_boundary_plan_from_version_file`). ## Tests `cargo test -p worker --lib async_function_boundary` — 10 passed. New cases: furthest boundary picked when several fit; oversized boundaries skipped in favor of the widest fitting one; error unchanged when no boundary fits; deleted versions never become widened targets; offset-zero backfill now targets the widest fitting boundary. ## Future work: bounded per-function pipelining Fattened windows fix the round-trips-per-record ratio but keep one window in flight per function, so remote generation latency still serializes a single tenant's drain. Pipelining K windows per function is the next lever (a large backfill's drain time drops roughly by K), but it is not a local change: concurrent runs resolve from the same persisted completion offset today (they would process the same window, not consecutive ones), the offset advance would need low-water-mark semantics in sysdb to tolerate out-of-order completion, and the generation service's tolerance for out-of-order windows is a contract that lives outside this repo. Deferred until those are settled. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 016a26f commit dfe209a

8 files changed

Lines changed: 218 additions & 85 deletions

File tree

rust/index/src/quantization/single_bit.rs

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -218,17 +218,19 @@ impl<T: AsRef<[u8]>> Code<1, T> {
218218
let p2 = &qq.bit_planes[2 * pb..3 * pb];
219219
let p3 = &qq.bit_planes[3 * pb..4 * pb];
220220
let (mut pop0, mut pop1, mut pop2, mut pop3) = (0u32, 0u32, 0u32, 0u32);
221-
for (x_chunk, (((q0, q1), q2), q3)) in packed.chunks_exact(8).zip(
222-
p0.chunks_exact(8)
223-
.zip(p1.chunks_exact(8))
224-
.zip(p2.chunks_exact(8))
225-
.zip(p3.chunks_exact(8)),
221+
for (x_chunk, (((q0, q1), q2), q3)) in packed.as_chunks::<8>().0.iter().zip(
222+
p0.as_chunks::<8>()
223+
.0
224+
.iter()
225+
.zip(p1.as_chunks::<8>().0.iter())
226+
.zip(p2.as_chunks::<8>().0.iter())
227+
.zip(p3.as_chunks::<8>().0.iter()),
226228
) {
227-
let x = u64::from_le_bytes(x_chunk.try_into().unwrap());
228-
pop0 += (x & u64::from_le_bytes(q0.try_into().unwrap())).count_ones();
229-
pop1 += (x & u64::from_le_bytes(q1.try_into().unwrap())).count_ones();
230-
pop2 += (x & u64::from_le_bytes(q2.try_into().unwrap())).count_ones();
231-
pop3 += (x & u64::from_le_bytes(q3.try_into().unwrap())).count_ones();
229+
let x = u64::from_le_bytes(*x_chunk);
230+
pop0 += (x & u64::from_le_bytes(*q0)).count_ones();
231+
pop1 += (x & u64::from_le_bytes(*q1)).count_ones();
232+
pop2 += (x & u64::from_le_bytes(*q2)).count_ones();
233+
pop3 += (x & u64::from_le_bytes(*q3)).count_ones();
232234
}
233235
pop0 + (pop1 << 1) + (pop2 << 2) + (pop3 << 3)
234236
};
@@ -383,10 +385,13 @@ impl Code<1, Vec<u8>> {
383385
// 16 elements = 64 bytes = one cache line = four NEON / one AVX-512 load.
384386
// 4 inner loops of 4: alternate between chains (a, b, a, b)
385387
// so each chain's additions are independent and the OoO core can pipeline them.
386-
for (out_pair, (emb_chunk, cen_chunk)) in packed
387-
.chunks_exact_mut(2)
388-
.zip(embedding.chunks_exact(16).zip(centroid.chunks_exact(16)))
389-
{
388+
for (out_pair, (emb_chunk, cen_chunk)) in packed.as_chunks_mut::<2>().0.iter_mut().zip(
389+
embedding
390+
.as_chunks::<16>()
391+
.0
392+
.iter()
393+
.zip(centroid.as_chunks::<16>().0.iter()),
394+
) {
390395
let mut byte_lo = 0u8;
391396
let mut byte_hi = 0u8;
392397

@@ -495,11 +500,13 @@ fn hamming_distance(a: &[u8], b: &[u8]) -> u32 {
495500
if let Some(bits) = <u8 as BinarySimilarity>::hamming(a, b) {
496501
bits as u32
497502
} else {
498-
a.chunks_exact(8)
499-
.zip(b.chunks_exact(8))
503+
a.as_chunks::<8>()
504+
.0
505+
.iter()
506+
.zip(b.as_chunks::<8>().0.iter())
500507
.map(|(lhs, rhs)| {
501-
let lhs = u64::from_le_bytes(lhs.try_into().unwrap());
502-
let rhs = u64::from_le_bytes(rhs.try_into().unwrap());
508+
let lhs = u64::from_le_bytes(*lhs);
509+
let rhs = u64::from_le_bytes(*rhs);
503510
(lhs ^ rhs).count_ones()
504511
})
505512
.sum()
@@ -570,15 +577,16 @@ impl QuantizedQuery {
570577
};
571578

572579
// Single fused pass: quantize each element, accumulate sum, and scatter
573-
// bits into a flat bit-plane buffer via chunks_exact(8). The exact-chunk
580+
// bits into a flat bit-plane buffer via as_chunks::<8>(). The exact-chunk
574581
// guarantee lets LLVM eliminate bounds checks and generate tighter code
575582
// (44% faster than chunks(8) on Apple M-series).
576583
//
577584
// Layout: plane j occupies bit_planes[j*padded_bytes .. (j+1)*padded_bytes].
578585
let inv_delta = 1.0 / delta;
579586
let mut bit_planes = vec![0u8; B_Q as usize * padded_bytes];
580587
let mut sum_q_u = 0u32;
581-
for (byte_idx, chunk) in r_q.chunks_exact(8).enumerate() {
588+
let (r_q_chunks, r_q_rem) = r_q.as_chunks::<8>();
589+
for (byte_idx, chunk) in r_q_chunks.iter().enumerate() {
582590
let (mut b0, mut b1, mut b2, mut b3) = (0u8, 0u8, 0u8, 0u8);
583591
for (bit, &v) in chunk.iter().enumerate() {
584592
let qu = (((v - v_l) * inv_delta).round() as u32).min(max_val);
@@ -594,11 +602,10 @@ impl QuantizedQuery {
594602
bit_planes[3 * padded_bytes + byte_idx] = b3;
595603
}
596604
// Handle remainder for dim not divisible by 8.
597-
let rem = r_q.chunks_exact(8).remainder();
598-
if !rem.is_empty() {
605+
if !r_q_rem.is_empty() {
599606
let byte_idx = r_q.len() / 8;
600607
let (mut b0, mut b1, mut b2, mut b3) = (0u8, 0u8, 0u8, 0u8);
601-
for (bit, &v) in rem.iter().enumerate() {
608+
for (bit, &v) in r_q_rem.iter().enumerate() {
602609
let qu = (((v - v_l) * inv_delta).round() as u32).min(max_val);
603610
sum_q_u += qu;
604611
b0 |= (((qu) & 1) as u8) << bit;

rust/types/src/base64_decode.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,9 @@ pub fn decode_base64_embedding(base64_str: &String) -> Result<Vec<f32>, Base64De
103103
}
104104

105105
let mut floats = Vec::with_capacity(float_count);
106-
for (embedding_index, chunk) in bytes.chunks_exact(4).enumerate() {
107-
let float_bytes: [u8; 4] = chunk
108-
.try_into()
109-
.map_err(|_| Base64DecodeError::EmbeddingConversionFailed { embedding_index })?;
106+
for (embedding_index, chunk) in bytes.as_chunks::<4>().0.iter().enumerate() {
110107
// handles little endian encoding
111-
let f = f32::from_le_bytes(float_bytes);
108+
let f = f32::from_le_bytes(*chunk);
112109
if !f.is_finite() {
113110
return Err(Base64DecodeError::NonFiniteFloatValue {
114111
embedding_index,

rust/types/src/sparse_posting_block.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -317,8 +317,10 @@ impl SparsePostingBlock {
317317
let weight_start = Self::body_weight_offset(num_entries, bits_per_delta);
318318
let weight_bytes = &raw_body[weight_start..weight_start + num_entries * 2];
319319
let values: Vec<f32> = weight_bytes
320-
.chunks_exact(2)
321-
.map(|b| f16::from_le_bytes([b[0], b[1]]).to_f32())
320+
.as_chunks::<2>()
321+
.0
322+
.iter()
323+
.map(|b| f16::from_le_bytes(*b).to_f32())
322324
.collect();
323325

324326
Decompressed { offsets, values }
@@ -975,8 +977,8 @@ pub fn convert_f16_to_f32(f16_bytes: &[u8], out: &mut [f32]) {
975977

976978
/// Scalar f16→f32 conversion via the `half` crate.
977979
pub fn convert_f16_to_f32_scalar(f16_bytes: &[u8], out: &mut [f32]) {
978-
for (o, chunk) in out.iter_mut().zip(f16_bytes.chunks_exact(2)) {
979-
*o = f16::from_le_bytes([chunk[0], chunk[1]]).to_f32();
980+
for (o, chunk) in out.iter_mut().zip(f16_bytes.as_chunks::<2>().0) {
981+
*o = f16::from_le_bytes(*chunk).to_f32();
980982
}
981983
}
982984

rust/types/src/where_parsing.rs

Lines changed: 28 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -258,14 +258,13 @@ pub fn parse_where(json_payload: &Value) -> Result<Where, WhereValidationError>
258258
}
259259
let (operator, operand) = value_obj.iter().next().unwrap();
260260
if operand.is_array() {
261-
let set_operator;
262-
if operator == "$in" {
263-
set_operator = crate::SetOperator::In;
261+
let set_operator = if operator == "$in" {
262+
crate::SetOperator::In
264263
} else if operator == "$nin" {
265-
set_operator = crate::SetOperator::NotIn;
264+
crate::SetOperator::NotIn
266265
} else {
267266
return Err(WhereValidationError::WhereClause);
268-
}
267+
};
269268
let operand = operand.as_array().unwrap();
270269
if operand.is_empty() {
271270
return Err(WhereValidationError::WhereClause);
@@ -374,14 +373,13 @@ pub fn parse_where(json_payload: &Value) -> Result<Where, WhereValidationError>
374373
pattern: operand_str.to_string(),
375374
}));
376375
}
377-
let operator_type;
378-
if operator == "$eq" {
379-
operator_type = PrimitiveOperator::Equal;
376+
let operator_type = if operator == "$eq" {
377+
PrimitiveOperator::Equal
380378
} else if operator == "$ne" {
381-
operator_type = PrimitiveOperator::NotEqual;
379+
PrimitiveOperator::NotEqual
382380
} else {
383381
return Err(WhereValidationError::WhereClause);
384-
}
382+
};
385383
return Ok(Where::Metadata(MetadataExpression {
386384
key: key.clone(),
387385
comparison: crate::MetadataComparison::Primitive(
@@ -405,14 +403,13 @@ pub fn parse_where(json_payload: &Value) -> Result<Where, WhereValidationError>
405403
),
406404
}));
407405
}
408-
let operator_type;
409-
if operator == "$eq" {
410-
operator_type = PrimitiveOperator::Equal;
406+
let operator_type = if operator == "$eq" {
407+
PrimitiveOperator::Equal
411408
} else if operator == "$ne" {
412-
operator_type = PrimitiveOperator::NotEqual;
409+
PrimitiveOperator::NotEqual
413410
} else {
414411
return Err(WhereValidationError::WhereClause);
415-
}
412+
};
416413
return Ok(Where::Metadata(MetadataExpression {
417414
key: key.clone(),
418415
comparison: crate::MetadataComparison::Primitive(
@@ -436,22 +433,21 @@ pub fn parse_where(json_payload: &Value) -> Result<Where, WhereValidationError>
436433
),
437434
}));
438435
}
439-
let operator_type;
440-
if operator == "$eq" {
441-
operator_type = PrimitiveOperator::Equal;
436+
let operator_type = if operator == "$eq" {
437+
PrimitiveOperator::Equal
442438
} else if operator == "$ne" {
443-
operator_type = PrimitiveOperator::NotEqual;
439+
PrimitiveOperator::NotEqual
444440
} else if operator == "$lt" {
445-
operator_type = PrimitiveOperator::LessThan;
441+
PrimitiveOperator::LessThan
446442
} else if operator == "$lte" {
447-
operator_type = PrimitiveOperator::LessThanOrEqual;
443+
PrimitiveOperator::LessThanOrEqual
448444
} else if operator == "$gt" {
449-
operator_type = PrimitiveOperator::GreaterThan;
445+
PrimitiveOperator::GreaterThan
450446
} else if operator == "$gte" {
451-
operator_type = PrimitiveOperator::GreaterThanOrEqual;
447+
PrimitiveOperator::GreaterThanOrEqual
452448
} else {
453449
return Err(WhereValidationError::WhereClause);
454-
}
450+
};
455451
return Ok(Where::Metadata(MetadataExpression {
456452
key: key.clone(),
457453
comparison: crate::MetadataComparison::Primitive(
@@ -475,22 +471,21 @@ pub fn parse_where(json_payload: &Value) -> Result<Where, WhereValidationError>
475471
),
476472
}));
477473
}
478-
let operator_type;
479-
if operator == "$eq" {
480-
operator_type = PrimitiveOperator::Equal;
474+
let operator_type = if operator == "$eq" {
475+
PrimitiveOperator::Equal
481476
} else if operator == "$ne" {
482-
operator_type = PrimitiveOperator::NotEqual;
477+
PrimitiveOperator::NotEqual
483478
} else if operator == "$lt" {
484-
operator_type = PrimitiveOperator::LessThan;
479+
PrimitiveOperator::LessThan
485480
} else if operator == "$lte" {
486-
operator_type = PrimitiveOperator::LessThanOrEqual;
481+
PrimitiveOperator::LessThanOrEqual
487482
} else if operator == "$gt" {
488-
operator_type = PrimitiveOperator::GreaterThan;
483+
PrimitiveOperator::GreaterThan
489484
} else if operator == "$gte" {
490-
operator_type = PrimitiveOperator::GreaterThanOrEqual;
485+
PrimitiveOperator::GreaterThanOrEqual
491486
} else {
492487
return Err(WhereValidationError::WhereClause);
493-
}
488+
};
494489
return Ok(Where::Metadata(MetadataExpression {
495490
key: key.clone(),
496491
comparison: crate::MetadataComparison::Primitive(

0 commit comments

Comments
 (0)