store

package
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 26, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package store persists findings so they survive a restart.

Two properties drive the design.

The packet loop must never wait on a disk. A sensor that stalls when storage gets slow drops traffic, and dropped traffic is undetected traffic, so alerts are handed to a buffered queue and written by a background goroutine. If the queue fills, alerts are dropped and counted rather than allowed to back up into the capture path. Losing a record of a finding is bad; losing the packets that would have produced the next one is worse.

And it stays cgo-free. modernc.org/sqlite is a pure-Go translation of SQLite, so `CGO_ENABLED=0 go build` still produces the static binary the rest of the project depends on. The alternative, mattn/go-sqlite3, is faster and would have cost the single-binary property outright.

Index

Constants

View Source
const (
	// DefaultQueueSize is how many alerts may be waiting to be written. Sized
	// so that a burst of findings during an incident is absorbed rather than
	// dropped, while still bounding memory.
	DefaultQueueSize = 4096
	// DefaultBatchSize is how many alerts go into one transaction. Committing
	// per alert makes SQLite fsync per alert, which is roughly two orders of
	// magnitude slower than batching.
	DefaultBatchSize = 256
	// DefaultFlushInterval bounds how long an alert can sit unwritten when
	// traffic is quiet and no batch fills up.
	DefaultFlushInterval = 2 * time.Second
)

Tuning for the background writer.

Variables

This section is empty.

Functions

This section is empty.

Types

type Options

type Options struct {
	QueueSize     int
	BatchSize     int
	FlushInterval time.Duration
}

Options configures a Store.

type Query

type Query struct {
	Limit       int
	MinSeverity model.Severity
	Since       time.Time
	RuleID      string
	Src         string
}

Query filters a history lookup. The zero value returns the most recent alerts at any severity.

type Stats

type Stats struct {
	Written uint64 `json:"written"`
	Dropped uint64 `json:"dropped"`
	Failed  uint64 `json:"failed"`
	Queued  int    `json:"queued"`
}

Stats reports what the writer has done.

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store is a SQLite-backed record of alerts and observed devices.

func Open

func Open(path string, opts Options) (*Store, error)

Open prepares a database at path, creating and migrating it as needed.

func (*Store) Alerts

func (s *Store) Alerts(ctx context.Context, q Query) ([]model.Alert, error)

Alerts returns stored alerts, newest first.

func (*Store) Close

func (s *Store) Close() error

Close flushes anything queued and closes the database.

func (*Store) CountAlerts

func (s *Store) CountAlerts(ctx context.Context) (int, error)

CountAlerts reports how many alerts are stored.

func (*Store) Devices

func (s *Store) Devices(ctx context.Context) ([]model.Device, error)

Devices returns the stored inventory, ordered by address.

func (*Store) Enqueue

func (s *Store) Enqueue(a model.Alert)

Enqueue hands an alert to the background writer. It never blocks.

This is called from the packet-processing goroutine, so blocking here would stall capture. A full queue means storage cannot keep up, and the honest response is to drop the record and count it rather than to stop detecting.

func (*Store) Prune

func (s *Store) Prune(ctx context.Context, before time.Time) (int64, error)

Prune deletes alerts older than the cutoff and returns how many went.

Without this a sensor left running fills its disk, which is a failure mode that arrives quietly and then stops the sensor entirely. Alerts are the only unbounded table: devices are keyed by address, so that one is bounded by the size of the network.

Deleting rows does not shrink the file. SQLite keeps the freed pages for reuse, which is the right default for a database that keeps being written to. Call Vacuum when the space actually needs returning to the filesystem.

func (*Store) PruneToCount

func (s *Store) PruneToCount(ctx context.Context, keep int) (int64, error)

PruneToCount keeps only the newest n alerts.

A time-based cutoff is the usual policy, but it gives no bound during an incident, when a single hour can produce more findings than a normal month. This provides the hard ceiling that keeps the file from growing without limit no matter what the traffic does.

func (*Store) SaveDevices

func (s *Store) SaveDevices(ctx context.Context, devices []model.Device) error

SaveDevices upserts the asset inventory.

Written in one transaction on a cadence rather than per change: a device's byte counters move on every packet, and persisting that would turn the inventory into the busiest table in the database for no analytical gain.

func (*Store) SizeOnDisk

func (s *Store) SizeOnDisk(ctx context.Context) (int64, error)

SizeOnDisk reports the database size in bytes, including the write-ahead log.

func (*Store) Stats

func (s *Store) Stats() Stats

Stats returns the writer counters.

func (*Store) Vacuum

func (s *Store) Vacuum(ctx context.Context) error

Vacuum rewrites the database, returning freed pages to the filesystem.

This rewrites the whole file, so it is deliberately not automatic: doing it on a schedule would make a sensor periodically stall on disk for no reason the operator asked for.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL