Distributed Systems · บทที่ 3 จาก 11

Distributed Objects & Remote Invocation

Request–Reply Protocol ในบทที่แล้วยังเป็นระดับ "ข้อความดิบ" — บทนี้ห่อหุ้มมันให้ดูเหมือนการเรียก function/method ธรรมดา (Remote Procedure Call / Remote Method Invocation) พร้อมตอบคำถามที่สำคัญที่สุดข้อหนึ่งของวิชา: เมื่อ remote call ล้มเหลว ระบบควร "รับประกัน" อะไรได้บ้าง

📚
อิงเนื้อหาจาก Coulouris บทที่ 5 (Distributed Objects and Remote Invocation) — ต่อยอดจาก Request-Reply Protocol ในบทที่ 2 โดยตรง และเชื่อมโยงกับ RPI/gRPC ในวิชา Network Computing หัวข้อที่ 4

1แนวคิด: ทำให้ Remote Call ดูเหมือน Local Call

RPC (Remote Procedure Call) / RMI (Remote Method Invocation)
เทคนิคที่ทำให้โปรแกรมเมอร์เรียกใช้ฟังก์ชันหรือ method ที่อยู่บนเครื่องอื่นได้ด้วย syntax เดียวกับการเรียก local function — ซ่อนรายละเอียดของ marshalling, การส่งข้อความผ่านเครือข่าย และการรอผลลัพธ์ไว้เบื้องหลัง
กับดักสำคัญ
Remote call ไม่ใช่ local call แม้จะเขียนโค้ดคล้ายกัน — remote call มี latency ที่ผันผวน มี failure mode ที่ local call ไม่มี (network ขาด, ปลายทางล่ม, partial failure) การซ่อน network ไว้หลัง syntax ที่ดูเหมือน local call ทำให้ผู้พัฒนาลืมออกแบบรับมือ failure ได้ง่าย — Coulouris เรียกสิ่งนี้ว่าปัญหาของ "transparency ที่มากเกินไป" ตรงกับ Chatty Services และ RPI ที่กล่าวถึงใน Network Computing หัวข้อที่ 4

2โครงสร้างของ Remote Object Reference

เพื่อให้ client อ้างอิงถึง object ที่อยู่บนเครื่องอื่นได้ ต้องมี reference ที่ระบุตัวตนได้ไม่ซ้ำกันทั่วทั้งระบบ ประกอบด้วย:

องค์ประกอบความหมาย
เลขที่อยู่ของโฮสต์ (IP address)เครื่องที่ object นั้นอาศัยอยู่
หมายเลขพอร์ตProcess/service ที่ object ทำงานอยู่บนเครื่องนั้น
เวลาที่ object ถูกสร้างป้องกันการอ้างอิงผิดตัวหาก object เดิมถูกทำลายและมี object ใหม่มาแทนที่ในตำแหน่งเดียวกัน
หมายเลข objectกำหนดโดย process เจ้าของ ใช้แยก object หลายตัวในเครื่องเดียวกัน
Interface ที่ object implement อยู่บอกว่า object นี้เรียก method อะไรได้บ้าง

3สถาปัตยกรรมของ Middleware: Proxy, Skeleton, Communication Module

แนวคิด Stub/Proxy Pattern
ทั้งฝั่ง client และ server มีโค้ดที่ generate อัตโนมัติ (จาก IDL) คั่นอยู่ระหว่างโปรแกรมของผู้ใช้กับเครือข่าย เพื่อทำให้การเรียก remote method "ดูเหมือน" การเรียก local method
องค์ประกอบบทบาท
Proxy (Client stub)อยู่ฝั่ง client — ทำตัวเหมือน object จริง เมื่อถูกเรียก method จะ marshal argument แล้วส่งข้อความไปหา server
Skeleton (Server stub / Dispatcher)อยู่ฝั่ง server — unmarshal argument ที่รับมา แล้วเรียก method จริงของ object ที่ implement อยู่ จากนั้น marshal ผลลัพธ์ส่งกลับ
Communication Moduleจัดการ request-reply protocol ที่เรียนในบทที่ 2 — ทั้งฝั่ง client และ server ใช้ module เดียวกัน
Remote Reference Moduleแปลงระหว่าง local reference กับ remote object reference — สร้าง/ค้นหา proxy ให้ตรงกับ remote object ที่ต้องการ

4กรณีศึกษาทางประวัติศาสตร์: CORBA และ Java RMI

CORBA (Common Object Request Broker Architecture)

มาตรฐานสำหรับระบบ distributed object ข้ามภาษา — ใช้ IDL (Interface Definition Language) เป็นกลางที่ไม่ผูกกับภาษาใดภาษาหนึ่ง ORB (Object Request Broker) ทำหน้าที่ส่ง request ระหว่าง client/server ที่อาจเขียนคนละภาษากัน

Java RMI

เรียบง่ายกว่าเพราะจำกัดเฉพาะ Java-to-Java — ใช้ Java interface เป็น IDL ในตัวเอง ส่ง object จริงผ่าน Java Serialization ได้ (ไม่ใช่แค่ primitive type) แต่แลกกับการไม่ข้ามภาษาได้เหมือน CORBA

ทำไมต้องรู้เทคโนโลยีเก่า
CORBA และ Java RMI ไม่ได้รับความนิยมในระบบใหม่แล้ว แต่ปัญหาที่มันแก้ (marshalling ข้ามภาษา, การ generate stub อัตโนมัติจาก interface, การจัดการ remote reference) ยังคงเป็นปัญหาเดิมที่ gRPC และ Thrift แก้อยู่ในปัจจุบัน — เข้าใจ CORBA/RMI ทำให้เข้าใจว่าทำไม gRPC ถึงออกแบบมาแบบที่เป็นอยู่

5Invocation Semantics: รับประกันอะไรได้บ้างเมื่อ Remote Call ล้มเหลว

นี่คือหัวใจสำคัญที่สุดของบทนี้ — เมื่อ client เรียก remote method แล้วไม่ได้รับคำตอบ (เพราะ timeout) ระบบสามารถเลือก "รับประกัน" ได้ 3 ระดับ ซึ่งแต่ละระดับแลกความน่าเชื่อถือกับความซับซ้อนต่างกัน

Semanticsพฤติกรรมเมื่อไม่ได้รับ replyความเสี่ยง
Maybe (มาก็ได้ ไม่มาก็ได้)ไม่ retry เลย — เรียกครั้งเดียวแล้วจบRequest อาจไม่ถูกประมวลผลเลยก็ได้ ไม่มีการรับประกันใด ๆ — ใช้ได้เฉพาะกรณีที่ยอมรับความสูญเสียได้ เช่น ส่งค่า sensor ที่ไม่สำคัญมาก
At-least-onceRetry จนกว่าจะได้ reply — ถ้า request เดิมไปถึงและถูกประมวลผลไปแล้วจริง (แค่ reply หาย) การ retry จะทำให้ operation ถูกเรียกซ้ำOperation ที่ไม่ idempotent จะเกิดผลข้างเคียงซ้ำซ้อน (เช่น ตัดเงินสองครั้ง)
At-most-onceRetry ได้ แต่ระบบ dedupe ด้วย request ID เพื่อไม่ให้ operation ถูกประมวลผลซ้ำ แม้ retry หลายครั้งต้องการเก็บ state ฝั่ง server (ประวัติ request ID ที่เคยตอบแล้ว) — ซับซ้อนกว่าแต่ปลอดภัยกว่า
ข้อควรจำสำหรับสอบ
Invocation semantics ในบทนี้คือทฤษฎีที่อยู่เบื้องหลัง Idempotency Key และ Idempotent Consumer ที่สอนในวิชา Network Computing (หัวข้อที่ 3 และ 4) โดยตรง — "at-most-once" ในเชิงทฤษฎีคือสิ่งที่ Idempotency Key พยายาม implement ในเชิงปฏิบัติ การเก็บ mapping ของ request ID ที่เคยตอบแล้วในบทนั้น ก็คือกลไกเดียวกับที่ Coulouris อธิบายไว้ในบทนี้

6ทันสมัย: จาก IDL สู่ gRPC และ Protocol Buffers

แนวคิดเดิม (CORBA/RMI)แนวคิดสมัยใหม่ (gRPC)
IDL กำหนด interface ข้ามภาษา.proto file กำหนด service และ message ข้ามภาษา generate stub ให้อัตโนมัติ (คล้ายกันมากในเชิงหลักการ)
ORB จัดการการส่ง requestgRPC runtime ทำงานบน HTTP/2 รองรับ streaming ทั้งสองทิศทาง (bidirectional streaming) ซึ่ง CORBA/RMI ทำไม่ได้ในตัว
CDR / Java SerializationProtocol Buffers — binary, กระชับ, เร็ว
gRPC เทียบกับ REST — เมื่อไรควรใช้อะไร
REST (Network Computing หัวข้อที่ 2) เน้นความเรียบง่าย เข้ากับ web browser และ caching ได้ดี เหมาะกับ public API — gRPC เน้นประสิทธิภาพและ type-safety เหมาะกับการสื่อสารภายในระหว่าง microservices ที่ควบคุมทั้งสองฝั่งเอง (internal service-to-service call) ซึ่งเป็นบริบทเดียวกับที่ RPC ถูกออกแบบมาตั้งแต่ต้นในบทนี้
คำถามซ้อมสอบ
  1. อธิบายว่าทำไมการทำให้ remote call "ดูเหมือน" local call จึงเป็นทั้งข้อดีและข้อเสียในเวลาเดียวกัน
  2. อธิบายองค์ประกอบของ remote object reference และเหตุผลที่ต้องมี "เวลาที่ object ถูกสร้าง" รวมอยู่ด้วย
  3. เปรียบเทียบ maybe, at-least-once, และ at-most-once invocation semantics พร้อมยกตัวอย่าง operation ที่เหมาะกับแต่ละแบบ
  4. อธิบายว่า at-most-once semantics เกี่ยวข้องกับ Idempotency Key ในวิชา Network Computing อย่างไร
  5. เปรียบเทียบ gRPC กับ REST ว่าเหมาะกับสถานการณ์ใดต่างกันอย่างไร โดยอ้างอิงกับแนวคิด RPC/RMI ในบทนี้

7Local Call กับ Remote Call: หน้าตาคล้าย แต่กฎคนละชุด

แนวคิด RPC/RMI ประสบความสำเร็จเพราะทำให้โปรแกรมเมอร์เรียกความสามารถบนอีกเครื่องด้วย syntax คล้าย function หรือ method ปกติ Stub ช่วย serialize argument ส่ง request รอ response แล้วแปลงค่ากลับมา ความซับซ้อนจำนวนมากจึงหายไปจากโค้ดธุรกิจ

ปัญหาคือ syntax ที่คล้ายกันชวนให้เราเผลอเชื่อว่า semantics คล้ายกันด้วย Local call ใช้เวลาโดยทั่วไปสั้น มี memory/process เดียว และถ้า function throw exception ผู้เรียกรู้ว่าการควบคุมกลับมาแล้ว Remote call ข้าม scheduler, network, proxy, server queue และ dependency หลายจุด ผู้เรียกอาจไม่รู้ว่า request ไปถึงหรือ side effect เกิดแล้วหรือยัง

