ASSETS ART
บล็อกทั้งหมดAll posts

วิศวกรรมEngineeringอ่าน 6 นาที6 min readมีตัวอย่างให้ลองTry it inside

Deploy เวอร์ชันใหม่ตอนมีคนใช้อยู่ ระบบสลับยังไงWhat happens when you redeploy an app people are using

โดย ทีมวิศวกรรม Assets ArtBy Assets Art Engineering

ขนาดตัวอักษรText size

คุณแก้โค้ดแล้ว deploy ในขณะที่ลูกค้ากำลังใช้แอปอยู่ ลูกค้าไม่ควรเห็นหน้าขาว ไม่ควรเห็นเวอร์ชันเก่าสลับกับใหม่ไปมา และถ้าเวอร์ชันใหม่มีบั๊กถึงขั้นสตาร์ตไม่ขึ้น เวอร์ชันเก่าต้องยังตอบอยู่ บทความนี้เล่าว่าตอนนี้ระบบทำยังไงให้ได้อย่างนั้น และยังทำไม่ได้อะไรบ้างYou change the code and deploy while customers are using the app. They should not see a blank page, they should not see the old and the new version alternate, and if the new version is so broken that it cannot start, the old one must keep answering. This post says how the system gets there today, and what it cannot do yet.

ทำไมต้องมีขั้นสลับWhy a switch is needed

ก่อนมีขั้นสลับ การ deploy เป็นแบบขี้เกียจ แอปที่กำลังรันอยู่ใช้การตั้งค่าเดิมต่อไปจนกว่าจะหลับแล้วตื่นใหม่ แอปที่มีคนเรียกตลอดไม่เคยหลับ เวอร์ชันใหม่จึงไม่เคยได้รับ traffic เลย ตอนนี้ระบบสลับให้เอง: เมื่อมีเวอร์ชันใหม่ที่ถูกเปิดใช้ มันสตาร์ตตัวใหม่ขึ้นมาข้าง ๆ ตัวเก่า แล้วย้ายงานไปทีเดียวBefore the switch existed, a deploy was lazy. A running app kept its old settings until it slept and woke again, and an app that is called all the time never sleeps, so the new version never got any traffic. Now the system does the switch itself: when a new version is activated, it starts the new instances next to the old ones, then moves the work over in one go.

rollback ใช้เส้นทางเดียวกันทุกประการ ระบบมองเวอร์ชันก่อนหน้าเป็นเวอร์ชันปัจจุบันใหม่ตัวหนึ่ง และไม่ build ใหม่ ส่วนจะสั่ง deploy หรือ rollback ด้วยปุ่มหรือคำสั่งไหน ชื่อและหน้าตายังไม่นิ่ง บทความนี้จึงไม่เล่าตรงนั้นA rollback takes exactly the same path. The system treats the previous version as a new current version, and it does not rebuild. Which button or command you use to deploy or roll back is not settled yet, so this post leaves that out.

ใหม่พร้อมก่อน แต่ยังไม่รับงานNew first, but not serving yet

ระบบตรวจแต่ละแอปวินาทีละครั้ง ถ้ายังมีตัวเก่าที่รับงานอยู่ และตัวใหม่มีไม่ครบ มันสตาร์ตตัวใหม่ที่ขาดทั้งหมดพร้อมกัน จำนวนที่ต้องการคือจำนวนตัวเก่าที่รับงานอยู่ แต่ไม่เกินจำนวนสูงสุดที่แอปตั้งไว้ ช่วงนี้แอปมีตัวรันมากกว่าปกติชั่วครู่ และใช้หน่วยความจำกับ CPU ของเครื่องเพิ่มตามนั้น1The system checks each app once a second. If old instances are still serving and the new version does not have enough, it starts all the missing new ones together. The number wanted is the number of old instances serving, capped at the maximum the app set. For a moment the app runs more instances than usual, and uses that much more memory and CPU on the machine.1

ตัวใหม่ที่สตาร์ตสำเร็จไม่ได้รับ request ทันที ระบบ กัน มันไว้ก่อน มันทำงานอยู่แล้ว แต่ประตูที่ส่งงานให้แอปยังไม่รู้ว่ามันมี การปลุกแอปก็ไม่เลือกมัน เหตุผลคือ จำนวนตัวที่พร้อมรับงานต้องไม่ต่ำกว่าก่อน deploy ระหว่างรอให้ตัวใหม่ขึ้นครบA new instance that starts fine does not get requests at once. The system holds it back: it is running, but the door that hands work to the app has not been told about it, and a wake does not pick it. The reason is that the number of instances ready to take work must never be lower than before the deploy while the rest of the new ones come up.

