check / check (push) Canceled after 0s
The routing table in memory now holds every route, so the two per-peer route tables go, with every write, delete, count and read that served them. The prefix handler applies each update to the table, writes the table's changes every second, and stores each new prefix with its first-seen time. The peer delete, its retry on the next flush and the updates held for it go too. The types and errors of the server's answers move to internal/server. The image's memory settings fit a full table of about 1 GB: GOMEMLIMIT of 2.5 GiB, a 512 MiB page cache for the writing connection, and SQLite heap limits of 768 MiB and 1 GiB. The README describes the new table, its database copy and the memory budget. Model: opus-5-5
128 lines
3.2 KiB
Go
128 lines
3.2 KiB
Go
package database
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
)
|
|
|
|
// liveCounts holds the running row counts that the stats endpoints report.
|
|
// They are seeded once at startup from the tables and then adjusted on every
|
|
// write, so a stats read serves them from memory instead of running a query
|
|
// over the tables. The COUNT(*) scans (issue 27) grew with the database until
|
|
// they took the whole request timeout and made /api/v1/stats return 500.
|
|
//
|
|
// A single mutex guards all fields so the stats reader takes a consistent
|
|
// snapshot at one instant and writers, which already run under the database
|
|
// write lock, adjust the counts after their transaction commits.
|
|
type liveCounts struct {
|
|
mu sync.RWMutex
|
|
asns int
|
|
prefixesV4 int
|
|
prefixesV6 int
|
|
peerings int
|
|
peers int
|
|
}
|
|
|
|
// seed sets every count to the value read from the tables at startup. It runs
|
|
// before any writer, so it needs no coordination with the adjust methods.
|
|
func (c *liveCounts) seed(asns, prefixesV4, prefixesV6, peerings, peers int) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
c.asns = asns
|
|
c.prefixesV4 = prefixesV4
|
|
c.prefixesV6 = prefixesV6
|
|
c.peerings = peerings
|
|
c.peers = peers
|
|
}
|
|
|
|
// addASNs adds n to the ASN count.
|
|
func (c *liveCounts) addASNs(n int) {
|
|
c.mu.Lock()
|
|
c.asns += n
|
|
c.mu.Unlock()
|
|
}
|
|
|
|
// addPrefixes adds to the IPv4 and IPv6 prefix counts.
|
|
func (c *liveCounts) addPrefixes(v4, v6 int) {
|
|
c.mu.Lock()
|
|
c.prefixesV4 += v4
|
|
c.prefixesV6 += v6
|
|
c.mu.Unlock()
|
|
}
|
|
|
|
// addPeerings adds n to the peering count.
|
|
func (c *liveCounts) addPeerings(n int) {
|
|
c.mu.Lock()
|
|
c.peerings += n
|
|
c.mu.Unlock()
|
|
}
|
|
|
|
// addPeers adds n to the BGP peer count.
|
|
func (c *liveCounts) addPeers(n int) {
|
|
c.mu.Lock()
|
|
c.peers += n
|
|
c.mu.Unlock()
|
|
}
|
|
|
|
// fill copies the counts into a Stats, including the derived total, under a
|
|
// single read lock so the reader sees one consistent snapshot.
|
|
func (c *liveCounts) fill(s *Stats) {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
|
|
s.ASNs = c.asns
|
|
s.IPv4Prefixes = c.prefixesV4
|
|
s.IPv6Prefixes = c.prefixesV6
|
|
s.Prefixes = c.prefixesV4 + c.prefixesV6
|
|
s.Peerings = c.peerings
|
|
s.Peers = c.peers
|
|
}
|
|
|
|
// countRows returns the number of rows in the named table. It is used only at
|
|
// startup to seed the in-memory counters, so a full COUNT(*) is acceptable.
|
|
func (d *Database) countRows(ctx context.Context, table string) (int, error) {
|
|
var n int
|
|
// table is one of a fixed set of literals below, never external input.
|
|
err := d.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM "+table).Scan(&n)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed to count %s: %w", table, err)
|
|
}
|
|
|
|
return n, nil
|
|
}
|
|
|
|
// seedCounts reads the current row counts from the tables into the in-memory
|
|
// counters. It runs once at startup, before the streamer begins writing.
|
|
func (d *Database) seedCounts(ctx context.Context) error {
|
|
asns, err := d.countRows(ctx, "asns")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
prefixesV4, err := d.countRows(ctx, "prefixes_v4")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
prefixesV6, err := d.countRows(ctx, "prefixes_v6")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
peerings, err := d.countRows(ctx, "peerings")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
peers, err := d.countRows(ctx, "bgp_peers")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
d.counts.seed(asns, prefixesV4, prefixesV6, peerings, peers)
|
|
|
|
return nil
|
|
}
|