mirror of
https://github.com/benjamin-wilson/public-pool.git
synced 2026-09-29 17:15:03 -07:00
Migrate pool storage to TimescaleDB and Redis
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user