Mongo ttl bson tag match
Skill esaldgut/ai-native-engineering-workspace/global-skills/aws-go/mongo-ttl-bson-tag-match
When creating or modifying a MongoDB TTL index from Go, the index Keys field MUST match the Go struct's bson tag exactly (byte-for-byte, case-sensitive) and the field MUST serialize to BSON Date. MongoDB silently skips documents that lack the indexed field or whose field isn't a date — no error, no warning, no log. A snake_case-vs-camelCase typo between bson:"expiresAt" and a Keys:{"expires_at"} index leaks storage forever and you find out weeks later via the cost graph. Use before any migration that calls SetExpireAfterSeconds, or when writing the first timestamped document of a new collection.From its SKILL.md
npx -y skills add esaldgut/ai-native-engineering-workspace --skill mongo-ttl-bson-tag-matchAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
7.1 KB, ~1.5k tokens by cl100k_base, as published. Nobody here has run it
MongoDB TTL index ↔ bson tag match
A MongoDB TTL index deletes documents whose indexed Date field is older than the threshold. The MongoDB Manual is explicit about the failure mode, verbatim:
"If a document does not contain the indexed field, the document will not expire."
"If the indexed field in a document doesn't contain one or more date values, the document will not expire."
MongoDB does not error, warn, or log when it skips those documents. The TTL monitor runs every 60 seconds, deletes what matches, and silently ignores everything else. There is no schema enforcement unless you explicitly add Schema Validation, and no observability surface for "documents that should expire but don't." So a single tag mismatch is a silent storage leak.
When to invoke
- Any migration or code path that creates/modifies a TTL index — i.e. calls
options.Index().SetExpireAfterSeconds(...). - Writing the first timestamped document of a new collection.
- Reviewing a struct whose field feeds a TTL index, or renaming such a field/tag.
Announce on invoke: "Using mongo-ttl-bson-tag-match to verify the TTL index Keys equals the bson tag exactly and the field is a BSON Date."
The two invariants
Keyskey string == the struct'sbson:"..."tag, byte-for-byte. MongoDB is case-sensitive and does not normalize:expiresAt≠expires_at≠expiresat. The Go driver writes the field under the bson tag name; the index points at the Keys name; if they differ, the index seesnullon every document and nothing expires.- The field must serialize to BSON
Date. Go'stime.Timemarshals to BSONDateby default — good.string(ISO-8601) andint64(Unix epoch) marshal to BSONString/Int64, which the TTL monitor treats as "not a date value" and skips. Usetime.Time.
Both failures are silent. Neither throws. The cost graph is your only late alarm — so catch it early.
Canonical example
import (
"context"
"time"
"go.mongodb.org/mongo-driver/v2/bson"
"go.mongodb.org/mongo-driver/v2/mongo"
"go.mongodb.org/mongo-driver/v2/mongo/options"
)
// CRITICAL: the bson tag below MUST equal the index Keys key, and the type MUST be time.Time.
type SessionDoc struct {
ID bson.ObjectID `bson:"_id,omitempty"`
Token string `bson:"token"`
ExpiresAt time.Time `bson:"expiresAt"` // BSON Date — NOT string, NOT int64
}
func EnsureSessionTTL(ctx context.Context, coll *mongo.Collection) error {
_, err := coll.Indexes().CreateOne(ctx, mongo.IndexModel{
Keys: bson.D{{Key: "expiresAt", Value: 1}}, // MUST match `bson:"expiresAt"` exactly
Options: options.Index().SetExpireAfterSeconds(0),
})
return err
}
SetExpireAfterSeconds(0) means "expire at the absolute timestamp stored in the field" (the doc dies
0–60s after ExpiresAt). A non-zero N means "expire N seconds after the field's Date." Both are
valid — choose deliberately; the value is int32.
Cheap insurance: a reflection test
// A unit test that asserts every TTL IndexModel's Keys matches a real bson tag on the struct
// fails in CI in milliseconds — far cheaper than a silent leak discovered weeks later in billing.
func assertTTLKeyMatchesTag[T any](keyName string) bool {
// walk reflect.TypeOf(T).Field(i).Tag.Get("bson"), strip ",omitempty", compare to keyName
// return true iff some field's bson tag == keyName
return true // implement per your structs
}
Anti-pattern to detect (greppable)
- A
Keys: bson.D{{Key: "...", ...}}whose key has no exactly-matchingbson:"..."tag on the persisting struct. - A TTL-indexed field typed
stringorint64instead oftime.Time. - A field/tag rename that doesn't update the index
Keys(or migration) in the same change. - Relying on "MongoDB will error if it's wrong" — it won't.
Decision aid
- Absolute per-document expiry timestamp? → store
time.Time, index that field,SetExpireAfterSeconds(0). - Fixed lifetime after a creation time? →
SetExpireAfterSeconds(N), N>0, on the creation-timetime.Time. - Tempted to store the timestamp as a string/epoch int? → don't; TTL silently ignores non-Date.
- Defense-in-depth? → add MongoDB Schema Validation to reject writes missing the field (catches it at write time, not TTL time).
Related skills
global-skills/aws-go/lambda-go-lazy-init-segregated/SKILL.md— the Mongo client this index lives on should be initialized lazily behind its ownsync.Once, off the cold-start happy path.
Sources
- MongoDB TTL Indexes ("the document will not expire"; 60s monitor) · Expire Data from Collections by Setting TTL
- mongo-driver/v2 — IndexModel / IndexView.CreateOne · options.Index().SetExpireAfterSeconds
- MongoDB Go Driver — Struct Tagging
Last verified: 2026-06-03 against the MongoDB Manual (live — "If a document does not contain the
indexed field, the document will not expire"; TTL monitor every 60 seconds; no error path) and
mongo-driver/v2 v2.6.0 (mongo.IndexModel{Keys bson.D}, IndexView.CreateOne,
options.Index().SetExpireAfterSeconds(int32)).
Re-check after: MongoDB Go driver v2 major / MongoDB server major, or by 2026-09-03.
Decay risk: low (TTL silent-skip semantics are long-stable).
Found a drift? Run /skill-pattern-freshness-audit aws-go.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.