ข้าม​ไป​ยัง​เนื้อหา

หยิบ​กลับ​มา​หนึ่ง​แถว​ทั้ง​แถว heap แตะ 1 หน้า ส่วน​คลัง column แตะ 42 หน้า

คำถาม​ที่​คลัง column ทำได้​แย่​ที่สุด​ไม่ใช่​คำถาม​ที่​แตะ​ทุก column มัน​คือ​คำถาม​ที่​บอก​ว่า เอา​แถว​นี้​มา​ทั้ง​แถว โดย​รู้ตัว​ชี้​ของ​แถว​นั้น​อยู่​แล้ว

heap ตอบ​คำถาม​นั้น​ด้วย​การ​แตะ​หน้า​เดียว เพราะ​ค่า​ทุก column ของ​แถว​นั้น​เขียน​ติด​กัน​อยู่​ใน​หน้า​เดียว ส่วน​คลัง column ต้อง​ไป​หยิบ​ที​ละ​ที่​แล้ว​ประกอบ​กลับ

การ​หยิบ​แบบ​นี้​เรียก​ว่า point lookuppoint lookupการ​หยิบ​หนึ่ง​แถว​ทั้ง​แถว​ด้วย​ตัว​ชี้​ที่​รู้​อยู่​แล้ว · heap แตะ 1 หน้า ส่วน​คลัง column แตะ 42 หน้า​ใน​ทรง bitpack และ 133 หน้า​ใน​ทรง raw · 42 หน้า​นั้น​แยก​ราย column ออก​มา​เป็น id 11 · customer 4 · status 2 · restaurant 2 · placed_at 11 และ total 12 · ที่​จ่าย​เพิ่ม​คือ dictionary ทั้ง​ใบ ซึ่ง​กิน​หน้าที่​เป็น dictionary ล้วน​รวม​ทุก row group ทั้ง 20 ก้อน​ไป 214 หน้าที่ id · 204 หน้าที่ placed_at และ 209 หน้าที่ total ค่า​สาม​ตัว​นี้​เป็น​ของ​ทั้ง column ไม่ใช่​ส่วน​หนึ่ง​ของ 42 และ​บท​นี้​วัด​ว่า​มัน​แพง​แค่​ไหน

คำ​ทำนาย​ที่​ผู้​เขียน probe เขียน​ไว้​ก่อน​วัด​คือ หนึ่ง​หน้า​ต่อ​หนึ่ง column ค่าที่​วัด​ได้​คือ 42 หน้า ใน​ทรง bitpack และ 133 หน้า ใน​ทรง raw

📦 ตัวเลข​ทุก​ตัว​ใน​บท​นี้​รัน​เอง​ได้ ไม่​ต้อง​มี Docker และ​ไม่​ต้อง​ต่อ​เน็ต

สั่ง python3 scripts/column-store/probe.py ได้​เลย มัน​ใช้ Python 3 กับ standard library ล้วน และ​ไม่มี container ให้​เก็บ​กวาด​ทีหลัง

ค่าที่​วัด​ได้​ทุก​ตัว​ที่​บท​นี้​พิมพ์​มี​คีย์​ของ​ตัว​เอง​อยู่​ใน scripts/column-store/expected/column-store.txt และ​ชื่อ​หมวด​กับ​ชื่อ​คีย์​เขียน​กำกับ​ไว้​ใน​ทุก​ตาราง

บท​นี้​ไม่​สร้าง​ตัวเลข​ใหม่​สัก​ตัว และ​ไม่​คำนวณ​อัตราส่วน​หรือ​ส่วน​ต่าง​ขึ้น​เอง​แม้แต่​ค่า​เดียว

คำ​ทำนาย​คือ​หนึ่ง​หน้า​ต่อ​หนึ่ง column และ​การ​วัด​หัก​ล้าง​มัน

หัวข้อ​ที่​มีชื่อ​ว่า “คำ​ทำนาย​คือ​หนึ่ง​หน้า​ต่อ​หนึ่ง column และ​การ​วัด​หัก​ล้าง​มัน”

heap แตะ​หน้า​เดียว ส่วน​คลัง column ทั้ง​สอง​ทรง​แตะ​มากกว่า​ที่​คำ​ทำนาย​ก่อน​วัด​เขียน​ไว้

ตัว​ชี้​ของ​แถว​ถูก​ส่ง​เข้าไป​ให้​ทั้ง​สอง​ฝั่ง​เหมือน​กัน ไม่มี​ฝั่ง​ไหน​ต้อง​ค้นหา​ก่อน สิ่ง​ที่​เหลือ​อยู่ จึง​เป็น​ราคา​ของ​การ​ไป​หยิบ​ค่า​จาก​ที่​ที่​มัน​ถูก​วาง​ไว้

