@oneuptime/common 10.5.37 → 10.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/AnalyticsModels/ExceptionInstance.ts +1 -0
- package/Models/AnalyticsModels/Log.ts +4 -0
- package/Models/AnalyticsModels/Metric.ts +27 -0
- package/Models/AnalyticsModels/Profile.ts +1 -0
- package/Models/AnalyticsModels/ProfileSample.ts +1 -0
- package/Models/AnalyticsModels/Span.ts +2 -0
- package/Server/Infrastructure/ClickhouseConfig.ts +15 -0
- package/Server/Services/AnalyticsDatabaseService.ts +112 -0
- package/Server/Services/IncidentService.ts +2 -4
- package/Server/Services/LogAggregationService.ts +49 -0
- package/Server/Services/ProfileAggregationService.ts +25 -0
- package/Server/Services/TelemetryAttributeService.ts +20 -0
- package/Server/Types/AnalyticsDatabase/ExistsBy.ts +8 -0
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +55 -30
- package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +52 -0
- package/Types/AnalyticsDatabase/TableColumn.ts +49 -6
- package/build/dist/Models/AnalyticsModels/ExceptionInstance.js +1 -0
- package/build/dist/Models/AnalyticsModels/ExceptionInstance.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Log.js +4 -0
- package/build/dist/Models/AnalyticsModels/Log.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Metric.js +27 -0
- package/build/dist/Models/AnalyticsModels/Metric.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Profile.js +1 -0
- package/build/dist/Models/AnalyticsModels/Profile.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/ProfileSample.js +1 -0
- package/build/dist/Models/AnalyticsModels/ProfileSample.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Span.js +2 -0
- package/build/dist/Models/AnalyticsModels/Span.js.map +1 -1
- package/build/dist/Server/Infrastructure/ClickhouseConfig.js +15 -0
- package/build/dist/Server/Infrastructure/ClickhouseConfig.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +82 -0
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +2 -4
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/LogAggregationService.js +31 -0
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/ProfileAggregationService.js +16 -0
- package/build/dist/Server/Services/ProfileAggregationService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryAttributeService.js +14 -0
- package/build/dist/Server/Services/TelemetryAttributeService.js.map +1 -1
- package/build/dist/Server/Types/AnalyticsDatabase/ExistsBy.js +2 -0
- package/build/dist/Server/Types/AnalyticsDatabase/ExistsBy.js.map +1 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +41 -22
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.js +42 -0
- package/build/dist/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.js.map +1 -1
- package/build/dist/Types/AnalyticsDatabase/TableColumn.js +16 -0
- package/build/dist/Types/AnalyticsDatabase/TableColumn.js.map +1 -1
- package/package.json +1 -1
|
@@ -80,6 +80,7 @@ export default class ExceptionInstance extends AnalyticsBaseModel {
|
|
|
80
80
|
|
|
81
81
|
const serviceTypeColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
|
82
82
|
key: "serviceType",
|
|
83
|
+
isLowCardinality: true,
|
|
83
84
|
title: "Service Type",
|
|
84
85
|
description:
|
|
85
86
|
"Discriminator for serviceId — tells the read side which resource table to dispatch to",
|
|
@@ -81,6 +81,7 @@ export default class Log extends AnalyticsBaseModel {
|
|
|
81
81
|
|
|
82
82
|
const serviceTypeColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
|
83
83
|
key: "serviceType",
|
|
84
|
+
isLowCardinality: true,
|
|
84
85
|
title: "Service Type",
|
|
85
86
|
description:
|
|
86
87
|
"Discriminator for serviceId — tells the read side which resource table to dispatch to",
|
|
@@ -175,6 +176,7 @@ export default class Log extends AnalyticsBaseModel {
|
|
|
175
176
|
|
|
176
177
|
const severityTextColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
|
177
178
|
key: "severityText",
|
|
179
|
+
isLowCardinality: true,
|
|
178
180
|
title: "Severity Text",
|
|
179
181
|
description: "Log Severity Text",
|
|
180
182
|
required: true,
|
|
@@ -241,6 +243,7 @@ export default class Log extends AnalyticsBaseModel {
|
|
|
241
243
|
|
|
242
244
|
const attributesColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
|
243
245
|
key: "attributes",
|
|
246
|
+
codec: { codec: "ZSTD", level: 3 },
|
|
244
247
|
title: "Attributes",
|
|
245
248
|
description: "Attributes",
|
|
246
249
|
required: true,
|
|
@@ -271,6 +274,7 @@ export default class Log extends AnalyticsBaseModel {
|
|
|
271
274
|
|
|
272
275
|
const attributeKeysColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
|
273
276
|
key: "attributeKeys",
|
|
277
|
+
codec: { codec: "ZSTD", level: 3 },
|
|
274
278
|
title: "Attribute Keys",
|
|
275
279
|
description: "Attribute keys extracted from attributes",
|
|
276
280
|
required: true,
|
|
@@ -94,6 +94,7 @@ export default class Metric extends AnalyticsBaseModel {
|
|
|
94
94
|
// this can also be the monitor id or the telemetry service id.
|
|
95
95
|
const serviceTypeColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
|
96
96
|
key: "serviceType",
|
|
97
|
+
isLowCardinality: true,
|
|
97
98
|
title: "Service Type",
|
|
98
99
|
description: "Type of the service that this telemetry belongs to",
|
|
99
100
|
required: false,
|
|
@@ -130,6 +131,7 @@ export default class Metric extends AnalyticsBaseModel {
|
|
|
130
131
|
// add name and description
|
|
131
132
|
const nameColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
|
132
133
|
key: "name",
|
|
134
|
+
codec: { codec: "ZSTD", level: 1 },
|
|
133
135
|
title: "Name",
|
|
134
136
|
description: "Name of the Metric",
|
|
135
137
|
required: true,
|
|
@@ -196,6 +198,7 @@ export default class Metric extends AnalyticsBaseModel {
|
|
|
196
198
|
const metricPointTypeColumn: AnalyticsTableColumn =
|
|
197
199
|
new AnalyticsTableColumn({
|
|
198
200
|
key: "metricPointType",
|
|
201
|
+
isLowCardinality: true,
|
|
199
202
|
title: "Metric Point Type",
|
|
200
203
|
description: "Metric Point Type of this Metric",
|
|
201
204
|
required: false,
|
|
@@ -232,6 +235,7 @@ export default class Metric extends AnalyticsBaseModel {
|
|
|
232
235
|
// this is end time.
|
|
233
236
|
const timeColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
|
234
237
|
key: "time",
|
|
238
|
+
codec: [{ codec: "DoubleDelta" }, { codec: "ZSTD", level: 1 }],
|
|
235
239
|
title: "Time",
|
|
236
240
|
description: "When did the Metric happen?",
|
|
237
241
|
required: true,
|
|
@@ -261,6 +265,7 @@ export default class Metric extends AnalyticsBaseModel {
|
|
|
261
265
|
|
|
262
266
|
const startTimeColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
|
263
267
|
key: "startTime",
|
|
268
|
+
codec: { codec: "ZSTD", level: 1 },
|
|
264
269
|
title: "Start Time",
|
|
265
270
|
description: "When did the Metric happen?",
|
|
266
271
|
required: false,
|
|
@@ -291,6 +296,7 @@ export default class Metric extends AnalyticsBaseModel {
|
|
|
291
296
|
// end time.
|
|
292
297
|
const timeUnixNanoColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
|
293
298
|
key: "timeUnixNano",
|
|
299
|
+
codec: { codec: "ZSTD", level: 1 },
|
|
294
300
|
title: "Time (in Unix Nano)",
|
|
295
301
|
description: "When did the Metric happen?",
|
|
296
302
|
required: true,
|
|
@@ -321,6 +327,7 @@ export default class Metric extends AnalyticsBaseModel {
|
|
|
321
327
|
const startTimeUnixNanoColumn: AnalyticsTableColumn =
|
|
322
328
|
new AnalyticsTableColumn({
|
|
323
329
|
key: "startTimeUnixNano",
|
|
330
|
+
codec: { codec: "ZSTD", level: 1 },
|
|
324
331
|
title: "Start Time (in Unix Nano)",
|
|
325
332
|
description: "When did the Metric happen?",
|
|
326
333
|
required: false,
|
|
@@ -350,6 +357,7 @@ export default class Metric extends AnalyticsBaseModel {
|
|
|
350
357
|
|
|
351
358
|
const attributesColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
|
352
359
|
key: "attributes",
|
|
360
|
+
codec: { codec: "ZSTD", level: 3 },
|
|
353
361
|
title: "Attributes",
|
|
354
362
|
description: "Attributes",
|
|
355
363
|
required: true,
|
|
@@ -380,6 +388,7 @@ export default class Metric extends AnalyticsBaseModel {
|
|
|
380
388
|
|
|
381
389
|
const attributeKeysColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
|
382
390
|
key: "attributeKeys",
|
|
391
|
+
codec: { codec: "ZSTD", level: 3 },
|
|
383
392
|
title: "Attribute Keys",
|
|
384
393
|
description: "Attribute keys extracted from attributes",
|
|
385
394
|
required: true,
|
|
@@ -445,6 +454,7 @@ export default class Metric extends AnalyticsBaseModel {
|
|
|
445
454
|
|
|
446
455
|
const countColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
|
447
456
|
key: "count",
|
|
457
|
+
codec: { codec: "ZSTD", level: 1 },
|
|
448
458
|
title: "Count",
|
|
449
459
|
description: "Count",
|
|
450
460
|
required: false,
|
|
@@ -474,6 +484,7 @@ export default class Metric extends AnalyticsBaseModel {
|
|
|
474
484
|
|
|
475
485
|
const sumColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
|
476
486
|
key: "sum",
|
|
487
|
+
codec: { codec: "ZSTD", level: 1 },
|
|
477
488
|
title: "Sum",
|
|
478
489
|
description: "Sum",
|
|
479
490
|
required: false,
|
|
@@ -503,6 +514,7 @@ export default class Metric extends AnalyticsBaseModel {
|
|
|
503
514
|
|
|
504
515
|
const valueColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
|
505
516
|
key: "value",
|
|
517
|
+
codec: { codec: "ZSTD", level: 1 },
|
|
506
518
|
title: "Value",
|
|
507
519
|
description: "Value",
|
|
508
520
|
required: false,
|
|
@@ -532,6 +544,7 @@ export default class Metric extends AnalyticsBaseModel {
|
|
|
532
544
|
|
|
533
545
|
const minColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
|
534
546
|
key: "min",
|
|
547
|
+
codec: { codec: "ZSTD", level: 1 },
|
|
535
548
|
title: "Min",
|
|
536
549
|
description: "Min",
|
|
537
550
|
required: false,
|
|
@@ -561,6 +574,7 @@ export default class Metric extends AnalyticsBaseModel {
|
|
|
561
574
|
|
|
562
575
|
const maxColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
|
563
576
|
key: "max",
|
|
577
|
+
codec: { codec: "ZSTD", level: 1 },
|
|
564
578
|
title: "Max",
|
|
565
579
|
description: "Max",
|
|
566
580
|
required: false,
|
|
@@ -590,6 +604,7 @@ export default class Metric extends AnalyticsBaseModel {
|
|
|
590
604
|
|
|
591
605
|
const bucketCountsColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
|
592
606
|
key: "bucketCounts",
|
|
607
|
+
codec: { codec: "ZSTD", level: 1 },
|
|
593
608
|
title: "Bucket Counts",
|
|
594
609
|
description: "Bucket Counts",
|
|
595
610
|
required: true,
|
|
@@ -621,6 +636,7 @@ export default class Metric extends AnalyticsBaseModel {
|
|
|
621
636
|
const explicitBoundsColumn: AnalyticsTableColumn = new AnalyticsTableColumn(
|
|
622
637
|
{
|
|
623
638
|
key: "explicitBounds",
|
|
639
|
+
codec: { codec: "ZSTD", level: 1 },
|
|
624
640
|
title: "Explicit Bounds",
|
|
625
641
|
description:
|
|
626
642
|
"Upper bounds (exclusive of the +inf overflow bucket) for each explicit-bucket histogram bucket. Stored as Float64 so sub-integer boundaries (e.g. 0.005, 0.01) survive ingest — the previous Array(Int64) representation silently truncated those to 0.",
|
|
@@ -659,6 +675,7 @@ export default class Metric extends AnalyticsBaseModel {
|
|
|
659
675
|
|
|
660
676
|
const scaleColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
|
661
677
|
key: "scale",
|
|
678
|
+
codec: { codec: "ZSTD", level: 1 },
|
|
662
679
|
title: "Scale",
|
|
663
680
|
description:
|
|
664
681
|
"ExponentialHistogram resolution. base = 2^(2^-scale); bucket index `i` covers (base^i, base^(i+1)].",
|
|
@@ -689,6 +706,7 @@ export default class Metric extends AnalyticsBaseModel {
|
|
|
689
706
|
|
|
690
707
|
const zeroCountColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
|
691
708
|
key: "zeroCount",
|
|
709
|
+
codec: { codec: "ZSTD", level: 1 },
|
|
692
710
|
title: "Zero Count",
|
|
693
711
|
description:
|
|
694
712
|
"ExponentialHistogram count of values within the zero region (|v| <= zeroThreshold).",
|
|
@@ -720,6 +738,7 @@ export default class Metric extends AnalyticsBaseModel {
|
|
|
720
738
|
const positiveOffsetColumn: AnalyticsTableColumn = new AnalyticsTableColumn(
|
|
721
739
|
{
|
|
722
740
|
key: "positiveOffset",
|
|
741
|
+
codec: { codec: "ZSTD", level: 1 },
|
|
723
742
|
title: "Positive Bucket Offset",
|
|
724
743
|
description:
|
|
725
744
|
"Bucket index of the first entry in positiveBucketCounts (ExponentialHistogram).",
|
|
@@ -752,6 +771,7 @@ export default class Metric extends AnalyticsBaseModel {
|
|
|
752
771
|
const positiveBucketCountsColumn: AnalyticsTableColumn =
|
|
753
772
|
new AnalyticsTableColumn({
|
|
754
773
|
key: "positiveBucketCounts",
|
|
774
|
+
codec: { codec: "ZSTD", level: 1 },
|
|
755
775
|
title: "Positive Bucket Counts",
|
|
756
776
|
description:
|
|
757
777
|
"Counts for the positive range of an ExponentialHistogram, indexed from positiveOffset.",
|
|
@@ -784,6 +804,7 @@ export default class Metric extends AnalyticsBaseModel {
|
|
|
784
804
|
const negativeOffsetColumn: AnalyticsTableColumn = new AnalyticsTableColumn(
|
|
785
805
|
{
|
|
786
806
|
key: "negativeOffset",
|
|
807
|
+
codec: { codec: "ZSTD", level: 1 },
|
|
787
808
|
title: "Negative Bucket Offset",
|
|
788
809
|
description:
|
|
789
810
|
"Bucket index of the first entry in negativeBucketCounts (ExponentialHistogram).",
|
|
@@ -816,6 +837,7 @@ export default class Metric extends AnalyticsBaseModel {
|
|
|
816
837
|
const negativeBucketCountsColumn: AnalyticsTableColumn =
|
|
817
838
|
new AnalyticsTableColumn({
|
|
818
839
|
key: "negativeBucketCounts",
|
|
840
|
+
codec: { codec: "ZSTD", level: 1 },
|
|
819
841
|
title: "Negative Bucket Counts",
|
|
820
842
|
description:
|
|
821
843
|
"Counts for the negative range of an ExponentialHistogram, indexed from negativeOffset.",
|
|
@@ -855,6 +877,7 @@ export default class Metric extends AnalyticsBaseModel {
|
|
|
855
877
|
const summaryQuantilesColumn: AnalyticsTableColumn =
|
|
856
878
|
new AnalyticsTableColumn({
|
|
857
879
|
key: "summaryQuantiles",
|
|
880
|
+
codec: { codec: "ZSTD", level: 1 },
|
|
858
881
|
title: "Summary Quantiles",
|
|
859
882
|
description:
|
|
860
883
|
"Quantile percentages in [0,1] for a Summary metric (parallel to summaryValues).",
|
|
@@ -886,6 +909,7 @@ export default class Metric extends AnalyticsBaseModel {
|
|
|
886
909
|
|
|
887
910
|
const summaryValuesColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
|
888
911
|
key: "summaryValues",
|
|
912
|
+
codec: { codec: "ZSTD", level: 1 },
|
|
889
913
|
title: "Summary Values",
|
|
890
914
|
description:
|
|
891
915
|
"Values corresponding to each quantile in summaryQuantiles for a Summary metric.",
|
|
@@ -917,6 +941,7 @@ export default class Metric extends AnalyticsBaseModel {
|
|
|
917
941
|
|
|
918
942
|
const traceIdColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
|
919
943
|
key: "traceId",
|
|
944
|
+
codec: { codec: "ZSTD", level: 1 },
|
|
920
945
|
title: "Trace ID",
|
|
921
946
|
description:
|
|
922
947
|
"Trace ID from an exemplar associated with this metric data point",
|
|
@@ -953,6 +978,7 @@ export default class Metric extends AnalyticsBaseModel {
|
|
|
953
978
|
|
|
954
979
|
const spanIdColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
|
955
980
|
key: "spanId",
|
|
981
|
+
codec: { codec: "ZSTD", level: 1 },
|
|
956
982
|
title: "Span ID",
|
|
957
983
|
description:
|
|
958
984
|
"Span ID from an exemplar associated with this metric data point",
|
|
@@ -989,6 +1015,7 @@ export default class Metric extends AnalyticsBaseModel {
|
|
|
989
1015
|
|
|
990
1016
|
const retentionDateColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
|
991
1017
|
key: "retentionDate",
|
|
1018
|
+
codec: [{ codec: "DoubleDelta" }, { codec: "ZSTD", level: 1 }],
|
|
992
1019
|
title: "Retention Date",
|
|
993
1020
|
description:
|
|
994
1021
|
"Date after which this row is eligible for TTL deletion, computed at ingest time as time + service.retainTelemetryDataForDays",
|
|
@@ -80,6 +80,7 @@ export default class Profile extends AnalyticsBaseModel {
|
|
|
80
80
|
|
|
81
81
|
const serviceTypeColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
|
82
82
|
key: "serviceType",
|
|
83
|
+
isLowCardinality: true,
|
|
83
84
|
title: "Service Type",
|
|
84
85
|
description:
|
|
85
86
|
"Discriminator for serviceId — tells the read side which resource table to dispatch to",
|
|
@@ -80,6 +80,7 @@ export default class ProfileSample extends AnalyticsBaseModel {
|
|
|
80
80
|
|
|
81
81
|
const serviceTypeColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
|
82
82
|
key: "serviceType",
|
|
83
|
+
isLowCardinality: true,
|
|
83
84
|
title: "Service Type",
|
|
84
85
|
description:
|
|
85
86
|
"Discriminator for serviceId — tells the read side which resource table to dispatch to",
|
|
@@ -112,6 +112,7 @@ export default class Span extends AnalyticsBaseModel {
|
|
|
112
112
|
|
|
113
113
|
const serviceTypeColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
|
114
114
|
key: "serviceType",
|
|
115
|
+
isLowCardinality: true,
|
|
115
116
|
title: "Service Type",
|
|
116
117
|
description:
|
|
117
118
|
"Discriminator for serviceId — tells the read side which resource table to dispatch to",
|
|
@@ -664,6 +665,7 @@ export default class Span extends AnalyticsBaseModel {
|
|
|
664
665
|
|
|
665
666
|
const kindColumn: AnalyticsTableColumn = new AnalyticsTableColumn({
|
|
666
667
|
key: "kind",
|
|
668
|
+
isLowCardinality: true,
|
|
667
669
|
title: "Kind",
|
|
668
670
|
description: "Kind of the span",
|
|
669
671
|
required: false,
|
|
@@ -44,6 +44,21 @@ const options: ClickHouseClientConfigOptions = {
|
|
|
44
44
|
* user-facing queries of HTTP sockets.
|
|
45
45
|
*/
|
|
46
46
|
max_open_connections: MaxClickhouseConnections,
|
|
47
|
+
/*
|
|
48
|
+
* Enable HTTP gzip compression in both directions. `request: true`
|
|
49
|
+
* gzips the client request body (large telemetry insert batches) before
|
|
50
|
+
* it goes over the wire; `response: true` asks ClickHouse to gzip query
|
|
51
|
+
* results (the wide log / span / metric JSON result sets dashboards
|
|
52
|
+
* read back). Both cut network bytes several-fold for the JSON payloads
|
|
53
|
+
* OneUptime exchanges, at a small CPU cost that the transfer savings
|
|
54
|
+
* outweigh. Response compression sends `enable_http_compression=1` per
|
|
55
|
+
* request, which requires a non-readonly user — the OneUptime ClickHouse
|
|
56
|
+
* user runs DDL and inserts, so that condition is satisfied.
|
|
57
|
+
*/
|
|
58
|
+
compression: {
|
|
59
|
+
request: true,
|
|
60
|
+
response: true,
|
|
61
|
+
},
|
|
47
62
|
};
|
|
48
63
|
|
|
49
64
|
if (ShouldClickhouseSslEnable && ClickhouseTlsCa) {
|
|
@@ -6,6 +6,7 @@ import ClickhouseDatabase, {
|
|
|
6
6
|
} from "../Infrastructure/ClickhouseDatabase";
|
|
7
7
|
import ClusterKeyAuthorization from "../Middleware/ClusterKeyAuthorization";
|
|
8
8
|
import CountBy from "../Types/AnalyticsDatabase/CountBy";
|
|
9
|
+
import ExistsBy from "../Types/AnalyticsDatabase/ExistsBy";
|
|
9
10
|
import CreateBy from "../Types/AnalyticsDatabase/CreateBy";
|
|
10
11
|
import CreateManyBy from "../Types/AnalyticsDatabase/CreateManyBy";
|
|
11
12
|
import DeleteBy from "../Types/AnalyticsDatabase/DeleteBy";
|
|
@@ -182,6 +183,16 @@ export default class AnalyticsDatabaseService<
|
|
|
182
183
|
dbResult.stream,
|
|
183
184
|
);
|
|
184
185
|
|
|
186
|
+
/*
|
|
187
|
+
* Unwrap LowCardinality(...) first so dictionary-encoded columns
|
|
188
|
+
* (e.g. LowCardinality(String), LowCardinality(Nullable(String)))
|
|
189
|
+
* map back to their logical type instead of falling through to null.
|
|
190
|
+
*/
|
|
191
|
+
if (strResult.includes("LowCardinality(")) {
|
|
192
|
+
const inner: string = strResult.split("LowCardinality(")[1] as string;
|
|
193
|
+
strResult = inner.substring(0, inner.lastIndexOf(")"));
|
|
194
|
+
}
|
|
195
|
+
|
|
185
196
|
// if strResult includes Nullable(type) then extract type.
|
|
186
197
|
|
|
187
198
|
if (strResult.includes("Nullable")) {
|
|
@@ -267,6 +278,46 @@ export default class AnalyticsDatabaseService<
|
|
|
267
278
|
}
|
|
268
279
|
}
|
|
269
280
|
|
|
281
|
+
/**
|
|
282
|
+
* Returns whether at least one row matches the query, without counting
|
|
283
|
+
* every match. Prefer this over `countBy(...).toNumber() === 0` for
|
|
284
|
+
* existence checks: `count()` scans every matching row, whereas this
|
|
285
|
+
* issues `SELECT 1 ... LIMIT 1`, which lets ClickHouse short-circuit at
|
|
286
|
+
* the first matching granule — dramatically cheaper on large tables
|
|
287
|
+
* (Metric / Span / Log).
|
|
288
|
+
*/
|
|
289
|
+
@CaptureSpan()
|
|
290
|
+
public async existsBy(existsBy: ExistsBy<TBaseModel>): Promise<boolean> {
|
|
291
|
+
try {
|
|
292
|
+
const checkReadPermissionType: CheckReadPermissionType<TBaseModel> =
|
|
293
|
+
await ModelPermission.checkReadPermission(
|
|
294
|
+
this.modelType,
|
|
295
|
+
existsBy.query,
|
|
296
|
+
null,
|
|
297
|
+
existsBy.props,
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
existsBy.query = checkReadPermissionType.query;
|
|
301
|
+
|
|
302
|
+
const existsStatement: Statement = this.toExistsStatement(existsBy);
|
|
303
|
+
|
|
304
|
+
const dbResult: ResultSet<"JSON"> =
|
|
305
|
+
await this.executeQuery(existsStatement);
|
|
306
|
+
|
|
307
|
+
const resultInJSON: ResponseJSON<JSONObject> =
|
|
308
|
+
await dbResult.json<JSONObject>();
|
|
309
|
+
|
|
310
|
+
return Boolean(
|
|
311
|
+
resultInJSON.data &&
|
|
312
|
+
Array.isArray(resultInJSON.data) &&
|
|
313
|
+
resultInJSON.data.length > 0,
|
|
314
|
+
);
|
|
315
|
+
} catch (error) {
|
|
316
|
+
await this.onFindError(error as Exception);
|
|
317
|
+
throw this.getException(error as Exception);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
270
321
|
@CaptureSpan()
|
|
271
322
|
public async addColumnInDatabase(
|
|
272
323
|
column: AnalyticsTableColumn,
|
|
@@ -335,6 +386,29 @@ export default class AnalyticsDatabaseService<
|
|
|
335
386
|
return (rows[0]!["compression_codec"] as string) || "";
|
|
336
387
|
}
|
|
337
388
|
|
|
389
|
+
/**
|
|
390
|
+
* The exact ClickHouse type string for a column as stored in the DB
|
|
391
|
+
* (e.g. "String", "Nullable(Int32)", "LowCardinality(Nullable(String))").
|
|
392
|
+
* Returns "" if the column does not exist. Used by migrations that need to
|
|
393
|
+
* re-state a column's type in a MODIFY COLUMN without guessing it.
|
|
394
|
+
*/
|
|
395
|
+
public async getColumnDatabaseType(columnName: string): Promise<string> {
|
|
396
|
+
const tableName: string = this.model.tableName;
|
|
397
|
+
const result: { data: Array<JSONObject> } = await (
|
|
398
|
+
await this.executeQuery(
|
|
399
|
+
`SELECT type FROM system.columns WHERE database = currentDatabase() AND table = '${tableName}' AND name = '${columnName}'`,
|
|
400
|
+
)
|
|
401
|
+
).json();
|
|
402
|
+
|
|
403
|
+
const rows: Array<JSONObject> = result.data || [];
|
|
404
|
+
|
|
405
|
+
if (rows.length === 0) {
|
|
406
|
+
return "";
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return (rows[0]!["type"] as string) || "";
|
|
410
|
+
}
|
|
411
|
+
|
|
338
412
|
public async setColumnCodecIfNotSet(data: {
|
|
339
413
|
columnName: string;
|
|
340
414
|
columnType: string;
|
|
@@ -841,6 +915,44 @@ export default class AnalyticsDatabaseService<
|
|
|
841
915
|
return statement;
|
|
842
916
|
}
|
|
843
917
|
|
|
918
|
+
public toExistsStatement(existsBy: ExistsBy<TBaseModel>): Statement {
|
|
919
|
+
if (!this.database) {
|
|
920
|
+
this.useDefaultDatabase();
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
const databaseName: string = this.database.getDatasourceOptions().database!;
|
|
924
|
+
|
|
925
|
+
const whereStatement: Statement = this.statementGenerator.toWhereStatement(
|
|
926
|
+
existsBy.query,
|
|
927
|
+
);
|
|
928
|
+
|
|
929
|
+
/*
|
|
930
|
+
* `SELECT 1 ... LIMIT 1` so ClickHouse stops at the first matching
|
|
931
|
+
* row instead of scanning every match like count() does. The
|
|
932
|
+
* max_execution_time cap is defense in depth; unlike the count and
|
|
933
|
+
* find statements we deliberately do NOT set timeout_overflow_mode
|
|
934
|
+
* = 'break' here, because a partial (empty) result would be read as
|
|
935
|
+
* "does not exist" — a false negative that could, for example, let a
|
|
936
|
+
* caller insert a duplicate. A LIMIT 1 over the sort key never gets
|
|
937
|
+
* near this cap in practice; if it ever did, throwing is the safe
|
|
938
|
+
* outcome.
|
|
939
|
+
*/
|
|
940
|
+
/* eslint-disable prettier/prettier */
|
|
941
|
+
const statement: Statement = SQL`
|
|
942
|
+
SELECT 1 as existsFlag
|
|
943
|
+
FROM ${databaseName}.${this.model.tableName}
|
|
944
|
+
WHERE TRUE `.append(whereStatement);
|
|
945
|
+
|
|
946
|
+
statement.append(SQL` LIMIT 1`);
|
|
947
|
+
|
|
948
|
+
statement.append(" SETTINGS max_execution_time = 45");
|
|
949
|
+
|
|
950
|
+
logger.debug(`${this.model.tableName} Exists Statement`, { tableName: this.model.tableName } as LogAttributes);
|
|
951
|
+
logger.debug(statement, { tableName: this.model.tableName } as LogAttributes);
|
|
952
|
+
|
|
953
|
+
return statement;
|
|
954
|
+
}
|
|
955
|
+
|
|
844
956
|
public toAggregateStatement(aggregateBy: AggregateBy<TBaseModel>): {
|
|
845
957
|
statement: Statement;
|
|
846
958
|
columns: Array<string>;
|
|
@@ -2796,20 +2796,18 @@ ${incidentSeverity.name}
|
|
|
2796
2796
|
* re-emitting, the dashboard Sum stays equal to the true count of
|
|
2797
2797
|
* distinct incidents.
|
|
2798
2798
|
*/
|
|
2799
|
-
const
|
|
2799
|
+
const incidentCountMetricExists: boolean = await MetricService.existsBy({
|
|
2800
2800
|
query: {
|
|
2801
2801
|
projectId: incident.projectId,
|
|
2802
2802
|
serviceId: data.incidentId,
|
|
2803
2803
|
name: IncidentMetricType.IncidentCount,
|
|
2804
2804
|
},
|
|
2805
|
-
skip: 0,
|
|
2806
|
-
limit: 1,
|
|
2807
2805
|
props: {
|
|
2808
2806
|
isRoot: true,
|
|
2809
2807
|
},
|
|
2810
2808
|
});
|
|
2811
2809
|
|
|
2812
|
-
if (
|
|
2810
|
+
if (!incidentCountMetricExists) {
|
|
2813
2811
|
const incidentCountMetric: Metric = new Metric();
|
|
2814
2812
|
|
|
2815
2813
|
incidentCountMetric.projectId = incident.projectId;
|
|
@@ -524,6 +524,15 @@ export class LogAggregationService {
|
|
|
524
524
|
|
|
525
525
|
statement.append(" ORDER BY bucket ASC");
|
|
526
526
|
|
|
527
|
+
/*
|
|
528
|
+
* Defense in depth: cap runtime below the client's 58s request_timeout
|
|
529
|
+
* (matches the histogram / facet paths above). 'break' returns partial
|
|
530
|
+
* aggregated results rather than holding a pool connection.
|
|
531
|
+
*/
|
|
532
|
+
statement.append(
|
|
533
|
+
" SETTINGS max_execution_time = 45, timeout_overflow_mode = 'break'",
|
|
534
|
+
);
|
|
535
|
+
|
|
527
536
|
return statement;
|
|
528
537
|
}
|
|
529
538
|
|
|
@@ -591,6 +600,14 @@ export class LogAggregationService {
|
|
|
591
600
|
}}`,
|
|
592
601
|
);
|
|
593
602
|
|
|
603
|
+
/*
|
|
604
|
+
* Cap runtime below the client's 58s request_timeout; 'break' returns
|
|
605
|
+
* partial results (matches the histogram / facet paths).
|
|
606
|
+
*/
|
|
607
|
+
statement.append(
|
|
608
|
+
" SETTINGS max_execution_time = 45, timeout_overflow_mode = 'break'",
|
|
609
|
+
);
|
|
610
|
+
|
|
594
611
|
return statement;
|
|
595
612
|
}
|
|
596
613
|
|
|
@@ -647,6 +664,14 @@ export class LogAggregationService {
|
|
|
647
664
|
}}`,
|
|
648
665
|
);
|
|
649
666
|
|
|
667
|
+
/*
|
|
668
|
+
* Cap runtime below the client's 58s request_timeout; 'break' returns
|
|
669
|
+
* partial results (matches the histogram / facet paths).
|
|
670
|
+
*/
|
|
671
|
+
statement.append(
|
|
672
|
+
" SETTINGS max_execution_time = 45, timeout_overflow_mode = 'break'",
|
|
673
|
+
);
|
|
674
|
+
|
|
650
675
|
return statement;
|
|
651
676
|
}
|
|
652
677
|
|
|
@@ -784,6 +809,14 @@ export class LogAggregationService {
|
|
|
784
809
|
}}`,
|
|
785
810
|
);
|
|
786
811
|
|
|
812
|
+
/*
|
|
813
|
+
* Cap runtime below the client's 58s request_timeout; 'break' returns
|
|
814
|
+
* partial rows rather than holding a pool connection on a large export.
|
|
815
|
+
*/
|
|
816
|
+
statement.append(
|
|
817
|
+
" SETTINGS max_execution_time = 45, timeout_overflow_mode = 'break'",
|
|
818
|
+
);
|
|
819
|
+
|
|
787
820
|
const dbResult: Results = await LogDatabaseService.executeQuery(statement);
|
|
788
821
|
const response: DbJSONResponse = await dbResult.json<{
|
|
789
822
|
data?: Array<JSONObject>;
|
|
@@ -926,6 +959,14 @@ export class LogAggregationService {
|
|
|
926
959
|
|
|
927
960
|
LogAggregationService.appendCommonFilters(totalStatement, request);
|
|
928
961
|
|
|
962
|
+
/*
|
|
963
|
+
* Cap the count scan below the client's 58s request_timeout; 'break'
|
|
964
|
+
* returns a partial (lower-bound) count, acceptable for an estimate.
|
|
965
|
+
*/
|
|
966
|
+
totalStatement.append(
|
|
967
|
+
" SETTINGS max_execution_time = 45, timeout_overflow_mode = 'break'",
|
|
968
|
+
);
|
|
969
|
+
|
|
929
970
|
// Get matching count using the filter query as body search
|
|
930
971
|
const matchStatement: Statement = SQL`
|
|
931
972
|
SELECT count() AS cnt
|
|
@@ -949,6 +990,14 @@ export class LogAggregationService {
|
|
|
949
990
|
bodySearchText: request.filterQuery,
|
|
950
991
|
});
|
|
951
992
|
|
|
993
|
+
/*
|
|
994
|
+
* Cap the count scan below the client's 58s request_timeout; 'break'
|
|
995
|
+
* returns a partial (lower-bound) count, acceptable for an estimate.
|
|
996
|
+
*/
|
|
997
|
+
matchStatement.append(
|
|
998
|
+
" SETTINGS max_execution_time = 45, timeout_overflow_mode = 'break'",
|
|
999
|
+
);
|
|
1000
|
+
|
|
952
1001
|
const [totalResult, matchResult] = await Promise.all([
|
|
953
1002
|
LogDatabaseService.executeQuery(totalStatement),
|
|
954
1003
|
LogDatabaseService.executeQuery(matchStatement),
|
|
@@ -528,6 +528,15 @@ export class ProfileAggregationService {
|
|
|
528
528
|
}}`,
|
|
529
529
|
);
|
|
530
530
|
|
|
531
|
+
/*
|
|
532
|
+
* Cap runtime below the client's 58s request_timeout; a flamegraph
|
|
533
|
+
* over a busy project can pull MAX_SAMPLE_FETCH raw samples. 'break'
|
|
534
|
+
* yields a partial flamegraph rather than holding a pool connection.
|
|
535
|
+
*/
|
|
536
|
+
statement.append(
|
|
537
|
+
" SETTINGS max_execution_time = 45, timeout_overflow_mode = 'break'",
|
|
538
|
+
);
|
|
539
|
+
|
|
531
540
|
return statement;
|
|
532
541
|
}
|
|
533
542
|
|
|
@@ -563,6 +572,14 @@ export class ProfileAggregationService {
|
|
|
563
572
|
}}`,
|
|
564
573
|
);
|
|
565
574
|
|
|
575
|
+
/*
|
|
576
|
+
* Cap runtime below the client's 58s request_timeout; 'break' yields
|
|
577
|
+
* a partial function list rather than holding a pool connection.
|
|
578
|
+
*/
|
|
579
|
+
statement.append(
|
|
580
|
+
" SETTINGS max_execution_time = 45, timeout_overflow_mode = 'break'",
|
|
581
|
+
);
|
|
582
|
+
|
|
566
583
|
return statement;
|
|
567
584
|
}
|
|
568
585
|
|
|
@@ -622,6 +639,14 @@ export class ProfileAggregationService {
|
|
|
622
639
|
LIMIT 10000`,
|
|
623
640
|
);
|
|
624
641
|
|
|
642
|
+
/*
|
|
643
|
+
* Cap runtime below the client's 58s request_timeout; 'break' yields
|
|
644
|
+
* partial service activity rather than holding a pool connection.
|
|
645
|
+
*/
|
|
646
|
+
statement.append(
|
|
647
|
+
" SETTINGS max_execution_time = 45, timeout_overflow_mode = 'break'",
|
|
648
|
+
);
|
|
649
|
+
|
|
625
650
|
return statement;
|
|
626
651
|
}
|
|
627
652
|
|
|
@@ -307,6 +307,16 @@ export class TelemetryAttributeService {
|
|
|
307
307
|
);
|
|
308
308
|
}
|
|
309
309
|
|
|
310
|
+
/*
|
|
311
|
+
* Cap runtime below the ClickHouse client's 58s request_timeout so a
|
|
312
|
+
* slow scan on a large project can't hold a pool connection for the
|
|
313
|
+
* full timeout. 'break' returns partial keys, which is fine for an
|
|
314
|
+
* attribute-key picker (matches the findBy / aggregation read paths).
|
|
315
|
+
*/
|
|
316
|
+
statement.append(
|
|
317
|
+
" SETTINGS max_execution_time = 45, timeout_overflow_mode = 'break'",
|
|
318
|
+
);
|
|
319
|
+
|
|
310
320
|
return statement;
|
|
311
321
|
}
|
|
312
322
|
|
|
@@ -451,6 +461,16 @@ export class TelemetryAttributeService {
|
|
|
451
461
|
}}`,
|
|
452
462
|
);
|
|
453
463
|
|
|
464
|
+
/*
|
|
465
|
+
* Cap runtime below the client's 58s request_timeout. This value
|
|
466
|
+
* autocomplete runs per keystroke and scans a Map subscript, so a
|
|
467
|
+
* pathological key/project must not hold a pool connection; 'break'
|
|
468
|
+
* returns partial values, acceptable for autocomplete.
|
|
469
|
+
*/
|
|
470
|
+
statement.append(
|
|
471
|
+
" SETTINGS max_execution_time = 45, timeout_overflow_mode = 'break'",
|
|
472
|
+
);
|
|
473
|
+
|
|
454
474
|
return statement;
|
|
455
475
|
}
|
|
456
476
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import Query from "./Query";
|
|
2
|
+
import AnalyticsBaseModel from "../../../Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel";
|
|
3
|
+
import DatabaseCommonInteractionProps from "../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
4
|
+
|
|
5
|
+
export default interface ExistsBy<TBaseModel extends AnalyticsBaseModel> {
|
|
6
|
+
query: Query<TBaseModel>;
|
|
7
|
+
props: DatabaseCommonInteractionProps;
|
|
8
|
+
}
|