23 คะแนน โดย GN⁺ 2026-01-23 | 4 ความคิดเห็น | แชร์ทาง WhatsApp
  • มักมีการใช้ การลบแบบซอฟต์โดยอิงคอลัมน์ archived_at เพื่อการกู้คืนข้อมูลและการปฏิบัติตามข้อกำหนด แต่เมื่อเวลาผ่านไป ความซับซ้อนและความไม่มีประสิทธิภาพ จะเพิ่มขึ้น
  • วิธีนี้ทำให้ คิวรี, ดัชนี, การทำมิเกรชัน, และตรรกะการกู้คืน ซับซ้อนขึ้น และเนื่องจากข้อมูลที่เก็บถาวรส่วนใหญ่แทบไม่ถูกอ่านกลับ จึงทำให้เกิด ภาระที่ไม่จำเป็นต่อฐานข้อมูล
  • ทางเลือกที่เสนอมีทั้ง การเก็บถาวรแบบอิงเหตุการณ์ของแอปพลิเคชัน, การเก็บถาวรแบบอิงทริกเกอร์, และ การเก็บถาวรแบบอิง WAL (Change Data Capture)
  • แต่ละแนวทางแตกต่างกันในด้าน ความซับซ้อนในการปฏิบัติการ, ความต้องการด้านโครงสร้างพื้นฐาน, และความง่ายในการกู้คืน โดยเฉพาะแบบอิง WAL ที่ต้องการ การผสานรวมกับระบบภายนอกอย่าง Kafka
  • หากเป็นโปรเจ็กต์ใหม่ แนวทางแบบอิงทริกเกอร์ คือ ตัวเลือกที่สมดุลที่สุดในแง่ความเรียบง่ายและการบำรุงรักษา

ปัญหาของการลบแบบซอฟต์

  • โดยทั่วไปจะใช้คอลัมน์บูลีน deleted หรือคอลัมน์ timestamp archived_at เพื่อลบข้อมูลในเชิงตรรกะ
    • สามารถกู้คืนได้เมื่อลูกค้าลบข้อมูลโดยไม่ตั้งใจ
    • บางกรณีก็จำเป็นต้องเก็บไว้เพื่อวัตถุประสงค์ด้านข้อบังคับหรือการตรวจสอบ
  • แต่คอลัมน์ archived_at ทำให้เกิดความซับซ้อนทั่วทั้ง คิวรี, การดำเนินงาน, และโค้ดแอปพลิเคชัน
    • ข้อมูลที่เก็บถาวรส่วนใหญ่แทบไม่ถูกอ่านกลับ
    • ปัญหาการทำงานของ API หรือเครื่องมืออัตโนมัติ (เช่น Terraform) อาจทำให้มี แถวที่ไม่จำเป็นสะสมเป็นหลักล้าน
  • หากไม่ได้ตั้งงานล้างข้อมูลที่เก็บถาวร จะเกิด ประสิทธิภาพลดลงระหว่างการสำรองและกู้คืนฐานข้อมูล
  • ต้องกรองข้อมูลที่เก็บถาวรออกจากคิวรีและดัชนี และมี ความเสี่ยงต่อการรั่วไหลของข้อมูล
  • ระหว่างมิเกรชันจะจัดการข้อมูลเก่าหรือแก้ค่าเริ่มต้นได้ยาก
  • ตรรกะการกู้คืนซับซ้อนขึ้น และหากต้องเรียกระบบภายนอกก็อาจเกิดบั๊กได้
  • สรุปแล้ว วิธี archived_at แม้ดูเรียบง่าย แต่ มีต้นทุนการบำรุงรักษาระยะยาวสูง

การเก็บถาวรระดับแอปพลิเคชัน

  • เมื่อมีการลบ จะ เผยแพร่เหตุการณ์ แล้วส่งไปยัง SQS เพื่อให้บริการอื่นนำไปเก็บถาวรใน S3
  • ข้อดี
    • ทำให้ฐานข้อมูลหลักและโค้ดแอปพลิเคชันเรียบง่ายขึ้น
    • การล้างทรัพยากรภายนอกทำแบบ อะซิงโครนัส ช่วยเพิ่มประสิทธิภาพและความเสถียร
    • สามารถ serialize เป็น JSON เพื่อเก็บในโครงสร้างที่ เหมาะกับแอปพลิเคชัน
  • ข้อเสีย
    • บั๊กในโค้ดแอปพลิเคชันอาจทำให้ ข้อมูลที่เก็บถาวรสูญหาย
    • เพิ่ม ความซับซ้อนของโครงสร้างพื้นฐานในการปฏิบัติการ เช่น message queue
    • ข้อมูลที่เก็บถาวรใน S3 ต้องมี เครื่องมือสำหรับค้นหาและกู้คืน