มิติLocal callRemote call
Latencyมักระดับ ns–µs และเสถียรกว่าµs–seconds มี tail และ network variation
Failureprocess เดียวกันมักล้มร่วมกันผู้เรียก ผู้รับ และทางเชื่อมล้มแยกกัน
Argumentsส่ง reference/pointer ได้ต้อง marshal by value หรือใช้ remote reference
Cancellationควบคุม stack เดียวกันได้มากกว่าผู้เรียกเลิกรอแต่ผู้รับอาจทำงานต่อ
Versioncaller/callee deploy พร้อมกันใน binary เดียวหลาย version อยู่พร้อมกันระหว่าง rollout
Securityอยู่ใน trust boundary เดียวกันบ่อยข้าม identity, network และ authorization boundary
Metaphor: กดสวิตช์กับโทรสั่งงาน
Local call คล้ายกดสวิตช์ไฟในห้อง เราเห็นผลแทบจะทันที Remote call คล้ายโทรให้คนอีกตึกเปิดไฟ ถ้าสายตัด เราไม่รู้ว่าเขาไม่ได้ยิน ได้ยินแล้วกำลังเดินไป หรือเปิดไฟแล้วแต่ยังไม่ทันบอก ความผิดพลาดสำคัญคือออกแบบการโทรให้ดูเหมือนสวิตช์จนคนใช้ลืมความไม่แน่นอนนี้

Fallacies of Distributed Computing

ความเชื่อผิดที่พบซ้ำ ได้แก่ network เชื่อถือได้ latency เป็นศูนย์ bandwidth ไม่จำกัด network ปลอดภัย topology ไม่เปลี่ยน มีผู้ดูแลคนเดียว transport cost เป็นศูนย์ และ network homogeneous แนวคิด remote transparency ทำให้ความเชื่อเหล่านี้แทรกในโค้ดได้ง่าย เพราะ call site ไม่แสดงว่ากำลังข้ามเครือข่าย

วิธีแก้ไม่ใช่เลิกใช้ RPC แต่ทำให้ boundary มองเห็นใน design: API client ต้องมี deadline, error model, retry policy และ telemetry; review ต้องนับ remote call ใน critical path; interface ควร coarse-grained พอไม่เกิด chatty traffic และ business operation ต้องนิยาม idempotency ก่อนเปิด auto-retry

8Proxy และ Skeleton ไม่ได้เป็นเพียงโค้ดประกอบ

Client proxy/stub ทำหน้าที่เป็นตัวแทน remote object: รับ method call, ตรวจ type, marshal argument, เติม metadata และส่งผ่าน communication module ฝั่ง server skeleton รับ request, unmarshal, dispatch ไป implementation แล้ว marshal result กลับ แนวคิดนี้ยังอยู่ใน generated gRPC client/server และ SDK ของ cloud service

แต่ proxy คือจุดที่ policy จำนวนมากถูกแทรกเข้ามา เช่น load balancing, retry, authentication token, tracing, circuit breaker และ caching ถ้าซ่อน policy เหล่านี้จนผู้เรียกไม่รู้ พฤติกรรม operation อาจเปลี่ยนโดยไม่เห็นจาก source code เช่นคำสั่งเขียนหนึ่งครั้งถูก library retry สามครั้ง

หน้าที่สิ่งที่ proxy ทำได้ความเสี่ยง
Serializationแปลง type เป็น wire formatschema/version และข้อมูลใหญ่เกิน
Discoveryแปล service name ไป instancecache address เก่าและ stale endpoint
Load balancingเลือก instance ต่อ request/connectionsession/state ผูก instance หรือ load ไม่เท่ากัน
Retryซ่อน transient failureduplicate side effect และ retry storm
Securityแนบ credential/ตรวจ TLStoken leakage และ trust boundary ไม่ชัด
Observabilityสร้าง span และ metric สม่ำเสมอเห็น proxy latency แต่ไม่เห็น business step ภายใน

Static proxy, Dynamic proxy และ Sidecar

Static/generated proxy ให้ type safety และ performance ดี แต่ต้อง regenerate เมื่อ contract เปลี่ยน Dynamic proxy ยืดหยุ่นและทำ cross-cutting concern ได้โดยไม่เขียนซ้ำ แต่ error อาจปรากฏตอน runtime ส่วน sidecar/service mesh ย้าย network policy ออกจาก process ไป proxy ข้าง service ทำให้หลายภาษาใช้ policy เดียวกันได้ แต่เพิ่ม hop, config และ failure mode ใหม่

