@oneuptime/common 11.5.12 → 11.6.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.
Files changed (193) hide show
  1. package/Models/DatabaseModels/AIInsight.ts +30 -0
  2. package/Models/DatabaseModels/EnterpriseLicense.ts +21 -0
  3. package/Models/DatabaseModels/EnterpriseLicenseInstance.ts +25 -0
  4. package/Models/DatabaseModels/GlobalConfig.ts +76 -0
  5. package/Models/DatabaseModels/Project.ts +30 -0
  6. package/Models/DatabaseModels/TelemetryException.ts +128 -0
  7. package/Server/API/AIAgentDataAPI.ts +129 -5
  8. package/Server/API/AIInvestigationAPI.ts +87 -0
  9. package/Server/API/EnterpriseLicenseAPI.ts +57 -0
  10. package/Server/API/GlobalConfigAPI.ts +62 -1
  11. package/Server/EnvironmentConfig.ts +22 -0
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1784640000000-AddExceptionTriageAndPrivacyColumns.ts +56 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1784659816363-AddInstanceVersionAndLatestReleaseColumns.ts +38 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1784705487674-AddEnterpriseLicenseEvaluationColumns.ts +25 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  16. package/Server/Services/AIRunService.ts +8 -0
  17. package/Server/Services/AnalyticsDatabaseService.ts +48 -45
  18. package/Server/Services/DatabaseService.ts +66 -1
  19. package/Server/Services/TelemetryExceptionService.ts +320 -24
  20. package/Server/Services/WorkflowService.ts +100 -39
  21. package/Server/Types/Workflow/Components/Schedule.ts +57 -32
  22. package/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.ts +15 -4
  23. package/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.ts +15 -4
  24. package/Server/Utils/AI/SRE/Insights/InsightScanner.ts +222 -24
  25. package/Server/Utils/AI/SRE/Insights/InsightTriageRunner.ts +225 -9
  26. package/Server/Utils/AI/SRE/SubjectCodeFixRun.ts +39 -0
  27. package/Server/Utils/AI/SRE/TelemetryImprovementTaskTrigger.ts +114 -0
  28. package/Server/Utils/AnalyticsDatabase/InsertDedupContext.ts +59 -0
  29. package/Server/Utils/Express.ts +13 -0
  30. package/Server/Utils/Telemetry/ExceptionSanitizer.ts +222 -0
  31. package/Server/Utils/Telemetry/TelemetryFanInWriter.ts +799 -0
  32. package/Server/Utils/Telemetry/TelemetryWriterClient.ts +263 -0
  33. package/Server/Utils/Telemetry/TelemetryWriterServer.ts +240 -0
  34. package/Server/Utils/Telemetry/TelemetryWriterShedMetrics.ts +93 -0
  35. package/Tests/Server/Services/DatabaseServiceSanitizeUpdateData.test.ts +232 -0
  36. package/Tests/Server/Services/TelemetryExceptionCodeFixRun.test.ts +205 -1
  37. package/Tests/Server/Services/UpdateOneByIdKeepsRowId.test.ts +107 -0
  38. package/Tests/Server/Services/WorkflowService.test.ts +308 -0
  39. package/Tests/Server/Utils/AI/Insights/InsightScanner.test.ts +150 -141
  40. package/Tests/Server/Utils/AI/Insights/InsightTriageRunner.test.ts +277 -1
  41. package/Tests/Server/Utils/Attribution.test.ts +203 -0
  42. package/Tests/Server/Utils/ExceptionSanitizer.test.ts +70 -0
  43. package/Tests/Server/Utils/InsightTriageClassification.test.ts +57 -0
  44. package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +183 -0
  45. package/Tests/Server/Utils/Marketing/GoogleAds.test.ts +340 -0
  46. package/Tests/Server/Utils/Monitor/Criteria/ExceptionMonitorCriteria.test.ts +87 -0
  47. package/Tests/Server/Utils/Monitor/Criteria/LogMonitorCriteria.test.ts +165 -0
  48. package/Tests/Server/Utils/Monitor/Criteria/TraceMonitorCriteria.test.ts +86 -0
  49. package/Tests/Server/Utils/Telemetry/TelemetryFanInWriter.test.ts +1565 -0
  50. package/Tests/Server/Utils/Telemetry/TelemetryWriterClient.test.ts +313 -0
  51. package/Tests/Server/Utils/Telemetry/TelemetryWriterServer.test.ts +270 -0
  52. package/Tests/Server/Utils/Telemetry/TelemetryWriterShedMetrics.test.ts +127 -0
  53. package/Tests/Server/Utils/TelemetryImprovementTaskTrigger.test.ts +141 -0
  54. package/Tests/Types/AI/CodeFixTaskType.test.ts +3 -0
  55. package/Tests/Types/Monitor/MonitorCriteriaMetricVariables.test.ts +228 -0
  56. package/Tests/Types/Monitor/MonitorStepDefaultTelemetryConfig.test.ts +169 -0
  57. package/Tests/Types/Monitor/MonitorStepExceptionMonitor.test.ts +289 -0
  58. package/Tests/Types/Monitor/MonitorStepLogMonitor.test.ts +231 -0
  59. package/Tests/Types/Monitor/MonitorStepMetricViewConfigUtil.test.ts +245 -0
  60. package/Tests/Types/Monitor/MonitorStepNetworkDeviceMonitor.test.ts +155 -0
  61. package/Tests/Types/Monitor/MonitorStepTelemetrySerialization.test.ts +141 -0
  62. package/Tests/Types/Monitor/MonitorStepTraceMonitor.test.ts +137 -0
  63. package/Tests/Types/Workspace/NotificationRules/NotificationRuleCondition.test.ts +311 -0
  64. package/Tests/UI/Components/ModelTableSelectFromColumns.test.ts +259 -0
  65. package/Tests/UI/Utils/Breadcrumb/BreadcrumbTrailResolver.test.ts +461 -0
  66. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +2852 -0
  67. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +758 -0
  68. package/Tests/UI/Utils/NotificationMethodUtil.test.ts +564 -0
  69. package/Tests/Utils/Array.test.ts +156 -0
  70. package/Tests/Utils/CronTab.test.ts +199 -0
  71. package/Tests/Utils/ModelImportExport.test.ts +101 -0
  72. package/Tests/Utils/Number.test.ts +179 -0
  73. package/Tests/Utils/VersionUtil.test.ts +348 -0
  74. package/Tests/jest.setup.ts +1 -0
  75. package/Types/AI/CodeFixTaskContext.ts +6 -0
  76. package/Types/AI/CodeFixTaskType.ts +38 -1
  77. package/Types/AI/ExceptionAIClassification.ts +27 -0
  78. package/Types/EnterpriseLicense/EnterpriseLicenseInstanceSummary.ts +6 -0
  79. package/Types/Log/LogScrubPatternType.ts +7 -0
  80. package/Types/Monitor/MonitorStep.ts +24 -4
  81. package/Types/Monitor/MonitorStepMetricViewConfigUtil.ts +105 -0
  82. package/Types/Trace/TraceScrubPatternType.ts +7 -0
  83. package/UI/Components/EditionLabel/EditionLabel.tsx +445 -12
  84. package/UI/Components/ModelTable/BaseModelTable.tsx +15 -46
  85. package/UI/Components/ModelTable/SelectFromColumns.ts +129 -0
  86. package/UI/Components/Workflow/ArgumentsForm.tsx +68 -30
  87. package/UI/Components/Workflow/CronScheduleField.tsx +411 -0
  88. package/UI/Utils/Breadcrumb/BreadcrumbTrailResolver.ts +176 -0
  89. package/UI/Utils/NotificationMethodUtil.ts +310 -0
  90. package/Utils/CronTab.ts +823 -0
  91. package/Utils/ModelImportExport.ts +21 -6
  92. package/Utils/VersionUtil.ts +260 -0
  93. package/build/dist/Models/DatabaseModels/AIInsight.js +31 -0
  94. package/build/dist/Models/DatabaseModels/AIInsight.js.map +1 -1
  95. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js +22 -0
  96. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js.map +1 -1
  97. package/build/dist/Models/DatabaseModels/EnterpriseLicenseInstance.js +26 -0
  98. package/build/dist/Models/DatabaseModels/EnterpriseLicenseInstance.js.map +1 -1
  99. package/build/dist/Models/DatabaseModels/GlobalConfig.js +81 -0
  100. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  101. package/build/dist/Models/DatabaseModels/Project.js +31 -0
  102. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  103. package/build/dist/Models/DatabaseModels/TelemetryException.js +134 -0
  104. package/build/dist/Models/DatabaseModels/TelemetryException.js.map +1 -1
  105. package/build/dist/Server/API/AIAgentDataAPI.js +97 -8
  106. package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
  107. package/build/dist/Server/API/AIInvestigationAPI.js +55 -0
  108. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  109. package/build/dist/Server/API/EnterpriseLicenseAPI.js +47 -0
  110. package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +1 -1
  111. package/build/dist/Server/API/GlobalConfigAPI.js +52 -1
  112. package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
  113. package/build/dist/Server/EnvironmentConfig.js +17 -0
  114. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  115. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784640000000-AddExceptionTriageAndPrivacyColumns.js +33 -0
  116. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784640000000-AddExceptionTriageAndPrivacyColumns.js.map +1 -0
  117. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784659816363-AddInstanceVersionAndLatestReleaseColumns.js +18 -0
  118. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784659816363-AddInstanceVersionAndLatestReleaseColumns.js.map +1 -0
  119. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784705487674-AddEnterpriseLicenseEvaluationColumns.js +14 -0
  120. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784705487674-AddEnterpriseLicenseEvaluationColumns.js.map +1 -0
  121. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  122. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  123. package/build/dist/Server/Services/AIRunService.js +9 -2
  124. package/build/dist/Server/Services/AIRunService.js.map +1 -1
  125. package/build/dist/Server/Services/AnalyticsDatabaseService.js +44 -16
  126. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  127. package/build/dist/Server/Services/DatabaseService.js +53 -1
  128. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  129. package/build/dist/Server/Services/TelemetryExceptionService.js +263 -22
  130. package/build/dist/Server/Services/TelemetryExceptionService.js.map +1 -1
  131. package/build/dist/Server/Services/WorkflowService.js +80 -30
  132. package/build/dist/Server/Services/WorkflowService.js.map +1 -1
  133. package/build/dist/Server/Types/Workflow/Components/Schedule.js +44 -19
  134. package/build/dist/Server/Types/Workflow/Components/Schedule.js.map +1 -1
  135. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js +12 -4
  136. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js.map +1 -1
  137. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js +12 -4
  138. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js.map +1 -1
  139. package/build/dist/Server/Utils/AI/SRE/Insights/InsightScanner.js +193 -22
  140. package/build/dist/Server/Utils/AI/SRE/Insights/InsightScanner.js.map +1 -1
  141. package/build/dist/Server/Utils/AI/SRE/Insights/InsightTriageRunner.js +178 -9
  142. package/build/dist/Server/Utils/AI/SRE/Insights/InsightTriageRunner.js.map +1 -1
  143. package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js +37 -0
  144. package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js.map +1 -1
  145. package/build/dist/Server/Utils/AI/SRE/TelemetryImprovementTaskTrigger.js +98 -0
  146. package/build/dist/Server/Utils/AI/SRE/TelemetryImprovementTaskTrigger.js.map +1 -0
  147. package/build/dist/Server/Utils/AnalyticsDatabase/InsertDedupContext.js +24 -0
  148. package/build/dist/Server/Utils/AnalyticsDatabase/InsertDedupContext.js.map +1 -0
  149. package/build/dist/Server/Utils/Express.js +12 -0
  150. package/build/dist/Server/Utils/Express.js.map +1 -1
  151. package/build/dist/Server/Utils/Telemetry/ExceptionSanitizer.js +149 -0
  152. package/build/dist/Server/Utils/Telemetry/ExceptionSanitizer.js.map +1 -0
  153. package/build/dist/Server/Utils/Telemetry/TelemetryFanInWriter.js +496 -0
  154. package/build/dist/Server/Utils/Telemetry/TelemetryFanInWriter.js.map +1 -0
  155. package/build/dist/Server/Utils/Telemetry/TelemetryWriterClient.js +165 -0
  156. package/build/dist/Server/Utils/Telemetry/TelemetryWriterClient.js.map +1 -0
  157. package/build/dist/Server/Utils/Telemetry/TelemetryWriterServer.js +136 -0
  158. package/build/dist/Server/Utils/Telemetry/TelemetryWriterServer.js.map +1 -0
  159. package/build/dist/Server/Utils/Telemetry/TelemetryWriterShedMetrics.js +76 -0
  160. package/build/dist/Server/Utils/Telemetry/TelemetryWriterShedMetrics.js.map +1 -0
  161. package/build/dist/Types/AI/CodeFixTaskType.js +38 -1
  162. package/build/dist/Types/AI/CodeFixTaskType.js.map +1 -1
  163. package/build/dist/Types/AI/ExceptionAIClassification.js +28 -0
  164. package/build/dist/Types/AI/ExceptionAIClassification.js.map +1 -0
  165. package/build/dist/Types/Log/LogScrubPatternType.js +7 -0
  166. package/build/dist/Types/Log/LogScrubPatternType.js.map +1 -1
  167. package/build/dist/Types/Monitor/MonitorStep.js +20 -4
  168. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  169. package/build/dist/Types/Monitor/MonitorStepMetricViewConfigUtil.js +73 -0
  170. package/build/dist/Types/Monitor/MonitorStepMetricViewConfigUtil.js.map +1 -0
  171. package/build/dist/Types/Trace/TraceScrubPatternType.js +7 -0
  172. package/build/dist/Types/Trace/TraceScrubPatternType.js.map +1 -1
  173. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +261 -13
  174. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  175. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +12 -37
  176. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  177. package/build/dist/UI/Components/ModelTable/SelectFromColumns.js +82 -0
  178. package/build/dist/UI/Components/ModelTable/SelectFromColumns.js.map +1 -0
  179. package/build/dist/UI/Components/Workflow/ArgumentsForm.js +30 -6
  180. package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
  181. package/build/dist/UI/Components/Workflow/CronScheduleField.js +209 -0
  182. package/build/dist/UI/Components/Workflow/CronScheduleField.js.map +1 -0
  183. package/build/dist/UI/Utils/Breadcrumb/BreadcrumbTrailResolver.js +129 -0
  184. package/build/dist/UI/Utils/Breadcrumb/BreadcrumbTrailResolver.js.map +1 -0
  185. package/build/dist/UI/Utils/NotificationMethodUtil.js +189 -0
  186. package/build/dist/UI/Utils/NotificationMethodUtil.js.map +1 -0
  187. package/build/dist/Utils/CronTab.js +609 -0
  188. package/build/dist/Utils/CronTab.js.map +1 -0
  189. package/build/dist/Utils/ModelImportExport.js +20 -6
  190. package/build/dist/Utils/ModelImportExport.js.map +1 -1
  191. package/build/dist/Utils/VersionUtil.js +193 -0
  192. package/build/dist/Utils/VersionUtil.js.map +1 -0
  193. package/package.json +1 -1
