1 คะแนน โดย GN⁺ 2024-10-31 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • Fast Dedup ใน OpenZFS 2.3.0 ดีขึ้นมากเมื่อเทียบกับ dedup แบบเดิม แต่สำหรับผู้ใช้ทั่วไปยังคงเป็นฟีเจอร์ที่ต้องชั่งน้ำหนักต้นทุนและเงื่อนไขก่อนเปิดใช้ราวกับเป็นค่าเริ่มต้น
  • dedup ทำงานโดยไม่เขียนบล็อกที่เคยจัดเก็บไว้แล้วซ้ำ แต่เพิ่มเฉพาะการอ้างอิง ดังนั้นทุกเส้นทางการเขียนและการคืนพื้นที่จึงมีต้นทุนในการค้นหาและอัปเดต dedup table เพิ่มเข้ามา
  • วิธีเดิมมีภาระหน่วยความจำและ IO สูง เนื่องจาก read-modify-write amplification ของตารางที่ใช้ ZAP, live entry list ที่เรียกคืนไม่ได้ระหว่างทรานแซกชัน และ unique entry ที่ไม่ก่อให้เกิดประโยชน์
  • Fast Dedup ลด live entry จาก 424 ไบต์เหลือ 216 ไบต์ และเพิ่ม dedup log, การ flush แบบค่อยเป็นค่อยไป, zpool ddtprune, dedup_table_quota, DDT prefetch และ kstats เพื่อให้ผู้ดูแลควบคุมต้นทุนได้ดีขึ้น
  • ในเวิร์กโหลดทั่วไป บล็อกที่ซ้ำจริงอาจมีน้อย และ BRT/block cloning ของ OpenZFS 2.2 ก็ให้การประหยัดในลักษณะคล้ายกันด้วยต้นทุนต่ำกว่า ดังนั้น dedup จึงควรใช้อย่างระมัดระวังเฉพาะเมื่อมีข้อมูลซ้ำจำนวนมากและไม่มีทางเลือกแบบ zero-copy

การทำงานพื้นฐานของ OpenZFS dedup

  • การลบข้อมูลซ้ำ (dedup) คือฟีเจอร์ที่ OpenZFS ใช้ตรวจสอบก่อนเขียนข้อมูลลงดิสก์ว่า มีข้อมูลเดียวกันอยู่แล้วหรือไม่ หากมี ก็จะข้ามการเขียนใหม่และเพิ่มการอ้างอิงไปยังสำเนาเดิมแทน
  • ส่วนที่ยากคือ ต้องจัดเก็บและค้นหาข้อมูลอย่างต่อเนื่องเพื่อให้รู้ได้เร็วว่า “มีอยู่บนดิสก์แล้วหรือไม่” และ “อยู่ที่ไหน”
  • โครงสร้างที่เก็บข้อมูลนี้คือ dedup table
    • ในเชิงแนวคิดคือ hash table ที่ใช้ checksum ของข้อมูลเป็น key และใช้ตำแหน่งบนดิสก์กับจำนวนการอ้างอิง (refcount) เป็น value
    • ไม่ใช่ข้อมูลผู้ใช้ แต่เป็นข้อมูลเชิงโครงสร้างของ pool ที่จัดเก็บเป็นส่วนหนึ่งของ pool metadata

ต้นทุนที่เพิ่มในเส้นทางการเขียนและคืนพื้นที่

  • หากปิด dedup อยู่ OpenZFS จะขอจัดสรรพื้นที่จาก metaslab allocator แล้วนำ DVA ที่ได้รับกลับมาใส่ใน block pointer เพื่อเขียนข้อมูล
  • หากเปิด dedup อยู่ จะค้นหา checksum ใน dedup table ก่อน
    • หากไม่มีรายการ จะจัดสรรพื้นที่ใหม่ เขียนข้อมูล แล้วสร้าง dedup entry ใหม่ที่มี refcount เป็น 1
    • หากมีรายการ จะคัดลอก DVA เดิมไปยัง block pointer ถือว่า write IO เสร็จสิ้น แล้วเพิ่ม refcount
  • บล็อกที่จัดสรรด้วย dedup จะมีการตั้งแฟล็ก D ใน block pointer
    • เมื่อคืนพื้นที่ หากมีแฟล็ก D จะค้นหา dedup table อีกครั้งและลด refcount
    • เมื่อ refcount เป็น 0 จะลบ dedup entry และคืนพื้นที่จริง
  • เนื่องจากทุกการเขียนและการคืนพื้นที่ต้องผ่านการค้นหาและอัปเดต dedup table การใช้ dedup จะคุ้มค่าก็ต่อเมื่อการประหยัดพื้นที่และ IO จริงมากกว่าค่า overhead ในการจัดการตาราง

