A room sensor may report a temperature every minute for several years. Those measurements share a source identifier, have a timestamp, and contain values that change at each observation. MongoDB's native time-series collection supports this workload with database-managed storage buckets. This differs from manually placing an array of readings inside one application document. This reference targets MongoDB 8.0 and mongosh 2.5.8. The complete fixtures create disposable collections in a dedicated teaching database, insert BSON Date timestamps, and aggregate ordinary measurement documents. They avoid direct writes to internal bucket collections. Changing a collection's type requires a deliberate migration rather than adding an option to an existing ordinary collection.
Separate time, metadata, and measurements
The timeField identifies a top-level BSON Date field, here at. A string that looks like an ISO timestamp is still a string and does not satisfy the field's contract. Parse incoming timestamps before insertion and reject invalid values. The optional metaField identifies information describing a series, here an object containing a sensor identifier and room. Temperature belongs outside that object because it changes with each measurement.
MongoDB can group measurements with matching metadata into internal buckets. Metadata that changes at every reading, such as a request identifier or measured temperature, prevents the intended grouping. Conversely, combining unrelated sources under identical metadata makes filtering by source inconvenient. Choose stable, useful dimensions at collection design time. A room can change during a sensor's lifetime; that change may appropriately create a different series grouping. The metadata field is a query dimension, not an assertion that all measurements must forever share the same physical device location.
Choose granularity from observation frequency
The granularity option describes the expected interval between measurements. Supported named values are seconds, minutes, and hours, with different bucket time spans. It does not round your measurement timestamps or reduce multiple readings to one automatically. In the main example, minute-spaced measurements retain their original times and values even though the collection uses minutes granularity.
Choose an appropriate bucket configuration for the ingestion pattern and query workload. Very broad buckets can read more unrelated measurements for a narrow time query; very small buckets can increase bucket overhead. MongoDB also supports documented custom bucket span options in relevant versions. Do not copy a production setting from a tiny fixture as a performance conclusion. Restrictions apply to later option changes, and reducing a bucket span is not an ordinary arbitrary update. Inspect the version-specific creation and modification rules before changing an existing collection. Repeated inserts and realistic range queries provide better sizing evidence than one aggregate over three documents.
Query measurements through the public collection
Read the named time-series collection with normal find and aggregation operations supported for that collection type. The main fixture matches sensor A and then calculates its sample count and arithmetic mean. It reads two measurements, twenty and twenty-four, producing twenty-two. The third measurement belongs to sensor B and cannot influence A's result. The database handles the internal representation while the application works with measurement-shaped documents.
Filter by relevant metadata and a bounded time interval early in an aggregation. MongoDB 6.3 and later automatically creates a compound time-and-metadata index for newly created time-series collections, but that does not prove every query is efficient. Explain the real predicate and inspect measurement unpacking and index use under your exact engine. Keep timestamps as Dates so interval comparisons remain temporal rather than lexical. A final sort is needed when presenting ordered aggregate groups. The time-series storage format does not replace an explicit output ordering requirement.
Distinguish retention from exact visibility
A collection-level expireAfterSeconds setting makes old measurements eligible for automatic removal according to time-series retention behavior. Expiration is performed in the background and interacts with bucket contents. It is not a precise timer that guarantees an old measurement disappears at a particular millisecond. A user-facing rule such as display only the last hour therefore still needs an explicit time predicate.
The practice inserts fixed measurements around a reporting boundary and uses a half-open interval. It intentionally does not wait for TTL deletion or assert a physical deletion deadline. That makes the query's business rule reproducible independently of asynchronous cleanup. Choose retention according to storage policy and recovery needs, then choose report visibility according to the report contract. Time-series collections also have specialized restrictions, including no change streams and no unique indexes in this version. Updates have documented limitations. Treat them as a measurement workload tool rather than a replacement for every ordinary collection.
MongoDB documentation and version notes
MongoDB Time Series Collections Examples
Each example shows the code, the output it produces and what each part does.
Inspecting time-series options and averaging readings
The reset drops only the named disposable measurement collection so rerunning the fixture can recreate its native time-series options. Three observations contain two sensors. The script inspects the public creation options and aggregates sensor A's two readings. The printed average is a sample average, not a time-weighted estimate between irregular observations. No undocumented internal bucket fields appear in the query or output. Run this mongosh script against the teaching MongoDB 8.0 server; substituting a production collection name would require a different reset policy.
const demo = db.getSiblingDB('codevra_ref_timeseries');
demo.getCollection('readings').drop();
demo.createCollection('readings', { timeseries: { timeField: 'at', metaField: 'meta', granularity: 'minutes' } });
const readings = demo.getCollection('readings');
readings.insertMany([
{ at: new Date('2026-10-01T12:00:00Z'), meta: { sensor: 'A', room: 'lab' }, temperature: 20 },
{ at: new Date('2026-10-01T12:01:00Z'), meta: { sensor: 'A', room: 'lab' }, temperature: 24 },
{ at: new Date('2026-10-01T12:00:00Z'), meta: { sensor: 'B', room: 'hall' }, temperature: 30 }
]);
const options = demo.getCollectionInfos({ name: 'readings' })[0].options.timeseries;
print(options.timeField + ' / ' + options.metaField + ' / ' + options.granularity);
const result = readings.aggregate([
{ $match: { 'meta.sensor': 'A' } },
{ $group: { _id: '$meta.sensor', samples: { $sum: 1 }, average: { $avg: '$temperature' } } },
{ $project: { _id: 0, sensor: '$_id', samples: 1, average: 1 } }
]).toArray()[0];
print(JSON.stringify({ sensor: result.sensor, samples: result.samples, average: result.average }));at / meta / minutes
{"sensor":"A","samples":2,"average":22}MongoDB Time Series Collections in Real Projects
Query an hourly window in a time-series collection
Select only observations from noon inclusive to one o'clock exclusive, grouped by sensor, while keeping ten years of configured retention. The fixed timestamps make the exercise independent of the wall clock. A reading exactly at one o'clock belongs to the next reporting interval. The solution creates a separate native collection, inserts all three boundary cases, and uses both comparison operators before grouping. Setting retention does not remove the need for this predicate. On a running server, background retention may eventually remove old fixed-date documents, so the immediate insert-and-query fixture demonstrates query boundaries rather than long-term retention timing.
const demo = db.getSiblingDB('codevra_ref_timeseries');
demo.getCollection('practice').drop();
demo.createCollection('practice', { timeseries: { timeField: 'at', metaField: 'sensor', granularity: 'minutes' }, expireAfterSeconds: 315360000 });
const readings = demo.getCollection('practice');
readings.insertMany([
{ at: new Date('2026-10-01T12:00:00Z'), sensor: 'A', value: 10 },
{ at: new Date('2026-10-01T12:59:59Z'), sensor: 'A', value: 14 },
{ at: new Date('2026-10-01T13:00:00Z'), sensor: 'A', value: 99 }
]);
const result = readings.aggregate([
{ $match: { at: { $gte: new Date('2026-10-01T12:00:00Z'), $lt: new Date('2026-10-01T13:00:00Z') } } },
{ $group: { _id: '$sensor', total: { $sum: '$value' }, samples: { $sum: 1 } } }
]).toArray()[0];
print(JSON.stringify({ sensor: result._id, total: result.total, samples: result.samples }));{"sensor":"A","total":24,"samples":2}MongoDB Time Series Collections FAQ
Does granularity round timestamps?
No. The option controls the expected measurement cadence and bucket configuration; it does not transform every supplied Date to the start of a minute. Both timestamps and values remain available for queries. Use an explicit aggregation expression when the report needs rounded time groups. Otherwise changing storage granularity could be mistakenly interpreted as a change in business measurement precision.
Can I write directly to system.buckets?
Use the public collection and documented time-series APIs. Internal bucket documents are storage machinery rather than an application schema to edit. Directly manipulating them risks violating collection invariants and makes code dependent on implementation details. If a correction workflow needs unsupported measurement updates, evaluate an ordinary collection or a documented rewrite/migration strategy instead of treating internal bucket access as a shortcut.
Will TTL enforce a one-hour display window?
It can support a retention policy but cannot guarantee a precise visibility deadline. Background cleanup is asynchronous, and time-series expiration is bucket-aware. Add a Date range predicate for a report that must exclude older measurements immediately. A report can be correct even while storage cleanup has not removed every expired record; those are separate responsibilities with different timing contracts.
Are native time-series collections suitable for mutable orders?
An order has a business identity and can undergo multiple state transitions requiring ordinary updates and uniqueness. Those requirements often fit an ordinary collection better. A separate stream of timestamped order measurements may fit time series, but specialized limitations remain. Select the collection type from access and mutation requirements rather than assuming every document containing a date should use the time-series storage format.
MongoDB Time Series Collections Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Which values contribute to the practice total for the noon-to-one interval?
MongoDB Time Series Collections Quiz
2 questions to check what you learned.
Which field belongs in stable series metadata?
- The sensor identifier
- The temperature that changes each minute
- A fresh random request ID for every observation
What does minutes granularity do to an observation at 12:01:30?
- It leaves that timestamp intact while configuring bucket behavior
- It changes the timestamp to 12:01:00
- It discards all other measurements in that minute