การเก็บถาวรแบบอิงทริกเกอร์

  • ทริกเกอร์ก่อนลบจะคัดลอกแถวไปยัง ตาราง archive แยกต่างหาก ในรูปแบบ JSON
    • ตัวอย่างตาราง: archive(id, table_name, record_id, data, archived_at, caused_by_table, caused_by_id)
  • ในกรณีลบด้วย foreign key แบบ cascade จะใช้ตัวแปรเซสชัน (archive.cause_table, archive.cause_id) เพื่อ ติดตามสาเหตุของการลบ
    • สามารถตรวจสอบได้ว่าเรคคอร์ดระดับบนตัวใดเป็นผู้ลบข้อมูลระดับล่าง
  • ข้อดี
    • ตารางใช้งานจริงสะอาดอยู่เสมอ ไม่ต้องมีคอลัมน์ archived_at
    • การล้างตารางเก็บถาวร (WHERE archived_at < NOW() - INTERVAL '90 days') ทำได้ง่าย
    • รักษาประสิทธิภาพของคิวรีและดัชนี พร้อมทำให้มิเกรชันง่ายขึ้น
    • ลดขนาดแบ็กอัป
  • ตารางเก็บถาวรสามารถจัดการด้วย tablespace แยก หรือ time partitioning ได้

การเก็บถาวรแบบอิง WAL (Change Data Capture)

  • อ่าน ล็อก WAL ของ PostgreSQL เพื่อสตรีมเหตุการณ์การลบไปยังระบบภายนอก
    • เครื่องมือที่เป็นตัวแทน: Debezium (ทำงานร่วมกับ Kafka)
    • ตัวอย่างเส้นทาง: PostgreSQL → Debezium → Kafka → Consumer → Archive Storage
  • ทางเลือกที่มีน้ำหนักเบากว่า
    • pgstream: ส่ง WAL ไปยัง webhook หรือ message queue โดยตรง
    • wal2json: แปลง WAL ออกมาเป็น JSON
    • pg_recvlogical: เครื่องมือ logical replication ที่มีมาใน PostgreSQL
  • ความซับซ้อนในการปฏิบัติการ
    • แบบอิง Kafka ต้องมี การมอนิเตอร์, การรับมือเหตุขัดข้อง, และการปรับแต่ง
    • หาก consumer ล่าช้าอาจเกิด ไฟล์ WAL สะสม → เสี่ยงพื้นที่ดิสก์ไม่พอ
    • จำกัดได้ด้วยการตั้งค่า max_slot_wal_keep_size ของ PostgreSQL 13+
    • ต้องมีการมอนิเตอร์และแจ้งเตือนเรื่อง replication slot lag
  • ข้อดี
    • จับการเปลี่ยนแปลงทั้งหมดได้ โดยไม่ต้องแก้โค้ดแอปพลิเคชัน
    • สตรีมไปยัง ปลายทางที่หลากหลาย (S3, data warehouse, search index) ได้
    • ไม่เพิ่มภาระเพิ่มเติมให้ฐานข้อมูลหลัก
  • ข้อเสีย
    • ความซับซ้อนในการปฏิบัติการและต้นทุนโครงสร้างพื้นฐาน สูง
    • หาก consumer ล่าช้าอาจเกิด ข้อมูลสูญหายหรือต้องซิงก์ใหม่
    • เมื่อสคีมาเปลี่ยน ต้องมี การประสานงานระหว่างต้นทางกับ consumer

ไอเดียของรีพลิกาที่ไม่ประมวลผลการลบ

  • มีการเสนอแนวคิดให้คง รีพลิกา PostgreSQL ที่เพิกเฉยต่อคำสั่ง DELETE
    • สามารถสะสมเก็บข้อมูลทั้งหมดที่ไม่ถูกลบได้
    • คิวรีข้อมูลที่เก็บถาวรได้โดยตรง
  • ปัญหาที่อาจเกิดขึ้น
    • อาจแยกไม่ออกว่าข้อมูลใดถูกลบไปแล้ว
    • เสี่ยงเกิดความขัดแย้งเมื่อทำมิเกรชัน
    • ต้นทุนด้านพื้นที่จัดเก็บและการปฏิบัติการเพิ่มขึ้น