ทรง​ที่​วัด · หมวด ### 2_SELECT_ดาวคีย์​ใน​ผล​ของ probeหน้าที่​แตะ
heap · หยิบ​หนึ่ง​แถว​โดย​มี​ตัว​ชี้หนึ่งแถว · heap (มี rid)1
คลัง column ที่​ยัง​ไม่​เข้า​รหัสหนึ่งแถว · column raw133
คลัง column · bit packingหนึ่งแถว · column bitpack42

แถว​ล่าง​สุด​คือ​ทรง​เดียว​กับ​ที่​ชนะ heap ขาดตอน​สแกน​ทั้ง​ตาราง ทรง​นั้น​ไม่​ได้​เปลี่ยน​อะไร​เลย เปลี่ยน​แค่​คำถาม แล้ว​มัน​กลาย​เป็น​ฝั่ง​ที่​แพ้

ทรง​ที่​วัด · หมวด ### 2_SELECT_ดาวคีย์​ใน​ผล​ของ probeหน้าที่​แตะ
heap · สแกน​ทั้ง​ตารางสแกนทั้งตาราง · heap3153
คลัง column ที่​ยัง​ไม่​เข้า​รหัส · สแกน​ทั้ง​ตารางสแกนทั้งตาราง · column raw3320
คลัง column · bit packing · สแกน​ทั้ง​ตารางสแกนทั้งตาราง · column bitpack989
🔁 ตัวเลข​สาม​ตัว​ที่​บท​นี้​ยืม​มา​จาก​บท​ก่อน​หน้า

ค่า 3153 · 3320 และ 989 เป็น​พาด​หัว​ของ บท​ที่ 1 ซึ่ง​เป็น​เจ้าของ​ทั้ง​สาม​ค่า

หมวด ### 2_SELECT_ดาว เก็บ​ทั้ง​สาม​ค่า​ไว้​เป็น​คีย์​ของ​ตัว​เอง​ด้วย ตาราง​ข้าง​บน​จึง​อ่าน​จาก หมวด​เดียว​กับ​ที่​บท​นี้​ใช้ ไม่​ได้​ลอก​ข้าม​หมวด​มา

dictionary ทั้ง​ใบ​คือ​ของ​ที่​ต้อง​อ่าน​ก่อน​ถอดรหัส​หนึ่ง​ค่า

หัวข้อ​ที่​มีชื่อ​ว่า “dictionary ทั้ง​ใบ​คือ​ของ​ที่​ต้อง​อ่าน​ก่อน​ถอดรหัส​หนึ่ง​ค่า”

สิ่ง​ที่​ทำให้​เลข​บวม​ไม่ใช่​จำนวน column ที่​ต้อง​แตะ แต่​เป็น​สิ่ง​ที่​ต้อง​อ่าน​ก่อน​แตะ​ค่า​นั้น

ทรง bitpack เก็บ​ตัว​รหัส​ไว้​ที่​หนึ่ง และ​เก็บ​รายการ​ค่าที่​ไม่​ซ้ำ​ไว้​อีก​ที่​หนึ่ง ตัว​รหัส​ตัว​เดียว แปลก​ลับ​เป็น​ค่า​จริง​ไม่​ได้ ถ้า​ไม่มี​รายการ​นั้น​อยู่​ใน​มือ

scripts/column-store/colstore.py:433 — ทาง​ที่ bit packing ใช้​ถอดรหัส​ค่า​เดียว
if enc in (DICT, BITPACK):
vs = self.dict_of(col, g) # ต้องมี dictionary ทั้งใบก่อนเสมอ
if enc == DICT:
w = m["bits"] // 8
fmt = {1: "<B", 2: "<H", 4: "<I"}[w]
b = self.read_range(col, m["off"] + m["codes_off"] + w * j, w)
return vs[struct.unpack(fmt, b)[0]]
a, ln = bit_range(j, m["bits"])
b = self.read_range(col, m["off"] + m["codes_off"] + a, ln)
v = (int.from_bytes(b, "little") >> ((j * m["bits"]) % 8)) & ((1 << m["bits"]) - 1)
return vs[v]

บรรทัด vs = self.dict_of(col, g) คือ​ราคา​ที่​ซ่อน​อยู่ การ​อ่าน​ตัว​รหัส​เอง​แตะ​หน้า​น้อย​มาก แต่ dictionary ที่​ต้อง​มี​ก่อน​นั้น​เป็น​ของ​ทั้ง​ใบ

