ปลุกฐานข้อมูลโดยไม่ให้การเชื่อมต่อที่ตายแล้วค้างอยู่
แอปที่เผยแพร่บน Mythex และฐานข้อมูลของมันจะเข้าโหมดหลับเมื่อไม่มีใครใช้งาน เมื่อแอปตื่นขึ้น คำขอแรกอาจค้างอยู่ 10–12 วินาที เพราะมันพยายามใช้การเชื่อมต่อฐานข้อมูลซ้ำ ซึ่งตายไประหว่างที่ทุกอย่างหลับ ตอนนี้เราให้แอป Spring Boot ได้รับการตั้งค่าฐานข้อมูลที่ปลอดภัยต่อการหลับ และบอก agent ให้สร้างแอปทุก stack ด้วยวิธีเดียวกัน บนแพลตฟอร์มโฮสติ้ง คำขอแรกหลังการตื่นลดจาก 12.7 วินาทีเหลือ 2.3 วินาที
ทำไมแอปและฐานข้อมูลถึงหลับ
แอปที่เผยแพร่บน Mythex รันบนเครื่องที่หยุดทำงานชั่วคราวหลังไม่มีการใช้งานไม่กี่นาที และฐานข้อมูลคือ serverless Postgres ที่หลับหลังไม่มีการใช้งานห้านาที และตื่นตอนคำสั่งถัดไปเข้ามา นี่คือสิ่งที่ทำให้แอปที่เงียบไม่ทำให้เสียค่าใช้จ่าย: คุณจ่ายตามสิ่งที่ฐานข้อมูลใช้งานจริง และแอปที่หลับไม่ใช้ compute เลย เราเขียนเรื่องด้านการเรียกเก็บเงินไว้ในการคิดค่าฐานข้อมูลตามที่ใช้งานจริง
ข้อควรระวังคือทั้งสองหลับแยกกัน และพวกมันไม่เห็นตรงกันว่าการเชื่อมต่อคืออะไร
อาการ: การเข้าชมครั้งแรกช้า
แอปเซิร์ฟเวอร์ส่วนใหญ่เก็บ connection pool ไว้ คือการเชื่อมต่อฐานข้อมูลที่เปิดค้างไว้จำนวนหนึ่งให้พร้อมใช้ เพื่อให้แต่ละคำขอไม่ต้องจ่ายต้นทุนการเปิดการเชื่อมต่อใหม่ นั่นเป็นการออกแบบที่ถูกต้องสำหรับแอปที่เปิดตลอดเวลา
เมื่อเกิดการหลับจะเป็นดังนี้:
- แอปกำลังรัน และมีการเชื่อมต่อว่างไม่กี่รายการใน pool
- ไม่มีใครเข้าชม เครื่องของแอปหยุดทำงาน ค้างอยู่ทั้งอย่างนั้น โดย pool ยังอยู่ครบ
- ฐานข้อมูลหลับและทิ้งปลายทางของการเชื่อมต่อทุกเส้น
- ผู้เข้าชมมาถึง แอปตื่นขึ้นเหมือนเดิมทุกประการ แต่ยังถือการเชื่อมต่อที่ฐานข้อมูลไม่รู้จักแล้ว
- pool ส่งการเชื่อมต่อหนึ่งในนั้นออกมา และต้องรู้ก่อนว่ามันตายแล้ว โดยลองใช้และรอ ก่อนจะยอมแพ้กับมัน
ในแอป Spring Boot การตรวจสอบการเชื่อมต่อที่ตายแล้วบล็อกคำขอแรกราว 10 วินาที ก่อนจะล้มเหลวด้วยข้อความ "Connection is not available" สำหรับผู้เข้าชม เว็บดูเหมือนค้าง แล้วอาจแสดงข้อผิดพลาด
ทำไมแอป Spring ได้รับผลกระทบหนักที่สุด
Spring Boot เป็นเฟรมเวิร์ก Java ที่นิยม และใช้ connection pool ชื่อ HikariCP เป็นค่าเริ่มต้น Spring สามารถตั้งค่า pool จากการตั้งค่าได้ รวมถึงจำนวนการเชื่อมต่อว่างที่จะเก็บไว้
แต่ Mythex ให้ฐานข้อมูลแก่ทุกแอปเป็น DATABASE_URL เดียวในรูปแบบ postgresql://user:pass@host/db Spring อ่านรูปแบบนั้นโดยตรงไม่ได้ ดังนั้นแอป Spring ที่สร้างบน Mythex จึงสร้าง pool ของตัวเองในโค้ดโดยแยก URL นั้น และ pool ที่สร้างด้วยมือจะเพิกเฉยการตั้งค่า spring.datasource.* ทั้งหมดของ Spring แม้ใครจะรู้ค่า pool ที่ถูกต้อง ก็ไม่มีที่ให้ค่านั้นมีผล
การแก้ไขส่วนที่ 1: การตั้งค่าที่ Spring ใช้ได้
ตอนนี้เมื่อโปรเจกต์มีฐานข้อมูล เราจะให้ environment variables ที่ Spring อ่านได้โดยตรงด้วย:
SPRING_DATASOURCE_URL,SPRING_DATASOURCE_USERNAMEและSPRING_DATASOURCE_PASSWORDแปลงจากDATABASE_URL(โดยต้องใช้ SSL เว้นแต่ URL จะระบุเป็นอย่างอื่น)- การตั้งค่า pool ที่รอดจากการหลับ:
| การตั้งค่า | ค่า | เหตุผล |
|---|---|---|
| จำนวนการเชื่อมต่อว่างขั้นต่ำ | 0 | ไม่เก็บการเชื่อมต่อใดไว้ระหว่างคำขอ จึงไม่มีอะไรให้ค้าง |
| เวลาหมดอายุของการว่าง | 30 วินาที | ปิดการเชื่อมต่อที่ไม่ได้ใช้เร็ว |
| อายุสูงสุดของการเชื่อมต่อ | 2 นาที | ยกเลิกทุกการเชื่อมต่อก่อนที่มันจะเก่า |
| เวลาหมดอายุของการเชื่อมต่อ | 5 วินาที | ไม่ให้คำขอค้างนานหากฐานข้อมูลตอบช้า |
เมื่อไม่มีการเชื่อมต่อว่างที่เก็บไว้ แอปที่ตื่นขึ้นจะเปิดการเชื่อมต่อใหม่หนึ่งเส้น แทนที่จะลุยผ่านการเชื่อมต่อที่ตายแล้ว
เมื่อมีตัวแปรเหล่านี้ Spring จะตั้งค่า pool เอง และการตั้งค่าจะมีผล สิ่งที่โปรเจกต์ตั้งไว้เองจะชนะเสมอ เราเติมเฉพาะส่วนที่ขาดไป แอปที่ไม่ใช่ Spring จะเพิกเฉยตัวแปรเหล่านี้
รายละเอียดที่เกือบทำให้มันไม่ทำอะไรเลย
Spring แมปตัวแปรสภาพแวดล้อมเข้ากับการตั้งค่าของมันด้วยกฎที่ง่ายจะผิด การตั้งค่า minimum-idle ต้องเขียนเป็น SPRING_DATASOURCE_HIKARI_MINIMUMIDLE คือตัดขีดกลางออกและต่อคำเข้าด้วยกัน การเขียนเป็น SPRING_DATASOURCE_HIKARI_MINIMUM_IDLE ซึ่งดูเป็นธรรมชาติกว่า จะแมปไปยังการตั้งค่าอื่นที่ไม่มีอยู่จริง (minimum.idle) และถูกเพิกเฉยโดยไม่มีข้อความ
ความผิดพลาดแบบนั้นผ่านการรีวิวทุกครั้งและไม่ทำอะไรเลยในโปรดักชัน ดังนั้นการทดสอบจึงตรวจทั้งว่าค่าถูกตั้งไว้ และไม่มีตัวแปรใดใช้รูปแบบที่คั่นด้วยขีดล่าง
การแก้ไขส่วนที่ 2: ทุก stack อื่น
Spring ไม่ใช่เฟรมเวิร์กเดียวที่มี pool และเราไม่สามารถตั้งค่าล่วงหน้าให้ทุกไลบรารีในทุกภาษาได้ ดังนั้น agent ของ Mythex ซึ่งเป็น AI ที่เขียนแอปของคุณ ตอนนี้จะได้รับคำสั่งถาวรสำหรับทุก stack: แอปที่เผยแพร่แล้วและฐานข้อมูลของมันจะหลับเมื่อไม่ได้ใช้งาน ดังนั้นการเชื่อมต่อฐานข้อมูลที่เปิดค้างไว้อาจตายแล้วในคำขอถัดไป มันควร:
- ไม่เปิดการเชื่อมต่อฐานข้อมูลว่างค้างไว้ หรือตรวจแต่ละการเชื่อมต่อก่อนใช้งาน
- ลองใหม่หนึ่งครั้งเมื่อเกิดข้อผิดพลาดของการเชื่อมต่อ และ
- ใช้การตั้งค่าฐานข้อมูลที่แพลตฟอร์มให้มาในตัวแปรสภาพแวดล้อม แทนที่จะสร้าง pool ของตัวเองโดยแยก
DATABASE_URL
ประเด็นสุดท้ายปิดวงจรกับส่วนที่ 1: แอปที่สร้างด้วยการตั้งค่าที่แพลตฟอร์มให้มาจะได้การตั้งค่าที่ปลอดภัยต่อการหลับโดยอัตโนมัติ
ผลลัพธ์
วัดบนแพลตฟอร์มโฮสติ้งที่เรารันแอปที่เผยแพร่แล้ว คำขอแรกหลังการตื่นลดจาก 12.7 วินาทีเหลือ 2.3 วินาที
สิ่งที่เราเรียนรู้
- ทุกชั้นที่หลับต้องตกลงกันว่าอะไรยังอยู่รอด แอปที่หยุดชั่วคราวและฐานข้อมูลที่หลับต่างก็ตัดสินใจอย่างสมเหตุสมผล เมื่อรวมกัน แอปตื่นขึ้นมาพร้อมสถานะที่ไม่เป็นความจริงแล้ว
- ค่าเริ่มต้นที่ออกแบบมาสำหรับเซิร์ฟเวอร์ที่เปิดตลอดไม่เหมาะกับแอปที่ลดเป็นศูนย์ การเก็บการเชื่อมต่อให้อุ่นเป็นความคิดที่ดี จนกว่าทุกอย่างรอบข้างจะเย็นลง
- ให้เฟรมเวิร์กได้รับข้อมูลในรูปแบบของมันเอง URL เดียวสะดวกสำหรับเรา แต่บังคับให้แอป Spring ทุกแอปต้องสร้าง pool ด้วยมือที่เพิกเฉยการตั้งค่าของตัวเอง
- ทดสอบว่าการตั้งค่าสะกดตรงกับที่เฟรมเวิร์กอ่าน ค่าที่ถูกต้องภายใต้ชื่อที่ผิดไม่ทำอะไรเลย โดยไม่มีข้อผิดพลาด
หากคุณยังใหม่กับวิธีการทำงานของฐานข้อมูลบน Mythex ดูเอกสารฐานข้อมูล และคำอธิบายของเราเรื่องPostgres คืออะไร