บทสรุป

  • สำหรับโปรเจ็กต์ใหม่ วิธีเก็บถาวรแบบอิงทริกเกอร์ เป็นตัวเลือกที่ใช้งานได้จริงที่สุด
    • ตั้งค่าง่าย และช่วยให้ตารางใช้งานจริงสะอาด
    • ค้นหาและจัดการข้อมูลที่เก็บถาวรได้ง่าย โดยไม่ต้องมีโครงสร้างพื้นฐานเพิ่มเติม
  • หากมีโครงสร้างพื้นฐานที่ซับซ้อนอยู่แล้ว หรือต้องการสตรีมไปหลายปลายทาง แนวทางแบบอิง WAL จะเหมาะกว่า

4 ความคิดเห็น

 
love7peace 2026-01-23

ถ้าเป็นแบบอิงทริกเกอร์ ผมเคยเรียนมาว่ามันจะเพิ่มภาระให้กับ DB นี่นา...? กลับมาแนะนำให้ใช้ทริกเกอร์ซะงั้น

 
nemorize 2026-01-24

ถ้าโอเวอร์เฮดที่เกิดจากทริกเกอร์ระดับนั้นกลายเป็นปัญหา ต่อให้ไม่มีทริกเกอร์ก็เป็นสถานการณ์ที่เต็มไปด้วยปัญหาอยู่ดี

 
cherrycoder 2026-01-23

