Skip to content

Commit 39d589c

Browse files
passcodclaude
andauthored
feat: track restore stage in _pgro.restore_info (#25)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent da39e6c commit 39d589c

3 files changed

Lines changed: 103 additions & 29 deletions

File tree

.rules

Lines changed: 0 additions & 24 deletions
This file was deleted.

src/controllers/restore/builders.rs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -785,21 +785,33 @@ else
785785
ALTER ROLE ${{ANALYTICS_USERNAME}} WITH SUPERUSER;
786786
SQLEOF
787787
fi
788-
echo "Writing restore metadata..."
788+
if [ -f /pgdata/needs-reindex ]; then
789+
PGRO_STAGE=restored
790+
else
791+
PGRO_STAGE=ready
792+
fi
793+
794+
echo "Writing restore metadata (stage=${{PGRO_STAGE}})..."
789795
psql -U postgres -d postgres << SQLEOF
790796
CREATE SCHEMA IF NOT EXISTS _pgro;
791797
CREATE TABLE IF NOT EXISTS _pgro.restore_info (
792798
id integer PRIMARY KEY DEFAULT 1,
793799
snapshot_id text NOT NULL,
794800
snapshot_time timestamptz,
795-
restored_at timestamptz NOT NULL DEFAULT now()
801+
restored_at timestamptz NOT NULL DEFAULT now(),
802+
stage text NOT NULL DEFAULT 'restored',
803+
last_transition_time timestamptz NOT NULL DEFAULT now()
796804
);
797-
INSERT INTO _pgro.restore_info (id, snapshot_id, snapshot_time)
798-
VALUES (1, '${{PGRO_SNAPSHOT_ID}}', CASE WHEN '${{PGRO_SNAPSHOT_TIME}}' = '' THEN NULL ELSE '${{PGRO_SNAPSHOT_TIME}}'::timestamptz END)
805+
ALTER TABLE _pgro.restore_info ADD COLUMN IF NOT EXISTS stage text NOT NULL DEFAULT 'restored';
806+
ALTER TABLE _pgro.restore_info ADD COLUMN IF NOT EXISTS last_transition_time timestamptz NOT NULL DEFAULT now();
807+
INSERT INTO _pgro.restore_info (id, snapshot_id, snapshot_time, stage, last_transition_time)
808+
VALUES (1, '${{PGRO_SNAPSHOT_ID}}', CASE WHEN '${{PGRO_SNAPSHOT_TIME}}' = '' THEN NULL ELSE '${{PGRO_SNAPSHOT_TIME}}'::timestamptz END, '${{PGRO_STAGE}}', now())
799809
ON CONFLICT (id) DO UPDATE
800810
SET snapshot_id = EXCLUDED.snapshot_id,
801811
snapshot_time = EXCLUDED.snapshot_time,
802-
restored_at = now();
812+
restored_at = now(),
813+
stage = EXCLUDED.stage,
814+
last_transition_time = now();
803815
SQLEOF
804816
805817
echo "Stopping temporary postgres..."
@@ -951,6 +963,7 @@ if [ -f /pgdata/needs-reindex ]; then
951963
PG_MAJOR=$(cat /pgdata/pgdata/PG_VERSION)
952964
(
953965
while ! pg_isready -q -U postgres -d postgres; do sleep 2; done
966+
psql -U postgres -d postgres -c "UPDATE _pgro.restore_info SET stage = 'reindexing', last_transition_time = now() WHERE id = 1;"
954967
for db in $(psql -U postgres -d postgres -At -c "SELECT datname FROM pg_database WHERE datallowconn AND datname <> 'template0'"); do
955968
INDEXES=$(psql -U postgres -d "$db" -At -c "
956969
SELECT DISTINCT indexrelid::regclass::text
@@ -973,6 +986,7 @@ if [ -f /pgdata/needs-reindex ]; then
973986
done
974987
done
975988
rm -f /pgdata/needs-reindex
989+
psql -U postgres -d postgres -c "UPDATE _pgro.restore_info SET stage = 'ready', last_transition_time = now() WHERE id = 1;"
976990
echo "Background reindex complete"
977991
) &
978992
fi

src/controllers/restore/tests.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,90 @@ fn deployment_init_script_sets_shared_buffers() {
307307
);
308308
}
309309

310+
#[test]
311+
fn init_script_sets_initial_stage_based_on_reindex_flag() {
312+
let (mut restore, replica) = test_restore_and_replica();
313+
restore.status = Some(PostgresPhysicalRestoreStatus {
314+
postgres_version: Some("16".to_string()),
315+
..Default::default()
316+
});
317+
318+
let deploy = build_deployment(&restore, "test-restore", "default", &replica).unwrap();
319+
let pod_spec = deploy.spec.unwrap().template.spec.unwrap();
320+
321+
let setup_auth = pod_spec
322+
.init_containers
323+
.as_ref()
324+
.unwrap()
325+
.iter()
326+
.find(|c| c.name == "setup-auth")
327+
.expect("setup-auth init container must exist");
328+
let script = &setup_auth.args.as_ref().unwrap()[0];
329+
330+
assert!(
331+
script.contains("stage text NOT NULL DEFAULT 'restored'"),
332+
"table must include stage column"
333+
);
334+
assert!(
335+
script.contains("last_transition_time timestamptz NOT NULL DEFAULT now()"),
336+
"table must include last_transition_time column"
337+
);
338+
assert!(
339+
script.contains("ADD COLUMN IF NOT EXISTS stage"),
340+
"must add stage column for existing tables carried in snapshot"
341+
);
342+
assert!(
343+
script.contains("ADD COLUMN IF NOT EXISTS last_transition_time"),
344+
"must add last_transition_time column for existing tables carried in snapshot"
345+
);
346+
assert!(
347+
script.contains("PGRO_STAGE=restored") && script.contains("PGRO_STAGE=ready"),
348+
"init must pick stage based on needs-reindex flag"
349+
);
350+
assert!(
351+
script.contains("'${PGRO_STAGE}'"),
352+
"insert must use the chosen stage"
353+
);
354+
}
355+
356+
#[test]
357+
fn postgres_container_updates_stage_around_reindex() {
358+
let (mut restore, replica) = test_restore_and_replica();
359+
restore.status = Some(PostgresPhysicalRestoreStatus {
360+
postgres_version: Some("16".to_string()),
361+
..Default::default()
362+
});
363+
364+
let deploy = build_deployment(&restore, "test-restore", "default", &replica).unwrap();
365+
let pod_spec = deploy.spec.unwrap().template.spec.unwrap();
366+
367+
let postgres = pod_spec
368+
.containers
369+
.iter()
370+
.find(|c| c.name == "postgres")
371+
.expect("postgres container must exist");
372+
let script = &postgres.args.as_ref().unwrap()[0];
373+
374+
let reindexing_pos = script
375+
.find("stage = 'reindexing'")
376+
.expect("must update stage to reindexing before the REINDEX loop");
377+
let ready_pos = script
378+
.find("stage = 'ready'")
379+
.expect("must update stage to ready after the REINDEX loop");
380+
assert!(
381+
reindexing_pos < ready_pos,
382+
"reindexing update must come before ready update"
383+
);
384+
assert!(
385+
reindexing_pos < script.find("REINDEX INDEX").unwrap(),
386+
"reindexing update must come before any REINDEX call"
387+
);
388+
assert!(
389+
script[..ready_pos].contains("rm -f /pgdata/needs-reindex"),
390+
"ready update must happen after the needs-reindex flag is cleared"
391+
);
392+
}
393+
310394
#[test]
311395
fn deployment_shared_buffers_with_custom_resources() {
312396
let (mut restore, mut replica) = test_restore_and_replica();

0 commit comments

Comments
 (0)