[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-76059":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":13,"openIssues":14,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":15,"stars7d":15,"stars30d":16,"stars90d":15,"forks30d":15,"starsTrendScore":15,"compositeScore":17,"rankGlobal":9,"rankLanguage":9,"license":18,"archived":19,"fork":19,"defaultBranch":20,"hasWiki":21,"hasPages":19,"topics":22,"createdAt":9,"pushedAt":9,"updatedAt":23,"readmeContent":24,"aiSummary":25,"trendingCount":15,"starSnapshotCount":15,"syncStatus":12,"lastSyncTime":26,"discoverSource":27},76059,"nanotdb","aymanhs\u002Fnanotdb","aymanhs","Single-binary observability, time-series database, and built-in dashboard for Raspberry Pi, edge devices, and local metrics.",null,"Go",130,2,106,1,0,22,1.43,"MIT License",false,"main",true,[],"2026-06-12 02:03:39","# NanoTDB\n\n\u003Cp align=\"center\">\n  \u003Cimg src=\"docs\u002FNanoTDB.png\" alt=\"NanoTDB mascot\" width=\"220\">\n\u003C\u002Fp>\n\nA small embedded time-series database for Raspberry Pi, edge devices, appliances,\nand other single-node systems where you want local metrics storage without a big\nstack behind it.\n\nNanoTDB is built for the case where InfluxDB-, VictoriaMetrics-, or Prometheus-\nstyle tooling can feel heavier than the problem. It stores metrics as plain\nfiles under one directory, supports offline inspection with `nanocli`, and keeps\nrollups inside the engine instead of pushing you into extra services.\n\n## Why NanoTDB\n\n- Plain files on disk: WAL, catalog, manifests, and partitioned `.dat` files are easy to inspect, back up, and reason about.\n- Small-system fit: designed for Raspberry Pi, edge nodes, and other resource-constrained hosts.\n- Offline workflow: `nanocli` can inspect data, export line protocol, inspect WAL files, and rebuild rollups without a running server.\n- Engine-owned rollups: hourly or daily summaries live in the database workflow instead of a separate pipeline.\n- Operationally simple: no external dependencies at runtime, no separate index service, and retention maps cleanly to file deletion.\n\n## Best Fit\n\nNanoTDB is a good fit when you want:\n\n- Local metrics storage on one machine.\n- Something you can understand from the filesystem.\n- A TSDB for hundreds of metrics, not huge multi-tenant cardinality.\n- A small self-hosted stack for sensors, host telemetry, appliances, or embedded apps.\n\n## Not A Fit\n\nNanoTDB is not trying to be:\n\n- A distributed or horizontally scaled TSDB.\n- A high-cardinality metrics backend for large fleets.\n- A system that accepts arbitrary out-of-order writes.\n- A system that hides its durability tradeoffs behind marketing language.\n\n## Use Cases\n\n### Raspberry Pi and edge host telemetry\n\nNanoTDB is a strong fit for small Linux systems where you want to keep metrics\nlocal, survive restart, and avoid wearing out SD storage with unnecessarily\nheavy write patterns. Pair it with `drip` when you want CPU, memory, disk, IO,\nnetwork, load, one-wire, or SD write probe metrics on one box.\n\n### Local app metrics without a bigger stack\n\nIf you have one appliance, one embedded app, or one self-hosted node, NanoTDB\ngives you metric writes, queries, rollups, and offline inspection without\nstanding up a larger metrics platform just to answer simple history questions.\n\n### Sensor retention you can inspect directly\n\nIf your data is fundamentally numeric time-series data, NanoTDB is often easier\nto live with than plain logs. You keep a queryable history, can inspect the WAL\nor `.dat` files directly with `nanocli`, and can delete old partitions as a\nsimple retention policy.\n\n### Long-horizon summaries on small disks\n\nWhen you want recent raw detail plus longer-term summaries, NanoTDB's built-in\nrollups let you keep local aggregates without building a separate compaction or\ndownsampling pipeline.\n\n---\n\n## 🚀 Quick Start\n\nStart here:\n\n- [Hello World](docs\u002FHELLO_WORLD.md) for the fastest copy\u002Fpaste path.\n- [Getting Started](GETTING_STARTED.md) for installation, examples, and a longer guided tour.\n- [Run As A Service](docs\u002FRUN_AS_A_SERVICE.md) for a brief systemd setup path.\n- [Glossary](docs\u002FGLOSSARY.md) for the canonical meaning of database, metric, sample, WAL, and related terms.\n\n### 60-Second Hello World\n\nTerminal 1:\n\n```bash\nmkdir -p ~\u002Fnanotdb-data\n.\u002Fnanotdb --init --config ~\u002Fnanotdb-data\u002Fengine.toml\n.\u002Fnanotdb --config ~\u002Fnanotdb-data\u002Fengine.toml\n```\n\nTerminal 2:\n\n```bash\ncurl -X POST \"http:\u002F\u002Flocalhost:8428\u002Fapi\u002Fv1\u002Fimport\" \\\n  -d $'demo\u002Froom.temp 21.5\\ndemo\u002Froom.humidity 48'\n\ncurl \"http:\u002F\u002Flocalhost:8428\u002Fapi\u002Fv1\u002Fquery?query=demo\u002Froom.temp\"\n\n.\u002Fnanocli inspect wal --root ~\u002Fnanotdb-data --db demo --verbose\n```\n\nThat flow is the point of NanoTDB: start one binary, write a few metrics, query\nthem back, and inspect the local files without standing up anything else.\n\nPrefer ready-to-use binaries? Download the latest release assets from\n[GitHub Releases](https:\u002F\u002Fgithub.com\u002Faymanhs\u002Fnanotdb\u002Freleases\u002Flatest):\n\n- Old Raspberry Pi (Pi 0\u002F1): `nanotdb-linux-armv6-rpi0-rpi1` and `nanocli-linux-armv6-rpi0-rpi1`\n- Newer 32-bit Raspberry Pi OS (Pi 2\u002F3\u002F4): `nanotdb-linux-armv7-rpi3-rpi4` and `nanocli-linux-armv7-rpi3-rpi4`\n- 64-bit Raspberry Pi OS: `nanotdb-linux-arm64` and `nanocli-linux-arm64`\n\nRelease\u002Fchange history:\n\n- Published release notes and downloads: [Releases](https:\u002F\u002Fgithub.com\u002Faymanhs\u002Fnanotdb\u002Freleases)\n- Detailed change log in-repo: [CHANGELOG.md](CHANGELOG.md)\n\nFor technical deep-dives, continue below.\n\n---\n\n## Core Concepts\n\n### What is a database?\n\nA database in NanoTDB is an isolated local namespace such as `prod`, `sensors`,\nor `weather`.\n\nEach database has its own:\n\n- metrics\n- WAL file\n- catalog\n- manifest\n- partitioned `.dat` files\n\nThat makes it easy to reason about retention, inspection, backup, and failure\nisolation one database at a time.\n\n### What is a metric?\n\nA metric is one numeric time-ordered stream inside a database.\n\nExamples:\n\n- `room.temp`\n- `room.humidity`\n- `cpu.usage_active`\n- `disk.sd_write_probe_ms`\n\nEach metric keeps one numeric type for its lifetime: `int32` or `float32`.\n\n### What does a sample look like?\n\nNanoTDB writes and reads line protocol in this shape:\n\n```text\nDB\u002Fmetric.name value [timestamp]\n```\n\nExamples:\n\n```text\nprod\u002Froom.temp 21.5 1715000000000000000\nsensors\u002Fpressure.hpa 1013\nweather\u002Foutdoor.humidity 48\n```\n\n- `DB` is the database name.\n- `metric.name` is the metric identifier.\n- `value` is an integer or float.\n- `timestamp` is optional; if omitted, NanoTDB uses the current time.\n\nFor a deeper storage and query walkthrough, see [docs\u002FARCHITECTURE.md](docs\u002FARCHITECTURE.md).\n\n### What is the WAL?\n\nWAL means write-ahead log.\n\nIn NanoTDB, each database has a local `\u003Cdb>.wal` file that protects the newest\nsamples before they are flushed into durable `.dat` pages.\n\nThat matters for both crashes and normal shutdown:\n\n- On clean shutdown, NanoTDB flushes open pages before exit so recent samples move into durable data files.\n- On restart after a crash or power loss, NanoTDB replays the WAL so unflushed samples can be recovered into the in-memory open page state.\n- Once a page is flushed and no open page still depends on that WAL content, the WAL can be reset.\n\nThis is one of NanoTDB's strongest operational properties for edge systems: the\nlatest data is not only local, it is recoverable after restart without needing\nan external service.\n\n### How WAL and recovery can be tuned\n\nThe main WAL and durability knobs are in `engine.toml` and per-database\nmanifests:\n\n- `wal.max_segment_size`: how large the WAL is allowed to grow before reset after flush.\n- `wal.fsync_policy`: `segment` for better throughput, `always` for stronger per-append durability.\n- `durability.profile`: `strict`, `balanced`, or `throughput` for page\u002Fcatalog fsync behavior.\n- `manifest_defaults.wal.enabled`: enable or disable WAL for newly created databases.\n- `manifest_defaults.wal.skip_before`: skip WAL for older backfill samples.\n- `manifest_defaults.page.max_records`, `max_bytes`, and `max_age`: control how quickly in-memory pages roll over and flush, which affects how long data remains WAL-backed before landing in `.dat` files.\n\nIf you want the strongest local recovery posture, the conservative end of the\nrange is:\n\n- `wal.fsync_policy = \"always\"`\n- `durability.profile = \"strict\"`\n\nIf you want less write overhead and can tolerate more crash risk, move toward\n`segment` and `balanced` or `throughput`.\n\n### Why the data files matter\n\nNanoTDB's durable `.dat` files are small, append-only, and friendly to simple\nretention and backup workflows.\n\nThat matters operationally, especially on SD-backed systems:\n\n- small files are easier to inspect and copy\n- retention is just removing old partition files\n- append-only writes are easier on flash media than rewrite-heavy designs\n- compressed page files keep local history practical on small disks\n\nAs one real Pi test point, a 69-metric workload sampled every 10 seconds\nproduced about `0.7 MB` for a full day in one `.dat` file, which worked out to\nunder `2 bytes` per metric point on disk after compression. Treat that as a\nreal-world example, not a universal promise, but it shows why NanoTDB can be a\nvery SD-friendly fit for local telemetry.\n\n### Why not plain logs?\n\nPlain logs are often enough until you want to ask metric-shaped questions like:\n\n- what was the last temperature?\n- show me the last 24 hours of humidity\n- downsample this series for a dashboard\n- inspect the local data without shipping it anywhere else\n\nYou can force numeric history into logs, but querying, retention, aggregation,\nand inspection stay awkward. NanoTDB keeps the operational simplicity of local\nfiles while giving you metric-native writes, queries, rollups, and offline\ninspection.\n\n### Why not a heavier TSDB?\n\nSometimes a larger metrics stack is the right answer. NanoTDB is for the cases\nwhere it is not.\n\nChoose NanoTDB when you want:\n\n- one local node instead of a broader platform\n- plain files you can inspect and back up directly\n- hundreds of metrics, not massive fleet-scale cardinality\n- built-in local rollups without extra components\n\nChoose a larger TSDB when you need:\n\n- very large scale or high-cardinality workloads\n- distributed storage and query execution\n- looser write-ordering expectations\n- a broader ecosystem of integrations than a small local stack needs\n\n---\n\n## Configuration (`engine.toml`)\n\nCreated automatically at `\u003Croot>\u002Fengine.toml` on first start. Key settings:\n\n| Key | Default | Effect |\n|---|---|---|\n| `engine.listen` | `:8428` | HTTP server address |\n| `wal.max_segment_size` | `67108864` (64 MiB) | WAL size before reset after a page flush |\n| `wal.fsync_policy` | `segment` | `segment` = fsync on WAL reset; `always` = fsync every append |\n| `durability.profile` | `strict` | `strict` \u002F `balanced` \u002F `throughput` (see below) |\n| `stats.enabled` | `true` | Emit engine self-metrics to the `internal` database |\n| `stats.interval` | `30s` | How often stats are flushed |\n\n### Logging (`engine.toml`)\n\nEngine and server logging are configured under `[logging]` with one or more `[[logging.logger]]` entries.\n\nExample:\n\n```toml\n[logging]\n\n[[logging.logger]]\noutput = \"console\"\nlevel = \"info\"\n\n[[logging.logger]]\noutput = \"\u002Fvar\u002Flog\u002Fnanotdb\u002Fdebug.log\"\nlevel = \"debug\"\n```\n\nLogging rules:\n\n- `output = \"console\"` writes to stderr.\n- Any other `output` value is treated as a file path and opened in append\u002Fcreate mode.\n- `level` can be `info`, `debug`, or `trace`.\n- Multiple logger entries are allowed, so you can keep sparse operator-facing console logs and more detailed file logs at the same time.\n\nLevel guidance:\n\n- `info`: startup, shutdown, database open\u002Freplay, backfill begin\u002Fend.\n- `debug`: page flushes, WAL resets, rollup trigger boundaries, file lifecycle details.\n- `trace`: per-sample ingest flow, new metric creation, stale\u002Fout-of-order rejection, HTTP request summaries.\n\n**Durability profiles:**\n\n| Profile | Page file fsync | Catalog fsync |\n|---|---|---|\n| `strict` | yes | yes |\n| `balanced` | yes | no |\n| `throughput` | no | no |\n\nPer-database settings (retention, partitioning, WAL skip window, page flush thresholds, rollups) live in\n`\u003Cdb>\u002Fmanifest.toml` and default values can be set in `engine.toml` under\n`[manifest_defaults]`.\n\nPartition options in `[retention]`:\n- `partition = \"day\"` (default): `data-YYYY-MM-DD.dat`\n- `partition = \"month\"`: `data-YYYY-MM.dat`\n- `partition = \"year\"`: `data-YYYY.dat`\n- `partition = \"forever\"`: `data-forever.dat`\n\n### Rollups (`manifest.toml`)\n\nRollup jobs are defined in the **source** database manifest under `[rollups]`.\n\nExample:\n\n```toml\n[rollups]\nenabled = true\ncheckpoint_file = \"rollup.checkpoints.log\"\ndefault_grace = \"5m\"\ndefault_interval = \"1h\"\ndefault_destination_db = \"sensors_rollup_1h\"\ndefault_aggregates = [\"min\", \"max\", \"avg\", \"count\"]\nglobal_exclude_patterns = [\"nanotdb.*\", \"*.debug\"]\n\n[[rollups.jobs]]\nid = \"all_metrics_1h\"\nsource_pattern = \"*\"\nexclude_patterns = [\"disk.sd_write_probe_ms\", \"net.*\"]\n\n[[rollups.jobs]]\nid = \"outside_temp_1h\"\nsource_metric = \"temp.out_dry\"\ninterval = \"1h\"\naggregates = [\"min\", \"max\", \"sum\", \"avg\", \"count\"]\ndestination_db = \"sensors_rollup_1h\"\ndestination_metric_prefix = \"temp.out_dry\"\n```\n\nRollup config reference:\n\n| Field | Scope | Required | Valid \u002F Default | Notes |\n|---|---|---|---|---|\n| `rollups.enabled` | DB | no | `true|false` (default `false`) | Enables rollup processing for this DB as a source. |\n| `rollups.checkpoint_file` | DB | no | string (default `rollup.checkpoints.log`) | Checkpoint log path, relative to source DB directory. |\n| `rollups.default_grace` | DB | no | Go duration or empty | Used when job `grace` is omitted. |\n| `rollups.default_interval` | DB | no | Go duration or empty | Used when job `interval` is omitted. |\n| `rollups.default_destination_db` | DB | no | string or empty | Used when job `destination_db` is omitted. |\n| `rollups.default_aggregates` | DB | no | subset of `min|max|sum|avg|count` | Used when job `aggregates` is omitted. |\n| `rollups.global_exclude_patterns` | DB | no | wildcard list | Excluded from selector-based jobs before expansion. |\n| `rollups.jobs[].id` | Job | yes | non-empty string | Unique per source DB for checkpoint tracking. |\n| `rollups.jobs[].source_metric` | Job | yes* | non-empty string | Exact metric to read from source DB. |\n| `rollups.jobs[].source_pattern` | Job | yes* | wildcard pattern supporting `*` | Selector-based job that expands to concrete metrics. |\n| `rollups.jobs[].exclude_patterns` | Job | no | wildcard list | Additional per-job exclusions for selector-based jobs. |\n| `rollups.jobs[].interval` | Job | no | valid Go duration (`>0`) | Rollup bucket size; may inherit `rollups.default_interval`. |\n| `rollups.jobs[].aggregates` | Job | no | `min|max|sum|avg|count` | Aggregate outputs; may inherit `rollups.default_aggregates`, otherwise defaults to all five. |\n| `rollups.jobs[].destination_db` | Job | no | non-empty string | Target DB; may inherit `rollups.default_destination_db`. |\n| `rollups.jobs[].destination_metric_prefix` | Job | no | string (default `source_metric`) | Output names are `\u003Cprefix>.\u003Cagg>`. |\n| `rollups.jobs[].grace` | Job | no | Go duration or empty | Overrides `default_grace` for this job. |\n\nNotes:\n- Each job must set exactly one of `source_metric` or `source_pattern`.\n- Checkpoints are stored in the source DB (default `rollup.checkpoints.log`).\n- Selector-based jobs expand to deterministic checkpoint keys: `\u003Cjob-id>::\u003Cmetric-name>`.\n- Destination DBs can also define their own rollup jobs to create cascades (for example `1h -> 1d`).\n- Auto-created rollup destination DBs are written with rollup-tuned manifests: WAL disabled, `partition = \"month\"` for sub-daily rollups or `\"year\"` for daily-or-larger rollups, and a longer `page.max_age` to reduce tiny sparse pages.\n\nBackfill helpers:\n\n- `nanocli rollup --root \u003Cdir> [--db \u003Csource-db>] [--json]` resets rebuildable rollup destination state and recomputes rollups offline.\n- `POST \u002Fapi\u002Fv1\u002Frollup\u002Fbackfill` runs the same engine-owned workflow inside a running `nanotdb` server.\n- Online backfill persists rebuilt destination `.dat` pages and `catalog.json` before returning, so offline `nanocli inspect\u002Fexport` can read the rebuilt DB immediately.\n\n### `nanocli` inspection helpers\n\nFor deeper file inspection, use the dedicated DAT\u002FWAL inspect commands:\n\n```bash\n.\u002Fnanocli inspect dat --root .\u002Fdevdata --db internal --verbose\n.\u002Fnanocli inspect wal --root .\u002Fdevdata --db internal --verbose\n```\n\nTerminal output uses aligned tables. Verbose DAT output shows per-page size\u002Fcompression stats; WAL verbose output adds tail diagnostics. Human-readable output shows `start` plus `duration`; `--json` retains the full machine-readable timestamps.\n\n---\n\n## Binaries\n\n### `nanotdb` — server\n\n```\nnanotdb --config \u003Cpath>      start server using given engine.toml\nnanotdb --init --config \u003Cpath>   write default engine.toml and exit\n```\n\nExposes a small HTTP API compatible with the VictoriaMetrics instant\u002Frange query\nwire format (`\u002Fapi\u002Fv1\u002Fimport`, `\u002Fapi\u002Fv1\u002Fimport\u002Fprometheus`, `\u002Fapi\u002Fv1\u002Fquery`, `\u002Fapi\u002Fv1\u002Fquery_range`).\n\nAPI quick reference:\n\n- `GET \u002Fhealth` - health check\n- `POST \u002Fapi\u002Fv1\u002Fimport` - import line protocol (raw body or JSON payload)\n- `POST \u002Fapi\u002Fv1\u002Fimport\u002Fprometheus` - Prometheus-compatible import endpoint\n- `GET \u002Fapi\u002Fv1\u002Fquery` - instant query (latest point)\n- `GET \u002Fapi\u002Fv1\u002Fquery_range` - range query\n\nAlso exposes discovery endpoints:\n\n- `GET \u002Fapi\u002Fv1\u002Fdatabases` (use `?include_internal=true` to include the internal DB)\n- `GET \u002Fapi\u002Fv1\u002Fmetrics?db=\u003Cname>` (use `&details=true` for id\u002Ftype metadata)\n- `POST \u002Fapi\u002Fv1\u002Frollup\u002Fbackfill` (optional JSON body: `{\"source_db\":\"name\"}` or `{\"source_dbs\":[...]}`)\n\n### `nanocli` — offline CLI tool\n\nOperates directly on the data directory without a running server.\n\n```\nnanocli inspect db  --root \u003Cdir> --db \u003Cname> [--verbose] [--json]  — database overview + optional detailed DAT\u002FWAL tables\nnanocli inspect dat --root \u003Cdir> --db \u003Cname> [--verbose] [--json]  — .dat file\u002Fpage inspection tables\nnanocli inspect wal --root \u003Cdir> --db \u003Cname> [--verbose] [--json]  — WAL inspection tables + optional tail diagnostics\n\nnanocli import --root \u003Cdir> --in \u003Cfile.lp>  [--json]     — bulk import line-protocol file\nnanocli rollup --root \u003Cdir> [--db \u003Csource-db>] [--json]  — reset and recompute rollup destinations from source manifests\nnanocli export --root \u003Cdir> --db \u003Cname> [--out \u003Cfile.lp>] — export database to line protocol (stdout when --out is omitted)\n\nnanocli query  --root \u003Cdir> --db \u003Cname> --metric \u003Cregex>\n               [--start \u003Ctime>] [--end \u003Ctime>] [--format table|json]\n```\n\nLP timestamps (import and exported files) accept \u002F use: `YYYY-MM-DD HH:MM:SS.nnnnnnnnn` (UTC)\nand also accept raw Unix nanoseconds on import.\n\n`--start` \u002F `--end` accept RFC3339 strings, `YYYY-MM-DD [HH[:MM[:SS[.nnnnnnnnn]]]]`,\nor Unix timestamps (seconds or nanoseconds).\n\n### `drip` — metrics collector\n\n`drip` is an optional host metrics collector intended for small edge systems such as Raspberry Pi.\nIt gathers CPU, memory, disk, IO, network, load average, one-wire temperature, and SD write probe metrics and POSTs them to NanoTDB using line protocol.\n\n### Rollup full-cycle check script\n\nFor deterministic end-to-end verification (generate LP -> import -> rollups -> export -> compare expected), run:\n\n```bash\n.\u002Fscripts\u002Frollup_full_cycle_check.sh\n```\n\nOptional arguments:\n- `.\u002Fscripts\u002Frollup_full_cycle_check.sh \u003Croot-dir> \u003Cduration-hours> \u003Cmetrics> \u003Ccadence-seconds> \u003Cgap-metrics>`\n- Defaults: `root-dir=test-data\u002Ffull-cycle-check`, `duration-hours=30`, `metrics=10`, `cadence-seconds=10`, `gap-metrics=2`\n\nGenerated artifacts are placed in `\u003Croot-dir>\u002Fwork` for easy discovery:\n- `scenario_summary.json` (duration, rates, counts, per-metric stats)\n- `known_gaps.csv` (deterministic missing windows for `temp.gap_probeXX` metrics)\n- `SCENARIO.md` (quick human-readable summary)\n\n---\n\n## Engine API (embedding)\n\n```go\ne, err := engine.OpenEngine(\"\u002Fdata\", 0)   \u002F\u002F 0 = default WAL segment size\ndefer e.Close()\n\n\u002F\u002F Ingest\nerr = e.AddLine(\"sensors\u002Ftemp 22.1 \" + strconv.FormatInt(time.Now().UnixNano(), 10))\n\n\u002F\u002F Range query\nerr = e.QueryRange(\"sensors\", \"temp\", fromTS, toTS, 1, func(s engine.Sample) error {\n    fmt.Println(s.TS, s.Float32)\n    return nil\n})\n\n\u002F\u002F Last value (from in-memory catalog cache)\nsample, ok, err := e.QueryLast(\"sensors\", \"temp\")\n\n\u002F\u002F Bulk import \u002F export\nerr = e.ImportFile(\"backup.lp\")\nerr = e.ExportFile(\"sensors\", \"backup.lp\")\n```\n\nKey types:\n\n| Type | Description |\n|---|---|\n| `Engine` | Top-level coordinator; safe for concurrent use |\n| `Database` | One named DB with WAL + catalog + data files |\n| `Catalog` | Metric name ↔ ID registry; persisted as JSON |\n| `Page` | In-memory buffer of interleaved samples; flushed when full |\n| `WAL` | Single-file write-ahead log with compact v2 encoding |\n| `Sample` | Decoded data point from a query |\n| `Timestamp` | `int64` Unix nanoseconds |\n| `MetricID` | `uint16` per-database metric address |\n","NanoTDB 是一个专为长期运行的传感器数据设计的小型、追加写入的时间序列数据库，适用于资源受限的硬件环境。其核心功能包括将度量数据存储为普通文件，支持离线检查和本地汇总，无需额外的服务或复杂的堆栈。技术特点上，NanoTDB 采用 Go 语言编写，确保了高效与轻量化，并且所有操作都在单个节点内完成，简化了运维复杂性。适合于树莓派、边缘设备等小型系统中需要本地度量存储但又不想引入庞大后端服务的场景使用，例如家庭自动化项目、小型物联网应用或是嵌入式系统的监控。此外，对于那些希望从文件系统层面理解数据存储机制，并且对度量指标数量要求不高的用户来说，NanoTDB 也是一个理想的选择。","2026-06-11 03:54:21","CREATED_QUERY"]