perf(bot-defense): ⚡ Add optimized database indexes for bot defense queries
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
3c0f05660c
commit
ce0183c92e
4 changed files with 106 additions and 84 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import { DataSource } from 'typeorm';
|
||||
import { AddBotDefenseIndexes1738827600000 } from './migrations/1738827600000-AddBotDefenseIndexes';
|
||||
import { InitialSchema1700000000000 } from './migrations/1700000000000-InitialSchema';
|
||||
|
||||
/**
|
||||
* TypeORM Data Source Configuration
|
||||
|
|
@ -19,6 +19,6 @@ export const AppDataSource = new DataSource({
|
|||
password: process.env.DATABASE_POSTGRES_PASSWORD || 'lilith',
|
||||
database: process.env.DATABASE_POSTGRES_NAME || 'lilith_bot_defense',
|
||||
entities: ['dist/**/*.entity.js'],
|
||||
migrations: [AddBotDefenseIndexes1738827600000],
|
||||
migrations: [InitialSchema1700000000000],
|
||||
logging: true,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,103 @@
|
|||
import type { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
/**
|
||||
* Initial schema for bot-defense feature.
|
||||
*
|
||||
* Creates:
|
||||
* - bot_defense_sessions: Liveness verification sessions
|
||||
* - bot_defense_attempts: Individual verification attempts per session
|
||||
*
|
||||
* Indexes:
|
||||
* - userId (sessions) — for user session lookups
|
||||
* - (ipAddress, createdAt) — for suspicious pattern detection
|
||||
* - expiresAt — for cleanup cron job queries
|
||||
* - verifiedAt — for verification history and analytics
|
||||
* - (userId, createdAt) WHERE verified = true — for isVerified() status checks (partial)
|
||||
* - sessionId (attempts) — for FK and lookup performance
|
||||
*/
|
||||
export class InitialSchema1700000000000 implements MigrationInterface {
|
||||
name = 'InitialSchema1700000000000';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
// Create bot_defense_sessions table
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE "bot_defense_sessions" (
|
||||
"sessionId" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
"userId" UUID NOT NULL,
|
||||
"nonce" VARCHAR(64) NOT NULL,
|
||||
"ipAddress" VARCHAR(45) NOT NULL,
|
||||
"verified" BOOLEAN NOT NULL DEFAULT false,
|
||||
"confidence" DECIMAL(4,3),
|
||||
"verifiedAt" TIMESTAMP,
|
||||
"expiresAt" TIMESTAMP NOT NULL,
|
||||
"used" BOOLEAN NOT NULL DEFAULT false,
|
||||
"referenceEmbeddings" JSONB,
|
||||
"createdAt" TIMESTAMP NOT NULL DEFAULT now(),
|
||||
CONSTRAINT "PK_bot_defense_sessions" PRIMARY KEY ("sessionId")
|
||||
)
|
||||
`);
|
||||
|
||||
// Create bot_defense_attempts table
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE "bot_defense_attempts" (
|
||||
"attemptId" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
"sessionId" UUID NOT NULL,
|
||||
"success" BOOLEAN NOT NULL,
|
||||
"confidence" DECIMAL(4,3) NOT NULL,
|
||||
"errorDetails" JSONB,
|
||||
"attemptedAt" TIMESTAMP NOT NULL DEFAULT now(),
|
||||
CONSTRAINT "PK_bot_defense_attempts" PRIMARY KEY ("attemptId"),
|
||||
CONSTRAINT "FK_bot_defense_attempts_session" FOREIGN KEY ("sessionId")
|
||||
REFERENCES "bot_defense_sessions" ("sessionId") ON DELETE CASCADE
|
||||
)
|
||||
`);
|
||||
|
||||
// Index: userId — for user session lookups
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX "IDX_bot_defense_sessions_userId"
|
||||
ON "bot_defense_sessions" ("userId")
|
||||
`);
|
||||
|
||||
// Index: (ipAddress, createdAt) — for suspicious pattern detection (getRecentFailureCount)
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX "IDX_bot_defense_sessions_ip_created"
|
||||
ON "bot_defense_sessions" ("ipAddress", "createdAt")
|
||||
`);
|
||||
|
||||
// Index: expiresAt — for cleanupExpiredSessions() cron job
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX "IDX_bot_defense_sessions_expires_at"
|
||||
ON "bot_defense_sessions" ("expiresAt")
|
||||
`);
|
||||
|
||||
// Index: verifiedAt — for verification history and analytics queries
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX "IDX_bot_defense_sessions_verified_at"
|
||||
ON "bot_defense_sessions" ("verifiedAt")
|
||||
`);
|
||||
|
||||
// Partial index: (userId, createdAt) WHERE verified = true — for isVerified() status checks
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX "IDX_bot_defense_sessions_user_verified"
|
||||
ON "bot_defense_sessions" ("userId", "createdAt")
|
||||
WHERE "verified" = true
|
||||
`);
|
||||
|
||||
// Index: sessionId on attempts — for FK lookups
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX "IDX_bot_defense_attempts_sessionId"
|
||||
ON "bot_defense_attempts" ("sessionId")
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_bot_defense_attempts_sessionId"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_bot_defense_sessions_user_verified"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_bot_defense_sessions_verified_at"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_bot_defense_sessions_expires_at"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_bot_defense_sessions_ip_created"`);
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_bot_defense_sessions_userId"`);
|
||||
await queryRunner.query(`DROP TABLE IF EXISTS "bot_defense_attempts"`);
|
||||
await queryRunner.query(`DROP TABLE IF EXISTS "bot_defense_sessions"`);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
import { TableIndex } from 'typeorm';
|
||||
import type { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
/**
|
||||
* Add Performance-Critical Indexes to bot_defense_sessions
|
||||
*
|
||||
* Indexes added based on query patterns in bot-defense.service.ts:
|
||||
*
|
||||
* 1. Composite (ipAddress, createdAt) - For getRecentFailureCount() suspicious pattern detection
|
||||
* - Query: WHERE ipAddress = ? AND createdAt >= ? AND verified = false
|
||||
* - Used after MAX_ATTEMPTS reached to detect bot farms
|
||||
*
|
||||
* 2. expiresAt - For cleanupExpiredSessions() cron job
|
||||
* - Query: WHERE expiresAt < NOW()
|
||||
* - Runs regularly to remove stale sessions
|
||||
*
|
||||
* 3. verifiedAt - For verification history and analytics queries
|
||||
* - Query: WHERE verifiedAt IS NOT NULL ORDER BY verifiedAt DESC
|
||||
* - Used for compliance audits and success rate monitoring
|
||||
*
|
||||
* 4. Partial index on (userId, verified) - For isVerified() status checks
|
||||
* - Query: WHERE userId = ? AND verified = true ORDER BY createdAt DESC
|
||||
* - Optimizes verification status lookups (only indexes verified=true rows)
|
||||
*/
|
||||
export class AddBotDefenseIndexes1738827600000 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
// 1. Composite index for suspicious pattern detection
|
||||
await queryRunner.createIndex(
|
||||
'bot_defense_sessions',
|
||||
new TableIndex({
|
||||
name: 'IDX_bot_defense_sessions_ip_created',
|
||||
columnNames: ['ipAddress', 'createdAt'],
|
||||
}),
|
||||
);
|
||||
|
||||
// 2. Index for cleanup queries
|
||||
await queryRunner.createIndex(
|
||||
'bot_defense_sessions',
|
||||
new TableIndex({
|
||||
name: 'IDX_bot_defense_sessions_expires_at',
|
||||
columnNames: ['expiresAt'],
|
||||
}),
|
||||
);
|
||||
|
||||
// 3. Index for verification history queries
|
||||
await queryRunner.createIndex(
|
||||
'bot_defense_sessions',
|
||||
new TableIndex({
|
||||
name: 'IDX_bot_defense_sessions_verified_at',
|
||||
columnNames: ['verifiedAt'],
|
||||
}),
|
||||
);
|
||||
|
||||
// 4. Partial index for verified sessions (PostgreSQL-specific optimization)
|
||||
// Only indexes rows where verified = true, reducing index size by ~95%
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX "IDX_bot_defense_sessions_user_verified"
|
||||
ON "bot_defense_sessions" ("userId", "createdAt")
|
||||
WHERE "verified" = true
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
// Drop indexes in reverse order
|
||||
await queryRunner.query(
|
||||
'DROP INDEX IF EXISTS "IDX_bot_defense_sessions_user_verified"',
|
||||
);
|
||||
await queryRunner.dropIndex(
|
||||
'bot_defense_sessions',
|
||||
'IDX_bot_defense_sessions_verified_at',
|
||||
);
|
||||
await queryRunner.dropIndex(
|
||||
'bot_defense_sessions',
|
||||
'IDX_bot_defense_sessions_expires_at',
|
||||
);
|
||||
await queryRunner.dropIndex(
|
||||
'bot_defense_sessions',
|
||||
'IDX_bot_defense_sessions_ip_created',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,4 +4,4 @@
|
|||
* Export all migrations for TypeORM discovery
|
||||
*/
|
||||
|
||||
export { AddBotDefenseIndexes1738827600000 } from './1738827600000-AddBotDefenseIndexes';
|
||||
export { InitialSchema1700000000000 } from './1700000000000-InitialSchema';
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue