ฐานข้อมูลเก็บค่าคีย์ดัชนี (บนดิสก์) สำหรับฟิลด์ความยาวผันแปรได้อย่างไร


16

บริบท

คำถามนี้เกี่ยวข้องกับรายละเอียดการใช้งานระดับต่ำของดัชนีในระบบฐานข้อมูล SQL และ NoSQL โครงสร้างที่แท้จริงของดัชนี (B + tree, hash, SSTable และอื่น ๆ ) นั้นไม่เกี่ยวข้องเนื่องจากคำถามนั้นเกี่ยวข้องกับคีย์ที่เก็บอยู่ในโหนดเดียวของการใช้งานเหล่านั้น

พื้นหลัง

ใน SQL (เช่น MySQL) และ NoSQL (CouchDB, MongoDB, ฯลฯ ) ฐานข้อมูลเมื่อคุณสร้างดัชนีในคอลัมน์หรือสาขาเอกสาร JSON ของข้อมูลสิ่งที่คุณกำลังจริงทำให้เกิดฐานข้อมูลในการทำคือการสร้างหลักรายการที่เรียงลำดับทั้งหมด ค่าเหล่านั้นพร้อมกับไฟล์ออฟเซ็ตลงในไฟล์ข้อมูลหลักที่บันทึกที่เกี่ยวข้องกับค่านั้นอยู่

(เพราะความเรียบง่ายฉันอาจโบกมือให้รายละเอียดที่ลึกลับอื่น ๆ ของความหมายเฉพาะ)

ตัวอย่าง SQL แบบง่าย ๆ

พิจารณาตาราง SQL มาตรฐานที่มีคีย์หลักแบบง่าย 32 บิตที่เราสร้างดัชนีในเราจะจบลงด้วยดัชนีบนดิสก์ของคีย์จำนวนเต็มเรียงและเชื่อมโยงกับออฟเซต 64 บิตลงในไฟล์ข้อมูลที่ บันทึกชีวิตเช่น:

id   | offset
--------------
1    | 1375
2    | 1413
3    | 1786

การเป็นตัวแทนบนดิสก์ของคีย์ในดัชนีมีลักษณะดังนี้:

[4-bytes][8-bytes] --> 12 bytes for each indexed value

ยึดติดกับกฎมาตรฐานทั่วไปเกี่ยวกับการเพิ่มประสิทธิภาพดิสก์ I / O ด้วยระบบไฟล์และระบบฐานข้อมูลสมมติว่าคุณเก็บคีย์ในบล็อก 4KB บนดิสก์ซึ่งหมายความว่า:

4096 bytes / 12 bytes per key = 341 keys per block

ไม่สนใจโครงสร้างโดยรวมของดัชนี (B + tree, hash, รายการที่เรียงลำดับเป็นต้น) เราอ่านและเขียนบล็อกของ 341 คีย์ในเวลาหนึ่งไปยังหน่วยความจำและกลับออกไปยังดิสก์ตามต้องการ

แบบสอบถามตัวอย่าง

การใช้ข้อมูลจากส่วนก่อนหน้าสมมติว่ามีคิวรีสำหรับ "id = 2" การค้นหาดัชนี DB แบบคลาสสิกจะเป็นดังนี้:

  1. อ่านรากของดัชนี (ในกรณีนี้ 1 บล็อก)
  2. ค้นหาไบนารี่บล็อกที่เรียงลำดับเพื่อค้นหาคีย์
  3. รับไฟล์ข้อมูลออฟเซ็ตจากค่า
  4. ค้นหาเรคคอร์ดในไฟล์ข้อมูลโดยใช้อ็อฟเซ็ต
  5. ส่งคืนข้อมูลไปยังผู้โทร

ตั้งค่าคำถาม ...

ตกลงนี่คือที่ที่คำถามมารวมกัน ...

ขั้นตอนที่ # 2 เป็นส่วนที่สำคัญที่สุดที่ช่วยให้แบบสอบถามเหล่านี้สามารถดำเนินการในเวลา O (logn) ... ข้อมูลจะต้องมีการเรียงลำดับแต่คุณจะต้องสามารถข้ามรายการในลักษณะการเรียงลำดับอย่างรวดเร็ว ... เพิ่มเติม โดยเฉพาะคุณต้องสามารถข้ามไปยังออฟเซ็ตที่กำหนดไว้อย่างดีเพื่ออ่านในค่าคีย์ดัชนีที่ตำแหน่งนั้น