@@ -0,0 +1,496 @@
1
+ import logger from "../Logger";
2
+ import Sleep from "../../../Types/Sleep";
3
+ import UUID from "../../../Utils/UUID";
4
+ import GracefulShutdown, { ShutdownPriority } from "../GracefulShutdown";
5
+ import { nextInsertDedupToken } from "../AnalyticsDatabase/InsertDedupContext";
6
+ import { ClickHouseError } from "@clickhouse/client";
7
+ /*
8
+ * Marker for transient failures raised by a pluggable insert transport
9
+ * (e.g. the remote telemetry-writer client): overload shedding (HTTP 429),
10
+ * writer-tier unavailability (503), or an ambiguous network failure. The
11
+ * writer retries these with the SAME dedup token, so an ambiguous failure
12
+ * whose rows actually landed never double-writes.
13
+ */
14
+ export class TransientInsertError extends Error {
15
+ constructor(message) {
16
+ super(message);
17
+ this.name = "TransientInsertError";
18
+ }
19
+ }
20
+ export class FanInInsertError extends Error {
21
+ constructor(data) {
22
+ const causeMessage = data.cause instanceof Error ? data.cause.message : String(data.cause);
23
+ super(`Fan-in insert into ${data.tableName} failed after ${data.attempts} attempt(s): ${causeMessage}`);
24
+ this.name = "FanInInsertError";
25
+ this.causeError = data.cause;
26
+ if (data.cause instanceof Error && data.cause.stack) {
27
+ this.stack = `${this.stack}\nCaused by: ${data.cause.stack}`;
28
+ }
29
+ }
30
+ }
31
+ /*
32
+ * ClickHouse server error codes that indicate transient overload or an
33
+ * ambiguous outcome. All are safe to retry here because every insert
34
+ * carries a dedup token that is stable across the writer's retries: a
35
+ * retry of a block that actually landed is dropped server-side.
36
+ * 202 TOO_MANY_SIMULTANEOUS_QUERIES, 209 SOCKET_TIMEOUT,
37
+ * 210 NETWORK_ERROR, 241 MEMORY_LIMIT_EXCEEDED, 252 TOO_MANY_PARTS,
38
+ * 319 UNKNOWN_STATUS_OF_INSERT
39
+ * NOTE: the client exposes `code` as a STRING (e.g. "202").
40
+ */
41
+ const RETRYABLE_CLICKHOUSE_CODES = new Set([
42
+ "202",
43
+ "209",
44
+ "210",
45
+ "241",
46
+ "252",
47
+ "319",
48
+ ]);
49
+ const NUMERIC_ERROR_CODE_REGEX = /^\d+$/;
50
+ const RETRYABLE_SOCKET_CODES = new Set([
51
+ "ECONNRESET",
52
+ "ECONNREFUSED",
53
+ "ECONNABORTED",
54
+ "ETIMEDOUT",
55
+ "EPIPE",
56
+ "EAI_AGAIN",
57
+ ]);
58
+ export function isRetryableInsertError(err) {
59
+ if (err instanceof ClickHouseError) {
60
+ return RETRYABLE_CLICKHOUSE_CODES.has(err.code);
61
+ }
62
+ if (err instanceof TransientInsertError) {
63
+ return true;
64
+ }
65
+ if (err instanceof Error) {
66
+ // Duck-typed TransientInsertError (duplicated module instances).
67
+ if (err.name === "TransientInsertError") {
68
+ return true;
69
+ }
70
+ const code = err["code"];
71
+ if (typeof code === "string") {
72
+ if (RETRYABLE_SOCKET_CODES.has(code)) {
73
+ return true;
74
+ }
75
+ // Duck-typed ClickHouseError (in case of duplicated package instances).
76
+ if (NUMERIC_ERROR_CODE_REGEX.test(code)) {
77
+ return RETRYABLE_CLICKHOUSE_CODES.has(code);
78
+ }
79
+ }
80
+ /*
81
+ * @clickhouse/client surfaces its socket-idle request_timeout as a plain
82
+ * Error("Timeout error."), with no code. The outcome is ambiguous (the
83
+ * body may have been received), which is exactly what the per-batch dedup
84
+ * token exists for — retry.
85
+ */
86
+ if (err.message.includes("Timeout error") ||
87
+ err.message.includes("socket hang up")) {
88
+ return true;
89
+ }
90
+ }
91
+ return false;
92
+ }
93
+ const readPositiveInt = (envKey, defaultValue) => {
94
+ const raw = process.env[envKey];
95
+ if (!raw) {
96
+ return defaultValue;
97
+ }
98
+ const parsed = parseInt(raw, 10);
99
+ return !isNaN(parsed) && parsed > 0 ? parsed : defaultValue;
100
+ };
101
+ export function readFanInWriterOptionsFromEnv() {
102
+ return {
103
+ maxBatchRows: readPositiveInt("TELEMETRY_FANIN_MAX_BATCH_ROWS", 100000),
104
+ maxWaitMs: readPositiveInt("TELEMETRY_FANIN_MAX_WAIT_MS", 5000),
105
+ maxConcurrentInserts: readPositiveInt("TELEMETRY_FANIN_MAX_CONCURRENT_INSERTS", 4),
106
+ /*
107
+ * Must stay comfortably above maxBatchRows: if the two are equal, the
108
+ * instant a full batch is cut every submitter blocks until that whole
109
+ * batch finishes inserting — no pipelining. 2x allows one batch in
110
+ * flight while the next fills.
111
+ */
112
+ maxPendingRows: readPositiveInt("TELEMETRY_FANIN_MAX_PENDING_ROWS", 200000),
113
+ retryMaxAttempts: readPositiveInt("TELEMETRY_FANIN_RETRY_MAX_ATTEMPTS", 6),
114
+ retryBaseDelayMs: readPositiveInt("TELEMETRY_FANIN_RETRY_BASE_DELAY_MS", 250),
115
+ retryMaxDelayMs: readPositiveInt("TELEMETRY_FANIN_RETRY_MAX_DELAY_MS", 10000),
116
+ };
117
+ }
118
+ export class TelemetryFanInWriter {
119
+ constructor(options) {
120
+ this.buffers = new Map();
121
+ this.pendingRows = 0;
122
+ this.capacityWaiters = [];
123
+ this.activeInserts = 0;
124
+ this.insertSlotWaiters = [];
125
+ this.inFlightDispatches = new Set();
126
+ /*
127
+ * Submits that have been called but have not yet buffered their rows
128
+ * (parked at the waitForCapacity await, or merely yielding its one
129
+ * microtask). flushAll() must not conclude the drain while any exist —
130
+ * their rows would land just after the final cut and strand.
131
+ */
132
+ this.acceptingSubmits = 0;
133
+ this.shutdownHandlerRegistered = false;
134
+ this.insertTransport = null;
135
+ this.options = options;
136
+ }
137
+ /*
138
+ * Install (or clear, with null) a custom delivery transport for token
139
+ * groups. Set once at boot when TELEMETRY_WRITER_URL is configured;
140
+ * batching, dedup-token capture, the per-pod semaphore, retry/backoff,
141
+ * and backpressure all stay in front of it unchanged.
142
+ */
143
+ setInsertTransport(transport) {
144
+ this.insertTransport = transport;
145
+ }
146
+ hasInsertTransport() {
147
+ return this.insertTransport !== null;
148
+ }
149
+ /** Test hook: override options on the shared instance (e.g. shrink maxWaitMs). */
150
+ configure(partial) {
151
+ this.options = Object.assign(Object.assign({}, this.options), partial);
152
+ }
153
+ getStats() {
154
+ let bufferedRows = 0;
155
+ for (const buffer of this.buffers.values()) {
156
+ bufferedRows += buffer.rowCount;
157
+ }
158
+ return {
159
+ bufferedRows,
160
+ pendingRows: this.pendingRows,
161
+ activeInserts: this.activeInserts,
162
+ };
163
+ }
164
+ /*
165
+ * Hand a batch of rows to the writer. Takes ownership of the `rows` array —
166
+ * callers must not mutate it afterwards.
167
+ *
168
+ * Awaiting submit() itself is the backpressure point: it resolves once the
169
+ * rows are accepted into the buffer (immediately under normal load, later
170
+ * when the pod is at maxPendingRows). The returned `flushed` promise is the
171
+ * acceptance ack — resolve it into the job's pending-ack list and await
172
+ * before completing the job.
173
+ *
174
+ * All submitters of one table must pass identical clickhouseSettings: a
175
+ * batch mixes submissions and applies the first submission's settings.
176
+ */
177
+ async submit(target, rows, options) {
178
+ var _a, _b, _c;
179
+ if (!rows || rows.length === 0) {
180
+ return { flushed: Promise.resolve() };
181
+ }
182
+ this.registerShutdownHandlerOnce();
183
+ const tableName = target.model.tableName;
184
+ /*
185
+ * Capture the deterministic per-job dedup token NOW, synchronously in
186
+ * the caller's flow: submit() runs inside the job's runWithInsertDedup
187
+ * scope, while the dispatch that eventually inserts these rows runs on
188
+ * a timer with no ambient context. Capturing before any await also
189
+ * keeps token order deterministic across retries of the same payload.
190
+ */
191
+ const dedupToken = (_a = options === null || options === void 0 ? void 0 : options.dedupToken) !== null && _a !== void 0 ? _a : nextInsertDedupToken(tableName);
192
+ this.acceptingSubmits++;
193
+ try {
194
+ await this.waitForCapacity();
195
+ let buffer = this.buffers.get(tableName);
196
+ if (!buffer) {
197
+ buffer = {
198
+ target,
199
+ submissions: [],
200
+ rowCount: 0,
201
+ timer: null,
202
+ };
203
+ this.buffers.set(tableName, buffer);
204
+ }
205
+ let resolveAck = () => { };
206
+ let rejectAck = () => { };
207
+ const flushed = new Promise((resolve, reject) => {
208
+ resolveAck = resolve;
209
+ rejectAck = reject;
210
+ });
211
+ buffer.submissions.push({
212
+ rows,
213
+ clickhouseSettings: options === null || options === void 0 ? void 0 : options.clickhouseSettings,
214
+ dedupToken,
215
+ resolveAck,
216
+ rejectAck,
217
+ });
218
+ buffer.rowCount += rows.length;
219
+ this.pendingRows += rows.length;
220
+ if (buffer.rowCount >= this.options.maxBatchRows) {
221
+ this.cutAndDispatch(tableName, false);
222
+ }
223
+ else if (!buffer.timer) {
224
+ buffer.timer = setTimeout(() => {
225
+ const timedBuffer = this.buffers.get(tableName);
226
+ if (timedBuffer) {
227
+ timedBuffer.timer = null;
228
+ }
229
+ this.cutAndDispatch(tableName, true);
230
+ }, this.options.maxWaitMs);
231
+ /*
232
+ * Never keep the process alive just for a pending flush timer —
233
+ * graceful shutdown drains buffers explicitly.
234
+ */
235
+ (_c = (_b = buffer.timer).unref) === null || _c === void 0 ? void 0 : _c.call(_b);
236
+ }
237
+ return { flushed };
238
+ }
239
+ finally {
240
+ this.acceptingSubmits--;
241
+ }
242
+ }
243
+ /** Force-flush everything buffered and wait for all in-flight inserts to settle. */
244
+ async flushAll() {
245
+ /*
246
+ * Dispatch promises never reject (failures are routed to submission
247
+ * acks), so a plain all() is safe. Re-cut on EVERY pass: a submit
248
+ * parked in waitForCapacity() resumes when a completing dispatch
249
+ * releases capacity — scheduled BEFORE the dispatch promise's own
250
+ * reactions — and buffers its rows WITHOUT creating a dispatch when
251
+ * they are below maxBatchRows (its flush timer is unref'd and will
252
+ * never fire before process exit). Waiting on inFlightDispatches alone
253
+ * would therefore return with those rows stranded. Exit only when a
254
+ * fresh cut pass leaves nothing in flight and no submit is still
255
+ * mid-acceptance; the microtask yield lets a not-yet-buffered submit
256
+ * (parked only on waitForCapacity's fast-path await) land its rows so
257
+ * the next pass picks them up.
258
+ */
259
+ for (;;) {
260
+ for (const tableName of Array.from(this.buffers.keys())) {
261
+ this.cutAndDispatch(tableName, true);
262
+ }
263
+ if (this.inFlightDispatches.size > 0) {
264
+ await Promise.all(Array.from(this.inFlightDispatches));
265
+ continue;
266
+ }
267
+ if (this.acceptingSubmits === 0) {
268
+ return;
269
+ }
270
+ await Promise.resolve();
271
+ }
272
+ }
273
+ registerShutdownHandlerOnce() {
274
+ if (this.shutdownHandlerRegistered) {
275
+ return;
276
+ }
277
+ this.shutdownHandlerRegistered = true;
278
+ GracefulShutdown.registerHandler("TelemetryFanInWriter", ShutdownPriority.Buffers, async () => {
279
+ await this.flushAll();
280
+ });
281
+ }
282
+ async waitForCapacity() {
283
+ while (this.pendingRows >= this.options.maxPendingRows) {
284
+ await new Promise((resolve) => {
285
+ this.capacityWaiters.push(resolve);
286
+ });
287
+ }
288
+ }
289
+ releaseCapacity() {
290
+ /*
291
+ * Wake all waiters; each re-checks the high-water mark in its
292
+ * waitForCapacity loop, so overshoot stays bounded to one submission
293
+ * per waiter.
294
+ */
295
+ const waiters = this.capacityWaiters;
296
+ this.capacityWaiters = [];
297
+ for (const wake of waiters) {
298
+ wake();
299
+ }
300
+ }
301
+ async acquireInsertSlot() {
302
+ while (this.activeInserts >= this.options.maxConcurrentInserts) {
303
+ await new Promise((resolve) => {
304
+ this.insertSlotWaiters.push(resolve);
305
+ });
306
+ }
307
+ this.activeInserts++;
308
+ }
309
+ releaseInsertSlot() {
310
+ this.activeInserts--;
311
+ const next = this.insertSlotWaiters.shift();
312
+ if (next) {
313
+ next();
314
+ }
315
+ }
316
+ /*
317
+ * Cut batches from a table buffer and dispatch them. When `drain` is false,
318
+ * only full batches (>= maxBatchRows) are cut — the remainder keeps waiting
319
+ * for more rows or the timer. When true, everything goes.
320
+ *
321
+ * A batch is a FIFO run of WHOLE submissions packed up to maxBatchRows
322
+ * (a single oversized submission forms its own batch), so an insert failure
323
+ * maps 1:1 onto the submissions it contained.
324
+ */
325
+ cutAndDispatch(tableName, drain) {
326
+ const buffer = this.buffers.get(tableName);
327
+ if (!buffer) {
328
+ return;
329
+ }
330
+ while (buffer.rowCount >= this.options.maxBatchRows ||
331
+ (drain && buffer.rowCount > 0)) {
332
+ const batch = [];
333
+ let batchRows = 0;
334
+ while (buffer.submissions.length > 0) {
335
+ const next = buffer.submissions[0];
336
+ if (batch.length > 0 &&
337
+ batchRows + next.rows.length > this.options.maxBatchRows) {
338
+ break;
339
+ }
340
+ buffer.submissions.shift();
341
+ batch.push(next);
342
+ batchRows += next.rows.length;
343
+ if (batchRows >= this.options.maxBatchRows) {
344
+ break;
345
+ }
346
+ }
347
+ if (batch.length === 0) {
348
+ break;
349
+ }
350
+ buffer.rowCount -= batchRows;
351
+ const dispatch = this.dispatchInsert(buffer.target, tableName, batch, batchRows);
352
+ this.inFlightDispatches.add(dispatch);
353
+ dispatch.finally(() => {
354
+ this.inFlightDispatches.delete(dispatch);
355
+ });
356
+ }
357
+ if (buffer.rowCount === 0 && buffer.timer) {
358
+ clearTimeout(buffer.timer);
359
+ buffer.timer = null;
360
+ }
361
+ }
362
+ /*
363
+ * Insert one batch while holding a single insert slot. Submissions that
364
+ * carry a deterministic per-job dedup token are inserted INDIVIDUALLY
365
+ * under their own token — this is what keeps BullMQ job retries
366
+ * duplicate-free: the retry re-derives byte-identical tokens and
367
+ * ClickHouse drops blocks a prior attempt already landed. Merging them
368
+ * under one shared cross-job token would break that (a token dedups by
369
+ * token, not content, so a differently-composed block under a reused
370
+ * token would silently drop other jobs' rows — the same hazard that kept
371
+ * the probe paths off dedup tokens historically). Untokened submissions
372
+ * are merged into one insert under a minted per-batch token.
373
+ *
374
+ * The tokened inserts run sequentially within this one slot, so the
375
+ * per-pod concurrency cap — the property that fixes the
376
+ * max_concurrent_queries incident — is unaffected; only the statement
377
+ * count returns to master's per-chunk granularity, and ClickHouse's
378
+ * async-insert coalescing (async_insert=1) still assembles fat parts
379
+ * server-side.
380
+ *
381
+ * Never rejects — outcomes are routed to the submissions' acks.
382
+ */
383
+ async dispatchInsert(target, tableName, batch, batchRows) {
384
+ await this.acquireInsertSlot();
385
+ try {
386
+ const untokened = [];
387
+ for (const submission of batch) {
388
+ if (submission.dedupToken) {
389
+ await this.insertGroupWithRetry(target, tableName, [submission], submission.dedupToken);
390
+ }
391
+ else {
392
+ untokened.push(submission);
393
+ }
394
+ }
395
+ if (untokened.length > 0) {
396
+ await this.insertGroupWithRetry(target, tableName, untokened, `fanin:${tableName}:${UUID.generateTimeOrdered()}`);
397
+ }
398
+ }
399
+ finally {
400
+ this.pendingRows -= batchRows;
401
+ this.releaseCapacity();
402
+ this.releaseInsertSlot();
403
+ }
404
+ }
405
+ /*
406
+ * Insert one token group, retrying transient failures with full-jitter
407
+ * exponential backoff and the SAME dedup token — an ambiguous failure
408
+ * (timeout, connection reset) whose rows actually landed is dropped
409
+ * server-side on the retry. The insert slot is held across backoff sleeps
410
+ * on purpose: when ClickHouse is saturated, offering it MORE concurrent
411
+ * load from this pod is the failure mode this component exists to
412
+ * prevent. Never rejects — outcomes are routed to the group's acks, so
413
+ * one group's definitive failure does not fail the batch's other groups.
414
+ */
415
+ async insertGroupWithRetry(target, tableName, group, dedupToken) {
416
+ var _a, _b;
417
+ const rows = [];
418
+ for (const submission of group) {
419
+ for (const row of submission.rows) {
420
+ rows.push(row);
421
+ }
422
+ }
423
+ const clickhouseSettings = (_a = group[0]) === null || _a === void 0 ? void 0 : _a.clickhouseSettings;
424
+ const sleep = (_b = this.options.sleep) !== null && _b !== void 0 ? _b : ((ms) => {
425
+ return Sleep.sleep(ms);
426
+ });
427
+ let lastError = null;
428
+ let attemptsMade = 0;
429
+ for (let attempt = 1; attempt <= this.options.retryMaxAttempts; attempt++) {
430
+ attemptsMade = attempt;
431
+ try {
432
+ if (this.insertTransport) {
433
+ await this.insertTransport(target, rows, {
434
+ dedupToken,
435
+ clickhouseSettings,
436
+ });
437
+ }
438
+ else {
439
+ await target.insertJsonRows(rows, {
440
+ dedupToken,
441
+ clickhouseSettings,
442
+ });
443
+ }
444
+ for (const submission of group) {
445
+ submission.resolveAck();
446
+ }
447
+ if (attempt > 1) {
448
+ logger.info(`TelemetryFanInWriter: insert into ${tableName} (${rows.length} rows) succeeded on attempt ${attempt}.`);
449
+ }
450
+ return;
451
+ }
452
+ catch (err) {
453
+ lastError = err;
454
+ if (!isRetryableInsertError(err) ||
455
+ attempt === this.options.retryMaxAttempts) {
456
+ break;
457
+ }
458
+ const expDelay = Math.min(this.options.retryMaxDelayMs, this.options.retryBaseDelayMs * Math.pow(2, attempt - 1));
459
+ const jitteredDelay = Math.ceil(Math.random() * expDelay);
460
+ logger.warn(`TelemetryFanInWriter: transient insert failure on ${tableName} (${rows.length} rows, attempt ${attempt}/${this.options.retryMaxAttempts}); retrying with the same dedup token in ${jitteredDelay}ms: ${err instanceof Error ? err.message : String(err)}`);
461
+ await sleep(jitteredDelay);
462
+ }
463
+ }
464
+ const finalError = new FanInInsertError({
465
+ tableName,
466
+ attempts: attemptsMade,
467
+ cause: lastError,
468
+ });
469
+ logger.error(`TelemetryFanInWriter: giving up on insert into ${tableName} (${rows.length} rows); failing ${group.length} submission(s).`);
470
+ logger.error(finalError);
471
+ for (const submission of group) {
472
+ submission.rejectAck(finalError);
473
+ }
474
+ }
475
+ }
476
+ /*
477
+ * Register a durability ack on a job's pending-ack list, wrapped in the
478
+ * service's storage error class and pre-observed: a mid-job batch failure
479
+ * would otherwise reject the stored promise long before the job's final
480
+ * `await Promise.all(pendingAcks)` reaches it, firing the process-level
481
+ * unhandledRejection logger once per ack. The no-op catch marks the promise
482
+ * handled without swallowing anything — the rejection is still delivered at
483
+ * the job's await point, failing the job so BullMQ retries the payload.
484
+ */
485
+ export function pushObservedAck(pendingAcks, flushed, wrapError) {
486
+ const ack = flushed.catch((error) => {
487
+ throw wrapError(error);
488
+ });
489
+ ack.catch(() => {
490
+ // Pre-observed; delivered for real at the job's await point.
491
+ });
492
+ pendingAcks.push(ack);
493
+ }
494
+ const defaultWriter = new TelemetryFanInWriter(readFanInWriterOptionsFromEnv());
495
+ export default defaultWriter;
496
+ //# sourceMappingURL=TelemetryFanInWriter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TelemetryFanInWriter.js","sourceRoot":"","sources":["../../../../../Server/Utils/Telemetry/TelemetryFanInWriter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,165 @@
1
+ import ClusterKeyAuthorization from "../../Middleware/ClusterKeyAuthorization";
2
+ import HTTPErrorResponse from "../../../Types/API/HTTPErrorResponse";
3
+ import URL from "../../../Types/API/URL";
4
+ import API from "../../../Utils/API";
5
+ import { TransientInsertError, } from "./TelemetryFanInWriter";
6
+ /*
7
+ * Worker-side client for the telemetry-writer tier.
8
+ *
9
+ * When TELEMETRY_WRITER_URL is set on a pod, its fan-in writer delivers
10
+ * every token group as one HTTP POST to the writer tier instead of
11
+ * inserting into ClickHouse itself. This decouples worker replica count
12
+ * from ClickHouse INSERT concurrency: telemetry insert load on ClickHouse
13
+ * is (writerReplicas × TELEMETRY_FANIN_MAX_CONCURRENT_INSERTS) regardless
14
+ * of how far the worker fleet scales out. (Reads and low-volume direct
15
+ * inserts that never go through the fan-in writer still use this pod's own
16
+ * ClickHouse pools.)
17
+ *
18
+ * Failure classification maps onto the fan-in writer's retry semantics:
19
+ * every payload carries a dedup token that is byte-identical across
20
+ * retries, so any AMBIGUOUS outcome (network error, timeout, writer pod
21
+ * dying mid-request) is safe to classify transient — a retry of a payload
22
+ * that actually landed is dropped server-side by ClickHouse. Only a
23
+ * definitive verdict from the writer (2xx success, 4xx/500 permanent
24
+ * failure) is treated as final.
25
+ */
26
+ export const TELEMETRY_WRITER_INSERT_ROUTE = "/telemetry-writer/insert";
27
+ export function getTelemetryWriterUrl() {
28
+ const raw = process.env["TELEMETRY_WRITER_URL"];
29
+ const trimmed = (raw || "").trim();
30
+ if (!trimmed) {
31
+ return null;
32
+ }
33
+ // Normalize away a trailing slash so route concatenation is predictable.
34
+ return trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
35
+ }
36
+ export function isTelemetryWriterForwardingEnabled() {
37
+ return getTelemetryWriterUrl() !== null;
38
+ }
39
+ export function getTelemetryWriterRequestTimeoutMs() {
40
+ const raw = process.env["TELEMETRY_WRITER_REQUEST_TIMEOUT_MS"];
41
+ const parsed = parseInt(raw || "", 10);
42
+ /*
43
+ * Generous (the writer holds the request until ClickHouse accepts the
44
+ * rows, which under load includes its batching wait and retry loop) but
45
+ * deliberately under the BullMQ job lock: the fan-in writer makes up to
46
+ * 6 attempts, and 6 x 90s + ~8s backoff ≈ 9.2 min must stay inside
47
+ * TELEMETRY_LOCK_DURATION_MS (10 min) — otherwise a saturated writer
48
+ * tier stalls jobs into redelivery churn instead of failing them
49
+ * cleanly for retry.
50
+ */
51
+ return !isNaN(parsed) && parsed > 0 ? parsed : 90000;
52
+ }
53
+ export function getTelemetryWriterMaxBodyBytes() {
54
+ const raw = process.env["TELEMETRY_WRITER_MAX_BODY_BYTES"];
55
+ const parsed = parseInt(raw || "", 10);
56
+ /*
57
+ * Stay well under the writer route's 50 MB express.json limit (plus
58
+ * headroom for headers/encoding). Bodies above this are split before
59
+ * sending — a 413 would be classified permanent and drop rows.
60
+ */
61
+ return !isNaN(parsed) && parsed > 0 ? parsed : 30000000;
62
+ }
63
+ const defaultPost = async (args) => {
64
+ let response;
65
+ try {
66
+ response = await API.post({
67
+ url: URL.fromString(args.url),
68
+ data: args.body,
69
+ headers: Object.assign({}, ClusterKeyAuthorization.getClusterKeyHeaders()),
70
+ options: {
71
+ timeout: args.timeoutMs,
72
+ skipAuthRefresh: true,
73
+ },
74
+ });
75
+ }
76
+ catch (err) {
77
+ /*
78
+ * API.post throws (APIException) when no HTTP response exists at all —
79
+ * connection refused, reset, DNS, timeout. Outcome unknown; statusCode 0
80
+ * classifies it transient.
81
+ */
82
+ return {
83
+ statusCode: 0,
84
+ errorMessage: err instanceof Error ? err.message : String(err),
85
+ };
86
+ }
87
+ if (response instanceof HTTPErrorResponse) {
88
+ return {
89
+ statusCode: response.statusCode,
90
+ errorMessage: response.message,
91
+ };
92
+ }
93
+ return { statusCode: response.statusCode };
94
+ };
95
+ /*
96
+ * Writer-tier verdicts that mean "retry with the same token": overload
97
+ * shedding (429) and gateway/unavailability statuses where the request may
98
+ * or may not have been processed.
99
+ */
100
+ const TRANSIENT_HTTP_STATUSES = new Set([429, 502, 503, 504]);
101
+ function isDefinitiveStatus(statusCode) {
102
+ /*
103
+ * Anything outside real HTTP semantics (0, -1, undefined coerced) is a
104
+ * network-level failure with an unknown outcome — transient by the dedup
105
+ * token argument above.
106
+ */
107
+ return statusCode >= 100 && statusCode < 600;
108
+ }
109
+ export function createTelemetryWriterTransport(postFn) {
110
+ const post = postFn !== null && postFn !== void 0 ? postFn : defaultPost;
111
+ const postGroup = async (tableName, rows, dedupToken, clickhouseSettings, baseUrl, maxBodyBytes) => {
112
+ const body = Object.assign({ tableName: tableName, rows: rows, dedupToken: dedupToken }, (clickhouseSettings
113
+ ? { clickhouseSettings: clickhouseSettings }
114
+ : {}));
115
+ /*
116
+ * Byte-aware splitting: the writer route sits behind a 50 MB JSON
117
+ * parser, and a 413 is (correctly) classified permanent — so an
118
+ * oversized group would be dropped after retries, a failure mode the
119
+ * direct ClickHouse path never had. Halve deterministically instead:
120
+ * a BullMQ retry re-produces byte-identical rows, hence the identical
121
+ * split and identical derived tokens ("<token>#0"/"<token>#1"), so
122
+ * idempotence is preserved (async-insert dedup is a content hash per
123
+ * insert body, and the distinct suffixes keep tokens collision-free
124
+ * for any future sync-insert mode). Halves post sequentially — this
125
+ * all happens under one fan-in insert slot, and splitting must not
126
+ * multiply the pod's concurrent load.
127
+ */
128
+ if (rows.length > 1) {
129
+ const bodyBytes = Buffer.byteLength(JSON.stringify(body), "utf8");
130
+ if (bodyBytes > maxBodyBytes) {
131
+ const mid = Math.floor(rows.length / 2);
132
+ await postGroup(tableName, rows.slice(0, mid), `${dedupToken}#0`, clickhouseSettings, baseUrl, maxBodyBytes);
133
+ await postGroup(tableName, rows.slice(mid), `${dedupToken}#1`, clickhouseSettings, baseUrl, maxBodyBytes);
134
+ return;
135
+ }
136
+ }
137
+ const result = await post({
138
+ url: `${baseUrl}${TELEMETRY_WRITER_INSERT_ROUTE}`,
139
+ body: body,
140
+ timeoutMs: getTelemetryWriterRequestTimeoutMs(),
141
+ });
142
+ if (result.statusCode >= 200 && result.statusCode < 300) {
143
+ return;
144
+ }
145
+ const detail = `telemetry-writer insert into ${tableName} (${rows.length} rows) returned status ${result.statusCode}${result.errorMessage ? `: ${result.errorMessage}` : ""}`;
146
+ if (TRANSIENT_HTTP_STATUSES.has(result.statusCode) ||
147
+ !isDefinitiveStatus(result.statusCode)) {
148
+ throw new TransientInsertError(detail);
149
+ }
150
+ throw new Error(detail);
151
+ };
152
+ return async (target, rows, options) => {
153
+ const baseUrl = getTelemetryWriterUrl();
154
+ if (!baseUrl) {
155
+ /*
156
+ * The transport is only installed when the URL is configured; losing
157
+ * it mid-flight (env mutation in tests) is a permanent config error,
158
+ * not something a same-token retry can fix.
159
+ */
160
+ throw new Error("TelemetryWriterClient: TELEMETRY_WRITER_URL is not configured.");
161
+ }
162
+ await postGroup(target.model.tableName, rows, options.dedupToken, options.clickhouseSettings, baseUrl, getTelemetryWriterMaxBodyBytes());
163
+ };
164
+ }
165
+ //# sourceMappingURL=TelemetryWriterClient.js.map