คำถามใบเดิมที่แตะแค่ 2 จาก 6 column แตะ 304 หน้า ส่วน heap ต้องอ่าน 3153
บทที่ 1 เลือกคำถามที่แตะครบ 6 จาก 6 column โดยตั้งใจ เพื่อปิดสิ่งที่ column storecolumn storeคลังที่วางค่าของ column เดียวกันไว้ติดกัน ต่างจาก heap ของคอร์สที่ 26 ที่วางทั้งแถวไว้ติดกัน · คอร์สนี้เก็บตารางเดิมกลับด้าน แล้วแยกผลของการวางแนวออกจากผลของการเข้ารหัส ซึ่งเป็นสองเรื่องที่สรุปตามตำรารวมไว้เป็นก้อนเดียว ทำได้ และคลังแนวแถวทำไม่ได้ทิ้งไป
บทนี้เปิดมันกลับ คำถามยังเป็นคำถามใบเดิม ตารางยังเป็นตารางใบเดิม แต่คราวนี้ให้มันแตะ แค่ 2 จาก 6 column
คลัง column ทรง bit packing ตอบคำถามนั้นด้วย 304 หน้า ส่วน heap ต้องอ่าน 3153 หน้า
อีกเจ็ดบทมีอยู่เพราะการวัดหักล้างคำทำนายที่สมเหตุสมผล บทนี้ไม่ใช่
ถ้าคอร์สข้ามบทนี้ไป ผู้อ่านจะสรุปจากบทที่ 1 ว่าคลัง column ไม่มีข้อดีเลย ซึ่งผิด สิ่งที่บทที่ 1 วัดคือ การวางแนวอย่างเดียวแพ้ ไม่ใช่ว่าการวางแนวไม่ซื้ออะไรกลับมา
บทนี้วัดว่ามันซื้ออะไรกลับมา และจ่ายคืนมากที่สุดตอนไหน
คำถามใบเดิม ที่แตะแค่ 2 จาก 6 column
หัวข้อที่มีชื่อว่า “คำถามใบเดิม ที่แตะแค่ 2 จาก 6 column”คำถามใบเดียวกันทั้งสองฝั่ง ต่างกันที่ฝั่งคลัง column อ่านแค่ 2 จาก 6 column
คำถามที่ probe ถามอยู่ที่คีย์ คำถาม ในหมวด ### 1_ฉายภาพ column ของผลที่มันพิมพ์ออกมา
คือ SELECT total WHERE restaurant = ครัวคุณแม่
คำถามนี้ต้องการค่าจาก column สองตัว คือ restaurant สำหรับเงื่อนไข และ total
สำหรับผลลัพธ์ อีกสี่ตัวที่เหลือไม่มีใครขอ
# ---------- 1. query ที่แตะ 2 จาก 6 column เทียบกับ 6 จาก 6 ---------- two = ("restaurant", "total") p2_raw, v2 = scan(st_raw, two) p2_bp, _ = scan(st_bp, two) p6_raw, v6 = scan(st_raw, COLS) p6_bp, _ = scan(st_bp, COLS)st_bp เป็นคลังใบเดียวที่ถูกเรียกทั้งสองรอบ สิ่งที่ต่างกันคือ two กับ COLS
| ทรงที่วัด | คีย์ในผลของ probe | หน้าที่แตะ |
|---|---|---|
| heap · ตอบคำถามใบนี้ | heap ต้องอ่าน | 3153 |
| คลัง column · bit packing · แตะ 2 จาก 6 | column 2 จาก 6 · bitpack | 304 |
| คีย์ในผลของ probe | ค่า |
|---|---|
heap หารด้วย 2 column bitpack | 10.37 |
heap อ่านเท่าเดิมเพราะหนึ่งหน้าของมันถือทั้งแถว
หัวข้อที่มีชื่อว่า “heap อ่านเท่าเดิมเพราะหนึ่งหน้าของมันถือทั้งแถว”ค่าของ
heap ต้องอ่านเท่ากับค่าของการสแกนทั้งตาราง และนั่นคือทั้งหมดที่ heap ทำได้ในทรงนี้
ฝั่ง heap ในทรงนี้เดินทีละหน้า แล้วแกะทุกช่องในหน้านั้นออกมาเป็นแถว ก่อนจะดูค่าของ column ที่เงื่อนไขถาม
for pno in range(npages): page = pg.read_page(pno) for s in range(nslots(page)): row = decode(read_slot(page, s)) if row[3] == TARGET: target_rows += 1decode คืนทั้งแถว ไม่ใช่ค่าเดียว เพราะ byte ของทั้งหกค่าอยู่ติดกันในหน้าเดียวกัน
การหยิบ restaurant ขึ้นมาหนึ่งค่าจึงลาก byte ของอีกห้า column ขึ้นมาด้วยเสมอ
ค่าของคีย์ heap ต้องอ่าน ที่ 3153 จึงเท่ากับค่าของคีย์
หน้าที่แตะเมื่อสแกนทั้งตาราง ในหมวด ### heap พอดี เลขสองตัวที่เท่ากันนี้คือกลไก
ไม่ใช่ความบังเอิญ
คลังแนวแถวไม่มีที่ให้เลือกไม่อ่าน หน่วยที่เล็กที่สุดที่มันหยิบขึ้นมาได้คือหนึ่งหน้า และหนึ่งหน้าถือทุก column ของแถวที่อยู่ในนั้น
projection pushdown คือสิทธิ์ที่จะไม่เปิด column ที่ไม่ได้ถาม
หัวข้อที่มีชื่อว่า “projection pushdown คือสิทธิ์ที่จะไม่เปิด column ที่ไม่ได้ถาม”กลไกอยู่ที่คลังเก็บแต่ละ column ไว้คนละที่ คำถามจึงเปิดเฉพาะตัวที่มันขอ
projection pushdownprojection pushdownการอ่านเฉพาะ column ที่คำถามขอ · คำถาม SELECT total WHERE restaurant = ครัวคุณแม่ ที่แตะ 2 จาก 6 column ในทรง bitpack แตะ 304 หน้า ส่วน heap ต้องอ่าน 3153 หน้า · ทรงเดียวกันที่แตะครบ 6 จาก 6 แตะ 989 หน้า และค่าที่ถอดรหัสขึ้นจาก 400,000 เป็น 1,200,000 ค่า คือการส่งรายชื่อ column ที่คำถามต้องการลงไปถึงชั้นที่อ่านคลัง แทนที่จะอ่านทุกอย่างขึ้นมาแล้วค่อยตัดทิ้งข้างบน
ในคลังของคอร์สนี้ กลไกนั้นเป็นเรื่องของที่อยู่ล้วนๆ
def path(self, col): return os.path.join(self.dir, col + ".col") def _pager(self, col): p = self.pagers.get(col) if p is None: p = Pager(self.store.path(col), "rb") self.pagers[col] = p return p_pager ถูกเรียกก็ต่อเมื่อมีใครขอค่าจาก column ตัวนั้น คำถามที่แตะ 2 จาก 6 column
จึงไม่เคยเปิดที่เก็บของอีกสี่ตัว และตัวนับ Pager.reads ของอีกสี่ตัวไม่เคยเดิน
นี่คือสิ่งที่การวางแนวซื้อกลับมา ไม่ใช่การอ่านที่เร็วกว่า แต่เป็นสิทธิ์ที่จะไม่อ่าน
ตัวแปรคือจำนวน column ที่แตะ ไม่ใช่การวางแนว
หัวข้อที่มีชื่อว่า “ตัวแปรคือจำนวน column ที่แตะ ไม่ใช่การวางแนว”ทรงเดียวกันสองรอบ ต่างกันที่จำนวน column ที่คำถามขอ แล้วส่วนได้เปรียบหดลง
ทรง bit packing ตอบทั้งสองคำถามจากคลังใบเดียวกัน row group ขนาดเดียวกัน และการเข้ารหัสชุดเดียวกัน สิ่งเดียวที่ขยับคือจำนวน column ที่แตะ
| ทรงที่วัด | คีย์ในผลของ probe | หน้าที่แตะ |
|---|---|---|
| แตะ 2 จาก 6 column | column 2 จาก 6 · bitpack | 304 |
| แตะ 6 จาก 6 column | column 6 จาก 6 · bitpack | 989 |
| คีย์ในผลของ probe | ค่า |
|---|---|
heap หารด้วย 2 column bitpack | 10.37 |
heap หารด้วย 6 column bitpack | 3.19 |
แถวล่างคือค่าเดียวกับที่บทที่ 1 วางไว้ ส่วนได้เปรียบไม่ได้หายไป มันหดลง และมันหดลงตามจำนวน column ที่คำถามแตะ
กฎที่อ่านออกมาจากคู่นี้ คลัง column จ่ายคืนมากที่สุดตอนที่คำถามแคบที่สุด และคำถามที่กว้างที่สุดคือคำถามที่มันได้เปรียบน้อยที่สุด
ตารางในงานวิเคราะห์ข้อมูลมี column หลายสิบตัว ส่วนคำถามหนึ่งข้อแตะไม่กี่ตัว อัตราส่วนของ 2 ต่อ 6 ในบทนี้จึงเป็นทรงที่ ให้เปรียบฝั่ง heap มาก เมื่อเทียบกับ ตารางจริง
ตารางใบนี้มี 6 column และนั่นคือสิ่งที่ probe วัด ส่วนตารางที่กว้างกว่าคือทรงที่ probe ไม่ได้วัด บทนี้จึงไม่อ้างตัวเลขของมัน
แม้แต่ทรงที่ยังไม่เข้ารหัสก็ชนะ ถ้าคำถามแคบพอ
หัวข้อที่มีชื่อว่า “แม้แต่ทรงที่ยังไม่เข้ารหัสก็ชนะ ถ้าคำถามแคบพอ”ค่าเดียวกันสองทรง แล้วทรงที่บทที่ 1 บอกว่าแพ้ กลับมาชนะเมื่อคำถามแคบลง
บทที่ 1 วัดทรง plain encoding ที่แตะครบ 6 จาก 6 column แล้วได้ 3320 หน้า ซึ่งมากกว่า heap ที่ 3153 หน้า ข้อสรุปตอนนั้นคือการวางแนวอย่างเดียวแพ้
คำถามที่แคบลงเปลี่ยนผลของทรงเดิมนั้น
| ทรงที่วัด | คีย์ในผลของ probe | หน้าที่แตะ |
|---|---|---|
| heap · ตอบคำถามใบนี้ | heap ต้องอ่าน | 3153 |
| คลัง column ที่ยังไม่เข้ารหัส · แตะ 2 จาก 6 | column 2 จาก 6 · raw | 2247 |
| คลัง column ที่ยังไม่เข้ารหัส · แตะ 6 จาก 6 | column 6 จาก 6 · raw | 3320 |
ไม่มีการเข้ารหัสตัวไหนเข้ามาช่วยในตารางนี้ ทั้งสามแถวเป็นค่าที่วัดจากคลังทรงเดิม ทั้งหมด สิ่งที่ขยับคือจำนวน column ที่แตะอย่างเดียว
ค่า 2247 น้อยกว่า 3153 ส่วนค่า 3320 มากกว่า ทรงเดียวกันจึงอยู่คนละฝั่ง ของ heap ขึ้นกับความกว้างของคำถาม
การวางแนวจึงมีส่วนได้เปรียบของตัวเองจริง มันเพียงไม่พอจะชนะตอนที่คำถามแตะครบทุกตัว และบทที่ 1 เลือกคำถามแบบนั้นเพื่อแยกมันออกมาให้เห็น
หน่วยที่สอง คือค่าที่ถอดรหัส และมันเดินไปทางเดียวกันในบทนี้
หัวข้อที่มีชื่อว่า “หน่วยที่สอง คือค่าที่ถอดรหัส และมันเดินไปทางเดียวกันในบทนี้”จำนวนค่าที่คลังต้องแกะออกมาเป็นตัวเลข ขยับตามจำนวน column ที่แตะเหมือนกัน
หน้าที่แตะไม่ใช่หน่วยเดียวที่ probe เก็บ อีกหน่วยหนึ่งคือจำนวนค่าที่ถูกถอดรหัสออกมา
ซึ่งคือค่า n ที่ scan นับไว้
| ทรงที่วัด | คีย์ในผลของ probe | ค่าที่ถอดรหัส |
|---|---|---|
| แตะ 2 จาก 6 column | ค่าที่ถอดรหัส 2 column | 400,000 |
| แตะ 6 จาก 6 column | ค่าที่ถอดรหัส 6 column | 1,200,000 |
ทั้งสองค่าคือจำนวนแถวของตาราง ซึ่งอยู่ที่คีย์ แถว = 200,000 คูณด้วยจำนวน column
ที่คำถามแตะพอดี เพราะการสแกนเต็มแกะทุกค่าในทุก column ที่มันเปิด
ในบทนี้สองหน่วยเดินไปทางเดียวกัน แตะ column น้อยลง ทั้งหน้าที่แตะและค่าที่ถอดรหัสก็ลดลง ทั้งคู่
จำไว้ว่ามันไม่ได้เดินด้วยกันเสมอ บทที่ 8 วัดกลไกที่ลดค่าที่ถอดรหัสได้จริง แต่ในทรงที่เงื่อนไขถามร้าน ครัวคุณแม่ มันไม่ลดหน้าที่แตะ สักหน้าเดียว นั่นคือเหตุผลที่คอร์สนี้รายงานสองหน่วยแยกกัน ไม่ใช่รวมเป็นตัวเลขเดียว
รูปเดียวของสามตัวเลขในบทนี้
หัวข้อที่มีชื่อว่า “รูปเดียวของสามตัวเลขในบทนี้”คำถามใบเดียว สองฝั่ง และเส้นที่แยกว่าฝั่งไหนมีสิทธิ์ไม่อ่านอะไร
flowchart TB Q["คำถาม · SELECT total WHERE restaurant = ครัวคุณแม่ · ขอค่าจาก column สองตัว"] Q --> H["heap · หนึ่งหน้าถือทั้งแถว · หยิบ restaurant ก็ลาก column ที่เหลือมาด้วย"] Q --> C["คลัง column · หนึ่ง column หนึ่งที่เก็บ · เปิด Pager เฉพาะตัวที่คำถามขอ"] H --> H1["ต้องอ่านทุกหน้า · 3153 หน้า"] C --> C1["แตะ 2 จาก 6 column · 304 หน้า"] C --> C2["แตะ 6 จาก 6 column · 989 หน้า"]
คำบรรยายภาพ: กิ่งซ้ายไม่มีทางแยก เพราะหนึ่งหน้าของ heap ถือทุก column ของแถวที่อยู่ในนั้น กิ่งขวาแตกเป็นสองใบที่ใช้คลังใบเดียวกัน ต่างกันที่จำนวน column ที่คำถามขอ ระยะจาก 304 ถึง 989 จึงเป็นราคาของความกว้างของคำถาม ไม่ใช่ราคาของการเข้ารหัส
สิ่งที่บทนี้ปิด และสิ่งที่บทถัดไปเปิด
หัวข้อที่มีชื่อว่า “สิ่งที่บทนี้ปิด และสิ่งที่บทถัดไปเปิด”รู้แล้วว่าการวางแนวซื้ออะไรกลับมา ยังไม่รู้ว่าการเข้ารหัสแต่ละแบบทำอะไรกับ column แต่ละตัว
บทที่ 1 กับบทนี้ประกอบกันเป็นธีสิสครึ่งแรกของคอร์ส
- การวางแนว ให้สิทธิ์ที่จะไม่อ่าน column ที่ไม่ได้ถาม · สิทธิ์นั้นจ่ายคืน
จากคีย์
heap หารด้วย 2 column bitpackที่ 10.37 ลงมาเหลือ 3.19 เมื่อคำถามกว้างขึ้นจนแตะครบ - การเข้ารหัส ตัดสินว่าค่าหนึ่งค่ากินที่เท่าไร · บทที่ 1 วัดว่ามันคือฝั่งที่พลิกตัวเลข ตอนคำถามแตะครบ 6 จาก 6 column
ทุกตัวเลขในบทนี้ใช้ทรง bit packing กับทรง plain encoding เป็นก้อนตันสองก้อน โดยไม่ถามว่า ข้างในก้อนนั้นมีอะไร
บทที่ 3 เปิดก้อนนั้นออกทีละ column แล้วเจอว่าการเข้ารหัสที่ตำราเรียกว่าตัวย่อขนาด ทำให้สาม column ในหกตัว ใหญ่ขึ้น
-
Apache Parquet — File Format (เข้าถึง 2026-09-10) — ที่มาของข้อที่ว่ารูปแบบแนว column เก็บตำแหน่งของทุก column chunk ไว้ใน metadata เพื่อให้คำถามกระโดดไปอ่านเฉพาะ column ที่มันขอได้ · คลังของคอร์สนี้ ไม่ใช่ Parquet มันใช้ที่เก็บแยกต่อหนึ่ง column แทน
-
ตัวเลขทุกตัวในบทนี้มาจาก
python3 scripts/column-store/probe.pyหมวด### 1_ฉายภาพ columnและหมวด### heap· ค่าที่ตรึงไว้อยู่ที่scripts/column-store/expected/column-store.txt -
fence ของฝั่ง heap ยกมาจาก
scripts/column-store/probe.pyบรรทัดที่ 243 ซึ่งเรียกPagerกับdecodeของคอร์สที่ 26 · คอร์สนี้ไม่แก้อะไรในscripts/query-engine/qe/แม้แต่ byte เดียว -
fence ของฝั่งคลัง column ยกมาจาก
scripts/column-store/colstore.pyบรรทัดที่ 338 และบรรทัดที่ 378 · หน้าที่แตะทุกตัวนับด้วยPager.readsตัวเดียวกันทั้งสองฝั่ง
เช็กความเข้าใจ — บทที่ 2
ข้อ 1 / 3คำถาม SELECT total WHERE restaurant = ครัวคุณแม่ บนคลัง column ทรง bit packing ที่แตะแค่ 2 จาก 6 column แตะกี่หน้า และ heap ต้องอ่านกี่หน้า