probe แยก​หน้าที่​เป็น dictionary ล้วน​ออก​มา​ราย​ตัว​ให้​แล้ว ค่า​ข้าง​ล่าง​นับ​หน้าที่​ไม่​ซ้ำ ของ​ทุก row grouprow groupก้อน​ของ​แถว​ที่​คลัง​แนว column ตัด​ออก​มา​เป็น​หน่วย​เดียวกัน ทุก column ใน​ก้อน​เดียวกัน​ถือ​แถว​ชุด​เดียวกัน · ขอบเขต​ของ​ก้อน​เป็น​สิ่ง​ที่​ตัดสิน​ว่า dictionary ถูก​คัด​ลอก​ซ้ำ​กี่​ใบ และ zone map ข้าม​อะไร​ได้​บ้าง รวม​กัน​ใน​ทรง bitpack

columnคีย์ หน้าที่เป็น dictionary ล้วนต่อ column (bitpack)หน้า
idid214
customercustomer58
statusstatus18
restaurantrestaurant25
placed_atplaced_at204
totaltotal209

column ที่​มี​ค่า​ไม่​ซ้ำ​เยอะ​จ่าย dictionary ที่​ใหญ่​ที่สุด และ column พวก​นั้น​คือ id placed_at กับ total ซึ่ง​เป็น​สาม​ตัว​เดียว​กับ​ที่​บท​ที่ 3 วัด​ว่า dictionary ทำให้​ใหญ่​ขึ้น

flowchart TB
  Q["หยิบหนึ่งแถวทั้งแถว · มีตัวชี้ของแถวอยู่แล้ว"]
  Q --> H["heap · ค่าทุก column ของแถวนี้อยู่ในหน้าเดียว · แตะ 1 หน้า"]
  Q --> C["คลัง column · ค่าของแถวนี้กระจายอยู่ใน column chunk คนละก้อน"]
  C --> C1["ทรง raw · สตริงความกว้างไม่คงที่ กระโดดไปค่าเดียวไม่ได้ · แตะ 133 หน้า"]
  C --> C2["ทรง bitpack · ต้องอ่าน dictionary ของก้อนนั้นทั้งใบก่อนถอดหนึ่งค่า · แตะ 42 หน้า"]

คำ​บรรยาย​ภาพ: กิ่ง​ซ้าย​คือ​คลัง​แนว​แถว​ที่​จบ​ใน​หน้า​เดียว กิ่ง​ขวา​แตก​เป็น​สอง​ทรง​ที่​วาง​แนว​เหมือน​กัน และ​แพ้​ด้วย​เหตุผล​คนละ​ข้อ ทรง​หนึ่ง​แพ้​ที่​ความ​กว้าง​ที่​ไม่​คงที่ อีก​ทรง​แพ้​ที่ dictionary

ทรง​ที่​ยัง​ไม่​เข้า​รหัส​แพง​กว่า​ทรง​ที่​เข้า​รหัส​แล้ว

หัวข้อ​ที่​มีชื่อ​ว่า “ทรง​ที่​ยัง​ไม่​เข้า​รหัส​แพง​กว่า​ทรง​ที่​เข้า​รหัส​แล้ว”

แยก​หน้าที่​แตะ​ออก​ราย​ตัว แล้ว​คน​ที่​กิน​หน้า​ใน​สอง​ทรง​นั้น​เป็น​คนละ​กลุ่ม​กัน

ส่วน​ของ column ตัว​เดียว​ที่​อยู่​ใน row group หนึ่ง​ก้อน​เรียก​ว่า column chunkcolumn chunkส่วน​ของ column หนึ่ง​ตัว​ภายใน row group หนึ่ง​ก้อน · มัน​เลือก​การ​เข้า​รหัส​และ​ความ​กว้าง​ของ​ตัว​รหัส​เป็น​ของ​ตัว​เอง ค่า​สอง​อย่าง​นั้น​จึง​เป็น​ค่า​ต่อ​ก้อน ไม่ใช่​ค่า​ต่อ​ทั้ง column การ​หยิบ​ค่า​เดียว​คือ​การ​เปิด chunk ของ​แถว​นั้น​ที​ละ column แล้ว​นับ​ว่า​ใคร​ทำให้​แตะ​กี่​หน้า

columnคีย์ หนึ่งแถว · หน้าที่แตะแยกราย column · rawคีย์ หนึ่งแถว · หน้าที่แตะแยกราย column · bitpack
id111
customer14
status252
restaurant1042
placed_at111
total112

