MongoDB time-series.

Create

db.createCollection("metrics", {
    timeseries: {
        timeField: "ts",
        metaField: "sensor",
        granularity: "minutes",       // seconds, minutes, hours
    },
    expireAfterSeconds: 86400 * 30,
})

metaField: tags / dimensions (sensor id, region).

Insert

db.metrics.insertMany([
    { ts: ISODate(), sensor: { id: 1, region: "ny" }, temp: 22.5 },
    { ts: ISODate(), sensor: { id: 1, region: "ny" }, temp: 22.6 },
])

Query

db.metrics.find({
    "sensor.id": 1,
    ts: { $gte: ISODate("2026-01-15"), $lte: ISODate("2026-01-16") },
})

Aggregation: bucket

db.metrics.aggregate([
    { $match: { "sensor.id": 1, ts: { $gte: ..., $lte: ... } } },
    {
        $group: {
            _id: {
                $dateTrunc: { date: "$ts", unit: "hour" },
            },
            avg: { $avg: "$temp" },
            min: { $min: "$temp" },
            max: { $max: "$temp" },
        },
    },
    { $sort: { _id: 1 } },
])

Granularity choice

  • seconds: high-frequency (>1/min).
  • minutes: typical (1-60/min).
  • hours: low-frequency (<1/min).

Mongo internally buckets writes for compression.

Compression

Time-series collections compress similar data efficiently. 5-10x vs regular.

Indexes

db.metrics.createIndex({ "sensor.id": 1, ts: 1 })

Auto-creates index on ts + metaField.

Retention (TTL)

expireAfterSeconds: 86400 * 30   // 30 days

Auto-deletes old buckets.

Window functions

db.metrics.aggregate([
    { $match: { ... } },
    {
        $setWindowFields: {
            partitionBy: "$sensor.id",
            sortBy: { ts: 1 },
            output: {
                rolling_avg: {
                    $avg: "$temp",
                    window: { range: [-1, 0], unit: "hour" },
                },
            },
        },
    },
])

Rolling averages, cumulative sums.

Downsampling

Manually aggregate to lower-resolution collection:

db.metrics.aggregate([
    { $group: { _id: { sensor: "$sensor", hour: { $dateTrunc: { date: "$ts", unit: "hour" } } }, avg: { $avg: "$temp" } } },
    { $merge: "metrics_hourly" },
])

Schedule daily.

Compared to Prometheus / Influx

Mongo time-series: good if Mongo already in stack. For pure metrics + huge scale: Prometheus or VictoriaMetrics + Grafana.

Common mistakes

  • Wrong granularity → poor compression.
  • Updates / deletes (limited in TS collections).
  • No retention → infinite growth.
  • Querying without metaField → scatter.
  • Wrong index choice.

Read this next

If you want my Mongo TS setup, it’s at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .