ODOO 19 · POSTGRESQL 16 · DIGITALOCEAN SGP1
เราอยากรู้ว่า Odoo 19 บนเครื่องขนาดเล็กรับงานได้จริงแค่ไหน จึงตั้งเครื่องทดสอบขึ้นมาเจ็ดรอบ แล้ววัดด้วยผู้ใช้เสมือนจนกว่าจะเจอขีดจำกัด ผลที่ได้ไม่ตรงกับที่เราเดาไว้ตั้งแต่ต้น ตัวที่ตันไม่ใช่ CPU และวิธีแก้ที่เราคิดว่าจะได้ผลกลับทำให้แย่ลง บันทึกนี้เล่าว่าเราวัดอะไร เจออะไร และยังไม่รู้อะไรบ้าง
เราตั้งเครื่องจริงบน DigitalOcean ใส่ข้อมูลจำลอง 20,304 ใบสั่งขาย แล้วปล่อย ผู้ใช้เสมือน (Virtual User หรือ VU) เข้าไปใช้งานพร้อมกัน เพิ่มทีละขั้นตั้งแต่ 10 VU จนถึง 60 VU โดยให้แต่ละ VU เว้นจังหวะกดแบบคนจริง ทั้งเปิดดู แก้ไข และสั่งพิมพ์รายงาน
ตั้งแต่ 10 คนถึง 60 คน เวลาตอบสนองเพิ่มจาก 37 เป็น 81 มิลลิวินาที ซึ่งยังเร็วกว่าเกณฑ์อ้างอิง 1 วินาที อยู่ราวสิบสองเท่า และ CPU เพิ่มขึ้นในสัดส่วนที่คงที่ ระดับที่ทดสอบสูงกว่าค่าประมาณการใช้งานราวหกเท่า และยังใช้ CPU ประมาณหนึ่งในสี่
ระดับที่ทดสอบสูงกว่าที่ระบบขนาดนี้มักเจอในงานจริงพอสมควร โดยทั่วไปจำนวนผู้ใช้ที่กดพร้อมกันจริง มักอยู่ราว 10 ถึง 25 เปอร์เซ็นต์ของบัญชีทั้งหมด ระบบที่มีผู้ใช้ราวสามสิบถึงสี่สิบบัญชีจึงมักมีผู้ใช้พร้อมกันไม่ถึง 10 VU ทั้งนี้ตัวเลขนี้ควรยืนยันจากการใช้งานจริงก่อนสรุปขนาดเครื่องเสมอ
ผลทั้งหมดมาจากการทดสอบด้วยผู้ใช้เสมือนและข้อมูลจำลอง ไม่ใช่จากการใช้งานจริง ข้อจำกัดสองข้อที่มีผลต่อการตีความมากที่สุดคือ ข้อมูลที่ใช้ทดสอบมีประมาณ 8.5 เปอร์เซ็นต์ ของปริมาณเป้าหมาย และยังไม่มีตัวเลขผู้ใช้พร้อมกันจากการใช้งานจริงมายืนยัน รายละเอียดทั้งหมดอยู่ในหัวข้อที่ 07
เราเพิ่มจำนวนผู้ใช้ต่อจนพบข้อจำกัด เพื่อระบุช่วงที่ประสิทธิภาพเริ่มลดลง ผลพบการเปลี่ยนแปลงชัดเจนระหว่าง 60 ถึง 80 คน โดยข้อจำกัดหลักไม่ได้อยู่ที่ CPU หรือหน่วยความจำ แต่อยู่ที่การรอแก้ไขข้อมูลสต็อกพร้อมกัน
| ตอนที่ประสิทธิภาพลดลง | ค่าที่วัดได้ | แปลว่า |
|---|---|---|
| CPU | 59% | ยังเหลือว่างอีกสี่สิบเปอร์เซ็นต์ ไม่ใช่ตัวที่ตัน |
| ฐานข้อมูล | 17 จาก 18 การเชื่อมต่อ | การเชื่อมต่อส่วนใหญ่อยู่ในสถานะรอ ไม่พบว่าฐานข้อมูลประมวลผลเต็ม |
| หน่วยความจำ | 150-170 MB | ต่อหนึ่ง worker จากเพดาน 352 MB ไม่ใกล้เต็ม |
| ยืนยันใบสั่งขาย | 11 วินาที | จากเดิม 1.5 วินาที และกินคิวงานจนหมด |
| การรอล็อกที่ stock_quant | 20-32 ครั้งต่อนาที | นี่คือข้อจำกัดที่พบจริง |
สาเหตุอยู่ที่การจองสต็อก เมื่อยืนยันใบสั่งขาย Odoo ต้องควบคุมข้อมูลใน stock_quant เพื่อไม่ให้หลายรายการตัดสต็อกชุดเดียวกันพร้อมกัน เมื่อมีการยืนยันพร้อมกันมากขึ้น งานจึงต้องรอแก้ไขข้อมูลชุดเดียวกัน และทำให้รายการอื่นช้าตามไปด้วย
เมื่อเกิดการรอล็อก Odoo จะลองทำรายการใหม่ให้เอง ผู้ใช้จึงไม่เห็นข้อความผิดพลาด และระบบเฝ้าระวังที่ตรวจเฉพาะคำขอที่ล้มเหลวจะไม่แจ้งเตือน ตลอดการทดสอบอัตราคำขอที่ล้มเหลว อยู่ที่ 0.00 เปอร์เซ็นต์ ทั้งที่เวลาตอบสนองเพิ่มขึ้นราวยี่สิบเท่า อาการเดียวที่สังเกตได้คือระบบช้าลง ระบบเฝ้าระวังจึงต้องวัดเวลาตอบสนอง ไม่ใช่ดูเพียงว่าระบบยังตอบอยู่หรือไม่
เมื่อระบบเริ่มช้า ทางเลือกหนึ่งคือเพิ่ม worker เพื่อรับงานพร้อมกันได้มากขึ้น เราจึงทดสอบบนเครื่องเดิมและข้อมูลชุดเดิม โดยเพิ่ม worker จาก 9 เป็น 16 ผลออกมาตรงข้ามกับที่คาด
| ที่ผู้ใช้ 60 คน | 9 workers | 16 workers |
|---|---|---|
| เวลายืนยันใบสั่งขาย | 1,900 ms | 2,700 ms |
| การรอล็อกต่อนาที | ~10 | ~25 |
| งานที่ทำได้ต่อวินาที | 41.9 | 41.4 |
| CPU | 23.1% | 29.0% |
การเพิ่ม worker ทำให้หลายงานพยายามแก้ไขข้อมูลสต็อกพร้อมกันมากขึ้น การรอล็อกจึงถี่ขึ้นสองเท่าครึ่ง ขณะที่ปริมาณงานที่ทำได้ต่อวินาทีแทบไม่เปลี่ยน ผลทดสอบชี้ว่าข้อจำกัดอยู่ที่วิธีที่ Odoo จัดลำดับการเขียนข้อมูล มากกว่าจำนวน core จึงยังไม่มีหลักฐานว่าการเพิ่ม core เพียงอย่างเดียวจะแก้ข้อจำกัดนี้ได้
ข้อนี้ยังหักล้างการคาดการณ์ของเราเองด้วย ก่อนหน้านี้เราคำนวณว่า 16 workers น่าจะดันเพดานไปถึง 130 ถึง 180 คน การคำนวณนั้นตั้งอยู่บนสมมติฐานว่า CPU คือตัวที่ตัน ซึ่งผิด
การเทียบครั้งนั้นยังมีช่องโหว่อยู่ข้อหนึ่ง คือมีแต่ผลของ 16 workers เท่านั้น ที่ถูกดันเกิน 60 คน จึงไม่มีผลของ 9 workers ที่ระดับเดียวกันมาเทียบ เราจึงย้อนกลับไปทดสอบ บนเครื่องเดิม ด้วยรูปแบบการเพิ่มผู้ใช้แบบเดียวกัน
| จุดที่เริ่มมีปัญหา | 16 workers | 9 workers |
|---|---|---|
| ขั้นสูงสุดที่ผ่านได้ | 60 คน | 80 คน |
| เริ่มมีปัญหาที่ | 80 คน | 80-100 คน |
| CPU ตอนที่มีปัญหา | 59% | 29% |
เครื่องที่มี worker น้อยกว่ากลับรับผู้ใช้ได้มากกว่า และใช้ CPU เพียงครึ่งเดียว worker ที่เพิ่มเข้ามาแต่ละตัวคืองานอีกหนึ่งงานที่พยายามแก้ไขข้อมูลสต็อกชุดเดียวกันในจังหวะเดียวกัน สำหรับงานลักษณะนี้ worker จึงไม่ใช่กำลังการผลิต แต่เป็นการแย่งคิวกันเอง
การทดสอบชุดแรกใช้ตัวเชื่อมที่สร้างใบสั่งขายแล้วหยุด ไม่เคยยืนยันใบสั่ง จึงไม่เคยสร้างใบส่งของและไม่เคยแตะการจองสต็อก ซึ่งเป็นเส้นทางเดียวกับที่ทำให้ระบบช้าลงในหัวข้อที่ 02 แต่ตัวเชื่อมที่ใช้งานจริงยืนยันใบสั่ง เราจึงแก้ให้ทำงานเต็มกระบวนการแล้วทดสอบใหม่
| ตัวเชื่อมทำงานลำพัง | สร้างอย่างเดียว | สร้างและยืนยัน |
|---|---|---|
| CPU ที่ใช้ | 4.3% | 11.4% |
| เวลาเขียน (p95) | 180 ms | 410 ms |
| ใบสั่งขายต่อนาที | 200 | 196 |
| การรอล็อก stock_quant ใน 20 นาที | ไม่พบ | 8 ครั้ง |
ต้นทุนจริงของตัวเชื่อมสูงกว่าที่วัดไว้เดิม 2.7 เท่า ที่สำคัญกว่าตัวเลข CPU คือการรอล็อก แม้ไม่มีผู้ใช้คนอื่นอยู่บนเครื่องเลย ตัวเชื่อมก็เดินไปถึงการจองสต็อกได้ด้วยตัวเอง ซึ่งการทดสอบชุดเดิมไปไม่ถึง เพราะเราออกแบบให้ไม่ยืนยันใบสั่ง
จากนั้นเราใส่ผู้ใช้กลับเข้าไปพร้อมกับตัวเชื่อม ด้วยรูปแบบเดียวกับการทดสอบเดิม ต่างกันเพียงตัวเชื่อมที่ทำงานจนจบกระบวนการ
| ที่ผู้ใช้ 60 คน + ตัวเชื่อม | สร้างอย่างเดียว | สร้างและยืนยัน |
|---|---|---|
| เวลาตอบสนอง (p95) | 350 ms | 830 ms |
| งานที่ทำได้ต่อวินาที | 41.2 | 34.7 |
| ใบสั่งขายจากตัวเชื่อม | 13,797 | 9,964 |
| ใบสั่งขายจากผู้ใช้ | 4,110 | 3,634 |
| การรอล็อกตลอดการทดสอบ | 518 | 791 |
| CPU ที่ใช้ | 28.5% | 25.5% |
| คำขอที่ล้มเหลว | 0 | 0 |
ให้อ่านสองแถวล่างพร้อมกัน เครื่องพยายามทำงานมากขึ้น ตอบสนองช้าลงกว่าสองเท่า ทำใบสั่งขายจากตัวเชื่อมได้น้อยลง 28 เปอร์เซ็นต์ และใช้ CPU น้อยลงสามจุด เพราะ worker นั่งรอคิวแก้ไขข้อมูลสต็อกชุดเดียวกัน worker ที่รออยู่ถูกจองไว้แล้วแต่ไม่ได้ทำงาน
ตัวเชื่อมตามงานของตัวเองไม่ทัน เมื่ออยู่ลำพังทำได้ 196 ใบต่อนาที เมื่อมีผู้ใช้อยู่ด้วยทำได้ 142 ใบต่อนาที ไม่มีอะไรล้มเหลว ไม่มี error รายงานออกมา เพียงแต่ทั้งสองฝ่ายทำงานได้น้อยลง
นี่คือผลที่มีน้ำหนักที่สุดต่อการวางระบบ เพราะปัญหามองไม่เห็นจากสองทางพร้อมกัน error อยู่ที่ศูนย์ เพราะ Odoo ลองใหม่ให้เองอย่างเงียบ ๆ ส่วนกราฟ CPU กลับลดลง ผู้ดูแลที่ตรวจสองอย่างนี้จะเห็นว่าระบบปกติดี ในจังหวะที่เวลาตอบสนองเพิ่มขึ้นกว่าสองเท่า
สิ่งที่เราได้จากการทดสอบชุดนี้ไม่ใช่คำตอบว่าเครื่องต้องใหญ่แค่ไหน แต่เป็นรายการสิ่งที่ควรออกแบบไว้ตั้งแต่แรก เพราะแก้ทีหลังยากกว่ามาก ทุกข้อด้านล่างมาจากสิ่งที่เราเห็นระหว่างทดสอบ ไม่ใช่จากตำรา
ตัวเลขในตารางแรกเป็นดอลลาร์ต่อเดือน คิดจากเครื่องที่เปิดทิ้งไว้ตลอดเดือนโดยไม่ผูกสัญญาล่วงหน้า ทุกรายการอ้างอิงโซนสิงคโปร์ และเทียบกันด้วยสเปกชุดเดียวกับที่ใช้ทดสอบจริง ราคาของ GCP รวมส่วนลดการใช้งานต่อเนื่องที่ระบบให้อัตโนมัติแล้ว ส่วน Azure ไม่มีส่วนลดลักษณะนี้ ค่าไอพีและปริมาณข้อมูลขาออกของ DigitalOcean เป็นศูนย์เพราะรวมอยู่ในค่าเครื่องแล้ว
| รายการ | สเปก | DigitalOcean | GCP | Azure |
|---|---|---|---|---|
| เครื่อง Odoo | 4 vCPU / 8 GB | 48 | 84 | 147 |
| ฐานข้อมูลแบบจัดการให้ | 2 vCPU / 4 GB รวมพื้นที่ 100 GB | 60 | 137 | 90 |
| พื้นที่ดิสก์ | 100 GB | 10 | 11 | 14 |
| พื้นที่เก็บไฟล์สำรอง | 250 GB | 5 | 5 | 5 |
| Cloudflare | ไม่เกิน 50 บัญชี ใช้ได้ฟรี | 0 | 0 | 0 |
| ไอพีและข้อมูลขาออก | ประมาณ 100 GB ต่อเดือน | 0 | 15 | 4 |
| อีเมลออก | ตามปริมาณที่ส่ง | 1 | 1 | 1 |
| รวมเฉพาะระบบจริง | 124 | 253 | 261 | |
| เครื่องทดสอบแยก | 2 vCPU / 4 GB ไว้ซ้อมก่อนแตะระบบจริง | 24 | 40 | 74 |
| รวมทั้งหมด | 148 | 293 | 335 |
ตารางถัดไปเรียงจากถูกไปแพง เพื่อให้เห็นว่าทางเลือกไหนอยู่ตรงไหน รายการที่เขียนว่าติดตั้งฐานข้อมูลบนเครื่องเอง หมายถึงไม่ใช้บริการฐานข้อมูลแบบจัดการให้ แต่ลง PostgreSQL บนเครื่องอีกตัวหนึ่งแทน
| ทางเลือก | บาทต่อปี | เงื่อนไข |
|---|---|---|
| AWS แบบจ่ายล่วงหน้า 1 ปี | 48,000 | ผูกพันสัญญา 12 เดือน |
| โครงสร้างที่ทดสอบ เฉพาะระบบจริง | 52,000 | ไม่ผูกพัน |
| GCP ติดตั้งฐานข้อมูลบนเครื่องเอง จ่ายล่วงหน้า 1 ปี | 58,000 | ผูกพันสัญญา 12 เดือน |
| AWS แบบจ่ายตามใช้ | 65,000 | ไม่ผูกพัน |
| GCP ติดตั้งฐานข้อมูลบนเครื่องเอง | 71,000 | ไม่ผูกพัน |
| Azure ติดตั้งฐานข้อมูลบนเครื่องเอง จ่ายล่วงหน้า 1 ปี | 72,000 | ผูกพันสัญญา 12 เดือน |
| GCP ใช้ฐานข้อมูลแบบจัดการให้ | 106,000 | ไม่ผูกพัน |
| Azure ใช้ฐานข้อมูลแบบจัดการให้ | 110,000 | ไม่ผูกพัน |
| Azure ติดตั้งฐานข้อมูลบนเครื่องเอง | 113,000 | ไม่ผูกพัน |
สิ่งที่ทำให้ราคาต่างกันมากที่สุดคือฐานข้อมูลแบบจัดการให้ ถ้าเลือกลง PostgreSQL บนเครื่องเอง ค่าใช้จ่ายของ GCP และ Azure จะลดลงประมาณหนึ่งในสาม แลกกับงานดูแลที่ต้องทำเอง ผู้ให้บริการรายใหญ่ทั้งสองเจ้าไม่ได้ถูกกว่าโครงสร้างที่ทดสอบ ถ้าจะเลือกใช้ เหตุผลควรมาจากเรื่องสิทธิ์ในการควบคุมระบบ การสำรองและกู้คืนข้อมูล และการเชื่อมต่อกับระบบอื่น ไม่ใช่เรื่องราคา
คำนวณที่ 35 บาทต่อดอลลาร์ ตัวเลขชุดนี้เป็นค่าโครงสร้างพื้นฐานของเครื่องที่ใช้ทดสอบเท่านั้น ไม่ใช่ราคาทั้งโครงการ เพราะยังไม่รวมค่าติดตั้ง ค่าย้ายข้อมูล ค่าพัฒนาเพิ่มเติม และค่าดูแลระบบ การทดสอบทั้งหมดใช้ Odoo Community จึงไม่มีค่าลิขสิทธิ์ Odoo Enterprise อยู่ในตัวเลขนี้ ราคาผู้ให้บริการอ้างอิงจากราคาประกาศ ณ ช่วงที่ทดสอบ
ข้อจำกัดต่อไปนี้มีผลต่อการตีความผลทดสอบ และต่อขนาดเครื่องที่ควรเลือก