ข้ามไปยังเนื้อหา

Backup และ security

หน้าที่ด้าน operations สองอย่างสุดท้ายคือสิ่งที่คุณหวังว่าจะไม่ต้องใช้และไม่อาจมองข้ามได้ Backup ให้คุณสร้างใหม่ได้หลังจากดิสก์ล้มเหลว การ deploy ที่ผิดพลาด หรือ DELETE ที่พลาดไป Security ควบคุมว่าใครอาจเชื่อมต่อได้ ใครอาจเห็นอะไร และข้อมูลเดินทางผ่านเครือข่ายอย่างไร ฐานข้อมูลที่เร็วและพร้อมใช้งานสูงก็ยังเป็นภาระอยู่ดี ถ้า restore กลับมาไม่ได้ หรือใครบนอินเทอร์เน็ตก็อ่านได้

บทเรียนนี้ครอบคลุม backup ทั้งสองตระกูลที่ PostgreSQL มีให้ จากนั้นเดินลงไปทีละชั้นของการควบคุมการเข้าถึงจากขอบเครือข่ายไปจนถึง row แต่ละ row

Backup ของ PostgreSQL มีสองสไตล์ที่เหมาะกับความต้องการที่ต่างกัน

ชนิดเครื่องมือเก็บอะไรไว้บ้างเหมาะที่สุดสำหรับ
Logicalpg_dump / pg_restoreSQL หรือ archive ของฐานข้อมูลหนึ่งฐานข้อมูลเดี่ยว, การย้ายเวอร์ชัน
Physicalbase backup + WAL archivingไฟล์ทั้ง cluster แบบ byte-for-bytecluster ขนาดใหญ่, point-in-time recovery

Logical backup รัน pg_dump ซึ่งอ่านฐานข้อมูลและเขียน statement ที่จำเป็นสำหรับสร้างฐานข้อมูลนั้นขึ้นมาใหม่ ไฟล์ที่ได้พกพาข้ามเครื่องและข้าม major version ได้ จึงเหมาะอย่างยิ่งกับการย้ายหรือ clone ฐานข้อมูลเดี่ยว

Terminal window
# Dump one database to a compressed custom-format archive
pg_dump --format=custom --file=shop.dump --dbname=shop
# Restore that archive into a fresh database
pg_restore --dbname=shop_restored --create shop.dump

รูปแบบ custom ที่ใช้ในที่นี้ให้ pg_restore restore บางส่วนแบบเลือกได้และรันแบบขนานได้ สำหรับฐานข้อมูลขนาดเล็ก ไฟล์ SQL ธรรมดาที่คุณ replay ด้วย psql ก็ใช้ได้เช่นกัน

สำหรับ cluster ขนาดใหญ่หรือยุ่ง การ dump แบบ logical ช้าเกินไป แทนที่จะทำเช่นนั้น คุณทำ base backup — สำเนาระดับ byte ของไฟล์ข้อมูล — และ archive write-ahead log อย่างต่อเนื่อง การ replay WAL ทับ base backup ให้คุณ restore กลับไปยังจังหวะเวลาใด ๆ ก็ได้ ซึ่งรู้จักกันในชื่อ point-in-time recovery (PITR)

Terminal window
# Take a base backup of the whole cluster
pg_basebackup --pgdata=/backups/base --format=tar --gzip --progress
# postgresql.conf: keep every WAL segment by archiving it
archive_mode = on
archive_command = 'test ! -f /archive/%f && cp %p /archive/%f'

ด้วย base backup บวกกับ WAL ที่ archive ไว้ การ recovery หมายถึงการ restore ไฟล์ base แล้วบอก PostgreSQL ให้ replay WAL ไปจนถึง timestamp ที่เลือก — คุณจึงสามารถย้อนกลับไปยังวินาทีก่อนที่ migration ที่ผิดพลาดจะรันได้

flowchart LR
  PG[(Primary cluster)] -->|pg_basebackup| Base[Base backup]
  PG -->|archive_command| WAL[Archived WAL segments]
  Base --> Restore[Restore]
  WAL -->|replay to a chosen time| Restore
  Restore --> Recovered[(Recovered cluster)]
base backup บวกกับ WAL ที่ archive ไว้ทำให้ point-in-time recovery เป็นไปได้

การควบคุมการเข้าถึงใน PostgreSQL มองภาพได้ดีที่สุดเป็นชุดของประตูที่ request ผ่านไป: ก่อนอื่นคือเครือข่าย จากนั้น authentication แล้วก็ role privilege และสุดท้ายคือกฎระดับ row แต่ละชั้นสามารถหยุด request ที่ชั้นก่อนหน้าปล่อยผ่านมาได้

flowchart LR
  Client[Client] -->|encrypted with TLS| HBA[pg_hba.conf - who may connect]
  HBA --> Auth[Authenticate the role]
  Auth --> Priv[Role privileges - GRANT and REVOKE]
  Priv --> RLS[Row-level security policy]
  RLS --> Data[(Table rows)]
connection ผ่านประตูเครือข่าย, auth, privilege, และ row

ประตูแรกคือ pg_hba.conf (host-based authentication) แต่ละบรรทัดบอกว่า role ใดอาจเชื่อมต่อกับฐานข้อมูลใดจาก address ใด และต้อง authenticate อย่างไร กฎถูกอ่านจากบนลงล่าง และการ match แรกชนะ

# TYPE DATABASE USER ADDRESS METHOD
hostssl all app 10.0.0.0/24 scram-sha-256
host all all 127.0.0.1/32 scram-sha-256

คีย์เวิร์ด hostssl กำหนดให้ connection ต้องเข้ารหัสด้วย TLS ซึ่งคุณเปิดใช้ใน postgresql.conf ด้วยการชี้ไปที่ certificate และ key