ไม่มีตำแหน่งใด “โปร่งใสฟรี” ถ้า retry อยู่ใน library ทีม application ต้องรู้ ถ้า retry อยู่ใน sidecar ทีม platform ต้องสื่อสาร semantics ถ้าทั้งสองชั้น retry ซ้อนกัน จำนวน request ขยายแบบคูณ การรวม policy จึงเป็นปัญหาสถาปัตยกรรม ไม่ใช่ config ย่อย

9Remote Object Reference, Identity และ Lifetime

Reference ของ object ในเครื่องใช้ address หรือ runtime handle ที่มีความหมายภายใน process แต่ remote object reference ต้องระบุว่าติดต่อที่ไหน object ใด interface อะไร และ incarnation ใด เพราะ server restart แล้วสร้าง object ID เดิมขึ้นมาอาจไม่ใช่วัตถุเดียวกับก่อน crash

องค์ประกอบหน้าที่ปัญหาถ้าขาด
Endpoint/service locationพา request ไปหา runtimeไม่รู้เส้นทางหรือผูกตำแหน่งถาวรเกินไป
Object/service identifierเลือก instance เชิงตรรกะdispatch ผิด object
Interface/typeรู้ operation และ schemacaller เรียก method ที่ไม่รองรับ
Incarnation/epochแยก instance ก่อนและหลัง restartmessage เก่ากลับมากระทบ state ใหม่
Security capabilityผูกสิทธิ์กับ reference บางระบบเดา ID แล้วเข้าถึง object ได้

Location transparency มักแก้ด้วย name service หรือ registry ให้ reference เชิงตรรกะแปลเป็น endpoint ปัจจุบัน แต่ cache ทำให้การย้ายไม่เกิดพร้อมกัน client บางตัวอาจยังเรียกที่เก่า ต้องมี TTL, draining, redirect หรือ compatibility ช่วงเปลี่ยนผ่าน

Distributed Garbage Collection

ใน process เดียว runtime รู้ reference ทั้งหมดได้ค่อนข้างครบ แต่ remote reference อาจอยู่บน client ที่ล่มหรือ network partition การไม่เห็น heartbeat ไม่ได้พิสูจน์ว่า reference หาย จึงใช้ lease: client เช่าสิทธิ์ช่วงเวลาหนึ่งและต้อง renew ถ้าไม่ renew server จึง reclaim resource

Lease เปลี่ยนปัญหาความรู้ไม่ครบเป็นนโยบายเวลา แต่ต้องรับมือ clock skew, pause และ network delay ค่า lease สั้นคืน resource เร็วแต่เสี่ยงหมดอายุผิด ค่า lease ยาวปลอดภัยกว่าแต่ resource ค้างนาน อีกทางคือ explicit close/release ร่วมกับ lease เป็น safety net เพราะ finalizer อย่างเดียวไม่แน่นอน

10Invocation Semantics แบบลงลึก

Maybe, at-least-once และ at-most-once ไม่ได้บอกว่า operation “สำเร็จแน่” แต่บอกขอบเขตจำนวนครั้งที่ server อาจ execute ภายใต้ retry/deduplication Maybe อาจไม่ทำเลยหรือทำหนึ่งครั้ง At-least-once มุ่งให้ทำอย่างน้อยหนึ่งแต่ซ้ำได้ At-most-once ป้องกันซ้ำแต่ยังอาจไม่ทำเลย

Client        Network          Server
  |--- request #42 ----------->| execute + commit
  |        X <--- reply -------| reply lost
  |--- retry #42 ------------->| dedupe and return cached reply

