ขนาด transaction ที่ถูกที่สุดไม่ใช่ใหญ่ที่สุด — วัดสองเอนจินด้วยงานเดียวกัน
คำแนะนำเรื่องนี้มีอยู่ในทุกที่ อย่า commit ทีละแถว ให้รวมงานเข้าเป็น transaction เดียว แล้ว I/O จะลดลงมหาศาล
หน้านี้ไปวัดว่ามันจริงแค่ไหน โดยรัน งานเดียวกันเป๊ะ บน PostgreSQL 18.6 กับ SQLite 3.53.4 ที่ทุกขนาด transaction แล้ววัด WAL ของแต่ละเอนจินด้วยหน่วยของมันเอง
คำตอบแยกเป็นสองชั้น ชั้นแรกคือคำแนะนำนั้นจริงมากบนเอนจินหนึ่ง และเกือบไม่จริงเลยบนอีกตัวหนึ่ง ต่างกัน 420 เท่า
ชั้นที่สองคือสิ่งที่ไม่มีใครเตือนไว้ บน PostgreSQL การ commit ครั้งเดียวทั้งชุดแพงกว่า การแบ่ง commit และตัวเลขบอกได้ว่าเพราะอะไร
ฝั่ง SQLite ของเรื่องนี้ มีคนวัดไว้ให้แล้ว และหน้านี้ยืนบนไหล่ของมัน
หัวข้อที่มีชื่อว่า “ฝั่ง SQLite ของเรื่องนี้ มีคนวัดไว้ให้แล้ว และหน้านี้ยืนบนไหล่ของมัน”เครดิตต้องให้ก่อน เพราะสามในสี่หัวข้อฝั่ง SQLite ของหน้านี้เป็นการยืนยันสิ่งที่คอร์สที่ 28 บทที่ 7 วัดไว้แล้ว
บทที่ 7 ของคอร์ส transaction engine วัดราคาของ commit บน SQLite ไว้ครบแล้ว ทั้งการนับเป็น frame ทั้งการกวาดขนาด transaction และทั้งราคาของลำดับที่คีย์มาถึง
หน้านี้จึงไม่ได้ค้นพบฝั่ง SQLite ขึ้นมาใหม่ มันวัดซ้ำด้วยเครื่องมือคนละตัวแล้วได้ทิศเดียวกัน ของใหม่ทั้งหมดอยู่ฝั่ง PostgreSQL และอยู่ที่การเอาสองฝั่งมาวางข้างกัน
callout ปิดท้ายของบทที่ 7 เขียนไว้ว่า “รูปของเส้นราคาจึงคล้ายกัน แต่ตัวคูณไม่ใช่ตัวเดียวกัน” เมื่อเทียบ SQLite กับ PostgreSQL
ตัวคูณต่างกันจริงตามที่บทเรียนบอก แต่รูปไม่คล้ายกัน ของ SQLite ลงอย่างเดียว ส่วนของ PostgreSQL เป็นรูปตัว U ซึ่งกลับทิศกลางทาง · ตารางในหัวข้อ กวาดทั้งช่วง ข้างล่างคือหลักฐาน
บทเรียนนั้นเขียนขึ้นตอนที่ยังไม่มีใครวัดฝั่ง PostgreSQL ด้วยงานเดียวกัน ประโยคนั้นจึงเป็น การประมาณที่สมเหตุสมผลในตอนนั้น ไม่ใช่ความผิดพลาด · ตอนนี้วัดแล้ว และบทเรียนถูกแก้ตามแล้ว
หน้านี้ถอดข้อจำกัดที่บทความก่อนหน้าเขียนกำกับตัวเองไว้
หัวข้อที่มีชื่อว่า “หน้านี้ถอดข้อจำกัดที่บทความก่อนหน้าเขียนกำกับตัวเองไว้”บทความที่ 18 วัดฝั่ง PostgreSQL ไว้แล้ว พร้อมประกาศเองว่าห้ามเอาตัวเลขข้ามเอนจิน หน้านี้คือการทำให้ข้ามได้
บทความที่ 18 กลไก transaction ของ PostgreSQL วัดไว้ว่า commit ทีละแถว เทียบ commit ครั้งเดียวต่างกัน 1.2 เท่า แล้วเขียน callout กำกับตัวเองไว้ว่าสิ่งที่หน้านั้นยืนยันได้คือ ทิศ ไม่ใช่ตัวคูณข้ามเอนจิน
เหตุผลที่หน้านั้นให้ไว้ถูกต้อง คือ งานที่วัดคนละงานกัน การหารตัวเลขของสองเอนจินเทียบกันตรง ๆ จึงไม่ได้แปลว่าอะไร
หน้านี้ถอดข้อจำกัดนั้นออกด้วยวิธีเดียวที่ถอดได้ คือรันงานเดียวกันทั้งสองฝั่ง แล้วเทียบ อัตราส่วนภายในเอนจินเดียวกัน ไม่ใช่เทียบ byte ดิบ
WAL ของสองตัวเก็บคนละอย่าง PostgreSQL เก็บ ระเบียนของการเปลี่ยนแปลง ส่วน SQLite เก็บ หน้าเต็ม ตัวเลข byte จึงอยู่คนละอันดับโดยธรรมชาติ ไม่ใช่เพราะตัวหนึ่งดีกว่าอีกตัว
สิ่งที่เทียบได้คืออัตราส่วนภายในเอนจินเดียวกัน คือ การรวม transaction ให้ใหญ่ขึ้น ช่วยเอนจินนี้กี่เท่า แล้วเอาสองอัตราส่วนนั้นมาเทียบกันอีกที ทุกข้อสรุปในหน้านี้อยู่ในรูปนั้น
วิธีวัด งานเดียวกันเป๊ะ ต่างแค่ขอบเขต transaction
หัวข้อที่มีชื่อว่า “วิธีวัด งานเดียวกันเป๊ะ ต่างแค่ขอบเขต transaction”ตารางเดียวกัน แถวเท่ากัน คำสั่งเดียวกัน ตัวแปรเดียวที่ขยับคือ commit ทุกกี่แถว
ตารางคือ t(id, v) ที่มี 1,000 แถว งานคือสั่ง update t set v=v+1 where id=?
ครบทุกแถวหนึ่งรอบ ทุกช่องในทุกตารางข้างล่างทำงานนี้เหมือนกันหมด
ฝั่ง PostgreSQL วัดด้วย pg_wal_lsn_diff ระหว่างสอง LSN คร่อมงาน ส่วนฝั่ง SQLite วัดจาก
ขนาดของ file -wal ที่โตขึ้น หลังตัด WAL ให้เป็นศูนย์ด้วย wal_checkpoint(TRUNCATE)
ตัวแปรที่คุมไว้มีสามตัว
- autovacuum ปิด ทั้งที่ระดับ server และระดับตาราง
checkpoint_timeoutตั้งไว้หนึ่งชั่วโมงwal_autocheckpointของ SQLite ปิด
ทั้งสามตัวถ้าไม่ปิดจะแทรกกลางการวัด แล้วตัวเลขที่ได้จะเป็นเศษที่เหลือหลังใครบางคนเก็บกวาดไปแล้ว ไม่ใช่ยอดที่งานเขียนจริง
สองปลายของช่วง คำแนะนำเดียวกันให้ผลคนละอันดับ
หัวข้อที่มีชื่อว่า “สองปลายของช่วง คำแนะนำเดียวกันให้ผลคนละอันดับ”คำแนะนำ “รวม transaction ให้ใหญ่” จริงมากบน SQLite และเกือบไม่จริงเลยบน PostgreSQL
| เอนจิน | commit ทีละแถว | commit ครั้งเดียว | ประหยัดได้ |
|---|---|---|---|
| PostgreSQL 18.6 | 328,096 B | 276,688 B | 1.2 เท่า |
| SQLite 3.53.4 | 4,120,032 B | 8,272 B | 498.1 เท่า |
อัตราส่วนของ SQLite สูงกว่าของ PostgreSQL 420 เท่า นี่คือตัวเลขที่หน้าก่อนหน้าประกาศว่า ตัวเองอ้างไม่ได้ และเป็นตัวเลขเดียวในหน้านี้ที่ข้ามเอนจิน
ความต่างไม่ได้อยู่ที่ตัวไหนเขียนเก่งกว่า มันอยู่ที่ หน่วยของสิ่งที่ WAL เก็บ ซึ่งย่อหน้าถัดไป คิดออกมาเป็นเลขได้ทั้งตัว
ตัวเลขของ SQLite อธิบายได้ทั้งตัว ไม่ใช่แค่วัดได้เท่านี้
หัวข้อที่มีชื่อว่า “ตัวเลขของ SQLite อธิบายได้ทั้งตัว ไม่ใช่แค่วัดได้เท่านี้”สูตรบนกระดาษให้เลขตรงกับที่วัดได้ทีละ byte ทั้งสองช่อง
SQLite เขียน WAL เป็น frame และหนึ่ง frame คือ หนึ่งหน้าเต็ม บวก header 4,096 + 24 byte ไม่ว่าหน้านั้นจะถูกแก้ไปกี่ byte ก็ตาม
commit ทีละแถวจึงเขียน 1,000 หน้า และได้ 1,000 × (4,096 + 24) + 32 = 4,120,032 B ตรงกับที่วัดได้ทีละ byte
commit ครั้งเดียวแตะแค่ 2 หน้า จึงได้ 2 × (4,096 + 24) + 32 = 8,272 B ตรงเช่นกัน เลข 32 ท้ายสูตรคือ header ของ file WAL
ตัวเลขที่อธิบายได้ทั้งตัวแบบนี้ ตรึงเป็น byte ได้ ตัวตรวจของหน้านี้จึงเทียบฝั่ง SQLite ทีละ byte ทุกช่อง
ฝั่ง PostgreSQL ตรึงแบบนั้นไม่ได้ วัดซ้ำสามรอบบนเครื่องเดียวกันแล้วช่องเดียวกันแกว่งได้ ถึง 56 B จาก 328,096 B ซึ่งคือ 0.02 % · ตัวตรวจจึงใช้กรอบ 0.5 % กับฝั่งนั้น กว้างกว่าที่วัดได้ยี่สิบเท่า แต่ยังแคบกว่าข้ออ้างของหน้านี้หลายสิบเท่า
กวาดทั้งช่วง แล้วเส้นโค้งของ PostgreSQL ไม่ได้ลงอย่างเดียว
หัวข้อที่มีชื่อว่า “กวาดทั้งช่วง แล้วเส้นโค้งของ PostgreSQL ไม่ได้ลงอย่างเดียว”สองปลายของช่วงบอกทิศถูก แต่บอกรูปทรงผิด และรูปทรงคือสิ่งที่เอาไปใช้ได้
| commit ทุกกี่แถว | PostgreSQL 18.6 | SQLite 3.53.4 |
|---|---|---|
| 1 | 328,096 B | 4,120,032 B |
| 10 | 238,576 B | 416,152 B |
| 100 | 240,800 B | 45,352 B |
| 1000 | 276,688 B | 8,272 B |
ฝั่ง SQLite ลงอย่างเดียวทุกขั้น จาก 4,120,032 ไปจนถึง 8,272 B ยิ่งรวมยิ่งถูก ไม่มีจุดกลับ
ฝั่ง PostgreSQL ลงถึงจุดต่ำสุดที่ commit ทุก 10 แถว แล้ว กลับขึ้น การ commit ครั้งเดียวทั้งชุดแพงกว่าจุดต่ำสุดนั้น 1.2 เท่า
พูดอีกอย่างคือ ถ้าเชื่อคำแนะนำจนสุดทางแล้วยัดทุกแถวไว้ใน transaction เดียว บน PostgreSQL คุณเดินเลยจุดที่ถูกที่สุดไปแล้ว
การ commit เพิ่มหนึ่งครั้งบน PostgreSQL มีราคาประมาณ 99 B คิดจากส่วนต่างของสองแถวแรก หารด้วยจำนวน commit ที่หายไป นี่คือขาลงของเส้นโค้ง ส่วนขาขึ้นเป็นคนละกลไก
หน้าเต็มไม่ใช่คำอธิบาย และตัวเลขบอกเองว่าไม่ใช่
หัวข้อที่มีชื่อว่า “หน้าเต็มไม่ใช่คำอธิบาย และตัวเลขบอกเองว่าไม่ใช่”ผู้ต้องสงสัยอันดับแรกคือ full page write หน้านี้ตัดมันออกด้วยการนับ ไม่ใช่ด้วยการให้เหตุผล
PostgreSQL เขียนหน้าเต็มลง WAL ครั้งแรกที่แตะหน้านั้นหลัง checkpoint กลไกนี้ชื่อ
full_page_writes และภาพหน้าเต็มที่มันเขียนเรียกว่า full page image (FPI) ซึ่งเป็นชื่อของ
column wal_fpi ใน pg_stat_wal ที่ตารางข้างล่างนับมา
มันคือคำอธิบายที่คนคิดถึงก่อนเสมอเมื่อ WAL โตผิดคาด
| commit ทุกกี่แถว | WAL | ระเบียน | หน้าเต็ม | update ที่ HOT | index โตขึ้น |
|---|---|---|---|---|---|
| 1 | 328,096 B | 4,414 | 9 | 325 | 8,192 B |
| 10 | 238,576 B | 2,565 | 9 | 325 | 8,192 B |
| 100 | 240,824 B | 2,513 | 9 | 260 | 16,384 B |
| 1000 | 276,688 B | 3,022 | 9 | 0 | 24,576 B |
จำนวน FPI เท่ากันหมดทุกช่อง คือ 9 ภาพ ขนาด transaction ไม่ได้เปลี่ยนมันเลย
แม้แต่ภาพเดียว full_page_writes จึงตกเป็นผู้ต้องสงสัยไม่ได้
column เดียวในตารางนี้ที่นิ่งคือ column หน้าเต็ม · อีกสี่ column ขยับหมด และ สอง column ขวาสุดขยับไปด้วยกัน
คำอธิบายคือ HOT update ที่หายไป
หัวข้อที่มีชื่อว่า “คำอธิบายคือ HOT update ที่หายไป”update ที่อยู่ในหน้าเดิมได้ ไม่ต้องแตะ index เลย และ transaction ที่ใหญ่เกินไปทำให้มันเป็นไปไม่ได้
PostgreSQL ไม่เคยเขียนทับแถวเก่า มันเขียนแถวใหม่แล้วปล่อยของเก่าค้างไว้ ถ้าแถวใหม่นั้น เป็น HOT update ได้ index จะยังชี้ถูกอยู่ และไม่ต้องเขียน index เลยสักระเบียน
เอกสารกำหนดเงื่อนไขไว้ สองข้อ ต้องครบทั้งคู่
- การ update นั้นไม่แตะ column ที่ index ตัวไหนอ้างถึง
- หน้าที่แถวเก่าอยู่ ยังมีที่ว่างพอสำหรับแถวใหม่
งานที่หน้านี้วัดแก้เฉพาะ v ซึ่งไม่มี index ตัวไหนอ้างถึง ข้อแรกจึงจริงตลอดทุกช่อง
ตัวแปรเดียวที่ขยับคือข้อสอง และนั่นคือสิ่งที่ขนาด transaction เปลี่ยน
ถ้าหน้าเดิมเต็ม แถวใหม่ต้องย้ายไปหน้าอื่น แล้ว index ก็ต้องได้ระเบียนใหม่ตามไปด้วย นี่คือราคาที่โผล่ในสอง column ขวาสุดของตารางข้างบน
flowchart LR
subgraph HOT["HOT update · หน้าเดิมยังมีที่ว่าง"]
direction TB
H1["แถวเก่า · หน้า 7"] --> H2["แถวใหม่ · หน้า 7"]
HI["index"] -. "ชี้ที่เดิม ไม่ต้องเขียน" .-> H1
end
subgraph COLD["ไม่ HOT · หน้าเดิมเต็มแล้ว"]
direction TB
C1["แถวเก่า · หน้า 7"] --> C2["แถวใหม่ · หน้า 9"]
CI["index"] ==> C2
end
HOT -->|"transaction ใหญ่ขึ้น<br/>ไม่มีใครเก็บกวาดระหว่างทาง"| COLD
คำบรรยายภาพ: เมื่อหน้าเดิมยังมีที่ว่าง update จบในหน้าเดียวและ index ไม่ต้องถูกเขียน เมื่อหน้าเต็ม แถวย้ายหน้า และ index ต้องเขียนตาม
ที่ commit ทีละแถว มี HOT update 325 จาก 1,000 ครั้ง ที่ commit ครั้งเดียว เหลือ 0 ศูนย์ ไม่ใช่ตัวเลขที่น้อยลง
เหตุผลคือระหว่างสอง transaction ที่ commit แล้ว PostgreSQL เก็บกวาดแถวเก่าในหน้านั้นได้ แล้วที่ว่างกลับมาให้ใช้ใหม่ ส่วนใน transaction เดียวที่ยาวตลอดงาน ไม่มีจังหวะไหนให้ทำแบบนั้นเลย
ผลคือ index ของ primary key โตขึ้น 24,576 B แทนที่จะเป็น 8,192 B และ WAL ที่ประหยัดได้จากการลดจำนวน commit ถูกกินคืนไปหมดแล้วเกิน
ลำดับที่ยิง update มีราคาบน SQLite แต่แทบไม่มีบน PostgreSQL
หัวข้อที่มีชื่อว่า “ลำดับที่ยิง update มีราคาบน SQLite แต่แทบไม่มีบน PostgreSQL”งานเดียวกัน จำนวน commit เท่ากัน ต่างแค่ลำดับของ id แล้วเอนจินหนึ่งจ่ายเพิ่มเท่าตัว
ทุกช่องข้างบนยิง update ตามลำดับ id ตารางนี้ยิงชุดเดิมด้วยลำดับสุ่มที่ปักหมุดไว้ด้วย seed ตายตัว งานและจำนวน commit เท่ากันทุกประการ
| commit ทุกกี่แถว | SQLite 3.53.4 เรียง | SQLite 3.53.4 สุ่ม | PostgreSQL 18.6 เรียง | PostgreSQL 18.6 สุ่ม |
|---|---|---|---|---|
| 1 | 4,120,032 B | 4,120,032 B | 328,096 B | 326,768 B |
| 10 | 416,152 B | 824,032 B | 238,576 B | 260,664 B |
| 100 | 45,352 B | 82,432 B | 240,800 B | 235,544 B |
| 1000 | 8,272 B | 8,272 B | 276,688 B | 277,312 B |
บน SQLite ที่ commit ทุก 10 แถว ลำดับสุ่มแพงกว่าลำดับเรียง 1.98 เท่า เพราะ batch ที่ id เรียงกันตกอยู่ในหน้าเดียว ส่วน batch ที่ id กระจายไปแตะหลายหน้า และ SQLite คิดราคาเป็นหน้าเต็มทุกหน้า
ที่ปลายทั้งสองข้างลำดับไม่มีผลเลย ตัวเลขเท่ากันทีละ byte ที่ commit ทีละแถวทุก commit แตะหน้าเดียวอยู่แล้ว ส่วนที่ commit ครั้งเดียวทุกหน้าถูกยุบรวมเป็นครั้งเดียวอยู่ดี
บน PostgreSQL ผลของลำดับเล็กกว่ามาก และ ไม่ได้ไปทางเดียวกันทุกช่อง ที่ commit ทุก 10 แถว ลำดับสุ่มแพงกว่า 9 % ส่วนที่ commit ทุก 100 แถว มัน ถูกกว่า 2 %
เหตุผลที่ผลเล็กคือ WAL เก็บระเบียนของการเปลี่ยนแปลง ไม่ใช่หน้า การกระจายของแถวจึงไม่ได้ คูณจำนวน byte ขึ้นทั้งหน้าแบบฝั่ง SQLite · ส่วนที่ยังเหลืออยู่มาจากการที่ลำดับเปลี่ยนว่า หน้าไหนเต็มก่อน ซึ่งเปลี่ยนจำนวน HOT update ตามไปด้วย
นี่คือความต่างที่เอาไปใช้ได้ทันที ถ้าเขียนงาน bulk update บน SQLite การเรียงตาม primary key ก่อนยิงเป็นการประหยัดจริง บน PostgreSQL มันไม่ใช่
แล้วควร commit ทุกกี่แถว
หัวข้อที่มีชื่อว่า “แล้วควร commit ทุกกี่แถว”บน SQLite คำตอบคือใหญ่เท่าที่ความถูกต้องยอมให้ · บน PostgreSQL มีจุดที่เลยไปแล้วแพงขึ้น
บน SQLite รวมให้ใหญ่เท่าที่ขอบเขตความถูกต้องยอมให้ เส้นโค้งลงอย่างเดียวจริง และราคาต่อ commit คือหน้าเต็มทุกหน้าที่แตะ ซึ่งแพงกว่าราคาต่อ commit ของ PostgreSQL หลายอันดับ
บน PostgreSQL จุดต่ำสุดของงานชุดนี้อยู่ที่ commit ทุก 10 แถว ตัวเลขนี้ไม่ใช่ ค่าคงที่สากล มันคือจุดที่ราคาคงที่ต่อ commit กับราคาของ HOT update ที่หายไป หักล้างกันพอดี
ตารางลำดับสุ่มในหัวข้อที่แล้วพิสูจน์เรื่องนี้ในหน้าเดียวกันนี้เอง พอเปลี่ยนแค่ลำดับที่ยิง update จุดต่ำสุดก็ย้ายไปอยู่ที่ commit ทุก 100 แถวแทน โดยที่งานยังเป็นงานเดิมทุกอย่าง
จุดนั้นขยับตามขนาดแถว ตามจำนวน index และตาม fillfactor ของตาราง สิ่งที่หน้านี้ยืนยันได้คือ เส้นโค้งเป็นรูปตัว U ไม่ใช่ว่าจุดต่ำสุดของงานคุณอยู่ที่เลขเดียวกันนี้
และมีเงื่อนไขหนึ่งที่ไม่ได้เลื่อนจุดต่ำสุด แต่ลบเส้นโค้งทิ้งทั้งเส้น ถ้า update ของคุณ แตะ column ที่ index ตัวใดตัวหนึ่งอ้างถึง เงื่อนไขข้อแรกของ HOT ตกทันที และจะไม่มี HOT update เลยสักตัวไม่ว่าจะซอย transaction เล็กแค่ไหน
งานที่หน้านี้วัดแก้เฉพาะ column ที่ไม่มี index อ้างถึง ตัวเลขทุกช่องจึงเป็นของกรณีที่ เงื่อนไขข้อแรกผ่านแล้ว · ถ้างานของคุณ update column ที่มี index ให้เริ่มจากคำถามนั้นก่อน ไม่ใช่จากขนาดของ transaction
ถ้าจะหาจุดของงานจริง วัดสามอย่างนี้ระหว่างรัน
n_tup_hot_updจากpg_stat_user_tables- ขนาดของ index
- WAL จาก
pg_wal_lsn_diff
ตารางข้างบนแสดงว่าสามตัวนี้ขยับ ไปด้วยกัน ไม่ได้ขยับตามกันเป็นลำดับ ตัวแรกอ่านง่ายที่สุด เพราะมันเป็นจำนวนนับตรง ๆ ไม่ต้องคร่อม LSN สองค่า
หน้านี้ไม่ได้ให้ต้นทุนมาเลือกขอบเขต มันตอบคำถามที่สอง
หัวข้อที่มีชื่อว่า “หน้านี้ไม่ได้ให้ต้นทุนมาเลือกขอบเขต มันตอบคำถามที่สอง”ไซต์นี้แยกสองคำถามนี้ไว้แล้ว หน้านี้อยู่ในคำถามที่สองทั้งหน้า
คอร์ส EF Core วางขอบเขตความถูกต้องไว้ที่ หนึ่ง aggregate ต่อหนึ่ง transaction
ส่วน บทที่ 7 ของคอร์ส transaction engine สรุปกฎที่ใช้ได้จริงไว้ว่า ให้ความถูกต้องเลือกขอบเขต แล้วให้ต้นทุนเลือกจำนวน commit ภายในขอบเขตนั้น
ตัวเลขทุกช่องในหน้านี้อยู่ในครึ่งหลังของประโยคนั้น งาน 1,000 แถวที่วัด ไม่ใช่ 1,000 aggregate มันคืองานนำเข้าข้อมูลก้อนเดียวที่หน่วยความถูกต้อง ของมันคือทั้งก้อน คำถามที่เหลือคือจะซอยเป็นกี่ commit
สิ่งที่หน้านี้เพิ่มเข้าไปคือครึ่งหลังของเส้นโค้ง บนฝั่ง SQLite ที่บทเรียนนั้นวัด คำตอบคือ ยิ่งรวมยิ่งถูก ไม่มีจุดกลับ · บน PostgreSQL มีจุดกลับ และการซอยเป็นก้อนพอประมาณ ถูกกว่า การยัดทั้งก้อนไว้ใน commit เดียว
ผลพลอยได้คือกฎ ห้ามขยาย transaction เพื่อประหยัดการเขียน ของบทเรียนนั้นแข็งแรงขึ้นอีก บน PostgreSQL การขยายขอบเขตเพื่อ I/O ไม่ได้แค่แลกด้วย lock ที่ถือนานขึ้น มันไม่ได้ ส่วนลดที่หวังไว้ด้วยซ้ำ
สิ่งที่หน้านี้ยืนยันไม่ได้
หัวข้อที่มีชื่อว่า “สิ่งที่หน้านี้ยืนยันไม่ได้”ขอบเขตของการวัดแคบกว่าที่หัวข้ออาจทำให้เข้าใจ และเส้นพวกนี้ต้องพูด ไม่ใช่ซ่อน
- byte ดิบข้ามเอนจินยังเทียบไม่ได้ ทุกข้อสรุปในหน้านี้เป็นอัตราส่วนภายในเอนจินเดียวกัน ยกเว้นตัวเลข 420 เท่า ซึ่งเป็นอัตราส่วนของอัตราส่วน
- หน้านี้ไม่ได้วัดเวลา ไม่มีวินาที ไม่มี throughput WAL ที่น้อยกว่าไม่ได้แปลว่าเร็วกว่าเสมอไป
เพราะจำนวน
fsyncกับ latency ของ commit เป็นคนละแกน - ตารางมี index ตัวเดียว คือ primary key งานจริงที่มีหลาย index จะเห็นขาขึ้นของเส้นโค้ง ชันกว่านี้ เพราะทุก index ต้องเขียนตามเมื่อ update ไม่เป็น HOT
- autovacuum ปิดอยู่ ทั้งระดับ server และระดับตาราง เพื่อไม่ให้มันแทรกกลางการวัด
งานจริงที่เปิดไว้จะได้ตัวเลขไม่เท่านี้ · การเก็บกวาดที่ทำให้ HOT เกิดได้ในหน้านี้คือ
การ prune ระดับหน้าที่เกิดตอนมีคนมาแตะหน้านั้น ซึ่งไม่ใช่ autovacuum · แต่ที่ว่างของแถว
ที่ย้ายหน้าไปแล้วต้องรอ
VACUUMถึงจะกลับมาใช้ได้ ตรงนั้น autovacuum เกี่ยวเต็ม ๆ - จุดต่ำสุดที่ 10 แถวผูกกับงานชุดนี้ ขนาดแถว จำนวน index และ fillfactor ย้ายจุดนั้นได้หมด สิ่งที่ยืนยันคือรูปทรงของเส้นโค้ง
- ตัวเลขทั้งหมดมาจากเครื่องเดียว PostgreSQL 18.6 ใน container และ SQLite 3.53.4 บน arm64 เอนจินคนละรุ่นให้ byte ไม่เท่ากันได้
ตัวเลขทุกตัวในหน้านี้มาจากสองคำสั่งใน repo ของไซต์นี้
scripts/pg-transaction-spike/up.sh # postgres ที่คุมตัวแปรแล้วnpm run verify:engine-compare # วัดซ้ำแล้วเทียบกับ file ข้อมูลตัววัดเขียน src/data/perf/engine-compare.ts ให้เอง หน้านี้อ่านตัวเลขจาก file นั้นทุกตัว
แก้ตัวเลขด้วยมือไม่ได้ เพราะตัวตรวจจะวัดใหม่แล้วเทียบทีละช่อง
ตัวตรวจไม่ได้ตรวจแค่ byte มันตรวจ ข้ออ้าง ด้วย สามข้อ
- เส้นโค้งของ PostgreSQL ต้องยังเป็นรูปตัว U
- เส้นโค้งของ SQLite ต้องยังลงอย่างเดียว
- HOT update ที่ commit ครั้งเดียวต้องยังเป็นศูนย์
ฝั่ง PostgreSQL ของตัววัดนี้ import code วัดตัวเดียวกับบทความที่ 18 ไม่ได้คัดลอกมา วิธีวัดของสองหน้าจึงแก้ที่เดียวเสมอ
แต่ ตัวเลข ของสองหน้าเป็นคนละ snapshot บทความที่ 18 ตรึงค่าไว้ตอนที่มันวัด หน้านี้ตรึง ตอนที่หน้านี้วัด สองค่านั้นห่างกันได้ในระดับที่ 0.5 % ครอบไว้ และของจริงตอนนี้ห่างกัน 40 B · ถ้าอยากได้เลขเดียวกันเป๊ะ ต้องวัดใหม่ทั้งสองหน้าในรอบเดียวกัน
- PostgreSQL 18 Documentation — 66.7 Heap-Only Tuples (HOT) (ตรวจแล้ว 2026-08-21) — ที่มาของเงื่อนไขสองข้อของ HOT update และของข้อที่ว่า HOT update ไม่สร้างระเบียน index ใหม่
- PostgreSQL 18 Documentation — 28.5 WAL Configuration
(ตรวจแล้ว 2026-08-21) — ที่มาของกลไก
full_page_writesที่ตารางในหัวข้อ หน้าเต็มไม่ใช่คำอธิบาย ตัดออกไป - PostgreSQL 18 Documentation — 27.2 The Cumulative Statistics System
(ตรวจแล้ว 2026-08-21) — ที่มาของ
n_tup_hot_updและpg_stat_walที่ใช้แยกก้อน WAL - SQLite — Write-Ahead Logging, §Format (ตรวจแล้ว 2026-08-21) — ที่มาของ header 32 byte ต่อ file และ 24 byte ต่อ frame ในสูตรข้างบน