ลองเลยTry itสลับ v1 เป็น v2 ทีละขั้นSwitch v1 to v2, one step at a time
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
v1
รับงานservingรับงานserving
v2
ปิดstoppedปิดstopped
ผู้ใช้ได้Users getv1v1v1v1v1
ระบบทำอะไรไปWhat the system did
now
v1 สองตัวรับงานอยู่two v1 instances serve
จำลองในเบราว์เซอร์: ลำดับขั้นเป็นของระบบจริง จำนวนและเวลาเป็นของตัวอย่างA simulation in your browser: the order of the steps is the real system's; the counts and times are the demo's.

ลำดับที่ทำให้คำตอบไม่สลับไปมาThe order that stops answers flipping

เมื่อตัวใหม่ครบ ระบบทำสองอย่างเรียงกัน ประกาศก่อนว่าตัวเก่าทุกตัวกำลังเลิกรับงาน แล้วจึงเปิดตัวใหม่ที่กันไว้ให้ประตูเห็น ลำดับนี้คือหัวใจ ประตูเห็น "เก่าเลิกรับ" ก่อน "ใหม่เริ่มรับ" เสมอ จึงไม่มีจังหวะที่ request ถูกส่งไปตัวเก่าหลังจากที่ตัวใหม่ตอบไปแล้ว ผู้ใช้เห็นคำตอบเปลี่ยนจาก v1 เป็น v2 ทางเดียว ไม่เห็นมันกระโดดกลับWhen the new instances are all up, the system does two things in order. It first announces that every old instance is going to stop taking work, and only then lets the door see the new ones it held back. The order is the point. The door always sees "old stops taking work" before "new starts", so there is never a moment when a request goes to an old instance after a new one has answered. Users see the answer change from v1 to v2 one way, and never jump back.

ไม่มีการค่อย ๆ แบ่งเปอร์เซ็นต์ การสลับเป็นแบบทีเดียวต่อรอบ เมื่อตัวใหม่พร้อมครบตามจำนวนที่ต้องการThere is no gradual percentage split. The switch is all at once per round, when the new instances are ready in the number wanted.

เก่าถอยเมื่อไม่มีงานค้างThe old one leaves when nothing is left

ตัวเก่าไม่ถูกปิดทันที ประตูนับ request ที่แต่ละตัวกำลังทำอยู่และรายงานให้ระบบ พอประกาศว่าตัวเก่าเลิกรับงาน ประตูรายงานทันที ไม่รอรอบปกติ ระบบรออย่างน้อยครึ่งวินาทีให้ตัวเลขนิ่ง แล้วปิดตัวเก่า เมื่อรายงานว่าเหลือ 0 หรือเมื่อครบ request timeout ของเวอร์ชันเก่า แล้วแต่อย่างไหนมาก่อน การปิดเริ่มจากขอให้ปิดตัวเองดี ๆ รอไม่เกินสิบวินาที แล้วจึงบังคับAn old instance is not stopped at once. The door counts the requests each instance is handling and reports to the system. When the old ones are announced as done taking work, the door reports straight away instead of waiting for its usual round. The system waits at least half a second for the figure to settle, then stops the old instance when the report says 0, or when the old version's request timeout runs out, whichever comes first. The stop begins by asking it to close itself, waits up to ten seconds, then forces it.

ถ้าเวอร์ชันใหม่พังWhen the new version is broken

ถ้าตัวใหม่ตัวใดตัวหนึ่งสตาร์ตไม่ขึ้น ระบบหยุดตัวใหม่ที่กันไว้ทั้งหมด และตัวเก่ารับงานต่อ ผู้ใช้ไม่เห็นเวอร์ชันที่พังเลย เพราะมันไม่เคยได้รับ traffic ติ๊ก "ทำให้ v2 พัง" ในตัวอย่างข้างบนเพื่อดู ระบบจำไว้ว่าเวอร์ชันนี้ล้มเหลวและส่งเหตุการณ์ออกไปให้ส่วนที่ดูแลเครื่องIf any new instance cannot start, the system stops all the new ones it held back, and the old ones carry on. Users never see the broken version, because it never got traffic. Tick "Make v2 broken" in the demo above to watch it. The system remembers that this version failed and sends an event to the part that looks after the machine.

