วิธีสำรองข้อมูลและกู้คืน Immich บน VPS อย่างถูกต้อง
เรียนรู้วิธีสำรองข้อมูล Immich ที่ถูกต้องประกอบด้วยไฟล์ต้นฉบับ SQL dump และไฟล์ docker-compose.yml เพื่อป้องกันปัญหาไทม์ไลน์ว่างเปล่าจากการคัดลอกโฟลเดอร์ Postgres โดยตรง
สิ่งที่การสำรองข้อมูล Immich ต้องมี
การสำรองข้อมูล Immich ประกอบด้วยสามส่วนที่ต้องบันทึกไว้ในเวลาเดียวกัน ได้แก่ ไฟล์ต้นฉบับภายใต้ UPLOAD_LOCATION, ไฟล์ SQL dump ของฐานข้อมูล Postgres และไฟล์ .env กับ docker-compose.yml ที่อธิบายโครงสร้างของ stack การกู้คืนข้อมูลหมายถึงการนำไฟล์ dump นั้นกลับเข้าสู่ฐานข้อมูลใหม่ในขณะที่เซิร์ฟเวอร์ Immich หยุดทำงาน และเริ่มการทำงานของ stack ส่วนที่เหลือหลังจากนั้นเท่านั้น หากลำดับขั้นตอนผิดพลาด คุณจะได้ Immich ที่ทำงานได้แต่แสดงไทม์ไลน์ว่างเปล่าบนดิสก์ที่มีข้อมูลเต็ม
การแยกส่วนข้อมูลมีความสำคัญเนื่องจาก Immich เก็บสถานะไว้ในสองที่ซึ่งไม่เชื่อมโยงกัน Postgres เก็บข้อมูลอัลบั้ม, กลุ่มใบหน้า, ลิงก์ที่แชร์, บัญชีผู้ใช้, API key และเส้นทางที่จัดเก็บของสินทรัพย์แต่ละรายการ ส่วนระบบไฟล์จะเก็บข้อมูลรูปภาพ หากกู้คืนไฟล์โดยไม่มีฐานข้อมูล Immich จะไม่แสดงผลใดๆ หากกู้คืนฐานข้อมูลโดยไม่มีไฟล์ สินทรัพย์ทุกรายการจะเปิดขึ้นมาเป็นภาพที่เสียหาย
คำสั่งในที่นี้เขียนขึ้นสำหรับ Immich v3.1.0 ซึ่งเป็นเวอร์ชันปัจจุบันในช่วงต้นเดือนสิงหาคม 2026 โครงการมีการพัฒนาอย่างรวดเร็วและขั้นตอนการสำรองข้อมูลที่ระบุไว้มีการเปลี่ยนแปลงมากกว่าหนึ่งครั้ง ดังนั้นโปรดตรวจสอบเวอร์ชันที่คุณใช้งานจริงก่อนคัดลอกคำสั่งใดๆ หาก stack ยังไม่เริ่มทำงาน ให้เริ่มจาก คู่มือการติดตั้ง Immich แล้วจึงกลับมาที่นี่อีกครั้ง
ทำความเข้าใจว่า path ของคุณชี้ไปที่ใด
ตัวแปรสองตัวใน .env เป็นตัวกำหนดทุกอย่างในหน้านี้ UPLOAD_LOCATION คือไดเรกทอรีหลักที่ Immich ใช้เขียนไฟล์สื่อทั้งหมดลงไป ส่วน DB_DATA_LOCATION คือไดเรกทอรีข้อมูลของ Postgres
ค่าเริ่มต้นใน example.env จะกำหนด UPLOAD_LOCATION=./library ซึ่งเป็นค่าเริ่มต้นที่ทำให้สับสนได้ง่าย เพราะ Immich จะสร้างโฟลเดอร์ชื่อ library ไว้ข้างใน นั้นอีกที ไฟล์ต้นฉบับของคุณจะไปอยู่ที่ ./library/library ให้กำหนดเป็น absolute path แทน เพื่อให้สคริปต์สำรองข้อมูลไม่ขึ้นอยู่กับไดเรกทอรีที่คุณสั่งรันสคริปต์นั้น
UPLOAD_LOCATION=/srv/immich/data
DB_DATA_LOCATION=/srv/immich/postgres
DB_USERNAME=postgres
DB_DATABASE_NAME=immich
IMMICH_VERSION=v3.1.0ภายใน UPLOAD_LOCATION Immich จะสร้างโฟลเดอร์ขึ้นมาหลายรายการ โดยมีสามรายการที่เก็บข้อมูลซึ่งไม่มีงานใดสามารถสร้างขึ้นใหม่ได้:
library: ไฟล์ต้นฉบับที่จัดเรียงตามเทมเพลตการจัดเก็บข้อมูลของคุณupload: ไฟล์ต้นฉบับที่ยังไม่ได้ถูกย้ายเข้าสู่โครงสร้างเทมเพลต รวมถึงไฟล์ที่กำลังอัปโหลดprofile: รูปโปรไฟล์ของผู้ใช้
หากสูญเสีย library ไป รูปภาพนั้นจะหายไปทันที Immich ไม่มีการเก็บสำเนาชุดที่สองของไฟล์ต้นฉบับไว้ที่ใดทั้งสิ้น
เหตุใดการคัดลอกไดเรกทอรีข้อมูลของ Postgres จึงไม่ใช่การสำรองข้อมูล
DB_DATA_LOCATION ดูเหมือนจะเป็นเป้าหมายที่ง่าย มันเป็นไดเรกทอรี และ rsync จะคัดลอกมัน และการคัดลอกก็เสร็จสิ้นโดยไม่มีข้อผิดพลาด แต่มันก็ยังไม่ใช่การสำรองข้อมูล ด้วยเหตุผล 2 ประการที่คุณสามารถสังเกตเห็นความล้มเหลวได้
ประการแรกคือการขาดความต่อเนื่อง (tearing) Postgres จะเขียนการเปลี่ยนแปลงทุกอย่างลงใน write-ahead log (WAL) ก่อน จากนั้นจึงนำไปปรับใช้กับไฟล์ตารางในภายหลัง ณ จุด checkpoint ดังนั้นในทุกขณะ ไฟล์บนดิสก์จึงอยู่ในสถานะที่ยังไม่สมบูรณ์ และการคัดลอกแบบต่อเนื่องที่ใช้เวลา 4 นาทีจะอ่านไฟล์แรกที่เวลา 02:00 และไฟล์สุดท้ายที่ 02:04 ไฟล์ทั้งสองนั้นไม่ได้เป็นส่วนหนึ่งของธุรกรรมเดียวกัน เมื่อคุณเริ่มการทำงานของ Postgres บนผลลัพธ์ดังกล่าว มันจะปฏิเสธการทำงานตั้งแต่เริ่มด้วย PANIC: could not locate a valid checkpoint record หรือมันอาจเริ่มทำงานแล้วหยุดลงเมื่ออ่านหน้าข้อมูลที่เสียหายเป็นครั้งแรกด้วย invalid page in block 1234 of relation base/16384/... ซึ่งทั้งสองกรณีไม่สามารถกู้คืนได้จากการคัดลอกนั้น
ประการที่สองคือปัญหาที่ยังคงอยู่แม้ว่าคุณจะหยุดทุกอย่างก่อนแล้วก็ตาม ไดเรกทอรีข้อมูลของ Postgres ผูกติดอยู่กับไบนารีชุดที่เขียนข้อมูลนั้นโดยเฉพาะ Immich กำหนดเวอร์ชันของอิมเมจฐานข้อมูลไว้ด้วย digest ซึ่งปัจจุบันคือ ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0 นั่นคือ Postgres 14 ที่คอมไพล์ร่วมกับส่วนขยาย vector-search สองตัว ไดเรกทอรีข้อมูลที่เขียนโดยบิลด์นั้นจะไม่สามารถเปิดได้ภายใต้ Postgres เวอร์ชันหลักที่ต่างออกไป และจะไม่สามารถเปิดได้ภายใต้บิลด์ที่มีเวอร์ชันของส่วนขยายต่างกัน โฮสต์ที่คุณใช้กู้คืนจะต้องจำลองอิมเมจนั้นให้เหมือนกันทุกประการ ในขณะที่ SQL dump ไม่สนใจเรื่องนี้ เพราะมันเป็นข้อความ และเซิร์ฟเวอร์ใดก็ตามที่เข้ากันได้จะสามารถประมวลผลข้อมูลนั้นใหม่ได้
pg_dump ช่วยเลี่ยงปัญหาเรื่องการขาดความต่อเนื่องได้โดยตรง มันจะอ่านฐานข้อมูลทั้งหมดภายใน snapshot เดียวของ MVCC (multi-version concurrency control) ดังนั้นมันจึงเห็นฐานข้อมูลในสถานะที่ตรงกับช่วงเวลาใดเวลาหนึ่งอย่างแม่นยำ ในขณะที่การเขียนข้อมูลอื่นยังคงดำเนินต่อไปรอบๆ นั่นคือเหตุผลที่คุณไม่จำเป็นต้องหยุด Postgres เพื่อทำการ dump ข้อมูล
สิ่งที่สามารถละเว้นจากการสำรองข้อมูลได้
ข้อมูลเหล่านี้สามารถสร้างใหม่ได้ คุณจึงอาจข้ามการสำรองข้อมูลได้:
thumbs: รูปภาพตัวอย่างและภาพย่อ (preview และ thumbnail)encoded-video: วิดีโอที่ผ่านการแปลงรหัส (transcoded video)DB_DATA_LOCATION: ข้อมูลที่สร้างใหม่จากไฟล์ dumpmodel-cacheDocker volume: โมเดล machine learning ซึ่งสามารถดาวน์โหลดใหม่ได้ตามความต้องการ
การละเว้นข้อมูลเหล่านี้เป็นการแลกเปลี่ยน ไม่ใช่ข้อดีที่ได้มาเปล่าๆ การสร้างภาพย่อและวิดีโอที่แปลงรหัสใหม่สำหรับคลังข้อมูลขนาดใหญ่ต้องใช้เวลาประมวลผลของ CPU นานหลายชั่วโมงบน VPS ขนาดเล็ก และจะแสดงเป็นพื้นที่ว่างสีเทาบนไทม์ไลน์ตลอดระยะเวลาดังกล่าว คุณสามารถสั่งประมวลผลใหม่ได้จาก Administration > Jobs โดยตั้งค่า "Generate Thumbnails" และ "Transcode Videos" ให้ทำงานเฉพาะกับสินทรัพย์ที่ขาดหายไป หากพื้นที่จัดเก็บข้อมูลสำรองของคุณเพียงพอ ให้รวมข้อมูลเหล่านี้ไว้เพื่อหลีกเลี่ยงการรอคอย แต่หากพื้นที่จัดเก็บใกล้เต็ม ให้ละเว้นข้อมูลเหล่านี้และวางแผนสำหรับการสร้างใหม่ การประเมินขนาดคลังข้อมูล Immich อธิบายว่าโฟลเดอร์เหล่านี้มีขนาดใหญ่ขึ้นเพียงใดเมื่อเทียบกับไฟล์ต้นฉบับ
มีอีกหนึ่งโฟลเดอร์ที่ควรทราบ UPLOAD_LOCATION/backups เป็นที่เก็บไฟล์ database dump อัตโนมัติของ Immich ซึ่งจะถูกเขียนขึ้นทุกวันเวลา 02:00 น. โดยจะเก็บย้อนหลังไว้ 14 วัน ซึ่งสามารถกำหนดค่าได้ที่ Administration > Settings > Backup ข้อมูลเหล่านี้ไม่มีค่าใช้จ่ายและมีประโยชน์อย่างแท้จริง อย่างไรก็ตาม ไฟล์เหล่านี้ถูกเก็บไว้ในดิสก์เดียวกับคลังข้อมูลที่ปกป้องอยู่ จึงช่วยได้ในกรณีการย้ายข้อมูลที่ผิดพลาด แต่ไม่ช่วยในกรณีที่เซิร์ฟเวอร์เสียหาย คุณควรทำไฟล์ dump ด้วยตนเองเสมอ เพราะไฟล์ dump ที่คุณสั่งทำเองจะเกิดขึ้นในช่วงเวลาเดียวกับไฟล์ snapshot ที่สอดคล้องกัน
การสำรองข้อมูลฐานข้อมูล
docker exec -t immich_postgres pg_dump --clean --if-exists \
--dbname=immich --username=postgres \
| gzip > /srv/immich/backup/immich.sql.gzแทนที่ immich และ postgres ด้วย DB_DATABASE_NAME และ DB_USERNAME ของคุณหากมีการเปลี่ยนแปลง --clean --if-exists จะเพิ่ม DROP ... IF EXISTS ไว้หน้า CREATE ทุกรายการ เพื่อให้การนำข้อมูลกลับเข้าฐานข้อมูล (dump replay) สามารถดำเนินการต่อไปได้แม้ในฐานข้อมูลที่มีออบเจกต์อยู่แล้ว แทนที่จะหยุดทำงานตั้งแต่รายการแรกที่พบข้อผิดพลาด
นี่คือรายละเอียดที่มักทำให้สคริปต์สำรองข้อมูลล้มเหลวโดยไม่แจ้งเตือน คำสั่งดังกล่าวเป็น pipeline และ shell จะรายงานสถานะการทำงานของคำสั่ง สุดท้าย ใน pipeline เท่านั้น หาก pg_dump ล้มเหลวเนื่องจากรหัสผ่านผิดหรือคอนเทนเนอร์ไม่ได้ทำงาน gzip จะได้รับสตรีมที่ว่างเปล่า และสร้างไฟล์ gzip ที่สมบูรณ์ออกมาพร้อมสถานะ exit 0 สคริปต์ของคุณจะบันทึกว่าสำเร็จทั้งที่ได้ไฟล์สำรองขนาดเพียง 20 ไบต์ ให้ใส่ pipefail ไว้ที่ส่วนบนสุดของสคริปต์สำรองข้อมูลทุกฉบับ:
#!/usr/bin/env bash
set -euo pipefailจากนั้นให้ตรวจสอบผลลัพธ์แทนการเชื่อถือ exit code เพียงอย่างเดียว:
ls -lh /srv/immich/backup/immich.sql.gz
gunzip -c /srv/immich/backup/immich.sql.gz | head -n 3บรรทัดแรกของไฟล์ dump ที่สมบูรณ์ควรขึ้นต้นด้วย -- PostgreSQL database dump หากไฟล์มีขนาดเพียงไม่กี่ร้อยไบต์ แสดงว่าการสำรองข้อมูลล้มเหลวไม่ว่าสคริปต์จะรายงานว่าอย่างไรก็ตาม
บันทึกข้อมูลว่า build ใดเป็นผู้สร้างไฟล์สำรองไว้ข้างไฟล์ dump:
docker inspect --format '{{.Config.Image}}' immich_server > /srv/immich/backup/immich-version.txtอย่าพึ่งพา .env สำหรับงานนี้ เนื่องจากไฟล์มาตรฐานจะกำหนด IMMICH_VERSION=v3 ซึ่งเป็น floating tag ที่เปลี่ยนไปตามทุกรุ่น 3.x จึงไม่สามารถระบุได้ว่า build ใดเป็นผู้สร้างไฟล์ dump จริง ให้ระบุ tag ที่แน่นอนลงใน .env ด้วยเช่นกัน
หยุดการทำงานของเซิร์ฟเวอร์แล้วทำ snapshot ด้วย restic
ไฟล์ภายใต้ UPLOAD_LOCATION จะไม่คงที่ในขณะที่ Immich ทำงานอยู่ เซิร์ฟเวอร์จะเขียนไฟล์อัปโหลดใหม่และงานจัดเก็บข้อมูลตามเทมเพลตจะย้ายไฟล์ไปมาระหว่างไดเรกทอรี หากเครื่องมือสำรองข้อมูลอ่านไฟล์ในขณะที่กำลังเขียนอยู่ เครื่องมือจะจัดเก็บไบต์เหล่านั้นเสมือนว่าเป็นไฟล์ที่สมบูรณ์โดยไม่มีการแจ้งเตือนข้อผิดพลาดใดๆ ให้หยุดคอนเทนเนอร์ของเซิร์ฟเวอร์ตลอดระยะเวลาที่ทำการสำรองข้อมูล:
docker stop immich_serverให้ปล่อย immich_postgres ทำงานต่อไปเนื่องจากกระบวนการ dump ข้อมูลจำเป็นต้องใช้ อินเทอร์เฟซเว็บและแอปมือถือจะออฟไลน์จนกว่าคุณจะเริ่มเซิร์ฟเวอร์ใหม่อีกครั้ง ซึ่งโดยปกติแล้วเวลา 03:00 น. สำหรับการใช้งานในบ้านถือว่าไม่มีปัญหา
restic เหมาะสมกับงานนี้เพราะสามารถทำ deduplication และเข้ารหัสข้อมูลก่อนที่จะส่งออกจากเครื่อง ให้ชี้เป้าหมายไปยัง repository ที่ไม่ได้อยู่บนเซิร์ฟเวอร์นี้:
export RESTIC_REPOSITORY=sftp:backup@backup.example.com:/srv/restic/immich
export RESTIC_PASSWORD_FILE=/root/.restic-password
restic initการใช้ object storage ก็มีหลักการเดียวกันและเป็นทางเลือกที่ดีกว่าหากคุณต้องการสำรองข้อมูลไว้นอกฮาร์ดแวร์ของคุณโดยสมบูรณ์:
export RESTIC_REPOSITORY=s3:https://s3.example.com/immich-backup
export AWS_ACCESS_KEY_ID=your-access-key
export AWS_SECRET_ACCESS_KEY=your-secret-key
restic initendpoint ดังกล่าวสามารถเป็น MinIO bucket ที่คุณรันเอง บนเครื่องที่สอง หรือผู้ให้บริการที่รองรับ S3 รายใดก็ได้ การเก็บ repository ไว้บนดิสก์ลูกเดียวกับคลังข้อมูลจะช่วยป้องกันได้เพียงการลบไฟล์โดยไม่ตั้งใจเท่านั้น แต่ไม่สามารถป้องกันความเสียหายในกรณีอื่นได้
จากนั้นให้ทำ snapshot โดยระบุสิ่งที่สำคัญอย่างชัดเจน:
restic backup \
/srv/immich/backup/immich.sql.gz \
/srv/immich/backup/immich-version.txt \
/srv/immich/data/library \
/srv/immich/data/upload \
/srv/immich/data/profile \
/srv/immich/.env \
/srv/immich/docker-compose.yml
docker start immich_serverrestic จะอ่านโครงสร้างไฟล์ทั้งหมดในทุกครั้งที่รัน แต่จะอัปโหลดเฉพาะบล็อกข้อมูลที่ไม่เคยพบมาก่อน ดังนั้น snapshot แรกจะย้ายคลังข้อมูลทั้งหมดของคุณ และ snapshot หลังจากนั้นจะย้ายเฉพาะรูปภาพใหม่ของแต่ละวันเท่านั้น
การเก็บรักษาและกุญแจที่ต้องเก็บไว้ที่อื่น
restic forget --prune --keep-daily 7 --keep-weekly 5 --keep-monthly 12forget จะลบ snapshot ออกจากดัชนี ส่วน --prune คือส่วนที่ทำหน้าที่ลบข้อมูลที่ snapshot เหล่านั้นเคยอ้างอิงเป็นลำดับสุดท้าย หากคุณรัน forget โดยไม่มี --prune ค่าใช้จ่ายในการจัดเก็บข้อมูลของคุณจะไม่ลดลง
การตรวจสอบโครงสร้างใช้ทรัพยากรน้อย ดังนั้นควรทำสัปดาห์ละครั้ง:
restic checkคำสั่งนี้จะตรวจสอบว่า metadata ของ repository มีความสอดคล้องกัน โดยไม่ได้อ่านข้อมูลจริงของคุณ เดือนละครั้ง ให้ลองอ่านข้อมูลตัวอย่างแล้วตรวจสอบเทียบกับค่า hash ที่บันทึกไว้:
restic check --read-data-subset=5%นี่คือการตรวจสอบเพียงวิธีเดียวที่สามารถตรวจพบความเสียหายของข้อมูลที่เกิดขึ้นเงียบๆ บน storage backend ได้ เนื่องจากเป็นการดาวน์โหลดบล็อกข้อมูลจริงมาคำนวณ checksum ใหม่ การรัน --read-data เต็มรูปแบบกับคลังรูปภาพหมายถึงการดาวน์โหลด repository ทั้งหมด ซึ่งหากเป็น object storage ที่คิดค่าบริการตามปริมาณการใช้งาน จะทำให้เสียค่าใช้จ่ายจริง ดังนั้นการสุ่มตรวจสอบแบบหมุนเวียนจึงเป็นวิธีที่ผู้ใช้งานจริงนิยมทำกัน
มาถึงส่วนที่คนมักจะข้ามไป รหัสผ่านของ restic repository ไม่สามารถกู้คืนได้ ไม่มีการรีเซ็ตรหัสผ่านและไม่มีช่องทางสนับสนุนใดๆ หากสำเนาเพียงชุดเดียวอยู่ใน /root/.restic-password บนเซิร์ฟเวอร์ที่คุณกำลังพยายามกู้คืน ข้อมูลสำรองของคุณก็จะเป็นเพียงข้อมูลที่ถูกเข้ารหัสจนอ่านไม่ออก เช่นเดียวกับ access key ของ object storage และ DB_PASSWORD จาก .env ให้เก็บข้อมูลทั้งหมดนี้ไว้ในที่ที่ไม่ขึ้นอยู่กับการทำงานของเครื่องนี้ เช่น พิมพ์ออกมาเก็บไว้ในลิ้นชัก หรือเก็บไว้ในโปรแกรมจัดการรหัสผ่านที่รันบนฮาร์ดแวร์อื่น หากโปรแกรมจัดการรหัสผ่านนั้นเป็นแบบ self-hosted เช่นกัน ก็ต้องใช้วิธีการเดียวกัน และการ สำรองข้อมูล Vaultwarden ก็ถือเป็นงานที่ต้องทำแยกต่างหาก
การกู้คืน Immich ตามลำดับที่ถูกต้อง
ลำดับการกู้คืนคือจุดที่การสำรองข้อมูลที่ดีอาจกลายเป็นไทม์ไลน์ที่ว่างเปล่าได้ โปรดปฏิบัติตามลำดับนี้บนโฮสต์ใหม่
กู้คืนการตั้งค่าก่อนเป็นอันดับแรก การตั้งค่าจะระบุเวอร์ชันที่ต้องใช้งานและตำแหน่งของ path ต่างๆ
restic restore latest --target /restore \
--include /srv/immich/.env \
--include /srv/immich/docker-compose.yml \
--include /srv/immich/backupกำหนดเวอร์ชันให้คงที่ก่อนเริ่มดำเนินการใดๆ อ่าน immich-version.txt แล้วตั้งค่า IMMICH_VERSION ใน .env ให้เป็น tag นั้นโดยเฉพาะ และอย่าเพิ่งใช้ release ล่าสุดในตอนนี้ Immich ไม่รองรับการย้อนเวอร์ชัน (downgrade) แม้จะเป็นระหว่าง patch release ก็ตาม ดังนั้นหากเซิร์ฟเวอร์เวอร์ชันใหม่เริ่มทำงานกับฐานข้อมูลเก่าและรันการ migration ไปแล้ว จะไม่สามารถย้อนกลับได้
กู้คืนไฟล์สื่อ (media)
restic restore latest --target /restore --include /srv/immich/dataจากนั้นย้าย library, upload และ profile ไปไว้ภายในตำแหน่งที่ UPLOAD_LOCATION ชี้ไปยังโฮสต์นี้โดยตรง ตัว path บนโฮสต์อาจเปลี่ยนแปลงได้เนื่องจากไฟล์ compose จะผูก directory นั้นเข้ากับ path ที่กำหนดไว้ภายใน container แต่โครงสร้างภายใน directory นั้นห้ามเปลี่ยนแปลง
เริ่มการทำงานของฐานข้อมูลแยกต่างหาก เว้นว่าง DB_DATA_LOCATION ไว้เพื่อให้ Postgres เริ่มต้นสร้าง cluster ใหม่
cd /srv/immich
docker compose pull
docker compose create
docker start immich_postgres
docker exec immich_postgres pg_isready --username=postgrespg_isready จะแสดง accepting connections เมื่อการตั้งค่าครั้งแรกเสร็จสิ้น ซึ่งใช้เวลาเพียงไม่กี่วินาที docker compose create จะทำการ build container ทุกตัวโดยไม่เริ่มการทำงาน ซึ่งเป็นจุดประสงค์หลักของขั้นตอนนี้: เซิร์ฟเวอร์ Immich ต้องยังไม่เริ่มทำงาน หากเซิร์ฟเวอร์เริ่มทำงานกับฐานข้อมูลที่ว่างเปล่า มันจะรันการ migration, สร้าง schema ใหม่ และขอให้คุณสร้างบัญชีผู้ดูแลระบบใหม่ ซึ่งจะทำให้คุณต้องนำไฟล์ dump ไปทับซ้อนกับแอปพลิเคชันที่กำลังทำงานอยู่
นำไฟล์ dump กลับเข้าสู่ระบบ (replay)
gunzip --stdout /restore/srv/immich/backup/immich.sql.gz \
| sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" \
| docker exec -i immich_postgres psql --dbname=immich --username=postgres \
--single-transaction --set ON_ERROR_STOP=onมีสองส่วนที่ทำหน้าที่สำคัญ sed มีไว้เนื่องจาก pg_dump จะเขียน search_path ที่ว่างเปล่าลงในผลลัพธ์เพื่อเป็นมาตรการความปลอดภัย เพื่อไม่ให้ชื่อที่ไม่มีการระบุ (unqualified names) ในไฟล์ dump ไปอ้างอิงถึง schema ที่ไม่คาดคิด ประเภทข้อมูล vector-search ของ Immich อยู่ใน public ดังนั้นหาก search path ว่างเปล่า การกู้คืนจะไปถึงคอลัมน์แรกที่ประกาศด้วยประเภท vector และ psql จะหยุดทำงานพร้อมกับ ERROR: type "vector" does not exist การนำ public กลับมาไว้บน path จะช่วยแก้ไขปัญหานี้
--single-transaction --set ON_ERROR_STOP=on จะครอบคลุมการกู้คืนทั้งหมดไว้ใน transaction เดียว ซึ่งจะยกเลิกทันทีหากเกิดข้อผิดพลาด คุณจะได้ฐานข้อมูลที่สมบูรณ์หรือฐานข้อมูลที่ไม่มีการเปลี่ยนแปลง หากไม่มีคำสั่งนี้ ความล้มเหลวระหว่างทางจะทำให้ได้ฐานข้อมูลที่เริ่มทำงานได้, ล็อกอินได้ แต่ขาดอัลบั้มรูปจำนวนหนึ่งไปโดยไม่ทราบสาเหตุ ซึ่งคุณอาจเพิ่งมาพบในอีกหลายสัปดาห์ให้หลัง
จากนั้นเริ่มการทำงานของทุกส่วน
docker compose up -d
docker compose ps
docker logs -f immich_serverรอจนกว่าจะมีบรรทัดสถานะการเริ่มทำงานเช่น Immich Server is listening on ปรากฏขึ้น จากนั้นเปิดพอร์ต 2283 และล็อกอินด้วยข้อมูลเดิมของคุณ เนื่องจากบัญชีผู้ใช้จะถูกกู้คืนมาพร้อมกับไฟล์ dump หากหน้าล็อกอินแสดงให้สร้างบัญชีผู้ดูแลระบบคนแรก แสดงว่าการกู้คืนฐานข้อมูลไม่สำเร็จ ให้หยุดและตรวจสอบผลลัพธ์ของ psql อีกครั้ง
คำเตือนเกี่ยวกับคำแนะนำการกู้คืนอย่างเป็นทางการ ซึ่งเริ่มต้นด้วย docker compose down -v คำสั่ง -v จะลบ named volume ออก ในไฟล์ compose มาตรฐาน UPLOAD_LOCATION และ DB_DATA_LOCATION เป็นแบบ bind mount จึงไม่ถูกลบ หากคุณเปลี่ยนส่วนใดส่วนหนึ่งเป็น named volume คำสั่งนั้นจะลบรูปภาพของคุณทิ้ง โปรดอ่านไฟล์ compose ของคุณก่อนพิมพ์คำสั่งดังกล่าว
เหตุใดเส้นเวลา (timeline) จึงว่างเปล่าหลังจากการกู้คืนข้อมูล
เส้นเวลาถูกสร้างขึ้นจากแถวข้อมูลในฐานข้อมูล Immich จะไม่ทำการสแกน upload/ ในขณะบูตเพื่อค้นหารูปภาพใหม่ เนื่องจากไฟล์ที่ไม่มีแถวข้อมูลในฐานข้อมูลจะไม่มีเจ้าของ ไม่มีวันที่ และไม่มีอัลบั้ม ดังนั้นข้อผิดพลาดที่พบบ่อยที่สุดในการกู้คืนคือการนำไฟล์กลับมาได้แต่ฐานข้อมูลหายไป Immich จะเริ่มทำงาน สร้าง schema เปล่าขึ้นมา และให้คุณใช้งาน instance ที่ว่างเปล่าในขณะที่ดิสก์เต็มไปด้วยรูปภาพของคุณ ข้อมูลไม่ได้สูญหายไปไหน แต่ไม่มีสิ่งใดแสดงขึ้นมา วิธีแก้ไขคือการนำไฟล์ dump กลับเข้าไปใหม่ในขณะที่หยุดการทำงานของเซิร์ฟเวอร์ไว้ ตามขั้นตอนที่ระบุไว้ข้างต้น
กรณีที่สองคือสถานการณ์ที่เงียบกว่า ฐานข้อมูลถูกกู้คืน เส้นเวลาแสดงรายการข้อมูลครบถ้วน แต่ไม่สามารถเปิดไฟล์รูปภาพได้ นั่นหมายความว่าแถวข้อมูลชี้ไปยังไฟล์ที่คอนเทนเนอร์มองไม่เห็น ซึ่งมักเกิดจาก library, upload และ profile อยู่ลึกเกินไปหนึ่งระดับหลังจากทำการ restic restore --target /restore โดยที่ไม่มีการย้ายไฟล์ไปยังตำแหน่งที่ถูกต้อง ให้ตรวจสอบจากภายในคอนเทนเนอร์แทนการคาดเดา:
docker exec immich_server ls /dataไฟล์ compose มาตรฐานจะ mount UPLOAD_LOCATION ไว้ที่ /data ดังนั้นรายการที่แสดงควรปรากฏ library, upload และ profile หากแสดงผลเป็นไดเรกทอรีว่างหรือมีโฟลเดอร์ srv โผล่มา แสดงว่า bind mount ของคุณชี้ไปยังระดับที่ไม่ถูกต้อง แต่ข้อมูลในแถวฐานข้อมูลนั้นถูกต้องแล้ว
การจับคู่เวอร์ชันระหว่างการสำรองข้อมูลและการกู้คืน
Immich มีการออกรุ่นบ่อยครั้งและโครงสร้างฐานข้อมูล (schema) ก็เปลี่ยนแปลงตามไปด้วย ดังนั้นไฟล์ dump จึงมีโครงสร้างของเซิร์ฟเวอร์เวอร์ชันที่สร้างไฟล์นั้นขึ้นมา
การกู้คืนไฟล์ dump เวอร์ชันเก่าลงในเซิร์ฟเวอร์เวอร์ชันใหม่มักจะทำได้สำเร็จ เนื่องจากเซิร์ฟเวอร์จะทำการรัน migration ที่ค้างอยู่เมื่อเริ่มต้นระบบและปรับโครงสร้างฐานข้อมูลให้เป็นปัจจุบัน เส้นทางการอัปเกรดนี้ได้รับการทดสอบมาตลอดลำดับการออกรุ่น แต่การข้ามเวอร์ชันหลักหลายเวอร์ชันในคราวเดียวมักจะทำให้เกิดปัญหา ซึ่งโครงการได้ระบุการเปลี่ยนแปลงที่อาจส่งผลกระทบ (breaking changes) ไว้ในรุ่นหลักและบันทึกไว้ใน changelog แล้ว
การกู้คืนไฟล์ dump เวอร์ชันใหม่ลงในเซิร์ฟเวอร์เวอร์ชันเก่าไม่สามารถทำได้ เนื่องจากไฟล์ dump มีตารางและคอลัมน์ที่โค้ดเวอร์ชันเก่าไม่รู้จัก และ Immich ระบุว่าไม่รองรับการลดเวอร์ชัน (downgrade) แม้แต่ระหว่างรุ่นย่อย (patch releases) ทั้งนี้ไม่มีคำสั่ง rollback ให้ใช้งาน
ดังนั้น วิธีการกู้คืนที่ปลอดภัยคือวิธีที่เรียบง่าย ให้รัน Immich เวอร์ชันเดียวกับที่สร้างไฟล์ dump นั้นขึ้นมา ทำการกู้คืนข้อมูล ล็อกอินเข้าสู่ระบบ ตรวจสอบว่า timeline ข้อมูลครบถ้วน แล้วจึงค่อยดำเนินการอัปเกรดหลังจากนั้น โดยให้อัปเกรดทีละรุ่น พร้อมกับปรับเวอร์ชันใน IMMICH_VERSION และรัน docker compose pull && docker compose up -d หลังจากอัปเกรดแต่ละครั้ง การเก็บไฟล์ dump ย้อนหลังไว้หนึ่งสัปดาห์จะช่วยได้มากในกรณีนี้ หากพบว่าไฟล์ dump ล่าสุดถูกสร้างขึ้นในระหว่างการอัปเกรดที่ล้มเหลว คุณก็ยังมีไฟล์ของเมื่อวานอยู่ใน repository ให้ใช้งาน
ตรวจสอบข้อมูลสำรองทุกเดือน
ข้อมูลสำรองที่คุณไม่เคยทดสอบกู้คืนถือเป็นเพียงการคาดเดาเท่านั้น ให้ทำการกู้คืนข้อมูลลงใน instance ชั่วคราวเดือนละหนึ่งครั้งแล้วตรวจสอบรูปภาพ การซ้อมกู้คืนนี้ใช้เวลาประมาณยี่สิบนาที และเป็นสิ่งเดียวที่จะเปลี่ยนเนื้อหาทั้งหมดในหน้านี้ให้กลายเป็นแผนการกู้คืนระบบที่ใช้งานได้จริง
restic snapshots
restic stats latestsnapshots ควรแสดงรายการการทำงานของเมื่อคืนนี้ stats latest ควรรายงานขนาดที่ใกล้เคียงกับคลังข้อมูลของคุณ ไม่ใช่เพียงไม่กี่เมกะไบต์
ให้กู้คืนลงในไดเรกทอรีชั่วคราว โดยควรทำบนโฮสต์สำรอง:
restic restore latest --target /tmp/immich-drillคัดลอก docker-compose.yml และ .env ออกมาจากชุดข้อมูลที่กู้คืนมา จากนั้นแก้ไขสามจุดในไฟล์สำเนา ให้ชี้ UPLOAD_LOCATION และ DB_DATA_LOCATION ไปยังไดเรกทอรีภายใต้ /tmp/immich-drill เปลี่ยนพอร์ตเว็บไปใช้พอร์ตอื่น เช่น 12283:2283 แทน 2283:2283 ลบบรรทัด container_name: ออก เนื่องจากไฟล์ compose มาตรฐานมีการระบุชื่อแบบ hard-code ไว้ เช่น immich_server ซึ่งจะทำให้ stack ที่สองบนโฮสต์เดียวกันเกิดความขัดแย้งกับ stack แรก และ Docker จะปฏิเสธการสร้างคอนเทนเนอร์
ดำเนินการตามขั้นตอนการกู้คืนจากด้านบน: กู้คืนเฉพาะฐานข้อมูล, replay ข้อมูล dump, แล้วตามด้วย docker compose up -d จากนั้นให้ทำการตรวจสอบสี่ประการเพื่อยืนยันความถูกต้อง:
- เข้าสู่ระบบด้วยรหัสผ่านที่คุณใช้ก่อนเริ่มการซ้อม หากเข้าสู่ระบบได้แสดงว่าการกู้คืนข้อมูล dump สำเร็จ
- เปิดไทม์ไลน์และเลื่อนไปยังเดือนที่เก่าที่สุด หากพบเนื้อหาตลอดช่วงเวลาทั้งหมด แสดงว่าข้อมูลทุกแถวถูกกู้คืนกลับมา ไม่ใช่แค่ข้อมูลล่าสุดเท่านั้น
- เปิดรูปภาพหนึ่งรูปที่ขนาดเต็มและดาวน์โหลดไฟล์ต้นฉบับออกมา
- เปรียบเทียบไฟล์นั้นกับไฟล์เดียวกันในคลังข้อมูลจริงของคุณด้วย
sha256sumหากค่าแฮชตรงกัน แสดงว่าข้อมูลไบต์ทั้งหมดผ่านกระบวนการรับส่งผ่าน restic มาได้อย่างสมบูรณ์
จากนั้นให้ลบการซ้อมกู้คืนทิ้งด้วย docker compose down -v ในไดเรกทอรีที่ใช้ซ้อม และลบ /tmp/immich-drill บันทึกวันที่ไว้ในที่ที่คุณจะมองเห็นได้ชัดเจน เพราะคุณค่าของกระบวนการนี้อยู่ที่การทำซ้ำในเดือนถัดไป หากคุณยังตัดสินใจไม่ได้ว่าจะเลือกใช้เซิร์ฟเวอร์รูปภาพตัวใด การเปรียบเทียบระหว่าง PhotoPrism และ Immich จะครอบคลุมถึงความแตกต่างของทั้งสองตัวในประเด็นนี้โดยเฉพาะ
FAQ
ฉันจำเป็นต้องหยุดการทำงานของ Immich เพื่อสำรองข้อมูลหรือไม่?
ให้หยุดการทำงานของ immich_server แต่ปล่อยให้ immich_postgres ทำงานต่อไป ฐานข้อมูลไม่จำเป็นต้องหยุดพักเนื่องจาก pg_dump จะอ่านข้อมูลภายใน MVCC snapshot ชุดเดียว ซึ่งจะเห็นสถานะที่สอดคล้องกัน ณ ช่วงเวลาใดเวลาหนึ่งโดยไม่สนใจว่าจะมีกระบวนการอื่นเขียนข้อมูลอยู่หรือไม่ ส่วนไฟล์เป็นเหตุผลที่ต้องหยุดการทำงาน เนื่องจากเซิร์ฟเวอร์จะเขียนไฟล์ที่อัปโหลดใหม่และงาน storage template จะย้ายไฟล์ระหว่างไดเรกทอรี ทำให้เครื่องมือสำรองข้อมูลอาจอ่านไฟล์ในขณะที่ยังเขียนไม่เสร็จและจัดเก็บสำเนาที่ไม่สมบูรณ์โดยไม่มีข้อผิดพลาดแจ้งเตือน การใช้ docker stop immich_server ก่อนทำ snapshot และ docker start immich_server หลังจากนั้นจะช่วยขจัดปัญหาความขัดแย้งนี้
ฉันสามารถคัดลอกโฟลเดอร์ข้อมูลของ Postgres แทนการใช้ pg_dump ได้หรือไม่?
ไม่ได้ การคัดลอกไดเรกทอรีข้อมูลในขณะที่ระบบกำลังทำงานอยู่จะทำให้อ่านไฟล์ต่างช่วงเวลากัน ผลลัพธ์ที่ได้จึงไม่ใช่สถานะที่สอดคล้องกัน และ Postgres จะปฏิเสธการทำงานเมื่อเริ่มระบบด้วยข้อผิดพลาด PANIC: could not locate a valid checkpoint record หรือเกิดความล้มเหลวในภายหลังเนื่องจากหน้าข้อมูลเสียหาย แม้แต่การคัดลอกในขณะที่หยุดทุกอย่างแล้วก็ยังผูกติดอยู่กับรุ่นของฐานข้อมูลที่เฉพาะเจาะจง: Immich กำหนดให้ใช้ Postgres 14 พร้อมส่วนขยาย vector-search เวอร์ชันเฉพาะ ซึ่งไดเรกทอรีดังกล่าวจะไม่สามารถเปิดใช้งานกับเวอร์ชันอื่นได้ แต่ SQL dump เป็นข้อความธรรมดาที่สามารถนำไปใช้กับเซิร์ฟเวอร์ที่รองรับได้ทุกเครื่อง
ทำไมไทม์ไลน์ใน Immich ของฉันถึงว่างเปล่าหลังจากกู้คืนข้อมูล?
เนื่องจากไทม์ไลน์ถูกสร้างขึ้นจากแถวข้อมูลในฐานข้อมูล และคุณกู้คืนเฉพาะไฟล์โดยไม่มีฐานข้อมูล Immich จะไม่สแกน upload/ เพื่อค้นหารูปภาพใหม่ ดังนั้นไฟล์ที่ไม่มีแถวข้อมูลอ้างอิงจะยังคงมองไม่เห็น แม้ตัวรูปภาพจะยังอยู่ครบถ้วน ให้หยุดเซิร์ฟเวอร์ นำ dump ไปโหลดเข้าสู่ Postgres ที่เริ่มต้นใหม่ แล้วจึงเริ่ม stack อีกครั้ง หากไทม์ไลน์แสดงข้อมูลครบแต่เปิดรูปภาพไม่ได้ แสดงว่าปัญหาเกิดจากสาเหตุตรงกันข้าม: library, upload และ profile ไม่ได้อยู่ในไดเรกทอรีที่ mount เข้าไปในคอนเทนเนอร์โดยตรง ให้ตรวจสอบด้วย docker exec immich_server ls /data
โฟลเดอร์ใดของ Immich ที่ฉันสามารถข้ามไปไม่ต้องสำรองข้อมูลได้?
thumbs และ encoded-video สามารถสร้างใหม่ได้จากไฟล์ต้นฉบับ และ DB_DATA_LOCATION สามารถสร้างใหม่ได้จากไฟล์ dump ดังนั้นจึงไม่จำเป็นต้องรวมโฟลเดอร์เหล่านี้ไว้ในชุดสำรองข้อมูล การข้ามโฟลเดอร์เหล่านี้จะทำให้ต้องเสียเวลาหลังจากกู้คืนข้อมูลแทนที่จะเสียพื้นที่จัดเก็บก่อนกู้คืน เนื่องจากกระบวนการสร้างพรีวิวและแปลงไฟล์สำหรับคลังข้อมูลขนาดใหญ่ต้องใช้เวลาประมวลผลหลายชั่วโมงผ่านเมนู Administration > Jobs สำหรับสินทรัพย์ที่หายไป สิ่งที่คุณห้ามข้ามเด็ดขาดคือ library, upload และ profile ซึ่งเป็นที่เก็บสำเนาเดียวของไฟล์ต้นฉบับทั้งหมด
ฉันสามารถกู้คืน Immich dump ไปยังเวอร์ชันที่ใหม่กว่าได้หรือไม่?
โดยปกติแล้วทำได้ เนื่องจากเซิร์ฟเวอร์จะใช้การย้ายข้อมูล (migrations) ที่ค้างอยู่เมื่อเริ่มทำงานและปรับปรุงโครงสร้างฐานข้อมูลให้เป็นปัจจุบัน แต่ในทางกลับกันจะไม่สามารถทำได้: Immich ไม่รองรับการลดเวอร์ชัน (downgrade) แม้แต่ระหว่างรุ่นย่อย ดังนั้น dump จากรุ่นที่ใหม่กว่าจึงไม่สามารถโหลดเข้าสู่เซิร์ฟเวอร์รุ่นเก่าได้ ให้กู้คืนด้วย IMMICH_VERSION ที่ระบุเวอร์ชันตรงกับรุ่นที่สร้าง dump นั้นขึ้นมา ตรวจสอบว่าไทม์ไลน์สมบูรณ์ แล้วจึงค่อยอัปเกรดในภายหลัง ควรบันทึกเวอร์ชันกำกับไว้กับไฟล์ dump แต่ละไฟล์ด้วย docker inspect --format '{{.Config.Image}}' immich_server เนื่องจากค่าเริ่มต้นอย่าง IMMICH_VERSION=v3 เป็น floating tag ที่ไม่ระบุข้อมูลเวอร์ชันที่ชัดเจน