
ภาพหน้าปก: อัตราการไหลของข้อมูลถูกควบคุมด้วย Backpressure
เวลาอ่านโดยประมาณ 20 นาที — บทความนี้จะพาคุณเจาะลึกแนวคิด Reactive Streams Backpressure และวิธีนำไปใช้งานอย่างเป็นระบบใน Spring WebFlux (บน Project Reactor) เพื่อป้องกัน Out-Of-Memory, ปรับสมดุล workload และยกระดับ throughput ของแอป Reactive API ที่ใช้ Netty หรือ Undertow อยู่เบื้องหลัง
สารบัญ
- Backpressure คืออะไร & ทำไมต้องสนใจ?
- สัญญา Reactive Streams 4 ข้อ
- Operators รับมือ Backpressure ยอดนิยม
- ตัวอย่างโค้ด ครบวงจร
- เทคนิค Tuning ใน Spring Boot 3.5+
- ข้อผิดพลาดที่พบบ่อย
- Checklist สำหรับ Production
1. Backpressure คืออะไร & ทำไมต้องสนใจ?
ในระบบ Reactive Publisher กับ Subscriber จะสื่อสารกันด้วยสัญญาณ request(n) ผู้บริโภค (Consumer) จะบอกผู้ผลิต (Producer) ว่าต้องการข้อมูลทีละกี่ชิ้น หากผู้ผลิตส่งเร็วกว่าที่ร้องขอ เราเรียกว่า “overflow”. อาการที่พบบ่อย:
- คิว buffer โตไม่จำกัด → RAM พุ่งจน OOM
- Thread ถูกบล็อกรอ I/O เพราะ Storm ของ onNext()
- Latency กระโดดสูงใน P99 เพราะ GC ยิงถี่

การเปิด Backpressure จึงเหมือนใส่วาล์วควบคุมแรงดัน ช่วยรักษาอัตราการไหลให้สมดุล
2. สัญญา Reactive Streams 4 ข้อ
- Responsive – ต้องพร้อมตอบสนองตลอด
- Resilient – ต้องทนต่อความผิดพลาด
- Elastic – ขยายตามภาระงาน
- Message-Driven – ใช้ non-blocking message เป็นตัวควบคุม
ข้อสุดท้ายคือหัวใจ เพราะ Publisher จะส่ง onNext ได้ก็ต่อเมื่อ Subscriber ขอ
3. Operators รับมือ Backpressure ยอดนิยม

| Operator | หลักการ | ข้อดี | ข้อควรระวัง |
|---|---|---|---|
limitRate(int n) | ผ่อนส่งข้อมูลทีละก้อน | ควบคุมแรงดันได้แม่น, API อ่านง่าย | เพิ่ม latency เล็กน้อยหาก n ต่ำไป |
buffer(int size) | กักข้อมูลในหน่วยความจำ | ไม่มี data loss | เสี่ยง OOM ถ้า size ไม่จำกัด |
onBackpressureDrop() | ทิ้ง element เกิน demand | รักษา latency, ใช้ RAM น้อย | ข้อมูลหาย ไม่เหมาะกับ critical data |
4. ตัวอย่างโค้ด ครบวงจร
4.1 Controller แบบ Endpoint-Streaming
// src/main/java/com/example/controller/EventController.java
@RestController
@RequestMapping("/v1/events")
public class EventController {
private final EventService eventService;
public EventController(EventService eventService) {
this.eventService = eventService;
}
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<Event> streamEvents() {
return eventService.events()
.limitRate(128) // ปรับแรงดัน
.onBackpressureBuffer(256) // กักไม่เกิน 256
.publishOn(Schedulers.boundedElastic());
}
}
4.2 Service จำลอง Producer เร็วกว่า Consumer
@Service
public class EventService {
public Flux<Event> events() {
return Flux.interval(Duration.ofMillis(5)) // ยิงถี่มาก
.map(this::toEvent);
}
private Event toEvent(Long seq) {
return new Event(seq, Instant.now());
}
}
4.3 ทดสอบ Backpressure ใน JUnit 5
@Test
void backpressure_should_not_overflow() {
StepVerifier.create(eventService.events()
.limitRate(10)
.take(100))
.expectNextCount(100)
.verifyComplete();
}
5. เทคนิค Tuning ใน Spring Boot 3.5+
- ปรับ
reactor.netty.io-worker-countเท่าจำนวน CPU logical core - ตั้ง
spring.codec.max-in-memory-sizeจำกัด buffer แปลง JSON ใหญ่ ๆ - ใช้
@Schedulerกำหนด thread pool เฉพาะสำหรับ blocking I/O - เปิด
BlockHoundตรวจเจอ blocking call ขณะ ทำ load test

6. ข้อผิดพลาดที่พบบ่อย
⚠ ลืม Backpressure ที่ DB Layer
เช่น ดึง Flux ใหญ่จาก R2DBC แล้ว collectList() ใน Service → กลายเป็น blocking
- ทำ parallel() แต่ไม่
.runOn()→ ยังคงทำงานบน main event-loop - ละเลย error signal → ต้อง
onErrorResume()ก่อน retry - เชน operator ที่ขยายข้อมูล (flatMap) โดยไม่จำกัด
concurrency
7. Checklist ก่อนขึ้น Production
- เขียน
StepVerifierกับทุก Flux/Mono ที่ Critical - ตั้งค่า limitRate ใน ทุก จุด ที่ Producer > Consumer
- กำหนดขนาด buffer ให้เหมาะกับ JVM Heap
- ใช้ SLA (P99) ตรวจ latency เสมอใน Grafana/Prometheus
- จำลอง Load Spike จริง ด้วย k6 หรือ Vegeta
สรุป
Backpressure ไม่ใช่แค่ตัวเลือก แต่คือหัวใจของสถาปัตยกรรม Reactive ทุกระดับ ตั้งแต่ function เล็ก ๆ จนถึง microservice ทั้งระบบ หากเข้าใจจังหวะ request-response ของ Reactive Streams และนำ operators มาใช้ถูกต้อง คุณจะได้แอปที่ เร็ว เสถียร และ รองรับ โหลดสูง อย่างมั่นใจ