Migrate pool storage to TimescaleDB and Redis

This commit is contained in:
Ben
2026-06-07 20:33:13 -04:00
parent fb367ac5f0
commit ec69f5fb6c
64 changed files with 4751 additions and 1146 deletions
+2 -95
View File
@@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common';
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { DataSource, Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ClientEntity } from './client.entity';
@@ -9,106 +9,13 @@ import { ClientEntity } from './client.entity';
@Injectable()
export class ClientService {
private heartbeatBulkUpdate: { [id: string]: { id: string, hashRate: number, updatedAt: Date } } = {};
constructor(
@InjectDataSource()
private dataSource: DataSource,
@InjectRepository(ClientEntity)
private clientRepository: Repository<ClientEntity>
) {
}
// client.service.ts
public async killDeadClients(): Promise<boolean> {
const BATCH_SIZE = 10000;
return this.dataSource.transaction('READ COMMITTED', async (manager) => { // ← CHANGE HERE
const deadClients: { id: string }[] = await manager.query(`
SELECT id
FROM client_entity
WHERE "deletedAt" IS NULL
AND "updatedAt" < NOW() - INTERVAL '5 minutes'
ORDER BY id
LIMIT $1
FOR UPDATE SKIP LOCKED
`, [BATCH_SIZE]);
if (deadClients.length === 0) {
return false;
}
await manager
.createQueryBuilder()
.update(ClientEntity)
.set({ deletedAt: () => 'NOW()' })
.whereInIds(deadClients.map(c => c.id))
.execute();
console.log(`Killed ${deadClients.length} dead clients`);
return true;
});
}
//public async heartbeat(id, hashRate: number, updatedAt: Date) {
// return await this.clientRepository.update({ id }, { hashRate, deletedAt: null, updatedAt });
// }
public heartbeatBulkAsync(id, hashRate: number, updatedAt: Date) {
if (this.heartbeatBulkUpdate[id] != null) {
this.heartbeatBulkUpdate[id].hashRate = hashRate;
this.heartbeatBulkUpdate[id].updatedAt = updatedAt;
return;
}
this.heartbeatBulkUpdate[id] = { id, hashRate, updatedAt };
}
public async doBulkHeartbeatUpdate() {
if (Object.keys(this.heartbeatBulkUpdate).length < 1) {
console.log('No heartbeats to update.')
return;
}
const values = Object.entries(this.heartbeatBulkUpdate).map(([key, value]) => {
return `('${value.id}', ${value.hashRate}, NOW())`
}).join(',');
const query = `
DO $$
BEGIN
CREATE TEMP TABLE temp_heartbeats (
id UUID,
"hashRate" DECIMAL,
"updatedAt" TIMESTAMP
) ON COMMIT DROP;
INSERT INTO temp_heartbeats (id, "hashRate", "updatedAt")
VALUES ${values};
UPDATE "client_entity" ce
SET "hashRate" = th."hashRate",
"deletedAt" = NULL,
"updatedAt" = th."updatedAt"
FROM temp_heartbeats th
WHERE ce.id = th.id;
END;
$$;
`;
try {
await this.clientRepository.query(query);
console.log(`Bulk updated ${Object.keys(this.heartbeatBulkUpdate).length} client heartbeats`);
} catch (error) {
console.error('Bulk heartbeat failed:', error.message, 'Query:', query);
throw error;
}
this.heartbeatBulkUpdate = {};
}
public async insert(partialClient: Partial<ClientEntity>): Promise<ClientEntity> {
const insertResult = await this.clientRepository.insert(partialClient);