Skip to content

Commit 60d859c

Browse files
g-talbotclaude
andcommitted
fix: add FOR UPDATE row locking to delete_metrics_splits
Mirror the CTE + FOR UPDATE pattern from delete_splits to prevent stale-state races. Without row locking, a concurrent mark_metrics_splits_for_deletion can commit between the state read and the DELETE, causing spurious FailedPrecondition errors and retry churn. The new query locks the target rows before reading their state, reports not-deletable (Staged/Published) and not-found splits separately, and only deletes when all requested splits are in MarkedForDeletion state. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 5582dcc commit 60d859c

File tree

1 file changed

+15
-7
lines changed
  • quickwit/quickwit-metastore/src/metastore/postgres

1 file changed

+15
-7
lines changed

quickwit/quickwit-metastore/src/metastore/postgres/metastore.rs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2625,23 +2625,31 @@ impl MetastoreService for PostgresqlMetastore {
26252625
if request.split_ids.is_empty() {
26262626
return Ok(EmptyResponse {});
26272627
}
2628+
let index_uid: IndexUid = request.index_uid().clone();
26282629

26292630
info!(
26302631
index_uid = %request.index_uid(),
26312632
split_ids = ?request.split_ids,
26322633
"deleting metrics splits"
26332634
);
26342635

2635-
// Only delete splits that are marked for deletion
2636-
// Match the non-metrics delete_splits pattern: distinguish
2637-
// "not found" (warn + succeed) from "not deletable" (FailedPrecondition).
2636+
// Match the non-metrics delete_splits pattern: CTE with FOR UPDATE
2637+
// to lock rows before reading state, avoiding stale-state races under
2638+
// concurrent mark_metrics_splits_for_deletion. Distinguishes "not found"
2639+
// (warn + succeed) from "not deletable" (FailedPrecondition).
26382640
const DELETE_SPLITS_QUERY: &str = r#"
26392641
WITH input_splits AS (
26402642
SELECT input_splits.split_id, metrics_splits.split_state
26412643
FROM UNNEST($2::text[]) AS input_splits(split_id)
2642-
LEFT JOIN metrics_splits
2643-
ON metrics_splits.index_uid = $1
2644-
AND metrics_splits.split_id = input_splits.split_id
2644+
LEFT JOIN (
2645+
SELECT split_id, split_state
2646+
FROM metrics_splits
2647+
WHERE
2648+
index_uid = $1
2649+
AND split_id = ANY($2)
2650+
FOR UPDATE
2651+
) AS metrics_splits
2652+
USING (split_id)
26452653
),
26462654
deleted AS (
26472655
DELETE FROM metrics_splits
@@ -2680,7 +2688,7 @@ impl MetastoreService for PostgresqlMetastore {
26802688
.bind(&request.split_ids)
26812689
.fetch_one(&self.connection_pool)
26822690
.await
2683-
.map_err(|sqlx_error| convert_sqlx_err(&request.index_uid().index_id, sqlx_error))?;
2691+
.map_err(|sqlx_error| convert_sqlx_err(&index_uid.index_id, sqlx_error))?;
26842692

26852693
if !not_deletable_ids.is_empty() {
26862694
let message = format!(

0 commit comments

Comments
 (0)