Copie programată a bazei de producție, în afara mașinii
Ține de #14 (closed). Nu îl închide — se închide după ce o restaurare a fost făcută efectiv pe o copie reală de producție.
Problema
Tot ce a făcut vreodată un client stă într-un singur fișier SQLite, pe un singur volum Docker, pe o singură mașină: comenzile, produsele, fiecare text editat din panou, conturile de admin. Nimic nu îl copia nicăieri. Un disc care cedează, un volum scăpat la un redeploy, o pagină coruptă — și nu mai există la ce să ne întoarcem.
Cum se ia copia
VACUUM INTO, nu cp. Fișierul e scris în timp ce magazinul servește, iar o copiere pe octeți îl poate prinde la mijlocul unei scrieri: copia se deschide, iar coruperea iese la iveală exact în ziua în care cineva are nevoie de ea. VACUUM INTO ia același instantaneu de citire pe care l-ar lua o interogare și scrie din el o bază compactată, deci rezultatul e întotdeauna un fișier pe care SQLite îl deschide.
Rulează prin Prisma, nu prin sqlite3: imaginea de runtime nu are shell-ul sqlite (verificat în backend/Dockerfile — se instalează doar openssl), iar adăugarea lui doar ca să livrăm un backup ar fi încă un lucru de ținut în funcțiune.
Verificat local pe o bază reală: SQLite 3.46.0, copie în 31 ms, 3,98 MB, iar copia se deschide cu numărătorile intacte — 61 de produse, 1 comandă, 1 admin, 72 de articole.
Unde ajunge
În afara volumului de date, deliberat. O copie care stă lângă fișierul pe care îl protejează nu supraviețuiește defecțiunii pentru care există. Copia se scrie în /tmp din container, se trage cu docker compose cp în spațiul de lucru al jobului, și de acolo pleacă de pe mașină ca artefact.
Artefactul e singura stocare din afara mașinii pe care o are proiectul fără să ceară cuiva credențiale. Înseamnă însă că fișierul stă în GitLab și conține nume, telefoane și adrese de e-mail ale clienților, citibile de oricine are acces la proiect. E un compromis asumat față de a nu avea niciun backup, nu un răspuns definitiv — pasul următor e stocare de obiecte cu retenția ei proprie, și cere o decizie și o cheie.
Retenția
Două joburi, două programări, retenția fiind chiar expirarea artefactului:
-
backup:production:daily— 8 zile, ca șapte copii să fie mereu acolo chiar și în ziua în care una pică -
backup:production:weekly— 5 săptămâni, adică patru supraviețuitoare plus cea care se înlocuiește
Setul săptămânal există pentru defecțiunea pe care n-o observă nimeni două săptămâni — un rând corupt, o migrare proastă — peste care o săptămână de copii zilnice a trecut deja.
Ambele rulează doar la programare: $CI_PIPELINE_SOURCE == "schedule", deci nimic nu pornește la un push sau la un merge.
Gărzile din script
Verificate, nu doar scrise: fără --out dă mesajul de utilizare; --out fără valoare la fel; o cale care conține ghilimea simplă e refuzată, pentru că ghilimeaua e exact ce ar închide literalul dat lui VACUUM INTO.
Scriptul citește numărătorile din copie, nu din original, și cade dacă diferă de sursă. Un backup pe care nu l-a deschis nimeni e o presupunere.
Verificat
| Comandă | Rezultat |
|---|---|
npx tsc --noEmit --strict --types node prisma/backup-database.ts |
exit 0 |
backend/ npm run build |
exit 0 |
backend/ npm test |
550 de teste, toate trec |
python3 -c "yaml.safe_load(...)" |
YAML valid, ambele joburi înregistrate |
Ce urmează, imediat după fuziune
- Se creează cele două programări în GitLab, cu variabila
BACKUP_KIND. - Se rulează programarea zilnică o dată, ca să existe o copie reală de producție.
- Se face restaurarea pe acea copie și se compară numărul de comenzi și de produse cu producția. Abia atunci #14 (closed) se închide.
Atenție: acest MR nu primește pipeline — atinge .gitlab-ci.yml, iar workflow.rules exclude explicit orice pipeline al cărui set de modificări îl include.
Rămâne pentru Vitalie
Dacă hostingul face deja instantanee de volum și cu ce frecvență — nu se poate afla din repo. Și decizia despre stocarea de obiecte, dacă artefactele GitLab nu sunt un loc acceptabil pentru date de clienți pe termen lung.