@transitive-sdk/clickhouse 0.4.3 → 0.5.1
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 +40 -23
- package/package.json +1 -1
- package/test/clickhouse.test.js +2 -3
package/index.js
CHANGED
|
@@ -47,21 +47,22 @@ 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
|
|
50
51
|
|
|
51
52
|
/** Create the client, connecting to Clickhouse */
|
|
52
|
-
async init({ url, dbName, user, password } = {}) {
|
|
53
|
+
async init({ url, dbName, user, password, interval } = {}) {
|
|
53
54
|
|
|
54
55
|
const _url = url || process.env.CLICKHOUSE_URL || 'http://clickhouse:8123';
|
|
55
56
|
const _dbName = dbName || process.env.CLICKHOUSE_DB || 'default';
|
|
56
57
|
const _user = user || process.env.CLICKHOUSE_USER || 'default';
|
|
58
|
+
interval ||= 10_000;
|
|
57
59
|
|
|
58
60
|
const {hostname, port} = URL.parse(_url);
|
|
59
|
-
const
|
|
60
|
-
await waitPort({ host: hostname, port: Number(port || 80),
|
|
61
|
+
const waitInterval = 200;
|
|
62
|
+
await waitPort({ host: hostname, port: Number(port || 80), waitInterval },
|
|
63
|
+
10000);
|
|
61
64
|
await new Promise(done => setTimeout(done, 200));
|
|
62
65
|
|
|
63
|
-
// console.debug(`Creating ClickHouse client for URL: ${_url}, DB: ${_dbName}, User: ${_user}`);
|
|
64
|
-
|
|
65
66
|
this._client = createClient({
|
|
66
67
|
url: _url,
|
|
67
68
|
max_open_connections: 10,
|
|
@@ -86,6 +87,9 @@ class ClickHouse {
|
|
|
86
87
|
});
|
|
87
88
|
|
|
88
89
|
await this._client.query({ query: 'SELECT 1' });
|
|
90
|
+
|
|
91
|
+
// start interval for batch insertion
|
|
92
|
+
setInterval(this.batchInsertCache.bind(this), interval);
|
|
89
93
|
}
|
|
90
94
|
|
|
91
95
|
/** Get the Clickhouse client (from @clickhouse/client) */
|
|
@@ -142,7 +146,7 @@ class ClickHouse {
|
|
|
142
146
|
* @param {string} orgId - organization ID to add to each row
|
|
143
147
|
* @param {string} deviceId - device ID to add to each row
|
|
144
148
|
*/
|
|
145
|
-
|
|
149
|
+
insert(tableName, rows, orgId, deviceId) {
|
|
146
150
|
// assert that orgId and deviceId are provided
|
|
147
151
|
if (!orgId || !deviceId) {
|
|
148
152
|
throw new Error('Both orgId and deviceId must be provided for multi-tenant insert');
|
|
@@ -155,16 +159,13 @@ class ClickHouse {
|
|
|
155
159
|
DeviceId: deviceId
|
|
156
160
|
}));
|
|
157
161
|
|
|
158
|
-
return
|
|
159
|
-
table: tableName,
|
|
160
|
-
values: rowsWithIds,
|
|
161
|
-
format: 'JSONEachRow'
|
|
162
|
-
});
|
|
162
|
+
return this.addToCache(tableName, rowsWithIds);
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
/* Enable history recording. Ensure the mqtt_history table exists with the
|
|
166
166
|
* correct schema, set dataCache, and subscribe to changes.
|
|
167
167
|
* @param {object} options = {dataCache, tableName, ttlDays}
|
|
168
|
+
* @param {number} interval = ms interval between batch insertions
|
|
168
169
|
*/
|
|
169
170
|
async enableHistory(options) {
|
|
170
171
|
const { dataCache, tableName = 'mqtt_history' } = options;
|
|
@@ -241,7 +242,7 @@ class ClickHouse {
|
|
|
241
242
|
// it matches any of the registered topics (this avoid duplicate triggers),
|
|
242
243
|
// then store to ClickHouse with current timestamp.
|
|
243
244
|
dataCache.subscribe((changes) => {
|
|
244
|
-
_.forEach(changes,
|
|
245
|
+
_.forEach(changes, (value, topic) => {
|
|
245
246
|
|
|
246
247
|
const matched =
|
|
247
248
|
_.some(this.topics, (_true, selector) => topicMatch(selector, topic));
|
|
@@ -257,24 +258,40 @@ class ClickHouse {
|
|
|
257
258
|
row.Payload = JSON.stringify(value);
|
|
258
259
|
} // else: omit
|
|
259
260
|
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
}
|
|
261
|
+
// cache it for batch insertion
|
|
262
|
+
this.addToCache(this.mqttHistoryTable, [row]);
|
|
269
263
|
})
|
|
270
264
|
});
|
|
271
|
-
|
|
272
|
-
|
|
273
265
|
}
|
|
274
266
|
|
|
275
267
|
this.mqttHistoryTable = tableName;
|
|
276
268
|
}
|
|
277
269
|
|
|
270
|
+
/** Add the given rows to the cache for batch-insertion to the given table */
|
|
271
|
+
addToCache(table, rows) {
|
|
272
|
+
this.rowCache[this.mqttHistoryTable] ||= [];
|
|
273
|
+
this.rowCache[this.mqttHistoryTable].push(...rows);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** Function responsible fgor inserting all cached rows */
|
|
277
|
+
batchInsertCache() {
|
|
278
|
+
_.forEach(this.rowCache, (rows, table) => {
|
|
279
|
+
if (rows.length == 0) return;
|
|
280
|
+
|
|
281
|
+
try {
|
|
282
|
+
this.rowCache[table] = [];
|
|
283
|
+
this.client.insert({
|
|
284
|
+
table,
|
|
285
|
+
values: rows,
|
|
286
|
+
format: 'JSONEachRow'
|
|
287
|
+
});
|
|
288
|
+
} catch (error) {
|
|
289
|
+
console.error(`Error inserting ${rows.length} rows into ${table}`, error.message);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
|
|
278
295
|
/* Register an MQTT topic for storage in ClickHouse subscribes to the topic
|
|
279
296
|
* and stores incoming messages JSON.stringify'd in a ClickHouse table.
|
|
280
297
|
* Retrieve using `queryMQTTHistory`, or, when quering directly, e.g., from
|
package/package.json
CHANGED
package/test/clickhouse.test.js
CHANGED
|
@@ -48,7 +48,7 @@ describe('ClickHouse', function() {
|
|
|
48
48
|
const dataCache = new DataCache({});
|
|
49
49
|
|
|
50
50
|
before(async () => {
|
|
51
|
-
await clickhouse.init({ url: CLICKHOUSE_URL });
|
|
51
|
+
await clickhouse.init({ url: CLICKHOUSE_URL, interval: 100 });
|
|
52
52
|
/* Register for `insert` events on ClickHouse client */
|
|
53
53
|
emitter = interceptInserts();
|
|
54
54
|
|
|
@@ -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
|
|