postgresql.conf
ssl = on
ssl_cert_file = '/etc/postgresql/server.crt'
ssl_key_file = '/etc/postgresql/server.key'

เมื่อเชื่อมต่อแล้ว สิ่งที่ role ทำได้จะกำกับด้วย privilege คุณสร้าง role แล้ว GRANT สิทธิ์ที่ต้องการ หรือ REVOKE คืนก็ได้ หลักการชี้นำคือ least privilege คือให้แต่ละ role เท่าที่จำเป็นต้องใช้จริงเท่านั้น

-- A read-only reporting role
CREATE ROLE reporting LOGIN PASSWORD 'set-a-strong-one';
GRANT CONNECT ON DATABASE shop TO reporting;
GRANT USAGE ON SCHEMA public TO reporting;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO reporting;
-- An application role that may also write to one table
CREATE ROLE app LOGIN PASSWORD 'set-a-strong-one';
GRANT SELECT, INSERT, UPDATE ON orders TO app;
REVOKE DELETE ON orders FROM app;

Privilege ควบคุมทั้ง table Row-level security (RLS) ลงลึกกว่านั้น โดยให้ policy ตัดสินว่า role อาจเห็นหรือเปลี่ยน row ใด — เป็นรากฐานของระบบ multi-tenant ที่แต่ละ tenant ต้องเห็นเฉพาะข้อมูลของตัวเองเท่านั้น

-- Turn on RLS and add a policy keyed to a session setting
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON orders
USING (tenant_id = current_setting('app.tenant_id')::bigint);

ด้วย policy นี้ query บน orders จะคืนค่าเฉพาะ row ที่ tenant_id ตรงกับค่าที่แอปพลิเคชันตั้งไว้สำหรับ session ปัจจุบันอย่างเงียบ ๆ WHERE ที่ลืมไปหนึ่งครั้งจะไม่ทำให้ข้อมูลของ tenant อื่นรั่วไหลอีกต่อไป

  • Backup จะเป็นของจริงก็ต่อเมื่อคุณ restore กลับมาได้สำเร็จแล้วเท่านั้น จัด table ทดสอบ restore เป็นประจำลงในฐานข้อมูลแบบใช้แล้วทิ้ง และยืนยันว่าข้อมูลยังครบถ้วน
  • Logical dump พกพาได้และเยี่ยมสำหรับฐานข้อมูลเดี่ยว; physical base backup บวกกับ WAL archiving คือสิ่งที่คุณต้องการสำหรับ cluster ขนาดใหญ่และ point-in-time recovery
  • ใช้ least privilege ในทุกที่ application role ไม่ควรเป็นเจ้าของ schema หรือสามารถ drop table ได้ และ reporting ควรเป็นแบบอ่านอย่างเดียว
  • อย่าใช้ authentication แบบ trust บน address ใดก็ตามที่เข้าถึงได้จากภายนอกเครื่อง — เพราะเปิดให้ใครก็ได้เชื่อมต่อในฐานะ role ใดก็ได้โดยไม่ต้องใช้ password
  • กำหนดให้ใช้ TLS สำหรับ connection ที่ข้ามเครือข่ายด้วย hostssl และจำไว้ว่ากฎ pg_hba.conf ถูก match จากบนลงล่าง การ match แรกชนะ
ตัวเลือกBenefitCost
Automated backup ที่ทดสอบ restore เป็นประจำกู้คืนได้จริงเมื่อเกิดภัยพิบัติจริงเสียพื้นที่ storage และต้องมีวินัยด้าน operations
พึ่งพา replication อย่างเดียวเพื่อความทนทานของข้อมูลfailover เร็ว พร้อมใช้งานสูงreplica ไม่ใช่ backup เพราะคัดลอกทั้งความผิดพลาดและการลบข้อมูลตามไปด้วย
  • ตั้งค่า backup ครั้งเดียวแล้วไม่เคยทดสอบ restore — ไม่มีใครรู้ว่า backup พังจนกว่าจะต้องใช้จริง จัดตาราง restore drill เป็นประจำ
  • ถือว่า replica เป็น backup — replica เป็นสำเนาที่ตามทัน primary แบบเรียลไทม์ ไม่ใช่สำเนาที่ point-in-time recoverable แยกต่างหาก ต้องมี backup จริงควบคู่ไปด้วยเสมอ
  • ให้ role ของแอปพลิเคชันมีสิทธิ์กว้างเกินจำเป็น (เช่นใช้ superuser role กับแอป) — ยึดหลัก least privilege ให้แต่ละ role ได้เฉพาะสิทธิ์ที่ต้องใช้จริงเท่านั้น

💡 ตัวอย่างจากของจริง

Instagram — ในช่วง scale ขึ้นไปรองรับผู้ใช้หลายร้อยล้านคน มีการซ้อม sharding และ backup อย่างสม่ำเสมอเป็นส่วนหนึ่งของ infrastructure practice

มาตรฐานทั่วไปในทีม PostgreSQL ระดับโปรดักชัน — การทดสอบ restore แบบอัตโนมัติเป็นประจำถือเป็น practice มาตรฐาน ไม่ใช่แค่การสร้าง backup อย่างเดียว

แนวทาง backup ใดเหมาะที่สุดสำหรับ point-in-time recovery บน cluster ขนาดใหญ่?
row-level security ให้คุณควบคุมอะไรที่ GRANT และ REVOKE ทำไม่ได้?
ทำไมคุณจึงควรหลีกเลี่ยง authentication แบบ trust บน address ที่เข้าถึงได้แบบสาธารณะ?