Skip to content

Commit 3f09282

Browse files
authored
CSHARP-4539: Improving Time Series Bucketing Scalability (#1228)
1 parent 8662001 commit 3f09282

File tree

5 files changed

+164
-15
lines changed

5 files changed

+164
-15
lines changed

specifications/collection-management/tests/timeseries-collection.json

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,71 @@
250250
]
251251
}
252252
]
253+
},
254+
{
255+
"description": "createCollection with bucketing options",
256+
"runOnRequirements": [
257+
{
258+
"minServerVersion": "6.3"
259+
}
260+
],
261+
"operations": [
262+
{
263+
"name": "dropCollection",
264+
"object": "database0",
265+
"arguments": {
266+
"collection": "test"
267+
}
268+
},
269+
{
270+
"name": "createCollection",
271+
"object": "database0",
272+
"arguments": {
273+
"collection": "test",
274+
"timeseries": {
275+
"timeField": "time",
276+
"bucketMaxSpanSeconds": 3600,
277+
"bucketRoundingSeconds": 3600
278+
}
279+
}
280+
},
281+
{
282+
"name": "assertCollectionExists",
283+
"object": "testRunner",
284+
"arguments": {
285+
"databaseName": "ts-tests",
286+
"collectionName": "test"
287+
}
288+
}
289+
],
290+
"expectEvents": [
291+
{
292+
"client": "client0",
293+
"events": [
294+
{
295+
"commandStartedEvent": {
296+
"command": {
297+
"drop": "test"
298+
},
299+
"databaseName": "ts-tests"
300+
}
301+
},
302+
{
303+
"commandStartedEvent": {
304+
"command": {
305+
"create": "test",
306+
"timeseries": {
307+
"timeField": "time",
308+
"bucketMaxSpanSeconds": 3600,
309+
"bucketRoundingSeconds": 3600
310+
}
311+
},
312+
"databaseName": "ts-tests"
313+
}
314+
}
315+
]
316+
}
317+
]
253318
}
254319
]
255320
}