หลังจากอ่านในบล็อกคุณจะต้องสามารถข้ามไปยังตำแหน่งที่ 170 ได้ทันทีอ่านค่าคีย์และดูว่าสิ่งที่คุณกำลังมองหาคือ GT หรือ LT ตำแหน่งนั้น (และต่อไปเรื่อย ๆ ... )

วิธีเดียวที่คุณจะสามารถกระโดดไปรอบ ๆ ข้อมูลในบล็อกแบบนั้นก็คือถ้าขนาดของคีย์มีค่าที่กำหนดไว้อย่างดีเช่นตัวอย่างของเราด้านบน (4 ไบต์และ 8 ไบต์ต่อคีย์)

คำถาม

ตกลงดังนั้นนี่คือที่ฉันติดกับการออกแบบดัชนีที่มีประสิทธิภาพ ... สำหรับคอลัมน์ varchar ในฐานข้อมูล SQL หรือเฉพาะเจาะจงมากขึ้นเขตข้อมูลแบบฟรีทั้งหมดในฐานข้อมูลเอกสารเช่น CouchDB หรือ NoSQL ที่เขตข้อมูลใด ๆ ที่คุณต้องการดัชนีสามารถใด ๆ ความยาวว่าคุณจะใช้ค่าที่สำคัญที่อยู่ในบล็อกของโครงสร้างดัชนีคุณสร้างดัชนีของคุณออกจาก?

ตัวอย่างเช่นสมมติว่าคุณใช้ตัวนับตามลำดับสำหรับ ID ใน CouchDB และคุณกำลังทำดัชนีทวีต ... คุณจะมีค่าที่เปลี่ยนจาก "1" ถึง "100,000,000,000" หลังจากนั้นไม่กี่เดือน

สมมติว่าคุณสร้างดัชนีในฐานข้อมูลในวันที่ 1 เมื่อมีเพียงทวีต 4 ตัวในฐานข้อมูล CouchDB อาจถูกล่อลวงให้ใช้โครงสร้างต่อไปนี้สำหรับค่าคีย์ภายในบล็อกดัชนี:

[1-byte][8-bytes] <-- 9 bytes
4096 / 9 = 455 keys per block

ในบางจุดการหยุดพักนี้และคุณจำเป็นต้องมีจำนวนตัวแปรไบต์เพื่อเก็บค่าคีย์ของคุณในดัชนี

ประเด็นนี้น่าสนใจยิ่งขึ้นถ้าคุณตัดสินใจที่จะจัดทำดัชนีฟิลด์ความยาวผันแปรที่แท้จริงเช่น "tweet_message" หรือบางอย่าง

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

แน่นอนถ้าคีย์ของคุณเป็นตัวแปรขนาดและคุณอ่านในบล็อกของคีย์ที่ไม่เพียง แต่คุณมีความคิดว่ากุญแจจำนวนมากที่ไม่มีจริงในบล็อก แต่คุณมีความคิดว่าจะกระโดดไปที่ตรงกลางของรายการที่จะทำไบนารีไม่มี ค้นหาพวกเขา

นี่คือสิ่งที่ฉันได้รับทั้งหมดสะดุด

ด้วยฟิลด์ที่พิมพ์แบบคงที่ในฐานข้อมูล SQL แบบคลาสสิก (เช่น bool, int, char, ฯลฯ ) ฉันเข้าใจว่าดัชนีสามารถกำหนดความยาวของคีย์ได้ล่วงหน้าและติดกับมัน ... แต่ในโลกของแหล่งข้อมูลเอกสารนี้ฉัน งงงวยว่าพวกเขากำลังสร้างแบบจำลองข้อมูลนี้บนดิสก์อย่างมีประสิทธิภาพเช่นนั้นยังสามารถสแกนในเวลา O (logn) และจะขอบคุณการชี้แจงใด ๆ ที่นี่

โปรดแจ้งให้เราทราบหากต้องการคำชี้แจงใด ๆ !

อัปเดต (คำตอบของ Greg)

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

ฉันได้ดูเป็น 3 แยกเป็นสัดส่วนการใช้งาน DBMS (CouchDB, kivaloo และ InnoDB) และทั้งหมดของพวกเขาจัดการกับปัญหานี้โดย deserializing บล็อกทั้งหมดในโครงสร้างข้อมูลภายในก่อนที่จะค้นหาค่าภายในสภาพแวดล้อมการดำเนินการของพวกเขา (Erlang / C)

