Crossdeck Docs
Dashboard

Buckets OSS

Buckets 6 min read · See where your reads go — and get paged before the bill

Buckets answers one question: for every database read your app makes, where did it go? It attributes every read to the feature that served it, shows you the effect of a change before and after you ship it, and — connect Slack — pages you the moment a real read spike begins, before the invoice is in the post. The collector is open source; this page is how you read it inside Crossdeck and how the cost alerts work.

TL;DR

Where your reads go

Find Buckets under Errors → Buckets OSS. The main view ranks your buckets by reads, so the heaviest surface is the longest bar. A bucket is either tagged — attributed to a named feature you wrapped — or untagged, shown in amber, meaning Buckets caught the read and labelled it by collection (col:events) but no feature name has been set on that path yet. Untagged is never a blind spot; it's a to-do with a one-line fix (see Name a bucket).

Before and after a change

The point of Buckets isn't to admire a number — it's to answer "I made a change, did it work?" Set the change-date marker to the day you shipped, and every bucket shows its reads before the change (a faint track) and now (the solid fill). A surface you fixed visibly shrinks; one you didn't sits unchanged. The headline tells you the whole story at a glance:

// the headline after shipping a maintained-read fix
2,358,918  1,632,118 reads/day   // −31%  ·  −726,800 reads/day

Switch the trend to hourly to watch a fix land this hour — you don't have to wait for tomorrow's number, or spend another day's reads to find out it worked.

Name a bucket

This works exactly like custom events: auto-capture is free, and you name the things that matter. When a bucket shows as unknown, wrap that read path in bucket() and every read inside it is attributed to that name:

import { bucket } from "@cross-deck/buckets";

// every read inside here is attributed to "nightly-export"
await bucket("nightly-export", async () => {
  const rows = await db.collection("events").where(/* … */).get();
});

Two grains: tag a whole surface — bucket("pulse-map", handler) — or tag a single query — bucket("owner-lookup", () => db.doc(id).get()). Drill in, ship, look again, and keep going until the read is named down to the line you care about.

Tagging applies going forward, not backward. A count is a fact at the moment it happens — Buckets never rewrites history. So after you ship a tag, the old unknown / col:* bucket won't rename itself; a new named bucket appears and grows as fresh reads land, while the unnamed one stops climbing. The next full day shows the path named from its first hour. Watch for the new name appearing — not the old bar changing colour.

Tagged it, but it's still showing untagged? You tagged the wrong path. The check after you ship is simple: did your new name start growing while the col:* bucket stopped climbing? If instead the untagged bucket keeps climbing and your name never grows, the reads aren't flowing through the code you wrapped. The collection tells you what is read, not where — and the same collection is usually read from several places. Re-evaluate which path actually issues the reads and tag that one. A tell: a smooth, constant rhythm — especially with nobody using the app — is a machine (a scheduled job or per-event processing), not a person; a spiky, daytime pattern is user-driven. Let the shape point you at the right path.

Slack alerts: paged before the bill

This is the part that turns Buckets from a dashboard you remember to open into a system that finds you. Connect Slack (Developers → Integrations → Slack) and Crossdeck watches your buckets and pings you the moment a real read spike starts:

🟡 Read spike detected512,000 reads in the last hour, about 10× your normal for this time of day (~50,000). Open Buckets to see which bucket moved.

An ongoing spike pings once, not every hour. Shipped a feature you know adds reads? One click — "Expected — quiet for 24h" — and it hushes while the baseline re-learns. Your knowledge of your own roadmap is the final authority; the system never pretends to know better.

No Slack connected? Alerts still appear in the Buckets dashboard's Read-spike alerts card — connecting Slack just adds the ping.

How the baseline learns you

An alert is only trustworthy if it never cries wolf, so the detector is deliberately conservative:

It never costs you reads

The one rule that holds the whole system together: the thing that watches your read bill must never run one up. The meter counts in memory and writes a small summary about once a minute. The baseline and the alert detector read that maintained summary — never your data. Nothing in Buckets, anywhere, scans. A cost tool that costs is a contradiction; this one can't.

What the collector sees — and what Crossdeck adds

The collector is open source and yours to run free. It's honest about its own edges — and each one is exactly what Crossdeck is for:

The numbers are identical on both sides — same counts, same source. Crossdeck just makes them whole, free to read, and impossible to miss. Onboard once, install the SDK, and read-cost comes with it — no second setup.