วิศวกรรมEngineeringอ่าน 6 นาที6 min readมีตัวอย่างให้ลองTry it inside
แอปหลับแล้วตื่นยังไงHow an app sleeps and wakes
โดย ทีมวิศวกรรม Assets ArtBy Assets Art Engineering
เที่ยงคืน มีคนเปิดเว็บของแอปที่ไม่มีใครแตะมาหลายชั่วโมง ตอนนั้นแอปไม่ได้รันอยู่ แต่ผู้เยี่ยมชมไม่เห็น error เห็นแค่หน้าที่มาช้ากว่าปกติหนึ่งครั้ง บทความนี้ตาม request ตัวนั้นตั้งแต่เข้ามาจนแอปตอบ และบอกตรง ๆ ว่าตอนนี้ระบบทำอะไรได้ ยังไม่ได้ทำอะไรAt midnight someone opens the site of an app nobody has touched for hours. The app is not running. The visitor sees no error, only one page that arrives slower than usual. This post follows that request from the moment it comes in until the app answers, and says plainly what the system does today and what it does not.
หลับคือปิดจริงAsleep means really off
แอปที่มี backend หรือ SSR จะหลับเมื่อว่างครบเวลาที่ตั้งไว้ ตั้งเป็น 0 ถ้าไม่ต้องการให้หลับ ค่าเริ่มต้นคือ 300 วินาที1ค่า 300 วินาทีคือค่าตั้งต้นของเครื่องมือสร้าง target ตั้งเป็นค่าอื่นได้ต่อ target ตอนหลับ ระบบสั่งให้แอปปิดตัวเองดี ๆ ก่อน รอไม่เกินสิบวินาที ถ้ายังไม่จบก็บังคับปิด แล้วเก็บกวาดทรัพยากรที่แอปใช้อยู่An app with a backend or SSR sleeps once it has been idle for the time you set. Set it to 0 if you never want it to sleep; the default is 300 seconds.1300 seconds is the default of the tool that creates a target. Each target can be set to another value. To put it to sleep, the system first asks the app to shut itself down, waits up to ten seconds, forces it off if it is still there, and cleans up what it was using.
นี่คือการปิดจริง ไม่ใช่การแช่แข็ง หน่วยความจำจึงคืนให้เครื่องทั้งหมด สิ่งที่เหลืออยู่คือสิ่งที่แอปต้องใช้ตอนตื่น: โค้ดที่ build ไว้ล่าสุด ตัวตนของแอปบนเครื่อง (หมายเลขและที่อยู่ของมัน) และดิสก์ของ ไม่มี container image ต้องดึงใหม่ และเพราะตัวตนถูกเก็บไว้ ตื่นมาจึงเป็นตัวเดิม ไฟล์ที่แอปเขียนลงดิสก์ก็ยังอยู่ แต่เฉพาะใน sandbox ตัวเดิม sandbox อื่นของแอปเดียวกันไม่เห็น และทุกครั้งที่ deploy ใหม่ ดิสก์จะเริ่มว่างThis is a real stop, not a freeze, so all the memory goes back to the machine. What stays is what the app needs to come back: the latest build, the app's identity on the machine (its number and address), and the disk of its . There is no container image to pull again, and because the identity is kept, what wakes is the same app. The files it wrote to that disk are still there too, but only in that same sandbox: another sandbox of the app does not see them, and every new deployment starts from an empty disk.
ผลที่ตามมามีข้อเดียวที่ต้องจำ อะไรที่อยู่ในหน่วยความจำจะหายตอนหลับ รวมถึง timer อย่าง setInterval ที่ไม่เดินระหว่างนั้น ลองกดดูOne consequence is worth remembering: anything held in memory is gone when the app sleeps, including timers such as setInterval, which do not tick meanwhile. Try it.
noodle-queue.app.asart.dev
- กด ส่ง request เพื่อเริ่มPress Send a request to start
คนเปิดเว็บ ใครถือ request ไว้A visitor arrives: who holds the request
request จากอินเทอร์เน็ตเข้าประตูหน้าของเราก่อน ประตูหน้าไม่ได้จำว่าแอปไหนหลับอยู่ มันส่งต่อไปที่ ของเครื่องที่แอปอยู่ gateway คือตัวที่ถือ request: ถ้าไม่มีแอปที่กำลังทำงาน มันสั่งให้ปลุก แล้วถือ request ไว้จนแอปพร้อมจึงส่งต่อ ถ้ามี request หลายตัวมาพร้อมกัน ทุกตัวรอการปลุกครั้งเดียวกันA request from the internet reaches our front door first. The front door does not remember which apps are asleep; it forwards to the of the machine the app lives on. The gateway is the one that holds the request: when no instance of the app is running it asks for a wake, holds the request until the app is ready, then passes it on. Requests that arrive together wait for the same wake.
gateway ถือ request ได้เท่ากับ request timeout ของแอป แต่ไม่เกิน 30 วินาที เกินเวลานั้นผู้เรียกได้ 504 ถ้าการปลุกล้มเหลวได้ 503 ไม่เช่นนั้นผู้เรียกไม่เห็น error เลย เรายังไม่สัญญาตัวเลขเวลาตื่น เพราะยังไม่มีตัวเลขที่พร้อมรับประกัน แต่ที่วัดได้: ในชุดทดสอบของเรา การปลุกแอป 50 รอบผ่านเส้นทางนี้ใช้เวลาค่ากลางราว 50 มิลลิวินาที และช้าที่สุดราว 0.3 วินาที3วัดกับแอปทดสอบขนาดเล็กบนเครื่องทดสอบของเรา รวมเวลาที่ curl ใช้ ไม่ใช่คำสัญญา แอปอื่นจะใช้เวลาต่างออกไปThe gateway holds a request for the app's request timeout, but never more than 30 seconds. Past that the caller gets a 504, and if the wake failed, a 503. Otherwise the caller sees no error at all. We do not promise a wake time, because there is no number we are ready to guarantee. What we have measured: in our test suite, 50 wakes through this path took a median of about 50 milliseconds, and the slowest about 0.3 seconds.3Measured with a small test app on our test machine, including the time curl takes. It is not a promise; other apps will take different times.
ระหว่างที่ gateway ถือ request อยู่ ระบบสร้าง process ของแอปขึ้นมาใหม่ แอปบน Cloud ไม่ได้รันใน container มันเป็น process ธรรมดาที่เราล้อมด้วยกลไกของ Linux เอง ทั้งการแยกพื้นที่ชื่อ การจำกัดทรัพยากร และการจำกัดว่า system call ไหนใช้ได้ ข้อดีคือตื่นโดยไม่ต้องเตรียม image ข้อแลกเปลี่ยนคือเราต้องเขียนรั้วทุกชั้นเองWhile the gateway holds the request, the system starts the app's process again. Apps on Cloud do not run in containers. Each is a plain process that we fence in with Linux's own mechanisms: separate namespaces, resource limits, and a list of the system calls it may make. The benefit is that the app wakes without any image to prepare; the trade-off is that we write every fence ourselves.
แอปเรียกแอปApps calling apps
แอปที่อยู่ใน VPC เดียวกันเรียกกันด้วยชื่อลงท้าย .internal ได้ ถ้าชื่อนั้นอยู่ใน manifest ของ deployment ตอนนี้ขั้นตอน deploy ยังไม่ใส่ให้เอง ชื่ออัตโนมัติอย่าง <app>.internal อยู่ในแผน ยังไม่ได้สร้าง ตัวช่วยแปลชื่อของ VPC ตอบที่อยู่ของแอปทั้งตัวที่ทำงานอยู่ และตัวที่หลับอยู่Apps in the same VPC can call each other by a name ending in .internal, if that name is in the deployment's manifest. Today the deploy step does not add names for you; an automatic name such as <app>.internal is planned and not built. The VPC's name resolver answers with the addresses of the app's instances, running and sleeping alike.
ตัวแปลชื่อ ไม่ได้เป็นคนปลุก คนปลุกคือ packet แรก เมื่อผู้เรียกส่ง ไปยังที่อยู่ของแอปที่หลับ โปรแกรมที่ดักอยู่บนเส้นทางของ packet ในเครื่องจะทิ้ง SYN นั้น แล้วแจ้งให้ปลุกแอป SYN ถัดไป ซึ่ง TCP ส่งซ้ำเองประมาณหนึ่งวินาทีต่อมา จึงต่อติด2ในชุดทดสอบของเรา ห้าแอปเรียกพร้อมกัน แต่ละการเชื่อมต่อสำเร็จที่ราว 1.02 ถึง 1.03 วินาที เป็นผลจากเครื่องทดสอบ ไม่ใช่คำสัญญาThe resolver does not wake anything. The first packet does. When the caller sends a to the address of a sleeping app, a program watching the packet path on the machine drops that SYN and raises the alarm to wake the app. The next SYN, which TCP resends by itself about a second later, connects.2In our test suite, five apps calling at once each connected at about 1.02 to 1.03 seconds. It is a result from a test machine, not a promise.
นั่นหมายความว่าโค้ดฝั่งผู้เรียกต้องเผื่อเวลา ลองเลื่อนดูว่ารอกี่วินาทีถึงจะรอดThat means the caller's code has to allow some time. Move the slider to see how long is long enough.
- 0.0 s
- ส่ง SYN แรก → ถูกทิ้ง แอปถูกสั่งให้ปลุกfirst SYN sent → dropped; the app is told to wake
- 0.8 s
- ผู้เรียกเลิกรอก่อนส่ง SYN ซ้ำ → ล้มcaller gives up before the SYN is resent → fails
- packet แรกหายเสมอ การเชื่อมต่อสำเร็จที่การส่งซ้ำครั้งแรก ในการทดสอบของเราใช้เวลาราว 1.03 วินาที ผู้เรียกที่ตั้ง connect timeout ต่ำกว่านั้นจะล้มในครั้งแรก และถ้าตั้งใกล้เคียงก็แทบไม่มีที่เผื่อThe first packet is always lost and the connection completes at the first resend. In our tests that took about 1.03 seconds. A caller with a connect timeout below that fails its first attempt, and one close to it has almost no margin.
- UDP ปลุกแอปไม่ได้ เพราะไม่มีการส่งซ้ำให้พึ่งUDP cannot wake an app, because there is no automatic resend to rely on.
- ปลุกได้เฉพาะแอปใน org และ VPC เดียวกับผู้เรียกOnly apps in the same org and VPC as the caller can wake it.
เขียนแอปให้หลับเก่งWriting apps that sleep well
แอปส่วนใหญ่ไม่ต้องแก้อะไร มีสี่ข้อที่ควรรู้Most apps need no changes. Four things are worth knowing.
- เก็บข้อมูลที่ต้องอยู่นานไว้ในฐานข้อมูลหรือ object storage (ที่เก็บไฟล์ภายนอกแอป) ไม่ใช่ในหน่วยความจำ และไม่ใช่บนดิสก์ของ sandbox ดิสก์ใช้เก็บไฟล์ชั่วคราวได้ ไฟล์ยังอยู่ตอนหลับ แต่หายทุกครั้งที่ deploy ใหม่ และ sandbox อื่นไม่เห็นKeep data that must last in a database or object storage (file storage outside the app), not in memory and not on the sandbox's disk. The disk is fine for scratch files: they stay through a sleep, but they are gone with every new deployment, and other sandboxes of the app do not see them.
- อย่านับเวลายาวด้วย timer ในหน่วยความจำ มันหยุดไปพร้อม processDo not count long stretches with in-memory timers; they stop with the process.
- ถ้าแอปเรียกแอปอื่นผ่านชื่อ
.internalหรือที่อยู่ของมัน ให้ตั้ง connect timeout อย่างน้อยสองสามวินาที ไม่ใช่หนึ่งวินาทีIf your app calls another through a.internalname or its address, set a connect timeout of a few seconds, not one second. - ถ้าหน้าไหนต้องเร็วเสมอ ให้เป็น static เพราะเว็บ static ไม่หลับIf a page must always be instant, make it static, because static sites never sleep.
ที่ยังไม่ได้ทำ: การแช่แข็งหรือ snapshot process เพื่อให้ตื่นเร็วขึ้นอยู่ในแผน ยังไม่มีให้ใช้Not built yet: freezing or snapshotting the process so it wakes faster is a plan, not something that runs today.
เชิงอรรถFootnotes
- 1ค่า 300 วินาทีคือค่าตั้งต้นของเครื่องมือสร้าง target ตั้งเป็นค่าอื่นได้ต่อ target300 seconds is the default of the tool that creates a target. Each target can be set to another value. กลับไปที่ข้อความBack to the text
- 2ในชุดทดสอบของเรา ห้าแอปเรียกพร้อมกัน แต่ละการเชื่อมต่อสำเร็จที่ราว 1.02 ถึง 1.03 วินาที เป็นผลจากเครื่องทดสอบ ไม่ใช่คำสัญญาIn our test suite, five apps calling at once each connected at about 1.02 to 1.03 seconds. It is a result from a test machine, not a promise. กลับไปที่ข้อความBack to the text
- 3วัดกับแอปทดสอบขนาดเล็กบนเครื่องทดสอบของเรา รวมเวลาที่ curl ใช้ ไม่ใช่คำสัญญา แอปอื่นจะใช้เวลาต่างออกไปMeasured with a small test app on our test machine, including the time curl takes. It is not a promise; other apps will take different times. กลับไปที่ข้อความBack to the text