เหตุผลที่ dedup แบบเดิมไม่ดี

  • amplification ของ dedup table ที่ใช้ ZAP

    • dedup table แบบเดิมใช้ ZAP ซึ่งเป็นอ็อบเจ็กต์ hash table มาตรฐานบนดิสก์ของ OpenZFS
    • ZAP เป็นโครงสร้างอเนกประสงค์ที่ใช้กับไดเรกทอรี รายการแอตทริบิวต์ และการจัดการภายในด้วย แต่ไม่เหมาะนักสำหรับการเก็บ dedup entry
    • dedup entry ทั่วไปประกอบด้วย key ขนาด 40 ไบต์ และ value หลังบีบอัดประมาณ 64 ไบต์ โดย ZAP block ขนาด 32K หนึ่งบล็อกเก็บ entry ทั่วไปได้ประมาณ 188 รายการ
    • เนื่องจาก OpenZFS ไม่เขียนบล็อกบางส่วนและไม่เขียนทับที่เดิม แม้อัปเดต entry เพียงรายการเดียว ก็ต้องอ่าน ZAP block ทั้งบล็อก แก้ไข แล้วเขียนกลับเป็นบล็อกใหม่
    • key แบบ checksum ต้องมีคุณสมบัติต้านทานการชนกันสูง ดังนั้นโอกาสที่ entry สองรายการใด ๆ จะถูกวางใกล้กันใน ZAP block เดียวกันจึงต่ำ และโอกาสที่การอัปเดตหลายรายการในทรานแซกชันเดียวจะไปรวมอยู่ในบล็อกเดียวกันก็ต่ำเช่นกัน
    • หากมี RAM เพียงพอจน ARC เก็บ dedup table ไว้ได้ตลอด ต้นทุนการอ่านจะลดลง แต่สิ่งนี้ทำให้เกิดคำแนะนำเก่า ๆ ว่า dedup ต้องใช้หน่วยความจำมาก
    • vdev class แบบ dedup สามารถลดความต้องการหน่วยความจำลงได้เล็กน้อยด้วยการเพิ่ม dedicated dedup vdev ที่ใหญ่และเร็วพอ แต่ในระดับที่ dedup มีความหมายจริง ๆ ก็ต้องมีขนาดพอเก็บตารางทั้งหมดและมีความเร็วเพียงพอ
  • การใช้หน่วยความจำของ live entry list

    • OpenZFS เก็บ dedup entry ที่สร้างหรือแก้ไขระหว่างทรานแซกชันไว้ใน live entry list ในหน่วยความจำ
    • โครงสร้างนี้มีไว้เพื่อป้องกันสถานการณ์ที่เมื่อข้อมูลเดียวกันถูกเขียนหลายครั้งในเวลาเดียวกัน แต่ละเธรดเขียนตัดสินว่า dedup table ยังไม่มีข้อมูลนั้น แล้วต่างก็เขียนใหม่ทั้งหมด
    • การ lookup จะตรวจสอบ live entry list ก่อน
      • หากมี entry นั้น จะเพิ่ม refcount
      • หากไม่มี จะสร้าง live entry ในสถานะ “in progress” แล้วอ่าน entry จริงจาก ZAP จากนั้นเปลี่ยนเป็น “ready”
      • เธรดเขียนอื่นที่เข้าถึงพร้อมกันจะรอจนกว่าจะ ready
    • เมื่อทรานแซกชันสิ้นสุด จะไล่ผ่าน live entry list เพื่อสะท้อนข้อมูลที่เกี่ยวข้องกลับไปยัง dedup ZAP
    • live entry แบบเดิมมีขนาด 424 ไบต์ ต่อรายการ และหน่วยความจำนี้ไม่ใช่ ARC แต่เป็น kernel slab memory จึงไม่สามารถเรียกคืนได้เมื่อระบบมีแรงกดดันด้านหน่วยความจำ
    • live entry list จะถูกล้างในแต่ละทรานแซกชัน แต่หากทรานแซกชันหนึ่งเขียนข้อมูลที่ต่างกันจำนวนมาก ค่า peak ก็จะสูงขึ้น
  • unique entry ทำให้ตารางพองตัว

    • dedup ติดตามทุกบล็อกที่จัดเก็บบนดิสก์ แต่ประโยชน์จริงจะเกิดขึ้นเฉพาะเมื่อ refcount มากกว่า 1 เท่านั้น
    • unique entry ที่มี refcount เป็น 1 แทบจะเป็นต้นทุนของการรอว่าสักวันข้อมูลเดียวกันจะถูกเขียนซ้ำ
    • dedup ทำงานในระดับบล็อกหลังจากการเข้ารหัสและการบีบอัด
    • แม้ข้อมูลต้นฉบับจะเหมือนกัน ก็ต้องตรงกันทั้งวิธีบีบอัด คีย์เข้ารหัส และการจัดแนวภายในไฟล์ จึงจะถือว่าเป็นบล็อกเดียวกัน
    • สำหรับเวิร์กโหลดใช้งานทั่วไป บล็อกที่ “เหมือนกันจริง ๆ” มีน้อย จึงง่ายที่ต้นทุนของ dedup จะมากกว่าประโยชน์

