Search

↑↓ to move↵ to openesc to closetop 6 documentation results

Book a demoTry TokenTriage

In development · Explore the product preview. Join the launch list or book a walkthrough.

Browse docs

The read model behind the dashboard

A SQLite file derived from your ledger, so the dashboard can answer a query in milliseconds. Why you can delete it, and when you should.

ExplanationLast updated

On this page

The dashboard groups a month of spend by tenant in the time it takes to render a chart, over a ledger that is a JSON Lines file. It does that by keeping a second copy of the interesting columns in SQLite, and tailing your decision log to keep it current.

Knowing that this file exists changes two operational decisions: what you back up, and what you do when a surface looks wrong.

Derived, and disposable

The decision ledger is the system of record. Every request appends one line to it, and that line is the durable artifact everything else is computed from. The read model is a projection of those lines into a queryable shape.

Which means the read model has no information the ledger lacks. Delete it and you lose nothing but the time it takes to rebuild. Corrupt it and the fix is to delete it. Restore a backup without it and the instance rebuilds it on the next boot.

Backup and restore is the executed procedure, and it makes the same distinction with a worked example.

Where it lives, and why the path matters

dashboard:
  index:
    path: /var/lib/tokentriage/index/decisions.sqlite   # default: ~/.tokentriage/index/decisions.sqlite

The default is a single path under your home directory. That is right for one instance on a laptop and wrong for anything else, because every instance that does not set a path opens the same file.

Give each instance its own. A shared read model produces two kinds of trouble: contention between processes, and a boot that spends a long time opening and catching up an index built from a different ledger. If a start hangs after the replica-identity log line and before the banner, the index path is where to look.

Rebuilding it

index rebuild deletes the file and replays the whole decision log into a fresh one. It is deterministic: rebuilding twice from the same log gives the same contents.

$ tokentriage index rebuild --index /var/lib/tokentriage/index/decisions.sqlite \
    --log /var/lib/tokentriage/decisions.jsonl
rebuilt index D:/tt-tmp/ttdocs/state/index/decisions.sqlite from D:/tt-tmp/ttdocs/state/decisions.jsonl
  rows indexed:    0
  indexed_through: file= offset=0 ts=

That capture is from an instance whose ledger was empty, which is why the counts are zero and the position is blank; a populated rebuild reports the row count and the file and offset it reached. The paths in the output are the ones the command was given.

Run it without arguments to rebuild the defaults, or name both files as above.

Stop the instance first. A running instance holds the file open, and on Windows the rebuild refuses rather than corrupting anything:

$ tokentriage index rebuild
Error: index rebuild: index: remove old index: remove …/decisions.sqlite: The process cannot access the file because it is being used by another process.

The instance self-heals on the next boot regardless, so an unplanned restart is a valid alternative to running the command at all.

When to rebuild

Four situations, and only two of them are problems.

A schema change between versions. Handled for you: the index rebuilds itself when it opens an older schema. You will see it take longer to boot once after an upgrade.

After restoring a ledger. A restored ledger with no index gets one built at boot. A restored ledger with somebody else’s index needs a rebuild, and this is the case where getting it wrong is quietly wrong rather than loudly wrong.

A surface disagrees with the CLI. Both read the same ledger, so a persistent disagreement means the index is behind or damaged. Rebuild and compare again.

Disk pressure. The read model is a second copy, so it costs real space. If it has grown out of proportion to the ledger it is derived from, delete it and rebuild; nothing in it is irreplaceable.

What is in it, and what is not

The index holds the projected columns the dashboard queries on: identifiers, the routing decision, usage and cost with its pricing snapshot, latency, and the business dimensions you tagged. It is built by a pure-Go SQLite driver, so the binary stays free of cgo and the single-file property holds.

It does not hold captured request or response bodies, which live in their own store under dashboard.capture.path. It does not hold your users, tokens, saved views or audit log, which live in the state directory. And it is not the query engine’s only source: a query can fall back to scanning the canonical log, which is what lets a surface answer while an index is still catching up.

The provenance envelope on every query response carries an indexed_through position — the file and offset the index has reached. A surface reporting a position behind the end of your log is telling you it is catching up rather than that rows are missing.

What can go wrong

The dashboard is behind the CLI. Read indexed_through on a query response. Catching up is normal after a restart or a large import; a position that stops advancing is not.

A rebuild fails with a file-in-use error. Stop the instance. See above.

Two instances fight over one file. Both are using the default path. Give each its own.

The file is far larger than the ledger. Delete it and rebuild. If the rebuilt file is much smaller than the one you deleted, the old one had accumulated space it never gave back, and the rebuild is the reclaim.