mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 16:02:26 +00:00
4.1 KiB
4.1 KiB
EventsCacheStore Overview
The EventsCacheStore is a specialized persistence layer designed to handle the application's critical state: thousands of video events and their deletion status (tombstones). It uses IndexedDB as the primary storage backend, falling back to localStorage (via PersistenceManager) only if IDB is unavailable.
1. Problem Statement
Nostr clients must handle high-volume event streams. Storing every event naively to disk on every change causes:
- Write Trashing: Frequent, redundant writes degrade performance and battery life.
- I/O Blocking: Large JSON serializations on the main thread cause UI jank.
- Startup Latency: Reading thousands of small files or one giant blob is slow.
2. Architecture: Incremental Persistence
EventsCacheStore solves this with a Differential State Snapshot approach.
Key Concepts
-
Memory Fingerprints:
- The store maintains a
Map<ID, Fingerprint>in memory, mirroring what is currently on disk. Event Fingerprint:JSON.stringify(videoObject)(fast enough for 1000s of items).Tombstone Fingerprint:ts:<timestamp>.
- The store maintains a
-
Debounced Snapshots:
PersistenceManagerdebounces writes (e.g., 75ms). When a write triggers, it passes the entire current state (allEvents) plus a set ofdirtyKeys(IDs that changed since last save).
-
The Diff Algorithm (
_diffAndStore):- Instead of clearing and rewriting the DB, it iterates over the
dirtyKeys. - It computes the new fingerprint.
- Optimization: If
newFingerprint === persistedFingerprint, the write is skipped. - If different, it performs a
put()operation and updates the memory fingerprint map.
- Instead of clearing and rewriting the DB, it iterates over the
-
Deletion Detection:
- After processing updates, it compares the size of the memory state (
items.size) vs the persisted state (fingerprintsMap.size). - Since
fingerprintsMapis updated during the loop to include new items, ifitems.size < fingerprintsMap.size, it implies some items in the persisted map are no longer in memory. - In this case, it iterates the entire fingerprint map to find and
delete()the missing keys.
- After processing updates, it compares the size of the memory state (
Flow Diagram
sequenceDiagram
participant App as NostrClient
participant PM as PersistenceManager
participant ECS as EventsCacheStore
participant IDB as IndexedDB
App->>App: Event arrives (updates allEvents)
App->>PM: saveLocalData(reason, dirtyKeys)
PM->>PM: Debounce (75ms)
PM->>ECS: persistSnapshot(payload, dirtyKeys)
activate ECS
ECS->>ECS: Ensure fingerprints loaded (once)
loop For each Dirty Key
ECS->>ECS: Compute Fingerprint
alt Fingerprint Changed
ECS->>IDB: put(record)
ECS->>ECS: Update Fingerprint Map
else Same Fingerprint
ECS->>ECS: Skip
end
end
alt items.size < fingerprintsMap.size
loop For each Persisted Key
alt Not in Memory
ECS->>IDB: delete(key)
ECS->>ECS: Remove from Fingerprint Map
end
end
end
ECS-->>PM: { writes, deletes, skipped }
deactivate ECS
3. Data Schema
The IndexedDB bitvid-events-cache (v1) contains three object stores:
| Store | Key Path | Value Description |
|---|---|---|
events |
id |
{ id, video, fingerprint } |
tombstones |
key |
{ key, timestamp, fingerprint } |
meta |
key |
{ key: "meta", savedAt: <ts>, version: 1 } |
4. Invariants & Assumptions
- Single Threaded: The store assumes it is the only writer. It does not handle cross-tab locking (though IDB transactions provide atomicity).
- Memory-First: The
allEventsMap in memory is the source of truth. IDB is a cache. - TTL: On
restoreSnapshot(), if the data is older than the TTL (e.g. 10 minutes), the entire cache is wiped to ensure freshness.
5. Maintenance
- Adding Fields: If the
Videoobject shape changes, the fingerprint (JSON string) automatically changes, forcing a rewrite. No migration needed for content changes. - Schema Changes: To add a new store, increment
EVENTS_CACHE_DB_VERSIONand updateonupgradeneededingetDb().