นี่คือสิ่งที่ฉันคิดว่ายอดเยี่ยมมากสำหรับข้อเสนอแนะของเกร็ก ขนาดบล็อกปกติของ 2048 ปกติจะมี 50 หรือน้อยกว่า offsets ส่งผลให้ตัวเลขบล็อกขนาดเล็กมากที่จะต้องอ่านมา

อัปเดต (ข้อเสียที่เป็นไปได้สำหรับข้อเสนอแนะของ Greg)

เพื่อที่จะดำเนินการโต้ตอบกับตัวเองได้ดีที่สุดนี้ฉันได้ตระหนักถึงข้อเสียต่อไปนี้ ...

  1. หาก "บล็อก" ทุกตัวมีข้อมูลตรงข้ามคุณไม่สามารถอนุญาตให้ปรับขนาดบล็อกในการกำหนดค่าในภายหลังบนถนนเนื่องจากคุณอาจสิ้นสุดการอ่านข้อมูลที่ไม่ได้เริ่มต้นด้วยส่วนหัวอย่างถูกต้องหรือบล็อกที่ มีหลายส่วนหัว

  2. หากคุณกำลังจัดทำดัชนีค่าคีย์ขนาดใหญ่ (บอกว่ามีคนพยายามจัดทำดัชนีคอลัมน์ของถ่าน (8192) หรือหยด (8192)) เป็นไปได้ที่คีย์ไม่พอดีในบล็อกเดียวและจำเป็นต้องล้นผ่านบล็อกสองด้านข้างกัน . ซึ่งหมายความว่าบล็อกแรกของคุณจะมีส่วนหัวออฟเซ็ตและบล็อกที่สองจะเริ่มต้นทันทีด้วยข้อมูลสำคัญ

วิธีแก้ปัญหาทั้งหมดนี้คือการมีขนาดบล็อกฐานข้อมูลคงที่ซึ่งไม่สามารถปรับได้และพัฒนาโครงสร้างข้อมูลบล็อกส่วนหัวรอบ ๆ ... ตัวอย่างเช่นคุณแก้ไขขนาดบล็อกทั้งหมดเป็น 4KB (โดยทั่วไปจะเป็นขนาดที่เหมาะสมที่สุด) และเขียนขนาดเล็กมาก ส่วนหัวของบล็อกที่มี "ประเภทบล็อก" ที่จุดเริ่มต้น หากเป็นบล็อกปกติจากนั้นทันทีหลังจากส่วนหัวบล็อกควรเป็นส่วนหัวออฟเซ็ต หากเป็นประเภท "โอเวอร์โฟลว์" แสดงว่าทันทีหลังจากส่วนหัวบล็อกคือข้อมูลดิบคีย์

อัปเดต (ที่อาจเกิดขึ้นด้านบนที่น่ากลัว)

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

เมื่อพบกุญแจที่คุณต้องการแล้วตัวชี้สามารถถอดรหัสและติดตามได้

ผลข้างเคียงที่ยอดเยี่ยมจากแนวคิดของ Greg! ศักยภาพในการเพิ่มประสิทธิภาพเวลาของ CPU ที่นี่มีขนาดใหญ่พอที่การตั้งค่าขนาดบล็อกคงที่อาจคุ้มค่าเพียงเพื่อให้ได้สิ่งเหล่านี้ทั้งหมด


สำหรับใครก็ตามที่สนใจในหัวข้อนี้นักพัฒนาซอฟต์แวร์ของ Redis กำลังประสบปัญหาที่แน่นอนนี้ในขณะที่พยายามใช้ส่วนประกอบ "ที่เก็บดิสก์" ที่หมดอายุสำหรับ Redis เดิมเขาเลือกใช้ขนาดคีย์แบบคงที่ "ใหญ่พอ" ขนาด 32- ไบต์ แต่ตระหนักถึงความเป็นไปได้ของปัญหาและเลือกที่จะเก็บแฮชของคีย์ (sha1 หรือ md5) แทนเพื่อให้ได้ขนาดที่สอดคล้องกัน สิ่งนี้จะฆ่าความสามารถในการค้นหาแบบเรียงลำดับ แต่จะทำให้ต้นไม้สมดุลกันอย่าง FWIW รายละเอียดได้ที่นี่redis.hackyhack.net/2011-01-12.html
Riyad Kalla

