* Atomic write-back: session blob + wallclock ZSET + projects SET + * profile index + ClickHouse buffer rows + counter. * * No TTLs on the blob or profile index — they are removed exclusively by * `cleanup()` after `session_end` emission. This guarantees the reaper * can always find a
(
current: IClickhouseSession,
chRows: IClickhouseSession[]
)
| 342 | * has been down. |
| 343 | */ |
| 344 | private async persist( |
| 345 | current: IClickhouseSession, |
| 346 | chRows: IClickhouseSession[] |
| 347 | ) { |
| 348 | const projectId = current.project_id; |
| 349 | const deviceId = current.device_id; |
| 350 | const wallClockMs = Date.now(); |
| 351 | |
| 352 | const multi = this.redis.multi(); |
| 353 | multi.set(sessionKey(projectId, deviceId), JSON.stringify(current)); |
| 354 | multi.zadd(wallclockSetKey(projectId), wallClockMs.toString(), deviceId); |
| 355 | multi.sadd(PROJECTS_SET_KEY, projectId); |
| 356 | |
| 357 | if (current.profile_id && current.profile_id !== current.device_id) { |
| 358 | multi.set(profileIndexKey(projectId, current.profile_id), deviceId); |
| 359 | } |
| 360 | |
| 361 | for (const row of chRows) { |
| 362 | multi.rpush(this.redisKey, JSON.stringify(row)); |
| 363 | } |
| 364 | await multi.exec(); |
| 365 | |
| 366 | const bufferLength = await this.getBufferSize(); |
| 367 | if (bufferLength >= this.batchSize) { |
| 368 | await this.tryFlush(); |
| 369 | } |
| 370 | } |
| 371 | |
| 372 | /** |
| 373 | * Squash the (sign=-1, sign=+1) rows for each session id down to the |
no test coverage detected