11use super :: committed_state:: CommittedState ;
2- use super :: datastore:: Result ;
2+ use super :: datastore:: { Locking , Result } ;
33use crate :: db_metrics:: DB_METRICS ;
44use crate :: error:: { IndexError , TableError } ;
55use crate :: locking_tx_datastore:: datastore:: ReplayError ;
6- use crate :: locking_tx_datastore:: state_view:: iter_st_column_for_table;
7- use crate :: locking_tx_datastore :: state_view :: StateView ;
8- use crate :: system_tables :: { is_built_in_meta_row, StFields as _} ;
9- use crate :: system_tables :: { StColumnRow , StTableFields , StTableRow , ST_COLUMN_ID , ST_TABLE_ID } ;
6+ use crate :: locking_tx_datastore:: state_view:: { iter_st_column_for_table, StateView } ;
7+ use crate :: system_tables :: {
8+ is_built_in_meta_row, StColumnRow , StFields as _, StTableFields , StTableRow , ST_COLUMN_ID , ST_TABLE_ID ,
9+ } ;
1010use anyhow:: { anyhow, Context } ;
1111use core:: ops:: { Deref , DerefMut , RangeBounds } ;
1212use parking_lot:: { RwLock , RwLockReadGuard } ;
13+ use prometheus:: core:: { AtomicF64 , GenericGauge } ;
14+ use prometheus:: IntGauge ;
1315use spacetimedb_commitlog:: payload:: txdata;
1416use spacetimedb_data_structures:: map:: { HashSet , IntMap , IntSet } ;
17+ use spacetimedb_durability:: History ;
18+ use spacetimedb_durability:: Txdata ;
1519use spacetimedb_lib:: Identity ;
1620use spacetimedb_primitives:: { ColId , ColList , TableId } ;
1721use spacetimedb_sats:: algebraic_value:: de:: ValueDeserializer ;
@@ -24,6 +28,68 @@ use spacetimedb_table::table::{InsertError, RowRef};
2428use std:: cell:: RefCell ;
2529use std:: sync:: Arc ;
2630
31+ pub fn apply_history (
32+ datastore : & Locking ,
33+ database_identity : Identity ,
34+ history : impl History < TxData = Txdata < ProductValue > > ,
35+ counters : ApplyHistoryCounters ,
36+ ) -> Result < ( ) > {
37+ log:: info!( "[{database_identity}] DATABASE: applying transaction history..." ) ;
38+
39+ // TODO: Revisit once we actually replay history suffixes, ie. starting
40+ // from an offset larger than the history's min offset.
41+ // TODO: We may want to require that a `tokio::runtime::Handle` is
42+ // always supplied when constructing a `RelationalDB`. This would allow
43+ // to spawn a timer task here which just prints the progress periodically
44+ // in case the history is finite but very long.
45+ let ( _, max_tx_offset) = history. tx_range_hint ( ) ;
46+ let mut last_logged_percentage = 0 ;
47+ let progress = |tx_offset : u64 | {
48+ if let Some ( max_tx_offset) = max_tx_offset {
49+ let percentage = f64:: floor ( ( tx_offset as f64 / max_tx_offset as f64 ) * 100.0 ) as i32 ;
50+ if percentage > last_logged_percentage && percentage % 10 == 0 {
51+ log:: info!( "[{database_identity}] Loaded {percentage}% ({tx_offset}/{max_tx_offset})" ) ;
52+ last_logged_percentage = percentage;
53+ }
54+ // Print _something_ even if we don't know what's still ahead.
55+ } else if tx_offset. is_multiple_of ( 10_000 ) {
56+ log:: info!( "[{database_identity}] Loading transaction {tx_offset}" ) ;
57+ }
58+ } ;
59+
60+ let time_before = std:: time:: Instant :: now ( ) ;
61+
62+ let mut replay = datastore. replay (
63+ progress,
64+ // We don't want to instantiate an incorrect state;
65+ // if the commitlog contains an inconsistency we'd rather get a hard error than showing customers incorrect data.
66+ ErrorBehavior :: FailFast ,
67+ ) ;
68+ let start_tx_offset = replay. next_tx_offset ( ) ;
69+ history
70+ . fold_transactions_from ( start_tx_offset, & mut replay)
71+ . map_err ( anyhow:: Error :: from) ?;
72+
73+ let time_elapsed = time_before. elapsed ( ) ;
74+ counters. replay_commitlog_time_seconds . set ( time_elapsed. as_secs_f64 ( ) ) ;
75+
76+ let end_tx_offset = replay. next_tx_offset ( ) ;
77+ counters
78+ . replay_commitlog_num_commits
79+ . set ( ( end_tx_offset - start_tx_offset) as _ ) ;
80+
81+ log:: info!( "[{database_identity}] DATABASE: applied transaction history" ) ;
82+ datastore. rebuild_state_after_replay ( ) ?;
83+ log:: info!( "[{database_identity}] DATABASE: rebuilt state after replay" ) ;
84+
85+ Ok ( ( ) )
86+ }
87+
88+ pub struct ApplyHistoryCounters {
89+ pub replay_commitlog_time_seconds : GenericGauge < AtomicF64 > ,
90+ pub replay_commitlog_num_commits : IntGauge ,
91+ }
92+
2793/// A [`spacetimedb_commitlog::Decoder`] suitable for replaying a transaction
2894/// history into the database state.
2995pub struct Replay < F > {
0 commit comments