Tre ting implementert: 1. PG-dump rutine (scripts/backup-pg.sh): - Daglig cron kl. 03:00 UTC via /etc/cron.d/synops-backup - pg_dump -Fc (custom format, komprimert), konsistent uten nedetid - Rotasjon: beholder 30 dager, sletter eldre - Verifiserer at dump-filen er gyldig (ikke tom) 2. STDB → PG gjenoppbygging ved krasj (stdb_monitor.rs): - Bakgrunnsmonitor sjekker STDB hvert 30. sekund - Oppdager krasj (var oppe → nå nede) - Venter på at containeren restarter (maks 10 min) - Kjører warmup (PG → STDB) automatisk - Hele prosessen logges 3. Forbedret backup-helsesjekk (health.rs): - Sjekker /srv/synops/backup/pg/ for nyeste dump - Rapporterer ok/stale/missing i /admin/health
145 lines
4.6 KiB
Rust
145 lines
4.6 KiB
Rust
// STDB-overvåker: oppdager SpacetimeDB-krasj og gjenoppbygger fra PG.
|
|
//
|
|
// Kjører i bakgrunnen med jevnlig helsesjekk. Hvis STDB var oppe og
|
|
// deretter feiler, kjøres warmup automatisk for å gjenoppbygge tilstand.
|
|
//
|
|
// Sekvens ved krasj:
|
|
// 1. Oppdage at STDB er nede (helsesjekk feiler)
|
|
// 2. Vente til STDB er tilbake (container restarter)
|
|
// 3. Kjøre warmup (PG → STDB)
|
|
// 4. Logge hendelsen
|
|
//
|
|
// Ref: docs/infra/backup.md, docs/infra/synkronisering.md
|
|
|
|
use sqlx::PgPool;
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
use std::sync::Arc;
|
|
|
|
use crate::stdb::StdbClient;
|
|
|
|
/// Start STDB-overvåker i bakgrunnen.
|
|
/// Sjekker STDB-helse hvert 30. sekund og kjører warmup ved krasj.
|
|
pub fn start_stdb_monitor(db: PgPool, stdb: StdbClient) {
|
|
tokio::spawn(async move {
|
|
monitor_loop(db, stdb).await;
|
|
});
|
|
}
|
|
|
|
/// Intern tilstand for overvåkeren.
|
|
struct MonitorState {
|
|
/// Var STDB oppe ved forrige sjekk?
|
|
was_up: bool,
|
|
/// Pågår det en recovery akkurat nå?
|
|
recovering: Arc<AtomicBool>,
|
|
}
|
|
|
|
async fn monitor_loop(db: PgPool, stdb: StdbClient) {
|
|
let mut state = MonitorState {
|
|
was_up: true, // Antar oppe etter warmup ved oppstart
|
|
recovering: Arc::new(AtomicBool::new(false)),
|
|
};
|
|
|
|
// Vent litt etter oppstart slik at warmup fullføres først
|
|
tokio::time::sleep(std::time::Duration::from_secs(60)).await;
|
|
tracing::info!("STDB-overvåker startet (sjekker hvert 30s)");
|
|
|
|
let mut interval = tokio::time::interval(std::time::Duration::from_secs(30));
|
|
|
|
loop {
|
|
interval.tick().await;
|
|
|
|
// Ikke sjekk hvis recovery allerede pågår
|
|
if state.recovering.load(Ordering::Relaxed) {
|
|
continue;
|
|
}
|
|
|
|
let is_up = check_stdb_health(&stdb).await;
|
|
|
|
match (state.was_up, is_up) {
|
|
(true, false) => {
|
|
// STDB gikk ned! Logg og start recovery-venting.
|
|
tracing::error!("STDB-overvåker: SpacetimeDB er NEDE — starter recovery-prosess");
|
|
state.recovering.store(true, Ordering::Relaxed);
|
|
|
|
let db_clone = db.clone();
|
|
let stdb_clone = stdb.clone();
|
|
let recovering = state.recovering.clone();
|
|
|
|
tokio::spawn(async move {
|
|
recover_stdb(db_clone, stdb_clone, recovering).await;
|
|
});
|
|
}
|
|
(false, true) => {
|
|
// STDB kom tilbake uten vår hjelp (recovery-tasken fikset det)
|
|
tracing::info!("STDB-overvåker: SpacetimeDB er tilbake");
|
|
state.was_up = true;
|
|
}
|
|
(false, false) => {
|
|
// Fortsatt nede — recovery-tasken håndterer dette
|
|
}
|
|
(true, true) => {
|
|
// Alt OK
|
|
}
|
|
}
|
|
|
|
if is_up {
|
|
state.was_up = true;
|
|
} else if !state.recovering.load(Ordering::Relaxed) {
|
|
state.was_up = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Sjekk om STDB svarer på en enkel reducer-kall.
|
|
async fn check_stdb_health(stdb: &StdbClient) -> bool {
|
|
stdb.delete_node("__healthcheck_nonexistent__").await.is_ok()
|
|
}
|
|
|
|
/// Vent til STDB er tilbake, deretter kjør warmup.
|
|
async fn recover_stdb(db: PgPool, stdb: StdbClient, recovering: Arc<AtomicBool>) {
|
|
let max_wait = std::time::Duration::from_secs(600); // Maks 10 min
|
|
let check_interval = std::time::Duration::from_secs(10);
|
|
let start = std::time::Instant::now();
|
|
|
|
tracing::info!("STDB-recovery: venter på at SpacetimeDB starter opp igjen (maks 10 min)");
|
|
|
|
// Vent til STDB svarer
|
|
loop {
|
|
if start.elapsed() > max_wait {
|
|
tracing::error!(
|
|
"STDB-recovery: SpacetimeDB kom ikke tilbake innen {} sekunder — gir opp",
|
|
max_wait.as_secs()
|
|
);
|
|
recovering.store(false, Ordering::Relaxed);
|
|
return;
|
|
}
|
|
|
|
tokio::time::sleep(check_interval).await;
|
|
|
|
if check_stdb_health(&stdb).await {
|
|
tracing::info!(
|
|
"STDB-recovery: SpacetimeDB svarer igjen etter {}s",
|
|
start.elapsed().as_secs()
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// STDB er tilbake — kjør warmup
|
|
tracing::info!("STDB-recovery: kjører warmup (PG → STDB)");
|
|
match crate::warmup::run(&db, &stdb).await {
|
|
Ok(stats) => {
|
|
tracing::info!(
|
|
"STDB-recovery: warmup fullført ({} noder, {} edges, {} access)",
|
|
stats.nodes,
|
|
stats.edges,
|
|
stats.access
|
|
);
|
|
}
|
|
Err(e) => {
|
|
tracing::error!("STDB-recovery: warmup feilet: {e}");
|
|
}
|
|
}
|
|
|
|
recovering.store(false, Ordering::Relaxed);
|
|
}
|