Skip to content

Commit 01d6891

Browse files
committed
store: Rebuild invalid postponed indexes left by interrupted runs
A CREATE INDEX CONCURRENTLY that is interrupted (e.g. by a node restart) leaves an invalid index behind. Because postponed indexes are created with 'if not exists', such a leftover was skipped on retry and never rebuilt, silently leaving the deployment without that index. Before creating each postponed index, drop any invalid remnant so the retry rebuilds it, mirroring what create_manual_index already does.
1 parent 8316ecb commit 01d6891

1 file changed

Lines changed: 24 additions & 3 deletions

File tree

store/postgres/src/deployment_store.rs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1693,7 +1693,9 @@ impl DeploymentStore {
16931693
/// logged and not returned to the caller. The `postponed_indexes_created`
16941694
/// flag is only set once all indexes have been created successfully, so a
16951695
/// failed run is retried the next time `graph-node` starts the
1696-
/// deployment.
1696+
/// deployment. A run that was interrupted (for example by a restart while
1697+
/// a `CREATE INDEX CONCURRENTLY` was still in flight) can leave an invalid
1698+
/// index behind; such remnants are dropped and rebuilt on the next run.
16971699
pub(crate) fn create_postponed_indexes(&self, site: Arc<Site>) {
16981700
async fn run(store: DeploymentStore, site: Arc<Site>) -> Result<(), StoreError> {
16991701
let layout = store.find_layout(site.cheap_clone()).await?;
@@ -1704,14 +1706,33 @@ impl DeploymentStore {
17041706
return Ok(());
17051707
}
17061708

1709+
let schema_name = site.namespace.as_str();
17071710
for table in layout.tables.values() {
17081711
let indexes = table.indexes(&layout.input_schema).map_err(|e| {
17091712
StoreError::ConstraintViolation(format!("failed to generate indexes: {}", e))
17101713
})?;
17111714
for idx in indexes {
1712-
if idx.to_postpone() {
1713-
IndexCreator::execute(&creat, &mut conn, &idx).await?;
1715+
if !idx.to_postpone() {
1716+
continue;
17141717
}
1718+
1719+
// A previous run that was interrupted (e.g. by a node
1720+
// restart) can leave an invalid index behind. Since we
1721+
// create indexes with `if not exists`, such a leftover
1722+
// would be skipped and never rebuilt, so drop it first.
1723+
// `check_index_is_valid` returns `false` both when the
1724+
// index is missing and when it is invalid; the
1725+
// `drop index ... if exists` is a no-op in the former
1726+
// case and removes the invalid remnant in the latter.
1727+
if let Some(name) = idx.name()
1728+
&& !catalog::check_index_is_valid(&mut conn, schema_name, &name).await?
1729+
{
1730+
let drop_sql =
1731+
format!("drop index concurrently if exists {schema_name}.{name}");
1732+
sql_query(drop_sql).execute(&mut conn).await?;
1733+
}
1734+
1735+
IndexCreator::execute(&creat, &mut conn, &idx).await?;
17151736
}
17161737
}
17171738

0 commit comments

Comments
 (0)