രൂപകൽപ്പന docs/architecture/23-ops.md-ലെ 8-ാം വിഭാഗമാണ്. ഇത് Docker Compose ഉൽപ്പന്നത്തിനുള്ള റൺബുക്കാണ്. അത് എഴുതാത്ത ഒരാൾ അത് പിന്തുടരാൻ വേണ്ടിയാണ് എഴുതിയിരിക്കുന്നത്; ഒരു ഘട്ടം വ്യക്തമല്ലെങ്കിൽ, അത് ഈ രേഖയിലെ ഒരു കേടുപാടാണ്.
എന്താണ് സംരക്ഷിതം, എങ്ങനെ
ആസ്തി
എങ്ങനെ
എവിടെ
ഡാറ്റാബേസ്
ആദ്യ ബൂട്ട് മുതൽ, കുറഞ്ഞത് ഓരോ 60 സെക്കൻഡിലും, WAL തുടർച്ചയായി ആർക്കൈവ് ചെയ്യുന്നു
pgwal വോള്യം
ഡാറ്റാബേസ്
pg_basebackup ഉപയോഗിച്ചുള്ള അടിസ്ഥാന ബാക്കപ്പുകൾ, സ്ഥിരസ്ഥിതിയിൽ ദിവസവും (backup-scheduler)
pgbackup വോള്യം
ഡാറ്റാബേസ്
അടിസ്ഥാന ബാക്കപ്പുകളുടെയും WAL-ന്റെയും എൻക്രിപ്റ്റ് ചെയ്ത പകർപ്പുകൾ, ഓരോ അഞ്ച് മിനിറ്റിലും (backup-offsite)
നിങ്ങൾ പേരിടുന്ന വേറിട്ട ഒരു സ്റ്റോർ
ഫയലുകൾ
files വോള്യം. നിങ്ങളുടെ ഹോസ്റ്റിന്റെ ബാക്കപ്പ് ടൂൾ ഉപയോഗിച്ച് അത് പകർത്തൂ, അല്ലെങ്കിൽ പതിപ്പുള്ള ഒബ്ജക്റ്റ് സ്റ്റോറേജ് ഉപയോഗിക്കൂ
files വോള്യം
രഹസ്യങ്ങൾ
docker/.env, എല്ലാത്തിനും മുകളിൽ QUIRE_MASTER_KEY (കൂടാതെ ഇപ്പോഴും ഉപയോഗത്തിലുള്ള ഏതെങ്കിലും QUIRE_MASTER_KEY_RETIRED), QUIRE_BACKUP_ENCRYPTION_KEY, കൂടാതെ docker/secrets/audit-signing-key.pem
ഒരു പകർപ്പ് ഈ ഹോസ്റ്റിന് പുറത്ത് സൂക്ഷിക്കൂ
തിരയൽ ഇൻഡെക്സുകൾ, ക്യാഷുകൾ, റെൻഡിഷനുകൾ
ബാക്കപ്പില്ല; വീണ്ടും നിർമ്മിക്കും
ലക്ഷ്യങ്ങൾ: പരാജയത്തിന് 60 സെക്കൻഡിനുള്ളിൽ ഒരു പുനഃസ്ഥാപന ബിന്ദു, 500 GB ഡാറ്റാബേസിന് 60 മിനിറ്റിനുള്ളിൽ ഒരു റീസ്റ്റോർ.
രണ്ട് തെറ്റുകൾ സാധാരണമാണ്. ഫയലുകളില്ലാതെ പുനഃസ്ഥാപിച്ച ഒരു ഡാറ്റാബേസ് കേടായ പേജുകൾ കാണിക്കും. QUIRE_MASTER_KEY ഇല്ലാതെ പുനഃസ്ഥാപിച്ച ഒരു ഡാറ്റാബേസിന് അത് സൂക്ഷിക്കുന്ന SSO, വെബ്ഹുക്ക്, ഇന്റഗ്രേഷൻ ക്രെഡൻഷ്യലുകൾ ഡിക്രിപ്റ്റ് ചെയ്യാൻ കഴിയില്ല; ഒന്നും പരിഹരിക്കാതെ ഒരു മാസ്റ്റർ കീ റൊട്ടേഷൻ പൂർത്തിയാകും വരെ (key-rotation.md), അതിൽ റിട്ടയർ ചെയ്ത കീകളും ഉൾപ്പെടും. രണ്ടും ബാക്കപ്പിന്റെ ഭാഗമാണ്.
ബാക്കപ്പുകൾ എടുക്കൽ
മുഴുവൻ ക്ലസ്റ്ററിന്റെയും ഒരു അടിസ്ഥാന ബാക്കപ്പ്:
docker compose -f docker/compose.yaml --profile backup run --rm backup
ഇത് ഏറ്റവും പുതിയ QUIRE_BACKUP_KEEP അടിസ്ഥാന ബാക്കപ്പുകൾ (സ്ഥിരസ്ഥിതി 5) സൂക്ഷിക്കുകയും, ഏറ്റവും പഴയതിന് ഇനി ആവശ്യമില്ലാത്ത WAL വെട്ടിനിരത്തുകയും ചെയ്യും, അതിനാൽ ആർക്കൈവ് അനന്തമായി വളരില്ല. ഹോസ്റ്റിൽ cron അല്ലെങ്കിൽ ഒരു systemd ടൈമർ ഉപയോഗിച്ച് അത് ദിവസവും ഷെഡ്യൂൾ ചെയ്യൂ:
15 2 * * * cd /srv/quire && docker compose -f docker/compose.yaml --profile backup run --rm backup >> /var/log/quire-backup.log 2>&1
അല്ലെങ്കിൽ സ്റ്റാക്കിനെ അത് ഷെഡ്യൂൾ ചെയ്യാൻ വിടൂ: backup പ്രൊഫൈൽ backup-scheduler പ്രവർത്തിപ്പിക്കും, അത് ഓരോ QUIRE_BACKUP_INTERVAL_HOURS (സ്ഥിരസ്ഥിതി 24) ഇടവിട്ട് ഒരു അടിസ്ഥാന ബാക്കപ്പ് എടുക്കും, കൂടാതെ താഴെ വിവരിക്കുന്ന backup-offsite-ഉം.
docker compose -f docker/compose.yaml --profile backup up -d
എൻക്രിപ്റ്റ് ചെയ്ത ഹോസ്റ്റിന് പുറത്തുള്ള പകർപ്പുകൾ
രണ്ട് വോള്യങ്ങളും ഡാറ്റാബേസിന്റെ അതേ ഹോസ്റ്റിലാണ്, പരാജയപ്പെട്ട മെഷീനിലുള്ള ഒരു ബാക്കപ്പ് ഒരു ബാക്കപ്പല്ല. backup-offsite ഓരോ അടിസ്ഥാന ബാക്കപ്പും ഓരോ ആർക്കൈവ് ചെയ്ത WAL സെഗ്മെന്റും സ്റ്റോറേജ് പോർട്ട് വഴി വേറിട്ട ഒരു സ്റ്റോറിലേക്ക്, എൻക്രിപ്റ്റ് ചെയ്ത്, പകർത്തുകയും നിലനിർത്തൽ ചട്ടപ്രകാരം അവിടെ സൂക്ഷിക്കുകയും ചെയ്യും:
എൻക്രിപ്ഷൻ.QUIRE_BACKUP_ENCRYPTION_KEY (അല്ലെങ്കിൽ QUIRE_BACKUP_ENCRYPTION_KEY_FILE പറയുന്ന ഫയൽ) ഉപയോഗിച്ച് AES-256-GCM: 32 ബൈറ്റ്, openssl rand -hex 32-ൽ നിന്ന്. ഓരോ ഫയലിനും സ്വന്തം നോൺസും ഓതന്റിക്കേഷൻ ടാഗുമുണ്ട്, അതിനാൽ കീയില്ലാതെ ഒരു പകർപ്പും വായിക്കാൻ കഴിയില്ല, അതിലുള്ള ഏതു മാറ്റവും കണ്ടെത്തപ്പെടും. കീ QUIRE_MASTER_KEY-യോടൊപ്പം, ഈ ഹോസ്റ്റിന് പുറത്തും ബാക്കപ്പ് സ്റ്റോറിന് പുറത്തും സൂക്ഷിക്കൂ. കീയില്ലെങ്കിൽ റീസ്റ്റോർ ഇല്ല.
എവിടെ.QUIRE_BACKUP_STORAGE_DRIVERs3, azure അല്ലെങ്കിൽ local (QUIRE_BACKUP_STORAGE_ROOT-ൽ മൌണ്ട് ചെയ്ത റിമോട്ട് ഡിസ്ക്) ആണ്. ക്രമീകരണങ്ങൾ ഫയൽ സ്റ്റോറേജ് ക്രമീകരണങ്ങളാണ്, QUIRE_BACKUP_ പ്രീഫക്സോടെ: QUIRE_BACKUP_S3_ENDPOINT, QUIRE_BACKUP_S3_BUCKET, QUIRE_BACKUP_S3_ACCESS_KEY_ID, തുടങ്ങിയവ. ഫയലുകളിൽ നിന്ന് വ്യത്യസ്തമായ ഒരു ബക്കറ്റും, നല്ലത് വേറിട്ട ഒരു അക്കൗണ്ടും ഉപയോഗിക്കൂ, പ്രൊവൈഡർ അനുവദിക്കുകയാണെങ്കിൽ എഴുതാൻ കഴിയുന്ന പക്ഷേ ഇല്ലാതാക്കാൻ കഴിയാത്ത ക്രെഡൻഷ്യലുകളോടെ.
നിലനിർത്തൽ. ഏറ്റവും പുതിയ QUIRE_BACKUP_OFFSITE_KEEP അടിസ്ഥാന ബാക്കപ്പുകൾ (സ്ഥിരസ്ഥിതി QUIRE_BACKUP_KEEP, ഇല്ലെങ്കിൽ 7), കൂടാതെ ഏറ്റവും പഴയതിന് ആവശ്യമുള്ള WAL; പഴയ കൂട്ടങ്ങളും സെഗ്മെന്റുകളും സ്റ്റോറിൽ നിന്ന് ഇല്ലാതാക്കും.
എപ്പോൾ. ഓരോ QUIRE_BACKUP_SHIP_INTERVAL_SECONDS-ലും (സ്ഥിരസ്ഥിതി 300). കയറ്റുമതി ഇഡംപോട്ടന്റാണ്: ഇതിനകം സൂക്ഷിച്ചത് ഒഴിവാക്കപ്പെടും, മാനിഫെസ്റ്റ് അവസാനം എഴുതിയതിന് ശേഷം മാത്രമേ ഒരു അടിസ്ഥാന ബാക്കപ്പ് സൂക്ഷിച്ചതായി കണക്കാക്കൂ.
അതേ കമാന്റ് കൈരേഖയിൽ പ്രവർത്തിക്കും:
docker compose -f docker/compose.yaml run --rm backup-offsite bun apps/worker/src/backups/main.ts shipdocker compose -f docker/compose.yaml run --rm backup-offsite bun apps/worker/src/backups/main.ts verify
ഒരു പുതിയ ഹോസ്റ്റിൽ പുനഃസ്ഥാപിക്കാൻ, ആദ്യം ഒരു കൂട്ടം തിരികെ കൊണ്ടുവരൂ, പിന്നെ താഴെയുള്ള ഘട്ടങ്ങൾ പിന്തുടരൂ — എടുത്ത ഡയറക്ടറി pgbackup വോള്യത്തിന് പകരമായും, എടുത്ത wal-archivepgwal-ന് പകരമായും വച്ച്:
bun apps/worker/src/backups/main.ts fetch base-20260924T021500Z /srv/restore
ഒരു സമയബിന്ദുവിലേക്ക് പുനഃസ്ഥാപിക്കൽ
ഡാറ്റ നഷ്ടപ്പെട്ടതിന് ശേഷം ഇത് ഉപയോഗിക്കൂ: കേടായ ഒരു ഇംപോർട്ട്, ഇല്ലാതാക്കിയ ഒരു കോഴ്സ്, പഴയതിലേക്ക് മടക്കേണ്ട ഒരു ചുരുക്കൽ മൈഗ്രേഷൻ. അത് ലൈവ് ഡാറ്റാബേസിനെ പകരം വയ്ക്കും, അതിനാൽ താഴെയുള്ള ഡ്രിൽ ആദ്യം പരിശീലിക്കൂ.
ലക്ഷ്യ സമയം തിരഞ്ഞെടുക്കൂ, UTC-ൽ, കേടുപാടിന് തൊട്ടുമുമ്പ്: 2026-09-24 09:30:00+00. ഓഡിറ്റ് ലോഗ് (/admin/audit) സാധാരണയായി ആ നിമിഷം കാണിക്കും.
ഓവർറൈഡ് മുഴുവൻ കമാന്റും പകരം വയ്ക്കും, അതിനാൽ പുനഃസ്ഥാപനം ആശ്രയിക്കുന്ന രണ്ട് ക്രമീകരണങ്ങളും അത് ആവർത്തിക്കും: പ്രൈമറിയുടെതിൽ കുറവല്ലാത്ത max_connections (അല്ലെങ്കിൽ “insufficient parameter settings” എന്ന പിശകോടെ പുനഃസ്ഥാപനം നിർത്തപ്പെടും), കൂടാതെ മൌണ്ട് ചെയ്ത pg_hba.conf.
docker compose -f docker/compose.yaml -f docker/compose.recover.yaml up -d postgresdocker compose -f docker/compose.yaml logs -f postgres # wait for "database system is ready"
ആർക്കും അനുവദിക്കും മുമ്പ് അത് പരിശോധിക്കൂ: ഓഡിറ്റ് ചെയിൻ (docker compose -f docker/compose.yaml run --rm worker bun tooling/audit-verify/run.ts), കൂടാതെ നഷ്ടപ്പെട്ട ഡാറ്റ തിരികെ വന്നിട്ടുണ്ടോ എന്ന്.
സാധാരണ നിലയിലേക്ക് മടങ്ങൂ: docker compose -f docker/compose.yaml up -d. ഇത് ആർക്കൈവിംഗ് ഓണോടെ Postgres വീണ്ടും ആരംഭിപ്പിക്കും, പുതിയ ഒരു WAL ടൈംലൈൻ ആരംഭിക്കും. ഉടൻ ഒരു പുതിയ അടിസ്ഥാന ബാക്കപ്പ് എടുക്കൂ.
പ്രോജക്റ്റിന്റെ പേരായ quire ഓരോ വോള്യത്തിനും മുന്നിൽ ചേരും; docker volume ls കൃത്യമായ പേരുകൾ കാണിക്കും.
ഷെഡ്യൂൾ ചെയ്ത പരിശോധന ഡ്രിൽ
backup-offsite ഓരോ QUIRE_BACKUP_DRILL_INTERVAL_HOURS-ലും (സ്ഥിരസ്ഥിതി 168, ആഴ്ചതോറും) ഒരു ഡ്രില്ലും പ്രവർത്തിപ്പിക്കും, ഒന്ന് പരാജയപ്പെട്ടാൽ അടുത്ത പ്രവർത്തനത്തിലും വീണ്ടും. അത് ഏറ്റവും പുതിയ ഹോസ്റ്റിന് പുറത്തുള്ള അടിസ്ഥാന ബാക്കപ്പും അതിന് ശേഷുള്ള ഓരോ WAL സെഗ്മെന്റും എടുക്കും, ഓരോന്നിനെയും ഡിക്രിപ്റ്റ് ചെയ്യും (കീ ഇപ്പോഴും അവ തുറക്കുന്നുവെന്നും ഒന്നും മാറ്റിയിട്ടില്ലെന്നും അത് തെളിയിക്കും), ഓരോ ഫയലും അതിന്റെ മാനിഫെസ്റ്റുമായി താരതമ്യം ചെയ്യും, ആർക്കൈവ് ഒരു Postgres ഡാറ്റ ഡയറക്ടറിയാണോ എന്ന് പരിശോധിക്കും, ബാക്കപ്പ് മുതലുള്ള WAL-ൽ വിടവില്ലെന്ന് പരിശോധിക്കും. റിപ്പോർട്ട് സ്റ്റോറിൽ reports/drill-<time>.json ആയും സേവനത്തിന്റെ ലോഗിലും എഴുതപ്പെടും; പരാജയപ്പെട്ട ഒരു ഡ്രിൽ ഫയലിന്റെ പേരോ ആദ്യത്തെ ഇല്ലാത്ത സെഗ്മെന്റോ പറയും.
റീസ്റ്റോർ ഡ്രിൽ
ഒരിക്കലും പുനഃസ്ഥാപിക്കാത്ത ഒരു ബാക്കപ്പ് ഒരു ബാക്കപ്പല്ല. ഡ്രിൽ ലക്ഷ്യത്തിന് മുമ്പ് എടുത്ത ഏറ്റവും പുതിയ പൂർണ്ണമായ അടിസ്ഥാന ബാക്കപ്പും കൂടാതെ WAL ആർക്കൈവും, ലൈവിനോട് ഒന്നും പങ്കിടാത്ത ഒരു സ്ക്രാറ്റ് Postgres-ലേക്ക് പുനഃസ്ഥാപിക്കുകയും ഫലം തെളിയിക്കുകയും ചെയ്യും:
docker/scripts/restore-drill.sh # to ninety minutes agodocker/scripts/restore-drill.sh --target "2026-09-24 09:30:00+00"
ലക്ഷ്യം കൃത്യമായി ആ രൂപത്തിലുള്ള UTC ആണ്. അതിനേക്കാൾ പഴയ ഒരു അടിസ്ഥാന ബാക്കപ്പും അതിനപ്പുറമുള്ള ആർക്കൈവ് ചെയ്ത WAL-ഉം അതിന് ആവശ്യമാണ്: പുതിയ ഇൻസ്റ്റലിൽ, ബാക്കപ്പിന് ശേഷമുള്ള ഒരു ലക്ഷ്യം തിരഞ്ഞെടുക്കും മുമ്പ്, ഒരു അടിസ്ഥാന ബാക്കപ്പ് എടുത്ത് അടുത്ത ആർക്കൈവ് ചെയ്ത സെഗ്മെന്റിനായി കാത്തിരിക്കൂ (എഴുത്തുകളോടെ കുറഞ്ഞത് ഒരു മിനിറ്റ്). ഡ്രില്ലിന് ഹോസ്റ്റിൽ Docker, bash എന്നിവ മാത്രമേ ആവശ്യമുള്ളൂ.
ഓരോ ഘട്ടവും ഡ്രിൽ പരാജയപ്പെടുത്തും:
പുനഃസ്ഥാപന കഴിവ്: സ്ക്രാറ്റ് ക്ലസ്റ്റർ ലക്ഷ്യത്തിലേക്ക് റീപ്ലേ ചെയ്ത് തുറക്കും.
പൂർണ്ണത: ഓരോ ടേബിളിന്റെയും വരി എണ്ണം ലൈവ് ഡാറ്റാബേസുമായി (tooling/restore-drill). ലൈവ് ഡാറ്റാബേസ് ലക്ഷ്യത്തിന് ശേഷം മുന്നോട്ട് പോയിട്ടുണ്ട്, അതിനാൽ ഒരു ടേബിൾ 500 വരികളുടെയോ അതിന്റെ വലിപ്പത്തിന്റെ പത്തിലൊന്നിന്റെയോ വലിയ അളവിൽ വ്യത്യസ്തമാകാം, ഏതു ദിശയിലും (എഴുത്തുകൾ അതിനെ വൈകിപ്പിക്കും, ഇല്ലാതാക്കലുകൾ റീസ്റ്റോറിനെ കൂടുതൽ സൂക്ഷിക്കും); ലക്ഷ്യത്തിന് ശേഷം നിർമ്മിച്ച ഒരു പാർട്ടിഷൻ നഷ്ടപ്പെട്ട ടേബിളല്ല. തിരക്കേറിയ ഇൻസ്റ്റലിൽ QUIRE_DRILL_MAX_BEHIND, QUIRE_DRILL_MAX_DRIFT_RATIO ഉപയോഗിച്ച് അനുവാദം വീതിയാക്കൂ. ഇല്ലാതായ അല്ലെങ്കിൽ ശൂന്യമാക്കിയ ടേബിൾ പരാജയപ്പെടുത്തും.
സമഗ്രത: പുനഃസ്ഥാപിച്ച പകർപ്പിൽ ഓഡിറ്റ് ഹാഷ് ചെയിൻ പരിശോധന കടന്നുപോകും.
ഉപയോഗ്യത: ആപ്ലിക്കേഷൻ റോൾ റോ-ലെവൽ സെക്യൂരിറ്റി വഴി വായിക്കും.
സമയം: പച്ച വരെയുള്ള സമയം, QUIRE_DRILL_RTO_SECONDS (സ്ഥിരസ്ഥിതി 3600) ഉപയോഗിച്ച്.
ഇത് ഒരിക്കലും ലൈവ് ഡാറ്റാബേസിലോ അതിന്റെ വോള്യങ്ങളിലോ എഴുതില്ല: ബാക്കപ്പും WAL വോള്യങ്ങളും വായന മാത്രമായി മൌണ്ട് ചെയ്യുകയും സ്ക്രാറ്റ് ക്ലസ്റ്റർ അവസാനം ഇല്ലാതാക്കപ്പെടുകയും ചെയ്യും, പാസായാലും പരാജയപ്പെട്ടാലും.
ഒരു JSON റിപ്പോർട്ട് എഴുതപ്പെടാൻ QUIRE_DRILL_REPORT ഒരു പാതയിലേക്ക് സജ്ജമാക്കൂ, പാസായാലും പരാജയപ്പെട്ടാലും, കൂടാതെ Docker ഹോസ്റ്റിൽ അത് ഷെഡ്യൂൾ അനുസരിച്ച് പ്രവർത്തിപ്പിക്കൂ:
മാസതോറും ഓരോ അപ്ഗ്രേഡിനും മുമ്പ് പ്രവർത്തിപ്പിക്കൂ. പരാജയപ്പെട്ട ഒരു ഡ്രിൽ അപ്ഗ്രേഡ് തടയും. ഓരോ പാദവും, ഈ റൺബുക്ക് എഴുതാത്ത ഒരാളെക്കൊണ്ട്, ഈ രേഖ മാത്രം ഉപയോഗിച്ച് ഒരു ശേഷിക്കുന്ന ഹോസ്റ്റിൽ യഥാർത്ഥമായ ഒരു പോയിന്റ്-ഇൻ-ടൈം റീസ്റ്റോർ നടത്തിക്കൂ.
ഫയലുകൾ
ലോക്കൽ ഫയലുകൾ files വോള്യത്തിലാണ്. അത് ഡാറ്റാബേസിനൊപ്പം, അതേ സമയത്ത് ബാക്കപ്പ് ചെയ്യൂ, രണ്ടും ഒന്നിച്ച് പുനഃസ്ഥാപിക്കൂ:
docker run --rm -v quire_files:/files:ro -v "$PWD":/out alpine tar -czf /out/files-$(date -u +%Y%m%d).tar.gz -C /files .
ഒബ്ജക്റ്റ് സ്റ്റോറേജ് ഉപയോഗിച്ചാൽ, ബക്കറ്റ് പതിപ്പുകൾ ഓണാക്കി 35 ദിവസത്തെ പഴയ പതിപ്പുകൾ സൂക്ഷിക്കൂ; ഫയലുകളുടെ പോയിന്റ്-ഇൻ-ടൈം പുനഃസ്ഥാപനം അപ്പോൾ ബക്കറ്റിന്റെ സ്വന്തമായിരിക്കും.