สุดท้ายแล้ว กฎระเบียบก็คือต้นทุนอยู่ดีนั่นแหละ แต่ยังไงซะก็เป็นภาระที่ผู้บริโภคต้องจ่ายอยู่แล้ว

 
GN⁺ 2026-01-23
ความคิดเห็นจาก Hacker News
  • ใน โดเมนธนาคาร ที่ฉันทำงานอยู่ กลับรู้สึกว่า soft delete มีข้อได้เปรียบมากกว่า
    ถ้ามีคอลัมน์ deleted_at การเขียนคิวรีจะชัดเจน และทั้งคิวรีสำหรับวิเคราะห์กับหน้าแอดมินก็สามารถใช้ชุดข้อมูลเดียวกันได้
    การลบส่วนใหญ่เกิดขึ้นไม่บ่อย และแทบไม่เคยเห็นว่าแถวที่ถูก soft delete จะก่อให้เกิด ปัญหาด้านประสิทธิภาพ
    อีกทั้งความสัมพันธ์ยังคงอยู่เหมือนเดิม ทำให้ การกู้คืน (undo) ทำได้ง่าย
    ยิ่งไปกว่านั้น ฉันชอบแนวทางที่ทำให้แถว ไม่เปลี่ยนแปลง (immutable) อย่างสมบูรณ์ และเพิ่มแถวใหม่เมื่อมีการอัปเดต
    ถ้าต้องการเก็บล็อก ฉันคิดว่าแนวทางที่ดีคือใช้ DB trigger เพื่อบันทึกลงตารางสำเนาเมื่อเกิด INSERT/UPDATE/DELETE

    • ที่คุณพูดก็ถูก แต่ผมคิดว่าใช้ได้เฉพาะกรณีที่การลบเกิดไม่บ่อยเท่านั้น
      ในบางตารางที่ผมเคยเห็น มีข้อมูลถูก soft delete ถึง 50~70% และมีผลต่อประสิทธิภาพอย่างชัดเจน
      สุดท้าย soft delete ก็ขึ้นอยู่กับบริบท และต้องมี การวิเคราะห์ล่วงหน้า
    • ใน Postgres สามารถตั้งค่าให้ ทำดัชนี เฉพาะข้อมูลที่ยังไม่ถูก soft delete ได้
      ส่วนใหญ่ไม่จำเป็น แต่ช่วย ประหยัด RAM ได้
    • ในระบบธนาคาร soft delete เป็นแค่ทางแก้ชั่วคราวที่ใช้ปิดบังการขาด ความสามารถในการตรวจสอบย้อนหลัง (auditability)
      ทางออกที่แท้จริงคือ Event Sourcing ที่บันทึกทุกการเปลี่ยนแปลงเป็นอีเวนต์
      แม้ประสิทธิภาพจะลดลง แต่ชดเชยได้ด้วย snapshot และการซิงก์ (sync)
    • ถ้าจะเดินระบบ DB แบบ immutable ก็ควรพิจารณาระบบอย่าง Datomic
      เพราะมีความสามารถแบบ time travel ที่ทำให้ตรวจสอบสถานะในอดีตได้อย่างสมบูรณ์
    • ตอนทำงานที่บริษัทประกันแห่งหนึ่งในอดีต แต่ละตารางก็ถูกใช้งานเป็น append-only log
      สถานะล่าสุดอยู่ที่แถวซึ่งมี timestamp มากที่สุด และสถานะในอดีตก็เรียกดูได้ด้วยตัวกรอง
      วิธีนี้ทำให้มี การจัดการประวัติย้อนหลังที่แข็งแรง
  • กับดักใหญ่ที่สุดของ soft delete คือ ความซับซ้อนของคิวรี
    ตอนแรกเหมือนแค่เพิ่ม WHERE deleted_at IS NULL ก็พอ แต่พอผ่านไปไม่กี่เดือน ข้อมูลผี ก็ไปโผล่ในรายงานเพราะลืมใส่เงื่อนไขกรอง
    จะแก้ด้วย View ก็ได้ แต่สุดท้ายก็ยังต้องรักษา รูปแบบการเข้าถึงแบบขนาน ไว้ และเวลาต้องอ่านข้อมูลที่ถูกลบก็จำเป็นต้องข้าม abstraction นั้น
    Event sourcing สะอาดกว่าก็จริง แต่มี ภาระในการปฏิบัติการ สูง เลยทำให้ส่วนใหญ่มักเลือกแนวทางแบบไฮบริด

    • View เป็นเครื่องมือที่ทรงพลังพออยู่แล้ว
      ปัญหาคือ วิศวกร SWE และ BI จำนวนมากไม่คุ้นกับ SQL และการออกแบบสคีมา
      ปัญหาที่พบได้บ่อยกว่า soft delete คือการจัดการ Type 2 Slowly Changing Dimension
      คนส่วนใหญ่มักสร้าง audit table โดยไม่จำเป็น แล้วทำ UPDATE/INSERT ที่ไม่มีประสิทธิภาพซ้ำ ๆ
      จริง ๆ แล้ว DB เป็นระบบที่สวยงามมาก แต่ก็น่าเสียดายที่มัน ไม่ได้รับความเคารพเท่าที่ควร
  • ฉันคิดว่าคงดีถ้า soft delete ถูก提供เป็น ฟีเจอร์ในตัวของ DB
    ถ้าเปิดใช้ได้ในระดับตาราง และเลือกกลยุทธ์การจัดการการลบได้ ก็น่าจะเป็นอุดมคติ

    • ที่จริงระบบอย่าง Iceberg, Delta Lake, BigQuery ก็มีฟีเจอร์ time travel อยู่แล้ว
      แต่หลายทีมสุดท้ายก็ยังต้องทำเองด้วยแนวทาง SCD(Slowly Changing Dimension) เพราะมีความต้องการเฉพาะ
  • จากประสบการณ์ของฉัน แนวทางแบบ trigger-based เสถียรที่สุด
    ตาราง archive ควรรักษาให้เป็นแบบ append-only และการกู้คืนควรจัดการที่ application layer
    การอัปเดตให้ถือเป็น soft delete และให้ trigger จับสถานะก่อนหน้าไว้
    trigger ควรรันในช่วง BEFORE เท่านั้น และ logic ควรเรียบง่าย
    การแบ่งพาร์ทิชันมักใช้รายเดือนเป็นมาตรฐาน และถ้ามีภาระการเขียนสูงก็ควรแบ่งเป็นรายวัน

  • ฉันอยากให้ DB วิวัฒน์จาก stateful → stateless
    ฉันชอบโครงสร้างที่บันทึกทุกการเปลี่ยนแปลงเป็น append-only event และแสดงข้อมูลที่ต้องการผ่าน view
    จะยิ่งดีถ้า DB จัดการ materialized index ให้โดยอัตโนมัติ
    DB รุ่นใหม่บางตัวมีฟีเจอร์ลักษณะนี้แล้ว แต่ตอนนี้การพัฒนาในฝั่ง OLTP ยังไม่มากพอ

    • สุดท้ายแล้วนี่ก็คือแนวคิดของ Event Sourcing
      น่าอ่านคำอธิบายของ Martin Fowler
  • บริษัทเก่าที่ฉันเคยทำงานใช้ soft delete กับทุกระบบ
    ยังจำได้ว่าอาจารย์เคยพูดว่า “ในโลกธุรกิจ ข้อมูลไม่มีวันถูกลบจริง

    • การลบแบบถาวรคือการจำกัด ความสามารถในการวิเคราะห์ข้อมูล ของตัวเองในอนาคต
      พื้นที่เก็บข้อมูลก็ถูกอยู่แล้ว เพราะงั้นไม่ควรลบข้อมูลทิ้งเลย
    • แต่น่าสนใจที่อาจารย์ไม่ได้พูดอะไรเกี่ยวกับการแก้ไขข้อมูลเลย
  • ฐานข้อมูลคือที่เก็บ ข้อเท็จจริง (fact)
    การสร้างเรคอร์ดคือข้อเท็จจริงใหม่ การลบก็เป็นข้อเท็จจริงอีกแบบหนึ่ง
    แต่ถ้าลบแถวออกทางกายภาพ ข้อเท็จจริงนั้นก็หายไป
    โดยมากแล้วการลบแบบนี้ไม่ใช่สิ่งที่พึงประสงค์

    • แต่ถ้าข้อมูลเป็นทรัพย์สินที่มี ความเสี่ยงต่อการรั่วไหล ก็อาจจำเป็นต้องลบออกจำนวนมากแทน
      ต้องคำนึงถึงต้นทุนการเก็บรักษาและความเสี่ยงด้านความปลอดภัย
    • ถ้า DB ไม่ได้เป็น immutable การแก้ไขข้อมูลเองก็เท่ากับสร้าง ข้อเท็จจริงที่หายไป อยู่แล้ว
      การตัดสินใจเก็บข้อมูลไว้ถาวรจึงควรพิจารณาอย่างรอบคอบ
    • โดยส่วนตัวผมคิดว่าที่เก็บข้อมูลควรรองรับแค่สองปฏิบัติการคือ การอ่านและการแทรกข้อมูล
      เพื่อให้ทำแบบนั้นได้ การเข้าใจ วงจรชีวิตของข้อมูล เป็นเรื่องสำคัญ
  • ที่ Firezone ตอนแรกใช้ soft delete เพื่อ audit log แต่ภายหลังเลิกใช้เพราะปัญหาเรื่อง migration
    จากนั้นจึงเปลี่ยนไปใช้ Postgres CDC(Change Data Capture) เพื่อส่งอีเวนต์ออกไปยังตารางแยกที่ปรับให้เหมาะกับการเขียน
    soft delete มีประโยชน์กับ ฟีเจอร์กู้คืนให้ผู้ใช้ แต่ไม่เหมาะกับงานด้าน การตรวจสอบหรือการปฏิบัติตามข้อกำหนด

    • ในโปรเจกต์ที่ไม่ซับซ้อน การ audit ตัว API call เอง อาจมีประสิทธิภาพกว่าการติดตามการเปลี่ยนแปลงใน DB
  • การสร้าง View บนตารางที่มีฟิลด์ soft delete เพื่อซ่อนแถวที่ถูกลบ เป็นวิธีที่สะอาดดี
    แบบนี้แอปพลิเคชันก็ไม่ต้องสนใจสถานะการลบ

    • ถ้าใช้ RLS(Row Level Security) ของ Postgres ก็สามารถซ่อนแถวที่ถูก soft delete ได้อัตโนมัติ
      แอปพลิเคชันยังคงอ่าน/เขียน/ลบกับตารางเดิมได้ตามปกติ
  • มีคำถามว่าแล้วจะจัดการ schema drift อย่างไร
    ถ้าจะกู้คืนข้อมูลที่ serialize ไว้ตามสคีมาตอนถูกลบในภายหลัง การเปลี่ยนสคีมาก็จะกลายเป็นปัญหา

    • จากประสบการณ์ของผม อ็อบเจ็กต์ที่ถูก archive แทบไม่ค่อยถูกเรียกใช้
      ส่วนใหญ่ถ้าจะกู้คืนก็มักเกิดภายในไม่กี่วันหลังลบ เลยได้รับผลจากการเปลี่ยนสคีมาน้อย
      การ migrate archive เก่าให้เข้ากับโมเดลใหม่เป็นงานที่ ซับซ้อนและมีโอกาสผิดพลาดสูง
      ท้ายที่สุดแล้ว วิธีรับมือก็ขึ้นอยู่กับรูปแบบการใช้งานของระบบ