At-most-once ต้องให้ server จำ request ID และผลเดิมในช่วงเวลาที่ retry อาจกลับมา ถ้าจำใน memory แล้ว server restart guarantee หาย ถ้าเขียน dedupe record แยกจาก business commit อาจ crash ตรงกลาง จึงควรทำ atomic ร่วมกันหรือมี unique constraint ที่ business state

Idempotent ไม่ได้แปลว่าเรียกซ้ำไม่มีต้นทุน

Operation idempotent ให้ผล state สุดท้ายเหมือนเรียกครั้งเดียว เช่น PUT ค่าเดิม แต่การเรียกซ้ำอาจยังเขียน audit log ส่ง notification หรือเสียค่าใช้จ่าย Idempotency ต้องกำหนดขอบเขตว่าครอบ side effect ใด และ key มี scope/expiry อย่างไร

Operationตามธรรมชาติทำให้ retry ปลอดภัยขึ้นอย่างไร
GET resourceมัก idempotent แต่ state อาจเปลี่ยนระหว่างครั้งระวัง hidden side effect และ cache semantics
PUT /orders/123กำหนดปลายทางเดิมconditional update/version ป้องกันเขียนทับ
POST /paymentsอาจสร้างใหม่ทุกครั้งidempotency key + atomic unique record
Increment counterไม่ idempotentใช้ operation ID dedupe หรือส่ง desired value/version

Retry Policy ต้องจำแนก Error

Unavailable, connection reset หรือ rate limit บางกรณีอาจ retry ได้ แต่ invalid argument, permission denied และ invariant conflict ไม่ดีขึ้นเพราะรอ การ retry write ต้องรู้ว่า server อาจ execute แล้ว ส่วน read อาจ retry ง่ายกว่าแต่ต้องคำนึง consistency และ deadline

ควรมี attempt limit, exponential backoff, jitter และ retry budget ระดับระบบ ไม่ใช่ทุกชั้นลองเอง หาก Gateway retry A และ A retry B อย่างละสามครั้ง request เดียวอาจสร้าง call ไป B เก้าครั้งก่อนนับ client retry

11Deadline, Cancellation และ Work ที่ไม่มีใครรอ

Timeout มักเป็นระยะเวลาต่อ call ส่วน deadline คือเวลาสิ้นสุดของงานทั้งหมด Deadline ส่งต่อได้และลดลงตามเวลาที่ใช้ไป ทำให้ downstream รู้ว่าเหลือเวลาเท่าใด การเริ่ม query ราคาแพงเมื่อเหลือ 5 ms มักไม่มีประโยชน์

Cancellation signal บอกว่า caller ไม่ต้องการผลแล้ว แต่ callee อาจผ่าน irreversible side effect ไปแล้ว จึงต้องออกแบบ cancellation point ถ้างานเป็น computation อ่านอย่างเดียวอาจหยุดได้ทันที ถ้าเป็นโอนเงินต้องให้ transaction จบอย่างสอดคล้อง แล้วตอบสถานะให้ตรวจภายหลังหรือทำ compensation

Metaphor: ยกเลิกพัสดุ
การโทรยกเลิกก่อนร้านแพ็กของต่างจากยกเลิกหลังรถออกไปแล้ว ปุ่ม Cancel เป็นการส่งคำขอใหม่ ไม่ใช่การลบเหตุการณ์ในอดีต API ที่ตอบเพียง “cancelled” โดยไม่บอกจุด commit อาจทำให้ผู้ใช้เข้าใจผิดว่าผลทั้งหมดถูกย้อนกลับแล้ว

Hedged Request

ระบบอ่านบางชนิดลด tail latency ด้วยการส่ง request สำรองไป replica อื่นเมื่อคำขอแรกช้ากว่าค่าหนึ่ง แล้วใช้คำตอบแรก วิธีนี้แลก latency กับ load ต้องใช้เฉพาะ operation อ่าน/idempotent ควบคุมอัตรา และยกเลิกสำเนาที่เหลือ มิฉะนั้นช่วง overload จะเพิ่มงานให้ระบบที่กำลังช้า