ช่อง​ซ้าย​บอก​ว่า​ใน​ทรง raw ค่าที่​แตะ​หน้า​มาก​คือ status กับ restaurant ซึ่ง​เป็น column ที่​เก็บ​สตริง​ตาม​ที่​มัน​เป็น ส่วน column ที่​เป็น​เลข​จ่าย​น้อย​ที่สุด​เท่า​ที่​เป็น​ไป​ได้

เหตุผล​อยู่​ใน​บรรทัด​สุดท้าย​ของ one_value เอง

scripts/column-store/colstore.py:444 — ทรง​ที่​กระโดด​ไป​ค่า​เดียว​ไม่​ได้
# RAW ที่ความกว้างไม่คงที่ และ RLE — กระโดดตรงไปค่าเดียวไม่ได้ ต้องอ่านทั้ง chunk
return self.chunk(col, g)[j]

ค่าที่​กว้าง​คงที่​คำนวณ​ตำแหน่ง​ได้​ตรง​ตัว จึง​อ่าน​เฉพาะ​ช่วง byte ที่​ต้องการ ส่วน​สตริง​ที่​ยาว​ไม่​เท่า​กัน บอก​ไม่​ได้​ว่า​ค่าที่​ต้องการ​เริ่ม​ที่ byte ไหน จนกว่า​จะ​อ่าน​ทุก​ค่า​ก่อน​หน้า​มัน​แล้ว

ช่อง​ขวา​สลับ​กลุ่ม​ผู้​จ่าย ใน​ทรง bitpack status กับ restaurant ถูก​ลง​มา​เหลือ​หน้า​น้อย​ที่สุด และ​ตัว​ที่​ขึ้น​มา​แทน​คือ id placed_at กับ total ซึ่ง​เป็น​เจ้าของ dictionary ใบ​ใหญ่

นี่​คือ​ข้อ​ที่​กลับ​ด้าน​กับ​สัญชาตญาณ ทรง​ที่​ยัง​ไม่​เข้า​รหัส​ควร​จะ​เบา​กว่า​ใน​การ​หยิบ​ค่า​เดียว เพราะ​ไม่มี dictionary ให้​อ่าน แต่​ค่าที่​วัด​ได้​คือ 133 เทียบ 42

หยิบ​ที​ละ​แถว​มัน​แพง แต่​ราคา​ของ​มัน​ไม่​ได้​โต​ตาม​จำนวน​แถว​ที่​หยิบ

probe สุ่ม​แถว​มา k แถว​แล้ว​ดึง​ทั้ง​แถว​ทุก​แถว ฝั่ง heap นับ​หน้าที่​ไม่​ซ้ำ​ที่​แถว​เหล่า​นั้น​ตก​อยู่ ส่วน​ฝั่ง​คลัง column นับ​หน้าที่​แตะ​ทั้งหมด​จาก​ตัว​นับ​เดียวกัน

kคีย์ heap (หน้าที่ไม่ซ้ำ)คีย์ column bitpack
1142
1010297
10099910
1000868987
20001491987
30001934989
50002519989
100003051989

ทั้งหมด​อยู่​ใต้​คีย์ สุ่ม k แถวแล้วดึงทั้งแถว · หน้าที่แตะ ใน​หมวด ### 2_SELECT_ดาว

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

ที่​วัด · หมวด ### 2_SELECT_ดาวคีย์​ใน​ผล​ของ probeค่า
ค่า​แรก​ใน​ตะแกรง​ที่​คลัง column ไม่​แพ้​แล้วk ที่คลัง column เริ่มไม่แพ้ heap2000
ค่า​นั้น​คิด​เป็น​สัดส่วน​ของ​ตารางคิดเป็นสัดส่วนของตาราง (%)1.0
ค่า​นี้​คือ​ค่า​แรก​ใน​ตะแกรง ไม่ใช่​จุด​ที่​สอง​เส้น​ตัด​กัน

probe เดิน​ตะแกรง​ของ k ตาม​ค่าที่​ตั้ง​ไว้ มัน​ไม่​ได้​ค้นหา​จุด​ที่​สอง​เส้น​ตัด​กัน

ที่ k = 1000 คลัง column ยัง​แพ้ คือ 987 เทียบ 868 และ​ที่ k = 2000 มัน​ไม่​แพ้​แล้ว คือ 987 เทียบ 1491

จุด​ที่​สอง​เส้น​ตัด​กัน​จริง​อยู่​ที่ไหน​สัก​แห่ง​ระหว่าง​จุด​สอง​จุด​นั้น และ probe ไม่​ได้​วัด​มัน ค่า 2000 ที่​รายงาน​ออก​มา​จึง​เป็น​ค่า​แรก​ใน​ตะแกรง​ที่​เงื่อนไข​เป็น​จริง ไม่ใช่​จุด​ตัด

