migration บรรทัดเดียวที่ไม่ได้แตะข้อมูลสักหน้า แต่หยุด query ทุกตัวที่ตามมา
คุณรีวิว SQL ของ migration มาแล้ว มันมีบรรทัดเดียว เป็น ADD COLUMN ที่ไม่มี default
คุณรันมันบน staging มันเสร็จก่อนที่คุณจะละสายตาจากจอ คุณจึงกด deploy ตอนบ่าย
แล้วเว็บก็ค้าง ไม่ใช่ช้า — ค้าง ทุกหน้าที่แตะตาราง orders หมุนค้างพร้อมกัน
พอเข้าไปดู pg_stat_activity คุณเห็นสิ่งที่ไม่คาดคิด คำสั่งที่ค้างอยู่ไม่ใช่ migration
มันคือ SELECT ธรรมดาของหน้ารายการออเดอร์ที่ปกติคืนทันที และมันไม่ได้ค้าง
เพราะตารางถูกล็อกอยู่ มันค้างเพราะมันต่อคิวอยู่หลังคำสั่งที่ยังไม่ได้ล็อกด้วยซ้ำ
หน้านี้วัดคิวนั้นบน PostgreSQL 18.6 ตัวจริง ผลคือ reader ที่ ไม่ได้ทำอะไรเลย หนึ่งตัว
บวก ALTER TABLE ที่ ไม่ได้แตะข้อมูลสักหน้า หนึ่งบรรทัด เท่ากับ
8 คำสั่งที่ไม่ได้รัน จาก 8 คำสั่งที่เข้ามา
และผู้ร้ายไม่ใช่ migration ตัวที่ถือ lock อยู่จริงคือ session ที่สถานะ
idle in transaction — เปิด transaction ค้างไว้แล้วไปทำอย่างอื่น
ALTER TABLE เป็นแค่ตัวที่บังเอิญมายืนต่อคิวหลังมัน แล้วกลายเป็นกำแพงให้คนที่มาทีหลังทั้งหมด
ทุกช่องในหน้านี้เป็นจำนวนนับ ไม่มีวินาทีสักตัว เพราะข้ออ้างของหน้านี้ไม่ได้พูดถึงความเร็วเครื่อง
หนี้ที่หน้านี้มาจ่าย
หัวข้อที่มีชื่อว่า “หนี้ที่หน้านี้มาจ่าย”คอร์ส EF Core ของไซต์นี้เตือนเรื่องนี้ไว้แล้ว แต่เตือนโดยไม่มีตัวเลขสักตัว
บทเรียนเรื่อง migration ของคอร์ส EF Core เขียนไว้ว่าให้มองหาคำสั่งอันตรายใน
migrate.sql ก่อนขึ้น prod แล้ววงเล็บกำกับไว้ว่า
ALTER COLUMN ... TYPE(อาจ rewrite ทั้งตารางและล็อกไว้นาน)
อีกย่อหน้าหนึ่งจัดกลุ่มก้าวแบบนั้นว่า Breaking (เสี่ยงข้อมูล/เสี่ยงล็อกตาราง)
สองประโยคนั้นไม่ผิด แต่ไม่มีตัวเลขสักตัว “นาน” คือเท่าไร “เสี่ยง” คือเสี่ยงต่ออะไร
และมันผูกคำว่าอันตรายไว้กับ การ rewrite กับข้อมูลหาย ซึ่งเป็นแกนที่มีจริง
แต่กลไกที่ทำให้เว็บหยุดไม่ใช่แกนนั้น มันคือ คิวของ lock และบนแกนนั้น
ALTER TABLE ทุกรูปเท่ากันหมด
หลักฐานอยู่ในรายการของบทเรียนเอง มันให้มองหาสามคำสั่ง — DROP COLUMN,
ALTER COLUMN ... TYPE และ ALTER COLUMN ... SET NOT NULL — แต่มีตัวเดียวที่ rewrite
ตารางในหัวข้อถัดไปวัดทั้งสามคำสั่งนั้น DROP COLUMN กับ SET NOT NULL
ไม่เปลี่ยน relfilenode และไม่ขยับจำนวนหน้าสักหน้า มีแต่ ALTER COLUMN TYPE
ที่เขียนตารางใหม่ทั้งใบ
สามคำสั่งนั้นจึงมีอย่างเดียวที่เหมือนกันจริง ๆ คือ ทั้งสามถือ ACCESS EXCLUSIVE ซึ่งเป็นแกนที่บทเรียนไม่ได้พูดถึง
และ ADD COLUMN ที่ไม่มี default ซึ่งก่อคิวทั้งหมดในการวัดข้างบนนี้
ไม่อยู่ในรายการนั้นเลย
หน้านี้วัดทั้งสามคำสั่งนั้นเอง ไม่ได้ยกพฤติกรรมมาจากเอกสารแล้วเชื่อตาม
· ร่างแรกของหน้านี้อ้างเอกสารสำหรับ DROP COLUMN กับ SET NOT NULL
แล้วจึงกลับไปวัดเพิ่ม เพราะข้ออ้างที่วัดเองกับข้ออ้างที่อ่านมา ไม่ควรปนกันในตารางเดียว
สิ่งที่ยังเป็นการอ้างเอกสารล้วน ๆ ในหน้านี้เหลือแค่คำนิยามของ lock แต่ละตัว · ส่วนกฎเรื่องคิว หาในเอกสารไม่เจอ จึงวัดเอาเอง ดูกล่องในหัวข้อกลไก
หน้านี้จ่ายหนี้ก้อนนั้น อ่านบทเรียนต้นทางได้ที่ migration ที่ apply แล้วย้อนได้
กลไก คิวของ lock manager ไม่ได้ถามว่าใครชนกับใคร
หัวข้อที่มีชื่อว่า “กลไก คิวของ lock manager ไม่ได้ถามว่าใครชนกับใคร”เอกสารบอกว่า ACCESS SHARE ไม่ชนกับ ACCESS SHARE แต่ไม่ได้บอกสิ่งที่เกิดขึ้นจริง
PostgreSQL มีตารางบอกว่า lock ตัวไหนชนกับตัวไหน SELECT ขอ ACCESS SHARE และเอกสาร
เขียนไว้ตรงตัวว่า “an ACCESS SHARE lock can be held by multiple transactions”
ส่วน ALTER TABLE ขอ ACCESS EXCLUSIVE ซึ่งชนกับทุกตัว
อ่านแค่ตารางนั้นแล้วคุณจะสรุปว่า SELECT สองตัวอยู่ด้วยกันได้เสมอ ซึ่งจริง
และคุณจะสรุปต่อว่า SELECT ตัวใหม่จึงเข้าได้เสมอตราบใดที่ยังไม่มีใครถือ ACCESS EXCLUSIVE
อยู่จริง ซึ่งผิด
คำขอที่ยังไม่ได้รับก็กันทางเหมือนกัน คำขอ ACCESS SHARE ตัวใหม่ที่มาทีหลังคำขอ ACCESS EXCLUSIVE ที่ยังรออยู่ จะต้องรอต่อท้ายมัน ทั้งที่มันไม่ได้ชนกับใครที่ถืออยู่เลย
ผมไปหาประโยคที่เขียนกฎนี้ไว้ในเอกสารทางการของ PostgreSQL 18 แล้วไม่เจอ หน้า Explicit Locking บอกแค่ว่า transaction ที่ขอ lock “will wait indefinitely for conflicting locks to be released” กับให้ตารางว่าอะไรชนกับอะไร
ช่องว่างนั้นคือเหตุผลที่คนแปลกใจ ตารางความขัดแย้งตอบคำถามว่า “ถือพร้อมกันได้ไหม” ไม่ได้ตอบคำถามว่า “เข้าคิวแซงกันได้ไหม” ผมจึงไม่อ้างเอกสารข้อนี้ ผมวัดมันแทน
วิธีวัด ลำดับตายตัวใน code ไม่มี sleep
หัวข้อที่มีชื่อว่า “วิธีวัด ลำดับตายตัวใน code ไม่มี sleep”สอง arm ที่ต่างกันบรรทัดเดียว แล้วดูว่าบรรทัดนั้นเปลี่ยนอะไร
ตัววัดขับ psql หลาย session ผ่าน pty แล้วสลับคิวตามลำดับที่เขียนตายตัวไว้ใน code
ไม่มี sleep ไม่มี thread แข่งกัน — anomaly ที่ต้องพึ่งความเร็วเครื่องคือ anomaly ที่สอนไม่ได้
สิ่งที่ตัดสินว่า session หนึ่งถูกกักไว้ ไม่ใช่นาฬิกา แต่เป็นแถวใน pg_locks ที่
granted = false ซึ่งอ่านมาจากคนละ connection กับ session ที่กำลังรออยู่
ตารางที่ใช้คือ orders 50,000 แถว ปิด autovacuum ทั้งที่ระดับเซิร์ฟเวอร์และระดับตาราง
เพราะ worker ของ autovacuum ถือ SHARE UPDATE EXCLUSIVE ซึ่งชนกับ ACCESS EXCLUSIVE
ถ้าไม่ปิด แถวของมันจะโผล่ใน pg_locks แล้วถูกนับเป็นความยาวคิว ทั้งที่ไม่ใช่ session ที่เราสั่ง
สอง arm ต่างกันบรรทัดเดียว
| ขั้น | แผงควบคุม | arm ที่วัดคิว |
|---|---|---|
| 1 | session R เปิด transaction แล้ว SELECT หนึ่งครั้ง | เหมือนกัน |
| 2 | R นั่งเฉย ๆ ไม่ commit | เหมือนกัน |
| 3 | — | session D ยิง ALTER TABLE |
| 4 | reader อีก 8 ตัวยิง SELECT | เหมือนกัน |
| 5 | อ่าน pg_locks จากคนละ connection | เหมือนกัน |
แผงควบคุมมีไว้ตัดข้อแก้ตัวว่า “ก็ reader ตัวยาวนั่นแหละที่บล็อก” ถ้าข้อนั้นจริง reader ทั้ง 8 ตัวต้องติดคิวในแผงควบคุมด้วย
ผลลัพธ์ บรรทัดเดียวที่เปลี่ยนทุกอย่าง
หัวข้อที่มีชื่อว่า “ผลลัพธ์ บรรทัดเดียวที่เปลี่ยนทุกอย่าง”reader ตัวยาวไม่ได้บล็อกใครเลย จนกระทั่งมี ALTER มาต่อคิวคั่นกลาง
| ติดคิว ไม่ได้รัน | ผ่าน ได้คำตอบ | แถวใน pg_locks ที่ยังไม่ได้รับ | |
|---|---|---|---|
แผงควบคุม ไม่มี ALTER | 0 | 8 | 0 |
มี ALTER คั่นกลาง | 8 | 0 | 9 |
reader ตัวยาวตัวเดียวกัน ค้างอยู่ในสถานะเดียวกัน ต่างกันแค่มี ALTER TABLE หนึ่งบรรทัด
มาต่อคิวคั่นกลาง แล้วคำสั่งที่ผ่านไปได้เปลี่ยนจาก 8 เป็น
แถวใน pg_locks คือ 9 — reader 8 ตัว
บวก ALTER เองอีกหนึ่ง สภาพคิวตอนนั้นหน้าตาแบบนี้
| ลำดับ | lock ที่ขอ | ได้รับแล้ว | สถานะของ session |
|---|---|---|---|
| ตัวที่ถืออยู่ | AccessShareLock | ✅ | idle in transaction |
ALTER TABLE | AccessExclusiveLock | ❌ | active |
| reader × 8 | AccessShareLock | ❌ | active |
บรรทัดแรกคือทั้งเรื่อง session ที่ก่อ outage นี้อยู่ในสถานะ
idle in transaction มันไม่ได้กำลัง query อะไรอยู่ มันเปิด transaction ทิ้งไว้
แล้วไปทำอย่างอื่น อาจจะรอ HTTP call อาจจะเป็น ORM ที่ลืมปิด อาจจะเป็นคนเปิด psql ค้างไว้
reader ทั้ง 8 ตัวขอ lock ตัวเดียวกันเป๊ะ กับตัวที่ session แรก
ถืออยู่และได้รับไปแล้ว สองตัวนั้นไม่ชนกัน มันแค่บังเอิญมาทีหลัง ALTER
- คิวนี้ผูกกับตาราง ไม่ใช่ทั้งฐานข้อมูล session ใหม่ที่ถามอีกตารางหนึ่งตอนคิวตันที่สุด
ผ่านไปได้ทันที การวัดบันทึกข้อนี้ไว้เป็น
otherTableCompleted - reader ที่รออยู่ไม่ได้ผิด มันแค่ไม่ได้รัน พอ session แรก commit ทั้ง 8 ตัวคืนคำตอบที่ถูกต้องออกมาครบ ไม่มีตัวไหน error ไม่มีตัวไหนตอบเพี้ยน
- ตัวที่ก่อคิวไม่ได้ทำงานเลย
ALTERที่ใช้คือalter table orders add column note textซึ่งหัวข้อถัดไปวัดได้ว่ามันเขียน heap ใหม่ 0 หน้า
ALTER TABLE ไม่ได้แพงเท่ากันทุกรูป แต่ล็อกเท่ากันทุกรูป
หัวข้อที่มีชื่อว่า “ALTER TABLE ไม่ได้แพงเท่ากันทุกรูป แต่ล็อกเท่ากันทุกรูป”lock เหมือนกันหมด ต่างกันที่จำนวนหน้าที่ต้องเขียนใหม่
คำเตือนของคอร์ส EF Core แยก ALTER COLUMN TYPE ออกจาก ADD COLUMN
ซึ่งถูกในแง่ของงาน และตารางข้างล่างยืนยันให้ · แต่บนแกนของ lock สองรูปนั้นเท่ากันเป๊ะ
ตัววัดยิงหกรูปใส่ตารางเดิม แล้วดูสองอย่าง lock ที่มันขอ กับ relfilenode เปลี่ยนไหม
(relfilenode เปลี่ยน = PostgreSQL เขียนตารางใหม่ลง file ใหม่แล้วทิ้ง file เก่า
เป็นคำตอบตรงตัวกว่าดูขนาดของ file)
รูปของ ALTER TABLE | lock ที่ขอ | หน้าก่อน | หน้าหลัง | เขียนใหม่กี่หน้า |
|---|---|---|---|---|
add column note text | AccessExclusiveLock | 271 | 271 | ไม่แตะ heap |
add column tag text default 'x' | AccessExclusiveLock | 271 | 271 | ไม่แตะ heap |
drop column note_legacy | AccessExclusiveLock | 271 | 271 | ไม่แตะ heap |
alter column note_legacy set not null | AccessExclusiveLock | 271 | 271 | ไม่แตะ heap |
add column token uuid default gen_random_uuid() | AccessExclusiveLock | 271 | 384 | 384 หน้า |
alter column total_satang type bigint | AccessExclusiveLock | 271 | 320 | 320 หน้า |
ช่อง lock ทั้งช่องมีค่าเดียว ทุกรูปขอ AccessExclusiveLock เท่ากันหมด
เอกสารของ ALTER TABLE เขียนไว้ว่า “An ACCESS EXCLUSIVE lock is acquired unless
explicitly noted.” และหกรูปนี้ไม่มีรูปไหนถูก note ไว้
ความต่างอยู่ที่ช่องสุดท้าย 4 รูปแรกไม่แตะ heap สักหน้า อีก 2 รูปเขียนตารางใหม่ทั้งใบ
แถวที่สามคือแถวที่ตอบทั้งบทความ DROP COLUMN ไม่ได้ลบข้อมูลออกจากหน้าสักหน้า
มันแก้ catalog แล้วจบ จำนวนหน้าเท่าเดิมเป๊ะ · แต่มันถือ lock ตัวเดียวกับ
ALTER COLUMN TYPE ที่เขียนใหม่ทั้งตาราง คำสั่งที่ไม่ทำอะไรกับข้อมูลเลย
ก็หยุดเว็บได้เท่ากัน
สี่รูปที่ไม่แตะ heap ไม่ฟรีเพราะเหตุผลเดียวกัน
หัวข้อที่มีชื่อว่า “สี่รูปที่ไม่แตะ heap ไม่ฟรีเพราะเหตุผลเดียวกัน”ตั้งแต่ PostgreSQL 11 การเพิ่ม column ที่มี default แบบไม่ volatile จะเก็บค่านั้นไว้ใน metadata ของตารางแล้วคืนให้ตอนอ่านแถวเก่า ไม่ต้องไล่เขียนทุกแถว เอกสารเขียนว่า “In neither case is a rewrite of the table required.”
แต่ default ที่ volatile ไม่ได้ เพราะทุกแถวต้องได้คนละค่า จึงต้องไล่เขียนจริง
เอกสารเขียนว่ามัน “will cause the entire table and its indexes to be rewritten”
การวัดข้างบนยืนยันทั้งสองข้อ — default 'x' ไม่ rewrite ส่วน
default gen_random_uuid() rewrite
สิ่งที่ตัดสินไม่ใช่คำว่า ADD COLUMN แต่เป็น default ที่ตามหลังมา
อีกสองรูปที่ไม่แตะ heap ไปถึงจุดเดียวกันคนละทาง
DROP COLUMN ไม่ได้ลบข้อมูลจริง มันทำให้ column นั้นมองไม่เห็นในระดับ catalog
แล้วปล่อยให้ที่ว่างทยอยคืนตอนแถวถูก update
ส่วน SET NOT NULL ต้อง อ่าน ทั้งตารางเพื่อยืนยันว่าไม่มีแถวไหนเป็น null
แต่ไม่ได้ เขียน อะไรใหม่เลย จำนวนหน้าจึงเท่าเดิม
SET NOT NULL เป็นตัวที่หลอกตาที่สุดในหกรูป เพราะมันอ่านทั้งตารางจริง ๆ
งานของมันโตตามขนาดตาราง แต่ไปโตในช่องที่ตารางข้างบนไม่ได้วัด — ตารางนั้นนับ หน้าที่เขียนใหม่
ไม่ได้นับ หน้าที่อ่าน
หกรูปข้างบนแยกได้เป็นสามกลุ่ม ไม่ใช่สองกลุ่มอย่างที่ตารางชวนให้คิด
- แก้ catalog แล้วจบ —
ADD COLUMN2 แบบที่ไม่ volatile และDROP COLUMN· งานคงที่ ไม่ขึ้นกับขนาดตาราง - อ่านทั้งตาราง แต่ไม่เขียน —
SET NOT NULL· งานโตตามขนาดตาราง แต่เลี่ยงได้ด้วยCHECKconstraint ที่ valid อยู่ก่อน ซึ่ง หน้านี้ไม่ได้วัดทางเลี่ยงนั้น - เขียนตารางใหม่ทั้งใบ — default ที่ volatile และ
ALTER COLUMN TYPE
ทั้งสามกลุ่มถือ ACCESS EXCLUSIVE เท่ากัน กลุ่มไหนถือนานกว่าเป็นอีกเรื่อง แต่คิวที่หน้านี้วัด เกิดตั้งแต่ก่อนที่กลุ่มไหนจะได้ lock ด้วยซ้ำ
- เวลาที่ถือ lock — ยาวตามจำนวนหน้าที่ต้องเขียน
ALTER COLUMN TYPEบนตารางล้านแถว ถือ ACCESS EXCLUSIVE ไว้จนกว่าจะเขียนเสร็จ นี่คือสิ่งที่คำเตือนของคอร์สพูดถึง - เวลาที่ รอ lock — ยาวตามอายุของ transaction ที่ค้างอยู่ ไม่เกี่ยวกับงานของ DDL เลย นี่คือสิ่งที่การวัดหน้านี้จับได้ และเป็นสิ่งที่คำเตือนของคอร์สไม่ได้พูดถึง
ADD COLUMN มีข้อแรกเป็นศูนย์ แต่ข้อสองเท่ากับทุกรูป ระหว่างที่มันรอ คิวก็ยาวขึ้นเรื่อย ๆ
เหมือนกัน
lock_timeout เปลี่ยน outage ให้เป็น migration ที่ล้ม
หัวข้อที่มีชื่อว่า “lock_timeout เปลี่ยน outage ให้เป็น migration ที่ล้ม”คำสั่งเดียวกัน ลำดับเดียวกัน ต่างกันที่ DDL ยอมแพ้แทนที่จะรอ
ตัววัดรัน arm ที่สามด้วยลำดับเดียวกับ arm ที่วัดคิวทุกประการ ต่างกันบรรทัดเดียว
คือ session ที่จะยิง ALTER ตั้ง SET lock_timeout = '250ms'
ไว้ก่อน
| ติดคิว | ผ่าน | แถวใน pg_locks ที่ยังไม่ได้รับ | column ลงจริง | |
|---|---|---|---|---|
ไม่ตั้ง lock_timeout | 8 | 0 | 9 | ✅ |
ตั้ง 250ms | 0 | 8 | 0 | ❌ |
ALTER คืน SQLSTATE 55P03 (lock_not_available)
แล้วปล่อยคิวทันที reader ที่ตามมาจึงไม่มีคำขอที่ยังไม่ได้รับให้ต้องรอต่อท้าย
ทั้ง 8 ตัวผ่านไปได้ และ column ไม่ได้ลง
นี่คือการเลือกที่ต้องเลือกไว้ล่วงหน้า ไม่ตั้ง = migration สำเร็จแน่นอน แต่คุณไม่รู้ ว่ามันจะพาคิวยาวแค่ไหนไปด้วย ตั้ง = migration ล้มบ่อยขึ้น แต่ล้มแบบที่ไม่มีใครนอกทีมรู้สึก
ที่สำคัญกว่าคือ มันล้มพร้อมรหัสที่ pipeline อ่านได้ SQLSTATE
55P03 แยกออกจาก error อื่นได้ชัดเจน จึงเขียนเป็นเงื่อนไข
retry ได้ตรง ๆ ต่างจากการปล่อยให้มันค้างซึ่งไม่มีรหัสอะไรออกมาเลย
-- ตั้งที่ระดับ session ของ migration ไม่ใช่ที่ระดับเซิร์ฟเวอร์-- ระดับเซิร์ฟเวอร์จะไปโดน query ปกติของแอปด้วยSET lock_timeout = '3s';ALTER TABLE orders ADD COLUMN note text;lock_timeout นับเฉพาะช่วงที่ รอ lock พอได้ lock มาแล้วมันหยุดนับ
แปลว่ามันกันกรณีของหน้านี้ได้ครบ แต่กัน ALTER COLUMN TYPE ที่ได้ lock ไปแล้ว
แล้วกำลังเขียนตารางใหม่ทั้งใบไม่ได้เลย สองกรณีนั้นต้องใช้เครื่องมือคนละตัว
และหน้านี้วัดแค่กรณีแรก · หน้านี้ไม่ได้วัดว่าการ rewrite กินเวลาเท่าไร
ทำอะไรได้บ้าง
หัวข้อที่มีชื่อว่า “ทำอะไรได้บ้าง”สามข้อ เรียงตามลำดับที่ควรทำ
หนึ่ง ตั้ง lock_timeout ที่ session ของ migration เป็นบรรทัดเดียวและได้ผลตามที่วัดไว้
ข้างบน ตั้งสั้น ๆ แล้ว retry ดีกว่าตั้งยาวแล้วหวังว่าจะไม่มีใครค้าง
สอง ไล่ transaction ที่ค้าง ตัวที่ก่อเรื่องในการวัดนี้อยู่ในสถานะ
idle in transaction ซึ่งเป็นสถานะที่หาเจอได้ล่วงหน้า
idle_in_transaction_session_timeout ตัดมันทิ้งได้อัตโนมัติ และควรตั้งไว้ก่อนที่จะมี migration
สาม รู้ว่าก้าวไหน rewrite ตารางในหัวข้อข้างบนคือคำตอบสำหรับสี่รูปที่พบบ่อยที่สุด รูปที่ rewrite ต้องแตกเป็นหลายก้าว หรือใช้ท่าที่เขียน column ใหม่แล้วค่อยสลับ ไม่ใช่ยิงตูมเดียวแล้วหวัง
- ไม่ได้วัดว่าคิวยาวเท่าไรต่อหนึ่งหน่วยเวลา 8 คือจำนวน reader ที่ตัววัดส่งเข้าไป ไม่ใช่ผลลัพธ์ ข้ออ้างของหน้านี้คือ เข้ามากี่ตัว รอครบทุกตัว ไม่ใช่ รอกี่ตัวต่อวินาที
- ไม่ได้วัดตารางที่มีคนเขียนอยู่ตลอด ตารางที่วัดนิ่ง ไม่มีใครเขียนแทรกระหว่างการวัด
- ไม่ได้วัด
CREATE INDEX CONCURRENTLYหรือท่าที่ไม่ใช้ ACCESS EXCLUSIVE หน้านี้วัดหกรูปที่ระบุไว้ในตารางเท่านั้น - ไม่ได้วัดทางเลี่ยง scan ของ
SET NOT NULLเอกสารบอกว่ามีCHECKconstraint ที่ valid อยู่ก่อนแล้วจะข้าม scan ได้ · หน้านี้วัดเฉพาะทางที่ไม่มี constraint นั้น - ไม่ได้วัดว่า EF Core สร้าง SQL แบบไหน หน้านี้วัดที่ชั้น SQL ตรง ๆ คอร์ส EF Core เป็นที่ของคำถามนั้น
- ตัวเลขจำนวนหน้าผูกกับ
block_sizeและรุ่นของเอนจิน ที่วัดคือ 8192 byte ตัวตรวจจึงตรวจว่า rewrite หรือไม่ rewrite ไม่ได้ตรึงจำนวนหน้าทีละตัว
ตัวเลขทุกตัวในหน้านี้มาจากสามคำสั่งใน repo ของไซต์นี้
# ต้องมี Docker จึงอยู่นอก npm test — กติกาเดียวกับ verify:pagination-pgKAEN_PG_CONTAINER=kaen-pg-ddl-lock scripts/ddl-lock/up.shnpm run verify:ddl-lockdocker rm -f kaen-pg-ddl-lock
# ไม่ต้องมี Docker — อ่านผลที่ commit ไว้ แล้วยิงการกลายพันธุ์ใส่ตัวตรวจpython3 scripts/ddl-lock/probe.py --selftestnpm run verify:ddl-lock ไม่ได้เทียบกับตัวเลขที่ commit ไว้เฉย ๆ มันวัดใหม่ทั้งชุด
แล้วตรวจว่าข้ออ้างของหน้านี้ยังเป็นจริงไหม — แผงควบคุมยังไม่มีใครติดคิว arm ที่มี ALTER
ยังติดครบทุกตัว ALTER ที่ก่อคิวยังเป็นรูปที่ไม่ rewrite และ lock_timeout
ยังคืนรหัสเดิม
ส่วน --selftest ไม่ต้องมี Docker มันอ่านผลที่ commit ไว้ แล้วบิดผลนั้นทีละข้ออ้าง
เพื่อบังคับว่าตัวตรวจต้องแดงทุกแบบ ด่านที่ไม่เคยถูกทำให้แดง
คือด่านที่ยังไม่มีใครรู้ว่ามันตรวจอะไร — และเพราะมันไม่ต้องมี Docker
มันจึงอยู่ในเกตของ CI ได้ ต่างจาก --check
- PostgreSQL 18 — Explicit Locking
— ตารางความขัดแย้ง และประโยค “an
ACCESS SHARElock can be held by multiple transactions” (ตรวจแล้ว 2026-08-22) - PostgreSQL 18 — ALTER TABLE
— “An
ACCESS EXCLUSIVElock is acquired unless explicitly noted.” และกฎ rewrite ของ default ที่ volatile กับไม่ volatile (ตรวจแล้ว 2026-08-22) - PostgreSQL 18 — Client Connection Defaults
—
lock_timeoutและidle_in_transaction_session_timeout(ตรวจแล้ว 2026-08-22) - PostgreSQL 18 — PostgreSQL Error Codes
—
55P03คือlock_not_available(ตรวจแล้ว 2026-08-22)
วัดบน PostgreSQL 18.6 · aarch64-unknown-linux-musl · autovacuum ปิด · block_size 8192 byte