บทที่ 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 ใน structsync.Mutexอย่าถือ lock ข้าม I/O
ส่งงานหรือโอน ownershipchannelระบุ owner, close และ capacity
counter/flag อิสระtyped sync/atomicหลาย field ไม่เป็น transaction
initialize ครั้งเดียวsync.OnceValueerror/retry semantics ต้องชัด
suppress request ซ้ำsingleflightไม่ใช่ cache; caller แชร์ผล/error
รอหลายงานและรวม errorerrgroupfunction ต้องฟัง 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