ใน​คำถาม​ของ​ร้าน ครัว​คุณ​แม่ late materialization ลด​ค่าที่​ถอดรหัส แต่​ไม่​ลด​หน้าที่​แตะ​สัก​หน้า

หัวข้อ​ที่​มีชื่อ​ว่า “ใน​คำถาม​ของ​ร้าน ครัว​คุณ​แม่ late materialization ลด​ค่าที่​ถอดรหัส แต่​ไม่​ลด​หน้าที่​แตะ​สัก​หน้า”

ยา​แก้​ที่​ตำรา​เสนอ​สำหรับ​ราคา​ข้อ​นี้ ให้​ผล​ไม่​เท่า​กัน​ใน​สอง​หน่วย และ​ไม่​เท่า​กัน​ใน​สี่​ทรง​ที่ probe วัด

ยา​แก้​ที่​ตำรา​เสนอ​คือ late materializationlate materializationการ​ถอดรหัส column เงื่อนไข​ก่อน แล้ว​ค่อย​ดึง​ค่า​ของ​แถว​ที่​ตรง​จริง · ผล​ของ​มัน​ต่าง​กัน​ตาม​ทรง​และ​ตาม​คำถาม · ใน​ทรง bitpack ของ​ร้าน ครัว​คุณ​แม่ ค่าที่​ถอดรหัส​ลง​จาก 400,000 เหลือ 211,934 ค่า แต่​หน้าที่​แตะ​ค้าง​ที่ 277 หน้า​ทั้ง​สอง​ทาง และ​เมื่อ​นับ dictionary ที่​ต้อง​อ่าน​ด้วย ค่าที่​ถอดรหัส​ขึ้น​เป็น 406,972 ค่า ซึ่ง​มากกว่า​ทาง​ที่​มัน​มา​แทน · ส่วน​ใน​ทรง raw ของ​ลูกค้า 995 หน้าที่​แตะ​ลง​จาก 196 เหลือ 78 หน้า คือ​ถอดรหัส column ที่​เป็น​เงื่อนไข​ก่อน แล้ว​ค่อย​ไป​ดึง​ค่า​ของ column ที่​ต้อง​ส่ง​ออก เฉพาะ​แถว​ที่​ตรง​เงื่อนไข​จริง

scripts/column-store/probe.py:164 — ลำดับ​ของ late materialization
def late_mat(store, pred_col, pred_val, payload_col):
"""late materialization: ถอดรหัส column เงื่อนไขก่อน แล้วค่อยไปหยิบเฉพาะแถวที่ตรง"""
q = cs.Q(store)
matched = []
for g in range(store.ngroups):
vals = q.chunk(pred_col, g)
for j, v in enumerate(vals):
if v == pred_val:
matched.append((g, j))
pred_pages = q.reads_of(pred_col)
for g, j in matched:
q.one_value(payload_col, g, j)
late_pages = q.reads_of(payload_col)
q.close()

ทาง​ที่​มัน​มา​แทน​คือ​การ​ถอดรหัส​ทั้ง​สอง column ทั้ง​ก้อน แล้ว​ค่อย​ทิ้ง​แถว​ที่​ไม่​ตรง ค่า​ของ​ทาง​นั้น​คือ​คีย์​ที่​ลงท้าย​ด้วย _early

ที่​วัด · หมวด ### 9_late_materialization · ทรง ร้าน ครัวคุณแม่ · payload total เข้ารหัสแบบ bitpackคีย์​ใน​ผล​ของ probeค่า
แถว​ที่​สแกน​ไป​ทั้งหมดแถวที่สแกน200,000
แถว​ที่​ตรง​เงื่อนไขแถวที่ตรง11,934
row group ที่​มี​แถว​ตรง​อยู่row group ที่มีแถวตรงอยู่20
ค่าที่​ถอดรหัส · ทาง​ที่​มัน​มา​แทนค่าที่ถอดรหัส_early400,000
ค่าที่​ถอดรหัส · นับ​เฉพาะ​ตัว​รหัสค่าที่ถอดรหัส_late_เฉพาะรหัส211,934
หน้าที่​แตะ​ที่ column ซึ่ง​ดึง​ออก​มา · ทาง​ที่​มัน​มา​แทนหน้าที่แตะ column ที่ดึงออกมา_early277
หน้าที่​แตะ​ที่ column ซึ่ง​ดึง​ออก​มา · ทาง lateหน้าที่แตะ column ที่ดึงออกมา_late277

