diff --git a/ka-note/VERSION b/ka-note/VERSION index 6581d43..911e8ab 100644 --- a/ka-note/VERSION +++ b/ka-note/VERSION @@ -1 +1 @@ -1.2.17 \ No newline at end of file +1.2.18 \ No newline at end of file diff --git a/ka-note/deploy.ps1 b/ka-note/deploy.ps1 index 92b96a6..2796467 100644 --- a/ka-note/deploy.ps1 +++ b/ka-note/deploy.ps1 @@ -42,6 +42,22 @@ $BackupDir = Join-Path $PSScriptRoot "backups" $null = New-Item -ItemType Directory -Path $BackupDir -Force $BackupFile = Join-Path $BackupDir ("ka-note-pre-deploy-$VERSION.db") +# Flush WAL before download so the snapshot is consistent (WAL file not included in Kudu download). +$AppUrl = "https://$APP.azurewebsites.net" +if ($env:KA_NOTE_DEPLOY_API_KEY) { + Write-Host " Flushing WAL checkpoint before download..." -ForegroundColor DarkGray + try { + $cpResult = Invoke-RestMethod -Uri "$AppUrl/api/admin/checkpoint" -Method POST ` + -Headers @{ Authorization = "Bearer $env:KA_NOTE_DEPLOY_API_KEY" } ` + -UseBasicParsing -TimeoutSec 30 + Write-Host " Checkpoint: $($cpResult.checkpointed)/$($cpResult.log) pages" -ForegroundColor DarkGray + } catch { + Write-Host " Checkpoint call failed (continuing anyway): $_" -ForegroundColor Yellow + } +} else { + Write-Host " KA_NOTE_DEPLOY_API_KEY not set, skipping WAL checkpoint" -ForegroundColor Yellow +} + Write-Host " Downloading ka-note.db from prod..." -ForegroundColor DarkGray try { Invoke-WebRequest -Uri $KuduDbUrl ` @@ -107,7 +123,6 @@ az webapp config appsettings set --name $APP --resource-group $RG --settings ` AZURE_TENANT_ID=$env:AZURE_TENANT_ID | Out-Null Write-Host "=== Graceful DB shutdown ===" -ForegroundColor Cyan -$AppUrl = "https://$APP.azurewebsites.net" if ($env:KA_NOTE_DEPLOY_API_KEY) { try { Write-Host " Waking up app..." -ForegroundColor DarkGray @@ -117,7 +132,9 @@ if ($env:KA_NOTE_DEPLOY_API_KEY) { -Headers @{ Authorization = "Bearer $env:KA_NOTE_DEPLOY_API_KEY" } ` -UseBasicParsing -TimeoutSec 30 Write-Host " Shutdown response: $($shutdownResult.message)" -ForegroundColor DarkGray - Start-Sleep -Seconds 3 + # Shutdown does WAL checkpoint synchronously before responding, then closes DB after 500ms. + # Wait 10s to ensure clean close before Azure kills the container. + Start-Sleep -Seconds 10 } catch { Write-Host " Shutdown call failed (continuing anyway): $_" -ForegroundColor Yellow } diff --git a/ka-note/server/ka-note.db-shm b/ka-note/server/ka-note.db-shm index 35530bc..4e0fd2b 100644 Binary files a/ka-note/server/ka-note.db-shm and b/ka-note/server/ka-note.db-shm differ diff --git a/ka-note/server/ka-note.db-wal b/ka-note/server/ka-note.db-wal index 5752428..24bc4c6 100644 Binary files a/ka-note/server/ka-note.db-wal and b/ka-note/server/ka-note.db-wal differ diff --git a/ka-note/server/src/routes/admin.ts b/ka-note/server/src/routes/admin.ts index d3134fd..e62f996 100644 --- a/ka-note/server/src/routes/admin.ts +++ b/ka-note/server/src/routes/admin.ts @@ -33,20 +33,37 @@ admin.get('/stats', handle('admin/stats', async (c) => { return c.json({ contextCount, topicCount }); })); +// Flush WAL to main DB file. Call before downloading the DB via Kudu. +admin.post('/checkpoint', handle('admin/checkpoint', async (c) => { + console.log('[admin] checkpoint requested'); + const result = sqlite.pragma('wal_checkpoint(FULL)', { simple: false }) as { blocking: number; log: number; checkpointed: number }[]; + const { log, checkpointed } = result[0]; + console.log(`[admin] checkpoint done: ${checkpointed}/${log} pages`); + return c.json({ ok: true, log, checkpointed }); +})); + // Graceful shutdown: checkpoint WAL, close DB, exit. // Call from deploy pipeline before restarting the container. admin.post('/shutdown', handle('admin/shutdown', async (c) => { console.log('[admin] shutdown requested via API'); + // Checkpoint synchronously before sending response — guarantees WAL is flushed + // even if the container is killed shortly after. + try { + sqlite.pragma('wal_checkpoint(TRUNCATE)'); + console.log('[admin] WAL checkpointed'); + } catch (e) { + console.error('[admin] checkpoint error during shutdown:', e); + } + // Close DB and exit after response has been flushed (500ms buffer). setTimeout(() => { try { - sqlite.pragma('wal_checkpoint(TRUNCATE)'); sqlite.close(); console.log('[admin] database closed cleanly, exiting'); } catch (e) { - console.error('[admin] error during shutdown:', e); + console.error('[admin] close error during shutdown:', e); } process.exit(0); - }, 100); + }, 500); return c.json({ ok: true, message: 'shutting down' }); }));