3 คะแนน โดย GN⁺ 2023-09-01 | 2 ความคิดเห็น | แชร์ทาง WhatsApp
  • Marginalia Search เพิ่งปรับปรุงครั้งล่าสุดโดยลด ความต้องการ RAM ลงครึ่งหนึ่ง และลดภาระการปฏิบัติการที่ก่อนหน้านี้ต้องปิดเสิร์ชเอนจินระหว่างการอัปเกรดลงอย่างมาก
  • ปัญหาเริ่มปรากฏหลังรีลีสล่าสุดในกระบวนการสลับดัชนี และโครงสร้างที่ต้องล้างฐานข้อมูล URL ระหว่างอัปเกรดทำให้เกิดการ ออฟไลน์เป็นเวลานาน
  • ที่เก็บ URL เปลี่ยนจากการพึ่งพาตารางและดัชนีขนาดมหึมาใน MariaDB ไปเป็น ฐานข้อมูล SQLite เดียว พร้อมโครงสร้าง URL ID แบบ 64 บิตที่สร้างโดยโปรเซส
  • การสร้าง inverted index เปลี่ยนจากการใช้ lexicon สำหรับ term ID แบบหนาแน่นและการเขียนแบบสุ่มปริมาณมหาศาล ไปเป็นการสร้าง preindex ขนาดเล็กแล้วค่อยรวมเข้าด้วยกัน
  • โครงสร้างใหม่ทำให้สามารถสำรองไฟล์เพื่อกู้คืนได้ภายในไม่กี่ชั่วโมงแม้เกิดการดีพลอยที่ผิดพลาด และทำให้ผลการประมวลผลข้อมูลขนาดใหญ่ที่เปลี่ยนไม่บ่อยอย่าง Wikipedia นำกลับมาใช้ซ้ำได้ง่ายขึ้น

เงื่อนไขการปฏิบัติการของเสิร์ชเอนจินที่เปลี่ยนไปภายในหนึ่งสัปดาห์

  • Marginalia Search ลดปัญหาระยะยาวหลายอย่างพร้อมกันได้จากการเขียนโค้ดเพียงหนึ่งสัปดาห์
    • ความต้องการ RAM ของเสิร์ชเอนจินลดลงครึ่งหนึ่ง
    • ไม่จำเป็นต้องปิดระบบให้ออฟไลน์ระหว่างการอัปเกรดอีกต่อไป
    • ขีดจำกัดตายตัวของจำนวนเอกสารที่สามารถทำดัชนีได้ถูกยกเลิก
    • ขีดจำกัดแบบซอฟต์ของจำนวนคีย์เวิร์ดที่รวมในคอร์ปัสได้เพิ่มขึ้น 4 เท่า
  • เป้าหมายระยะยาวคือทำให้ระบบยังทำงานได้บนฮาร์ดแวร์ประสิทธิภาพต่ำ และการเปลี่ยนแปลงครั้งนี้ถือเป็นการปรับปรุงครั้งใหญ่กว่าครั้งก่อนที่ทำให้เครื่องพัฒนาซึ่งมี RAM 32GB รู้สึกเหลือเฟือ

โครงสร้างการอัปเกรดเดิมที่ทำให้ปัญหารุนแรงขึ้น

  • Marginalia Search ออฟไลน์เกือบหนึ่งสัปดาห์เพราะปัญหาในรีลีสล่าสุด
  • สาเหตุโดยตรงเป็นเพียงปัญหาด้านสเกลที่ค่อนข้างเล็กหลายอย่างต่อเนื่องกัน แต่ต้องรีสตาร์ตกระบวนการที่ปกติใช้เวลา 1 วัน และถ้ามีปัญหาอาจใช้ 2~3 วัน อยู่หลายครั้ง
    • การรีสตาร์ตส่วนใหญ่เกิดจาก RAM ไม่พอ
  • เหตุที่ผู้ใช้รับรู้ปัญหาอย่างชัดเจน เพราะต้องปิดระบบให้ออฟไลน์ระหว่างการสลับดัชนี
  • ในโครงสร้างเดิม ฐานข้อมูล URL จะถูกล้างระหว่างการอัปเกรด และการแทรกหรืออัปเดตแถวอย่างรวดเร็วในตารางที่มีเกือบ 1 พันล้านแถวกลายเป็นคอขวด
    • โปรเซสที่โหลดข้อมูลใหม่ใช้ RAM จำนวนมาก
    • จึงยากที่จะรันพร้อมกับบริการสร้างดัชนีที่ใช้ RAM หนักในลักษณะเดียวกัน
    • คีย์หลักเป็นจำนวนเต็ม 32 บิต จึงมีข้อจำกัดที่ยากจะเกิน 2 พันล้าน

การออกแบบฐานข้อมูล URL ใหม่

  • ฐานข้อมูล URL เป็นองค์ประกอบเก่าแก่ของเสิร์ชเอนจิน และการตัดสินใจออกแบบตั้งแต่แรกเริ่มไม่สอดคล้องกับโครงสร้างระบบปัจจุบันอีกต่อไป
  • แกนหลักคือสองตารางที่ถูกเขียนเฉพาะตอนโหลดดัชนีใหม่ และถูกอ่านผ่านการค้นหาคีย์หลักเท่านั้น
    • ตาราง URL กำหนดหมายเลข ID เฉพาะให้แต่ละ URL
    • ตาราง PAGE_DATA เก็บข้อมูลอย่างชื่อและคำอธิบายของลิงก์ที่ถูกทำดัชนี
    • มีดัชนีเพิ่มเติมเพื่อคงความไม่ซ้ำของพาธภายในโดเมน แต่มีขนาดใหญ่มากเมื่อเทียบกับบทบาทของมัน
  • โครงสร้างใหม่รวมสองตารางนี้เป็น ฐานข้อมูล SQLite แบบตารางเดียว ให้โปรเซสตัวโหลดรับหน้าที่สร้าง ID เฉพาะ และแยกรายการ URL ที่ไม่ทำดัชนีไปเก็บในอีกตารางหนึ่ง
  • การใช้ MariaDB ร่วมกับ SQLite ไม่ใช่ตัวเลือกที่พบได้ทั่วไปนัก แต่ที่เก็บข้อมูลทั้งสองรองรับความต้องการคนละแบบ
    • ระบบต้องการมุมมองโลกถาวรที่มีขนาดค่อนข้างเล็ก
    • ขณะเดียวกันก็ต้องสามารถสลับสถานะระดับเทราไบต์ได้แบบ plug-and-play
  • ตาราง DOCUMENT และ DOMAIN มีความเกี่ยวข้องกันแบบหลวม ๆ ข้ามฐานข้อมูล แต่ไม่จำเป็นต้อง join จริง
    • ในสถานการณ์ฉุกเฉิน สามารถสร้างส่วนสำคัญของตาราง DOMAIN กลับจากตาราง DOCUMENT ได้

การสร้าง URL ID 64 บิตโดยไม่ใช้ดัชนี

  • ตาราง URL เดิมใช้ ID 32 บิต และปัญหาใกล้ integer overflow เป็นปัญหาด้านสเกลที่ทราบกันอยู่แล้ว
  • หนึ่งในเหตุผลที่ต้องล้างฐานข้อมูล URL ก็คือเพื่อป้องกันไม่ให้คอลัมน์ ID ล้น
  • การใช้ตัวนับอย่างเดียวไม่สามารถจัดการ URL ที่ซ้ำได้ และต้องหลีกเลี่ยงการเก็บคอลเลกชันของ URL ทั้งหมดที่เคยเห็นไว้ในหน่วยความจำ
  • วิธีใหม่ไม่ได้ให้ฐานข้อมูลเป็นผู้กำหนด ID แต่ประกอบ ID 64 บิต ขึ้นมาเองโดยตรง
    • 26 บิตล่างใช้เป็นลำดับรันนิ่ง
    • บิต 26~56 ใช้เป็น document ID
    • บิตบนสุดถูกสงวนไว้สำหรับเทคนิคการจัดเรียงดัชนี
  • การจัดวางนี้รองรับเอกสารได้ราว 67 ล้านรายการต่อโดเมน
    • ประมาณ 10 เท่าของ English Wikipedia
    • รองรับโดเมนได้สูงสุด 2 พันล้านโดเมน
    • เกือบ 100 เท่าของจำนวนโดเมนที่ Marginalia เคยพบการอ้างอิงจากที่ใดก็ได้บนเว็บ
  • ความรับผิดชอบด้านความสอดคล้องของข้อมูลถูกย้ายจากฐานข้อมูลไปยังโปรเซสที่สร้างข้อมูล แต่ต้นทุนในการบังคับใช้ฝั่งนั้นต่ำกว่ามาก

ภาระหน่วยความจำที่ลดลงจากการออกแบบ URL ใหม่

  • เมื่อเอาดัชนีขนาดมหึมาและตารางใหญ่ที่ปะปนข้อมูล URL แบบ hot/cold ออกไป เซิร์ฟเวอร์ MariaDB ก็ไม่ต้องการ RAM 36GB อีกต่อไป
  • ข้อมูล hot ที่เหลือใน MariaDB มีขนาดเพียงไม่กี่ร้อย MB และ RAM ที่จัดสรรให้เซิร์ฟเวอร์อาจไม่จำเป็นต้องเกิน 2GB
  • ข้อมูลใน SQLite ยังต้องมีดัชนีสำหรับคีย์หลัก แต่คาดว่าส่วนของดัชนีที่เป็น hot จริงจะต่ำกว่า 1GB
    • ปริมาณข้อมูลที่ต้องทำดัชนีลดลงระดับเลขหลักเดียว
    • เป้าหมายของดัชนีคือ long 64 บิตเพียงค่าเดียว
    • ไม่มี unique constraint เพิ่มเติม
  • เมื่อผลลัพธ์จากตัวโหลดกลายเป็นชุดไฟล์ จึงสร้างระบบสำรองอัตโนมัติได้ง่ายขึ้น
  • แม้จะเกิดการดีพลอยที่ผิดพลาด ก็สามารถ กู้คืนได้ภายในไม่กี่ชั่วโมง แทนที่จะเกือบหนึ่งสัปดาห์เหมือนเดิม

คอขวดในการสร้าง inverted index

  • ตัวโหลดจะส่งออกฐานข้อมูล URL และ journal แบบยึดเอกสารเป็นศูนย์กลางในรูป (document, words[]) แต่การค้นหาต้องใช้ดัชนีที่แปลงเป็นรูป (word, documents[])
  • วิธีเดิมใช้ lexicon เพื่อแมปสตริงคีย์เวิร์ดเป็น term ID แบบหนาแน่น
    • คำแรกได้ ID 0 คำถัดไปได้ ID 1
    • สตริงถูกแมปเป็น long ด้วยแฮช 64 บิต
    • โครงสร้างโดยพื้นฐานคือ open hashmap แบบ long-to-int
  • โครงสร้างนี้เต็มได้ง่ายเมื่อขนาดใหญ่ขึ้น
    • Java ไม่อนุญาตให้อาร์เรย์เกิน 2 พันล้านรายการ
    • implementation ที่ใช้อยู่ติดเพดานตั้งแต่ระดับ 1 พันล้านแล้ว
    • ที่ 1 พันล้าน entry ใช้ RAM 12GB
  • บริการ index service ที่ใช้งานจริงกิน RAM 60GB และส่วนใหญ่เป็นหน่วยความจำนอกฮีป
  • อีกปัญหาคือขนาด on-heap ที่เกิน 32GB ทำให้ใช้ CompressedOOPs ได้ยาก

write amplification ของอัลกอริทึม inverted index แบบเดิม

  • อัลกอริทึมเดิมจะหาค่า word ID ที่ใหญ่ที่สุด สร้างอาร์เรย์ counts ตามขนาดนั้น จากนั้นคำนวณตำแหน่งเอกสารรายคำและเขียนข้อมูลเอกสารลงไฟล์ memory-mapped
  • วิธีนี้ต้องอาศัย lexicon เพราะตั้งอยู่บนสมมติฐานว่าโดเมนของ word ID ถูกเติมอย่างหนาแน่น
  • คอขวดที่ใหญ่กว่าคือการเขียนข้อมูลราว 1TB ลงไฟล์ memory-mapped ในลำดับที่แทบจะสุ่มทั้งหมด
  • SSD จัดการการเข้าถึงแบบสุ่มในการอ่านได้ดี แต่เมื่อเป็นการเขียนขนาดเล็กจะเกิดปัญหา write amplification
    • แม้อัปเดตเพียง 1 ไบต์บนดิสก์ SSD ก็ต้องลบและเขียนทั้งเพจใหม่
    • แม้จะมีแคชที่รวมการเขียนใกล้เคียงกัน แต่ก็แทบไม่ช่วยกับการเขียนแบบสุ่มระดับเทราไบต์
    • ระหว่างที่เขียน 1TB ไดรฟ์จริงกลับต้องเขียนราว 0.5PB
  • มีมาตรการบรรเทาชั่วคราวชื่อ RandomWriteFunnel
    • แบ่งการเขียนออกเป็นบักเก็ตไฟล์เล็ก ๆ ก่อน
    • จากนั้นจึงเขียนไฟล์ใหญ่ตามลำดับ
    • ดีกว่าวิธีเดิม แต่ก็ยังช้ามาก

สร้าง preindex ขนาดเล็กแล้วค่อยรวม

  • การสร้าง inverted index แบบใหม่ใช้วิธีสร้างดัชนีขนาดเล็กหลายชุดที่ข้อมูลทั้งหมดอยู่ในหน่วยความจำได้ แล้วค่อยรวมเข้าด้วยกัน
  • การ merge รายการที่เรียงลำดับแล้วทำได้เร็ว และเหมาะแม้กับฮาร์ดดิสก์แบบจานหมุน
  • วิธีนี้ทำให้ ไม่ต้องใช้ lexicon อีกต่อไป
    • ใช้แฮชสตริง 64 บิตโดยตรงเป็น term ID ได้
  • ปัญหาที่เจอในช่วงแรกคืออินพุตถูกบีบอัดไว้ และวิธีการรวมดัชนี
    • แทนที่จะอ่านบางส่วนของอินพุตที่บีบอัดอย่างรวดเร็ว จึงสร้างไฟล์เล็กหลายไฟล์แล้วอ่านทีละไฟล์
    • และแทนที่จะรวมดัชนีหลังสร้างเสร็จ ก็รวมข้อมูลก่อนสร้างดัชนี
  • abstraction ใหม่ชื่อ preindex ประกอบด้วยอาร์เรย์ของ word ID, อาร์เรย์ counts และอาร์เรย์ข้อมูลเอกสาร
  • เพราะอินพุตมีขนาดเล็ก preindex ส่วนใหญ่จึงสร้างใน RAM ได้ และสามารถ commit ลงดิสก์ระหว่างการสร้าง ก่อนจะนำไปสร้าง preindex สุดท้ายในขั้นตอน merge ถัดไป
  • preindex สุดท้ายจะถูกแปลงเป็น inverted index เหมือนเดิม โดยเพิ่มตาราง words และดัชนี btree แบบ static ให้แต่ละ document block ซึ่งขั้นตอนนี้ค่อนข้างเร็ว

การเปลี่ยนแปลงต่อการปฏิบัติการและการใช้ข้อมูลซ้ำ

  • การตัด lexicon ออกช่วยลดความต้องการ RAM และการสร้าง inverted index แบบใหม่ก็เร็วกว่าอัลกอริทึมเดิม
  • lexicon แบบเดิมสร้างภาษาถิ่นแยกกันสำหรับข้อมูลแต่ละชุดที่ประมวลผล
    • คำเดียวกันอาจได้ ID หนึ่งในการรันครั้งหนึ่ง และอีก ID หนึ่งในการรันอีกครั้ง
    • ด้วยเหตุนี้จึงต้องประมวลผลข้อมูลทั้งหมดพร้อมกัน
  • เมื่อเลิกใช้ lexicon ก็สามารถรวมข้อมูลจากคนละชุดเข้าด้วยกันได้
  • ทำให้สามารถนำผลการประมวลผลของข้อมูลขนาดใหญ่ที่เปลี่ยนไม่บ่อยอย่าง Wikipedia กลับมาใช้ซ้ำ และรวมเข้ากับข้อมูลดัชนีที่เปลี่ยนบ่อยกว่าได้
  • การเปลี่ยนแปลงครั้งนี้ลดทั้งปัญหาด้านสเกลที่รู้กันอยู่แทบทั้งหมดและความไม่สะดวกด้านปฏิบัติการ พร้อมทำให้สามารถพิจารณาผลข้างเคียงอื่น ๆ เพิ่มเติมได้

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

 
GN⁺ 2023-09-01
ความคิดเห็นจาก Hacker News
  • เห็นกรณีความสำเร็จแบบนี้ในโปรเจกต์ที่เดินไปในทิศทาง ตรงข้ามอย่างสิ้นเชิง กับโลกที่เหลือแล้วรู้สึกดีจริง ๆ
    อยากเรียกมันว่า Engildification และควรมีอะไรแบบนี้มากขึ้น
    ผมชอบบทความ Sleeping At Night ด้วย และเมื่อรวมกับบทความล่าสุด “Lie Still in Bed” ก็ดูเหมือนเป็นทางเลือกที่เรียบง่ายมากในการแก้ปัญหาการฝึกการนอน

    • ดูเหมือนเป็นฟังก์ชันของขนาด ยิ่งทีมหรือบริษัทที่อยู่เบื้องหลังผลิตภัณฑ์ใหญ่เท่าไร ค่าสัมประสิทธิ์และความเป็นไปได้ของ enshittification ก็ยิ่งสูงขึ้นเท่านั้น
      เสิร์ชเอนจิน Marginalia ผู้เขียนเพิ่งเปลี่ยนมาทำเต็มเวลา และเท่าที่รู้ทีมมีขนาด 1 คน จึงอยู่ไกลที่สุดจากความเสี่ยงเรื่อง enshittification กลับกัน อัญมณีมักถูกสร้างขึ้นในระดับขนาดแบบนี้ และความสร้างสรรค์ ความแปลกใหม่ และวิสัยทัศน์ก็เปล่งประกาย
      ความคิดเห็นนี้ได้รับการสนับสนุนโดยชมรม “ลาออกจากงานนั่งโต๊ะแล้วออกมาทำงานอิสระกันเถอะ”
    • ดูจากบทความ 3 ชิ้นล่าสุดแล้วสดใหม่จริง ๆ
      ลดการใช้ทรัพยากรลงครึ่งหนึ่ง ทำงานได้มีประสิทธิผลกว่าก่อนบนหน้าจอที่เล็กลง และตอนกลางคืนก็หลับลึกเหมือนท่อนไม้
    • ปีนี้กำลังทรมานกับปัญหาการนอน เลยค่อนข้างมีประโยชน์ที่ได้รู้ว่าวิธีไหนใช้ได้ผลกับคนอื่น
      ลิงก์สำหรับผู้สนใจ: https://www.marginalia.nu/log/86-sleep/
    • ชอบคำว่า engildification
    • กรณีแบบนั้นมีอยู่จริง เพียงแต่คุณจะไม่มีวันหาเจอด้วย Google เท่านั้นเอง
  • อ้างอิงเพิ่มเติม เสิร์ชเอนจินแบบเสียเงิน Kagi ซึ่งคุณน่าจะเคยเห็นใน Hacker News ก็ใช้ Marginalia เป็นหนึ่งในแหล่งข้อมูล
    https://help.kagi.com/kagi/search-details/search-sources.htm...
    ถ้าใช้เลนส์ “non-commercial” ผลลัพธ์แบบนี้จะถูกจัดลำดับความสำคัญร่วมกับผลลัพธ์จากดัชนีของ Kagi เองและแหล่งอิสระบางแห่ง

  • พออ่านบทความนี้แล้ว ก็เริ่มคิดว่ามนุษย์อาจเป็นสิ่งมีชีวิตที่ยากจะสร้างของที่ยอดเยี่ยมได้ หากไม่มี ข้อจำกัดที่ตั้งขึ้นโดยเจตนา
    เหตุผลที่ Marginalia มีประสิทธิภาพอย่างเหลือเชื่อ ก็เพราะ Victor จงใจจำกัดฮาร์ดแวร์ที่ใช้รันและปริมาณหน่วยความจำ
    ถ้าแค่เพิ่ม 32GiB เข้าไป ตอนนี้ก็คงพอเดินต่อได้สักพัก แต่การออกแบบที่ไม่มีประสิทธิภาพจะยังอยู่ และภายหลังเมื่อระบบซับซ้อนขึ้น ปัญหาเดิมก็อาจระเบิดขึ้นมาและแก้ยากกว่าเดิม
    ถ้าสมมติฐานนี้ถูกต้อง มันก็อธิบายได้ว่าทำไมซอฟต์แวร์ยุคนี้ถึงบวม ช้า และมีบั๊กเยอะ ซอฟต์แวร์แต่ละตัวโดยลำพังแทบไม่ชนขีดจำกัด ดังนั้นแม้แต่ละชิ้นจะไม่มีประสิทธิภาพ ก็ยังใช้ M2 Pro รุ่นใหม่กับการเชื่อมต่อระดับ GiB ไล่แซงจุดที่เป็นปัญหาไปเรื่อย ๆ
    สรุปอาจเป็นว่า การจำกัดตัวเองในระยะยาวดีกว่าทั้งต่อตัวเองและทุกคน

    • ส่วนใหญ่เป็นปัญหาเรื่อง ลำดับความสำคัญ
      ในแอปพลิเคชันจำนวนมาก การใช้เวลามากขนาดนั้นไปกับการปรับแต่งเล็ก ๆ น้อย ๆ ไม่สมเหตุสมผลเลย ถ้าเพิ่ม RAM 32GiB ให้เซิร์ฟเวอร์ได้ในราคาต่ำกว่า 50 ดอลลาร์ เมื่อเทียบกับการใช้เวลานักพัฒนา 40 ชั่วโมงขึ้นไปที่คิดอย่างน้อยชั่วโมงละ 20 ดอลลาร์ จากมุมธุรกิจคำตอบก็ชัดเจนอยู่แล้ว ยิ่งกว่านั้น เว็บไซต์ยังล่มไปทั้งสัปดาห์ ซึ่งแค่นั้นก็อาจทำให้ธุรกิจส่วนใหญ่ตายได้
      คนสายเทคนิคชอบการขุดลึกแบบนี้ จึงอยากปรับแต่งโค้ดเล็ก ๆ น้อย ๆ แบบละเอียดไปเป็นปี ๆ แต่การไล่ตัดขนจามรีไม่รู้จบหาเงินไม่ได้ ถ้าโค้ดรันบนเครื่องแค่จำนวนน้อยมาก ปกติก็ไม่คุ้ม การปรับแต่งแบบนี้ยังมักลงเอยด้วยโค้ดที่ดูแลรักษายากขึ้นด้วย
      ผมมองว่า software bloat ของแอปที่รันบนอุปกรณ์ผู้ใช้ยุคนี้ ส่วนใหญ่มาจากความไม่สอดคล้องกันระหว่างเครื่องของนักพัฒนากับเครื่องของผู้ใช้ นักพัฒนาต้องมีเวิร์กสเตชันประสิทธิภาพสูงเพื่อทำงาน และใช้มันทำการทดสอบพื้นฐานด้วย แต่ผู้ใช้รันบนอุปกรณ์ที่เมื่อ 5 ปีก่อนยังเป็นรุ่นกลางหรือต่ำกว่า
      การขายกับผู้จัดการว่า “เราประหยัดหน่วยความจำได้ 150MB” นั้นยาก แต่ “ถ้าประหยัดหน่วยความจำได้ 150MB ประสิทธิภาพแอปสำหรับผู้ใช้ 10% จะดีขึ้นจากแย่มากเป็นพอใช้งานได้แบบฉิวเฉียด” แบบนี้ขายได้
    • เห็นด้วยกับมุมมองนี้ ข้อจำกัดก่อให้เกิดไหวพริบ และไม่ได้เป็นจริงแค่กับผลงานสายเดโมซีนเท่านั้น ปัญหาเรื่อง scalability สักวันก็ต้องเจออยู่ดี และการจัดการตั้งแต่เนิ่น ๆ ง่ายกว่าปล่อยไว้จนสายมาก ถ้าซอฟต์แวร์รันบน Raspberry Pi ได้ดี บนเซิร์ฟเวอร์จริงก็คงเร็วแบบเหลือเชื่อ
      เมื่อก่อนเราสร้างซอฟต์แวร์กันแบบนั้นจริง ๆ นั่นจึงทำให้แม้แต่เครื่องอย่าง Pentium 1 ก็ยังรันระบบปฏิบัติการที่มีแทบทุกอย่างที่เราคาดหวังในวันนี้ได้ดี ตรงกันข้าม ตอนนี้แม้แต่สมาร์ตโฟนที่มีทรัพยากรมากกว่าตามทุกมิติเป็นพันเท่าตัวอักษร ๆ ก็ยังเลื่อนหน้าเว็บกระตุก ทีม Word 95 ต้องชนกับข้อจำกัดและการแลกเปลี่ยนด้านประสิทธิภาพตลอดเวลา และผลลัพธ์ก็ชัดเจนว่าใช้งานได้หรือไม่ได้
      ถ้าแค่เพิ่ม RAM เข้าไป ก็ยังคงติดอยู่กับการออกแบบที่ด้อยกว่า และอีกไม่นานก็คงต้องซื้อ RAM เพิ่มอีก สิ่งที่น่าทึ่งของการเปลี่ยนแปลงครั้งนี้ไม่ใช่แค่ลดการใช้ทรัพยากร แต่ RAM ที่เหลือทำให้ disk cache เพิ่มขึ้น ส่งผลให้ระบบทำงานได้ดีขึ้นและเร็วขึ้น
      [1] เช่น อันนี้รันบน Pi เครื่องเดียว และเพราะไม่อนุญาตให้อัปเดต จึงเร็วกว่า Wikipedia ที่ใช้งานจริงอยู่มาก: https://encyclopedia.marginalia.nu/article/Hacker_News
    • ผมก็ชอบสมมติฐานนี้เหมือนกัน
      นั่นอาจเป็นเหตุผลที่คอมพิวเตอร์เก่า ๆ ให้ความรู้สึกดี และเกมเก่า ๆ ก็อาจยอดเยี่ยมได้แบบนั้น
      อาจเกี่ยวกับความซับซ้อนของระบบที่เราจัดการอยู่ ถ้าทรัพยากรอย่าง RAM พื้นที่ทางกายภาพ อาหาร วัสดุ เวลา เงิน มีจำกัด เราก็ต้องวางแผนว่าจะใช้อย่างไร และจำเป็นต้องฉลาดขึ้น
      ถ้ามีทรัพยากรแทบไม่จำกัด ก็สร้างได้ตามใจ แต่ความจำเป็นที่จะต้องใส่ใจกับสภาพสุดท้ายก็ลดลง แค่เริ่มทำ แล้วค่อยดูตอนมันรันได้ก็พอ
      ผมไม่ใช่เกมเมอร์ตัวยง แต่ก็ทึ่งเสมอกับความสามารถของมนุษย์ที่ยัดอารมณ์ การผจญภัย และชั่วโมงความสนุกได้มากขนาดนั้นลงในตลับ ROM ขนาดไม่กี่ KB/MB Ocarina Of Time ROM มีขนาดแค่ราว ๆ รูปถ่ายล่าสุด 8 รูปที่ผมถ่ายด้วย iPhone เท่านั้น
    • American Airlines เคยรันระบบออกตั๋วและจองเที่ยวบิน SABRE บนเมนเฟรม System/360 สองเครื่องในช่วงกลางทศวรรษ 1970 และ throughput ก็อยู่แค่ระดับหลายสิบล้านคำสั่งต่อวินาที
      Raspberry Pi 2 ประมวลผลคำสั่ง Dhrystone ได้มากกว่า 4 พันล้านคำสั่งต่อวินาที และ Pi 4 ได้มากกว่า 1 หมื่นล้านคำสั่งต่อวินาที
      แน่นอนว่าเมื่อเทียบกับมาตรฐานปัจจุบัน SABRE ช่วงกลางทศวรรษ 1970 ค่อนข้างพื้นฐานสำหรับระบบหลักของสายการบิน แต่ในทางทฤษฎี การรันระบบแบบย่อของสายการบินมากกว่า 100 แห่งพร้อมกันบน Pi 2 เครื่องเดียวก็เป็นไปได้
      โปรแกรมสมัยใหม่อยู่ห่างไกลจากการปรับแต่งมาก ยกเว้นกรณีที่มีการคำนวณทางคณิตศาสตร์หนัก ๆ การปรับปรุง 1000 เท่าหรือ 10000 เท่า ก็เป็นไปได้
    • ประเด็นที่ว่าแต่ละตัวโดยลำพังไม่ชนขีดจำกัดเป็นจุดที่ดี
      ผมคิดว่า Microsoft เจอปัญหานี้หนักมาก แล็ปท็อปราคา 3000 ดอลลาร์เมื่อ 5 ปีก่อน ก็ช้าจนยอมรับได้ยากถ้ารัน Teams call, Office หลายตัว และเบราว์เซอร์ที่เปิด 30 แท็บพร้อมกัน
      ถ้าทดสอบแยกกันทีละตัว ตอนรันแค่อันเดียวก็ดูโอเค แต่คนจริง ๆ ไม่ได้ใช้งานแบบนั้น
      ขีดจำกัดที่ตั้งขึ้นโดยเจตนา เช่น จำกัดเวลาในการรันบนฮาร์ดแวร์ที่นิยามไว้อย่างชัดเจน และเพิ่มขีดจำกัดนั้นได้เฉพาะหลังมีการตัดสินใจอย่างชัดแจ้ง อาจเป็นทางออก
      แต่อีกด้าน ผมเขียนแต่ซอฟต์แวร์ธุรกิจที่ประเด็นด้านประสิทธิภาพมักสรุปได้ว่า “อย่าทำอะไรโง่ ๆ กับฐานข้อมูล ส่วนที่เหลือไม่ต้องสนใจเพราะลูกค้าไม่ได้จ่ายเงินให้กังวลเรื่องนั้น” ดังนั้นผมอาจเข้าใจผิดไปไกลก็ได้
  • ทุกครั้งที่มีอัปเดตของ marginalia.nu โผล่มา ผมดีใจเสมอ ผมเป็นผู้ใช้ที่เห็นคุณค่าของไซต์นี้ และหวังว่าจะยังโพสต์ต่อไป

  • ขอบคุณครับ ผมกำลังทำโปรเจกต์งานอดิเรกเกี่ยวกับ search engine อยู่ แต่ไม่รู้ทำไมถึงค้นหาแต่คำแปรผันของ “Magnolia” มาตลอด ชื่อ Marginalia จำยากสำหรับผม ตอนนี้กำลังทำความเข้าใจ Searx อยู่
    สงสัยว่า Marginalia รองรับการค้นหาด้วย ตัวกรองเวลา อย่างวันล่าสุดหรือสัปดาห์ล่าสุดไหม ดูจากคีย์เวิร์ดพิเศษแล้ว พารามิเตอร์ค้นหาดูเหมือนจะรับเฉพาะแบบอิงปีเท่านั้น
    year>2005 (beta) เอกสารดูเหมือนเผยแพร่หลังปี 2005
    year=2005 (beta) เอกสารดูเหมือนเผยแพร่ในปี 2005
    year<2005 (beta) เอกสารดูเหมือนเผยแพร่ก่อนปี 2005

    • ดัชนีค้นหาไม่ได้อัปเดตบ่อยเกินเดือนละครั้ง จึงไม่มีตัวกรองแบบนั้น
      ตัวกรองปีก็ค่อนข้างหยาบ และการจับวันที่ของเว็บเพจส่วนใหญ่ให้แม่นยำนั้นยากมาก
    • มันเป็น search engine สำหรับคอนเทนต์เฉพาะกลุ่ม พูดอีกอย่างคือสำหรับคอนเทนต์ชายขอบ ดังนั้นชื่อ Marginalia จึงเข้าใจได้
  • สงสัยว่าคำพูดที่ว่า “ทุกครั้งที่ SSD อัปเดตไบต์เดียวที่ไหนสักแห่งบนดิสก์ มันต้องลบทั้งเพจแล้วเขียนใหม่” นั้นตรงกับ SSD จริงหรือไม่
    สำหรับแฟลชดิบ ๆ ไม่ใช่แบบนั้น ถ้าเป็นการเขียนทับค่า all-ones ที่ “ว่าง” หรือเปลี่ยนได้เฉพาะ 1 เป็น 0 ก็ทำได้ การเขียนช้ากว่าการอ่านหลายลำดับขั้น แต่ก็เร็วกว่าการลบหลายลำดับขั้นเช่นกัน และงบประมาณการสึกหรอคิดเฉพาะการลบเท่านั้น
    ถ้าคอนโทรลเลอร์ SSD ไม่ใช้ประโยชน์จากจุดนี้ ก็ดูเหมือนทำเข้าประตูตัวเอง แต่ถ้าภายในเป็นแบบ log-structured จริง ๆ ก็พอคิดได้ว่าอาจทำไม่ได้

    • อาจขึ้นอยู่กับกรณี
      เมื่อก่อนเคยเขียนไดรเวอร์ให้ชิปแฟลชยอดนิยมหลายตัว และชิปที่ทำงานด้วยในตอนนั้นทั้งหมดใช้ได้เฉพาะ I/O แบบเป็นเพจขนาดคงที่ สำหรับทั้งอ่านและเขียน
      SSD คือการรวมชิปหลายตัวเข้าด้วยกัน จึงคาดว่าชิปแต่ละตัวใน SSD ก็น่าจะรองรับเฉพาะ I/O แบบเพจขนาดคงที่เช่นกัน
    • ในกรณีนี้ โดยพื้นฐานแล้วกำลังเขียนฮาร์ดไดรฟ์ทั้งลูกใหม่ทั้งหมดใน ลำดับสุ่ม ซึ่งเป็นกรณีเลวร้ายที่สุดสำหรับ SSD
      โดยปกติคอนโทรลเลอร์จะใช้หลายเทคนิค เช่น overprovisioning, buffering, write reordering เพื่อหลีกเลี่ยงแพตเทิร์นเลวร้ายแบบนี้ แต่ก็มีขีดจำกัด
    • เท่าที่ผมเข้าใจคือแบบนี้
      หน่วยที่เล็กที่สุดที่เขียนได้ใน SSD คือเพจ และหน่วยที่เล็กที่สุดที่ลบได้คือบล็อกซึ่งประกอบด้วยหลายเพจ
      ดังนั้นแม้งานเขียนจะกระทบเพียง 1 ไบต์ในเพจ SSD ก็ไม่สามารถลบเฉพาะไบต์นั้นได้ แต่ก็ไม่ได้แปลว่าต้องลบทั้งบล็อกเสมอไป
      SSD สามารถทำงานแบบ “read-modify-write” ได้ โดยอ่านทั้งเพจที่มีไบต์ที่จะเปลี่ยนเข้าไปในบัฟเฟอร์แคชของ SSD แก้เฉพาะไบต์นั้นในแคชของเพจ จากนั้นลบบล็อกใหม่ที่ว่าง เขียนเพจที่แก้แล้วจากแคชลงในบล็อกใหม่ และอัปเดต ตารางแมป FTL ให้ชี้ไปยังเพจที่อัปเดตในบล็อกใหม่
      ดังนั้นแม้เปลี่ยนเพียง 1 ไบต์ เพจก็ต้องถูกเขียนใหม่อยู่ดี เพียงแต่สามารถหลีกเลี่ยงการลบทั้งบล็อกได้จนกว่าจะมีเพจจำนวนมากในนั้นถูกแก้ไข
    • ไม่ถูกต้องเสียทีเดียว มุมมองเชิงตรรกะที่ว่าเพจอยู่ที่แอดเดรสแฟลชใดแฟลชหนึ่งนั้นไม่ตรงกับความจริง
      เมื่อมีการเขียน เพจจะถูกย้ายไปมาภายในอุปกรณ์จริง ตัวไดรฟ์เองจะเก็บแผนที่ที่บอกว่าแอดเดรสใดใช้ทำอะไร สถานะสุขภาพเป็นอย่างไร ฯลฯ เป็นเหมือน รูปแบบการจัดเก็บแบบ sparse ชนิดหนึ่ง
      ยังมีคำสั่งอย่าง TRIM การบำรุงรักษาเป็นระยะ และ garbage collection ด้วย
      ในทางปฏิบัติ เมื่อเขียนลงไดรฟ์ที่ยังไม่เต็ม มันจะหาเพจสำหรับใส่ข้อมูล ตรวจว่าตรงนั้นมีข้อมูลอยู่หรือไม่ ถ้าจำเป็นก็ทำ read/modify/write ตัดสินใจว่าจะเขียนข้อมูลไว้ที่ไหน แล้วจึงเขียน เนื่องจาก wear leveling มีโอกาสสูงที่จะไม่กลับไปอยู่ที่เดิม
      ถูกแล้วที่คอนโทรลเลอร์ทำขั้นตอนที่ซับซ้อนกว่านี้มากเพื่อประสิทธิภาพ ดังนั้นไดรฟ์ใหม่ที่ว่างจึงทำงานได้ดีกว่าอยู่พักหนึ่ง และไดรฟ์เก่าที่เต็มจนไม่มีเพจสำรองก็อาจช้าลงจริง
      ขอเสริมที่มาว่า เคยเป็นวิศวกรหลักของตัวเร่งแฟลชแบบ memory-mapped ที่มี cache coherency ทำให้ไดรฟ์ถูกแมปได้อย่างมีประสิทธิภาพมากจาก Linux user space แต่สุดท้ายช่วงหนึ่งก็ต้องยอมจำนนต่อโมเดลการเขียนโปรแกรมที่ “ง่าย” ซึ่งทำให้มันดูเหมือนฮาร์ดไดรฟ์อีกลูกหนึ่งเท่านั้น
    • ผิดทั้งหมด แม้แต่คอนโทรลเลอร์ SSD ที่ดิบที่สุดก็ยังพยายามบรรเทาสถานการณ์แบบนี้
  • นึกขึ้นได้ว่าเจ้านายของผมที่ Mojeek ก็น่าจะเดินเส้นทางคล้ายกันมาก บทความนี้สอดคล้องกับบทสนทนาในอดีตหลายอย่าง
    Mojeek เริ่มในปี 2004 โครงหลักส่วนใหญ่สร้างโดยนักพัฒนาคนเดียว และสร้างแทบทั้งหมดของระบบค้นคืนสารสนเทศกับโครงสร้างพื้นฐาน
    เรื่องข้อจำกัดด้านเงินทุนและฮาร์ดแวร์ การตัดสินใจเรื่อง ID แบบ 32 บิตเทียบกับ 64 บิต, sharding, ความเร็วในการอัปเดต ฟังดูคุ้นมาก
    ยังทำให้นึกถึง “Google dance” ของ Google สมัยก่อนด้วย ตอนนั้นผลการค้นหาอัปเดตเดือนละครั้ง ส่วนตอนนี้แกว่งไหวทุกวัน ทั้งหมดเป็นกระบวนการวิวัฒน์ และก็ดีที่ได้เห็น Marginalia มอบอีกมุมมองหนึ่งต่อเว็บที่อยู่นอกเหนือบิ๊กเทค

  • อ่านสนุก
    หลายคนมองการปรับแต่งประสิทธิภาพว่าเป็นมนตร์ดำลึกซึ้ง แต่ส่วนใหญ่กลับง่ายกว่าการแก้บั๊กทั่วไปเสียอีก แค่ปฏิบัติต่อ การใช้ทรัพยากรมากเกินไป เหมือนเป็นบั๊กอย่างหนึ่งก็พอ
    ผมมองว่าบั๊กส่วนใหญ่ที่ทำซ้ำได้ง่ายไม่ต้องใช้เวทมนตร์ในการแก้ ถ้าจิ้มสำรวจบั๊กได้ โดยปกติก็จำแนกมันได้ และแม้แต่ข้อบกพร่องด้านการออกแบบที่พบยาก พอทำซ้ำได้แล้วก็มักเผยตัวค่อนข้างเร็ว
    ซอฟต์แวร์ที่ไม่มีใครมองประสิทธิภาพอย่างวิพากษ์ ก็เหมือนซอฟต์แวร์ที่มีบั๊กทำซ้ำง่ายเป็นร้อย ๆ ตัวแต่ไม่มีใครดีบัก คุณสามารถค่อย ๆ削มันออกทีละอย่างได้อยู่นานก่อนจะชนของยากจริง ๆ
    ผมมองว่าทัศนคติแบบนี้ใกล้เคียงกับมรดกตกค้างจากยุคที่ผู้คนปรับแอดเดรสเป้าหมายของ branch ให้มาถึงตรงกับช่วงที่หัวดรัมส่งคำสั่งให้ CPU ต้องการพอดี ในยุคที่ทรัพยากรถูกจำกัดสุดขีด ต้องเขียนทุกอย่างด้วยแอสเซมบลีมือ และตำแหน่งหน่วยความจำ global มีความหมายต่างกันตามขั้นตอนของโปรแกรม ตอนนั้นคนที่ฉลาดมาก ๆ ได้พิจารณาประสิทธิภาพอย่างวิพากษ์แล้ว จึงต้องไปหาสิ่งที่พวกเขาหาไม่เจอ แต่ในโค้ดสมัยใหม่ เรื่องแบบนี้พบไม่บ่อย

    • โดยรวมเห็นด้วย แต่บั๊กมักตามรอยได้ง่ายกว่ามากด้วยกลยุทธ์ divide and conquer
      ถ้าส่งคำขอ X ไปยังบริการ Y แล้วทำให้บั๊กเกิดซ้ำได้ ก็แค่ลดขนาด test case ลงเรื่อย ๆ จนกว่าจะพบตัวการ
      การปรับแต่งประสิทธิภาพมักเป็นปัญหาด้านสถาปัตยกรรม บางครั้งก็เป็นกรณีที่สามารถนำบัฟเฟอร์กลับมาใช้ซ้ำได้แต่กลับคัดลอกอยู่ ซึ่งของแบบนั้นจะหมดให้แก้เร็ว และ profiler ก็บอกข้อมูลที่ต้องการให้
      ผลได้ด้านประสิทธิภาพขนาดใหญ่มักมาจากการเปลี่ยน data flow ทั้งหมด และตัดส่วนสำคัญออก เพื่อให้โค้ดทำสิ่งที่จำเป็นด้วยขั้นตอนให้น้อยที่สุดเท่าที่เป็นไปได้
  • สงสัยว่าทำไมถึงเลือก SQLite แทนที่จะเป็น key-value store ดูเหมือนต้องการแค่อ่านด้วย ID และไม่จำเป็นต้องใช้คอลัมน์อื่น ดังนั้นฐานข้อมูลเชิงสัมพันธ์ดูจะไม่จำเป็น

    • ก็อาจทำแบบนั้นได้ จะสร้างตารางชั่วคราวใน MariaDB ก็ได้เหมือนกัน
      SQLite มีข้อดีตรงเป็นไฟล์เดียว จึงทำอะไรดี ๆ อย่างการคัดลอกหรือแชร์ได้
  • ชอบที่นี่เป็นอีกตัวอย่างหนึ่งของข้อจำกัดที่ก่อให้เกิดนวัตกรรม นวัตกรรมมักพบได้จาก ข้อจำกัด มากกว่าความอุดมสมบูรณ์