specifications/collection-management/tests/timeseries-collection.yml

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -83,21 +83,21 @@ tests:
8383
arguments:
8484
documents: &docs
8585
- {
86-
_id: 1,
87-
time: {
88-
$date: {
89-
$numberLong: "1552949630482"
90-
}
86+
_id: 1,
87+
time: {
88+
$date: {
89+
$numberLong: "1552949630482"
9190
}
9291
}
92+
}
9393
- {
94-
_id: 1,
95-
time: {
96-
$date: {
97-
$numberLong: "1552949630483"
98-
}
94+
_id: 1,
95+
time: {
96+
$date: {
97+
$numberLong: "1552949630483"
9998
}
10099
}
100+
}
101101
- name: find
102102
object: *collection0
103103
arguments:
@@ -127,3 +127,38 @@ tests:
127127
filter: {}
128128
sort: { time: 1 }
129129
databaseName: *database0Name
130+
131+
- description: "createCollection with bucketing options"
132+
runOnRequirements:
133+
- minServerVersion: "6.3"
134+
operations:
135+
- name: dropCollection
136+
object: *database0
137+
arguments:
138+
collection: *collection0Name
139+
- name: createCollection
140+
object: *database0
141+
arguments:
142+
collection: *collection0Name
143+
timeseries: &timeseries1
144+
timeField: "time"
145+
bucketMaxSpanSeconds: 3600
146+
bucketRoundingSeconds: 3600
147+
- name: assertCollectionExists
148+
object: testRunner
149+
arguments:
150+
databaseName: *database0Name
151+
collectionName: *collection0Name
152+
expectEvents:
153+
- client: *client0
154+
events:
155+
- commandStartedEvent:
156+
command:
157+
drop: *collection0Name
158+
databaseName: *database0Name
159+
- commandStartedEvent:
160+
command:
161+
create: *collection0Name
162+
timeseries: *timeseries1
163+
databaseName: *database0Name
164+

src/MongoDB.Driver.Core/TimeSeriesOptions.cs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,24 @@ public class TimeSeriesOptions
2727
private readonly TimeSeriesGranularity? _granularity;
2828
private readonly string _metaField;
2929
private readonly string _timeField;
30+
private readonly int? _bucketMaxSpanSeconds;
31+
private readonly int? _bucketRoundingSeconds;
3032

3133
/// <summary>
3234
/// Initializes a new instance of the <see cref="TimeSeriesOptions"/> class.
3335
/// </summary>
3436
/// <param name="timeField">The name of the top-level field to be used for time.</param>
3537
/// <param name="metaField">The name of the top-level field describing the series upon which related data will be grouped.</param>
36-
/// <param name="granularity">The <see cref="TimeSeriesGranularity"/> for the time series.</param>
37-
public TimeSeriesOptions(string timeField, Optional<string> metaField = default, Optional<TimeSeriesGranularity?> granularity = default)
38+
/// <param name="granularity">The <see cref="TimeSeriesGranularity"/> for the time series. Do not set if using bucketMaxSpanSeconds</param>
39+
/// <param name="bucketMaxSpanSeconds">The maximum time between timestamps in the same bucket.</param>
40+
/// <param name="bucketRoundingSeconds">The interval used to round down the first timestamp when opening a new bucket.</param>
41+
public TimeSeriesOptions(string timeField, Optional<string> metaField = default, Optional<TimeSeriesGranularity?> granularity = default, Optional<int?> bucketMaxSpanSeconds = default, Optional<int?> bucketRoundingSeconds = default)
3842
{
3943
_timeField = Ensure.IsNotNullOrEmpty(timeField, nameof(timeField));
4044
_metaField = metaField.WithDefault(null);
4145
_granularity = granularity.WithDefault(null);
46+
_bucketMaxSpanSeconds = bucketMaxSpanSeconds.WithDefault(null);
47+
_bucketRoundingSeconds = bucketRoundingSeconds.WithDefault(null);
4248
}
4349

4450
/// <summary>
@@ -56,6 +62,16 @@ public TimeSeriesOptions(string timeField, Optional<string> metaField = default,
5662
/// </summary>
5763
public string TimeField => _timeField;
5864

65+
/// <summary>
66+
/// The maximum time between timestamps in the same bucket.
67+
/// </summary>
68+
public int? BucketMaxSpanSeconds => _bucketMaxSpanSeconds;
69+
70+
/// <summary>
71+
/// The interval used to round down the first timestamp when opening a new bucket.
72+
/// </summary>
73+
public int? BucketRoundingSeconds => _bucketRoundingSeconds;
74+
5975
/// <summary>
6076
/// The BSON representation of the time series options.
6177
/// </summary>
@@ -66,7 +82,9 @@ public BsonDocument ToBsonDocument()
6682
{
6783
{ "timeField", _timeField },
6884
{ "metaField", _metaField, _metaField != null },
69-
{ "granularity", () => _granularity.Value.ToString().ToLowerInvariant(), _granularity.HasValue }
85+
{ "granularity", () => _granularity.Value.ToString().ToLowerInvariant(), _granularity.HasValue },
86+
{ "bucketMaxSpanSeconds", _bucketMaxSpanSeconds, _bucketMaxSpanSeconds != null },
87+
{ "bucketRoundingSeconds", _bucketRoundingSeconds, _bucketRoundingSeconds != null }
7088
};
7189
}
7290
}

tests/MongoDB.Driver.Core.Tests/TimeSeriesOptionsTests.cs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,34 @@ public void constructor_with_all_parameters_should_initialize_instance()
5454
const string timeField = "time";
5555
const string metaField = "meta";
5656
const TimeSeriesGranularity granularity = TimeSeriesGranularity.Hours;
57+
const int bucketMaxSpanSeconds = 30;
58+
const int bucketRoundingSeconds = 30;
5759

58-
var result = new TimeSeriesOptions(timeField, metaField, granularity);
60+
var result = new TimeSeriesOptions(timeField, metaField, granularity, bucketMaxSpanSeconds, bucketRoundingSeconds);
5961

6062
result.TimeField.Should().Be(timeField);
6163
result.MetaField.Should().Be(metaField);
6264
result.Granularity.Should().Be(granularity);
65+
result.BucketMaxSpanSeconds.Should().Be(bucketMaxSpanSeconds);
66+
result.BucketRoundingSeconds.Should().Be(bucketRoundingSeconds);
67+
}
68+
69+
[Theory]
70+
[InlineData(true, "{ timeField: 'time' }")]
71+
[InlineData(false, "{ timeField: 'time', metaField: 'meta', granularity: 'hours', bucketMaxSpanSeconds: 30, bucketRoundingSeconds: 30 }")]
72+
public void ToBsonDocument_should_return_expected_result(bool withDefaults, string expected)
73+
{
74+
const string timeField = "time";
75+
const string metaField = "meta";
76+
const TimeSeriesGranularity granularity = TimeSeriesGranularity.Hours;
77+
const int bucketMaxSpanSeconds = 30;
78+
const int bucketRoundingSeconds = 30;
79+
80+
var result = withDefaults
81+
? new TimeSeriesOptions(timeField)
82+
: new TimeSeriesOptions(timeField, metaField, granularity, bucketMaxSpanSeconds, bucketRoundingSeconds);
83+
84+
result.ToBsonDocument().Should().Be(expected);
6385
}
6486
}
6587
}

tests/MongoDB.Driver.Tests/UnifiedTestOperations/UnifiedCreateCollectionOperation.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,16 @@ public IUnifiedEntityTestOperation Build(string targetDatabaseId, BsonDocument a
215215
{
216216
granularity = (TimeSeriesGranularity)Enum.Parse(typeof(TimeSeriesGranularity), granularityValue.AsString, true);
217217
}
218-
timeSeriesOptions = new TimeSeriesOptions(timeField, metaField, granularity);
218+
219+
var bucketMaxSpanSeconds =
220+
timeseries.TryGetValue("bucketMaxSpanSeconds", out var bucketMaxSpanSecondsValue)
221+
? bucketMaxSpanSecondsValue.AsInt32
222+
: (int?)null;
223+
var bucketRoundingSeconds =
224+
timeseries.TryGetValue("bucketRoundingSeconds", out var bucketRoundingSecondsValue)
225+
? bucketRoundingSecondsValue.AsInt32
226+
: (int?)null;
227+
timeSeriesOptions = new TimeSeriesOptions(timeField, metaField, granularity, bucketMaxSpanSeconds, bucketRoundingSeconds);
219228
break;
220229
case "viewOn":
221230
viewOn = argument.Value.AsString;

0 commit comments

Comments
 (0)