การสร้างเวิร์กโฟลว์แบบทนทานใน Postgres
(dbos.dev)- เวิร์กโฟลว์แบบทนทาน (Durable) จะ checkpoint สถานะการทำงานลงฐานข้อมูล เพื่อให้กู้คืนต่อจากขั้นตอนล่าสุดที่เสร็จแล้วได้หลังเกิดการขัดข้อง
- การออร์เคสเตรตภายนอก แบบ Temporal, Airflow, AWS Step Functions เพิ่มทั้งตัว orchestrator กลางและที่เก็บข้อมูล ทำให้ความซับซ้อนสูงขึ้น
- สถาปัตยกรรมบน Postgres ให้แอปพลิเคชันเซิร์ฟเวอร์ polling ตารางเวิร์กโฟลว์และบันทึกผลลัพธ์ของแต่ละขั้นตอนโดยตรง เพื่อให้กู้คืนได้
- หลายเซิร์ฟเวอร์ป้องกันการรันซ้ำด้วย lock และ integrity constraint พร้อมรับมือปัญหาด้านการขยายระบบ ความพร้อมใช้งาน และการสังเกตการณ์ด้วยแนวทางของ Postgres
- Postgres เดียวสามารถขยายแนวตั้งเพื่อรองรับ เวิร์กโฟลว์หลายหมื่นรายการต่อวินาที และยังใช้ประโยชน์จากการทำ replication, multi-AZ และการวิเคราะห์ด้วย SQL ได้โดยตรง
โมเดลพื้นฐานของเวิร์กโฟลว์แบบทนทาน
- เวิร์กโฟลว์แบบทนทาน คือแนวทางที่บันทึกความคืบหน้าระหว่างการทำงานของโปรแกรมเป็น checkpoint ลงฐานข้อมูลเป็นระยะ เพื่อให้กู้คืนต่อจากขั้นตอนล่าสุดที่เสร็จสมบูรณ์ได้หลังการขัดข้องหรือความล้มเหลว
- สามารถมองได้เหมือนการเซฟและโหลดเกมวิดีโอ ที่คอย “บันทึก” ความคืบหน้าของโปรแกรมเป็นระยะ และ “โหลด” กลับจาก checkpoint ล่าสุดหลังเกิดการขัดข้อง
- การใช้งานที่พบได้ทั่วไปคือระบบ การออร์เคสเตรตภายนอก เช่น Temporal, Airflow และ AWS Step Functions
- ในการออร์เคสเตรตภายนอก โปรแกรมแบบทนทานจะถูกเขียนเป็นเวิร์กโฟลว์ที่ประกอบด้วยหลายขั้นตอน และมี orchestrator กลางคอยประสานการทำงาน
วิธีทำงานของการออร์เคสเตรตภายนอก
- เมื่อไคลเอนต์ส่งเวิร์กโฟลว์เข้ามา orchestrator จะสร้างเรคอร์ดในที่เก็บข้อมูล และ dispatch งานไปยัง worker เพื่อเริ่มรัน
- ทุกครั้งที่ worker ทำแต่ละขั้นตอนเสร็จ จะส่งผลลัพธ์กลับไปยัง orchestrator ซึ่งจะ checkpoint เอาต์พุตนั้นลงที่เก็บข้อมูล แล้ว dispatch ขั้นตอนถัดไป
- หาก worker ขัดข้องหรือล้มเหลว orchestrator จะ dispatch เวิร์กโฟลว์นั้นไปยัง worker ตัวอื่นอีกครั้ง และเริ่มต่อจากขั้นตอนล่าสุดที่ถูก checkpoint ไว้
- โครงสร้างนี้เพิ่ม เซิร์ฟเวอร์ orchestrator แยกต่างหาก เข้าไปบนแนวคิดหลักที่เก็บสถานะเวิร์กโฟลว์ในฐานข้อมูล ทำให้ระบบซับซ้อนขึ้น
สถาปัตยกรรมที่ใช้ Postgres เป็น orchestrator
- หากแก่นของเวิร์กโฟลว์แบบทนทานคือการ checkpoint สถานะโปรแกรมไว้ในฐานข้อมูล การใช้ ตัวฐานข้อมูลเอง เป็น orchestrator โดยไม่ต้องมีเซิร์ฟเวอร์แยกต่างหากจะเรียบง่ายและมีประสิทธิภาพมากกว่า
- Postgres เป็นตัวเลือกที่เหมาะกับการสร้างเวิร์กโฟลว์แบบทนทาน เพราะมีความนิยม ขยายระบบได้ และมี ecosystem ที่สมบูรณ์
- ในระบบเวิร์กโฟลว์แบบทนทานที่อิง Postgres แอปพลิเคชันเซิร์ฟเวอร์จะสื่อสารกับ Postgres โดยตรงเพื่อรันเวิร์กโฟลว์ โดยไม่ต้องผ่าน orchestrator กลาง
- ไคลเอนต์จะส่งงานรันโดยสร้างรายการในตารางเวิร์กโฟลว์ของ Postgres ส่วนแอปพลิเคชันเซิร์ฟเวอร์จะ polling ตารางนั้นเพื่อ dequeue และรันเวิร์กโฟลว์
- ระหว่างที่เซิร์ฟเวอร์รันเวิร์กโฟลว์ มันจะ checkpoint ผลลัพธ์ของแต่ละขั้นตอนไว้ใน Postgres และหากเซิร์ฟเวอร์ที่กำลังรันขัดข้องหรือทำงานล้มเหลว เซิร์ฟเวอร์ตัวอื่นจะกู้คืนเวิร์กโฟลว์จาก checkpoint ได้
เหตุผลที่ไม่จำเป็นต้องมี orchestrator กลาง
- แอปพลิเคชันเซิร์ฟเวอร์สามารถ ประสานกันผ่าน Postgres ได้ จึงไม่จำเป็นต้องมี orchestrator กลางมาคอย dispatch เวิร์กโฟลว์ไปยัง worker
- เซิร์ฟเวอร์สามารถช่วยกัน dequeue เวิร์กโฟลว์จากตารางใน Postgres และใช้กลไกอย่าง lock clause เพื่อให้มั่นใจว่าแต่ละเวิร์กโฟลว์จะถูก dequeue โดย worker เพียงตัวเดียวเท่านั้น
- การ checkpoint เอาต์พุตของแต่ละขั้นตอนก็ให้ worker เขียนลง Postgres โดยตรง แทนที่จะผ่าน orchestrator
- หากมี worker หลายตัวพยายามรันเวิร์กโฟลว์เดียวกันพร้อมกัน integrity constraint ของ Postgres จะตรวจจับงานซ้ำในจังหวะ checkpoint และทำให้ตัวที่ซ้ำต้องถอยออกไป
- เมื่อนำ Postgres หรือฐานข้อมูลอื่นมาแทน orchestrator กลาง ปัญหาอย่างการขยายระบบ ความพร้อมใช้งาน การสังเกตการณ์ และความปลอดภัย ก็สามารถจัดการได้ด้วยแนวทาง native ของ Postgres ที่เป็นที่รู้จักดี
การขยายระบบและความพร้อมใช้งาน
- ความสามารถในการขยายระบบและความพร้อมใช้งานของระบบเวิร์กโฟลว์แบบทนทานที่อิงฐานข้อมูลนั้น ถูกกำหนดโดย ฐานข้อมูลพื้นฐาน เป็นหลัก
- สามารถขยายแนวนอนได้ด้วยการเพิ่ม worker server ดังนั้นขีดความสามารถสูงสุดจึงขึ้นอยู่กับว่าฐานข้อมูลประมวลผลเวิร์กโฟลว์ได้เร็วเพียงใด
- เพราะ worker สามารถทดแทนกันได้และกู้คืนสถานะให้กันและกันได้ ระบบจึงยังคงพร้อมใช้งานตราบใดที่ฐานข้อมูลพื้นฐานยังใช้งานได้
- เมื่อใช้ Postgres ข้อดีคือเรื่องการขยายระบบและความพร้อมใช้งานเป็นปัญหาที่ถูกศึกษาอย่างยาวนาน และมีวิธีแก้ที่แข็งแรงอยู่แล้ว
- Postgres เซิร์ฟเวอร์เดียวสามารถขยายแนวตั้งเพื่อรองรับ เวิร์กโฟลว์หลายหมื่นรายการต่อวินาที
- การขยายเพิ่มเติมสามารถทำได้ด้วย distributed Postgres เช่น CockroachDB หรือ Postgres แบบ sharding
- Postgres รองรับ streaming replication ที่มี automatic failover และบริการแบบ managed ก็มักให้ multi-AZ deployment พร้อม SLA ด้าน high availability มาเป็นพื้นฐาน
- งานวิศวกรรมและงานวิจัยที่สั่งสมมาหลายทศวรรษเพื่อการรัน Postgres ในระดับใหญ่ ยังสามารถนำมาใช้กับการดำเนินงานของเวิร์กโฟลว์แบบทนทานได้โดยตรง
การสังเกตการณ์
- ในการรันแบบทนทานที่อิง Postgres เวิร์กโฟลว์และขั้นตอนต่าง ๆ จะถูก checkpoint ลงใน ตารางของ Postgres ดังนั้นเราจึงสแกน checkpoint เหล่านี้เพื่อติดตามเวิร์กโฟลว์แบบเรียลไทม์และแสดงภาพการรันได้
- Postgres มีจุดแข็งตรงที่แทบทุกคำถามด้านการสังเกตการณ์ของเวิร์กโฟลว์สามารถเขียนเป็น SQL ได้
- งานอย่างการค้นหาเวิร์กโฟลว์ทั้งหมดที่เกิดข้อผิดพลาดในช่วงหนึ่งเดือนที่ผ่านมา สามารถแสดงเป็นการกรองและวิเคราะห์ที่ซับซ้อนแบบ declarative ด้วย SQL
- สิ่งนี้เป็นไปได้เพราะอาศัย relational model ของ Postgres และงานวิจัยด้าน query optimization ที่สั่งสมมาหลายทศวรรษ
- ระบบจำนวนมากที่ใช้ data model ที่ง่ายกว่า เช่น key-value store ที่ orchestrator ภายนอกยอดนิยมใช้งานอยู่ ไม่สามารถรองรับการวิเคราะห์ด้วย SQL ในระดับเดียวกันได้
- หากเก็บข้อมูลเวิร์กโฟลว์และขั้นตอนไว้ในตาราง Postgres และเพิ่ม secondary index สำหรับคำสั่งวิเคราะห์ที่ต้องการความเร็ว ก็จะได้ การสังเกตการณ์ ที่มีประสิทธิภาพสำหรับการรันแบบทนทาน
ความน่าเชื่อถือและความปลอดภัย
- ในการรันแบบทนทานที่ใช้งาน orchestrator ภายนอก ทั้ง orchestrator และที่เก็บข้อมูลของมันต่างเป็น จุดล้มเหลวเพียงจุดเดียว
- เนื่องจาก orchestrator และที่เก็บข้อมูลทำหน้าที่ประสานการรันเวิร์กโฟลว์โดยตรง หากตัวใดตัวหนึ่งล่ม แอปพลิเคชันทั้งหมดก็จะใช้งานไม่ได้
- เพราะทั้งสองระบบจัดการและเก็บ checkpoint ของเวิร์กโฟลว์และขั้นตอนต่าง ๆ จึงอาจเข้าถึงข้อมูลแอปพลิเคชันที่มีความอ่อนไหว และต้องมีการ hardening การควบคุมการเข้าถึง และการตรวจสอบ audit เช่นเดียวกับโครงสร้างพื้นฐานสำคัญ
- ในการรันแบบทนทานที่อิง Postgres จุดล้มเหลวเพียงจุดเดียวคือ Postgres เอง และข้อมูลเวิร์กโฟลว์ทั้งหมดถูกเก็บลง Postgres โดยตรงโดยไม่ต้องผ่านระบบอื่น
- หากแอปพลิเคชันของคุณพึ่งพา Postgres อยู่แล้ว การเพิ่มการรันแบบทนทานจะไม่สร้างจุดล้มเหลวใหม่ และไม่เพิ่มพื้นผิวการโจมตีใหม่ที่ต้องปกป้อง
- ฐานข้อมูลเป็นโครงสร้างพื้นฐานหลักอยู่แล้ว ดังนั้นแทนที่จะเพิ่มโครงสร้างพื้นฐานหลักใหม่เพื่อการ orchestration การนำฐานข้อมูลเดิมกลับมาใช้จึงสมเหตุสมผลกว่า
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
absurdของ Armin Ronacher เป็น implementation ของ durable workflow สำหรับ Postgreshttps://lucumr.pocoo.org/2025/11/3/absurd-workflows/
https://github.com/earendil-works/absurd
https://earendil-works.github.io/absurd/
ยังไม่ได้ลองใช้เอง แต่ก็น่าจะคุ้มที่จะเอาไปเทียบกับตัวเลือกอื่น
absurdกับdurableที่เป็น implementation สาย Rust เป็นตัวเลือกที่ดีสำหรับการทำให้ ฝั่ง client เรียบง่ายมากมันเบาพอที่ coding agent จะเก็บโครงสร้างทั้งหมดไว้ในหัวได้ง่าย และถ้าจำเป็นก็แค่ query ดูสถานะได้
ผมใช้ dbos.dev, restate.dev และ cf workflows อยู่ และใน Agents.md ของเราก็เขียนไว้แบบนี้
Restate.dev ใช้กับการเชื่อมต่อระบบชำระเงินของ northflank เร็วกว่า cf workflows, ไม่ขึ้นกับ Cloudflare และปัญหาล่มของมัน, และ self-host ได้จึงไม่ติด vendor lock-in
Cloudflare workflows ใช้กับงานที่ความสำคัญต่ำกว่า เช่น การสร้างรายงาน CSV/PDF เพราะราคาถูกมาก
DBOS.dev ใช้กับ workflow ที่ต้องการ atomic messaging ซึ่งผูกกับ Postgres transaction และต้องการความน่าเชื่อถือ/ความทนทาน 100% เช่น การเติม materialized row หรือการส่งอีเมล/พุชที่สำคัญถึงร้านค้า
DBOS กับ Restate ภายนอกดูคล้ายกัน แต่ Restate ต้องมี “orchestrator” กลาง จึงมีทั้งข้อดีข้อเสีย และทำให้สร้างร่วมกับเซิร์ฟเวอร์เลส worker ของ cf/vercel ได้ง่ายขึ้น
อีกทั้งยังมี VirtualObject จึงเป็นโอเพนซอร์สทางเลือกที่ดีแบบไม่ติด vendor lock-in สำหรับ DurableObject แบบ single-thread ของ Cloudflare
จุดที่ DBOS เด่นเป็นพิเศษมีสองอย่าง 1) สามารถทำ atomic messaging ได้ภายใน DB transaction เดียวกับ business logic ผ่าน
dbos.enqueue_workflowส่วนนี้มักเป็นจุดเปราะบางที่สุดของหลายวิธีแก้ปัญหา ดังนั้นถ้าจัดการให้เป็นแบบ atomic และ durable ได้ใน transaction เดียวกับที่รัน business logic ก็จะลดความซับซ้อนได้มาก2) DBOS เก็บสถานะ workflow ไว้ใน DB จึงน่าจะทำ observability dashboard ด้วย metabase/looker ได้ง่าย Restate ก็น่าจะดีถ้าสามารถเปิดเผยอินสแตนซ์ rocksdb เพื่อเชื่อมกับ metabase ได้เช่นกัน
อยากรู้ความเห็นจากคนที่เคยใช้ DBOS และ Temporal
เมื่อก่อนเคยใช้ Temporal และมันทำงานได้ค่อนข้างดี แต่เคยรู้สึกไม่สะดวกตอนออกแบบวิธีแก้เพราะข้อจำกัดเรื่องขนาดของ request payload หรือ event
มันมีข้อดีตรงที่บังคับให้ทำตาม แนวปฏิบัติทางวิศวกรรมที่ดี แต่ก็ไม่อยากต้องเขียนลอจิกพิเศษตลอดเวลาแบบว่า ถ้าไฟล์ CSV ใหญ่กว่า 2MB ก็อัปขึ้น S3 ส่งลิงก์ต่อ แล้วค่อยดาวน์โหลดกลับมาอีกครั้งใน workflow
เลยอยากรู้ว่าประสบการณ์กับ DBOS เป็นอย่างไร และถ้าเทียบกับ Temporal ในแง่ความซับซ้อนของการดูแลระบบหรือความเท่าเทียมของฟีเจอร์แล้วเป็นอย่างไรบ้าง
ที่บ้านก็เอามาใช้จัดการงาน home automation ที่ไม่ได้ไวต่อเวลามากนักด้วย latency ของ workflow ไม่ได้แย่มาก แต่คงไม่ใช้กับ trigger ที่ต้องตอบสนองทันที เช่น event ตรวจจับการเคลื่อนไหวในบ้าน ส่วน timeout ประเภทปิดอะไรบางอย่างหลังไม่มีความเคลื่อนไหวถือว่าโอเค
ผมค่อนข้างชอบวิธีวาง REST API แบบบาง ๆ ไว้หน้า Temporal ภายใน VPC หรือ Kubernetes cluster เพราะจะได้ไม่ต้องให้ trigger แบบ event-based มาสนใจเรื่องการยืนยันตัวตนของ Temporal หรือการตรวจสถานะ workflow และยังช่วยให้ event เรียบง่ายที่สุดโดยแทบไม่มีลอจิก
ตัวอย่างเช่น ให้ DB trigger ทำงานโดยตรงหรือใส่ event ลงคิว แล้ว handler ไปเรียก REST API แบบบาง ๆ พร้อมรายละเอียด event ที่จำเป็น REST API จะเป็นตัวตัดสินใจว่าอันนี้ควรเริ่ม workflow, ส่ง signal ไปยัง workflow เดิม หรือเพิกเฉย รูปแบบขึ้นอยู่กับกรณี แต่สำหรับผมมักใช้
SignalWithStartบ่อย หรือถ้าไม่คุ้มจะเริ่มและก็ไม่มี workflow เดิมอยู่แล้ว ก็ทิ้งไปเลยอีกอย่าง ฟีเจอร์ parent/child workflow มีประโยชน์มากเวลาต้อง orchestrate พฤติกรรมที่เป็นอิสระต่อกันภายในวงจรชีวิตของอ็อบเจ็กต์เดียว และชอบตรงที่ยกเลิกได้เมื่อปัจจัยภายนอกทำให้เส้นทางการดำเนินของอ็อบเจ็กต์เปลี่ยนไป
ถ้าจะพูดแบบยาว ๆ และกำกวมหน่อย มันทรงพลังมาก ใช้งานง่ายมาก และช่วยได้มากในการย้ายลอจิกวงจรชีวิตออกไปจาก API ถ้าเก็บไว้ใน API หนี้ทางเทคนิคจะสะสมง่ายและการดูแลจะเริ่มเปราะบาง เห็นด้วยว่าการที่มันบังคับให้ทำตามแนวปฏิบัติที่ดี ดีกว่าปล่อยให้โยนลอจิกไว้ในที่ที่ดูง่ายแต่สุดท้ายกลายเป็นกับดักที่ซ่อนอยู่
แต่พอลองใช้ผลิตภัณฑ์ Cloud ก็ช็อกราคา ใช้เครดิตฟรี 1,000 ดอลลาร์หมดไปก่อนจะขึ้น production เสียอีก และก็ไม่อยากรัน Temporal แบบ local เองด้วย
ส่วนตัวคิดว่าทางที่ดีที่สุดคือเอาแค่แนวคิดจากสถาปัตยกรรมของมันมา แล้วไปทำเองบน Postgres
ยังไม่ถึงกับชอบ 100% มันให้ความรู้สึกเหมือนของที่เติมเข้ามาทีหลังมากกว่าจะเป็นส่วนแกน และยังเป็น release ระยะแรก ๆ อยู่ แต่ตอนนี้ก็ถือว่าแก้ปัญหาได้แล้วในทางปฏิบัติ
จากประสบการณ์ที่รันใน production มานานกว่าหนึ่งปี Temporal ออกแบบมาไม่ดี ช้า และหนักฝั่งโครงสร้างพื้นฐานแบบเกินเหตุ
ถ้าเป็นงานที่ไม่เล็กน้อย เช่น มี event มากกว่า 200 รายการต่อ workflow และรันพร้อมกันวันละหลายร้อยตัวตลอดทั้งวัน คุณอาจเผางบโครงสร้างพื้นฐานไปหลายล้านดอลลาร์ และมันก็ยังไม่ดีอยู่ดี
ถ้ารัน benchmark เอง ตัวเลขออกมาแย่มาก
ทีมขายก็แย่มากจริง ๆ และดูสิ้นหวังมาก
แต่ในมุมของนักพัฒนา SDK ค่อนข้างดี
อย่าไปติดอยู่กับ nexus และถ้าทีมขายโทรมา ควรให้ฝ่ายกฎหมายเข้าห้องมาด้วยเสมอ
ใช้เวลาสักพักกว่าจะเข้าใจว่าจะ migrate มาจาก Celery อย่างไร แต่สำหรับกรณีของเรา มันคุ้มค่า
Conductor OSS ก็ทำเรื่องนี้ได้ค่อนข้างดี https://docs.conductor-oss.org/devguide/ai/index.html
https://github.com/agentspan-ai/agentspan โดยพื้นฐานแล้วคือ ชั้น Agent SDK สำหรับ Conductor ที่สามารถทำให้เอเจนต์ของ langgraph, OpenAI, vercel และ ADK มี durability และเพิ่ม orchestration ได้โดยไม่ต้องแก้โค้ด
แทนที่จะแยก data store, state machine, ข้อจำกัดของสถานะที่ถูกต้อง และลอจิกที่เปลี่ยนผ่านระหว่างสถานะที่ถูกต้องออกจากกัน ถ้าสามารถรวมสิ่งเหล่านี้เข้าเป็นเคอร์เนลบางอย่างของสถานะแอปได้ก็คงดี
พูดตรง ๆ คือ Postgres มีความสามารถแบบนี้อยู่แล้วเยอะมาก แต่ในระดับแอปหรือผลิตภัณฑ์ ผมยังไม่เห็นภาพที่ชัดเจนว่าจะแสดงชุดสถานะที่พิสูจน์ได้ว่าแอปสามารถเปลี่ยนผ่านไปถึงได้อย่างไร และเปิดเผยสิ่งนั้นให้ฝั่งไคลเอนต์แบบที่มีประโยชน์โดยอัตโนมัติ เช่น ผู้ใช้คนนี้กดไลก์โพสต์นี้ได้ แต่แก้ไขไม่ได้
สำหรับผมมันดูคล้าย colored Petri net แต่ยังไม่เห็นกระบวนทัศน์สถานะแอปที่เรียบง่ายเหมือนที่ฐานข้อมูลมีขอบเขตความสำเร็จที่ชัดเจน
แต่ก็ไม่แน่ใจว่ามันเป็นการบูรณาการแบบสมบูรณ์หรือไม่
เพราะ DBOS ไม่รองรับ Rust เลยไปทำเวอร์ชัน Rust ที่เล็กมากแต่คล้ายกันไว้ที่ https://github.com/tensorzero/durable
มันค่อนข้างเสถียรและขยายได้ดี แต่แน่นอนว่าต้องระวังมากกับ การ implement SQL หวังว่าคนอ่านที่นี่จะสนใจ
https://flawless.dev/
เข้าใจแนวคิดนี้อย่างถ่องแท้และเห็นด้วยทั้งหมด นี่เป็นวิธีที่ยอดเยี่ยมในการใส่ความทนทานลักษณะนี้เข้าไปในระบบเวิร์กโฟลว์
แต่ด้วยสมองแบบเกมเมอร์ ฉันก็อยากเรียกสิ่งนี้ว่า “save scumming ขนาดใหญ่” อยู่ดี หลายคนน่าจะรู้อยู่แล้วว่าแนวทางนี้ใช้ได้ผล แต่อาจยังไม่ได้เชื่อมมันเข้ากับแนวคิดวิทยาการคอมพิวเตอร์เชิงนามธรรม
อีกกลยุทธ์หนึ่งในการเพิ่มความแข็งแกร่งคือประกอบเวิร์กโฟลว์จากการทำงานแบบ idempotent ซึ่งอาจมีประโยชน์ในกรณีที่สถานะของเวิร์กโฟลว์ใหญ่เกินกว่าจะสำรองข้อมูลได้ง่าย แทนที่จะต้องแบ็กอัป ก็แค่รันงานใหม่ตั้งแต่ต้น และทุกอย่างก่อนถึงจุดที่กลับมามีความคืบหน้าอีกครั้งก็จะกลายเป็น no-op ทั้งหมด
น่าทึ่งอยู่เรื่อยว่าถ้ามี Postgres อยู่ในกล่องเครื่องมือ เราจะทำอะไรได้มากแค่ไหนด้วยเครื่องมือเพียงไม่กี่ชิ้น
ไม่นานมานี้ฉันพัฒนา distributed queue ขึ้นมา มันทำงานได้ดีมาก ผลเบนช์มาร์กก็ดี และไม่มี race condition หรือการชนกันเลย โดยใช้
SKIP LOCKEDเพื่อให้ worker หลายตัวแย่งงานกันได้อย่างปลอดภัยถ้าต้องการให้ worker ข้ามหลายโหนดหลีกเลี่ยงการชนกัน ก็ยังใช้ mutex ระดับเซสชัน หรือ
pg advisory lockได้ด้วยSELECT FOR UPDATEไว้เยอะ ๆ มันจะสเกลได้ไม่ดีแก้ไข: พอกลับไปตรวจอีกที ดูเหมือนว่าตอนนี้คำแนะนำจะเปลี่ยนเป็นตรงกันข้ามแล้ว
ใน Rails มี backend สำหรับงานที่อิงฐานข้อมูลอยู่หลายตัว แต่ตามธรรมเนียมแล้วงานหนึ่งงานควรทำแค่เรื่องเดียวและถ้าเป็นไปได้ก็ควรจบให้เร็วมาก
เพราะแบบนี้การสร้างเวิร์กโฟลว์เลยค่อนข้างฝืน ๆ กลายเป็นว่าบรรทัดสุดท้ายของงานแรกคือต้องคิวงานที่สอง แล้วบรรทัดสุดท้ายของงานที่สองก็คิวงานที่สามต่อ
backend งานไม่ได้แสดงสิ่งเหล่านี้เป็นเวิร์กโฟลว์ที่เชื่อมต่อกัน แต่ปฏิบัติกับมันเป็นงานอิสระ และถ้าจะพยายามทำความเข้าใจเวิร์กโฟลว์ในภาพรวมระดับสูง ก็ต้องไปอ่านหลายคลาสของงาน
ช่วงหลัง Rails เพิ่งเพิ่มแนวคิด continuable เข้ามา ซึ่งให้ทำ checkpoint เป็นขั้น ๆ ภายในงานและกลับมาทำต่อได้ แต่ธรรมเนียมเรื่องการทำให้งานมีความรับผิดชอบเดียวก็ยังแรงอยู่ ทำให้มันยังดูแปลก ๆ หากจะใช้กับเวิร์กโฟลว์จริง
อยากรู้ว่าคนอื่นเคยเจอปัญหาแบบนี้ไหม และหาวิธีแก้กันอย่างไร
นี่เป็นแพตเทิร์นที่ยอดเยี่ยม ควรทำสิ่งต่าง ๆ ให้มากที่สุดเท่าที่ทำได้ภายในฐานข้อมูล
Spanner ภายนอกมี change streams ให้ใช้ ส่วน Spanner ภายในนั้นต่างออกไป สาเหตุหลักคือบางกรณีมีความต้องการด้านการสเกลที่รุนแรงมาก รวมถึงมีทั้งเหตุผลแบบ “ของเดิมมันก็ทำงานได้ดีอยู่แล้ว” และ “change stream ตามอำเภอใจมันน่ากลัว” ปะปนกันไป
Spanner ภายในเปิดให้ทรานแซกชันใด ๆ ก็ได้เขียน queue entry ลงไป โดย queue ในที่นี้ก็คือ table ชนิดพิเศษที่รับรู้เรื่องเวลาแบบคร่าว ๆ คุณสามารถตั้งเวลาส่งล่วงหน้าได้ และ entry จะถูก push จาก queue ไปยัง handler โดยที่ handler เองก็สามารถเขียน DB ภายในทรานแซกชัน dequeue ได้ด้วย และยังคงได้ความสามารถในการสเกลทั้งหมดเหมือนเดิม