ชื่อเดียวกัน แต่เป็นการเข้ารหัสคนละตัว · บน id แบบหนึ่งใช้ 14 บิตต่อค่า อีกแบบใช้ 2 บิตต่อค่า
id ของตารางออเดอร์เป็นเลขวิ่งทีละหนึ่ง คำทำนายที่สมเหตุสมผลจึงเขียนตัวเองออกมา
เก็บส่วนต่างแทนตัวค่า แล้วหนึ่งบิตต่อค่าก็พอ
ผู้เขียน probe เขียนคำทำนายนั้นไว้ก่อนวัด และการวัดหักล้างมัน
delta(frame of reference) บน id ใช้ 14 บิตต่อค่า และได้ 350,360 ไบต์
ส่วน delta(ส่วนต่างของตัวติดกัน) บน id ตัวเดียวกันใช้ 2 บิตต่อค่า และได้
50,360 ไบต์
ทั้งสองทรงนี้ถูกเรียกว่า delta encoding เหมือนกัน และนั่นคือทั้งบท
สั่ง python3 scripts/column-store/probe.py ได้เลย มันใช้ Python 3 กับ standard
library ล้วน ไม่ต้องติดตั้งอะไรก่อน
ค่าที่วัดได้ทุกตัวที่บทนี้พิมพ์มีคีย์ของตัวเองอยู่ใน
scripts/column-store/expected/column-store.txt และตารางทุกใบในบทนี้เขียนชื่อหัวข้อ
กับชื่อคีย์กำกับไว้
หัวข้อที่บทนี้อ่านเกือบทั้งบทคือ ### 10_ข้อที่ขัดกับตำรา ส่วนตัวเลขของก้อนมาจาก
### สนามแข่ง
ชื่อเดียว แต่เป็นสองทรงที่เขียนคนละอย่างลงคลัง
หัวข้อที่มีชื่อว่า “ชื่อเดียว แต่เป็นสองทรงที่เขียนคนละอย่างลงคลัง”สองทรงนี้ต่างกันที่ค่าที่ถูกลบออกจากตัวค่า และความต่างนั้นอยู่ในสองบรรทัด
delta encodingdelta encodingชื่อที่ใช้เรียกการเข้ารหัสสองแบบซึ่งไม่ใช่แบบเดียวกัน · บน id ที่เป็นเลขวิ่งทีละหนึ่ง แบบ frame of reference ได้ 350,360 ไบต์ที่ 14 บิตต่อค่า ส่วนแบบส่วนต่างของตัวติดกันได้ 50,360 ไบต์ที่ 2 บิตต่อค่า · บน total ผลกลับด้าน คือ 450,360 ไบต์ เทียบ 475,320 ไบต์ เป็นชื่อร่ม ใต้ร่มนั้นมีสองทรงที่ colstore.py เขียนแยกกัน
และคอร์สนี้เรียกมันด้วยชื่อของ probe เสมอ
ทรงแรกคือ frame of referenceframe of reference (FOR)การเก็บส่วนต่างจากค่าที่น้อยที่สุดใน row group นั้น · บน customer ได้ 275,360 ไบต์ที่ 11 บิตต่อค่า · บน placed_at ความกว้างไม่เท่ากันในแต่ละก้อน probe คืนค่าออกมาเป็นเซต คือ 17 · 18 · 19 · 20 · 21 · 22 บิตต่อค่า ไม่ใช่เป็นช่วง มันหาค่าที่น้อยที่สุดใน column chunk ก้อนนั้น แล้วเก็บส่วนต่างจากค่านั้น
elif enc == DELTA: # frame of reference: เก็บ min ไว้ในหัว แล้ว bit-pack ส่วนต่างจาก min if kind != "u32": raise ValueError("delta ใช้กับ column ตัวเลขเท่านั้น") base = min(vals) span = max(vals) - base bits = max(1, span.bit_length()) body = pack_bits([v - base for v in vals], bits)ทรงที่สองเก็บส่วนต่างจากค่าที่อยู่ก่อนหน้า ไม่ใช่จากค่าที่น้อยที่สุด
elif enc == DELTA2: # ส่วนต่างของตัวที่ติดกัน · ห่อด้วย zigzag เพราะส่วนต่างติดลบได้ if kind != "u32": raise ValueError("delta2 ใช้กับ column ตัวเลขเท่านั้น") base = vals[0] zz = [zigzag(vals[i] - vals[i - 1]) for i in range(1, n)] bits = max(1, max(zz).bit_length()) if zz else 1 body = pack_bits(zz, bits)zigzag ห่อจำนวนเต็มมีเครื่องหมายให้เป็นจำนวนเต็มไม่มีเครื่องหมาย เพราะส่วนต่างของ
ตัวที่ติดกันติดลบได้ ส่วน pack_bits เป็นตัวเดียวกับที่บทที่ 4 ใช้
คำทำนายที่ผิด และค่าทั้งสี่ตัวบน id
หัวข้อที่มีชื่อว่า “คำทำนายที่ผิด และค่าทั้งสี่ตัวบน id”ค่าที่วัดได้บน column ตัวเดียว สองทรง สองความกว้าง และสองขนาด
id มีค่าที่ไม่ซ้ำเท่ากับจำนวนแถว dictionary encoding จึงแพ้บน column ตัวนี้ตั้งแต่
บทที่ 3 คำถามของบทนี้คือ แล้ว delta encoding ทำอะไรกับมันได้บ้าง
หัวข้อ ### 10_ข้อที่ขัดกับตำรา → delta เทียบ dictionary บน column ที่ dictionary แพ้
→ กลุ่ม id
| ทรงที่วัด | คีย์ในกลุ่ม id | ค่า |
|---|---|---|
| plain encoding | raw | 800,360 ไบต์ |
| dictionary encoding | dict | 1,200,440 ไบต์ |
| frame of reference | delta(frame of reference) | 350,360 ไบต์ |
| ความกว้างของ frame of reference | บิตต่อค่าของ delta(frame of reference) | 14 บิตต่อค่า |
| ส่วนต่างของตัวติดกัน | delta(ส่วนต่างของตัวติดกัน) | 50,360 ไบต์ |
| ความกว้างของส่วนต่างของตัวติดกัน | บิตต่อค่าของ delta(ส่วนต่างของตัวติดกัน) | 2 บิตต่อค่า |
probe เก็บอัตราส่วนของสองทรงนี้ไว้ด้วย แต่เก็บเทียบกับ raw ไม่ได้เก็บเทียบกันเอง
คีย์ในกลุ่ม id | ค่า |
|---|---|
delta หารด้วย raw | 0.4378 |
delta2 หารด้วย raw | 0.0629 |
ประโยคที่พบทั่วไปคือ delta encoding ทำให้ column ที่เป็นเลขเรียงเล็กลงมาก
ประโยคนั้นจริงกับทรงเดียวจากสองทรง delta(ส่วนต่างของตัวติดกัน) บน id ได้
50,360 ไบต์ ส่วน delta(frame of reference) บน id เดียวกันได้ 350,360 ไบต์
ชื่อของการเข้ารหัสไม่ได้บอกว่ามันทำอะไร และเครื่องมือจริงสองตัวที่เขียนคำว่า delta บนหน้าเอกสารของมัน อาจหมายถึงคนละทรงกัน
ทำไม frame of reference ยังกิน 14 บิต บน column ที่วิ่งทีละหนึ่ง
หัวข้อที่มีชื่อว่า “ทำไม frame of reference ยังกิน 14 บิต บน column ที่วิ่งทีละหนึ่ง”ค่าที่ถูกลบออกคือค่าที่น้อยที่สุดของทั้งก้อน ไม่ใช่ค่าที่อยู่ก่อนหน้า
base = min(vals) กับ span = max(vals) - base อ่านค่าทั้งก้อนพร้อมกัน ความกว้างของ
ตัวรหัสจึงมาจากระยะระหว่างค่าที่น้อยที่สุดกับค่าที่มากที่สุดของก้อน
| ของที่ตรึงไว้ | หัวข้อ · คีย์ | ค่า |
|---|---|---|
| จำนวนแถวต่อหนึ่งก้อน | ### สนามแข่ง · row group ปริยาย | 10,000 |
| จำนวนก้อนทั้งคลัง | ### สนามแข่ง · row group ทั้งหมด | 20 |
id วิ่งทีละหนึ่ง ระยะจากค่าแรกถึงค่าสุดท้ายของก้อนจึงเกือบเท่าจำนวนแถวในก้อน
ไม่ใช่เท่ากับหนึ่ง ผลที่ได้คือ 14 บิตต่อค่า
ความกว้างของตัวรหัส (bit widthbit widthจำนวนบิตที่ใช้ต่อหนึ่งค่า และที่มาของมันต่างกันตามการเข้ารหัส · bit packing เอามาจากจำนวนค่าที่ไม่ซ้ำใน column chunk นั้น ส่วน frame of reference เอามาจากระยะระหว่างค่าที่มากที่สุดกับค่าที่น้อยที่สุดในก้อนเดียวกัน · ทั้งสองแบบเป็นค่าต่อ row group ไม่ใช่ค่าต่อทั้ง column ดังนั้น column ตัวเดียวกันมีความกว้างไม่เท่ากันในแต่ละก้อนได้) เป็นสิ่งที่ทรงของการเข้ารหัสกำหนด ไม่ใช่สิ่งที่รูปของ ข้อมูลกำหนดเพียงลำพัง ข้อมูลชุดเดียวกันให้ 14 กับทรงหนึ่ง และให้ 2 กับอีกทรงหนึ่ง
ทรงที่สองลบค่าที่อยู่ก่อนหน้าออก ส่วนต่างของ id สองตัวที่ติดกันจึงเป็นหนึ่งเกือบทุกคู่
และหลังห่อด้วย zigzag แล้ว ความกว้างที่ probe วัดได้คือ 2 บิตต่อค่า
customer กับ total กลับด้าน และนั่นคือราคาของการเลือก
หัวข้อที่มีชื่อว่า “customer กับ total กลับด้าน และนั่นคือราคาของการเลือก”สองทรงเดียวกันบนอีกสอง column และคราวนี้ frame of reference เป็นฝ่ายชนะ
หัวข้อ ### 10_ข้อที่ขัดกับตำรา → delta เทียบ dictionary บน column ที่ dictionary แพ้
→ กลุ่ม customer และกลุ่ม total
| คีย์ | กลุ่ม customer | กลุ่ม total |
|---|---|---|
raw | 800,360 ไบต์ | 800,360 ไบต์ |
dict | 559,336 ไบต์ | 1,180,592 ไบต์ |
delta(frame of reference) | 275,360 ไบต์ | 450,360 ไบต์ |
บิตต่อค่าของ delta(frame of reference) | 11 บิตต่อค่า | 18 บิตต่อค่า |
delta(ส่วนต่างของตัวติดกัน) | 300,340 ไบต์ | 475,320 ไบต์ |
บิตต่อค่าของ delta(ส่วนต่างของตัวติดกัน) | 12 บิตต่อค่า | 19 บิตต่อค่า |
delta หารด้วย raw | 0.344 | 0.5627 |
delta2 หารด้วย raw | 0.3753 | 0.5939 |
total เป็นค่าเงินที่สุ่มล้วน ค่าสองตัวที่ติดกันจึงไม่ได้ใกล้กันเลย ส่วนต่างของมันกินช่วง
ที่กว้างกว่าช่วงของตัวค่าเอง ความกว้างจึงขึ้นจาก 18 เป็น 19 บิตต่อค่า
customer เดินตามรูปเดียวกัน คือ 11 บิตต่อค่ากับ frame of reference และ 12
บิตต่อค่ากับส่วนต่างของตัวติดกัน
ไม่มีทรงไหนชนะทุก column ทรงที่ชนะขาดบน id เป็นทรงที่แพ้บน total และการเลือก
จึงต้องเลือกทีละ column ไม่ใช่เลือกครั้งเดียวให้ทั้งคลัง
placed_at ใช้ความกว้างไม่เท่ากันในแต่ละ row group
หัวข้อที่มีชื่อว่า “placed_at ใช้ความกว้างไม่เท่ากันในแต่ละ row group”ความกว้างเป็นค่าต่อก้อน ไม่ใช่ค่าต่อทั้ง column และ column ตัวนี้แสดงข้อนั้นออกมา
บิตต่อค่า ที่ probe เก็บไว้เป็นเซตของค่าที่พบในทุกก้อน สาม column ก่อนหน้าคืนเซตที่มี
สมาชิกตัวเดียว ส่วน placed_at ไม่ใช่
หัวข้อ ### 10_ข้อที่ขัดกับตำรา → delta เทียบ dictionary บน column ที่ dictionary แพ้
→ กลุ่ม placed_at
คีย์ในกลุ่ม placed_at | ค่า |
|---|---|
raw | 800,360 ไบต์ |
dict | 1,188,004 ไบต์ |
delta(frame of reference) | 491,610 ไบต์ |
บิตต่อค่าของ delta(frame of reference) | 17 · 18 · 19 · 20 · 21 · 22 บิตต่อค่า |
delta(ส่วนต่างของตัวติดกัน) | 276,589 ไบต์ |
บิตต่อค่าของ delta(ส่วนต่างของตัวติดกัน) | 10 · 11 · 12 · 16 บิตต่อค่า |
delta หารด้วย raw | 0.6142 |
delta2 หารด้วย raw | 0.3456 |
ค่าในสองแถวของความกว้างเป็นเซตที่เรียงแล้ว แถวล่างไม่มี 13 · 14 · 15 เซตนั้นบอกว่า ก้อนไหนก้อนหนึ่งมีคู่ที่ห่างกันมากกว่าก้อนอื่น ไม่ได้บอกว่าทุกก้อนไล่ความกว้างขึ้นทีละหนึ่ง
ผลที่ตามมามีสองข้อ
- ก้อนหนึ่งเลือกการเข้ารหัสของตัวเองได้ ความกว้างจึงตอบสนองต่อข้อมูลในก้อนนั้น ไม่ใช่ต่อค่าที่แย่ที่สุดของทั้ง column
- ค่าที่รายงานต่อทั้ง column เป็นเซต ไม่ใช่ค่าเดียว การพิมพ์ค่าเดียวจึงเป็นการพิมพ์ ค่าของก้อนหนึ่ง แล้วเรียกมันว่าค่าของทั้ง column
รูปเดียวที่วางสองทรงไว้บน column เดียวกัน
หัวข้อที่มีชื่อว่า “รูปเดียวที่วางสองทรงไว้บน column เดียวกัน”ค่าที่ถูกลบออกต่างกัน ความกว้างจึงต่างกัน และขนาดที่ได้ก็ต่างกัน
flowchart TB ID["column id · เลขวิ่งทีละหนึ่ง · 20 row group ก้อนละ 10,000 แถว"] ID --> F["frame of reference · ลบค่าที่น้อยที่สุดของก้อนออก"] ID --> S["ส่วนต่างของตัวติดกัน · ลบค่าที่อยู่ก่อนหน้าออก"] F --> FB["ความกว้าง 14 บิตต่อค่า"] S --> SB["ความกว้าง 2 บิตต่อค่า"] FB --> FZ["350,360 ไบต์"] SB --> SZ["50,360 ไบต์"]
คำบรรยายภาพ: สองกิ่งอ่าน column ตัวเดียวกัน วางแนวเหมือนกัน และตัดก้อนเท่ากัน ต่างกันที่ค่าที่ถูกลบออกจากตัวค่าเท่านั้น กิ่งซ้ายคือทรงที่ตำราเรียกว่า delta encoding และกิ่งขวาก็ถูกเรียกด้วยชื่อเดียวกัน
เลือกให้ถูกต่อ column แล้วทั้งคลังเหลือเท่าไร
หัวข้อที่มีชื่อว่า “เลือกให้ถูกต่อ column แล้วทั้งคลังเหลือเท่าไร”การเข้ารหัสสามแบบชนะแบบละสอง column พอดี และไม่มีแบบไหนชนะเกินสอง
หัวข้อ ### 10_ข้อที่ขัดกับตำรา → คลังทั้งใบเมื่อเลือกการเข้ารหัสที่ดีที่สุดต่อ column
| column | คีย์ การเข้ารหัสที่เล็กที่สุด | คีย์ ไบต์ |
|---|---|---|
id | delta(ส่วนต่างของตัวติดกัน) | 50,360 |
customer | delta(frame of reference) | 275,360 |
status | bitpack | 76,740 |
restaurant | bitpack | 108,680 |
placed_at | delta(ส่วนต่างของตัวติดกัน) | 276,589 |
total | delta(frame of reference) | 450,360 |
นับชื่อในช่องกลาง bitpack ได้สอง delta(frame of reference) ได้สอง และ
delta(ส่วนต่างของตัวติดกัน) ได้สอง
| ทรงที่วัด | คีย์ในกลุ่มเดียวกัน | ค่า |
|---|---|---|
| คลัง column เมื่อเลือกทีละ column | ไบต์รวม | 1,238,089 ไบต์ |
| คลัง column เมื่อเลือกทีละ column | หน้ารวม | 305 หน้า |
heap ที่ heapfile.build_heap เขียน | heap ไบต์ | 12,914,688 ไบต์ |
heap ที่ heapfile.build_heap เขียน | heap หน้า | 3153 หน้า |
สองแถวล่างเป็นของ heap ที่ scripts/query-engine/qe/heapfile.py เขียน ไม่ใช่ของอะไรที่
colstore.py เขียน คอร์สนี้ไม่แก้อะไรใน scripts/query-engine/qe/ แม้แต่ byte เดียว
ช่องกลางของตารางแรกเป็นผลของ ตารางใบนี้ ที่ seed นี้ ที่ row group ขนาด 10,000 ไม่ใช่กฎที่ยกไปใช้กับตารางใบอื่นได้
สิ่งที่ยกไปใช้ได้คือวิธี คือวัดทุกทรงบนข้อมูลจริงของตัวเอง แล้วเลือกทีละ column
· id กับ placed_at เลือกคนละทรงกับ customer กับ total ทั้งที่ทั้งสี่ตัวเป็นเลข
- บทที่ 3 — dictionary ที่ทำให้มันใหญ่ขึ้น
วัดว่า
idplaced_atและtotalเป็นสาม column ที่ dictionary แพ้ · บทนี้จึงหยิบ สามตัวนั้นมาต่อ - บทที่ 4 — บิต ไม่ใช่ byte วางเรื่องความกว้างของตัวรหัสไว้ก่อน · บทนี้ใช้หน่วยเดียวกัน
- บทที่ 7 — เรียงเพื่อ column หนึ่ง คือจ่ายให้อีก column วัดว่าไบต์ของ delta ขยับไปทางไหนเมื่อลำดับที่เขียนแถวลงคลังเปลี่ยน
คำถามที่บทนี้ทิ้งไว้
หัวข้อที่มีชื่อว่า “คำถามที่บทนี้ทิ้งไว้”รู้แล้วว่าเลือกการเข้ารหัสอย่างไร ยังไม่รู้ว่าจะไม่อ่านทั้งก้อนได้เมื่อไร
ทั้งบทนี้วัดขนาด คือไบต์ที่ค่าหนึ่งค่ากินหลังเข้ารหัส หน่วยนั้นตอบว่าคลังใหญ่แค่ไหน แต่ไม่ได้ตอบว่าคำถามหนึ่งคำถามต้องแตะกี่หน้า
การเข้ารหัสที่เล็กที่สุดยังต้องถูกอ่านทั้งก้อน ถ้าไม่มีอะไรบอกล่วงหน้าว่าก้อนนั้นไม่มี แถวที่ตรงเลย
บทที่ 6 วัดกลไกที่ตัดทั้งก้อนทิ้งโดยไม่อ่านข้างใน แล้วพบว่า column ที่มีค่าไม่ซ้ำเป็นแสน ตัวหนึ่งข้ามได้เกือบทุกก้อน ส่วนอีกตัวข้ามไม่ได้สักก้อน
-
Apache Parquet — Encodings (เข้าถึง 2026-09-10) — ที่มาของข้อที่ว่ารูปแบบหนึ่งรูปแบบนิยามการเข้ารหัสตระกูล delta ไว้มากกว่าหนึ่งตัว และแต่ละตัวเก็บค่าที่ต่างกันเป็นฐาน · คอร์สนี้อ้างข้อนี้ ในฐานะสิ่งที่รูปแบบกำหนด ไม่ใช่ในฐานะสิ่งที่ probe วัด
-
คลังของคอร์สนี้ไม่ใช่ Parquet ทรงทั้งสองที่บทนี้วัดเขียนด้วย
scripts/column-store/colstore.pyบรรทัดที่ 204 และบรรทัดที่ 219 ·zigzagอยู่ที่ บรรทัดที่ 95 · ชื่อของทั้งสองทรงมาจากENC_NAMEที่บรรทัดที่ 41 -
ตัวเลขทุกตัวในบทนี้มาจาก
python3 scripts/column-store/probe.pyซึ่งสร้างคลังขึ้นใหม่ ทุกรอบจากfixture.pyของคอร์สที่ 26 · ค่าที่ตรึงไว้อยู่ที่scripts/column-store/expected/column-store.txtหัวข้อ### 10_ข้อที่ขัดกับตำราและ### สนามแข่ง
เช็กความเข้าใจ — บทที่ 5
ข้อ 1 / 3บน column `id` ที่เป็นเลขวิ่งทีละหนึ่ง frame of reference ใช้ความกว้างเท่าไรต่อค่า