fix(api): восстановить разделы PostgreSQL в одной транзакции

This commit is contained in:
Denozordec committed 2026-10-07 23:28:16 +07:00
1 parent c24ad7810c
commit f3e205950f
10 files changed
+261 -10

No files matched your search

+1 -1
View File
@@ -37,7 +37,7 @@
"test:ospf-optimizer": "tsx src/services/ospf-optimizer-identity.test.ts && tsx src/services/ospf-optimizer-metrics.test.ts && tsx src/services/ospf-optimizer-plan.test.ts && tsx src/routes/ospf-optimizer.test.ts",
"test:containers": "tsx src/services/containers-state.test.ts && tsx src/routes/containers-state.test.ts",
"test:certificates": "tsx src/services/certificate-issue-execution.test.ts && tsx src/services/certificate-issue-request.test.ts && tsx src/routes/certificates-issue.test.ts && tsx src/services/certificate-issue-dto.test.ts && tsx src/services/certificate-parse.test.ts && tsx src/services/certificates-state.test.ts && tsx src/routes/certificates-state.test.ts",
"test:system-database": "tsx src/services/system-database-cache.test.ts && tsx src/services/database-background-work.test.ts && tsx src/services/database-request-gate.test.ts && tsx src/services/traffic-flow-worker-shutdown.test.ts && tsx src/services/system-database-restore-execution.test.ts && tsx src/services/system-database-operation.test.ts && tsx src/routes/system-database.test.ts && tsx src/plugins/auth.system-database.test.ts"
"test:system-database": "tsx src/services/system-database-partition-restore.test.ts && tsx src/services/system-database-cache.test.ts && tsx src/services/database-background-work.test.ts && tsx src/services/database-request-gate.test.ts && tsx src/services/traffic-flow-worker-shutdown.test.ts && tsx src/services/system-database-restore-execution.test.ts && tsx src/services/system-database-operation.test.ts && tsx src/routes/system-database.test.ts && tsx src/plugins/auth.system-database.test.ts"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.888.0",
+12 -5
View File
@@ -1,6 +1,6 @@
import { randomUUID } from "node:crypto"
import { spawn } from "node:child_process"
import { mkdir, rm, writeFile } from "node:fs/promises"
import { chmod, mkdir, rm, writeFile } from "node:fs/promises"
import os from "node:os"
import path from "node:path"
import { executeSystemDatabaseRestore, validateSystemDatabaseRestoreArchive, type SystemDatabaseRestoreOutcome } from "./system-database-restore-execution.js"
@@ -11,6 +11,7 @@ import { executeDatabaseOperation, type DatabaseOperationWarnings } from "./syst
import { restartSchedulerAfterDatabaseOperation, stopSchedulerForDatabaseOperation } from "./scheduler.js"
import { pauseRipeBackgroundWork, resumeRipeBackgroundWork } from "./traffic-flow-ripe.js"
import { invalidateSystemDatabaseCaches } from "./system-database-cache.js"
import { restorePartitionedDatabase } from "./system-database-partition-restore.js"
import {
restartTrafficFlowListenerAfterDatabaseOperation,
stopTrafficFlowListenerForDatabaseOperation,
@@ -78,12 +79,18 @@ export async function restoreSystemDatabaseBackup(buffer: Buffer): Promise<Syste
return await withDatabaseOperation(() => executeSystemDatabaseRestore(buffer, env.DATABASE_URL, {
allocate: async () => {
const tempDir = path.join(os.tmpdir(), "mmapp-db-restore")
await mkdir(tempDir, { recursive: true })
await mkdir(tempDir, { recursive: true, mode: 0o700 })
await chmod(tempDir, 0o700)
return path.join(tempDir, `restore-${randomUUID()}.dump`)
},
write: (file, data) => writeFile(file, data),
run: async args => { await spawnCollected("pg_restore", args) },
remove: file => rm(file, { force: true }),
write: (file, data) => writeFile(file, data, { mode: 0o600 }),
run: async args => {
await restorePartitionedDatabase(args[args.length - 1], env.DATABASE_URL, {
command: spawnCollected,
prepareSql: file => writeFile(file, "", { mode: 0o600 }),
})
},
remove: async file => { await Promise.all([rm(file, { force: true }), rm(`${file}.sql`, { force: true })]) },
invalidate: invalidateSystemDatabaseCaches,
}))
}
@@ -0,0 +1,47 @@
import assert from "node:assert/strict"
import { test } from "node:test"
import { partitionRestorePrelude, restorePartitionedDatabase } from "./system-database-partition-restore.js"
const toc = "1; 1259 1 TABLE public servers mmapp\n2; 1259 2 TABLE public schema_migrations mmapp\n3; 1259 3 TABLE public server_snapshots mmapp\n"
test("preparation covers only known application parents present in the archive", () => {
const sql = partitionRestorePrelude(toc + "4; 1259 4 TABLE other flow_buckets owner\n5; 1259 5 TABLE public custom_table owner\n")
assert.ok(sql.includes("IN ('server_snapshots')"))
assert.ok(sql.includes("c.conparentid = 0"))
assert.ok(sql.includes("format('ALTER TABLE %I.%I DROP CONSTRAINT %I CASCADE'"))
assert.equal(sql.includes("flow_buckets"), false)
assert.equal(sql.includes("custom_table"), false)
assert.throws(() => partitionRestorePrelude("1; 1259 1 TABLE public unrelated owner\n"), /обязательные таблицы/)
})
test("archive decoding precedes one transactional psql execution with fail-fast SQL", async () => {
const calls: string[] = []
await restorePartitionedDatabase("fixture.dump", "fixture-db", {
prepareSql: async file => { assert.equal(file, "fixture.dump.sql"); calls.push("prepare") },
command: async (command, args) => {
calls.push(command)
if (command === "pg_restore" && args[0] === "--list") return Buffer.from(toc)
if (command === "psql") {
assert.deepEqual(args.slice(0, 6), ["-X", "--single-transaction", "--set", "ON_ERROR_STOP=on", "-d", "fixture-db"])
assert.equal(args[6], "-c"); assert.ok(args[7].includes("DROP CONSTRAINT"))
assert.deepEqual(args.slice(8), ["-f", "fixture.dump.sql"])
}
return Buffer.alloc(0)
},
})
assert.deepEqual(calls, ["pg_restore", "prepare", "pg_restore", "psql"])
})
test("invalid or undecodable archive never starts target SQL", async () => {
for (const phase of ["list", "decode"]) {
let executions = 0
await assert.rejects(restorePartitionedDatabase("fixture.dump", "fixture-db", {
prepareSql: async () => {},
command: async (command, args) => {
if (command === "psql") executions++
if ((phase === "list" && args[0] === "--list") || (phase === "decode" && args[0] === "--clean")) throw Error("archive failed")
return Buffer.from(toc)
},
}), /archive failed/)
assert.equal(executions, 0)
}
})
@@ -0,0 +1,47 @@
import { PARTITION_SPECS } from "../db/partitions.js"
/** Only application parents actually present in the archive may be prepared. */
export function partitionRestorePrelude(toc: string): string {
const tables = new Set<string>()
for (const line of toc.split(/\r?\n/)) {
const match = /^\d+;\s+\d+\s+\d+\s+TABLE public ([a-z_][a-z_0-9]*)\s/.exec(line)
if (match) tables.add(match[1])
}
if (!tables.has("servers") || !tables.has("schema_migrations")) {
throw new Error("Архив не содержит обязательные таблицы базы приложения")
}
const parents = PARTITION_SPECS.map(s => s.parent).filter(name => tables.has(name))
if (!parents.length) return "SELECT 1;"
return `DO $mm_restore$
DECLARE r record;
BEGIN
FOR r IN
SELECT n.nspname, t.relname, c.conname
FROM pg_constraint c
JOIN pg_class t ON t.oid = c.conrelid
JOIN pg_namespace n ON n.oid = t.relnamespace
WHERE n.nspname = 'public' AND t.relkind = 'p'
AND c.conparentid = 0 AND c.contype IN ('p', 'u')
AND t.relname IN (${parents.map(name => `'${name}'`).join(", ")})
LOOP
EXECUTE format('ALTER TABLE %I.%I DROP CONSTRAINT %I CASCADE', r.nspname, r.relname, r.conname);
END LOOP;
END $mm_restore$;`
}
interface Dependencies {
command: (command: string, args: string[]) => Promise<Buffer>
prepareSql: (file: string) => Promise<void>
}
export async function restorePartitionedDatabase(file: string, databaseUrl: string, deps: Dependencies): Promise<void> {
const toc = await deps.command("pg_restore", ["--list", file])
const prelude = partitionRestorePrelude(toc.toString("utf8"))
const sqlFile = `${file}.sql`
await deps.prepareSql(sqlFile)
// Fully decode before connecting to the target database; never rewrite archive SQL/data.
await deps.command("pg_restore", ["--clean", "--if-exists", "--file", sqlFile, file])
// psql wraps both -c and -f in ONE transaction and rolls back on a SQL error.
await deps.command("psql", ["-X", "--single-transaction", "--set", "ON_ERROR_STOP=on",
"-d", databaseUrl, "-c", prelude, "-f", sqlFile])
}
+5 -1
View File
@@ -23,7 +23,7 @@
Реализация: [bootstrap](../backend/src/db/bootstrap.ts), [migrations](../backend/src/db/migrate.ts), [0008](../backend/drizzle/0008_alert_outbox_outcomes.sql), [0009](../backend/drizzle/0009_alert_outbox_resolution.sql), [database operation](../backend/src/services/system-database-operation.ts), [restore execution](../backend/src/services/system-database-restore-execution.ts).
Приложение использует pg_dump -Fc и pg_restore --single-transaction --clean --if-exists. Транзакционный режим описан в [документации PostgreSQL](https://www.postgresql.org/docs/current/app-pgrestore.html). Проверка пяти байтов PGDMP проверяет заголовок, но не полноту и не доверие к архиву. Для rollback нужен сценарий с ошибкой исполнения restore, а не только отказ HTTP до его начала.
После12.5.15 приложение используетpg_dump -Fc, проверкуpg_restore --list, генерациюpg_restore --clean --if-exists --file иодинpsql -X --single-transaction --set ON_ERROR_STOP=on -c partition parent preparation -f SQL. Предварительнаяподготовкаиrestore находятсявобщейтранзакции. Транзакционный режим описан в [документации PostgreSQL](https://www.postgresql.org/docs/current/app-pgrestore.html). Проверка пяти байтов PGDMP проверяет заголовок, но не полноту и не доверие к архиву. Для rollback нужен сценарий с ошибкой исполнения restore, а не только отказ HTTP до его начала.
В локальном блоке12.5.8 добавлено ожидание текущих scheduler jobs с закрытием admission, но этот код ещё не развёрнут на стенде673e5ba. Блок12.5.9 добавляет отдельный graceful shutdown worker для database operation, serial flush, pool.end и обязательные acknowledgement/exit0. Локальные protocol tests и настоящий isolated collector/UDP/blocked SQL drain12.5.10 прошли: [evidence](ux-ui/acceptance/netflow-worker-2026-10-07.json). Основной код на стенде ещё не обновлён; app-wide quiescence этим не подтверждена. Блок12.5.11 локально добавляет admission/drain всех API handlers иtraffic live streams; новые tests/build passed, на стенде patch не развёрнут. В12.5.12 локально добавлен общий background registry/drain дляbackup/audit/certificate/partition maintenance/cache work, атакжеpause RIPE enqueue. Tests/build passed; В12.5.13 добавленcache invalidation послеуспешногоrestore идоconsumer restart, локальныесовместныеhelpers tests passed. Всяцепочка требуетreal-PG integrated quiescence/restore12.5.14. Основнойстенд покаsource673e5ba. Другие экземпляры/хосты требуют отдельного контроля. PG-6 остаётся открытым до наблюдаемого подтверждения; восстановление стенда выполняется после его получения. Реальные тесты backend, способные очищать таблицы, запускаются только против выделенной тестовой БД.
@@ -93,3 +93,7 @@ BGP-prefix evaluator, отдельное live GRE расписание и recove
## Обнаруженный блокер restore — 2026-10-07
12.5.14 realAPI/sourcef1aac49 навыделеннойPG18.6 подтвердилadmission/drain,ноrestore400 из-заinheritedpartitionPRIMARY KEY при--clean. [Evidence/rollback controls](ux-ui/acceptance/real-restore-2026-10-07.json). PG-4 подтверждёнвграницахнастоящейSQLошибкииконтрольныхстрок/sequence/constraint; PG-3успех/последующийID иполныйPG-6 непроверены. Следующий12.5.15 исправляеттранзакционнуюочистку. Основнойruntime673e5ba сохранён.
## Restore исправлен — 2026-10-07
12.5.15 realAPI200/контрольархива/новыйID3 иinjectedCREATE INDEX400/rollback данных,sequence и1587constraints прошли: [evidence](ux-ui/acceptance/real-restore-fixed-2026-10-07.json). PG-3/4 принятывэтихграницах, Portal/UI/fullchecksum не проверены. Основнойruntime673e5ba сохранён; integratedPG-6/deployment ещёпредстоят. Приrestart ресурсныйcollector попыталсяопрашиватьdisabledserver3; исправление12.5.16.
+1 -1
View File
@@ -1,6 +1,6 @@
# План реализации UX/UI по пятичасовым окнам
Актуальное продолжение 2026-10-07:12.5.14 выполненкакреальнаяприёмка isolated API/sourcef1aac49. ОбнаружендефектPG-3:pg_restore --clean падаетнаinherited partition PRIMARY KEY. HTTP admission409/ожиданиеwrite иrollback контрольныхданныхподтверждены. Основнойruntime673e5ba сохранён. Следующий12.5.15 — исправитьтранзакционнуюочисткуразделов иповторитьrestore; PG-3/полныйPG-6 открыты.
Актуальное продолжение 2026-10-07:12.5.15 завершён — транзакционный partition-safe restore исправлен и прошёл realAPI200/контрольархива/новыйID3; injected CREATE INDEX failure400 сохранилданные/sequence/1587constraints.25tests,HTTP/JWT иbuild passed. Основнойruntime673e5ba не обновлён. Следующий12.5.16 — disabled-server collector defect, integrated PG-6 иdeployment; весьэтап12.5 остаётсяоткрытым.
Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-07. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат.
+13 -1
View File
@@ -1,6 +1,6 @@
# UX/UI: журнал реализации
Актуальное продолжение 2026-10-07:12.5.14 выполненкакреальнаяприёмка isolated API/sourcef1aac49. ОбнаружендефектPG-3:pg_restore --clean падаетнаinherited partition PRIMARY KEY. HTTP admission409/ожиданиеwrite иrollback контрольныхданныхподтверждены. Основнойruntime673e5ba сохранён. Следующий12.5.15 — исправитьтранзакционнуюочисткуразделов иповторитьrestore; PG-3/полныйPG-6 открыты.
Актуальное продолжение 2026-10-07:12.5.15 завершён — транзакционный partition-safe restore исправлен и прошёл realAPI200/контрольархива/новыйID3; injected CREATE INDEX failure400 сохранилданные/sequence/1587constraints.25tests,HTTP/JWT иbuild passed. Основнойruntime673e5ba не обновлён. Следующий12.5.16 — disabled-server collector defect, integrated PG-6 иdeployment; весьэтап12.5 остаётсяоткрытым.
Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md).
@@ -34,6 +34,18 @@
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Завершён в границах локальной приёмки 2026-10-03; ограничения и evidence в11Б.7 |
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в описанных границах:12.3.83,main1186/1186,context221/221. Этап остаётся в работе: стенд12.5.1–3 развёрнут,bootstrap/smoke проверены;остальные сценарии12.5 не выполнены. |
## 12.5.15 — транзакционное восстановление разделов PostgreSQL (2026-10-07)
Исправленреальныйдефект12.5.14. pg_restore --list проверяетобязательныеTABLE public servers/schema_migrations иопределяетприсутствующиевархивеизвестныеpartition parents приложения. ВременныйSQL полностьюформируется pg_restore --clean --if-exists --file безподключениякцелевойБД. Затемодинpsql -X --single-transaction --set ON_ERROR_STOP=on выполняет -c parent constraint preparation и -f SQLархива. РодительскиеPK/UNIQUE снимаютсячерезpg_catalog/format(%I) внутриэтойжетрaнзакции; inheritedchildconstraints удаляютсякаскадно, поэтомупоследующиеIF EXISTS DROP больше непадают. Подготовкаограниченаpublic/известнымиparents, присутствующимивархиве; нетотдельногоcommittedDROP/SQLпереписыванияданных/автоповтора. [Транзакционныйрежимpsql](https://www.postgresql.org/docs/18/app-psql.html) охватываетобеопции-c/-f.
Customdump иSQL содержатприватныеданные: tempdirectory0700 (chmod такжеисправляетранее созданныйкаталог), файлы0600, обафайлаудаляютсяприуспехе/ошибке. Ошибкаdecode/неподходящийTOC непускаютtargetSQL. Архивдолженбытьдовереннымполнымbackup приложения; проверкадвухTABLE неявляетсяпроверкойвсейсхемыилидовериякSQLархива. CASCADEdependencies восстанавливаютсяизполногоархива; частичные/custom archives этимнепринимались.
Локальныепроверки:3новыхpartition-restoretests включенывtest:system-database; полныйнабор25pure/protocol tests иHTTP/JWT scripts passed, contracts/backend build passed. Провереныscope parent/table/schema, quotedcatalogidentifiers, полностьюdecode-before-targetSQL, одинpsqltransaction, отказdecodeбезtargetSQL. Существующийrestore lifecycle сохраняетcache invalidation толькопослеуспеха.
[RealAPI evidence](ux-ui/acceptance/real-restore-fixed-2026-10-07.json):161.104.46.222/PG18.6, isolatedcompiledf1aac49+partition-restoreworktreepatch (SHA256 patch совпал), mmapp_acceptance_20261007. Backup200/475305bytes; concurrentactiveHTTPwrite ждалSQLlock, новыйGET409. Restore200/oktrueбезwarnings, контрольныеservers/retention/speed/migrations/sequence совпалисархивом. СледующийINSERT disabledtestserver192.0.2.250 получилID3. Послеretention19 ивведенияeventtrigger, которыйпадаетнаCREATE INDEX, второйrestore400/необъявленуспешным. НезависимыеSQLпослеrollback подтвердиливсеперечисленныеконтрольныеданные/sequence3 и1587constraint descriptors. Eventtrigger/function удалены, temporarycontainer--rm/consumers/pool штатноостановлены.
ПроверочнаяБД теперьсодержитdisabledserver3/retention19; основнаясохранена:server1–2,retention14,healthok/source673e5ba. Во времяштатногоrestart ресурсныйcollector неожиданнопыталсяпрочитатьdisabledserver3 иполучилtimeout10s — отдельныйдефектдляследующегоблока. RouterOS apply не выполнялся. PG-3/4 принятывграницахrealAPI/контрольныхданных/новогоID/SQLrollback/constraints, безполногоchecksum/Portal/browser UI. Integrated UDP+detachedjobs втомжекритическоминтервалеимультиэкземплярыещёнеприняты; полныйPG-6 открыт. Новыйкоднеразвёрнутнаосновномстенде. Следующий12.5.16 — исправитьdisabled-server polling, integratedPG-6/deployment. Локальныйкоммитбезpush.
## 12.5.14 — реальный API restore: дефект очистки разделов и контроль rollback (2026-10-07)
Временный docker compose run --rm --no-deps backend использовалcompiled dist sourcef1aac49, mount/app/backend/acceptance, DATABASE_URL толькоmmapp_acceptance_20261007, HTTP127.0.0.1:8014 внутриконтейнера. Основнойbackend/frontend не обновлялись. [Очищенный отчёт](ux-ui/acceptance/real-restore-2026-10-07.json); SHA256 переданногоcompiled archive совпалслокальным. РеальныйbuildApp иAPI routes работалибезHTTP mocks; startScheduler=false приinitial build, стандартныеdatabase operation restarts выполнялиреальныйrefreshScheduler/consumer lifecycle.
+13 -1
View File
@@ -1,6 +1,6 @@
# Этап 12: итоговая приёмка UX/UI
Актуальное продолжение 2026-10-07:12.5.14 выполненкакреальнаяприёмка isolated API/sourcef1aac49. ОбнаружендефектPG-3:pg_restore --clean падаетнаinherited partition PRIMARY KEY. HTTP admission409/ожиданиеwrite иrollback контрольныхданныхподтверждены. Основнойruntime673e5ba сохранён. Следующий12.5.15 — исправитьтранзакционнуюочисткуразделов иповторитьrestore; PG-3/полныйPG-6 открыты.
Актуальное продолжение 2026-10-07:12.5.15 завершён — транзакционный partition-safe restore исправлен и прошёл realAPI200/контрольархива/новыйID3; injected CREATE INDEX failure400 сохранилданные/sequence/1587constraints.25tests,HTTP/JWT иbuild passed. Основнойruntime673e5ba не обновлён. Следующий12.5.16 — disabled-server collector defect, integrated PG-6 иdeployment; весьэтап12.5 остаётсяоткрытым.
План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push.
@@ -1088,3 +1088,15 @@ GETbackup200/478810bytes/PGDMP полученвпамять безкопиров
Изолированныйконтейнерудалён--rm,егоscheduler/background/worker остановлены,соединениязакрыты. ПроверочнаяБД теперьretention16; основнойстендпо-прежнемуretention14,обаCHRзаписи сохранены,healthok/version1.0.0-test.673e5ba. Nativegre/BGP collector можетвыполнятьчтениеприштатномrestart,ноRouterOS apply/конфигурациянеизменялись. РеальныйPortal/browser restore UI иintegrated UDP+detached jobs втомжекритическоминтервалеэтимнепроверены.
Обнаруженныйдефектблокируетуспешнуюприёмкуrestore; коднеразвёрнутвосновномприложении. Следующий12.5.15 — исправитьочисткуpartition constraints внутриоднойтранзакции, безпредварительногонеобратимогоDROP ибезавтоматическогоповторапосленеизвестногоcommit, затемповторитьPG-3/4/полныйPG-6. Локальныйкоммитбезpush.
## 12.5.15 — транзакционное восстановление разделов PostgreSQL (2026-10-07)
Исправленреальныйдефект12.5.14. pg_restore --list проверяетобязательныеTABLE public servers/schema_migrations иопределяетприсутствующиевархивеизвестныеpartition parents приложения. ВременныйSQL полностьюформируется pg_restore --clean --if-exists --file безподключениякцелевойБД. Затемодинpsql -X --single-transaction --set ON_ERROR_STOP=on выполняет -c parent constraint preparation и -f SQLархива. РодительскиеPK/UNIQUE снимаютсячерезpg_catalog/format(%I) внутриэтойжетрaнзакции; inheritedchildconstraints удаляютсякаскадно, поэтомупоследующиеIF EXISTS DROP больше непадают. Подготовкаограниченаpublic/известнымиparents, присутствующимивархиве; нетотдельногоcommittedDROP/SQLпереписыванияданных/автоповтора. [Транзакционныйрежимpsql](https://www.postgresql.org/docs/18/app-psql.html) охватываетобеопции-c/-f.
Customdump иSQL содержатприватныеданные: tempdirectory0700 (chmod такжеисправляетранее созданныйкаталог), файлы0600, обафайлаудаляютсяприуспехе/ошибке. Ошибкаdecode/неподходящийTOC непускаютtargetSQL. Архивдолженбытьдовереннымполнымbackup приложения; проверкадвухTABLE неявляетсяпроверкойвсейсхемыилидовериякSQLархива. CASCADEdependencies восстанавливаютсяизполногоархива; частичные/custom archives этимнепринимались.
Локальныепроверки:3новыхpartition-restoretests включенывtest:system-database; полныйнабор25pure/protocol tests иHTTP/JWT scripts passed, contracts/backend build passed. Провереныscope parent/table/schema, quotedcatalogidentifiers, полностьюdecode-before-targetSQL, одинpsqltransaction, отказdecodeбезtargetSQL. Существующийrestore lifecycle сохраняетcache invalidation толькопослеуспеха.
[RealAPI evidence](ux-ui/acceptance/real-restore-fixed-2026-10-07.json):161.104.46.222/PG18.6, isolatedcompiledf1aac49+partition-restoreworktreepatch (SHA256 patch совпал), mmapp_acceptance_20261007. Backup200/475305bytes; concurrentactiveHTTPwrite ждалSQLlock, новыйGET409. Restore200/oktrueбезwarnings, контрольныеservers/retention/speed/migrations/sequence совпалисархивом. СледующийINSERT disabledtestserver192.0.2.250 получилID3. Послеretention19 ивведенияeventtrigger, которыйпадаетнаCREATE INDEX, второйrestore400/необъявленуспешным. НезависимыеSQLпослеrollback подтвердиливсеперечисленныеконтрольныеданные/sequence3 и1587constraint descriptors. Eventtrigger/function удалены, temporarycontainer--rm/consumers/pool штатноостановлены.
ПроверочнаяБД теперьсодержитdisabledserver3/retention19; основнаясохранена:server1–2,retention14,healthok/source673e5ba. Во времяштатногоrestart ресурсныйcollector неожиданнопыталсяпрочитатьdisabledserver3 иполучилtimeout10s — отдельныйдефектдляследующегоблока. RouterOS apply не выполнялся. PG-3/4 принятывграницахrealAPI/контрольныхданных/новогоID/SQLrollback/constraints, безполногоchecksum/Portal/browser UI. Integrated UDP+detachedjobs втомжекритическоминтервалеимультиэкземплярыещёнеприняты; полныйPG-6 открыт. Новыйкоднеразвёрнутнаосновномстенде. Следующий12.5.16 — исправитьdisabled-server polling, integratedPG-6/deployment. Локальныйкоммитбезpush.
+4
View File
@@ -101,3 +101,7 @@ ssh -N -L 3000:127.0.0.1:3000 -L 8000:127.0.0.1:8000 [email protected]
## Реальный API restore failure — 2026-10-07
[12.5.14 evidence](ux-ui/acceptance/real-restore-2026-10-07.json): isolatedsourcef1aac49/API againstmmapp_acceptance_20261007, backup200, activewrite drain/GET409. Restore400:inheritedpartitionPK нельзяудалитьсуществующим--clean. Контрольrollback прошёл (servers/speed/migrations/sequence/retention16/constraint). ПроверочнаяБДretention16,основнаяretention14/source673e5ba/healthok сохранены. Runtimeнеобновлён,PG-3 иполныйPG-6 открыты. Следующий12.5.15 — транзакционноеисправлениеclean иповторнаяприёмка.
## Partition-safe API restore — 2026-10-07
12.5.15:isolatedf1aac49+compiledpatch, [realAPIrestore200/новыйID3/injectedSQL400/rollback1587constraints](ux-ui/acceptance/real-restore-fixed-2026-10-07.json). ПроверочнаяБДmmapp_acceptance_20261007 содержитserver3 disabled192.0.2.250/retention19. ОсновнаяБДserver1–2/retention14/source673e5ba/healthok сохранена.25localtests,HTTP/JWT иbuild passed. Patchещёнеразвёрнут; следующий12.5.16 — disabled-server collector defect, integratedPG-6/deployment.
@@ -0,0 +1,118 @@
{
"date": "2026-10-07",
"source": "f1aac49+partition-restore-worktree",
"database": "mmapp_acceptance_20261007",
"mode": "real HTTP API against isolated PostgreSQL",
"mainDatabaseTouched": false,
"backup": {
"status": 200,
"bytes": 475305
},
"admission": {
"blockedGet": 409,
"waitedForActiveWrite": true
},
"restore": {
"status": 200,
"outcome": {
"ok": true
}
},
"controlsMatch": true,
"nextServerId": 3,
"failedRestore": {
"status": 400,
"expectedSqlFailure": true,
"reportedSuccess": false
},
"rollbackControlsMatch": true,
"constraintsMatch": true,
"constraintCount": 1587,
"controlsAfterFailure": {
"servers": [
{
"id": 1,
"host": "194.226.121.150",
"enabled": true
},
{
"id": 2,
"host": "104.165.244.7",
"enabled": true
},
{
"id": 3,
"host": "192.0.2.250",
"enabled": false
}
],
"uptime": [
{
"id": 1,
"retention_days": 19
}
],
"speed": [
{
"count": 5
}
],
"sequence": [
{
"last_value": 3,
"is_called": true
}
],
"migrations": [
{
"id": "0000_postgresql"
},
{
"id": "0002_compact_schema"
},
{
"id": "0003_backup_s3"
},
{
"id": "0004_geoip_settings"
},
{
"id": "0005_flow_facts"
},
{
"id": "0006_config_revisions"
},
{
"id": "0007_flow_buckets_nat"
},
{
"id": "0008_alert_outbox_outcomes"
},
{
"id": "0009_alert_outbox_resolution"
}
]
},
"limits": [
"One isolated backend instance; no cross-host quiescence proof",
"Integrated UDP and detached jobs in same critical interval not injected",
"No Portal SSO or browser restore UI tested",
"Main runtime remains673e5ba; isolated f1aac49 plus compiled patch tested",
"Restart resource collector attempted disabled server3 at192.0.2.250 and timed out; separate defect to investigate"
],
"patchSha256": "19fca8bb99b1b7051224078a554562abacf35feebe0ff918756e082adc9dcaee",
"restoreMethod": "pg_restore --list, pg_restore --clean --if-exists --file, psql -X --single-transaction --set ON_ERROR_STOP=on -c parent preparation -f generated SQL",
"healthMainAfter": {
"status": "ok",
"timestamp": "2026-10-07T16:26:19.859Z",
"version": "1.0.0-test.673e5ba",
"eventLoopDelayMs": 20,
"flowWorker": {
"alive": false,
"bound": false,
"pendingSize": 0,
"dropped": 0,
"packetsReceived": 0
}
}
}