Class: DiscordStore::Replay
- Inherits:
-
Object
- Object
- DiscordStore::Replay
- Defined in:
- lib/discord_store/replay.rb
Overview
Rebuilds a local database from the Discord log.
This is the operation that decides whether any of this is a real design or a stunt. If the channel is the source of truth, then a machine holding no local file must be able to reach the same state by reading it, and it must reach the same state every time. That is the whole contract:
rm storage/production.sqlite3 && rake discord:replay
should leave the database exactly as it was, on any machine, from nothing but a bot token and a channel ID.
Statements replay inside a local transaction per journalled transaction, so a batch that was atomic when written is atomic when replayed.
Instance Method Summary collapse
-
#call(from: nil, progress: nil) ⇒ Hash
statements:, cursor:, skipped:.
-
#initialize(connection:, journal:) ⇒ Replay
constructor
A new instance of Replay.
-
#status ⇒ Hash
How far behind the local database is.
Constructor Details
#initialize(connection:, journal:) ⇒ Replay
Returns a new instance of Replay.
21 22 23 24 |
# File 'lib/discord_store/replay.rb', line 21 def initialize(connection:, journal:) @connection = connection @journal = journal end |
Instance Method Details
#call(from: nil, progress: nil) ⇒ Hash
Returns statements:, cursor:, skipped:.
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
# File 'lib/discord_store/replay.rb', line 29 def call(from: nil, progress: nil) applied = 0 statements = 0 skipped = 0 cursor = from # Replay is a rewrite of local state from the log, so it must not be # journalled back into the log. Without this a replay would duplicate # every transaction it read. @journal.while_paused do # Inside the pause, because reading the stored cursor creates the # bookkeeping table, and that DDL must not become a log entry. from ||= stored_cursor cursor = from @journal.each_transaction(from: from) do |entry, record_cursor| begin apply(entry["tx"]) applied += 1 statements += entry["tx"].size rescue StandardError => e skipped += 1 warn "discord_store: replay skipped #{record_cursor}: #{e.class}: #{e.}" end cursor = record_cursor store_cursor(cursor) progress&.call(applied, cursor) end end @journal.local_cursor = cursor { applied: applied, statements: statements, cursor: cursor, skipped: skipped } end |
#status ⇒ Hash
How far behind the local database is.
67 68 69 70 71 |
# File 'lib/discord_store/replay.rb', line 67 def status local = stored_cursor remote = @journal.tip { local: local, remote: remote, behind: local != remote } end |