ความสำเร็จที่เหลือเชื่อ
(marginalia.nu)- 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 ความคิดเห็น
Marginalia - เสิร์ชเอนจิน DIY แบบอิสระ
ความคิดเห็นจาก Hacker News
เห็นกรณีความสำเร็จแบบนี้ในโปรเจกต์ที่เดินไปในทิศทาง ตรงข้ามอย่างสิ้นเชิง กับโลกที่เหลือแล้วรู้สึกดีจริง ๆ
อยากเรียกมันว่า Engildification และควรมีอะไรแบบนี้มากขึ้น
ผมชอบบทความ Sleeping At Night ด้วย และเมื่อรวมกับบทความล่าสุด “Lie Still in Bed” ก็ดูเหมือนเป็นทางเลือกที่เรียบง่ายมากในการแก้ปัญหาการฝึกการนอน
เสิร์ชเอนจิน Marginalia ผู้เขียนเพิ่งเปลี่ยนมาทำเต็มเวลา และเท่าที่รู้ทีมมีขนาด 1 คน จึงอยู่ไกลที่สุดจากความเสี่ยงเรื่อง enshittification กลับกัน อัญมณีมักถูกสร้างขึ้นในระดับขนาดแบบนี้ และความสร้างสรรค์ ความแปลกใหม่ และวิสัยทัศน์ก็เปล่งประกาย
ความคิดเห็นนี้ได้รับการสนับสนุนโดยชมรม “ลาออกจากงานนั่งโต๊ะแล้วออกมาทำงานอิสระกันเถอะ”
ลดการใช้ทรัพยากรลงครึ่งหนึ่ง ทำงานได้มีประสิทธิผลกว่าก่อนบนหน้าจอที่เล็กลง และตอนกลางคืนก็หลับลึกเหมือนท่อนไม้
ลิงก์สำหรับผู้สนใจ: https://www.marginalia.nu/log/86-sleep/
อ้างอิงเพิ่มเติม เสิร์ชเอนจินแบบเสียเงิน 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% จะดีขึ้นจากแย่มากเป็นพอใช้งานได้แบบฉิวเฉียด” แบบนี้ขายได้
เมื่อก่อนเราสร้างซอฟต์แวร์กันแบบนั้นจริง ๆ นั่นจึงทำให้แม้แต่เครื่องอย่าง 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 เท่านั้น
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
ตัวกรองปีก็ค่อนข้างหยาบ และการจับวันที่ของเว็บเพจส่วนใหญ่ให้แม่นยำนั้นยากมาก
สงสัยว่าคำพูดที่ว่า “ทุกครั้งที่ SSD อัปเดตไบต์เดียวที่ไหนสักแห่งบนดิสก์ มันต้องลบทั้งเพจแล้วเขียนใหม่” นั้นตรงกับ SSD จริงหรือไม่
สำหรับแฟลชดิบ ๆ ไม่ใช่แบบนั้น ถ้าเป็นการเขียนทับค่า all-ones ที่ “ว่าง” หรือเปลี่ยนได้เฉพาะ 1 เป็น 0 ก็ทำได้ การเขียนช้ากว่าการอ่านหลายลำดับขั้น แต่ก็เร็วกว่าการลบหลายลำดับขั้นเช่นกัน และงบประมาณการสึกหรอคิดเฉพาะการลบเท่านั้น
ถ้าคอนโทรลเลอร์ SSD ไม่ใช้ประโยชน์จากจุดนี้ ก็ดูเหมือนทำเข้าประตูตัวเอง แต่ถ้าภายในเป็นแบบ log-structured จริง ๆ ก็พอคิดได้ว่าอาจทำไม่ได้
เมื่อก่อนเคยเขียนไดรเวอร์ให้ชิปแฟลชยอดนิยมหลายตัว และชิปที่ทำงานด้วยในตอนนั้นทั้งหมดใช้ได้เฉพาะ I/O แบบเป็นเพจขนาดคงที่ สำหรับทั้งอ่านและเขียน
SSD คือการรวมชิปหลายตัวเข้าด้วยกัน จึงคาดว่าชิปแต่ละตัวใน SSD ก็น่าจะรองรับเฉพาะ I/O แบบเพจขนาดคงที่เช่นกัน
โดยปกติคอนโทรลเลอร์จะใช้หลายเทคนิค เช่น 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 แต่สุดท้ายช่วงหนึ่งก็ต้องยอมจำนนต่อโมเดลการเขียนโปรแกรมที่ “ง่าย” ซึ่งทำให้มันดูเหมือนฮาร์ดไดรฟ์อีกลูกหนึ่งเท่านั้น
นึกขึ้นได้ว่าเจ้านายของผมที่ Mojeek ก็น่าจะเดินเส้นทางคล้ายกันมาก บทความนี้สอดคล้องกับบทสนทนาในอดีตหลายอย่าง
Mojeek เริ่มในปี 2004 โครงหลักส่วนใหญ่สร้างโดยนักพัฒนาคนเดียว และสร้างแทบทั้งหมดของระบบค้นคืนสารสนเทศกับโครงสร้างพื้นฐาน
เรื่องข้อจำกัดด้านเงินทุนและฮาร์ดแวร์ การตัดสินใจเรื่อง ID แบบ 32 บิตเทียบกับ 64 บิต, sharding, ความเร็วในการอัปเดต ฟังดูคุ้นมาก
ยังทำให้นึกถึง “Google dance” ของ Google สมัยก่อนด้วย ตอนนั้นผลการค้นหาอัปเดตเดือนละครั้ง ส่วนตอนนี้แกว่งไหวทุกวัน ทั้งหมดเป็นกระบวนการวิวัฒน์ และก็ดีที่ได้เห็น Marginalia มอบอีกมุมมองหนึ่งต่อเว็บที่อยู่นอกเหนือบิ๊กเทค
อ่านสนุก
หลายคนมองการปรับแต่งประสิทธิภาพว่าเป็นมนตร์ดำลึกซึ้ง แต่ส่วนใหญ่กลับง่ายกว่าการแก้บั๊กทั่วไปเสียอีก แค่ปฏิบัติต่อ การใช้ทรัพยากรมากเกินไป เหมือนเป็นบั๊กอย่างหนึ่งก็พอ
ผมมองว่าบั๊กส่วนใหญ่ที่ทำซ้ำได้ง่ายไม่ต้องใช้เวทมนตร์ในการแก้ ถ้าจิ้มสำรวจบั๊กได้ โดยปกติก็จำแนกมันได้ และแม้แต่ข้อบกพร่องด้านการออกแบบที่พบยาก พอทำซ้ำได้แล้วก็มักเผยตัวค่อนข้างเร็ว
ซอฟต์แวร์ที่ไม่มีใครมองประสิทธิภาพอย่างวิพากษ์ ก็เหมือนซอฟต์แวร์ที่มีบั๊กทำซ้ำง่ายเป็นร้อย ๆ ตัวแต่ไม่มีใครดีบัก คุณสามารถค่อย ๆ削มันออกทีละอย่างได้อยู่นานก่อนจะชนของยากจริง ๆ
ผมมองว่าทัศนคติแบบนี้ใกล้เคียงกับมรดกตกค้างจากยุคที่ผู้คนปรับแอดเดรสเป้าหมายของ branch ให้มาถึงตรงกับช่วงที่หัวดรัมส่งคำสั่งให้ CPU ต้องการพอดี ในยุคที่ทรัพยากรถูกจำกัดสุดขีด ต้องเขียนทุกอย่างด้วยแอสเซมบลีมือ และตำแหน่งหน่วยความจำ global มีความหมายต่างกันตามขั้นตอนของโปรแกรม ตอนนั้นคนที่ฉลาดมาก ๆ ได้พิจารณาประสิทธิภาพอย่างวิพากษ์แล้ว จึงต้องไปหาสิ่งที่พวกเขาหาไม่เจอ แต่ในโค้ดสมัยใหม่ เรื่องแบบนี้พบไม่บ่อย
ถ้าส่งคำขอ X ไปยังบริการ Y แล้วทำให้บั๊กเกิดซ้ำได้ ก็แค่ลดขนาด test case ลงเรื่อย ๆ จนกว่าจะพบตัวการ
การปรับแต่งประสิทธิภาพมักเป็นปัญหาด้านสถาปัตยกรรม บางครั้งก็เป็นกรณีที่สามารถนำบัฟเฟอร์กลับมาใช้ซ้ำได้แต่กลับคัดลอกอยู่ ซึ่งของแบบนั้นจะหมดให้แก้เร็ว และ profiler ก็บอกข้อมูลที่ต้องการให้
ผลได้ด้านประสิทธิภาพขนาดใหญ่มักมาจากการเปลี่ยน data flow ทั้งหมด และตัดส่วนสำคัญออก เพื่อให้โค้ดทำสิ่งที่จำเป็นด้วยขั้นตอนให้น้อยที่สุดเท่าที่เป็นไปได้
สงสัยว่าทำไมถึงเลือก SQLite แทนที่จะเป็น key-value store ดูเหมือนต้องการแค่อ่านด้วย ID และไม่จำเป็นต้องใช้คอลัมน์อื่น ดังนั้นฐานข้อมูลเชิงสัมพันธ์ดูจะไม่จำเป็น
SQLite มีข้อดีตรงเป็นไฟล์เดียว จึงทำอะไรดี ๆ อย่างการคัดลอกหรือแชร์ได้
ชอบที่นี่เป็นอีกตัวอย่างหนึ่งของข้อจำกัดที่ก่อให้เกิดนวัตกรรม นวัตกรรมมักพบได้จาก ข้อจำกัด มากกว่าความอุดมสมบูรณ์