สอง​แถว​ล่าง​เป็น​ค่า​เดียวกัน ใน​ทรง​นี้​กลไก​นี้​ไม่​ลด​หน้าที่​แตะ​สัก​หน้า ทั้ง​ที่​มัน​ลด​ค่าที่​ถอดรหัส​ลง​จริง · อีก​สาม​ทรง​อยู่​ใน​หัวข้อ​ถัด​ไป และ​สอง​ใน​นั้น​ลด​หน้าที่​แตะ​ลง​จริง

เหตุผล​อยู่​ที่​แถว row group ที่มีแถวตรงอยู่ ซึ่ง​เท่ากับ​จำนวน​ก้อน​ทั้ง​คลัง แถว​ที่​ตรง​เงื่อนไข กระจาย​อยู่​ทุก​ก้อน การ​ข้าม​ก้อน​จึง​ทำ​ไม่​ได้ และ​หน้าที่​ต้อง​เปิด​ก็​เท่า​เดิม

dictionary กิน​กลไก​นี้​ทิ้ง และ​ทรง​ที่​มัน​ช่วย​คือ​ทรง raw

หัวข้อ​ที่​มีชื่อ​ว่า “dictionary กิน​กลไก​นี้​ทิ้ง และ​ทรง​ที่​มัน​ช่วย​คือ​ทรง raw”

เมื่อ​นับ dictionary ที่​ต้อง​อ่าน​เข้าไป​ด้วย ค่าที่​ถอดรหัส​วิ่ง​กลับ​ไป​หา​ทาง​ที่​มัน​มา​แทน และ​ทรง​หนึ่ง​วิ่ง​เลย​ไป

การ​ถอดรหัส​ตัว​รหัส​หนึ่ง​ตัว​ต้อง​มี dictionary ทั้ง​ใบ​ก่อน ตาม​ที่ one_value บังคับ​ไว้ probe จึง​เก็บ​ค่าที่​นับ dictionary เข้าไป​ด้วย​เป็น​คีย์​ของ​ตัว​เอง

ทรง · หมวด ### 9_late_materializationค่าที่ถอดรหัส_earlyค่าที่ถอดรหัส_late_เฉพาะรหัสค่าที่ถอดรหัส_late_รวม dictionary
ร้าน ครัวคุณแม่ · payload total เข้ารหัสแบบ bitpack400,000211,934406,972
ร้าน ครัวคุณแม่ · payload total แบบ raw400,000211,934211,934
ลูกค้า 995 · payload total เข้ารหัสแบบ bitpack400,000200,097395,135
ลูกค้า 995 · payload total แบบ raw400,000200,097200,097

แถว​ที่​เป็น bitpack ทั้ง​สอง​แถว​มี​ช่อง​ขวา​สุด​วิ่ง​กลับ​ขึ้น​ไป​หา​ช่อง​ซ้าย​สุด และ​แถว​บน​สุด​วิ่ง​เลย​ไป​แล้ว คือ 406,972 มากกว่า 400,000 ซึ่ง​แปล​ว่า​ใน​ทรง​นั้น​กลไก​นี้​ถอดรหัส​มากกว่า​ทาง​ที่​มัน​มา​แทน ส่วน​แถว​ที่​เป็น raw มี​ช่อง​ขวา​สุด​เท่ากับ​ช่อง​กลาง เพราะ​ทรง​นั้น​ไม่มี dictionary ให้​อ่าน

หน้าที่​แตะ​บอก​เรื่อง​เดียวกัน​ด้วย​หน่วย​อีก​หน่วย

ทรง · หมวด ### 9_late_materializationแถวที่ตรงหน้าที่แตะ column ที่ดึงออกมา_earlyหน้าที่แตะ column ที่ดึงออกมา_late
ร้าน ครัวคุณแม่ · payload total เข้ารหัสแบบ bitpack11,934277277
ร้าน ครัวคุณแม่ · payload total แบบ raw11,934196196
ลูกค้า 995 · payload total เข้ารหัสแบบ bitpack97277258
ลูกค้า 995 · payload total แบบ raw9719678

แถว​ล่าง​สุด​คือ​แถว​เดียว​ใน​ตาราง​ที่​หน้าที่​แตะ​ลด​ลง​อย่าง​เห็น​ได้ และ​มัน​เป็น​ทรง raw ที่​เงื่อนไข​ตรง​แค่ 97 แถว

ประโยค​ที่​บท​นี้​หัก​ล้าง

ประโยค​ที่​พบ​ทั่วไป​คือ ราคา​ของ​การ​ประกอบ​แถว​กลับ​คืน​แก้​ได้​ด้วย late materialization

