-
-
Notifications
You must be signed in to change notification settings - Fork 504
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support mapping time series collections (#2687)
* Store metadata information for time series collections * Specify time series options when creating collection * Use named arguments for attributes * Don't skip empty metadata field names * Leave encoding of enum values to the MongoDB driver * Remove unused isTimeSeries option * Disable early exit requirement in XML driver * Use explicit closure instead of empty() checks * Support bucketMaxSpanSeconds and bucketRoundingSeconds in time series collections * Read bucket options for time series in XML driver * Add attribute documentation for time series collections * Add cookbook entry for time series data * Update documentation links * Expand time series cookbook to use multiple measurements * Simplify markAsTimeSeries tests with granularity and bucket options * Apply wording suggestions from code review Co-authored-by: Jeremy Mikola <jmikola@gmail.com> --------- Co-authored-by: Jeremy Mikola <jmikola@gmail.com>
- Loading branch information
Showing
17 changed files
with
570 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
Storing Time Series Data | ||
======================== | ||
|
||
.. note:: | ||
|
||
Support for mapping time series data was added in ODM 2.10. | ||
|
||
`time-series data <https://www.mongodb.com/docs/manual/core/timeseries-collections/>`__ | ||
is a sequence of data points in which insights are gained by analyzing changes | ||
over time. | ||
|
||
Time series data is generally composed of these components: | ||
|
||
- | ||
Time when the data point was recorded | ||
|
||
- | ||
Metadata, which is a label, tag, or other data that identifies a data series | ||
and rarely changes | ||
|
||
- | ||
Measurements, which are the data points tracked at increments in time. | ||
|
||
A time series document always contains a time value, and one or more measurement | ||
fields. Metadata is optional, but cannot be added to a time series collection | ||
after creating it. When using an embedded document for metadata, fields can be | ||
added to this document after creating the collection. | ||
|
||
.. note:: | ||
|
||
Support for time series collections was added in MongoDB 5.0. Attempting to | ||
use this functionality on older server versions will result in an error on | ||
schema creation. | ||
|
||
Creating The Model | ||
------------------ | ||
|
||
For this example, we'll be storing data from multiple sensors measuring | ||
temperature and humidity. Other examples for time series include stock data, | ||
price information, website visitors, or vehicle telemetry (speed, position, | ||
etc.). | ||
|
||
First, we define the model for our data: | ||
|
||
.. code-block:: php | ||
<?php | ||
use DateTimeImmutable; | ||
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; | ||
use MongoDB\BSON\ObjectId; | ||
#[ODM\Document] | ||
readonly class Measurement | ||
{ | ||
#[ODM\Id] | ||
public string $id; | ||
public function __construct( | ||
#[ODM\Field(type: 'date_immutable')] | ||
public DateTimeImmutable $time, | ||
#[ODM\Field(type: 'int')] | ||
public int $sensorId, | ||
#[ODM\Field(type: 'float')] | ||
public float $temperature, | ||
#[ODM\Field(type: 'float')] | ||
public float $humidity, | ||
) { | ||
$this->id = (string) new ObjectId(); | ||
} | ||
} | ||
Note that we defined the entire model as readonly. While we could theoretically | ||
change values in the document, in this example we'll assume that the data will | ||
not change. | ||
|
||
Now we can mark the document as a time series document. To do so, we use the | ||
``TimeSeries`` attribute, configuring appropriate values for the time and | ||
metadata field, which in our case stores the ID of the sensor reporting the | ||
measurement: | ||
|
||
.. code-block:: php | ||
<?php | ||
// ... | ||
#[ODM\Document] | ||
#[ODM\TimeSeries(timeField: 'time', metaField: 'sensorId')] | ||
readonly class Measurement | ||
{ | ||
// ... | ||
} | ||
Once we create the schema, we can store our measurements in this time series | ||
collection and let MongoDB optimize the storage for faster queries: | ||
|
||
.. code-block:: php | ||
<?php | ||
$measurement = new Measurement( | ||
time: new DateTimeImmutable(), | ||
sensorId: $sensorId, | ||
temperature: $temperature, | ||
humidity: $humidity, | ||
); | ||
$documentManager->persist($measurement); | ||
$documentManager->flush(); | ||
Note that other functionality such as querying, using aggregation pipelines, or | ||
removing data works the same as with other collections. | ||
|
||
Considerations | ||
-------------- | ||
|
||
With the mapping above, data is stored with a granularity of seconds. Depending | ||
on how often measurements come in, we can reduce the granularity to minutes or | ||
hours. This changes how the data is stored internally by changing the bucket | ||
size. This affects storage requirements and query performance. | ||
|
||
For example, with the default ``seconds`` granularity, each bucket groups | ||
documents for one hour. If each sensor only reports data every few minutes, we'd | ||
do well to configure ``minute`` granularity. This reduces the | ||
number of buckets created, reducing storage and making queries more efficient. | ||
However, if we were to choose ``hours`` for granularity, readings for a whole | ||
month would be grouped into one bucket, resulting in slower queries as more | ||
entries have to be traversed when reading data. | ||
|
||
More details on granularity and other consideration scan be found in the | ||
`MongoDB documentation <https://www.mongodb.com/docs/manual/core/timeseries/timeseries-considerations/>`__. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
29 changes: 29 additions & 0 deletions
29
lib/Doctrine/ODM/MongoDB/Mapping/Annotations/TimeSeries.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Doctrine\ODM\MongoDB\Mapping\Annotations; | ||
|
||
use Attribute; | ||
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; | ||
use Doctrine\ODM\MongoDB\Mapping\TimeSeries\Granularity; | ||
|
||
/** | ||
* Marks a document or superclass as a time series document | ||
* | ||
* @Annotation | ||
* @NamedArgumentConstructor | ||
*/ | ||
#[Attribute(Attribute::TARGET_CLASS)] | ||
final class TimeSeries implements Annotation | ||
{ | ||
public function __construct( | ||
public readonly string $timeField, | ||
public readonly ?string $metaField = null, | ||
public readonly ?Granularity $granularity = null, | ||
public readonly ?int $expireAfterSeconds = null, | ||
public readonly ?int $bucketMaxSpanSeconds = null, | ||
public readonly ?int $bucketRoundingSeconds = null, | ||
) { | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.