- หากนำ OpenDocument Presentation (ODP) ไปเก็บไว้ใน คอนเทนเนอร์ SQLite แทนไฟล์ ZIP archive จะสามารถออกแบบวิธีบันทึก เริ่มต้น และกู้คืนเอกสารให้ปลอดภัยและรวดเร็วขึ้นได้
- ปัจจุบัน ODP มีโครงสร้างที่รวมไฟล์ XML และไฟล์รูปภาพไว้ใน ZIP archive และไฟล์งานนำเสนอตัวอย่าง 49 สไลด์ประกอบด้วยรายการทั้งหมด 78 รายการ เช่น
content.xml, styles.xml, meta.xml, settings.xml และรูปภาพต่าง ๆ
- ในโครงสร้างแบบ ZIP แม้มีการเปลี่ยนแปลงเล็กน้อยก็มักต้องเขียน archive ทั้งหมดใหม่ ทำให้ทำ incremental update ได้ยาก และนำไปสู่ความล่าช้าในการ File/Save รวมถึงปริมาณการเขียนบน SSD ที่เพิ่มขึ้น
- หากเปลี่ยนเป็น SQLite จะสามารถเก็บไฟล์เป็นแถวในตารางได้ และยิ่งไปกว่านั้นยังสามารถแบ่งเนื้อหาและเวอร์ชันตามสไลด์ เพื่ออ่านเฉพาะสไลด์แรกหรือบันทึกเฉพาะสไลด์ที่เปลี่ยนแปลงได้
- นี่ไม่ใช่ข้อเสนอให้วิจารณ์หรือเปลี่ยน OpenDocument เอง แต่เป็นตัวอย่างที่แสดงให้เห็นว่า SQLite ช่วยทำให้ฟอร์แมตไฟล์ของแอปพลิเคชันสร้างความสามารถอย่าง การบันทึกแบบ atomic, การเข้าถึง, การจัดการเวอร์ชัน และการกู้คืนได้ง่ายขึ้น
ขอบเขตและเป้าหมายของการทดลองทางความคิด
- เป้าหมายคือ ODP (OpenDocument Presentation) ซึ่งเป็นฟอร์แมตเอกสารนำเสนอใน OpenDocument
- จุดประสงค์ไม่ใช่การเปลี่ยน OpenDocument จริง ๆ แต่เป็นการพิจารณาวิธีใช้ SQLite เป็นคอนเทนเนอร์ในการออกแบบฟอร์แมตไฟล์ในอนาคต
- ผลที่คาดหวังคือเอกสารมีขนาดเล็กลง, File/Save เร็วขึ้น, เริ่มต้นเร็วขึ้น, ใช้หน่วยความจำน้อยลง, จัดการเวอร์ชันเอกสารได้ และมอบประสบการณ์ผู้ใช้ที่ดีขึ้น
โครงสร้างไฟล์ ODP ปัจจุบัน
- ไฟล์ ODP คือ ZIP archive ที่บรรจุไฟล์ XML และทรัพยากรรูปภาพ
- ตัวอย่างเช่น ไฟล์งานนำเสนอ 49 สไลด์เกี่ยวกับ SQLite จาก SouthEast LinuxFest ปี 2014 มีรายการทั้งหมด 78 รายการในผลลัพธ์
zip -l
- ไฟล์ XML สี่ไฟล์ ได้แก่
content.xml, styles.xml, meta.xml, settings.xml ใช้กำหนดเลย์เอาต์สไลด์ เนื้อหาข้อความ และสไตล์
- ในไฟล์งานนำเสนอมี รูปภาพ 62 รูป ตั้งแต่ภาพเต็มหน้าจอไปจนถึงไอคอนขนาดเล็ก ซึ่งถูกเก็บเป็นไฟล์แยก
- ไฟล์
mimetype มีบรรทัดเดียวคือ application/vnd.oasis.opendocument.presentation
- ไฟล์ของโปรแกรมประมวลผลคำและสเปรดชีตใน OpenDocument ก็มีโครงสร้างคล้ายกัน แต่สิ่งที่วิเคราะห์คือ ODP
ข้อจำกัดของ ODP ที่ใช้ ZIP
- ZIP archive คล้ายกับ ฐานข้อมูล key/value ที่ปรับให้เหมาะกับการเขียนครั้งเดียว อ่านหลายครั้ง และเหมาะกับโครงสร้างที่มี key จำนวนน้อยโดยแต่ละ key มีค่า BLOB ขนาดใหญ่
- เนื่องจากอัปเดตรายการรายตัวได้ยาก เมื่อผู้ใช้เลือก
File/Save โดยทั่วไปจึงต้องสร้าง ZIP archive ทั้งหมดขึ้นใหม่
- แม้จะสามารถอัปเดตรายการรายตัวโดยไม่ทำให้เอกสารทั้งไฟล์เสียหายระหว่างไฟดับหรือแครชได้ แต่ก็ยากพอสมควรจนในทางปฏิบัติแทบไม่ถูกใช้
- ในไฟล์งานนำเสนอขนาด 50MB แม้เปลี่ยนตัวอักษรเพียงตัวเดียวก็อาจต้องเขียนข้อมูลทั้ง 50MB ใหม่ทั้งหมด
- เวลาเริ่มต้นอาจช้าลง
- ODP เก็บเนื้อหาของทุกสไลด์ไว้ในไฟล์ XML ขนาดใหญ่ไฟล์เดียวชื่อ
content.xml
- LibreOffice ต้องอ่านและ parse ไฟล์นี้ทั้งหมดเพื่อแสดงสไลด์แรก
- ดูเหมือนว่ารูปภาพทั้งหมดก็ถูกอ่านเข้าไปในหน่วยความจำด้วย ส่งผลให้เมื่อดับเบิลคลิกไฟล์ สิ่งที่ปรากฏไม่ใช่สไลด์แรก แต่เป็นแถบความคืบหน้า
- ใช้หน่วยความจำมากขึ้น
- โครงสร้าง ZIP ชักนำให้การใช้งานถูกออกแบบเป็นการอ่านเอกสารทั้งหมดเข้า memory ตอนเริ่มต้น แก้ไขใน memory แล้วเขียนเอกสารทั้งหมดลงดิสก์ตอนบันทึก
- ไฟล์งานนำเสนอขนาด 50MB อาจใช้ RAM มากกว่า 200MB
- หากเปิดไฟล์งานนำเสนอหลายไฟล์พร้อมกัน และใช้งานเบราว์เซอร์กับแอปเดสก์ท็อปไปด้วย อาจเกิดการ swapping ได้
- การกู้คืนหลังแครชยุ่งยากขึ้น
- ตระกูล OpenOffice จะสำรองเอกสารที่อยู่ใน memory เป็นระยะเพื่อรับมือกับการแครช
- ระหว่างสำรอง แอปอาจค้างไปหลายวินาที และหลังเริ่มใหม่ต้องผ่านกล่องโต้ตอบกู้คืนแยกต่างหาก
- การเข้าถึงเนื้อหาทำได้ยาก
- แม้จะใช้เครื่องมือ ZIP ดึงรูปภาพออกมาได้ แต่การดึงหรือแก้ไขข้อความในสไลด์ด้วยเครื่องมือทั่วไปทำได้ยาก
content.xml ของไฟล์ตัวอย่างมีบรรทัดแรกเป็น XML declaration และบรรทัดที่สองมี XML 211,792 ตัวอักษร อยู่ในบรรทัดเดียว
การปรับปรุงแรก: แทนที่ ZIP ด้วย SQLite
- ขั้นแรกคือโครงสร้างง่าย ๆ ที่เปลี่ยนรายการใน ZIP ให้เป็นแถวของตาราง SQLite
CREATE TABLE OpenDocTree(
filename TEXT PRIMARY KEY,
filesize BIGINT,
content BLOB
);
- ในขั้นนี้ไม่ได้เปลี่ยนโครงสร้างส่วนอื่นของฟอร์แมตไฟล์
- ยังคงเป็นโครงสร้างแบบ “กองไฟล์” แต่แต่ละไฟล์กลายเป็นแถวในฐานข้อมูล SQLite แทนที่จะเป็น entry ใน ZIP
- การเปรียบเทียบขนาดไฟล์ SQLite ที่นำเนื้อหาเดียวกับ
self2014.odp ที่สร้างโดย NeoOffice มาบรรจุใหม่ด้วยยูทิลิตี SQLAR เป็นดังนี้
self2014.odp: 10,514,994 ไบต์
self2014.sqlar: 10,464,256 ไบต์
zip.odp ที่บีบอัดใหม่ด้วย zip บน command line: 10,416,644 ไบต์
- ไฟล์ SQLite มีขนาดเล็กกว่า ODP ที่ NeoOffice สร้างประมาณ 0.5%
- ไฟล์ ZIP ที่บีบอัดอย่างดีด้วย
zip บน command line มีขนาดเล็กกว่า SQLite อีกประมาณ 0.5%
- ฐานข้อมูล SQLite สามารถแข่งขันกับ ZIP archive ได้ในแง่ขนาด
- SQLite มี การเขียนแบบ atomic จึงสามารถบันทึกการเปลี่ยนแปลงแบบเพิ่มส่วนได้โดยไม่เสี่ยงทำให้เอกสารเสียหายระหว่างแครชหรือไฟดับ
- ข้อจำกัดที่ยังคงอยู่คือยังต้องเขียน
content.xml ทั้งหมดใหม่
- ถึงอย่างนั้น ไฟล์ที่เหลืออีก 77 ไฟล์ก็ปล่อยไว้เหมือนเดิมได้ ทำให้ File/Save เร็วขึ้นและลดปริมาณการเขียนบน SSD
การปรับปรุงที่สอง: แบ่งเนื้อหาเป็นชิ้นเล็ก ๆ
- SQLite สามารถเก็บทั้งก้อนข้อมูลขนาดใหญ่และชิ้นข้อมูลเล็กจำนวนมากได้อย่างมีประสิทธิภาพ จึงสามารถมีตารางเนื้อหาแยกตามสไลด์ได้
CREATE TABLE slide(
pageNumber INTEGER,
slideContent TEXT
);
CREATE INDEX slide_pgnum ON slide(pageNumber);
- เมื่อจะแสดงหน้าจอแรก แอปพลิเคชันต้องอ่านเฉพาะสไลด์แรกเท่านั้น
SELECT slideContent FROM slide WHERE pageNumber=1;
- โครงสร้างนี้ทำให้ดึง parse และแสดงเฉพาะเนื้อหาของสไลด์แรกได้อย่างรวดเร็ว โดยไม่ต้องอ่าน
content.xml ทั้งหมดตอนเริ่มต้น
- ทางเลือกในการ implement ก็เพิ่มขึ้น
- หลังแสดงสไลด์แรกแล้ว สามารถอ่านหน้าที่เหลือใน background thread ได้
- สามารถเก็บเฉพาะสไลด์ปัจจุบันไว้ใน memory ได้
- หรือเพื่อการเปลี่ยนสไลด์ที่รวดเร็ว อาจเก็บเฉพาะสไลด์ปัจจุบันและสไลด์ถัดไปไว้ใน memory ก็ได้
- การบันทึกก็เขียนใหม่เฉพาะหน้าที่เปลี่ยนแปลงได้ จึงทำให้ File/Save เร็วยิ่งขึ้น
- สำหรับข้อความสั้น ๆ ประสิทธิภาพการบีบอัดจะลดลงและอาจทำให้ขนาดเอกสารเพิ่มขึ้น
- อย่างไรก็ตาม พื้นที่ส่วนใหญ่ของเอกสารถูกใช้โดยรูปภาพ ดังนั้นต้นทุนจากประสิทธิภาพการบีบอัดข้อความที่ลดลงจึงถือว่าน้อยเมื่อเทียบกับประสบการณ์ผู้ใช้ที่ดีขึ้น
การปรับปรุงที่สาม: การจัดการเวอร์ชัน
- หากเก็บสไลด์เป็นวัตถุแยกกัน ก็สามารถบรรจุ ประวัติเวอร์ชัน ไว้ในเอกสารเดียวกันได้
CREATE TABLE slide(
slideId INTEGER PRIMARY KEY,
derivedFrom INTEGER REFERENCES slide,
content TEXT
);
CREATE TABLE version(
versionId INTEGER PRIMARY KEY,
priorVersion INTEGER REFERENCES version,
checkinTime DATETIME,
comment TEXT,
manifest TEXT
);
- แต่ละสไลด์มี
slideId ที่ไม่ซ้ำ แทนที่จะมีเลขหน้า และลำดับถูกกำหนดโดยรายการ slideId ที่เก็บไว้ใน manifest ของตาราง version
- ตอนเริ่มต้น แอปพลิเคชันจะเลือกเวอร์ชันที่จะแสดงก่อน และโดยปกติสามารถดึงเวอร์ชันล่าสุดมาได้
SELECT manifest, versionId FROM version ORDER BY versionId DESC LIMIT 1;
- สามารถใช้ query เพื่อดึงเวอร์ชันล่าสุดตาม
checkinTime ได้เช่นกัน
SELECT manifest, versionId, max(checkinTime) FROM version;
- ใน SQLite query
max(checkinTime) ข้างต้นจะคืนผลลัพธ์ที่กำหนดไว้ชัดเจน แต่ในฐานข้อมูล SQL อื่น ๆ จำนวนมากอาจคืนผลลัพธ์ที่ไม่ถูกกำหนดไว้หรือเกิด error ได้
- เมื่อผู้ใช้สั่ง
File/Save สามารถเพิ่มเฉพาะสไลด์ที่แก้ไขเป็นแถวใหม่ในตาราง slide และสร้างแถว version ใหม่ที่มี manifest ที่แก้ไขแล้วได้
- ตาราง
version บันทึกเวลา check-in, คอมเมนต์ของผู้ใช้ และเวอร์ชันแม่ เพื่อรักษาประวัติการเปลี่ยนแปลง
- ยังสามารถเก็บงานนำเสนอหลายชุดไว้ในเอกสารเดียวกันได้ด้วย
- หากใช้เวอร์ชันพิเศษชื่อ
pending แทนไฟล์สำรองแยกต่างหาก ก็สามารถบันทึกการเปลี่ยนแปลงที่ยังไม่ได้ save ได้บ่อย ๆ อย่างเงียบ ๆ
- เพราะเขียนเฉพาะส่วนที่เปลี่ยน ไม่ใช่เอกสารทั้งหมด งานเขียนข้อมูลจึงเป็นระดับไม่กี่ KB ไม่ใช่หลาย MB
- เวลาในการบันทึกอาจอยู่ในระดับมิลลิวินาที ไม่ใช่หลายวินาที
- แม้รีบูตหลังแครช ก็สามารถรักษางานของผู้ใช้ไว้ได้ส่วนใหญ่หรือเกือบทั้งหมด
- หากผู้ใช้ต้องการทิ้งการเปลี่ยนแปลงที่ยังไม่ได้ save ก็เพียงย้อนกลับไปเวอร์ชันก่อนหน้า
ความสามารถเพิ่มเติมที่เป็นไปได้ในฟอร์แมตไฟล์ SQLite
- คอนเทนเนอร์ SQLite สามารถเพิ่มความสามารถสำคัญให้ฟอร์แมตไฟล์ของแอปพลิเคชันได้แม้ใช้เพียงสามตาราง
- นอกจากนี้ยังสามารถใช้ schema, index, trigger, view และ constraint เพื่อเพิ่มประสิทธิภาพ ความสะดวก และความสอดคล้องได้
- แนวคิดในการขยายมีดังนี้
- เก็บ สแตก undo/redo อัตโนมัติ ไว้ในตารางฐานข้อมูล เพื่อให้ย้อนกลับไปถึง session การแก้ไขก่อนหน้าได้
- เพิ่มความสามารถ full-text search ให้ slide deck หนึ่งชุดหรือหลายชุด
- แยก
settings.xml ออกเป็นตาราง SQL เพื่อให้แอปพลิเคชันอื่นดูและแก้ไขได้ง่ายขึ้น
- แยก speaker notes ของแต่ละสไลด์ออกเป็นตารางต่างหาก เพื่อให้แอปหรือสคริปต์ third-party เข้าถึงได้ง่าย
- รองรับโครงสร้างงานนำเสนอที่มีเส้นทางและทางเบี่ยงต่างกันตามปฏิกิริยาของผู้ชม นอกเหนือจากลำดับสไลด์แบบเส้นตรงธรรมดา
ความรู้สึกต่อต้าน SQLite ที่พบบ่อยและข้อโต้แย้ง
- ประสบการณ์กับฐานข้อมูล SQL ระดับ enterprise อาจทำให้รู้สึกต่อต้านการใช้ SQLite เป็นฟอร์แมตไฟล์ของแอปพลิเคชัน
- ฐานข้อมูล enterprise จำนวนมากแนะนำว่าไม่ควรใส่ string ขนาดใหญ่หรือ BLOB ไว้ในฐานข้อมูล แต่ให้เก็บเป็นไฟล์แยก ทว่า SQLite แตกต่างออกไป
- คอลัมน์ใด ๆ ของ SQLite สามารถเก็บ string หรือ BLOB ได้ถึงประมาณ 1GB
- สำหรับ string และ BLOB ขนาดไม่เกิน 100KB ประสิทธิภาพ I/O ดีกว่า การใช้ไฟล์แยก
- ความคิดที่ว่า schema SQL ทุกแบบต้องเป็น รูปแบบปกติที่สาม (3NF) และต้องเก็บเฉพาะ primitive type ขนาดเล็ก ก็อาจเป็นข้อจำกัดได้
- ทฤษฎี relational มีความสำคัญ แต่ในฟอร์แมตไฟล์จริง การเก็บข้อมูลซับซ้อนอย่าง XML หรือ JSON ไว้ใน text field ก็เป็นตัวเลือกที่ยอมรับได้
SQLite ในฐานะฟอร์แมตไฟล์ของแอปพลิเคชัน
- ไฟล์ฐานข้อมูล SQLite มีขนาดเกือบเท่ากับ ZIP archive ที่บรรจุข้อมูลเดียวกัน และบางกรณีอาจเล็กกว่า
- ด้วย การอัปเดตแบบ atomic จึงสามารถบันทึกการเปลี่ยนแปลงเล็ก ๆ ลงในเอกสารได้อย่างปลอดภัย ลด disk I/O และเพิ่มประสิทธิภาพ File/Save
- แอปพลิเคชันสามารถอ่านเฉพาะเนื้อหาที่จำเป็นสำหรับหน้าจอแรก เพื่อลดเวลาเริ่มต้น
- สามารถเก็บเฉพาะเนื้อหาที่เกี่ยวข้องกับสิ่งที่กำลังแสดงไว้ใน memory และปล่อยส่วนที่เหลือไว้บนดิสก์ ทำให้ลดการใช้หน่วยความจำได้มาก
- schema SQL สามารถแสดงข้อมูลได้ตรงและกระชับกว่าโครงสร้าง key/value อย่าง ZIP
- แอปและสคริปต์ third-party เข้าถึงได้ดีขึ้น
- การทำฟีเจอร์ขั้นสูงอย่างการจัดการเวอร์ชันเอกสารในตัวและการกู้คืนงานหลังแครชทำได้ง่ายขึ้น
- OpenDocument เป็นฟอร์แมตที่ได้รับการยอมรับและออกแบบมาอย่างดีอยู่แล้ว อีกทั้ง SQLite เกิดขึ้นภายหลัง OpenDocument ดังนั้นเนื้อหานี้ไม่ได้เป็นการวิจารณ์ตัวเลือกเดิม
- เอกสาร Application File Format ให้แนวคิดเพิ่มเติมเกี่ยวกับการใช้ SQLite เป็นฟอร์แมตไฟล์ของแอปพลิเคชัน
1 ความคิดเห็น
ความเห็นบน Hacker News
กำลังทำแอปที่ใช้ SQLite เป็น รูปแบบไฟล์
เพราะอยากคงโฟลว์ทั่วไปที่ไฟล์จะเปลี่ยนเฉพาะตอนผู้ใช้แก้ไขเอกสารแล้วกดบันทึก จึงคัดลอกเป็นฐานข้อมูล
:memory:ตอนเปิดไฟล์: https://www.sqlite.org/inmemorydb.htmlผู้ใช้แก้ไขได้ตามต้องการ และแอปสะท้อนการเปลี่ยนแปลงลงในรูปแบบฐานข้อมูลโดยตรงโดยไม่ต้องมีโมเดลเอกสารแยกต่างหาก ตอนบันทึกก็ใช้
VACUUMเขียนกลับไปเป็นไฟล์ฐานข้อมูลอีกครั้ง: https://www.sqlite.org/lang_vacuum.htmlใช้งานได้ดีสำหรับไฟล์ขนาดพอเหมาะ และในแอปของฉันก็อยู่ในช่วงนั้นเสมอ
สู้บันทึกอัตโนมัติลงฐานข้อมูลโดยตรงแล้วตัดปุ่มบันทึกออกไปเลยจะดีกว่า ทนต่อการชนกันได้ดี มีฐานข้อมูลเดียวทำให้โค้ดและบั๊กน้อยลง และการเขียนของ SQLite ก็มีแค่สำเร็จหรือไม่สำเร็จ ไม่มีสถานะค้างกลางทาง ในทางกลับกัน ตามที่เอกสารที่อ้างถึงระบุไว้
VACUUM INTOอาจทำให้ฐานข้อมูลเอาต์พุตไม่สมบูรณ์หรือเสียหายได้หากโปรแกรมปิดแบบไม่คาดคิดหรือไฟดับถ้าใช้ SQLite ตามวิธีที่มันตั้งใจไว้แต่แรก ก็ไม่ต้องกังวลเรื่องส่วนนี้ตลอดอายุการใช้งานของ SQLite
การบันทึกไว้ในตำแหน่งชั่วคราวหลังแต่ละการทำงาน เช่น ตามไดเรกทอรี XDG อย่าง
~/.local/share/application/yourappแล้วเมื่อผู้ใช้กดบันทึกค่อยคัดลอกไฟล์ไปยังตำแหน่งที่ต้องการ จะดีกว่ามาก หลังไฟดับแล้วเปิดแอปใหม่ก็จะกู้คืนได้เกือบถึงจุดเดิม และอาจเสียไปแค่ไม่กี่วินาทีสุดท้ายเท่านั้นเมื่อผู้ใช้บันทึก ก็ทำ checkpoint เพื่อรวมเนื้อหาใน WAL เข้ากับฐานข้อมูลหลักก็พอ
VACUUMจะคัดลอกเนื้อหาไปยังไฟล์ฐานข้อมูลชั่วคราวก่อน แล้วเขียนทับไฟล์ต้นฉบับ และตอนเขียนทับจะใช้ rollback journal หรือ WAL เหมือนทรานแซกชันทั่วไป ดังนั้นจึงต้องมีพื้นที่ว่างประมาณไม่เกินสองเท่าของไฟล์ต้นฉบับVACUUM INTOจะใช้ไฟล์ที่ระบุในINTOแทนฐานข้อมูลชั่วคราว และข้ามขั้นตอนคัดลอกกลับไปทับต้นฉบับ สิ่งสำคัญคือของที่ใช้อยู่จริงเป็นVACUUMที่ทนต่อการตัดไฟได้ หรือเป็นVACUUM INTOที่เปราะบางต่อไฟดับระหว่างเขียนและถ้าใช้ชื่อไฟล์เดิมก็ดูเหมือนอาจทำให้ไฟล์เสียหายได้ปัญหาของ SQLite คือมันไม่ใช่ รูปแบบไฟล์มาตรฐาน
มันมีเอกสารดีและเป็นที่เข้าใจกันกว้างขวาง แต่ไม่มีมาตรฐาน ISO ที่นิยามอย่างละเอียดว่าต้องตีความไฟล์ SQLite อย่างไร การทำ implementation ทดแทนก็เช่นกัน
Zip และ XML มีพื้นผิว API เล็กกว่า SQLite มาก API ของ SQLite ไม่ได้มีแค่ฟังก์ชัน C ไม่กี่ตัว แต่รวมถึงภาษา SQL เองด้วย และการ implement parser ของ SQL, query optimizer, compiler, bytecode virtual machine, full-text search engine ฯลฯ โดยไม่ทำให้ข้อมูลเสียหาย เป็นงานที่ใหญ่กว่า XML parser มาก
ถ้าเป็นแอปปิดเฉพาะโดเมนที่ไม่ให้ความสำคัญกับ interoperability หรือมาตรฐาน ISO SQLite ก็เป็นรูปแบบไฟล์ที่ดี แต่เข้าใจว่าใน OpenOffice เคยมีความกังวลแบบนั้นจริง ๆ
ไลบรารี SQLite C ก็เป็น public domain ซอร์สเปิดทั้งหมด จัดการรูปแบบไฟล์ได้ และมีระดับเอกสารดีกว่ามาตรฐาน ISO ส่วนใหญ่ด้วยซ้ำ binding สำหรับภาษาหลัก ๆ ก็มีแทบครบ
ถ้าปัญหาคือรูปแบบ OpenDocument บางอย่างที่จะถูกเก็บไว้ในไฟล์ SQLite ยังต้องถูกสร้างและจัดทำเอกสารขึ้นมาก่อน นั่นก็เป็นอีกเรื่องหนึ่ง มาตรฐาน ISO เป็นสิ่งที่ดี แต่ถ้าต้องรอให้ ISO นิยามรูปแบบไฟล์ก่อน เราคงมีของให้ใช้ได้น้อยเกินไปมาก
ก็เหมือนกับที่ไม่จำเป็นต้อง implement ฟังก์ชันสเปรดชีตทั้งหมดเพื่ออ่านสเปรดชีตของ LibreOffice สิ่งที่ต้องมีคือความสามารถในการประกอบตารางกลับขึ้นมา และหลังจากนั้นข้อมูลที่ต้องการก็ไล่ดูได้ด้วยโค้ด imperative ที่เขียนในภาษาที่เลือก
การ implement ไลบรารีนั้นใหม่คงเป็นเรื่องใหญ่ แต่ก็เป็นงานประเภทเดียวกับการ implement โค้ดที่ใช้รูปแบบไฟล์ OpenDocument ใหม่ ตัวรูปแบบไฟล์เองค่อนข้างเรียบง่าย
ถ้ากังวลเรื่องความเข้ากันได้ ก็ทำให้เอกสารสามารถเข้าถึงผ่านฐานข้อมูลอื่นอย่าง MySQL ได้ด้วยก็ได้
ตอนแรกคิดว่าเมื่อ Audacity หันมาใช้ SQLite ฟีเจอร์การบันทึกไฟล์จะดีขึ้นมาก แต่ในทางปฏิบัติมีหลุมพรางเยอะ
บน Linux ถ้าบันทึกเป็นไฟล์ใหม่ลงในเมานต์ NTFS ที่ root เป็นเจ้าของแต่ทุกคนเขียนได้ ซึ่งสร้างไว้ด้วย
/etc/fstabจะล้มเหลวด้วยเหตุผลอย่างข้อผิดพลาดเรื่องสิทธิ์ แต่ถ้าบันทึกทับไฟล์เดิมจะทำงานปกติทันทีที่เริ่มแก้ไขโปรเจกต์ ไฟล์บนดิสก์ก็ถูกแก้ไข ทำให้ถ้าใส่โปรเจกต์ Audacity ไว้ใน Git ในรูปก้อนข้อมูลไบนารี จะเกิด diff ของ Git ที่ไม่จำเป็น แม้จะบันทึกแล้ว ข้อมูลเก่าหรือข้อมูลที่ลบไปก็ยังค้างอยู่ในไฟล์ SQLite จนกว่าจะปิดหน้าต่างโปรเจกต์ ดังนั้นถ้าไม่ปิดหน้าต่างก่อน commit มันอาจเข้าไปอยู่ใน repository ได้ เท่าที่จำได้ เมื่อก่อนต้อง
VACUUMไฟล์.aup3เอง แต่ตอนนี้แค่ปิดหน้าต่างก็พอ ให้ความรู้สึกเหมือน Fast Save ของ Word 2003ตอนที่โปรเจกต์ซึ่งควรมีขนาดไม่กี่ร้อย MB กลายเป็นหลาย GB และต้องประหยัดพื้นที่ดิสก์ สำหรับงานแทร็กเดียวแบบง่าย ๆ Mix and Render คือทางออก มันไม่เปลี่ยนเสียง แต่ช่วยเก็บกวาดเศษข้อมูลตอนบันทึกและปิดได้
เรื่องนี้ไม่ใช่ปัญหาของ SQLite เอง แต่เป็นปัญหาระดับแอปพลิเคชันอย่างชัดเจน Audacity 2 ดูเหมือนจะมีแนวคิดเรื่องพื้นที่ทำงานชั่วคราว แต่ Audacity 3 ดูเหมือนจะใช้ไฟล์
.aup3เองเป็นพื้นที่ทำงานเคยลองดูรูปแบบของ Audacity 3 แล้วพบว่า แม้จะเก็บข้อมูลโปรเจกต์ที่เทียบเท่ากับไฟล์
.aupเดิมไว้เป็น XML ในตารางแถวเดียว แต่กลับไม่ได้เขียนเป็นข้อความตรง ๆ และเข้ารหัสด้วยตัวเข้ารหัสพจนานุกรมแบบง่าย ๆ ซึ่งน่างงมาก ทำให้การทำงานร่วมกันและการตรวจสอบยากขึ้นมาก มีผลเสียต่อประสิทธิภาพเล็กน้อย และการประหยัดพื้นที่ก็คงเป็นแค่ระดับไม่กี่ KB ซึ่งเป็นความคลาดเคลื่อนจากการปัดเศษเมื่อเทียบกับไฟล์เสียงขนาดหลายร้อย MBจากมุมมองของ Git การใช้รูปแบบข้อความที่ diff และ merge ได้ง่ายจะได้เปรียบกว่า ไม่รู้ว่า dump ของ SQLite จะง่ายในแง่นี้แค่ไหน
ถ้าสำคัญจริง ๆ ก็พอจะแก้ด้วยมือได้ แต่ปกติแค่ทิ้งไฟล์นั้นไปก็กลับมาใช้ได้
เป็นบทความที่ดี อย่างไรก็ตาม ผมชอบตรงที่ OpenDocument เป็นชุดไฟล์ XML ภายใน Zip archive
สามารถสร้างเอกสารอย่างสเปรดชีตได้ค่อนข้างง่าย แม้ไม่มีไลบรารีหนัก ๆ ที่เข้าใจรูปแบบเอกสาร
บางครั้งผู้ใช้เว็บเซอร์วิสต้องการใช้ข้อมูลที่ส่งออกเป็นแถวตารางกับเครื่องมือหลายแบบ UTF-8 CSV เปิดกว้าง เป็นธรรมเนียมที่ใช้กัน และพอใช้ได้ แต่ใครที่เคยให้ CSV กับผู้ใช้ปลายทางคงรู้ดีถึงความเจ็บปวดเมื่อไปติดกับแอปสเปรดชีต
ผมบันทึกสเปรดชีตตัวอย่างเป็น ODS ของ OpenDocument และ XLSX ของ OOXML ซึ่งเป็นสัตว์ประหลาด XML ของ Microsoft แล้วทำความเข้าใจพื้นฐานของรูปแบบ XML เท่านั้น จากนั้นลด Zip archive ให้เหลือเฉพาะส่วนจำเป็น ทำเครื่องหมายตำแหน่งที่จะใส่เนื้อหาไว้ และสร้างไฟล์สเปรดชีตใหม่เมื่อมี request ตอนนี้จึงส่งออกข้อมูลเดียวกันได้เป็น CSV, ODS, XLSX และ JSON
SQLite ก็ทำได้ แต่จะซับซ้อนกว่าเล็กน้อยและทำให้พัฒนาช้าลง การที่สามารถสร้างเอกสารเทมเพลตด้วยชุดโปรแกรมออฟฟิศแล้วขุดเข้าไปใน XML ของไฟล์ที่บันทึกได้ เป็นฟีเจอร์เฉพาะทางแต่ดี
โดยเฉพาะ Excel ใน locale อย่าง
nl_NLมีปัญหา เพราะมันทำเหมือน hardcode ว่าตัวคั่นคอลัมน์ของไฟล์ CSV คือเซมิโคลอน เนื่องจาก Microsoft ตัดสินใจอย่างฉาวโฉ่วว่าชาวดัตช์ไม่ใช้จุลภาคในไฟล์ comma separated valueslocaleconv()->decimal_pointถ้าค่าเป็น,Excel จะใช้ เซมิโคลอน ทั้งในไฟล์ CSV และภาษาสำหรับเขียนสูตรเมื่อก่อนตอนเปิด CSV/TXT ใน Excel สามารถตั้งค่าได้ และใน LibreOffice ยังทำได้อยู่ แต่ในกระบวนการทำให้ UI เรียบง่ายขึ้นโดยรวม มันถูกย้ายไปอยู่ที่ไหนสักแห่งในเมนู/แท็บริบบอน
Dataต้องเปิดเวิร์กบุ๊กใหม่แล้วหา option ที่ถูกต้อง และถ้าอยากประหยัดเวลา ใช้ LibreOffice จะดีกว่าส่วนนี้น่าทึ่งจริง ๆ ไม่น่าเชื่อว่าไม่ต้องใช้ nested query
SELECT manifest, versionId, max(checkinTime) FROM version;ว่ากันว่าใน SQLite query ที่สองนี้ซึ่งใช้
max(checkinTime)ทำงานได้จริงและคืนคำตอบที่กำหนดไว้ชัดเจน ในเอนจินฐานข้อมูล SQL อื่น ๆ จะคืนคำตอบที่ไม่กำหนดหรือเกิดข้อผิดพลาด แต่ใน SQLite จะคืนmanifestและversionIdของรายการที่มีcheckinTimeสูงสุดกรณีนี้ไม่จำเป็นต้องใช้ nested query แค่เรียงตาม
checkinTimeแล้วจำกัดให้เหลือหนึ่งรายการก็พอ:select manifest, versionId, checkinTime from version order by checkinTime desc limit 1อย่างน้อยก็น่าจะใช้ได้ใน SQLite และ PostgreSQL ส่วน Oracle เท่าที่จำได้ต้องใช้
where rownum=1เลยเคยต้องใช้ nested queryGROUP BY manifest, versionId ORDER BY 3 DESC LIMIT 1หรือ CTE ที่หาcheckinTimeสูงสุดแล้ว join กลับอย่างไรก็ตาม ถ้ามีหลายแถวที่มีค่า
checkinTimeสูงสุดเท่ากัน ก็มีความสุ่มอยู่ จึงอาจกลายเป็น ปืนจ่อเท้าตัวเอง ได้ เลยไม่ค่อยใช้ฟีเจอร์เฉพาะของ SQLite3 แบบนี้ ถ้าต้องเลือกแถวที่ดีที่สุดอย่างกำหนดแน่นอน ต้องใช้วิธีที่ชัดเจนคล้ายกับข้างบนใน Postgres สามารถทำสิ่งคล้ายกันได้ด้วย query
DISTINCT ONเป็นหนึ่งในงานที่ดูเรียบง่ายใน SQL แต่ผมรู้สึกว่ายากที่สุดงานหนึ่งmanifestและversionIdไม่ได้ ขึ้นต่อเชิงฟังก์ชัน กับmax(checkinTime)เช่น อาจมีสองแถวที่มีค่า
checkinTimeเดียวกัน และค่านั้นเป็นค่าสูงสุดเคยเปิดตัวผลิตภัณฑ์ที่ใช้ทั้ง SQLite และไฟล์ XML
หนึ่งในส่วนที่ปรับปรุงคือการย้ายบางตารางที่มีข้อมูลน้อยไปไว้ในไฟล์ XML เนื่องจากไฟล์มีขนาดเล็กและแทบไม่ถูกใช้งาน ทำให้เลเยอร์การเข้าถึงข้อมูลและการวินิจฉัยปัญหาง่ายขึ้น และทำเป็น XML แบบหลายบรรทัดพร้อมการเยื้องด้วยแท็บ
การขอให้เจ้าหน้าที่เทคนิคที่ต้องวินิจฉัยผลิตภัณฑ์เปิดดูฐานข้อมูล SQLite ถือเป็นภาระค่อนข้างมาก แต่ในส่วนหลักของผลิตภัณฑ์ SQLite เหนือกว่าไฟล์ XML อย่างท่วมท้น เวอร์ชันก่อนหน้าใช้ไฟล์ XML แต่ไฟล์ XML ไม่มีวิธีที่ดีสำหรับการ อัปเดตแบบ incremental จึงมีปัญหาด้าน scalability
ข้อดีของ XML ที่มนุษย์อ่านง่ายจะทำงานได้ดีจริงก็ต่อเมื่อไฟล์มีขนาดเล็กและการออกแบบ schema ถูกปรับให้เหมาะกับ XML ที่อ่านง่ายเท่านั้น การต้องเขียนไฟล์ XML ทั้งหมดใหม่ทุกครั้ง รวมถึงความซับซ้อนที่เกิดขึ้นเมื่อมีฟีเจอร์มากขึ้น บั่นทอนข้อดีที่ใหญ่ที่สุดของ XML อย่างรวดเร็ว
กรณีที่ผู้ใช้ทั่วไปจำเป็นต้องแก้ไขภายในเอกสารออฟฟิศโดยตรงนั้นพบได้น้อยพอสมควร ดังนั้นการเรียนรู้วิธีใช้ SQLite reader จึงเป็นอุปสรรคในการเริ่มต้นที่ยอมรับได้ ข้อจำกัดของ XML+Zip ในการเขียนแบบสุ่มตรงกลางไฟล์นั้น แม้แต่กฎของมัวร์ก็เอาชนะไม่ได้
TEXTหรือBLOBของ SQLite ถูกบีบอัดหรือไม่ หรือถือว่าผู้เรียกจะบีบอัด BLOB ก่อนเขียนเองODT ถูกออกแบบมาโดยคำนึงถึงการทำให้เป็นมาตรฐาน รูปแบบก่อนหน้าก็คล้ายกันมาก แต่พึ่งพามาตรฐานเดิมอย่าง XHTML, SVG, CSS อย่างมาก
หากไม่สามารถอ้างอิงมาตรฐานเดิมได้ ข้อกำหนดของ ODT เองก็คงจะใหญ่โตขึ้นทันที ความพยายามในการปรับปรุงมาตรฐานก็ดูไม่น้อย และในช่วงไม่กี่ปีที่ผ่านมาก็แทบไม่มีความคืบหน้า
ในทางปฏิบัติ รูปแบบ SQLite อาจนำเสนอเป็นตัวเลือกได้ แต่ดูเหมือนว่าเรือของรูปแบบเอกสารออฟฟิศได้ออกจากท่าไปแล้ว อย่างไรก็ดี นี่เป็นเหตุผลสนับสนุนที่ดีต่อข้อเสนอให้จัดทำข้อกำหนด SQLite เป็น มาตรฐานทางการ
หากไม่นับข้อบกพร่องบางอย่าง เช่น ปัญหาที่แอตทริบิวต์
ooo:rsidทำให้ local style และช่วงข้อความเพิ่มจำนวนพรวดพราด, สเปรดชีตที่ไม่ sparse, และกลไกแปลก ๆ ของการจัดสไตล์ตาราง นี่เป็นมาร์กอัปที่ออกแบบมาดีมากสำหรับข้อมูลเอกสารแบบนี้ มีสมดุลที่ดีระหว่าง semantic markup กับการแสดงผลที่ผู้ใช้ต้องการทำจริงในทางกลับกัน Office OpenXML มีแท็กว่างสำหรับการจัดรูปแบบที่มีสถานะ และใน DOCX มันจะ toggle ว่าข้อความถัดไปจะแสดงเป็นตัวหนาหรือไม่
การผูกฟอร์แมตไฟล์เข้ากับ SQLite มันให้ความรู้สึกเหมือนมีอะไรไม่ถูกต้อง
SQLite นั้นดี แต่ในพื้นที่นี้ค่อนข้างมีลักษณะเฉพาะ เพราะมันทำหลายอย่างมาก จึงยากที่จะลอกเลียนแบบได้ตรง ๆ
แต่ในกรณีนี้ ถ้าถามว่าจำเป็นต้องมีฟังก์ชันมากขนาดนั้นไหม ก็ไม่จำเป็น ต้องการแค่ semantics ของทรานแซกชันที่พื้นฐานและปลอดภัย กับความสามารถในการเก็บโครงสร้างตารางแบบง่าย ๆ เท่านั้น ไม่จำเป็นถึงขั้นมาตรฐาน SQL ทั้งหมดหรือ query optimizer
อาจมีฟอร์แมตไฟล์ที่ดีกว่านี้ได้ แต่ถ้าเป็น ฟอร์แมตที่แยกจาก SQLite ก็น่าจะดีกว่า
ขนาดต่ำกว่า 1MB และ https://sqlite.org/footprint.html แม้เปิดฟีเจอร์ทั้งหมดก็ 750KB: https://www.sqlite.org/about.html
ตอนคอมไพล์สามารถตัดฟีเจอร์ออกได้มากพอสมควร และดูเหมือนจะมีตัวเลือกสำหรับปรับหรือย่อ query planner ด้วย: https://www.sqlite.org/compile.html
แถมยังมีคำกล่าวว่า “SQLite ไม่ได้แข่งขันกับฐานข้อมูลแบบ client/server แต่ SQLite แข่งขันกับ
fopen()”: https://www.sqlite.org/whentouse.htmlสุดท้ายสิ่งที่ต้องการไม่ใช่ตัวฐานข้อมูลเอง แต่เป็นไลบรารีที่ให้ API และพฤติกรรมแบบฐานข้อมูล
SQLITE_BUSYค่อนข้างลำบากผมรู้ว่าในการจัดการทรานแซกชัน ความล้มเหลวจาก serialization เป็นสิ่งที่คาดได้ แต่ใน SQLite แยกความล้มเหลวถาวรอย่างภาวะ deadlock กับตัวเอง ออกจากปัญหาการอัปเดตพร้อมกันแบบชั่วคราวได้ยาก ถ้าเป็นความล้มเหลวชั่วคราว ก็แค่รัน closure ที่นิยามงานของทรานแซกชันใหม่ แต่ถ้าเป็นความล้มเหลวถาวร ก็ไม่มีความหมาย
ส่วนหนึ่งของปัญหาคือ
sqlite3_stmtรวมลักษณะของทั้ง prepared statement และ result set ไว้ด้วยกัน เรามักถือมันไว้นานเพื่อแคช bytecode ที่คอมไพล์แล้ว แต่ถ้าหยุดกลางการวนซ้ำ มันอาจถือ lock อยู่ ณ จุดนั้น ส่งผลให้เกิดความล้มเหลวในการอัปเกรด lock ที่ไม่คาดคิดได้สุดท้ายผมใช้
sqlite3_next_stmt,sqlite3_stmt_busy,sqlite3_sqlทำรายงานข้อผิดพลาดแบบละเอียดแล้วกำจัดปัญหาไปได้ แม้เป็นงานใช้ส่วนตัว โค้ด retry ทรานแซกชันก็ยังเต็มไปด้วย logging แบบเลือกเปิดได้และคอมเมนต์ ส่วน logic retry ทรานแซกชันสำหรับ PostgreSQL ง่ายกว่ามากอีกเรื่องที่ทำให้ประหลาดใจคือเอกสารระบุว่าในโหมด WAL ที่
synchronous=NORMALทรานแซกชันที่ commit แล้วอาจถูก rollback หลังไฟดับหรือระบบ crash ได้: https://sqlite.org/pragma.html#pragma_synchronousสำหรับแอปพลิเคชันของผมไม่เกี่ยวข้อง
ถ้า Richard Hipp และบริษัทแสดงให้เห็นมาตรฐาน SQLite แบบ ISO/IEC/ANSI/ETSI ที่จะไม่หลุดออกนอกกรอบอย่างเด็ดขาด, การตรวจสอบทางกฎหมายว่าไม่มีสิทธิบัตรใด ๆ ที่จะส่งผลกระทบ, และ implementation SQLite ที่เข้ากันได้หลายตัวโดยยังรักษาข้อดีทั้งหมดไว้ได้ ถึงตอนนั้นค่อยคุยกันเรื่องการแนะนำให้ใช้เป็นฟอร์แมตไฟล์ มิฉะนั้นก็เท่ากับบอกให้ผูกพึ่งพา implementation จากแหล่งเดียวอย่างหนัก แล้วผลักภาระนั้นไปให้ผู้ใช้ด้วย
XML, ASN.1, JFIF เป็นมาตรฐานทางการ และ ZIP ก็เป็นมาตรฐานทางการที่ถูกนำมาใช้เป็น ISO/IEC 21320-1:2015 ในกระบวนการทำมาตรฐาน OpenDocument
สิ่งสำคัญที่สุดของเอกสารคือทุกคนอื่นต้องอ่านได้ การลดเวลาอัปเดตดิสก์เป็นเรื่องรอง เราไม่ควรไม่ได้เรียนรู้อะไรจากกรณีที่ Microsoft พยายามบิดเบือนองค์กรกำหนดมาตรฐานเพื่อรักษาการผูกขาด: https://arstechnica.com/uncategorized/2008/10/norwegian-standards-body-implodes-over-ooxml-controversy/
มันคงไม่ใช่ตัวเลือกที่ผิดได้ขนาดนั้น
อีกตัวอย่างคือไทล์แผนที่แบบ raster จริง ๆ แล้วมันคือรูปภาพสี่เหลี่ยมเล็ก ๆ ที่อาจมีได้ถึงหลักล้านไฟล์
ผมลองทั้ง Zip, tar, file system และ SQLite แล้ว SQLite เร็วที่สุดและเล็กที่สุด ดีกว่า archive ปกติที่ไม่มี overhead เสียอีก
SQLite มีข้อเสียใหญ่ BLOB ที่ได้จากฐานข้อมูลไม่สามารถ
mmapได้ จึงต้องคัดลอกไปที่อื่น ส่วนไฟล์ Zip ถ้าไม่ได้บีบอัด หรือถูกบีบอัดด้วย encoding แปลก ๆ อย่าง PVRTC ก็สามารถmmapได้ตรง ๆOpenDocument คือรูปภาพที่ถูกบีบอัดกับ XML กล่าวคือสุดท้ายก็หมายถึงต้องพาร์สรูปแบบทั้งหมดแล้วโหลดขึ้นหน่วยความจำ
ไม่ค่อยแน่ใจว่า SQLite จะปรับปรุงเรื่องนี้ได้อย่างไร XML อาจไม่ใช่อุดมคติ แต่ถูกบีบอัดด้วย Zip อยู่แล้ว จึงไม่ได้มีโทษด้านขนาดมากนัก
ข้อดีทั้งหมดที่บทความ SQLite ยกมา สามารถทำได้หากใช้ SQLite เป็นโมเดลรันไทม์ของเอกสาร ทำได้ทั้งบนดิสก์และในหน่วยความจำ แต่ SQLite ไม่จำเป็นต้องเป็น รูปแบบสำหรับการส่งต่อ
ตรงกันข้าม SQLite อาจมีขนาดใหญ่กว่ารูปแบบปัจจุบันด้วยซ้ำ เมื่อผ่านการแก้ไขจะเกิดพื้นที่ที่ไม่ได้ใช้ แตกกระจาย และอาจกลายเป็นไฟล์โปร่งได้ หากต้อง optimize ทุกครั้ง ข้อดีอย่างการบันทึกอย่างรวดเร็วก็จะหายไป
รูปแบบที่ต้องการเดลตาอัปเดตและการค้นหาดัชนีอย่างรวดเร็ว และไม่ควรโหลดไฟล์ทั้งก้อนขึ้นหน่วยความจำ จริง ๆ แล้วมักใช้ SQLite เป็นรูปแบบไฟล์กันมาก เพียงแต่รู้สึกว่า OpenDocument เป็นตัวอย่างที่เลือกมาไม่ดีสำหรับการเล็งใช้ SQLite ในสถานการณ์สมมตินี้
หากใช้ SQLite เป็นรูปแบบบนดิสก์และแอปพลิเคชันถูกพัฒนาอย่างถูกต้อง ก็อาจไม่จบลงในสถานะที่เสียหาย
XML/Zip ก็อาจทำให้ได้คล้ายกันด้วยทริกการเปลี่ยนชื่อไฟล์ แต่ SQLite ให้สิ่งนั้นภายในไฟล์ดิสก์เดี่ยวอยู่แล้ว หากใช้ SQLite เป็นโมเดลในหน่วยความจำอยู่แล้ว ก็ไม่มีเหตุผลที่จะไม่ใช้เป็นรูปแบบบนดิสก์/สำหรับการส่งต่อด้วย ณ จุดนั้นแทบจะได้มาฟรี ๆ
ปัญหาเรื่องขนาดไฟล์น่าจะจัดการได้ด้วย
VACUUM