ที่ยังไม่ได้ทำมีสามข้อ ข้อแรก ตอนนี้ระบบยังไม่บอกคุณว่า deploy ล้มเหลวและเวอร์ชันก่อนหน้ายังใช้อยู่ เหตุการณ์ข้างบนถูกเก็บไว้แต่ยังไม่มีส่วนไหนอ่านแล้วแจ้งผู้ใช้ คุณจะรู้จากการที่เวอร์ชันใหม่ไม่ขึ้นเท่านั้น ข้อสอง ระบบไม่ลองสลับซ้ำเอง จนกว่าจะมี deploy ครั้งถัดไป ข้อสาม ความล้มเหลวที่จำไว้อยู่ในหน่วยความจำ ถ้าตัวจัดการบนเครื่องรีสตาร์ต มันจะลองสลับอีกหนึ่งครั้ง และจะล้มซ้ำถ้าเวอร์ชันนั้นยังพังThree things are not done yet. First, the system does not yet tell you that a deploy failed and the previous version is still live. The event above is stored, but nothing reads it and notifies the user, so you find out because the new version does not appear. Second, the system does not retry the switch on its own until the next deploy. Third, the remembered failure lives in memory: if the manager on the machine restarts, it tries the switch once more, and fails again if that version is still broken.

เราทดสอบเส้นทางนี้ด้วยแอปที่ตอบ "v1" หรือ "v2" ตามเวอร์ชัน ยิงโหลดจากสี่ thread แล้วสั่งสลับ เงื่อนไขคือทุกคำตอบต้องเป็น 200 คำตอบ v2 แรกต้องมาภายใน 30 วินาที และห้ามมี request ที่เริ่มหลังคำตอบ v2 แรกแล้วได้ v1 จากนั้นทดสอบเวอร์ชันที่สตาร์ตไม่ขึ้น และ rollback2We test this path with an app that answers "v1" or "v2" by version, send load from four threads, and trigger the switch. Every answer must be a 200, the first v2 answer must arrive within 30 seconds, and no request that started after the first v2 answer may get v1. Then we test a version that cannot start, and a rollback.2

เขียนแอปให้ deploy ได้ไม่สะดุดWriting apps that deploy smoothly

  • เวอร์ชันเก่ากับใหม่รันพร้อมกันได้ชั่วครู่ ถ้า deploy นี้เปลี่ยน schema ของฐานข้อมูล ให้เปลี่ยนแบบที่เวอร์ชันเก่าอ่านได้ด้วยOld and new run side by side for a short time. If a deploy changes a database schema, change it in a way the old version can still read.
  • ตั้ง request timeout ให้มากกว่า request ที่นานที่สุดของคุณ มันเป็นทั้งเวลาที่ระบบรอคำตอบจากแอป และเพดานของการรอให้เวอร์ชันเก่าจบงานSet the request timeout above your longest request. It is both how long the system waits for the app's answer and the ceiling of the wait for the old version to finish.
  • ถ้าแอปใช้ จะมีช่วงสั้นเท่าเวลาสตาร์ตหนึ่งครั้งที่ไม่มีใครฟังที่ที่อยู่นั้น ผู้เรียกต้อง retry ระบบคืนที่อยู่ตายตัวให้ตัวใหม่ทีหลังIf the app uses a , there is a gap of about one instance start in which nothing listens at that address, so callers must retry. The system hands the fixed address back to the new instance afterwards.

เชิงอรรถFootnotes

  1. 1ตัวเลขชั่วคราวนี้ไม่มีเพดานตายตัว มันเป็นผลของสูตรจำนวนที่ต้องการ และระบบไม่ได้ตรวจล่วงหน้าว่าเครื่องมีที่พอThis temporary number has no fixed ceiling; it follows from the formula for the number wanted, and the system does not check beforehand that the machine has room. กลับไปที่ข้อความBack to the text
  2. 2เทสนี้ตั้งแอปให้มีได้ตัวเดียวและ request timeout 30 วินาที ตัวเลขในเทสจึงมาจากค่านั้น และเทสเรียกตัวจัดการบนเครื่องตรง ไม่ผ่านขั้น buildThe test sets the app to a single instance and a 30-second request timeout, so its figures come from that; it talks to the manager on the machine directly and skips the build step. กลับไปที่ข้อความBack to the text

เรื่องอื่นในหมวด วิศวกรรมMore in Engineering