diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e1e047c..d1fdf118 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ This release adds new control over Raft snapshotting, a key part of the Raft con ### Implementation changes and bug fixes - [PR #1528](https://github.com/rqlite/rqlite/pull/1528): Support setting trailing logs for user-requested snapshot. - [PR #1529](https://github.com/rqlite/rqlite/pull/1529): Remove obsolete code related to user-triggered snapshots. +- [PR #1536](https://github.com/rqlite/rqlite/pull/1536): Store WAL path in store, to avoid races. ## 8.13.5 (December 26th 2023) ### Implementation changes and bug fixes diff --git a/db/db.go b/db/db.go index d01b16e3..79b883d5 100644 --- a/db/db.go +++ b/db/db.go @@ -111,6 +111,11 @@ type PoolStats struct { MaxLifetimeClosed int64 `json:"max_lifetime_closed"` } +// WALPath returns the path to the WAL file for the given database path. +func WALPath(dbPath string) string { + return dbPath + "-wal" +} + // IsValidSQLiteFile checks that the supplied path looks like a SQLite file. // A non-existent file is considered invalid. func IsValidSQLiteFile(path string) bool { diff --git a/db/db_test.go b/db/db_test.go index 11d3fee5..a329bdfe 100644 --- a/db/db_test.go +++ b/db/db_test.go @@ -86,6 +86,9 @@ func Test_DBPaths(t *testing.T) { if exp, got := pathWAL+"-wal", dbWAL.WALPath(); exp != got { t.Fatalf("expected WAL path %s, got %s", exp, got) } + if p1, p2 := WALPath(pathWAL), dbWAL.WALPath(); p1 != p2 { + t.Fatalf("WAL paths are not equal (%s != %s)", p1, p2) + } db, path := mustCreateOnDiskDatabase() defer db.Close() diff --git a/snapshot/store.go b/snapshot/store.go index c1c2d7e5..8a798660 100644 --- a/snapshot/store.go +++ b/snapshot/store.go @@ -176,7 +176,9 @@ func (s *Store) SetFullNeeded() error { return f.Close() } -// Stats returns stats about the Snapshot Store. +// Stats returns stats about the Snapshot Store. This function may return +// an error if the Store is in an inconsistent state. In that case the stats +// returned may be incomplete or invalid. func (s *Store) Stats() (map[string]interface{}, error) { snapshots, err := s.getSnapshots() if err != nil { diff --git a/store/store.go b/store/store.go index d05c0818..99e853cf 100644 --- a/store/store.go +++ b/store/store.go @@ -215,14 +215,15 @@ type Store struct { restorePath string restoreDoneCh chan struct{} - raft *raft.Raft // The consensus mechanism. - ly Layer - raftTn *NodeTransport - raftID string // Node ID. - dbConf *DBConfig // SQLite database config. - dbPath string // Path to underlying SQLite file. - dbDir string // Path to directory containing SQLite file. - db *sql.DB // The underlying SQLite store. + raft *raft.Raft // The consensus mechanism. + ly Layer + raftTn *NodeTransport + raftID string // Node ID. + dbConf *DBConfig // SQLite database config. + dbPath string // Path to underlying SQLite file. + walPath string // Path to WAL file. + dbDir string // Path to directory containing SQLite file. + db *sql.DB // The underlying SQLite store. queryTxMu sync.RWMutex @@ -338,6 +339,7 @@ func New(ly Layer, c *Config) *Store { raftID: c.ID, dbConf: c.DBConf, dbPath: dbPath, + walPath: sql.WALPath(dbPath), dbDir: filepath.Dir(dbPath), leaderObservers: make([]chan<- struct{}, 0), reqMarshaller: command.NewRequestMarshaler(), @@ -952,11 +954,6 @@ func (s *Store) Stats() (map[string]interface{}, error) { return nil, err } - snapsStats, err := s.snapshotStore.Stats() - if err != nil { - return nil, err - } - lAppliedIdx, err := s.boltStore.GetAppliedIndex() if err != nil { return nil, err @@ -981,7 +978,6 @@ func (s *Store) Stats() (map[string]interface{}, error) { "apply_timeout": s.ApplyTimeout.String(), "heartbeat_timeout": s.HeartbeatTimeout.String(), "election_timeout": s.ElectionTimeout.String(), - "snapshot_store": snapsStats, "snapshot_threshold": s.SnapshotThreshold, "snapshot_interval": s.SnapshotInterval.String(), "reap_timeout": s.ReapTimeout.String(), @@ -995,6 +991,13 @@ func (s *Store) Stats() (map[string]interface{}, error) { "sqlite3": dbStatus, "db_conf": s.dbConf, } + + // Snapshot stats may be in flux if a snapshot is in progress. Only + // report them if they are available. + snapsStats, err := s.snapshotStore.Stats() + if err == nil { + status["snapshot_store"] = snapsStats + } return status, nil } @@ -1738,8 +1741,8 @@ func (s *Store) fsmSnapshot() (fSnap raft.FSMSnapshot, retErr error) { } else { compactedBuf := bytes.NewBuffer(nil) var err error - if pathExistsWithData(s.db.WALPath()) { - walFD, err := os.Open(s.db.WALPath()) + if pathExistsWithData(s.walPath) { + walFD, err := os.Open(s.walPath) if err != nil { return nil, err } @@ -1758,7 +1761,7 @@ func (s *Store) fsmSnapshot() (fSnap raft.FSMSnapshot, retErr error) { } walFD.Close() // We need it closed for the next step. - walSz, err := fileSize(s.db.WALPath()) + walSz, err := fileSize(s.walPath) if err != nil { return nil, err } @@ -1975,7 +1978,7 @@ func (s *Store) runWALSnapshotting() (closeCh, doneCh chan struct{}) { for { select { case <-ticker.C: - sz, err := s.db.WALSize() + sz, err := fileSize(s.walPath) if err != nil { s.logger.Printf("failed to get WAL size: %s", err.Error()) continue