สิ่งที่ Fast Dedup ปรับปรุง

  • ลดขนาด live entry

    • Fast Dedup เริ่มจากการลด memory footprint ของ live entry list
    • เปลี่ยนแฟล็กชนิดตัวเลขขนาดใหญ่ใน ddt_entry_t ให้เป็น bitfield และทำให้ฟิลด์ซิงโครไนซ์เรียบง่ายขึ้น
    • แยกสถานะขนาด 40 ไบต์ที่ใช้เฉพาะเมื่อเขียนบล็อกข้อมูลที่ถูก dedup เป็นครั้งแรกหรือเมื่อจำเป็นต้อง repair write ออกเป็นอ็อบเจ็กต์ IO state แยกต่างหาก
    • value ของ dedup entry แบบเดิมมีขนาด 256 ไบต์ โดยรวม physical entry สี่รายการ
    • physical entry แต่ละรายการมี DVA แบบ 128 บิตสามตัว, refcount และ birth transaction id
    • entry ตัวที่สี่เป็นร่องรอยของฟีเจอร์ dedupditto ในอดีต และ OpenZFS สมัยใหม่รองรับเฉพาะการอ่าน ไม่เขียนใหม่
    • Fast Dedup ไม่เก็บ variant เดิมเป็น entry แยกต่างหากเมื่อ copies= เปลี่ยนจนต้องใช้ DVA เพิ่ม แต่จะจัดสรรเฉพาะสำเนาเพิ่มเติมที่จำเป็นแล้วเพิ่มเข้าไปใน dedup entry เดิม
    • value ของ entry ใน Fast Dedup table ใหม่ลดจาก 256 ไบต์เดิมเหลือ 72 ไบต์
    • entry หนึ่งรายการใน live list ลดจาก 424 ไบต์เดิมเหลือ 216 ไบต์
  • เพิ่ม dedup log

    • วิธีเดิมจะสะท้อน live entry list กลับไปยัง dedup ZAP ทันทีเมื่อสิ้นสุดทรานแซกชัน และเกิดต้นทุนการอัปเดตระดับบล็อกแม้ว่า entry อีก 187 รายการรอบ ๆ จะไม่เกี่ยวข้องเป็นส่วนใหญ่
    • Fast Dedup เพิ่ม dedup log โดยอาศัยข้อสังเกตว่า บล็อกที่เพิ่งสร้างหรือเพิ่งพบว่าซ้ำ มีโอกาสสูงกว่าที่จะถูกพบว่าซ้ำอีกครั้งหรือถูกคืนพื้นที่
    • เมื่อจบทรานแซกชัน จะไม่เขียนการเปลี่ยนแปลงของ live entry ไปยัง ZAP ทันที แต่บันทึกลง log
      • on-disk log จำเป็นเพื่อความปลอดภัยเมื่อเกิด crash
      • in-memory log ถูกคงไว้เพื่อการ lookup ที่รวดเร็ว
    • ลำดับการ lookup จึงเป็น live entry list, in-memory log, dedup ZAP
    • on-disk log ใช้สำหรับกู้คืน in-memory log เมื่อ import pool
  • การ flush log แบบค่อยเป็นค่อยไป

    • หาก flush log ไปยัง ZAP ทีเดียวเมื่อ log ใหญ่เกินไปเหมือนวิธีเริ่มต้น แม้มี entry เพียงไม่กี่พันรายการก็อาจทำให้เกิด pause นานได้
    • Fast Dedup ใช้ incremental flushing ที่สะท้อน log บางส่วนไปยัง ZAP ในทุกทรานแซกชัน
    • ปริมาณการ flush จะปรับโดยเปรียบเทียบกับเวลาที่ใช้กับ IO จริง
      • ช่วงที่งานยุ่งจะเขียนน้อยลง และช่วงที่เงียบจะเขียนมากขึ้น
      • หาก in-memory log โตขึ้นจนสร้างแรงกดดันต่อหน่วยความจำ ก็สามารถเร่งการ flush ได้
    • เพื่อให้ on-disk log เป็นแบบ append-only ได้ต่อไป แต่ยังล้างได้โดยไม่หยุดทั้งระบบ จึงใช้ log สองชุด
      • ชุดหนึ่งเป็น active log สำหรับรับการเปลี่ยนแปลงใหม่
      • อีกชุดเป็น flushing log สำหรับสะท้อนกลับไปยัง ZAP
      • เมื่อ flushing log ว่าง จะ zero on-disk log แล้วสลับบทบาทของ log ทั้งสอง
    • ในการสแกน pool เช่น scrub หรือ resilver เนื่องจาก dedup log ไม่มีแนวคิดเรื่องตำแหน่งที่เสถียร เมื่อมีคำขอ scan จะเร่ง log flushing เพื่อสะท้อนทั้งหมดไปยัง dedup ZAP ก่อน แล้วจึง scan ด้วยวิธีเดิม

การจัดการ unique entry และฟีเจอร์สำหรับการปฏิบัติการ

  • zpool ddtprune ลบ unique entry บางส่วนออกจาก dedup table ของ pool
    • สามารถระบุได้ตามอายุหรือ percentage
    • เกณฑ์ตามอายุเหมาะเป็นพิเศษกับเวิร์กโหลดที่ข้อมูลที่เพิ่งใช้งานมีโอกาสถูกพบว่าซ้ำอีกครั้งสูง
  • หากบล็อกที่ dedup entry ถูกลบด้วย pruning ถูกคัดลอกในภายหลัง จะไม่ถูกลบข้อมูลซ้ำกับบล็อกเดิม แต่จะถูกจัดสรรเป็นบล็อกใหม่
    • อย่างไรก็ตาม หากบล็อก unique เก่าถูกคัดลอกหลายครั้งอย่างกะทันหัน ก็อาจเกิดการอ้างอิงหลายรายการไปยังบล็อกใหม่หนึ่งบล็อกได้
  • pool property dedup_table_quota จำกัดขนาดสูงสุดของ dedup table
    • หากการสร้าง entry ใหม่เกินขีดจำกัด จะไม่สร้าง entry และจะประมวลผลเป็นการเขียนแบบ non-dedup ปกติ
    • สามารถใช้ร่วมกันเพื่อป้องกันไม่ให้ล้นไปยัง main device เมื่อ dedicated dedup device เต็ม
  • zpool prefetch -t ddt โหลด dedup table เข้า ARC ล่วงหน้า
    • อาจช่วยเรื่องประสิทธิภาพทันทีหลัง import pool
    • ยังใช้ได้กับ Fast Dedup เพราะการค้นหา entry ที่ไม่ได้อยู่ใน log และการเข้าถึง ZAP ระหว่าง flush ยังจำเป็นอยู่
  • มีการเพิ่ม kstats และ tuneable ใหม่ด้วย
    • Linux: /proc/spl/kstat/zfs/<pool>/ddt_stats_<checksum>
    • FreeBSD: kstat.zfs.<pool>.misc.ddt_stats_<checksum>
    • Linux tuneable: /sys/modules/zfs/parameters/zfs_dedup_log_*
    • FreeBSD tuneable: vfs.zfs.dedup.log_*
  • เครื่องมือเดิมที่เข้าใจ dedup เช่น zpool status -D, zdb -D, zdb -S ก็ได้รับการอัปเดตให้เข้าใจโครงสร้างใหม่ด้วย

