@transitive-sdk/clickhouse 0.4.3 → 0.5.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/index.js CHANGED
@@ -47,6 +47,8 @@ class ClickHouse {
47
47
 
48
48
  mqttHistoryTable = null; // name of the table used for MQTT history, if used
49
49
  topics = {}; // list of topics registered for storage, as object for de-duplication
50
+ rowCache = {}; // cache of rows awaiting insertion, by table
51
+ insertionInterval = null; // the actual interval
50
52
 
51
53
  /** Create the client, connecting to Clickhouse */
52
54
  async init({ url, dbName, user, password } = {}) {
@@ -142,7 +144,7 @@ class ClickHouse {
142
144
  * @param {string} orgId - organization ID to add to each row
143
145
  * @param {string} deviceId - device ID to add to each row
144
146
  */
145
- async insert(tableName, rows, orgId, deviceId) {
147
+ insert(tableName, rows, orgId, deviceId) {
146
148
  // assert that orgId and deviceId are provided
147
149
  if (!orgId || !deviceId) {
148
150
  throw new Error('Both orgId and deviceId must be provided for multi-tenant insert');
@@ -155,18 +157,15 @@ class ClickHouse {
155
157
  DeviceId: deviceId
156
158
  }));
157
159
 
158
- return await this.client.insert({
159
- table: tableName,
160
- values: rowsWithIds,
161
- format: 'JSONEachRow'
162
- });
160
+ return this.addToCache(tableName, rowsWithIds);
163
161
  }
164
162
 
165
163
  /* Enable history recording. Ensure the mqtt_history table exists with the
166
164
  * correct schema, set dataCache, and subscribe to changes.
167
165
  * @param {object} options = {dataCache, tableName, ttlDays}
166
+ * @param {number} interval = ms interval between batch insertions
168
167
  */
169
- async enableHistory(options) {
168
+ async enableHistory(options, interval = 10_000) {
170
169
  const { dataCache, tableName = 'mqtt_history' } = options;
171
170
 
172
171
  if (this.mqttHistoryTable != tableName) {
@@ -241,7 +240,7 @@ class ClickHouse {
241
240
  // it matches any of the registered topics (this avoid duplicate triggers),
242
241
  // then store to ClickHouse with current timestamp.
243
242
  dataCache.subscribe((changes) => {
244
- _.forEach(changes, async (value, topic) => {
243
+ _.forEach(changes, (value, topic) => {
245
244
 
246
245
  const matched =
247
246
  _.some(this.topics, (_true, selector) => topicMatch(selector, topic));
@@ -257,24 +256,46 @@ class ClickHouse {
257
256
  row.Payload = JSON.stringify(value);
258
257
  } // else: omit
259
258
 
260
- try {
261
- await this.client.insert({
262
- table: this.mqttHistoryTable,
263
- values: [row],
264
- format: 'JSONEachRow'
265
- });
266
- } catch (error) {
267
- console.error('Error inserting MQTT message into ClickHouse:', error.message);
268
- }
259
+ // cache it for batch insertion
260
+ this.addToCache(this.mqttHistoryTable, [row]);
269
261
  })
270
262
  });
263
+ }
271
264
 
265
+ this.mqttHistoryTable = tableName;
272
266
 
267
+ // start interval for batch insertion
268
+ if (!this.insertionInterval) {
269
+ this.insertionInterval =
270
+ setInterval(this.batchInsertCache.bind(this), interval);
273
271
  }
272
+ }
274
273
 
275
- this.mqttHistoryTable = tableName;
274
+ /** Add the given rows to the cache for batch-insertion to the given table */
275
+ addToCache(table, rows) {
276
+ this.rowCache[this.mqttHistoryTable] ||= [];
277
+ this.rowCache[this.mqttHistoryTable].push(...rows);
276
278
  }
277
279
 
280
+ /** Function responsible fgor inserting all cached rows */
281
+ batchInsertCache() {
282
+ _.forEach(this.rowCache, (rows, table) => {
283
+ if (rows.length == 0) return;
284
+
285
+ try {
286
+ this.rowCache[table] = [];
287
+ this.client.insert({
288
+ table,
289
+ values: rows,
290
+ format: 'JSONEachRow'
291
+ });
292
+ } catch (error) {
293
+ console.error(`Error inserting ${rows.length} rows into ${table}`, error.message);
294
+ }
295
+ });
296
+ }
297
+
298
+
278
299
  /* Register an MQTT topic for storage in ClickHouse subscribes to the topic
279
300
  * and stores incoming messages JSON.stringify'd in a ClickHouse table.
280
301
  * Retrieve using `queryMQTTHistory`, or, when quering directly, e.g., from
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@transitive-sdk/clickhouse",
3
- "version": "0.4.3",
3
+ "version": "0.5.0",
4
4
  "description": "A tiny ClickHouse utility class for use in the Transitive framework.",
5
5
  "homepage": "https://transitiverobotics.com",
6
6
  "repository": {
@@ -63,7 +63,7 @@ describe('ClickHouse', function() {
63
63
  await clickhouse.enableHistory({
64
64
  dataCache,
65
65
  tableName: TABLE_NAME
66
- });
66
+ }, 100);
67
67
 
68
68
  await clickhouse.registerMqttTopicForStorage(STANDARD_TOPIC_PATTERN);
69
69
  });
@@ -130,7 +130,6 @@ describe('ClickHouse', function() {
130
130
  await new Promise(resolve => setTimeout(resolve, 10));
131
131
  dataCache.update([org, 'device1', 'data'], null);
132
132
  await once(emitter, 'insert');
133
- await once(emitter, 'insert');
134
133
 
135
134
  const rows = await queryRowsByOrg(org);
136
135