Skip to content
Explainer · Analytics and distributed databases

MongoDB Time-Series Collections for Sensor Data

MongoDB time series collections are a special collection type that groups measurements from the same source into compressed, time-ordered buckets, which the manual says improves query efficiency and reduces disk usage compared with a regular collection. This article explains how bucketing works, how to choose the timeField, metaField and granularity (or custom bucketing parameters) for building and equipment telemetry, how expireAfterSeconds removes old data, and the limitations in the current manual that decide whether MongoDB time series is the right fit.

Facts checked 27 October 2026 against the vendors' official documentation. Versions covered: MongoDB manual (current stable release series 9.0) as published on 27 October 2026; time series collections were introduced in MongoDB 5.0. Next review due April 2027. Products, licences and prices in this field change quickly; confirm on the vendor's site before you choose. No benchmarks are quoted unless the source and test setup are named.
Short answer
  • A time series collection stores measurements in buckets: documents with an identical metaField value and nearby timeField values are grouped and compressed together in a columnar format.
  • timeField is required and must hold a BSON date; metaField should identify the series (site, sensor) and rarely change; both are fixed when the collection is created.
  • granularity (seconds, minutes, hours) or the custom pair bucketMaxSpanSeconds and bucketRoundingSeconds set the bucket span; match it to the interval between readings from one source.
  • expireAfterSeconds removes whole buckets once every measurement in them has expired; deletion is not immediate.
  • The manual lists real limits: no change streams, schema validation or unique indexes, restricted updates, no writes in transactions, a 4 MB document limit and sharding rules.
How we know: Research-based from the MongoDB manual (Time Series overview, About Time Series Data, Create and Query, Set Granularity, Automatic Removal, Considerations, Best Practices, Limitations, Compression, Shard a Time Series Collection, Release Notes and 9.0 release notes pages), checked 27 October 2026; the update limitations were re-checked that day in the current (9.0) manual and in the 7.0 and 8.0 manuals, which list the same requirements and do not describe deleting individual measurements. The licence statements are quoted from the mongodb/mongo repository README and LICENSE-Community.txt, checked the same day. Delete-and-re-insert of a single measurement was not verified. The bucket-boundary arithmetic on this page was run on PostgreSQL 18.6 (PGlite 0.5.11) as a cross-check of the manual's example. The mongosh commands are adapted from the manual and were not run here; no MongoDB server was installed. No performance figures are quoted.

What MongoDB time series collections are, and who this is for

Since MongoDB 5.0, a collection can be created as a time series collection. The manual describes the idea simply: writes are organised so that data from the same source is stored alongside other data points from a similar point in time. Each document is a measurement with three parts: a time (the timeField), metadata that identifies the series and rarely changes (the metaField), and the metric values. Compared with a regular collection, the manual says this improves query efficiency and reduces disk usage for the data and its secondary indexes.

This guide is for teams whose applications already run on MongoDB (a CAFM portal, a mobile maintenance app, an IoT platform) and who want to keep sensor readings in the same database rather than add a separate time-series engine. It focuses on the MongoDB time series design choices and limits. For a head-to-head with a SQL option, see MongoDB Time Series vs TimescaleDB; for general background, see the time-series database overview. On 27 October 2026 the release notes listed MongoDB 9.0 as the current stable release series.

How bucketing works

Internally, MongoDB groups measurements into buckets. A document goes into an open bucket only if its metaField value is identical and its timeField falls inside the bucket's time span; otherwise a new bucket is created. A bucket is closed when time moves past its span, when it reaches 1,000 measurements or 125 KB (the manual's defaults), when the set of open buckets no longer fits in cache, or on restart and some conflicting operations. Closed buckets remain queryable. Inside a bucket, data is stored column by column and compressed.

  • Storage format. Time series collections use zstd compression (the global default is snappy) plus column compression with delta and delta-of-delta encoding, object and array compression, and run-length encoding. The 9.0 manual states: "Starting in 9.0, MongoDB stores time series collections in single namespaces that contain the data compressed into organized groups, or buckets." When you query, MongoDB converts the bucketed data to one document per measurement. The 9.0 release notes add that time series collections "are no longer writable non-materialized views"; earlier releases behave differently, so check the release notes for the version you run.
  • Index. From 6.3, MongoDB creates a compound index on metaField and timeField automatically for new time series collections. There is no index on _id.
  • Cardinality. The number of buckets depends on the number of distinct metaField values. Fine-grained or changing metadata creates many sparsely filled, short-lived buckets, which reduces storage and query efficiency.