12Interface Design: จาก Method เล็ก ๆ ไปสู่ Business Capability

Distributed object แบบละเอียดชวนออกแบบ interface ตาม object ใน memory เช่น cart.getItems() แล้วเรียก item.getPrice() ทีละรายการ บนเครื่องเดียวอาจปกติ แต่ข้าม network กลายเป็น N+1 remote calls และ failure surface จำนวนมาก

Remote interface ควร coarse-grained ตามหน่วยงานที่มีความหมาย เช่น quoteCart(cartId) คืนข้อมูลที่ caller ต้องใช้ในรอบเดียว แต่ไม่ควรรวมใหญ่จน endpoint ทำทุกอย่างและเปลี่ยนตาม client ทุกตัว นี่คือสมดุลระหว่าง chatty กับ over-general API

คำถามออกแบบเหตุผล
Operation เป็น command หรือ queryช่วยกำหนด side effect, caching และ retry
ใครเป็น owner ของ invariantไม่กระจาย business rule ข้าม client
payload เป็น snapshot หรือ referenceกำหนด staleness และจำนวน call เพิ่ม
Error เป็น business rejection หรือ system failurecaller ตัดสินใจ retry/แก้ข้อมูลได้ถูก
Compatibility ช่วง rolloutclient/server อัปเกรดไม่พร้อมกัน

Error Model ที่ดี

Error ควรมี stable code, human message, retryability ที่ระวัง, correlation ID และรายละเอียดที่ไม่เปิดข้อมูลลับ อย่าให้ client parse ประโยค error เพื่อสร้าง logic และอย่าส่ง stack trace ภายในออกสู่ public boundary

Business error เช่นสินค้าหมดเป็นผลลัพธ์ที่ระบบเข้าใจ ไม่ควรปนกับ network unavailable ส่วน timeout เป็น ambiguity ไม่ใช่คำยืนยันว่า operation ไม่เกิด การแยกชนิดช่วยให้ caller เลือก compensate, retry, ask user หรือส่งงานตรวจภายหลังได้

13CORBA และ RMI: เรียนประวัติศาสตร์เพื่อเห็นขอบเขตของ Abstraction

CORBA ใช้ IDL และ ORB เพื่อข้ามภาษา/แพลตฟอร์ม ส่วน Java RMI ใช้ ecosystem ภาษาเดียว ทำ remote object และ serialization สะดวก ทั้งสองแสดงความพยายามสร้าง location/access transparency สูง แนวคิด stub, object reference, naming และ invocation semantics ส่งต่อมาถึง middleware สมัยใหม่

สิ่งที่เปลี่ยนคือระบบปัจจุบันมักลด distributed object ที่มี state ละเอียด แล้วใช้ stateless/coarse-grained service API มากขึ้น ใช้ schema ที่ชัดกว่า language object serialization และยอมเปิดเผย deadline/status/streaming แต่ gRPC ยังมี generated stub และ runtime เหมือนหลักเดิม ความใหม่จึงเป็นการเลือก trade-off ใหม่ ไม่ใช่การเกิดจากศูนย์

บทเรียนจากอดีตการประยุกต์ปัจจุบัน
IDL ทำ contract ข้ามภาษาProtobuf/OpenAPI และ code generation
ORB ให้ location transparencyservice discovery, client LB, mesh
Remote object referenceresource name, service endpoint, capability URL
Distributed GC ยากlease, TTL, explicit lifecycle
Fine-grained call แพงcoarse API, batching, streaming

14gRPC สมัยใหม่: ความสามารถใหม่ ปัญหาเดิม และปัญหาเพิ่ม

HTTP/2 multiplexing ลด connection overhead และรองรับ unary, server streaming, client streaming และ bidirectional streaming Protobuf ให้ schema/type และ payload กระชับ แต่ streaming ไม่ได้ลบ backpressure; multiplexing อาจมี connection-level issue; และ binary protocol ทำให้ inspect ด้วยตายากขึ้น

