เงื่อนไขบน placed_at ข้าม 19 จาก 20 กลุ่ม ส่วน total ที่มีค่าไม่ซ้ำ 125,327 ค่า ข้ามได้ 0 กลุ่ม
ห้าบทแรกถามว่าค่าหนึ่งค่ากินที่เท่าไร บทนี้เปลี่ยนคำถาม มันถามว่า ค่าไหนที่ไม่ต้องอ่านเลย
คลัง column ตัดแถวออกเป็นกลุ่ม แล้วเก็บ min กับ max ของทุกกลุ่มไว้ ถ้าเงื่อนไข ของคำถามอยู่นอกช่วงนั้น ทั้งกลุ่มถูกข้ามไปโดยไม่ต้องแตะสักหน้า
คำทำนายที่สมเหตุสมผลคือ กลไกนี้ได้ผลดีบน column ที่มีค่าที่ไม่ซ้ำน้อย เพราะค่าซ้ำๆ น่าจะทำให้ช่วง min ถึง max ของแต่ละกลุ่มแคบลง
ค่าที่วัดได้ปฏิเสธคำทำนายนั้นทั้งข้อ status มีค่าที่ไม่ซ้ำ 7 ค่า และข้ามได้ 0
กลุ่ม ส่วน id มีค่าที่ไม่ซ้ำ 200,000 ค่า และข้ามได้ 19 กลุ่ม
พาดหัวของบทคือ total ซึ่งมีค่าที่ไม่ซ้ำ 125,327 ค่า ใช้ 14 บิตต่อค่า
เท่ากับ id เป๊ะ และข้ามได้ 0 กลุ่ม
สั่ง python3 scripts/column-store/probe.py ได้เลย มันใช้ Python 3 กับ standard
library ล้วน และไม่มี container ให้เก็บกวาดทีหลัง
ค่าที่วัดได้ทุกตัวที่บทนี้พิมพ์มีคีย์ของตัวเองอยู่ใน
scripts/column-store/expected/column-store.txt และชื่อคีย์นั้นเขียนกำกับไว้ในทุกตาราง
ตัวเลขในบทนี้จึงอ้างอิงเป็นคู่ของทรงกับคีย์เสมอ
กลไกอยู่ที่ min กับ max ของทุกกลุ่ม ไม่ใช่ที่ค่าข้างใน
หัวข้อที่มีชื่อว่า “กลไกอยู่ที่ min กับ max ของทุกกลุ่ม ไม่ใช่ที่ค่าข้างใน”เขียนสองค่านั้นตอนสร้างกลุ่ม แล้วใช้มันตัดกลุ่มทิ้งก่อนเปิดอ่าน
ตอนเขียนคลัง colstore.py เก็บ min กับ max ของทุก row grouprow groupก้อนของแถวที่คลังแนว column ตัดออกมาเป็นหน่วยเดียวกัน ทุก column ในก้อนเดียวกันถือแถวชุดเดียวกัน · ขอบเขตของก้อนเป็นสิ่งที่ตัดสินว่า dictionary ถูกคัดลอกซ้ำกี่ใบ และ zone map ข้ามอะไรได้บ้าง ไว้ใน metadata
ข้างเดียวกับ offset และความยาวของกลุ่มนั้น
vals = data[g * rg:(g + 1) * rg] blob, m = encode_chunk(vals, kind, self.enc[col]) m["off"] = off m["len"] = len(blob) m["min"] = min(vals) m["max"] = max(vals) m["g"] = gmetadata ชุดนี้คือ zone mapzone mapค่า min และ max ของแต่ละ column chunk ที่เก็บไว้ใน metadata เพื่อให้ข้ามทั้ง row group ได้โดยไม่ต้องอ่านค่าข้างใน · มันคือการตัดทิ้ง ไม่ใช่การประมาณ คอร์สนี้จึงรายงานจำนวนกลุ่มที่ข้ามได้จริงจากการรัน และไม่คำนวณค่าประมาณความเลือกสรรสักตัว และการใช้มันเป็นการเทียบสองบรรทัดต่อกลุ่ม ไม่ใช่การเปิดอ่านค่าข้างในกลุ่ม
def prune(self, col, lo, hi): """คืนรายการ row group ที่ min/max ไม่ตัดออก · นี่คือการตัดทิ้ง ไม่ใช่การประมาณ""" keep = [] for m in self.meta[col]: if m["max"] < lo or m["min"] > hi: continue keep.append(m["g"]) return keepการส่งเงื่อนไขของคำถามลงไปให้ชั้นที่เก็บข้อมูลตัดสินก่อนอ่าน เรียกว่า
predicate pushdownpredicate pushdownการส่งเงื่อนไขลงไปให้ชั้นที่เก็บข้อมูลตัดสินก่อนอ่าน · ค่าทั้งหมดข้างล่างวัดบนคลัง column ทรง bit packing ที่ตัดเป็น 20 row group · เงื่อนไขบน placed_at ในวันที่ 90 ข้ามได้ 19 จาก 20 กลุ่ม สแกน 10,000 แถว ตรง 1,017 แถว และแตะ 29 หน้า · เงื่อนไขบน customer ระหว่าง 995 ถึง 1004 ข้ามได้ 0 กลุ่ม สแกน 200,000 แถว ตรง 995 แถว และแตะ 384 หน้า · ส่วน heap ที่ heapfile.build_heap เขียน อ่านทั้งตาราง 3153 หน้าทั้งสองคำถาม และ prune คือทั้งหมดที่คลังของคอร์สนี้ทำในชั้นนั้น
prune คืนรายการกลุ่มที่รอด คำถามจึงอ่านเฉพาะกลุ่มในรายการนั้น และกลุ่มที่ถูกตัดออก
ไม่ถูกเปิดเลยสักหน้า
def zone_query(store, col, lo, hi, extra=()): """ตัด row group ด้วย min/max แล้วอ่านเฉพาะกลุ่มที่เหลือ — นี่คือการตัดทิ้ง ไม่ใช่การประมาณ""" keep = store.prune(col, lo, hi) q = cs.Q(store) matched = 0 scanned = 0 for g in keep: vals = q.chunk(col, g) scanned += len(vals) for j, v in enumerate(vals): if lo <= v <= hi: matched += 1ที่นี่ไม่มีการประมาณสักขั้น ไม่มีการเดาว่าเงื่อนไขจะตรงกี่แถว มีแต่การเทียบ ช่วงของกลุ่มกับช่วงของเงื่อนไข แล้วนับกลุ่มที่รอดออกมาเป็นจำนวนเต็ม
คำถามสองข้อที่เจอแถวใกล้เคียงกัน แต่ข้ามกลุ่มได้ต่างกันสุดขั้ว
หัวข้อที่มีชื่อว่า “คำถามสองข้อที่เจอแถวใกล้เคียงกัน แต่ข้ามกลุ่มได้ต่างกันสุดขั้ว”เงื่อนไขคนละ column บนตารางใบเดียวกัน แล้วจำนวนกลุ่มที่ข้ามได้ห่างกันสุดช่วง
คำถามข้อแรกถาม placed_at ในวันที่ 90 ข้อที่สองถาม customer ระหว่าง 995 ถึง 1004
ทั้งสองข้อวิ่งบนคลังทรง bit packing ใบเดียวกัน ที่ตัดเป็น 20 กลุ่มเท่ากัน
ทรงที่วัด · ### 6_zone_map | placed_at ในวันที่ 90 | customer ระหว่าง 995 ถึง 1004 |
|---|---|---|
กลุ่มทั้งหมด | 20 | 20 |
กลุ่มที่เหลือ | 1 | 20 |
กลุ่มที่ข้าม | 19 | 0 |
แถวที่ตรง | 1,017 | 995 |
แถวสุดท้ายคือแถวที่ทำให้การเทียบนี้ยุติธรรม 1,017 กับ 995 อยู่ระดับเดียวกัน สองคำถามจึงไม่ได้ต่างกันเพราะข้อหนึ่งถามแคบกว่าอีกข้อ
ถ้าจำนวนแถวที่ตรงของสองข้อห่างกันมาก ผลที่ต่างกันจะอธิบายด้วยความกว้างของคำถามได้ทันที probe จึงเลือกช่วงลูกค้าให้จำนวนแถวมาลงใกล้กับหนึ่งวันโดยตั้งใจ
ประโยคที่พบทั่วไปคือ zone map ได้ผลบน column ที่มีค่าที่ไม่ซ้ำน้อย เพราะช่วงของ แต่ละกลุ่มจะแคบ
status มีค่าที่ไม่ซ้ำ 7 ค่า ซึ่งน้อยที่สุดใน column ทั้งหกตัว และคีย์
กลุ่มที่ข้ามได้เมื่อถามค่าเดียวตรงกลาง ของมันเป็น 0
ค่าที่ซ้ำมากไม่ได้ทำให้ช่วงแคบ มันทำให้ทุกกลุ่มมีค่าครบทุกแบบ ช่วง min ถึง max ของทุกกลุ่มจึงทับกันสนิท และไม่มีกลุ่มไหนถูกตัดทิ้งได้
ข้ามไม่ได้สักกลุ่ม ไม่ได้แปลว่าแพงขึ้นนิดหน่อย มันแปลว่าอ่านทั้ง column
หัวข้อที่มีชื่อว่า “ข้ามไม่ได้สักกลุ่ม ไม่ได้แปลว่าแพงขึ้นนิดหน่อย มันแปลว่าอ่านทั้ง column”เมื่อไม่มีกลุ่มไหนถูกตัด จำนวนแถวที่สแกนกลับไปเท่ากับทั้งตาราง
ทรงที่วัด · ### 6_zone_map | placed_at ในวันที่ 90 | customer ระหว่าง 995 ถึง 1004 |
|---|---|---|
แถวที่สแกน | 10,000 | 200,000 |
หน้าที่แตะ_column เงื่อนไข | 15 | 107 |
หน้าที่แตะ_รวม column ที่ดึงออกมา | 29 | 384 |
แถวที่สแกน ของคำถามข้อแรกเท่ากับขนาดของหนึ่งกลุ่มพอดี เพราะเหลือกลุ่มเดียว
ส่วนของข้อที่สองเท่ากับจำนวนแถวของทั้งตาราง เพราะไม่เหลือกลุ่มไหนถูกตัดทิ้ง
สองแถวล่างเป็นหน้าที่แตะ แถวกลางนับเฉพาะ column ที่เงื่อนไขอ่าน ส่วนแถวล่างนับ
ตอนดึง total ของแถวที่ตรงออกมาด้วย
ฝั่งคลังแนวแถวไม่มีกลไกนี้ให้ใช้ heap ไม่ได้ถือ min กับ max รายกลุ่มไว้เลย มันจึงต้องอ่านทั้งตารางทั้งสองคำถาม
| ทรงที่วัด | คีย์ในผลของ probe | หน้าที่แตะ |
|---|---|---|
heap ที่ heapfile.build_heap เขียน · ทั้งสองคำถาม | heap ต้องอ่านเท่าไรทั้งสองคำถาม | 3153 |
ค่าเดียวกันทั้งสองคำถามคือประเด็น ฝั่ง heap ไม่มีอะไรให้ตัดทิ้ง ตัวเลขของมัน จึงไม่ขยับตามรูปของ column ที่คำถามถาม
ส่วนฝั่งคลัง column ตัวเลขขยับจาก 29 ไปเป็น 384 ระหว่างสองคำถามที่เจอแถว ตรงพอๆ กัน กลไกนี้จึงไม่ใช่ของที่ได้เปล่า มันได้ผลกับบาง column เท่านั้น
หัวใจของบทคือ total ซึ่งมีค่าที่ไม่ซ้ำเป็นแสน และข้ามได้ศูนย์กลุ่ม
หัวข้อที่มีชื่อว่า “หัวใจของบทคือ total ซึ่งมีค่าที่ไม่ซ้ำเป็นแสน และข้ามได้ศูนย์กลุ่ม”จำนวนค่าที่ไม่ซ้ำและความกว้างของตัวรหัสไม่ได้ทำนายผลของกลไกนี้เลย
probe ถาม ค่าเดียวที่อยู่ตรงกลาง ของแต่ละ column แล้วนับกลุ่มที่ตัดทิ้งได้ บนคลังทรง bit packing ใบเดียวกันที่ตัดเป็น 20 กลุ่ม
column · ### 10_ข้อที่ขัดกับตำรา | ค่าที่ไม่ซ้ำ | บิตต่อค่าเมื่อ bit-pack | กลุ่มที่ข้ามได้เมื่อถามค่าเดียวตรงกลาง |
|---|---|---|---|
id | 200,000 | 14 | 19 |
customer | 2,000 | 11 | 0 |
status | 7 | 3 | 0 |
restaurant | 10 | 4 | 0 |
placed_at | 196,891 | 14 | 19 |
total | 125,327 | 14 | 0 |
อ่านสามแถวที่ใช้ 14 บิตต่อค่าเท่ากันเรียงกัน id ข้ามได้ 19 placed_at
ข้ามได้ 19 และ total ข้ามได้ 0
สาม column นี้มีค่าที่ไม่ซ้ำอยู่ในระดับแสนเหมือนกัน และตัวรหัสกว้างเท่ากันเป๊ะ สองตัวนั้นจึงทำนายผลของกลไกนี้ไม่ได้เลย
ตัวแปรที่เหลืออยู่ตัวเดียวคือ ความสัมพันธ์กับลำดับที่เขียนแถวลงคลัง id เดิน
ขึ้นตามลำดับที่เขียน placed_at ก็เดินขึ้นตามลำดับเดียวกัน ส่วน total ไม่เดิน
ตามอะไรเลย
กลุ่มที่ 0 ของ total จึงถือค่าตั้งแต่ต่ำสุดถึงสูงสุด และกลุ่มที่ 19 ก็ถือช่วง
เดียวกัน ค่าเดียวที่ถามอยู่ในช่วงของทุกกลุ่ม และไม่มีกลุ่มไหนถูกตัดทิ้ง
ลำดับที่เขียน เป็นสิ่งเดียวที่ตัดสินว่ากลุ่มไหนถูกตัดทิ้งได้
หัวข้อที่มีชื่อว่า “ลำดับที่เขียน เป็นสิ่งเดียวที่ตัดสินว่ากลุ่มไหนถูกตัดทิ้งได้”รูปเดียวที่วางสอง column ซึ่งมีค่าที่ไม่ซ้ำระดับเดียวกันไว้คนละกิ่ง
flowchart TB W["ลำดับที่เขียนแถวลงคลัง · ตัดเป็น 20 กลุ่ม · กลุ่มละ 10,000 แถว"] W --> A["placed_at · เดินขึ้นตามลำดับที่เขียน · ช่วง min ถึง max ของแต่ละกลุ่มแทบไม่ทับกัน"] W --> B["total · ไม่เดินตามลำดับที่เขียน · ช่วง min ถึง max ของทุกกลุ่มทับกันสนิท"] A --> A1["เงื่อนไขวันที่ 90 · ข้าม 19 กลุ่ม · สแกน 10,000 แถว"] B --> B1["เงื่อนไขค่าเดียวตรงกลาง · ข้าม 0 กลุ่ม"] A1 --> N["ค่าที่ไม่ซ้ำของสอง column นี้อยู่ระดับแสนทั้งคู่ · ตัวรหัสกว้าง 14 บิตเท่ากัน"] B1 --> N
คำบรรยายภาพ: สองกิ่งเริ่มจากลำดับที่เขียนชุดเดียวกัน และจบที่ผลตรงข้ามกัน กล่องล่างสุดคือสองค่าที่เท่ากันทั้งคู่ จึงไม่ใช่สิ่งที่ทำให้ผลต่างกัน
ที่วัดได้ กับที่รูปแบบกำหนด
หัวข้อที่มีชื่อว่า “ที่วัดได้ กับที่รูปแบบกำหนด”ตัวเลขมาจากการรัน ส่วนเหตุผลที่ min กับ max อยู่ใน metadata มาจากรูปแบบ
ค่า 19 และ 0 กับหน้าที่แตะ 29 และ 384 เป็นสิ่งที่ probe วัดจากคลัง
ที่ colstore.py เขียนลงจริง ส่วน 3153 วัดจาก heap ที่ heapfile.build_heap
ของคอร์สที่ 26 เขียน
ทั้งสองฝั่งนับหน้าด้วย Pager.reads ตัวเดียวกัน ตัวเลขสองฝั่งจึงวางทาบกันได้
ส่วนเหตุผลที่รูปแบบแนว column เก็บสถิติ min กับ max ไว้ระดับ column chunk ใน metadata ท้ายคลัง เป็นสิ่งที่เอกสารของ Parquet กำหนดไว้ บทนี้ไม่ได้พิสูจน์ข้อนั้น
คลังของคอร์สนี้ไม่ใช่ Parquet มันยืมกลไกมาใช้ แล้ววัดผลบนตารางใบเดียวของตัวเอง ผลที่ได้จึงเป็นผลของตารางใบนี้ ไม่ใช่ค่าที่รูปแบบรับประกัน
บทนี้ไม่รายงานค่าประมาณความเลือกสรรสักตัว zone map คือการตัดทิ้ง ไม่ใช่การประมาณ
prune เทียบช่วงแล้วคืนรายการกลุ่ม มันไม่ได้เดาว่าจะตรงกี่แถว
การประมาณจำนวนแถวด้วย histogram และข้อสมมติเรื่องการกระจายตัว เป็นเรื่องของ คอร์สที่ 26 ซึ่งมีคำและตัวเลขของตัวเองอยู่แล้ว
ค่า 1,017 กับ 995 ในบทนี้เป็น จำนวนแถวที่นับได้จริง ไม่ใช่ค่าประมาณ และมันมีไว้พิสูจน์ว่าสองคำถามอยู่ระดับเดียวกัน
คำถามที่บทนี้ทิ้งไว้
หัวข้อที่มีชื่อว่า “คำถามที่บทนี้ทิ้งไว้”ถ้าลำดับที่เขียนเป็นตัวแปร แล้วเปลี่ยนลำดับนั้นจะเกิดอะไรขึ้น
ตารางในบทนี้เรียงตาม placed_at มาตั้งแต่ต้น zone map ของ column ตัวนั้นจึงทำงาน
และของ customer กับ total ไม่ทำงาน
คำถามถัดไปที่ตามมาเองคือ ถ้าเรียงใหม่ตาม column ที่ถามบ่อยกว่า จะได้ผลนั้นคืนมาไหม
บทที่ 7 เรียงตาม restaurant แล้ววัดทั้งสองฝั่ง ทั้งฝั่งที่ตื่นขึ้นและฝั่งที่
ทรุดลง เพราะตารางมีลำดับได้ลำดับเดียว การเลือกลำดับจึงเป็นการยกส่วนได้เปรียบ
จาก column หนึ่งไปให้อีก column
อ่านต่อที่ บทที่ 7 · เรียงเพื่อ column หนึ่ง คือจ่ายให้อีก column
-
Apache Parquet — Metadata (เข้าถึง 2026-09-10) — ที่มาของกลไกที่ว่ารูปแบบเก็บสถิติของ column chunk ไว้ใน metadata เพื่อให้ตัวอ่านตัดส่วนที่เงื่อนไขเอื้อมไม่ถึงทิ้งได้ · เป็นข้ออ้างเรื่องรูปแบบ ไม่ใช่ผลการวัดของคอร์สนี้
-
Apache Parquet — File Format (เข้าถึง 2026-09-10) — ที่มาของข้อที่ว่า metadata ถูกเขียนไว้หลังข้อมูล ซึ่งเป็นเหตุผล ที่ตัวอ่านหยิบสถิติทั้งคลังได้ก่อนแตะข้อมูลจริง
-
ตัวเลขทุกตัวในบทนี้มาจาก
python3 scripts/column-store/probe.py· ค่าที่ตรึงไว้อยู่ที่scripts/column-store/expected/column-store.txtหมวด### 6_zone_mapและหมวด### 10_ข้อที่ขัดกับตำรา -
fence สาม block ข้างบนยกมาจาก
scripts/column-store/colstore.pyบรรทัดที่ 324 และ 355 กับscripts/column-store/probe.pyบรรทัดที่ 135 · คอร์สนี้ไม่แก้อะไรในscripts/query-engine/qe/แม้แต่ byte เดียว
เช็กความเข้าใจ — บทที่ 6
ข้อ 1 / 3เงื่อนไขบน placed_at ในวันที่ 90 กับเงื่อนไขบน customer ระหว่าง 995 ถึง 1004 ให้ผลต่างกันอย่างไร และอะไรทำให้การเทียบนี้ยุติธรรม