ค่าที่​วัด​ได้​บอก​ว่า​กลไก​นี้​จ่าย​คืน​น้อย​ที่สุด​ตรง​ที่​ตำรา​แนะนำ​ให้​ใช้​การ​เข้า​รหัส​หนัก​ที่สุด ทรง bitpack ที่​ลูกค้า 995 ลด​หน้าที่​แตะ​จาก 277 เหลือ 258 ขณะ​ที่​ทรง raw ใน​คำถาม​เดียวกัน​ลด​จาก 196 เหลือ 78

การ​เข้า​รหัส​ที่​ทำให้​การ​สแกน​ชนะ​ใน​บท​ที่ 1 คือ​สิ่ง​ที่​กิน​กลไก​นี้​ทิ้ง​ใน​บท​นี้ และ​นั่น​คือ ราคา​อีก​ด้าน​ของ​การ​เลือก ซึ่ง​คอร์ส​นี้​เจอ​มา​แล้ว​ทุก​บท

ทุก​แถว​ใน​ตาราง​นี้​ชี้​กลับ​ไป​ที่​บท​ที่​วัด​มัน และ​ตาราง​ถัด​ไป​คือ​แถว​ที่​คอร์ส​นี้​ยัง​ทำ​ไม่​ได้

ธี​สิ​ส​ของ​คอร์ส​คือ​การ​วาง​แนว​กับ​การ​เข้า​รหัส​เป็น​คนละ​เรื่อง สรุป​ตาม​ตำรา​รวม​สอง​อย่าง​นี้​ไว้​เป็น ก้อน​เดียว และ​การ​รวม​นั้น​วัด​ได้​ว่า​ผิด​ตั้งแต่​บท​แรก

ข้อ​อ้าง​ที่​วัด​ได้บท​ที่​พิสูจน์​มัน
การ​วาง​แนว​อย่าง​เดียว​อ่าน​มากกว่า​คลัง​แนว​แถว​ที่​มัน​มา​แทนบท​ที่ 1
สิ่ง​ที่​การ​วาง​แนว​ซื้อ​กลับ​มา​คือ​สิทธิ์​ที่​จะ​ไม่​อ่าน column ที่​ไม่​ได้​ถามบท​ที่ 2
dictionary encoding ไม่​ได้​ทำให้​เล็ก​ลง​เสมอ เงื่อนไข​อยู่​ที่​ขอบเขต​ของ row groupบท​ที่ 3
ความ​กว้าง​ของ​ตัว​รหัส​เป็น​ตัวแปร​ที่​เลือก​ได้ ไม่ใช่ byte เต็ม​เสมอบท​ที่ 4
ชื่อ​ของ​การ​เข้า​รหัส​ไม่​ได้​บอก​ว่า​มัน​ทำ​อะไร รูป​ของ​ข้อมูล​ต่าง​หาก​ที่​บอกบท​ที่ 5
zone map ตัด​ก้อน​ทิ้ง​ได้​ก็​ต่อ​เมื่อ​ค่า​ใน column นั้น​เดิน​ตาม​ลำดับ​ที่​เขียนบท​ที่ 6
ตาราง​มี​ลำดับ​ได้​ลำดับ​เดียว การ​เลือก sort key จึง​ยก​ส่วน​ได้​เปรียบ​จาก column หนึ่ง​ไป​ให้​อีก columnบท​ที่ 7
ราคา​ของ​การ​วาง​แนว​คือ​การ​ประกอบ​แถว​กลับ​คืน และ​ยา​แก้​ของ​ตำรา​ถูก​การ​เข้า​รหัส​กิน​ทิ้งบท​นี้

รูป​แบบ ParquetApache Parquetรูป​แบบ​ของ​คลัง​แนว column ที่​วางข้อ​มูล​เป็น row group และ​เก็บ min/max กับ​ตำแหน่ง​ของ​ทุก column chunk ไว้​ใน metadata ท้าย​คลัง · คอร์ส​นี้​อ้าง​มัน​ใน​ฐานะ​รูป​แบบ​เท่านั้น สิ่ง​ที่​วัด​จริง​คือ​คลัง​ที่ colstore.py สร้าง​ขึ้น ซึ่ง​ไม่ใช่ Parquet วางข้อ​มูล​เป็น row group และ​เก็บ metadata ไว้​ท้าย​คลัง คอร์ส​นี้​ยืม​รูป กับ​ศัพท์​มา​ใช้ แต่​คลัง​ที่​วัด​คือ​คลัง​ที่ colstore.py เขียน ไม่ใช่ Parquet

แถว​ข้าง​ล่าง​เป็น​สิ่ง​ที่​คอร์ส​นี้​ไม่​ได้​วัด และ​เหตุผล​ของ​แต่ละ​แถว​ต่าง​กัน การ​เขียน​ถึง​มัน​ใน​ฐานะ ข้อ​อ้าง​จึง​เป็นการ​อ้าง​เกิน​สิ่ง​ที่ probe พิสูจน์​ได้