ความเข้ากันได้กับ dedup table เดิม

  • Fast Dedup ส่วนใหญ่ต้องมีการเปลี่ยนแปลง on-disk format จึงไม่สามารถนำไปใช้กับ dedup table เดิมได้โดยตรง
  • ใน table เดิม ฟีเจอร์ที่ไม่ต้องเปลี่ยนรูปแบบบนดิสก์ยังสามารถทำงานได้
    • dedup_table_quota
    • zpool prefetch -t ddt
    • lookup และ hit count ของ ddt_stats_*
    • ZAP shrink
  • การทำให้ dedup log ทำงานกับ table แบบดั้งเดิมยังเป็นงานที่ค่อนข้าง straightforward
    • อย่างไรก็ตาม จะไม่ได้ประโยชน์จาก live/log entry ที่เล็กลง
  • zpool ddtprune เพิ่มได้ง่ายเฉพาะโหมด “percentage of uniques” ใน table เดิม
    • โหมด age ต้องใช้ข้อมูลใน entry format ใหม่ จึงทำไม่ได้กับ format เดิม
  • ขณะนี้ยังไม่มีฟีเจอร์แปลง table เดิมเป็น format ใหม่
    • ในกรณีง่าย ๆ ที่ copies= ไม่เคยเปลี่ยน สามารถสร้าง ZAP ใหม่แล้วแปลงและคัดลอก entry เดิมได้
    • การแปลงแบบ online ซับซ้อนเพราะต้อง lookup หรือเขียน ZAP เก่า/ใหม่พร้อมกัน
    • การแปลงแบบ offline ง่ายกว่า แต่ต้องทำให้ pool offline
    • หากการเปลี่ยน copies= ทำให้มี variant หลายรายการที่มี refcount การแปลงแบบสมบูรณ์อาจเป็นไปไม่ได้
  • การส่ง deduplicated dataset ไปยัง pool อื่นที่รองรับ dedup แบบใหม่สามารถทำงานได้

“ดีขึ้นแล้ว แต่ทำไมยังไม่ควรเปิดใช้”

  • Fast Dedup มี overhead ลดลงเมื่อเทียบกับเดิม จึงอาจมีประโยชน์ในสถานการณ์ก้ำกึ่งมากขึ้น
  • แต่ dedup ยังคงเป็นปัญหาเรื่องสมดุลระหว่าง IO throughput, การใช้หน่วยความจำ และขนาด dedup table
  • ในเวิร์กโหลดใช้งานทั่วไป บล็อกที่สามารถ dedup ได้อาจมีน้อยมาก
  • ผล DDT จำลองจาก zdb -S ของ pool บนแล็ปท็อปตัวอย่างแทบไม่เห็นประโยชน์จาก dedup
    • entry 11.7M ส่วนใหญ่เป็น unique entry ที่มี refcount เป็น 1
    • จำนวน entry ที่ dedup ได้จริงอยู่ในระดับความคลาดเคลื่อนจากการปัดเศษเมื่อเทียบกับทั้งหมด
    • แสดงผลเป็น dedup = 1.00
  • ในกรณีเช่นนี้ การเปิด dedup จะเพิ่มเฉพาะแรงกดดันต่อ IO และหน่วยความจำโดยแทบไม่ได้อะไรกลับมา

กรณีที่ BRT/block cloning ดีกว่า

  • ตั้งแต่ OpenZFS 2.2 เป็นต้นมา มี BRT หรือ block cloning/reflinks
  • dedup table เป็นโครงสร้างสำหรับตรวจสอบโดยไม่มีบริบทว่า “ข้อมูลนี้มีอยู่บนดิสก์แล้วหรือไม่”
  • ในระบบสมัยใหม่ บางกรณีสามารถส่งต่อให้ storage stack รู้ได้ว่างานคัดลอกคืองานคัดลอก
    • copy_file_range() ของไฟล์ซิสเต็มบน Linux และ FreeBSD
    • copyfile() ของ macOS
    • FSCTL_SRV_COPYCHUNK ของ Windows
    • NFS, CIFS, OS block device driver, SCSI EXTENDED COPY, NVMe Copy ฯลฯ ก็มีฟังก์ชันคล้ายกัน
  • หากโปรแกรมไคลเอนต์และเลเยอร์กลางส่งสัญญาณ copy offload ต่อไป OpenZFS ก็สามารถเพิ่มเฉพาะ refcount ใน BRT ได้
  • BRT ไม่มีต้นทุนหากบล็อกไม่ได้ถูก clone และหากถูก clone แล้ว entry มีขนาด 16 ไบต์
  • ใน pool ตัวอย่าง BRT แสดงผล used 292M; saved 309M; ratio 2.05x
  • raw saving น้อยกว่าและอยู่ในระดับใกล้เคียงกับ dedup simulation เล็กน้อย แต่ไม่มีต้นทุนก้อนใหญ่ในการติดตามบล็อกทั้งหมดที่ไม่ได้ถูก clone

