Files
AFC-Demo/app/Jobs/SyncBackendDataPool.php
T

235 lines
7.3 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class SyncBackendDataPool implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
use SerializesModels;
public int $timeout = 3600; // 1 hour timeout
public int $tries = 3;
/**
* Create a new job instance.
*/
public function __construct(
public bool $fullSync = true,
public ?int $batchSize = 1000,
) {
}
/**
* Execute the job.
*/
public function handle(): void
{
$startTime = now();
Log::info('SyncBackendDataPool: Starting sync', [
'full_sync' => $this->fullSync,
'batch_size' => $this->batchSize,
]);
try {
if ($this->fullSync) {
$this->performFullSync();
} else {
$this->performIncrementalSync();
}
$duration = now()->diffInSeconds($startTime);
Log::info('SyncBackendDataPool: Sync completed successfully', [
'duration_seconds' => $duration,
]);
} catch (\Exception $e) {
Log::error('SyncBackendDataPool: Sync failed', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw $e;
}
}
/**
* Perform full synchronization - truncates and rebuilds the data pool
*/
private function performFullSync(): void
{
DB::transaction(function () {
// Truncate existing data pool
DB::table('backend_data_pool')->truncate();
Log::info('SyncBackendDataPool: Truncated backend_data_pool');
// Insert all data in batches
$this->insertDataInBatches();
});
}
/**
* Perform incremental synchronization - only syncs new/updated records
*/
private function performIncrementalSync(): void
{
DB::transaction(function () {
// Get the last synced timestamp
$lastSyncedAt = DB::table('backend_data_pool')
->max('synced_at');
Log::info('SyncBackendDataPool: Incremental sync', [
'last_synced_at' => $lastSyncedAt,
]);
// Insert new records
$this->insertDataInBatches($lastSyncedAt);
});
}
/**
* Insert data from backend schema into public.backend_data_pool in batches
*/
private function insertDataInBatches(?string $lastSyncedAt = null): void
{
$totalInserted = 0;
$offset = 0;
while (true) {
// Build the query
$query = $this->buildSyncQuery($lastSyncedAt)
->limit($this->batchSize)
->offset($offset);
$records = $query->get();
if ($records->isEmpty()) {
break;
}
// Transform records for insertion
$dataToInsert = $records->map(function ($record) {
return [
'transaction_id' => $record->transaction_id,
'corporate_entity' => $record->corporate_entity,
'corporate_counterparty' => $record->corporate_counterparty,
'tx_date' => $record->tx_date,
'tx_amount' => $record->tx_amount,
'tx_currency' => $record->tx_currency,
'tx_purpose' => $record->tx_purpose,
'tx_country_outgoing' => $record->tx_country_outgoing,
'tx_country_incoming' => $record->tx_country_incoming,
'source_file' => $record->source_file,
'raw_payload' => $record->raw_payload,
'status' => $record->status,
'created_at' => $record->created_at,
'last_modified_at' => $record->last_modified_at,
'risk_score' => $record->risk_score,
'risk_level' => $record->risk_level,
'risk_details_json' => $record->risk_details_json,
'prompt_id' => $record->prompt_id,
'output_key' => $record->output_key,
'content' => $record->content,
'run_id' => $record->run_id,
'synced_at' => now(),
];
})->toArray();
// Insert batch
DB::table('backend_data_pool')->insert($dataToInsert);
$totalInserted += count($dataToInsert);
Log::info('SyncBackendDataPool: Batch inserted', [
'batch_size' => count($dataToInsert),
'total_inserted' => $totalInserted,
]);
$offset += $this->batchSize;
}
Log::info('SyncBackendDataPool: All batches inserted', [
'total_records' => $totalInserted,
]);
}
/**
* Build the sync query that replicates the SQL:
* SELECT * FROM backend.transactions t
* LEFT JOIN backend.transaction_outputs tout ON t.id = tout.transaction_id
* WHERE t.status = 'done'
* ORDER BY transaction_id, prompt_id
*/
private function buildSyncQuery(?string $lastSyncedAt = null): \Illuminate\Database\Query\Builder
{
$query = DB::connection('pgsql')
->table('backend.transactions as t')
->leftJoin('backend.transaction_outputs as tout', 't.id', '=', 'tout.transaction_id')
->select([
't.id as transaction_id',
't.corporate_entity',
't.corporate_counterparty',
't.tx_date',
't.tx_amount',
't.tx_currency',
't.tx_purpose',
't.tx_country_outgoing',
't.tx_country_incoming',
't.source_file',
't.raw_payload',
't.status',
't.created_at',
't.last_modified_at',
't.risk_score',
't.risk_level',
't.risk_details_json',
'tout.prompt_id',
'tout.output_key',
'tout.content',
'tout.run_id',
])
->where('t.status', '=', 'done')
->orderBy('t.id')
->orderBy('tout.prompt_id');
// For incremental sync, only get records modified after last sync
if ($lastSyncedAt !== null) {
$query->where('t.last_modified_at', '>', $lastSyncedAt);
}
return $query;
}
/**
* Get statistics about the sync
*/
public function getStats(): array
{
return [
'backend_transactions_done' => DB::connection('pgsql')
->table('backend.transactions')
->where('status', 'done')
->count(),
'backend_transaction_outputs' => DB::connection('pgsql')
->table('backend.transaction_outputs')
->count(),
'backend_data_pool_records' => DB::table('backend_data_pool')->count(),
'last_synced_at' => DB::table('backend_data_pool')->max('synced_at'),
];
}
}