แถว​ที่​คอร์ส​นี้​ยัง​ทำ​ไม่​ได้เหตุผล
vectorized execution และ SIMDprobe ไม่​ได้​วัด​มัน​สัก​ทรง และ​การ​วัด​มัน​ต้อง​ใช้​หน่วย​เวลา
การ​บีบ​อัด​ทั่วไป​อย่าง Snappy · gzip · zstdprobe วัด​การ​เข้า​รหัส ไม่​ได้​วัด​ตัว​บีบ​อัด​สัก​ตัว
Parquet · ORC · Arrow ของ​จริงคอร์ส​นี้​อ้าง​รูป​แบบ​ได้​เฉพาะ​เรื่อง​กลไก ไม่ใช่​ใน​ฐานะ​ผล​การ​วัด
การ​เขียน​และ​การ​อัปเดต​แถว​ใน​คลัง​แนว columnprobe เขียน​คลัง​ครั้ง​เดียว​แล้ว​วัด​การ​อ่าน​อย่าง​เดียว
การ​ขนาน​และ I/O จริง​ของ diskPager นับ​ครั้ง​ที่​อ่าน​หน้า · heapfile.py:74 เรียก seek และ heapfile.py:76 เรียก read จริง ส่วน​คำถาม​ว่า​ครั้ง​ไหน​ถึง​จาน​หมุน​จริง เป็น​เรื่อง​ของ page cache ซึ่ง​คอร์ส​นี้​ไม่​วัด
ความเร็วทั้ง​คอร์ส​ไม่มี​หน่วย​เวลา​สัก​ตัว เหมือน​คอร์ส​ที่ 26 และ 28 ใน​เสา​เดียวกัน
การ​ประมาณ​จำนวน​แถว · histogram · uniformity · independencezone map เป็นการ​ตัด​ทิ้ง ไม่ใช่​การ​ประมาณ · เรื่อง​นี้​เป็น​ของ​คอร์ส​ที่ 26 บท​ที่ 4 ซึ่ง​มี​หัวข้อ​ของ​มัน​เอง​อยู่​แล้ว
transaction และ isolation บน​คลัง​แนว columnเป็น​ของ​คอร์ส​ที่ 28 ซึ่ง​เป็น​คนละ​คำถาม

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


🔗 อ้างอิง​ต้นทาง​ของ​บท​นี้
  • Apache Parquet — File Format (เข้า​ถึง 2026-09-10) — ที่มา​ของ​ศัพท์ row group กับ column chunk และ​ของ​ข้อ​ที่​ว่า ตำแหน่ง​ของ​ทุก column chunk ถูก​เก็บ​ไว้​ใน metadata ท้าย​คลัง

  • Apache Parquet — Metadata (เข้า​ถึง 2026-09-10) — ที่มา​ของ​ข้อ​ที่​ว่า metadata ท้าย​คลัง​ถือ​ตำแหน่ง​ของ​ทุก column chunk ทำให้​ผู้​อ่าน​กระโดด​ไป​ที่ chunk ที่​ต้องการ​ได้ · เป็น​กลไก​ของ​รูป​แบบ ไม่ใช่​ผล​การ​วัด​ของ​คอร์ส​นี้

  • ตัวเลข​ทุก​ตัว​ใน​บท​นี้​มา​จาก python3 scripts/column-store/probe.py ซึ่ง​สร้าง​คลัง​ขึ้น​ใหม่ ทุกร​อบ​จาก fixture.py ของ​คอร์ส​ที่ 26 · ค่าที่​ตรึง​ไว้​อยู่​ที่ scripts/column-store/expected/column-store.txt หมวด ### 2_SELECT_ดาว และ ### 9_late_materialization

  • หน้าที่​แตะ​ทุก​ค่า​มา​จาก Pager.reads ตัว​เดียว​กับ​คอร์ส​ที่ 26 ที่ scripts/query-engine/qe/heapfile.py บรรทัด​ที่ 71 และ​บรรทัด​ที่ 75 · คอร์ส​นี้​ไม่​แก้​อะไร​ใน scripts/query-engine/qe/ แม้แต่ byte เดียว

เช็กความเข้าใจ — บทที่ 8

ข้อ 1 / 3

หยิบหนึ่งแถวทั้งแถวจากคลัง column โดยมีตัวชี้ของแถวอยู่แล้ว แตะกี่หน้า เทียบกับ heap ที่แตะ 1 หน้า