Deadline, status code, metadata และ interceptor ทำให้ concerns ที่ RPC เก่าซ่อนไว้บางส่วนถูกเปิดเผยดีขึ้น อย่างไรก็ตาม auto-generated client ยังทำให้ remote call ดูง่ายจนสร้าง synchronous chain ยาวได้ Observability ต้องเก็บ trace ข้าม hop และ API review ต้องนับ fan-out

REST กับ gRPC ไม่ใช่การแข่งขันแบบผู้ชนะคนเดียว

บริบทREST/HTTP JSONgRPC
Public/browser APIเข้าถึงง่าย tooling/caching กว้างbrowser ต้องมี bridge บางกรณี
Internal typed APIยืดหยุ่นและอ่านง่ายschema/codegen/streaming เด่น
Debugcurl และข้อความอ่านได้ใช้ grpcurl/reflection/tool เฉพาะ
Performanceเพียงพอสำหรับงานจำนวนมากpayload/connection มีประสิทธิภาพ แต่ต้องวัด end-to-end

15Workshop: ทำให้ Remote Call แสดงตัวตน

  1. สร้าง Client → Service A → Service B ด้วย REST หรือ gRPC ให้แต่ละ hop log trace/request ID และ deadline ที่เหลือ
  2. วัด baseline แล้วให้ B หน่วงแบบสุ่ม สร้าง latency distribution ไม่ใช่ค่าเดียว
  3. ตั้ง timeout ไม่สัมพันธ์กัน แล้วสังเกต work ที่ทำต่อหลัง client เลิกรอ จากนั้นแก้ด้วย deadline propagation
  4. ทำ B commit แล้วทิ้ง response ให้ A retry พิสูจน์ duplicate ก่อนเพิ่ม idempotency key
  5. เปิด retry ที่สองชั้นแล้วนับ amplification จากนั้นรวม policy ให้เหลือชั้นที่เหมาะ
  6. เปลี่ยน schema เพิ่ม optional field ให้ client เก่า/ใหม่ทำงานพร้อมกัน
  7. เปลี่ยน API fine-grained หลาย call เป็น coarse-grained call แล้วเปรียบเทียบ latency และ failure probability
สิ่งที่ต้องอธิบาย
อย่ารายงานเพียงว่ารุ่นใดเร็วกว่า ให้อธิบาย call graph, จำนวน remote call, semantics เมื่อ timeout, จุด commit, retry scope และหลักฐานว่า duplicate ถูกป้องกันที่ business effect ไม่ใช่แค่ที่ proxy

16สรุปและขั้นตอนถัดไป

Remote Invocation คือ abstraction ที่ทรงพลังเพราะเปลี่ยน message protocol ให้เป็น interface ที่ใช้ง่าย แต่ความสำเร็จด้าน syntax เป็นเหตุให้เราเผลอลืมความต่างด้าน semantics Remote call มี latency, partial failure, serialization, version และ security boundary ที่ local call ไม่มี

หลักที่ควรจำคือทำให้ interface ใช้ง่ายโดยไม่ทำให้ต้นทุนและความไม่แน่นอนหายจากการตัดสินใจ Stub ซ่อน byte ได้ แต่ design ต้องเปิด deadline, idempotency, error และ compatibility ให้ชัด Proxy ช่วย retry ได้ แต่ไม่ควร retry side effect โดยไม่รู้ และ cancellation ช่วยหยุดการรอแต่ย้อน business commit ไม่ได้

ขั้นตอนถัดไปคือ Transactions & Concurrency Control เมื่อ remote call หลายคำสั่งเข้าถึง state เดียวกันพร้อมกัน เราต้องนิยามว่าผลลัพธ์แบบใดถือว่าถูกต้อง แล้วใช้ locking, optimistic control, timestamp หรือ MVCC เพื่อรักษา invariant โดยแลกกับ waiting, abort และ coordination