Compare commits
1
Commits
next
..
cdc0d33da7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cdc0d33da7 |
@@ -5,22 +5,6 @@ table in real time: it streams BGP UPDATE messages from the RIPE RIS Live
|
|||||||
service, maintains a live routing table in SQLite, and provides HTTP APIs for
|
service, maintains a live routing table in SQLite, and provides HTTP APIs for
|
||||||
querying routing information.
|
querying routing information.
|
||||||
|
|
||||||
## Rationale
|
|
||||||
|
|
||||||
RIPE RIS Live streams the BGP updates that RIPE's route collectors receive, as
|
|
||||||
they happen, from the moment a client connects. It reports changes, not the
|
|
||||||
routing table they add up to, so it cannot say which AS an IP address is routed
|
|
||||||
to now or which routes a prefix has. RouteWatch applies the feed's updates to
|
|
||||||
live routing tables in its own SQLite database and answers IP-to-AS lookups and
|
|
||||||
prefix pages from them.
|
|
||||||
|
|
||||||
The feed sends no table when a client connects, so these tables hold only the
|
|
||||||
routes the feed has announced since RouteWatch first started: an IP-to-AS lookup
|
|
||||||
finds nothing for an address whose routes have not been announced since then.
|
|
||||||
The tables also miss the updates RouteWatch drops when it falls behind under
|
|
||||||
load and those sent while it is stopped, so a lookup or a prefix page can show a
|
|
||||||
route the feed has withdrawn.
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Real-time streaming of BGP updates from RIPE RIS Live
|
- Real-time streaming of BGP updates from RIPE RIS Live
|
||||||
|
|||||||
@@ -169,20 +169,6 @@ func New(cfg *config.Config, logger *logger.Logger) (*Database, error) {
|
|||||||
return nil, fmt.Errorf("failed to create database directory: %w", err)
|
return nil, fmt.Errorf("failed to create database directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The size of the WAL file the previous run left, which Initialize logs
|
|
||||||
// with the start-up checkpoint. It is read before the database is opened,
|
|
||||||
// because opening it writes a frame to the WAL. There is no WAL file when
|
|
||||||
// the database is new or the last run closed the database, which deletes
|
|
||||||
// it.
|
|
||||||
var walBytes int64
|
|
||||||
|
|
||||||
wal, err := os.Stat(dbPath + walFileSuffix)
|
|
||||||
if err == nil {
|
|
||||||
walBytes = wal.Size()
|
|
||||||
} else if !errors.Is(err, os.ErrNotExist) {
|
|
||||||
logger.Warn("Failed to read the WAL file's size on startup", "error", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Per-connection SQLite settings go in the DSN so every pooled connection
|
// Per-connection SQLite settings go in the DSN so every pooled connection
|
||||||
// gets them, not just the one that runs the Initialize pragmas. _txlock=
|
// gets them, not just the one that runs the Initialize pragmas. _txlock=
|
||||||
// immediate makes every transaction take the write lock at BEGIN. Without it
|
// immediate makes every transaction take the write lock at BEGIN. Without it
|
||||||
@@ -243,7 +229,7 @@ func New(cfg *config.Config, logger *logger.Logger) (*Database, error) {
|
|||||||
counts: &liveCounts{},
|
counts: &liveCounts{},
|
||||||
}
|
}
|
||||||
|
|
||||||
err = database.Initialize(walBytes)
|
err = database.Initialize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to initialize database: %w", err)
|
return nil, fmt.Errorf("failed to initialize database: %w", err)
|
||||||
}
|
}
|
||||||
@@ -265,10 +251,8 @@ const (
|
|||||||
startupCheckpointIncompleteMessage = "WAL checkpoint on startup incomplete"
|
startupCheckpointIncompleteMessage = "WAL checkpoint on startup incomplete"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Initialize creates the database schema if it doesn't exist. walBytes is the
|
// Initialize creates the database schema if it doesn't exist.
|
||||||
// size of the WAL file the previous run left, which New reads before it opens
|
func (d *Database) Initialize() error {
|
||||||
// the database.
|
|
||||||
func (d *Database) Initialize(walBytes int64) error {
|
|
||||||
// Set SQLite pragmas for performance. Per-connection settings (cache_size,
|
// Set SQLite pragmas for performance. Per-connection settings (cache_size,
|
||||||
// synchronous, busy_timeout, journal_mode) live in the DSN, and New sets the
|
// synchronous, busy_timeout, journal_mode) live in the DSN, and New sets the
|
||||||
// writing connection's cache_size; temp_store is left at its default so
|
// writing connection's cache_size; temp_store is left at its default so
|
||||||
@@ -290,10 +274,21 @@ func (d *Database) Initialize(walBytes int64) error {
|
|||||||
|
|
||||||
// The start-up checkpoint copies the WAL a previous run left into the
|
// The start-up checkpoint copies the WAL a previous run left into the
|
||||||
// database file and empties the WAL file; its frame counts then read 0,
|
// database file and empties the WAL file; its frame counts then read 0,
|
||||||
// so walBytes is logged with them.
|
// so the WAL file's size is read first. There is no WAL file when the
|
||||||
|
// database is new or the last run closed the database, which deletes it.
|
||||||
|
var walBytes int64
|
||||||
|
|
||||||
|
wal, err := os.Stat(d.path + walFileSuffix)
|
||||||
|
if err == nil {
|
||||||
|
walBytes = wal.Size()
|
||||||
|
} else if !errors.Is(err, os.ErrNotExist) {
|
||||||
|
d.logger.Warn("Failed to read the WAL file's size on startup",
|
||||||
|
"error", err)
|
||||||
|
}
|
||||||
|
|
||||||
var busy, walFrames, checkpointedFrames int
|
var busy, walFrames, checkpointedFrames int
|
||||||
|
|
||||||
err := d.writer.QueryRowContext(context.Background(),
|
err = d.writer.QueryRowContext(context.Background(),
|
||||||
"PRAGMA wal_checkpoint(TRUNCATE)",
|
"PRAGMA wal_checkpoint(TRUNCATE)",
|
||||||
).Scan(&busy, &walFrames, &checkpointedFrames)
|
).Scan(&busy, &walFrames, &checkpointedFrames)
|
||||||
|
|
||||||
|
|||||||
@@ -1057,9 +1057,10 @@ func walFileSize(t *testing.T, db *Database) int64 {
|
|||||||
// TestStartupCheckpointLogsItsFigures opens a new database, writes a route,
|
// TestStartupCheckpointLogsItsFigures opens a new database, writes a route,
|
||||||
// and opens the same file again while the first stays open, so that its WAL
|
// and opens the same file again while the first stays open, so that its WAL
|
||||||
// file stays. The first open finds no WAL file: it logs a size of 0 and no
|
// file stays. The first open finds no WAL file: it logs a size of 0 and no
|
||||||
// warning. The second logs the size of the WAL file the first left, and the
|
// warning. The second logs the size of the WAL file it found, at least what
|
||||||
// checkpoint's busy flag and frame counts, each under its own name; the
|
// the first left (opening the file writes to the WAL before the size is
|
||||||
// counts read 0 once the checkpoint has emptied the file.
|
// read), and the checkpoint's busy flag and frame counts, each under its own
|
||||||
|
// name; the counts read 0 once the checkpoint has emptied the file.
|
||||||
// Initialize logged the busy flag as wal_pages and the counts under each
|
// Initialize logged the busy flag as wal_pages and the counts under each
|
||||||
// other's names (https://git.eeqj.de/sneak/routewatch/issues/117).
|
// other's names (https://git.eeqj.de/sneak/routewatch/issues/117).
|
||||||
func TestStartupCheckpointLogsItsFigures(t *testing.T) {
|
func TestStartupCheckpointLogsItsFigures(t *testing.T) {
|
||||||
@@ -1102,9 +1103,10 @@ func TestStartupCheckpointLogsItsFigures(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if line["wal_bytes"] != float64(walBytes) {
|
loggedBytes, _ := line["wal_bytes"].(float64)
|
||||||
t.Errorf("start-up checkpoint logged wal_bytes = %v, want the %d "+
|
if loggedBytes < float64(walBytes) {
|
||||||
"bytes the first database left", line["wal_bytes"], walBytes)
|
t.Errorf("start-up checkpoint logged wal_bytes = %v, want at least "+
|
||||||
|
"the %d bytes the first database left", line["wal_bytes"], walBytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1154,11 +1156,6 @@ func TestStartupCheckpointWarnsWhenIncomplete(t *testing.T) {
|
|||||||
_, lines := openLogged(t, cfg)
|
_, lines := openLogged(t, cfg)
|
||||||
line := findLine(t, lines, startupCheckpointIncompleteMessage)
|
line := findLine(t, lines, startupCheckpointIncompleteMessage)
|
||||||
|
|
||||||
if line[slog.LevelKey] != slog.LevelWarn.String() {
|
|
||||||
t.Errorf("start-up checkpoint logged at level %v, want %s",
|
|
||||||
line[slog.LevelKey], slog.LevelWarn)
|
|
||||||
}
|
|
||||||
|
|
||||||
if line["busy"] != 1.0 {
|
if line["busy"] != 1.0 {
|
||||||
t.Errorf("start-up checkpoint logged busy = %v, want 1", line["busy"])
|
t.Errorf("start-up checkpoint logged busy = %v, want 1", line["busy"])
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user