ฉันพบข้อมูลเพิ่มเติมบางอย่าง ดูเหมือนว่า SQLite จะมีขีด จำกัด จำนวนคีย์ที่สามารถรับได้หรือตัดค่าคีย์ที่ขอบเขตบนและทำให้ส่วนที่เหลืออยู่ใน "overflow page" บนดิสก์ สิ่งนี้สามารถสร้างคิวรีสำหรับคีย์ขนาดใหญ่ที่น่ากลัวในขณะที่ i / o แบบสุ่มเพิ่มขึ้นเป็นสองเท่า เลื่อนลงไปที่ส่วน "หน้าต้นไม้ B" ที่นี่sqlite.org/fileformat2.html
Riyad Kalla

คำตอบ:


7

คุณสามารถจัดเก็บดัชนีของคุณเป็นรายการออฟเซ็ตขนาดคงที่ในบล็อกที่มีข้อมูลหลักของคุณ ตัวอย่างเช่น:

+--------------+
| 3            | number of entries
+--------------+
| 16           | offset of first key data
+--------------+
| 24           | offset of second key data
+--------------+
| 39           | offset of third key data
+--------------+
| key one |
+----------------+
| key number two |
+-----------------------+
| this is the third key |
+-----------------------+

(ดีข้อมูลที่สำคัญจะถูกจัดเรียงในตัวอย่างจริง แต่คุณได้รับความคิด)

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


เกร็กฉันยังไม่ได้เลือกคำตอบของคุณเป็นคำตอบ defacto เพราะฉันหวังว่าจะได้ข้อเสนอแนะเพิ่มเติมรวมทั้งทำการวิจัยเพิ่มเติมใน DBMS อื่น ๆ (ฉันกำลังเพิ่มความคิดเห็นของฉันลงในต้นฉบับ Q) จนถึงวิธีที่พบบ่อยที่สุดดูเหมือนว่าจะเป็นขีด จำกัด บนแล้วส่วนที่เหลือของคีย์ในตารางล้นที่จะถูกตรวจสอบเฉพาะเมื่อจำเป็นต้องใช้รหัสเต็ม ไม่สง่างาม โซลูชันของคุณมีความสง่างามตามที่ฉันชอบ แต่ในกรณีขอบที่ปุ่มกดขนาดหน้ากระดาษของคุณวิธีการของคุณจะยังคงต้องมีตารางล้นหรือไม่อนุญาต
Riyad Kalla

ฉันหมดพื้นที่ ... ในระยะสั้น ๆ ถ้าผู้ออกแบบ db สามารถใช้ชีวิตด้วยข้อ จำกัด ขนาดใหญ่ในคีย์ฉันคิดว่าแนวทางของคุณมีประสิทธิภาพและยืดหยุ่นที่สุด คำสั่งผสมที่ดีของพื้นที่และประสิทธิภาพของซีพียู ตารางล้นมีความยืดหยุ่นมากขึ้น แต่สามารถเพิ่มการสุ่ม i / o เพื่อค้นหาคีย์ที่ล้นอย่างต่อเนื่อง ขอบคุณสำหรับข้อมูลนี้!
Riyad Kalla

เกร็กฉันคิดถึงเรื่องนี้มากขึ้นเรื่อย ๆ โดยมองหาทางเลือกอื่นและฉันคิดว่าคุณจับมันด้วยแนวคิดส่วนหัวออฟเซต หากคุณเก็บบล็อกของคุณไว้เล็ก ๆ คุณสามารถลบออกได้ด้วย 8-bit (1 byte) offsets โดยที่ block ที่ใหญ่กว่า 16-bit นั้นจะปลอดภัยที่สุดแม้จะมากถึง 128KB หรือ 256KB ก็ตามที่ควรจะสมเหตุสมผล ชัยชนะที่ยิ่งใหญ่คือราคาที่ถูกและรวดเร็วคุณสามารถอ่านข้อมูลออฟเซ็ตได้และคุณจะประหยัดได้มากน้อยเพียงใด ข้อเสนอแนะที่ดีเยี่ยมขอบคุณอีกครั้ง
Riyad Kalla

นี่เป็นวิธีการที่ใช้ใน UpscaleDB: upscaledb.com/about.html#varlength
Mathieu Rodic
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.