เกณฑ์ตัดสินในทางปฏิบัติ

  • Fast Dedup ปรับปรุงทั้งสามแกนของ dedup แบบดั้งเดิม ได้แก่ IO throughput, memory usage และ dedup table size
  • ต้นทุนแบบร้ายแรงเมื่อเกิดความล้มเหลวก็ลดลง และผู้ดูแลมีเครื่องมือสำหรับจำกัดและทำความสะอาด table แล้ว
  • ถึงอย่างนั้น เงื่อนไขที่จะได้ประโยชน์ยังชัดเจน
    • ขนาดข้อมูลต้องใหญ่มาก
    • ต้องมีการคัดลอกข้อมูลเดียวกันจำนวนมาก
    • ต้องไม่สามารถใช้ทางเลือก zero-copy อื่นของ OpenZFS เช่น block cloning หรือ snapshot clone ได้
  • หากเป็นเวิร์กโหลดที่ไคลเอนต์สามารถให้สัญญาณชัดเจนว่า “ช่วยคัดลอกให้” ได้ block cloning อาจให้ประโยชน์มากด้วยต้นทุนต่ำกว่า

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

 
GN⁺ 2024-10-31
ความเห็นจาก Hacker News
  • อยากให้มี การลบข้อมูลซ้ำแบบออฟไลน์ หรือ การลบข้อมูลซ้ำแบบหน่วงเวลา ที่ไม่จำเป็นต้องถอดพูลลงทั้งหมด แต่ก็ไม่ได้ทำงานทันที
    เมื่อเปิดการลบข้อมูลซ้ำ การเขียนและการปล่อยพื้นที่ทุกครั้งต้องมีการค้นหาและเขียนตารางลบข้อมูลซ้ำ ซึ่งในกรณีส่วนใหญ่ดูเหมือนเป็นแนวทางที่ผิด เวลาเขียนข้อมูล เราต้องการให้เสร็จเร็วที่สุด แม้จะใช้พื้นที่ดิสก์มากขึ้นก็ตาม และนั่นคือเหตุผลที่เราไม่เก็บไฟล์ที่กำลังทำงานไว้ในไฟล์เก็บถาวร 7zip อยากให้ภายหลังตอนระบบว่าง ZFS ค้นหาข้อมูลซ้ำและเรียกคืนพื้นที่ด้วยอะไรอย่าง BRT และดูเหมือนจะทำเป็นส่วนหนึ่งของงาน scrub ปกติได้ด้วย

    • การลบข้อมูลซ้ำแบบหน่วงเวลา/ออฟไลน์จำเป็นต้องมี การเขียน Block Pointer ใหม่ แต่ ZFS ไม่ใช่ระบบ CAS จริง ๆ ดังนั้นต่อไปก็คงยากที่จะได้การเขียน BP ใหม่ที่เหมาะสม
      ตำแหน่งทางกายภาพถูกแฮชเข้าไปใน Merkle hash tree ดังนั้นถ้าจะย้ายตำแหน่งทางกายภาพ ก็ต้องเขียนโหนดภายในทั้งหมดบนเส้นทางไปยังโหนดที่จะเปลี่ยนใหม่ ซึ่งมีต้นทุนสูงเกินไป การออกแบบที่ดีกว่าน่าจะเป็นการแบ่งโหนดทั้งหมดที่มี block pointer ออกเป็นส่วนที่มีเพียง logical block pointer และถูกแฮชเข้าไปใน tree กับอีกส่วนที่เก็บเฉพาะตำแหน่งทางกายภาพที่สอดคล้องกับ logical pointer นั้นในลักษณะเหมือนแคช แต่ไม่ถูกแฮชเข้าไปใน Merkle tree แบบนั้นการเขียน BP ใหม่ก็ต้องเขียนใหม่เฉพาะบล็อกที่ไม่อยู่ใน Merkle tree ด้วยโครงสร้างปัจจุบันจึงยากที่จะได้ฟีเจอร์ที่ต้องการจาก ZFS แต่อาจมีทางเลี่ยงได้ เช่น เมื่ออ่านแล้วแฮชไม่ตรงกัน ก็ใช้แฮชของ pointer ไปหาบล็อกในตารางลบข้อมูลซ้ำ แล้วจัดสรรบล็อกที่ถูกลบข้อมูลซ้ำใหม่ ราคาที่ต้องจ่ายก็แค่การอ่านที่เปล่าประโยชน์หนึ่งครั้ง ซึ่งก็ไม่ได้แย่มาก แต่ถ้าเขียน BP ใหม่ไม่ได้ ก็มักจะเกิดการปะผุแบบนี้
    • วิธีนี้เหมือนกับแนวทางของ Windows Deduplication เคยใช้มาค่อนข้างเยอะ และถ้าฮาร์ดแวร์เพียงพอก็โดยรวมพอใจ
      มันกิน RAM และ I/O มาก แต่สามารถตั้งเวลาและจำกัด “groveler” ได้ อย่างไรก็ตามเคยเจอความเสียหายแบบกินข้อมูลเพราะบั๊กสมัย Windows 2012 R2
    • เรื่องนี้ทำได้ด้วย ตัวตรวจหาไฟล์ซ้ำแบบออฟไลน์ เช่นกัน
      ตัวอย่างเช่น jdupes หรือ duperemove ผมเคยส่ง PR ไปทั้งฝั่ง ZFS และ duperemove เพื่อรองรับ system call ที่จำเป็น ฝั่ง ZFS ใช้เวลาตรวจรีวิวนาน และเพิ่งนึกได้ว่าลืมปิดงานให้เสร็จจริง ๆ เลยต้องกลับไปดูอีกครั้ง
    • ความสามารถในการเปลี่ยน snapshot เดิมใน ZFS นั้นมีข้อจำกัดอย่างยิ่ง แม้จะเป็นวิธีที่รักษาข้อมูลไว้ครบถ้วนก็ตาม ดังนั้นถ้ามีฟีเจอร์แบบนั้นก็คงดี แต่ถ้ารอ Block Pointer Rewrite อยู่ ก็คงตายไปนานแล้ว
    • ข้อดีของการลบข้อมูลซ้ำแบบ inline คือถ้ามีแฮชของบล็อกอยู่แล้ว ก็ไม่จำเป็นต้องเขียนบล็อกนั้นจริง ๆ
      ในหลายสถานการณ์ มันลด I/O การเขียน ได้มาก ในอาร์เรย์สตอเรจที่มีการลบข้อมูลซ้ำ ยังมีส่วนขยายที่เมื่อคัดลอกไฟล์ระหว่าง VM สองตัว จะไม่คัดลอกข้อมูลจริง แต่เพิ่ม reference counter ของบล็อกเดิมเท่านั้น ฝั่งระบบปฏิบัติการจะเห็นเหมือนมีความเร็วเขียนระดับ TB/s ที่เหลือเชื่อ ซึ่งก็ค่อนข้างเจ๋ง
  • ข้ออ้างที่ว่า “ปัญหาพื้นฐานของการลบข้อมูลซ้ำแบบดั้งเดิมคือ overhead แบบนี้ใหญ่เกินไป จึงยากจะคุ้มทุนเว้นแต่งานบางแบบที่พบไม่บ่อยและเฉพาะเจาะจง” ฟังดูค่อนข้างแปลก
    เคยดูแลอาร์เรย์ของ Pure และ Dell/EMC และกับ workload ของ VMWare โดยปกติได้ การประหยัดจาก deduplication/compression อย่างน้อย 3:1 วิธีเก็บสำเนา base VM image เพียงชุดเดียวทำงานได้ดีมาก แม้แต่บน syslog server ก็เคยเห็นการประหยัด 6:1 เพราะ deduplication/compression ได้ผลดี ประสิทธิผลของการลบข้อมูลซ้ำขึ้นอยู่กับขนาดบล็อกที่ถูกแฮชอย่างมาก ยิ่งเล็กยิ่งดี ยิ่งบล็อกเล็กลง โอกาสเกิดบล็อกที่ตรงกันก็เพิ่มขึ้นอย่างรวดเร็ว และจากประสบการณ์ ขนาดบล็อกที่ชอบคือ 4KB

    • VM image เป็น ข้อมูลที่มีความซ้ำซ้อนสูงมาก เช่นไดรฟ์ C ของอิมเมจ Windows Server ที่แทบเหมือนกัน ส่วนบทความต้นฉบับยกตัวอย่างเนื้อหาในโน้ตบุ๊กของตัวเอง
      อีกทั้งดูเหมือนจะเอาฟีเจอร์คนละอย่างอย่าง compression กับ deduplication มาปนกัน ใน ZFS สามารถเปิด compression ให้พูลได้และแทบจะคุ้มค่าเสมอ แต่ deduplication สามารถปิดไว้ได้
    • base VM image ถือเป็น workload ที่พบไม่บ่อยและเฉพาะเจาะจง และเป็นหนึ่งในไม่กี่กรณีที่ deduplication สมเหตุสมผล
      แต่ถ้าโฮสต์ VM บนไฟล์ซิสเต็ม ZFS ก็น่าจะใช้กลยุทธ์ที่ดีกว่า เช่น block clone หรือ filesystem clone อยู่แล้ว การไม่ทำแบบนั้นเท่ากับทิ้งจุดต่างสำคัญข้อหนึ่งของ ZFS ในสภาพแวดล้อมนั้น สำหรับ file server อเนกประสงค์หรือการใช้งานเดสก์ท็อป/โน้ตบุ๊กส่วนตัว โดยทั่วไปมีบล็อกซ้ำน้อยมากจน overhead ไม่คุ้ม ส่วน backup อาจได้หรือไม่ได้ ขึ้นกับวิธี implement และว่ามีการเข้ารหัสก่อนถึงชั้นไฟล์ซิสเต็มหรือไม่ Compression เป็นคนละเรื่องโดยสิ้นเชิง และ best practice ของ ZFS ตอนนี้คือเปิดเป็นค่าเริ่มต้นใน workload แทบทั้งหมด ทุกวันนี้ต้นทุน CPU น้อยจนแทบไม่ควรค่าแก่การพูดถึง และนอกจากประหยัดพื้นที่เก็บข้อมูลแล้ว ยังอาจลด I/O ได้มากด้วย สำหรับที่เก็บ log ทั่วไป จากประสบการณ์ยังประหยัดได้ดีกว่า 6:1 มาก
    • ไม่ได้ลองเองโดยตรง แต่ตัวเลขที่ถูกอ้างถึงอย่างกว้างขวางเกี่ยวกับ deduplication ของ ZFS แบบเก่าคือ ต้องใช้ RAM 5GB ต่อดิสก์ 1TB
      ถ้าคิดว่าปัจจุบันดิสก์ 1TB ราคาประมาณ 15 ดอลลาร์ และ RAM เซิร์ฟเวอร์ 5GB ประมาณ 25 ดอลลาร์ แค่ให้ถึงจุดคุ้มทุนก็ต้องมี อัตรา deduplication 3:1 แล้ว ถ้าข้อมูลเข้าทางมาก ๆ อาจพออยู่ได้ด้วย 1GB ต่อ TB แต่ถ้าโชคร้าย 5GB ก็อาจไม่พอ นั่นจึงเป็นเหตุผลที่บทความบอกว่า deduplication ของ ZFS มี sweet spot เล็ก ๆ ที่ข้อมูลต้องเหมาะพอดี และเป็นเหตุผลที่คนส่วนใหญ่ไม่สนใจ ไฟล์ซิสเต็มอื่น ๆ มักชอบ deduplication แบบออฟไลน์ที่มีความคุ้มค่าทางเศรษฐศาสตร์ดีกว่า
    • VM เป็นกรณีที่รู้กันว่ามีประโยชน์จาก deduplication ดังนั้นจึงเห็นผลได้ในส่วนนั้น แต่ ZFS ไม่ได้เป็นแค่ SAN สำหรับองค์กรเท่านั้น แต่เป็น ไฟล์ซิสเต็มอเนกประสงค์ จึงไม่ใช่ว่าผู้ใช้ ZFS จำนวนมากจะรัน VM
      คำพูดที่ว่า deduplication/compression ได้ผลดีกับ syslog ถ้าดูรายละเอียดแล้ว deduplication กับ compression ไม่ใช่สิ่งเดียวกัน ในโลกสตอเรจองค์กร ทั้งสองมักถูกจับคู่กัน แต่ log น่าจะได้ประโยชน์จาก compression ไม่ใช่ deduplication และ ZFS ก็มี compression มาแต่เดิมอยู่แล้ว
    • การไม่เก็บ base VM image หลายชุดด้วย deep copy นั้นแน่นอนว่าสมเหตุสมผล แต่ใน ZFS deduplication ไม่ใช่วิธีที่เหมาะสม
      ควร clone base image แทน ซึ่งก่อนมีการเปลี่ยนแปลงแทบไม่กินพื้นที่เลย นี่เป็นผลจากคุณสมบัติ copy-on-write ของ ZFS ส่วน deduplication ของ ZFS เป็นฟีเจอร์ที่พยายามค้นหาสำเนาเดิมของข้อมูลที่ถูกเขียนลงใน volume สำหรับบางการใช้งานอย่างที่เก็บ container image อาจสมเหตุสมผลอยู่มาก แต่ถ้ารู้อยู่แล้วตั้งแต่แรกว่า dataset หนึ่งเป็น clone ของอีกชุดหนึ่ง วิธีนี้ก็ไม่มีประสิทธิภาพอย่างมาก
  • เมื่อก่อนเคยใช้การลบข้อมูลซ้ำของ ZFS อย่างกว้างขวางและได้ประโยชน์มาก กรณีใช้งานเฉพาะคือ สตอเรจสำหรับคลัสเตอร์ VMWare และมี VM Linux กับ Windows หลายร้อยเครื่องที่เนื้อหาโดยรวมคล้ายกันมาก เป็นเรื่องในยุคก่อน Docker

    • เห็นหลายกรณีที่ใช้การลบข้อมูลซ้ำกับ VM ตรงนี้ แต่รู้สึกว่าถ้าไปทำที่ ไฮเปอร์ไวเซอร์ น่าจะมีประสิทธิภาพกว่าการทำในไฟล์ซิสเต็มมากไม่ใช่หรือ
    • เห็นด้วย เพิ่งได้แล็ปท็อปทำงานเครื่องใหม่ที่มี zfs แบบ “experimental” ของ Ubuntu ช่วงหลัง ๆ นี้ และการใช้ การลบข้อมูลซ้ำกับ nix store ช่วยได้มากจริง ๆ
  • ตั้งตารอ การลบข้อมูลซ้ำแบบเร็ว มาก หลายปีมานี้อยากใช้การลบข้อมูลซ้ำของ ZFS กับข้อมูล ArchiveBox และด้วยการลบข้อมูลซ้ำแบบเร็ว ดูเหมือนว่าในที่สุดจะเป็นไปได้ที่จะเก็บถาวร URL หลายล้านรายการไว้ในคอลเลกชันเดียว แล้วให้ไฟล์ซิสเต็มจัดการการบีบอัดทั้งหมด
    ในข้อมูลเก็บถาวรจะมีสิ่งอย่าง jquery.min.js, bootstrap.min.css, รูปโลโก้ ฯลฯ ซ้ำอยู่ในสแนปช็อตหลายพันชุด เครื่องมืออื่น ๆ จะบีบอัดภายใน crawl ครั้งเดียวแล้วสร้างไฟล์ wacz หรือ warc.gz แต่ดูเหมือนยังไม่มีเครื่องมือใดพยายามบีบอัดข้ามฐานข้อมูลทั้งหมดของสแนปช็อตทุกชุดที่เคยเก็บมา ยังสงสัยด้วยว่ามีใครเคยลองแนวทาง การลบข้อมูลซ้ำเชิงความน่าจะเป็น เช่นใช้ Bloom filter เพื่อไม่ต้องเก็บตารางแฮชสำหรับการลบข้อมูลซ้ำทั้งหมดไว้ตรง ๆ หรือไม่ เช่นจัดกลุ่มแฮชของบล็อกประมาณ 100 รายการเป็นบัคเก็ต แล้วเก็บตัวแทนที่บีบอัดอย่างมากไว้ใน Bloom filter ตอนเขียนก็ตรวจแฮชของบล็อกที่จะเขียนใน Bloom filter และถ้าพบความเป็นไปได้ว่าจะเจอข้อมูลซ้ำ ก็ไปไล่ดูบล็อก 100 รายการในบัคเก็ตนั้นโดยตรงเพื่อหาแฮชที่ตรงกัน ในทางทฤษฎีอาจมีลำดับชั้นของ Bloom filter ที่ความละเอียดต่างกัน และเมื่อมีแรงกดดันด้านหน่วยความจำสูง ก็ย้ายฟิลเตอร์ความละเอียดสูงลงดิสก์แบบไดนามิกได้ ถ้าทำให้ความแม่นยำของ Bloom filter เป็นพารามิเตอร์ที่ปรับได้ ก็จะเลือกความต้องการระหว่างเวลา CPU/โอเวอร์เฮดกับสัดส่วนไบต์ที่ประหยัดได้

    • แม้มีการเปลี่ยนแปลงนี้ การลบข้อมูลซ้ำของ ZFS ก็ยังเป็นแบบ อิงการจัดแนวบล็อก ดังนั้นถ้าเว็บแอสเซ็ตที่ซ้ำกันไม่ได้อยู่ที่ออฟเซ็ตเดียวกันเสมอในไฟล์เก็บถาวร WARC ก็น่าจะจับคู่ได้ไม่ดี
      dm-vdo ก็ทำงานแบบเดียวกัน ทางเลือกที่อาจดีกว่าคือใช้ solid compression ที่มองช่วงข้อมูลยาว ๆ หรือคลี่ไฟล์ WARC ออกเป็นโครงสร้างใกล้เคียงไดเรกทอรี หรือถ้ามีระบบ FUSE ที่อิง content-defined chunking ก็อาจใช้แนวนั้น Seafile อาจเป็นแบบนั้นก็ได้
    • เข้าใจกรณีใช้งาน แต่ในกรณีส่วนใหญ่ โดยเฉพาะกรณีนี้ คิดว่าทำ ฝั่งไคลเอนต์ จะดีกว่ามาก
      ถ้าดูมาตรฐาน WARC จะเห็นว่ามีวิธีลบข้อมูลซ้ำด้วยแฮชอยู่แล้ว และหลังจากบันทึกครั้งแรกก็ใช้พอยน์เตอร์แทน ดังนั้นนี่เป็นตัวอย่างชัดเจนที่การลบข้อมูลซ้ำในชั้นไฟล์ซิสเต็มไม่ค่อยเหมาะ
    • กรณีใช้งานต่างกันนิดหน่อย แต่ถ้ายังไม่รู้จัก zbackup น่าจะชอบ
  • สงสัยว่าทำไมถึงลำบากกันนักในการทำให้สิ่งนี้ทำงานได้ดีโดยลดการใช้ RAM ลง อุปกรณ์สตอเรจเชิงพาณิชย์ทำเรื่องนี้ได้มาอย่างน้อยราว 10 ปีแล้ว แม้ในระบบที่มี RAM “น้อย” เมื่อเทียบกับความจุดิสก์ที่ต่ออยู่
    แค่เก็บ ฟิงเกอร์พรินต์ไว้ในฐานข้อมูล แล้วไล่ตรวจตอนกลางคืนเพื่อแก้บล็อกพอยน์เตอร์ก็พอไม่ใช่หรือ

    • “แก้บล็อกพอยน์เตอร์” นั่นแหละคือเหตุผล ด้วยเหตุผลหลายอย่าง ZFS ไม่มีความสามารถในการ เขียนบล็อกพอยน์เตอร์ใหม่
      เป็นฟีเจอร์ที่ถูกขอมานาน และถ้าทำได้ก็จะทำ defragmentation ได้ด้วย เคยคิดว่าการใช้การอ้อมผ่านบล็อกพอยน์เตอร์แบบหน่วยความจำเสมือนอาจแก้ได้โดยแลกกับค่าใช้จ่ายด้านความเร็วเล็กน้อย แต่ผมไม่ใช่นักพัฒนา ZFS จึงคงมีอะไรที่พลาดไปแน่นอน http://eworldproblems.mbaynton.com/posts/2014/zfs-block-poin... / https://github.com/openzfs/zfs/issues/3582
    • การแก้บล็อกพอยน์เตอร์นี่เองคือสิ่งเดียวที่ ZFS พยายามไม่ทำมาโดยตลอด
    • อีกวิธีคือใช้ DragonFlyBSD กับ Hammer2 Hammer2 รองรับ การลบข้อมูลซ้ำทั้งแบบออนไลน์และออฟไลน์ และในหลายด้านก็คล้าย ZFS มาก
      ข้อเสียใหญ่คือไม่มีโปรโตคอลถ่ายโอนไฟล์ที่ใช้ RDMA ได้ยินว่ามี branch ทดลองที่ทำให้รัน Hammer2 บน FreeBSD ได้ด้วย แต่ FreeBSD ก็ไม่มีการรองรับ RDMA เช่นกัน ใน FreeBSD 15 นั้น Chelsio สนับสนุนการรองรับ NVMe-oF ทั้งฝั่ง target และ initiator แต่ดูเหมือนจะเป็นเฉพาะ TCP
  • ใช้ cp --reflink=auto ก็ได้
    จะได้ การลบข้อมูลซ้ำระดับไฟล์ คำสั่งนี้ทำการคัดลอกแบบเบา ๆ โดยบล็อกข้อมูลจะถูกคัดลอกเฉพาะเมื่อมีการแก้ไข คล้าย ZFS clone ระดับไฟล์ ไม่ใช่ hard link แต่เป็นสำเนา ไฟล์ซิสเต็มแบบ copy-on-write เชิงทรานแซกชันอื่น ๆ ที่รองรับ reflink ก็น่าจะทำงานแบบเดียวกัน

  • อยากใช้ ZFS จริง ๆ แต่ข้อมูลทั้งหมดควรต้อง เข้ารหัส เป็นเรื่องปกติอยู่แล้ว ทว่าการใช้งานกลับซับซ้อนกว่าที่คาดไว้มาก และพอเรื่องยุ่งยากก็แปลกใจที่หลายคนเลือกไม่เข้ารหัสข้อมูลเลย
    แม้แต่ Proxmox ที่มีคำว่า “Enterprise” บนเว็บไซต์ ผมก็นึกว่าจะรองรับการเข้ารหัสในการติดตั้งพื้นฐาน แต่พอพยายามใช้ร่วมกับการเข้ารหัสกลับเสียฟีเจอร์สำคัญไป ต้องดู issue tracker ให้ดีด้วย มีเรื่องน่าประหลาดใจบางอย่างที่ไม่คิดว่าจะอยู่ในไฟล์ซิสเต็มสำหรับใช้งานจริง

    • วิธีที่ดีที่สุดในการเข้ารหัส ZFS คือวาง ZFS ที่ไม่เข้ารหัสไว้บนวอลุ่มที่เข้ารหัสแล้ว เช่น วอลุ่ม LUKS การ “เข้ารหัส” ของ ZFS เหลือข้อมูลไว้เป็น plaintext มากเกินไปจนทำให้ไม่สบายใจ
  • อยากให้มี API ที่ต่างออกไปโดยสิ้นเชิงสำหรับไฟล์ซิสเต็ม พื้นผิว API ของไฟล์ซิสเต็ม ในทุกระบบปฏิบัติการเป็นความยุ่งเหยิงเต็มรูปแบบที่ถูกขังไว้เพราะต้องเข้ากันได้ย้อนหลัง

    • ภายในแล้ว ZFS โดยแก่นแท้คือ object store เคยมีงานที่พยายามเปิดเผยสิ่งนี้เป็น API แบบ object store แต่น่าเสียดายที่ดูเหมือนไม่ได้ไปต่อ
      พยายามหาสไลด์นำเสนอแล้วแต่ไม่สำเร็จ คิดว่าเคยเห็นที่ Developer Summit แต่คงไม่ใช่
    • มันยุ่งเหยิงอย่างไร และจะแทนที่ด้วยอะไรได้บ้าง API แบบ AWS S3 จะถือว่าเป็นการปรับปรุงไหม
  • ลืมการลบข้อมูลซ้ำไปเถอะ ใช้ การบีบอัดของ ZFS คุ้มกว่ามาก

    • ข้อยกเว้นคือถ้าชุดข้อมูลเป็นไฟล์มีเดียที่ถูกบีบอัดหนักอยู่แล้ว
      โดยทั่วไปแม้แต่ตอนทำงาน rsync ก็มักปิดการบีบอัดสำหรับไฟล์วิดีโอขนาดใหญ่ เพราะการบีบอัดแทบไม่ช่วยหรือไม่ช่วยเลยทั้งด้านพื้นที่จัดเก็บและการถ่ายโอน แต่ยังกิน RAM กับ CPU การลบข้อมูลซ้ำเหมาะกับอิมเมจ OS ของ Virtual Machine เพราะต้นทุนพื้นที่จัดเก็บส่วนใหญ่อยู่ที่อิมเมจฐานที่ซ้ำกัน
  • การกำจัดข้อมูลซ้ำแบบใช้งานทั่วไปในทางทฤษฎีดูดี แต่ในทางปฏิบัติมักไม่ค่อยเป็นไปตามนั้น IPFS ใช้ ชิ้นส่วนขนาดแปรผันและ rolling hash เพื่อกำจัดข้อมูลซ้ำเหมือน rsync แต่ในความเป็นจริงกลับไม่ได้สร้างความแตกต่าง และมีแต่เพิ่มความซับซ้อนโดยไม่จำเป็น