บทที่ 18 · Part 5 — Concurrency That Stops
Synchronization and Backpressure
เลือก mutex, channel, atomic และ bounded queue จาก invariant ไม่ใช่จากความชอบ
ระบบที่รับ request ได้เร็วกว่า downstream ทำงานได้จะสะสมบางอย่างเสมอ: goroutine, channel item, memory, connection หรือ latency ถ้า design ไม่ระบุว่าจะสะสมได้เท่าไร production จะเป็นผู้เลือก limit ให้ผ่าน OOM, timeout หรือ database collapse Backpressure จึงเป็น correctness และ reliability contract
จบบทนี้คุณจะ
- เลือก mutex, channel, atomic และ singleflight จาก invariant
- คำนวณ capacity และกำหนด behavior เมื่อเต็ม
- รู้ตำแหน่งของ
samber/roและsamber/hotใน toolbox โดยไม่ใช้เป็น default
เลือก Primitive จากสิ่งที่ต้องสื่อ
| ปัญหา | Default | สิ่งที่ต้องระวัง |
|---|---|---|
| ปกป้อง state/invariant ใน struct | sync.Mutex | อย่าถือ lock ข้าม I/O |
| ส่งงานหรือโอน ownership | channel | ระบุ owner, close และ capacity |
| counter/flag อิสระ | typed sync/atomic | หลาย field ไม่เป็น transaction |
| initialize ครั้งเดียว | sync.OnceValue | error/retry semantics ต้องชัด |
| suppress request ซ้ำ | singleflight | ไม่ใช่ cache; caller แชร์ผล/error |
| รอหลายงานและรวม error | errgroup | function ต้องฟัง context |
RWMutex ไม่ได้เร็วกว่า Mutex เสมอ มี overhead และ writer contention วัด workload ก่อนเปลี่ยน
sync.Map เหมาะกับรูปแบบเฉพาะตาม package docs เช่น key เขียนครั้งเดียวอ่านมาก หรือ goroutine แต่ละชุด
แตะ key คนละกลุ่ม map+mutex ปกติมัก type-safe และรักษา invariant หลาย field ได้ง่ายกว่า
Queue ทุกตัวต้องมีคำตอบเมื่อเต็ม
buffer ขนาด 100 ไม่ได้ดีเพราะเป็นเลขกลม ให้เริ่มจากจำนวน worker, service time, burst ที่ยอมรับ และ memory ต่อ item สมมติ worker 8 ตัวใช้เฉลี่ย 50 ms จะรับได้ประมาณ 160 งาน/วินาทีใน steady state ถ้า producer ส่ง 500 งาน/วินาทีต่อเนื่อง buffer ใดก็เต็ม ต่างกันเพียงช้าหรือเร็ว
กำหนด policy ให้ชัดเมื่อเต็ม:
- Block เพื่อส่ง backpressure ถึง caller เมื่อ caller มี deadline และยอมรอได้
- Reject ด้วย error ที่ retry ได้ เช่น HTTP 429/503 พร้อม policy ของระบบ
- Shed/drop เฉพาะข้อมูลที่ยอมเสียได้ พร้อม metric ไม่ทำเงียบ
- Persist ไป durable queue เมื่อห้ามงานหาย แต่ยังต้องจำกัด local buffering
channel buffer ไม่ได้เพิ่ม throughput โดยอัตโนมัติ มัน decouple producer/consumer ชั่วคราวและกิน memory unbuffered channel ให้ rendezvous ที่ชัด ส่วน buffer 1 เหมาะกับ result ที่ producer ต้องส่งได้แม้ caller เริ่ม cancel แต่ heuristic ต้องอธิบายจาก protocol ไม่ใช่กฎตายตัว
Worker Pool ที่หยุดและระบายได้
owner ควรสร้าง input channel, เริ่ม worker จำนวนคงที่, ปิด input เมื่อหยุดรับงาน และรอ worker drain
ถ้า shutdown ต้อง cancel ทันที ให้ worker select ctx.Done() ระหว่างรับงานและส่ง downstream
อย่าปิด channel จาก receiver หลายตัว
I/O concurrency ต้องสัมพันธ์กับ downstream pool เช่น worker 100 ตัวแต่ MySQL MaxOpenConns=10
จะสร้าง queue ซ้อนใน database/sql และทำให้ deadline หมดก่อน query เริ่ม ในบท
Runtime-Aware MySQL and Redis Clients เราจะวาง budget ทั้งเส้น
Reactive Streams และ In-Memory Cache
samber/ro ให้ ReactiveX operators, scheduling และ backpressure
vocabulary เหมาะกับ event pipeline ที่ทีมเข้าใจ reactive model และ composition ลด code จริง แต่สำหรับ
worker pool ทั่วไป channel + errgroup อ่านง่ายกว่าและเข้ากับ Go ecosystem มากกว่า จึงเป็น elective
samber/hot มี in-memory cache algorithms หลายแบบ เช่น LRU,
LFU และ TinyLFU เหมาะเมื่อ process-local cache, eviction policy และ metrics เป็น requirement ชัด
มันไม่แทน Redis เมื่อหลาย instance ต้องแชร์ state และไม่ควร cache authoritative decision โดยไม่กำหนด
staleness/invalidation contract
Production Toolbox
Default: mutex สำหรับ shared invariant, channel/errgroup สำหรับ work ownership, typed atomic สำหรับ
state เล็ก และ bounded capacity ทุกชั้น ใช้ singleflight กัน stampede, ro สำหรับ reactive pipeline
เฉพาะทาง และ hot สำหรับ measured local-cache workload
Checklist ของ Backpressure
- producer rate, worker count, service time และ memory/item ถูกวัดหรือประมาณพร้อมสมมติฐาน
- queue/pool/buffer ทุกตัวมี bound และ full policy
- ไม่มี lock ถูกถือระหว่าง network/DB I/O
- concurrency limit สอดคล้องกับ downstream pool และ rate limit
- drop/reject/block/persist เป็น observable behavior
- cache มี owner, TTL/eviction, invalidation และ authoritative boundary
- load test ครอบคลุม overload และ shutdown ไม่ใช่เฉพาะ happy path
อ่านเพิ่ม: Mutex or Channel?,
sync package,
singleflight และ
Go pipelines and cancellation