From b4714270a9b33403ae064d687fe538f5b30ff9d3 Mon Sep 17 00:00:00 2001 From: DevSoftTech Server Date: Fri, 14 Aug 2026 13:14:13 +0530 Subject: [PATCH] Add combined ABDM log prune command and api log index --- app/Commands/AbdmPruneLogs.php | 108 ++++++++++++++++++ ...-000002_AddCreatedAtIndexToAbdmApiLogs.php | 43 +++++++ scripts/install_hms_cron.sh | 2 +- 3 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 app/Commands/AbdmPruneLogs.php create mode 100644 app/Database/Migrations/2026-08-14-000002_AddCreatedAtIndexToAbdmApiLogs.php diff --git a/app/Commands/AbdmPruneLogs.php b/app/Commands/AbdmPruneLogs.php new file mode 100644 index 0000000..92c43c6 --- /dev/null +++ b/app/Commands/AbdmPruneLogs.php @@ -0,0 +1,108 @@ + 'Retention window in days (default: 2).', + '--table' => 'all | abdm_api_logs | abdm_hiu_workflows (default: all).', + '--batch' => 'Rows deleted per statement (default: 2000).', + '--dry-run' => 'Report what would be deleted without deleting.', + '--optimize' => 'Rebuild the table afterwards to release disk space.', + ]; + + /** + * Extra guard per table, ANDed with the age filter. + * HIU rows with a future retry slot are still in flight for abdm:hiu-retry. + */ + private const GUARDS = [ + 'abdm_api_logs' => '', + 'abdm_hiu_workflows' => ' AND (is_retryable = 0 OR next_retry_at IS NULL OR next_retry_at <= NOW())', + ]; + + public function run(array $params) + { + $days = (int) (CLI::getOption('days') ?? 2); + if ($days <= 0) { + $days = 2; + } + + $batch = (int) (CLI::getOption('batch') ?? 2000); + if ($batch <= 0) { + $batch = 2000; + } + + $dryRun = CLI::getOption('dry-run') !== null; + $optimize = CLI::getOption('optimize') !== null; + + $requested = trim((string) (CLI::getOption('table') ?? 'all')); + $tables = $requested === 'all' || $requested === '1' + ? array_keys(self::GUARDS) + : [$requested]; + + foreach ($tables as $table) { + if (! array_key_exists($table, self::GUARDS)) { + CLI::error('Unsupported table: ' . $table); + + return; + } + } + + $db = \Config\Database::connect(); + $cutoff = date('Y-m-d H:i:s', strtotime('-' . $days . ' days')); + + CLI::write('ABDM Log Prune' . ($dryRun ? ' (dry-run)' : ''), 'yellow'); + CLI::write('Retention: ' . $days . ' day(s) — cutoff ' . $cutoff); + + foreach ($tables as $table) { + if (! $db->tableExists($table)) { + CLI::write($table . ': table not found, skipped.'); + continue; + } + + $hasUpdatedAt = in_array('updated_at', $db->getFieldNames($table) ?? [], true); + $age = $hasUpdatedAt + ? '((created_at IS NOT NULL AND created_at < ?) OR (created_at IS NULL AND updated_at IS NOT NULL AND updated_at < ?))' + : '(created_at IS NOT NULL AND created_at < ?)'; + $binds = $hasUpdatedAt ? [$cutoff, $cutoff] : [$cutoff]; + $where = $age . self::GUARDS[$table]; + + $total = (int) ($db->query('SELECT COUNT(*) AS c FROM ' . $table . ' WHERE ' . $where, $binds) + ->getRowArray()['c'] ?? 0); + + if ($total === 0) { + CLI::write($table . ': nothing to prune.'); + continue; + } + + if ($dryRun) { + CLI::write($table . ': ' . $total . ' row(s) eligible.'); + continue; + } + + $deleted = 0; + do { + $db->query('DELETE FROM ' . $table . ' WHERE ' . $where . ' ORDER BY id LIMIT ' . $batch, $binds); + $affected = (int) $db->affectedRows(); + $deleted += $affected; + } while ($affected > 0); + + CLI::write($table . ': deleted ' . $deleted . ' row(s).', 'green'); + + if ($optimize) { + // InnoDB reports "recreate + analyze"; that rebuild is what frees the file space. + $db->query('OPTIMIZE TABLE ' . $table); + CLI::write($table . ': rebuilt, disk space released.', 'green'); + } + } + } +} diff --git a/app/Database/Migrations/2026-08-14-000002_AddCreatedAtIndexToAbdmApiLogs.php b/app/Database/Migrations/2026-08-14-000002_AddCreatedAtIndexToAbdmApiLogs.php new file mode 100644 index 0000000..c435f0e --- /dev/null +++ b/app/Database/Migrations/2026-08-14-000002_AddCreatedAtIndexToAbdmApiLogs.php @@ -0,0 +1,43 @@ +db->tableExists('abdm_api_logs')) { + return; + } + + // Only channel/status/event_type were indexed, so retention pruning by age would table-scan. + if (! $this->indexExists('abdm_api_logs', 'idx_abdm_api_logs_created_at')) { + $this->db->query('CREATE INDEX idx_abdm_api_logs_created_at ON abdm_api_logs (created_at)'); + } + } + + public function down(): void + { + if (! $this->db->tableExists('abdm_api_logs')) { + return; + } + + if ($this->indexExists('abdm_api_logs', 'idx_abdm_api_logs_created_at')) { + $this->db->query('DROP INDEX idx_abdm_api_logs_created_at ON abdm_api_logs'); + } + } + + private function indexExists(string $table, string $indexName): bool + { + $rows = $this->db->query('SHOW INDEX FROM ' . $table)->getResultArray(); + foreach ($rows as $row) { + if (($row['Key_name'] ?? '') === $indexName) { + return true; + } + } + + return false; + } +} diff --git a/scripts/install_hms_cron.sh b/scripts/install_hms_cron.sh index 04cb078..598af72 100644 --- a/scripts/install_hms_cron.sh +++ b/scripts/install_hms_cron.sh @@ -38,7 +38,7 @@ $BLOCK_START * * * * * cd $PROJECT_DIR && $PHP_BIN spark snomed:process-coding >> $LOG_DIR/snomed_process.log 2>&1 */2 * * * * cd $PROJECT_DIR && $PHP_BIN spark abdm:hiu-poll --limit=30 >> $LOG_DIR/abdm_hiu_poll.log 2>&1 */5 * * * * cd $PROJECT_DIR && $PHP_BIN spark abdm:hiu-retry --limit=20 >> $LOG_DIR/abdm_hiu_retry.log 2>&1 -30 2 * * * cd $PROJECT_DIR && $PHP_BIN spark abdm:hiu-prune --days=2 --optimize >> $LOG_DIR/abdm_hiu_prune.log 2>&1 +30 2 * * * cd $PROJECT_DIR && $PHP_BIN spark abdm:prune-logs --days=2 --optimize >> $LOG_DIR/abdm_prune_logs.log 2>&1 $BLOCK_END EOF