Choosing timeField, metaField and granularity for building telemetry

timeField is required and must contain a BSON date. Use the time the reading was taken at the device or gateway, not the time it reached the database.

metaField should identify one series and be used in filters: for building data, an object such as { site: "HQ", pointId: "AHU-01.SAT" }. The manual advises choosing fields that rarely or never change, preferring stable identifiers used in filter expressions, and keeping fields that are not used for filtering out of the metaField (store them as measurements, or better in an asset collection). Do not put the reading value, a status that changes every minute or a unique message ID in the metaField, or each reading gets its own bucket. Query the metaField by its scalar sub-fields ("meta.site", "meta.pointId"), because MongoDB may reorder the fields of a metaField object.

granularity is "seconds" (default, one-hour buckets), "minutes" (24-hour buckets) or "hours" (30-day buckets). Set it to the value closest to the interval between readings from one source: "seconds" for 1 to 10-second BMS points, "minutes" for one-minute or five-minute points, "hours" for hourly meter reads. Consider typical queries too: the manual suggests "minutes" when queries fetch a day at a time. From 6.3 you can instead set bucketMaxSpanSeconds and bucketRoundingSeconds to the same value (for example 14,400 for four-hour reporting windows) when queries follow fixed intervals. Granularity or the custom values can later be increased with collMod, but never decreased.

Create a collection for one-minute BMS points with 396-day retention, and insert a batch (mongosh. Illustrative configuration, not run here: adapted from the MongoDB manual; check every setting against the manual for your version before use)
db.createCollection("bms_readings", {
  timeseries: {
    timeField: "ts",
    metaField: "meta",
    granularity: "minutes"
  },
  expireAfterSeconds: 34214400        // 396 days
});

// one insertMany per batch, grouped by series, unordered
db.bms_readings.insertMany([
  { meta: { site: "HQ", pointId: "AHU-01.SAT" }, ts: ISODate("2026-10-27T08:00:00Z"), value: 13.8, quality: 0 },
  { meta: { site: "HQ", pointId: "AHU-01.SAT" }, ts: ISODate("2026-10-27T08:01:00Z"), value: 13.9, quality: 0 },
  { meta: { site: "HQ", pointId: "L3-Z04.CO2" }, ts: ISODate("2026-10-27T08:00:00Z"), value: 742,  quality: 0 },
  { meta: { site: "HQ", pointId: "L3-Z04.CO2" }, ts: ISODate("2026-10-27T08:01:00Z"), value: 755,  quality: 0 }
], { ordered: false });
Custom bucketing aligned to four-hour reporting windows (mongosh. Illustrative configuration, not run here: adapted from the MongoDB manual; check every setting against the manual for your version before use)
db.createCollection("meter_kwh", {
  timeseries: {
    timeField: "ts",
    metaField: "meta",
    bucketMaxSpanSeconds: 14400,
    bucketRoundingSeconds: 14400
  }
});

Cross-checking a bucket boundary

The manual's example says that with 14,400-second custom bucketing, a document stamped 2023-03-27T16:24:35Z that needs a new bucket gets a bucket from 16:00:00 to 19:59:59. Rounding down to a 14,400-second boundary is the same arithmetic as PostgreSQL's date_bin, which confirms the boundaries. This ran on PostgreSQL 18.6 as a planning aid; it is not MongoDB code.

Bucket boundaries for a 14,400-second rounding interval (run on PostgreSQL 18.6)
SET TimeZone = 'UTC';

SELECT date_bin(interval '14400 seconds', t, timestamptz '1970-01-01 00:00+00')::text AS bucket_min_time,
       (date_bin(interval '14400 seconds', t, timestamptz '1970-01-01 00:00+00') + interval '14399 seconds')::text AS bucket_max_time
FROM (VALUES (timestamptz '2023-03-27 16:24:35+00')) AS v(t);
Result on PostgreSQL 18.6 (PGlite)
    bucket_min_time     |    bucket_max_time
------------------------+------------------------
 2023-03-27 16:00:00+00 | 2023-03-27 19:59:59+00

Retention with expireAfterSeconds

Set expireAfterSeconds when creating the collection, or add or change it later with collMod; set it to "off" to disable removal. A measurement expires when its timeField plus the interval has passed, but MongoDB removes data a bucket at a time: the bucket is deleted only once every measurement in it has expired, by a background task that runs every 60 seconds. Data can therefore stay visible for up to a bucket span (one hour, 24 hours or 30 days depending on granularity) after its own expiry, longer on a busy server. Size retention-related storage with that lag in mind. The 396-day and 90-day values in the examples on this page are illustrative assumptions, not recommendations; choose your own from how long operators and reports need raw readings, any record-keeping obligations, and storage cost.

One option for long-term trends is a separate rollup collection: a scheduled aggregation can $group readings by hour with $dateTrunc and write the results with $merge into a regular collection (the manual allows $merge from a time series collection to another collection, but not into one; use $out to write into a time series collection). Wider retention design is covered in Time-Series Retention and Downsampling Design.

Change retention to 90 days, and an hourly rollup into a regular collection (mongosh. Illustrative configuration, not run here: adapted from the MongoDB manual; check every setting against the manual for your version before use)
db.runCommand({ collMod: "bms_readings", expireAfterSeconds: 7776000 });   // 90 days

db.bms_readings.aggregate([
  { $match: { "meta.site": "HQ",
              ts: { $gte: ISODate("2026-10-26T00:00:00Z"), $lt: ISODate("2026-10-27T00:00:00Z") } } },
  { $group: {
      _id: { site: "$meta.site", pointId: "$meta.pointId",
             hour: { $dateTrunc: { date: "$ts", unit: "hour" } } },
      avg: { $avg: "$value" }, min: { $min: "$value" }, max: { $max: "$value" }, n: { $sum: 1 } } },
  { $merge: { into: "bms_hourly", whenMatched: "replace", whenNotMatched: "insert" } }
]);

Inserts, queries and indexes

  • Batch writes. Use one insertMany() per batch instead of many insertOne() calls, group measurements with the same metaField in the same batch, and set ordered: false. Keep the field order identical in every document, round values to the precision you need, and omit empty fields; all three help compression.
  • Query patterns. Filter on the metaField sub-fields for equality and on timeField for ranges; the automatic compound index supports both. Add secondary indexes for other common filters, within the limits below. Use $group instead of distinct, which cannot be indexed efficiently on time series collections.
  • Late and duplicate readings. Out-of-order readings are accepted, but a reading that falls outside every open bucket opens a new one, so heavy backfills create extra buckets. There is no unique index, so duplicate replays from a gateway are stored twice unless the writer deduplicates; see Handling Duplicate Telemetry Events in SQL for patterns.
  • Sharding. Shard on the metaField or its sub-fields (for example a hashed meta.site). Shard keys containing the timeField are deprecated from 8.0, zone sharding is not supported, and sharding administration commands cannot be run on sharded time series collections.

If a $group over a long range fails with a memory error, see the MongoDB error library entry for code 292 (memory limit without external sorting); creating a time series collection over an existing name returns NamespaceExists (48). Other server errors are listed in the MongoDB error library.

Limitations in the current manual

Time series collections behave like regular collections with exceptions that matter for production. As listed in the manual on 27 October 2026:

MongoDB time series limitations that affect sensor applications
AreaWhat the manual saysPractical effect
Unsupported featuresMongoDB Search, change streams, Client-Side Field Level Encryption, Database Triggers, schema validation rules and reIndex are not supportedNo change-stream alerts from the collection; validate readings before insert
UpdatesUpdate commands must meet these requirements: "You can only match on the metaField field value." "You can only modify the metaField field value." The update document may contain only update operator expressions, must not limit the number of documents updated (multi: true or updateMany()), and "must not set upsert: true". The 7.0 and 8.0 manuals list the same requirements.Measured values cannot be corrected with an update. On deletes the documentation is incomplete: the limitations pages for 9.0, 8.0 and 7.0 list update requirements and, for old data, say "To automatically delete old data, set up automatic removal (TTL).", but describe neither support for nor restrictions on deleting individual measurements. We did not run delete-and-re-insert of a single measurement; test it on your version. Keeping corrections in a separate collection is our design suggestion, not a manual recommendation.
IndexesNo default _id index; no unique or text indexes; multikey, 2d and sparse indexes only on the metaFieldDatabase cannot enforce one reading per sensor and time
Document sizeMaximum 4 MB per documentKeep each measurement small; do not embed arrays of samples
Collection typeCannot convert to or from a time series collection; timeField and metaField are fixed at creationDecide the design before loading; migrations copy data
GranularityBucket span can be increased but not decreased; bucket limit 1,000 measurements or 125 KBStart with the finest span you will need
TransactionsWrites in transactions are not supported (reads are)Keep work-order updates and readings in separate operations
Capped collectionsA time series collection cannot be cappedUse expireAfterSeconds for retention

