Ingot · Open-source engineering project
Storage designed to fail carefully and recover cleanly.
Ingot is a public Go library for applications that need compressed local metric history without a separate database server. It shows how I handle failure, recovery, resource limits, and testing.
The technical constraint
Some Go applications need local sensor readings or metric history without the cost and complexity of operating a separate service such as Prometheus or InfluxDB. Edge gateways, self-instrumenting binaries, and offline tools still need durable writes, useful compression, bounded retention, and time-range queries.
Ingot treats this as an embedded storage problem, not a reduced monitoring platform. The application imports a library and remains responsible for synchronization, presentation, and operating policy.
- My role
- Architecture, implementation, tests, tooling, and documentation
- Language
- Go
- Model
- Label-indexed float time series
- Evidence
- Public source, design document, and validation suite
How the storage path works
Handle damaged writes explicitly
Committed samples pass through a segmented write-ahead log with checksummed records before entering the in-memory head. Recovery scans valid records and stops cleanly at a truncated or corrupt tail instead of accepting a partially written record.
Use one query path across memory and disk
Recent samples remain in memory while older data moves into immutable, memory-mapped blocks. Queries resolve label matchers and merge matching series across both layers, including the boundary where persistence and concurrency bugs tend to appear.
Bound storage as part of the design
Background compaction merges blocks into larger time spans and expires data according to policy. The design bounds steady-state memory by the active head and bounds disk by retention, while acknowledging temporary duplication during compaction.
Deliberate limits
Compression depends on the workload
Ingot uses Gorilla delta-of-delta timestamp and XOR value encoding. Published benchmarks range from 0.42 bytes per sample for constant values to about 7.5 for an adversarial full-precision random walk. The case for compression is workload-specific, so the repository exposes the benchmark rather than presenting one universal ratio.
Keep the database narrow
Replication, a query language, arbitrary value types, multi-process access, deletes, and out-of-order ingestion are explicit non-goals. Leaving them out keeps the storage model and failure surface appropriate for a single-process embedded library.
Make diagnosis possible
The repository includes tools for listing blocks, decoding chunks, checking index and checksum integrity, exposing database statistics, and serving a small HTTP query bridge. Stored data can be inspected directly instead of leaving diagnosis entirely to application logs.
Testing and limits
The public implementation is tested against storage-engine failure modes. The same discipline matters when business software must survive partial writes, stay within operating limits, recover predictably, and be tested beyond the happy path.
- Recovery
- Truncated and corrupt WAL-tail cases
- Concurrency
- Race testing across append and query behavior
- Encoding
- Round trips, adversarial values, fuzzing, and benchmarks
- Queries
- Comparison with a naive reference implementation
- Resources
- 48-hour simulated sustained-load soak test
The library remains alpha software and has not established external field reliability.
