mirror of
https://github.com/benjamin-wilson/public-pool.git
synced 2026-09-29 17:15:03 -07:00
postgres init
This commit is contained in:
@@ -11,7 +11,7 @@ export class AddressSettingsEntity extends TrackedEntity {
|
||||
@Column({ default: 0 })
|
||||
shares: number;
|
||||
|
||||
@Column({ type: 'real', default: 0 })
|
||||
@Column({ type: 'decimal', default: 0 })
|
||||
bestDifficulty: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Column, Entity, Index, PrimaryColumn } from 'typeorm';
|
||||
|
||||
import { TrackedEntity } from '../utils/TrackedEntity.entity';
|
||||
|
||||
@Entity({ withoutRowid: true })
|
||||
@Entity()
|
||||
//Index for the heartbeat update
|
||||
@Index(["address", "clientName", "sessionId", "time"])
|
||||
export class ClientStatisticsEntity extends TrackedEntity {
|
||||
@@ -21,13 +21,13 @@ export class ClientStatisticsEntity extends TrackedEntity {
|
||||
sessionId: string;
|
||||
|
||||
@Index()
|
||||
@Column({ type: 'integer' })
|
||||
@Column({ type: 'bigint' })
|
||||
time: number;
|
||||
|
||||
@Column({ type: 'real' })
|
||||
@Column({ type: 'decimal' })
|
||||
shares: number;
|
||||
|
||||
@Column({ default: 0, type: 'integer' })
|
||||
@Column({ default: 0, type: 'bigint' })
|
||||
acceptedCount: number;
|
||||
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ export class ClientStatisticsService {
|
||||
|
||||
|
||||
return result.map(res => {
|
||||
res.label = new Date(res.label).toISOString();
|
||||
res.label = new Date(parseInt(res.label)).toISOString();
|
||||
return res;
|
||||
}).slice(0, result.length - 1)
|
||||
|
||||
@@ -84,7 +84,7 @@ export class ClientStatisticsService {
|
||||
// FROM
|
||||
// client_statistics_entity AS entry
|
||||
// WHERE
|
||||
// entry.address = ? AND entry.time > ${oneHour}
|
||||
// entry.address = $1 AND entry.time > ${oneHour}
|
||||
// `;
|
||||
|
||||
// const result = await this.clientStatisticsRepository.query(query, [address]);
|
||||
@@ -101,12 +101,12 @@ export class ClientStatisticsService {
|
||||
|
||||
const query = `
|
||||
SELECT
|
||||
time label,
|
||||
time AS label,
|
||||
(SUM(shares) * 4294967296) / 600 AS data
|
||||
FROM
|
||||
client_statistics_entity AS entry
|
||||
WHERE
|
||||
entry.address = ? AND entry.time > ${yesterday.getTime()}
|
||||
entry.address = $1 AND entry.time > $2
|
||||
GROUP BY
|
||||
time
|
||||
ORDER BY
|
||||
@@ -115,10 +115,10 @@ export class ClientStatisticsService {
|
||||
|
||||
`;
|
||||
|
||||
const result = await this.clientStatisticsRepository.query(query, [address]);
|
||||
const result = await this.clientStatisticsRepository.query(query, [address, yesterday.getTime()]);
|
||||
|
||||
return result.map(res => {
|
||||
res.label = new Date(res.label).toISOString();
|
||||
res.label = new Date(parseInt(res.label)).toISOString();
|
||||
return res;
|
||||
}).slice(0, result.length - 1);
|
||||
|
||||
@@ -136,7 +136,7 @@ export class ClientStatisticsService {
|
||||
FROM
|
||||
client_statistics_entity AS entry
|
||||
WHERE
|
||||
entry.address = ? AND entry.clientName = ? AND entry.time > ${oneHour.getTime()}
|
||||
entry.address = $1 AND entry.clientName = $2 AND entry.time > ${oneHour.getTime()}
|
||||
`;
|
||||
|
||||
const result = await this.clientStatisticsRepository.query(query, [address, clientName]);
|
||||
@@ -153,12 +153,12 @@ export class ClientStatisticsService {
|
||||
|
||||
const query = `
|
||||
SELECT
|
||||
time label,
|
||||
time AS label,
|
||||
(SUM(shares) * 4294967296) / 600 AS data
|
||||
FROM
|
||||
client_statistics_entity AS entry
|
||||
WHERE
|
||||
entry.address = ? AND entry.clientName = ? AND entry.time > ${yesterday.getTime()}
|
||||
entry.address = $1 AND entry."clientName" = $2 AND entry.time > ${yesterday.getTime()}
|
||||
GROUP BY
|
||||
time
|
||||
ORDER BY
|
||||
@@ -169,7 +169,7 @@ export class ClientStatisticsService {
|
||||
const result = await this.clientStatisticsRepository.query(query, [address, clientName]);
|
||||
|
||||
return result.map(res => {
|
||||
res.label = new Date(res.label).toISOString();
|
||||
res.label = new Date(parseInt(res.label)).toISOString();
|
||||
return res;
|
||||
}).slice(0, result.length - 1);
|
||||
|
||||
@@ -181,13 +181,13 @@ export class ClientStatisticsService {
|
||||
|
||||
const query = `
|
||||
SELECT
|
||||
createdAt,
|
||||
updatedAt,
|
||||
"createdAt",
|
||||
"updatedAt",
|
||||
shares
|
||||
FROM
|
||||
client_statistics_entity AS entry
|
||||
WHERE
|
||||
entry.address = ? AND entry.clientName = ? AND entry.sessionId = ?
|
||||
entry.address = $1 AND entry."clientName" = $2 AND entry."sessionId" = $3
|
||||
ORDER BY time DESC
|
||||
LIMIT 2;
|
||||
`;
|
||||
@@ -227,7 +227,7 @@ export class ClientStatisticsService {
|
||||
FROM
|
||||
client_statistics_entity AS entry
|
||||
WHERE
|
||||
entry.address = ? AND entry.clientName = ? AND entry.sessionId = ? AND entry.time > ${yesterday.getTime()}
|
||||
entry.address = $1 AND entry."clientName" = $2 AND entry."sessionId" = $3 AND entry.time > ${yesterday.getTime()}
|
||||
GROUP BY
|
||||
time
|
||||
ORDER BY
|
||||
@@ -238,7 +238,7 @@ export class ClientStatisticsService {
|
||||
const result = await this.clientStatisticsRepository.query(query, [address, clientName, sessionId]);
|
||||
|
||||
return result.map(res => {
|
||||
res.label = new Date(res.label).toISOString();
|
||||
res.label = new Date(parseInt(res.label)).toISOString();
|
||||
return res;
|
||||
}).slice(0, result.length - 1);
|
||||
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import { Column, Entity, Index, PrimaryColumn } from 'typeorm';
|
||||
|
||||
import { DateTimeTransformer } from '../utils/DateTimeTransformer';
|
||||
import { TrackedEntity } from '../utils/TrackedEntity.entity';
|
||||
|
||||
//https://www.sqlite.org/withoutrowid.html
|
||||
|
||||
//The WITHOUT ROWID optimization is likely to be helpful for tables that have non-integer
|
||||
// or composite (multi-column) PRIMARY KEYs and that do not store large strings or BLOBs.
|
||||
//WITHOUT ROWID tables work best when individual rows are not too large.
|
||||
@Entity({ withoutRowid: true })
|
||||
@Entity()
|
||||
@Index(['address', 'clientName', 'sessionId'], { unique: true })
|
||||
export class ClientEntity extends TrackedEntity {
|
||||
|
||||
@@ -28,13 +23,13 @@ export class ClientEntity extends TrackedEntity {
|
||||
|
||||
|
||||
|
||||
@Column({ type: 'datetime', transformer: new DateTimeTransformer() })
|
||||
@Column({ type: 'timestamp' })
|
||||
startTime: Date;
|
||||
|
||||
@Column({ type: 'real', default: 0 })
|
||||
@Column({ type: 'decimal', default: 0 })
|
||||
bestDifficulty: number
|
||||
|
||||
@Column({ default: 0 })
|
||||
@Column({ default: 0, type: 'decimal' })
|
||||
hashRate: number;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Interval } from '@nestjs/schedule';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { BehaviorSubject, firstValueFrom } from 'rxjs';
|
||||
import { ObjectLiteral, Repository } from 'typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { ClientEntity } from './client.entity';
|
||||
|
||||
@@ -12,8 +10,6 @@ import { ClientEntity } from './client.entity';
|
||||
export class ClientService {
|
||||
|
||||
|
||||
public insertQueue: { result: BehaviorSubject<ObjectLiteral | null>, partialClient: Partial<ClientEntity> }[] = [];
|
||||
|
||||
|
||||
constructor(
|
||||
@InjectRepository(ClientEntity)
|
||||
@@ -22,26 +18,13 @@ export class ClientService {
|
||||
|
||||
}
|
||||
|
||||
@Interval(1000 * 5)
|
||||
public async insertClients() {
|
||||
const queueCopy = [...this.insertQueue];
|
||||
this.insertQueue = [];
|
||||
|
||||
const results = await this.clientRepository.insert(queueCopy.map(c => c.partialClient));
|
||||
|
||||
queueCopy.forEach((c, index) => {
|
||||
c.result.next(results.generatedMaps[index]);
|
||||
});
|
||||
}
|
||||
|
||||
public async killDeadClients() {
|
||||
var fiveMinutes = new Date(new Date().getTime() - (5 * 60 * 1000)).toISOString();
|
||||
|
||||
return await this.clientRepository
|
||||
.createQueryBuilder()
|
||||
.update(ClientEntity)
|
||||
.set({ deletedAt: () => "DATETIME('now')" })
|
||||
.where("deletedAt IS NULL AND updatedAt < DATETIME(:fiveMinutes)", { fiveMinutes })
|
||||
.set({ deletedAt: () => "NOW()" })
|
||||
.where("deletedAt IS NULL AND updatedAt < NOW() + interval '5 minutes' ")
|
||||
.execute();
|
||||
}
|
||||
|
||||
@@ -55,19 +38,11 @@ export class ClientService {
|
||||
|
||||
|
||||
public async insert(partialClient: Partial<ClientEntity>): Promise<ClientEntity> {
|
||||
|
||||
const result = new BehaviorSubject(null);
|
||||
|
||||
this.insertQueue.push({ result, partialClient });
|
||||
|
||||
|
||||
// const insertResult = await this.clientRepository.insert(partialClient);
|
||||
|
||||
const generatedMap = await firstValueFrom(result);
|
||||
const insertResult = await this.clientRepository.insert(partialClient);
|
||||
|
||||
const client = {
|
||||
...partialClient,
|
||||
...generatedMap
|
||||
...insertResult.generatedMaps[0]
|
||||
};
|
||||
|
||||
return client as ClientEntity;
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { CreateDateColumn, DeleteDateColumn, UpdateDateColumn } from 'typeorm';
|
||||
|
||||
import { DateTimeTransformer } from './DateTimeTransformer';
|
||||
|
||||
export abstract class TrackedEntity {
|
||||
@DeleteDateColumn({ nullable: true, type: 'datetime', transformer: new DateTimeTransformer() })
|
||||
@DeleteDateColumn({ nullable: true, type: 'timestamp' })
|
||||
public deletedAt?: Date;
|
||||
|
||||
@CreateDateColumn({ type: 'datetime', transformer: new DateTimeTransformer() })
|
||||
@CreateDateColumn({ type: 'timestamp' })
|
||||
public createdAt?: Date
|
||||
|
||||
@UpdateDateColumn({ type: 'datetime', transformer: new DateTimeTransformer() })
|
||||
@UpdateDateColumn({ type: 'timestamp' })
|
||||
public updatedAt?: Date
|
||||
}
|
||||
+30
-10
@@ -1,7 +1,7 @@
|
||||
import { HttpModule } from '@nestjs/axios';
|
||||
import { CacheModule } from '@nestjs/cache-manager';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { ScheduleModule } from '@nestjs/schedule';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
@@ -9,11 +9,17 @@ import { AppController } from './app.controller';
|
||||
import { AddressController } from './controllers/address/address.controller';
|
||||
import { ClientController } from './controllers/client/client.controller';
|
||||
import { BitcoinAddressValidator } from './models/validators/bitcoin-address.validator';
|
||||
import { AddressSettingsEntity } from './ORM/address-settings/address-settings.entity';
|
||||
import { AddressSettingsModule } from './ORM/address-settings/address-settings.module';
|
||||
import { BlocksEntity } from './ORM/blocks/blocks.entity';
|
||||
import { BlocksModule } from './ORM/blocks/blocks.module';
|
||||
import { ClientStatisticsEntity } from './ORM/client-statistics/client-statistics.entity';
|
||||
import { ClientStatisticsModule } from './ORM/client-statistics/client-statistics.module';
|
||||
import { ClientEntity } from './ORM/client/client.entity';
|
||||
import { ClientModule } from './ORM/client/client.module';
|
||||
import { RpcBlockEntity } from './ORM/rpc-block/rpc-block.entity';
|
||||
import { RpcBlocksModule } from './ORM/rpc-block/rpc-block.module';
|
||||
import { TelegramSubscriptionsEntity } from './ORM/telegram-subscriptions/telegram-subscriptions.entity';
|
||||
import { TelegramSubscriptionsModule } from './ORM/telegram-subscriptions/telegram-subscriptions.module';
|
||||
import { AppService } from './services/app.service';
|
||||
import { BitcoinRpcService } from './services/bitcoin-rpc.service';
|
||||
@@ -39,16 +45,30 @@ const ORMModules = [
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot(),
|
||||
TypeOrmModule.forRoot({
|
||||
type: 'sqlite',
|
||||
database: './DB/public-pool.sqlite',
|
||||
synchronize: true,
|
||||
autoLoadEntities: true,
|
||||
cache: true,
|
||||
logging: false,
|
||||
enableWAL: true,
|
||||
busyTimeout: 30 * 1000,
|
||||
TypeOrmModule.forRootAsync({
|
||||
useFactory: (configService: ConfigService) => {
|
||||
return {
|
||||
type: 'postgres',
|
||||
host: configService.get('DB_HOST'),
|
||||
port: parseInt(configService.get('DB_PORT')),
|
||||
username: configService.get('DB_USERNAME'),
|
||||
password: configService.get('DB_PASSWORD'),
|
||||
database: configService.get('DB_DATABASE'),
|
||||
entities: [
|
||||
ClientEntity,
|
||||
AddressSettingsEntity,
|
||||
BlocksEntity,
|
||||
ClientStatisticsEntity,
|
||||
RpcBlockEntity,
|
||||
TelegramSubscriptionsEntity
|
||||
],
|
||||
synchronize: true,
|
||||
logging: false,
|
||||
|
||||
}
|
||||
},
|
||||
imports: [ConfigModule],
|
||||
inject: [ConfigService]
|
||||
}),
|
||||
CacheModule.register(),
|
||||
ScheduleModule.forRoot(),
|
||||
|
||||
@@ -30,7 +30,7 @@ export class ClientController {
|
||||
return {
|
||||
sessionId: worker.sessionId,
|
||||
name: worker.clientName,
|
||||
bestDifficulty: worker.bestDifficulty.toFixed(2),
|
||||
bestDifficulty: parseFloat(worker.bestDifficulty as any).toFixed(2),
|
||||
hashRate: worker.hashRate,
|
||||
startTime: worker.startTime,
|
||||
lastSeen: worker.updatedAt
|
||||
|
||||
@@ -17,16 +17,7 @@ export class AppService implements OnModuleInit {
|
||||
}
|
||||
|
||||
async onModuleInit() {
|
||||
// if (process.env.NODE_APP_INSTANCE == '0') {
|
||||
// await this.dataSource.query(`VACUUM;`);
|
||||
// }
|
||||
//https://phiresky.github.io/blog/2020/sqlite-performance-tuning/
|
||||
//500 MB DB cache
|
||||
await this.dataSource.query(`PRAGMA cache_size = -500000;`);
|
||||
//Normal is still completely corruption safe in WAL mode, and means only WAL checkpoints have to wait for FSYNC.
|
||||
await this.dataSource.query(`PRAGMA synchronous = off;`);
|
||||
//6Gb
|
||||
await this.dataSource.query(`PRAGMA mmap_size = 6000000000;`);
|
||||
|
||||
}
|
||||
|
||||
@Interval(1000 * 60 * 60)
|
||||
|
||||
Reference in New Issue
Block a user