Sizing, security and operations

  • Sizing. Storage depends on bucket fill: few, stable series with regular readings fill buckets well; thousands of series with sparse readings do not. Load a representative week of data and check collection statistics before sizing a cluster, rather than relying on a general compression ratio.
  • Bucket catalogue memory. Open buckets are tracked in an in-memory catalogue (by default up to 2.5 per cent of system memory). Very high series counts with irregular arrival close buckets early and increase write work.
  • Security. Client-Side Field Level Encryption is not available on time series collections, so rely on TLS in transit, encryption at rest, network isolation and least-privilege roles; readings rarely contain personal data, but tenant and location metadata may be commercially sensitive.
  • Upgrades. Time series collections are a backwards-incompatible feature: the manual lists downgrade paths that require dropping them first. Check this before upgrading a replica set you may need to roll back.
  • Atlas. Time series collections can be created from the Atlas UI as well as from drivers and mongosh; pricing depends on the Atlas tier and is not covered here.
  • Licence. The MongoDB server repository README states that versions released before 16 October 2018 "are published under the AGPL" and that later versions, including patch fixes for prior versions, are published under the Server Side Public License (SSPL) v1; the licence text in the repository is headed "Server Side Public License VERSION 1, OCTOBER 16, 2018". MongoDB Atlas is a commercial service on its own terms. This is a factual description, not legal advice: read the README, the licence and any service terms, and take advice for your situation.

When not to use MongoDB time series collections

  • You do not already run MongoDB. Adopting MongoDB only for telemetry brings a new operational stack; a SQL time-series option such as TimescaleDB may fit a SQL-based CAFM or CMMS better.
  • Readings that must be unique or corrected in place. Without unique indexes and with metaField-only updates, removing duplicate readings and correcting values are the application's job.
  • Event-driven alarms from the database. Change streams and triggers are not supported; raise alarms in the streaming layer (Kafka consumers or the MQTT gateway) instead.
  • Very large analytical workloads. Our editorial view: portfolio-wide analysis over years of second-level data is likely to fit a column store better, such as ClickHouse or a warehouse.
  • Unordered or non-time data. The manual says time series collections are not intended for unordered data or data that is not time-dependent.

For a wide-column alternative whose documentation names high write volumes and multi-site availability among its design goals, see Apache Cassandra for High-Volume Telemetry, and for a shortlist across engines, the guide to choosing a time-series database.

Frequently asked questions

What is a MongoDB time series collection?

A collection type, available since MongoDB 5.0, that stores measurements in compressed, time-ordered buckets grouped by a metaField that identifies each series. The manual says it improves query efficiency and reduces disk usage compared with a regular collection.

What should I use as the metaField for sensor data?

A small object of stable identifiers you filter on, such as site and sensor or point ID. Avoid values that change often or are unique per reading, because each distinct metaField value gets its own buckets.

Which granularity should I choose?

The one closest to the interval between readings from a single source: seconds for second-level data, minutes for minute-level data, hours for hourly data. You can increase it later with collMod but not decrease it.

Does expireAfterSeconds delete readings exactly on time?

No. MongoDB removes a whole bucket once all of its measurements have expired, using a background task that runs every 60 seconds, so data can remain for some time after its own expiry.

Can I update a reading in a time series collection?

Only in limited ways. The current (9.0) manual says "You can only match on the metaField field value" and "You can only modify the metaField field value", and the 7.0 and 8.0 manuals say the same. A measured value therefore cannot be corrected with an update. The documentation we checked does not say whether a single measurement can be deleted and re-inserted (the limitations page covers updates and points to TTL for removing old data); we did not run it, so test it on your version. Another option is, as a design suggestion of ours, recording corrections in a separate collection that reports read in preference to the raw data.

Sources

Checked 27 October 2026.

How we research these articles: our editorial method. Research-based from official documentation; SQL examples are run on a local database where the page says so. CodeWithSQL earns nothing from the vendors mentioned.

Building a sensor or telemetry database?

Start with the overview, or compare time-series databases side by side.