If God Built Netflix

ผมพยายามเขียนบล็อคนึงมาหลายปีแต่ก็ไม่เคยตกผลึกเขียนจบสักที

section ใหญ่ๆ ในบล็อคนั้นคือ ถ้าพระเจ้าสร้าง Netflix มันจะ design ระบบมายังไงกันนะ

เพราะในช่วงนั้น Microservice กำลังอยู่ในช่วงบูม gut feeling ผมบอกว่า microservice มันไม่ถูก แต่ไม่รู้ว่าที่ถูกต้องทำยังไงเลยไปนั่งคิดอยู่ว่าถ้าออกแบบระบบโดยมีเวลาไม่จำกัด มีเงินไม่จำกัด และทีมพัฒนาเทพมากไม่ต้องกังวลว่าจะเกิด feature creep มันจะออกมาหน้าตายังไง


ช่วงนี้ได้กลับไปดูคนอื่นแก้ module ที่เคยถือก่อนหน้านี้

module นี้ผมก็อยู่ในทีมก่อตั้งเลย แต่ตอนนั้นดูฝั่ง client side เลยไม่ค่อยรู้เรื่องว่ามัน design มายังไง จนตอนหลังมาปะติดปะต่อภาพแล้วเข้าใจว่าไอเดียของมันคือจะมี API กลางตัวนึงซึ่งมันแทบจะเป็น REST API ให้ database เลยแหละ แล้วก็ handle authn/authz ต่างๆ และ API นี้จะไม่รู้จัก module ที่เป็น UI เลยจะรู้จักแต่ module ที่ provide service เพื่อ implement feature ของมันเท่านั้น

ในฝั่ง UI ด้านนอกก็จะทำหน้าที่แทบจะเป็น API Gateway แค่นั้นไม่ได้มี logic อะไรเพิ่มนอกจาก map field ต่างๆ

ปัญหาก็คือบางครั้ง UI อยากจะทำ push notification เมื่อ object change ซึ่งเนื่องจาก API กลางจะไม่รู้จักกับ UI module เลยจะ push ไป trigger ไม่ได้ ก็ต้องส่ง message เข้าไปใน message queue ว่ามีการ change แล้ว แล้วใครอยากได้ก็อ่านไป ใน message เองก็จะไม่ได้ระบุว่า change อะไรบ้างส่งมาแต่ snapshot ของ object ที่เวลานั้นๆ

Design นี้ถ้าเคยศึกษา Kubernetes ก็น่าจะพอ map ได้เลยว่า API กลางเหมือน kube-apiserver แล้ว UI ก็คือพวก controller/operator ต่างๆ ซึ่งก็ถือว่าทันสมัยมากเพราะในตอนนั้น Kubernetes ยังเป็นของใหม่มากๆ และคิดว่าทีมก็ยังไม่ได้ศึกษา design ของ Kubernetes มาก่อน แต่มันก็เป็นไปตามหลัก microservice design

ที่จะแตกต่างกันคือ Kubernetes จะมีส่วนเก็บ status ของ object ที่ให้ controller สามารถเขียน state กลับไปได้ ทำให้ controller ทุกตัวเป็น stateless ได้หมด แต่โปรแกรมนี้ไม่ได้คิดไปถึงตรงนั้น

ฟีเจอร์แรกๆ ที่ต้องยิง callback ผมจำได้ว่าใช้วิธีเปิด Redis ของ UI module ตัวนั้นมาเก็บ state ส่วนตัว พอมาฟีเจอร์ที่สองบนอีก UI module หนึ่ง คนที่ออกแบบก็เลือกจะเอา API กลางยิงออกไป 3rd party แทนแล้ว ผมก็ยังไม่แน่ใจว่าทำไมเลือกใช้ design แบบนี้ในตอนนั้น

หลังจากนั้นแล้วผมก็ทิ้งหายจาก module นี้ไปนาน ทีมที่เขียนก็แยกย้ายกันไป บางคนก็ยังอยู่ในบริษัทแต่ทำหน้าที่อื่นๆ แล้ว จนกระทั่งผมได้กลับไปดู UI module ตัวนึงซึ่งทีมปัจจุบันไม่มีคนดู ปัญหาใหญ่ๆ ที่ผมเห็นคือมัน scale ไม่ได้และห้าม restart ด้วยเพราะมันเก็บ state ใน memory ตอนนั้นถึงเข้าใจว่า design ของระบบจริงๆ เค้าคิดอะไรอยู่

หนึ่งปีให้หลัง ทีมปัจจุบันเข้ามาแก้เพิ่มฟีเจอร์ใน UI module นั้น ผมรีวิวแล้วก็เพิ่งรู้ตัวว่า design ของระบบนี้มันเปลี่ยนไปเยอะมากเพราะทีมใหม่คุ้นชินกับการที่ทำฟีเจอร์ใหม่เป็น explicit เกือบหมด อยากได้อะไรก็ยิงไปบอกคนนั้นว่าให้ทำแบบนี้ ซึ่งมันเขียนง่ายขึ้นเยอะแต่ service ก็จะพันกันหมด


ถามว่าถ้าตอนนั้นไปลอก design แบบ Kubernetes มาคือแก้ให้ API กลางช่วย module อื่นเก็บ state ได้มันจะแก้ปัญหานี้มั้ย?

ผมก็ยังคิดว่าปัญหามันยังไม่จบนะ ถ้าดูจาก Kubernetes เองก็จะเห็นว่า API บางอย่างมันจำเป็นต้องมี flow explicit action จริงๆ เช่นขอดู log ที่ถ้าทำเป็น implicit แล้ว kubelet ต้อง stream log ไปที่ API server ตลอดเวลา หรือ API server ต้องมี Kubelet log URL เพื่อให้ client ไปยิงเป็น explicit กันเอง ก็เป็นปัญหา network security อีก และอย่างคำสั่ง exec นี่คงจะไม่มีทางเลี่ยง ยังไงก็ต้องเปิด channel ตรงๆ

นอกจากนี้ Kubernetes design ยังทำให้เกิด traffic ในการ polling เยอะ ก็จะเห็นว่า Kubernetes เริ่มติด scaling limit ระดับหลายพัน node ที่ API Server กินทรัพยากรจำนวนมาก

สำหรับ project ที่เล่าไปนี้ design ปัจจุบันที่ยิง action ตรงๆ ผมว่ามันเป็นอะไรที่ตรงไปตรงมา น่าเบื่อ แต่มันก็เหมือนจะเป็น system design ที่ผ่านการพิสูจน์มานานแล้ว ถ้าไม่กังวลปัญหาใหม่ๆ อย่าง microservice design ก็ดูเหมือนจะเป็น solution ที่น่าสนใจ


ผมเล่าเรื่องถ้าพระเจ้าสร้าง Netflix ลงใน Facebook อาจารย์ผมมาอ่านแล้ว comment ว่า

“ถ้าจะแก้ทีนึงก็ต้องสวดชุมนุมเทวดาก่อน”

ผมขำอยู่ปีนึงถึงจะเข้าใจความหมายที่อาจารย์จะสื่อ

Some History of On Screen Graphics

นั่งเขียน RuneKit บน OS ต่างๆ แล้วต้องไปจับ API low level ของ display server พอสมควร เลยนั่งอ่านเพิ่มแล้วรู้สึกว่าภาพบนจอคอมพิวเตอร์ที่เราใช้มันแทบจะคือต้องให้ดาวเรียงตัวกันถึงจะใช้งานได้เลยนะ แต่มันกลับ reliable มากๆ

Windows 98

สมัยก่อนการ capture หน้าจอใน Windows XP หรือเก่ากว่า ถ้ามีโปรแกรมซ้อนหน้าต่างอยู่จะสังเกตว่า capture แล้วจะได้ภาพหน้าต่างที่ทับกันอยู่ด้วย เหมือนกับที่เราเห็นบนหน้าจอ รวมถึงโปรแกรมอัดวิดีโอต่างๆ ก็จะเห็นว่าไม่สามารถทำ window capture ได้เหมือนระบบสมัยใหม่

ที่เป็นแบบนี้เพราะใน OS เก่าๆ นั้นโปรแกรมทุกตัวจะวาดภาพที่เราเห็นอยู่บนหน้าจอไปยังภาพเดียวกัน เมื่อเรา capture หน้าจอก็คือการเอาภาพนี้ออกมาเป็นไฟล์กราฟฟิคแล้ว crop ให้พอดีกับขนาดของหน้าต่างที่เราต้องการเท่านั้น ไม่มีภาพหน้าต่างส่วนอื่นๆ อีก

ปัญหาของการวาดเป็นภาพเดียวคือในกรณีที่มี 2 หน้าต่างวางซ้อนทับกันอยู่ เมื่อเราย้ายหน้าต่างด้านบนหลบไปแล้วเราจะไม่ทราบว่าภาพที่ถูกบังอยู่เป็นภาพอะไร ระบบก็ต้องส่งสัญญาณขอให้โปรแกรมที่อยู่ด้านล่างนั้นวาดภาพขึ้นมาใหม่ด้วย รวมถึงหน้าต่างด้านบนก็ต้องวาดที่ตำแหน่งใหม่เช่นกัน เราจึงจะเห็นว่าการย้ายหรือปรับขนาดหน้าต่างใน OS รุ่นเก่านั้นจะมีอยู่สองประเภทคือ

  • แบบเร็วจะเห็นแต่กรอบตอนขณะที่ลาก แล้วจึงค่อยปรากฏภาพหน้าต่างเมื่อปล่อยเมาส์
  • แบบช้าคือบอกให้ทั้ง 2 หน้าต่างวาดภาพใหม่ในขณะที่ลากด้วย ซึ่งอาจจะเห็นอาการกระตุก

ถ้าหากระบบส่งสัญญาณให้หน้าต่างด้านหลังวาดใหม่แล้วแต่โปรแกรมนั้นค้างไม่ยอมวาดใหม่ ระบบก็จะค้างภาพหน้าจอพิกเซลนั้นไว้ จึงเป็นสาเหตุที่ทำให้เราวาดภาพทับโปรแกรมที่ค้างได้

อีกปัญหาหนึ่งของระบบสมัยก่อนคือ video ต่างๆ ที่เราไม่สามารถใช้ print screen จับภาพได้ หรือ game ก็ตาม และถ้าซวยใหญ่ก็คือวิดีโอไม่เล่นกลายเป็นสีเขียวๆ แบบนี้

เหตุผลก็คือคอมพิวเตอร์มักจะไม่แรงพอที่จะถอดรหัส video บน CPU ได้ หรือถึงจะแรงพอแต่ CPU ก็จะ 100% (ลองเปิด YouTube 4K ดูตอนนี้ก็ได้) ซึ่งคอมก็จะช้าทำอย่างอื่นไม่ได้อีก ดังนั้นการจะถอดรหัสวิดีโอต้องไปทำในชิพเฉพาะเช่นการ์ดจอแทน หลังจากถอดแล้วภาพก็จะแสดงผลบนหน้าจอทันทีไม่ผ่านเข้าไปในแรมที่พักภาพอีก เราจึงไม่สามารถ capture ภาพออกมาได้ รวมถึงโปรแกรมเล่นวิดีโอเองก็จะเห็นว่าไม่มีตัวไหนในสมัยนั้นที่เอา video control ไปวางทับภาพ

แต่การ์ดจอรู้ได้ยังไงว่าต้องวาง video ไว้ตรงไหน? โปรแกรมที่จะเล่น video นอกจากส่งวิดีโอไปแล้วยังต้องบอกตำแหน่งที่ต้องการวางภาพด้วย ปัญหาก็คือถ้ามีหน้าต่างวางซ้อนวิดีโออยู่ก็จะถูกวิดีโอแสดงทับ

วิธีแก้ไขก็พื้นๆ คือใช้ green screen อย่างที่เห็น แล้วบอกว่าที่ตำแหน่งนี้ถ้ามีสีเขียวขอให้แสดงเป็นภาพวิดีโอ (หรืออาจจะเป็นสีอื่นๆ)

X11

ใน Linux หรือ Unix ใดๆ นั้น เนื่องจากระบบออกแบบมาให้ใช้งานหลาย user พร้อมๆ กันจึงไม่สามารถให้โปรแกรมวาดภาพหน้าจอเองได้เพื่อความปลอดภัย การทำงานจึงเป็นรูปแบบของ client-server โดยที่โปรแกรม X Server จะควบคุมหน้าจอ แล้วโปรแกรมอื่นๆ เชื่อมต่อเข้ามาใช้งาน

การวาดภาพต่างๆ บนหน้าจอบน X server นั้นโปรแกรมต่างๆ ไม่มีสิทธิ์เข้าถึงหน้าจอจึงจะต้องส่งคำสั่งวาดมาที่ X server แล้ว X server จึงจะวาดให้ โดยโปรโตคอลที่ใช้งานก็เป็นรูปแบบ binary request-response ก็ไม่แน่ว่าถ้ามันเพิ่งคิดค้นมาในยุคนี้มันอาจจะเป็น gRPC ไปแล้วก็ได้เหมือนที่ Docker เป็น HTTP over Unix socket

ข้อดีอย่างหนึ่งของการที่มันเป็น client server ก็คือทั้งสองฝั่งไม่จำเป็นต้องอยู่บนเครื่องเดียวกันก็ได้ เรียกว่า X forwarding คือเราสามารถ ssh ไปอีกเครื่องหนึ่งและ forward X socket บนเครื่องไปด้วย แล้วรันโปรแกรมบนเครื่องนั้นก็จะปรากฏภาพหน้าจอบนเครื่องของเรา อย่างตัวอย่างในภาพคือ X server ที่รันใน Windows 7 แต่ว่าแสดงหน้าจอของโปรแกรมบน Unix

ทั้งนี้เนื่องจาก X protocol นั้นไม่ได้ออกแบบมาให้ใช้งานข้ามเครื่องกันนอกเหนือจากภายใน LAN เดียวกันจึงยังจำเป็นต้องใช้โปรโตคอล remote desktop ต่างๆ ที่มีการบีบอัดสำหรับการใช้งานผ่านอินเทอร์เน็ต แต่จากที่เห็นในภาพด้านบนก็จะเห็นว่าบน X forwarding เราเห็นหน้าต่างเป็นหน้าต่างจริงๆ สามารถ minimize แยกกันได้อย่างอิสระต่างกับ remote desktop ที่เห็นเป็นภาพหน้าจอไม่ใช่แยกหน้าต่างกัน

Quartz

ตอนที่ Apple จะสร้าง OS X ในรุ่นแรกๆ ก็ใช้ X11 แต่ว่า Apple อยากได้ระบบกราฟฟิคแบบล้ำๆ ก็เลยเลือกที่จะทำระบบขึ้นมาใหม่เองเรียกว่า Quartz

ไอเดียของ Quartz อยู่ที่ว่าโปรแกรมไม่เข้ามาวาดบนหน้าจอเองแล้ว แต่ระบบจะมีพื้นที่วาดส่วนตัวให้แต่ละแอพวาดโดยใช้ Quartz 2D หรือ OpenGL แล้วโปรแกรม Quartz Compositor จะ copy ภาพของแต่ละแอพมารวมกันเป็นรูปสุดท้าย

ดังนั้นมันจึงแก้ปัญหาของระบบวาดลงหน้าจอแบบเก่าได้ทุกข้อ !!

  • เราสามารถแคปจอหน้าต่างเดียวได้แล้ว เพียงแค่ไปขโมยภาพของโปรแกรมนั้นๆ ออกมา
  • เวลาลากหน้าจอไม่มีการกระตุกใดๆ ทั้งสิ้นเพราะ Quartz Compositor แค่เปลี่ยนที่ paste รูปให้ถูกจุด

ข้อเสียคือมันต้องก๊อปรูปไปมาหลายรอบ เปลือง CPU แทน ก็กระตุกอยู่ดี

ใน OS X 10.2 Quartz Compositor ย้ายไปรันบน OpenGL เรียกว่า Quartz Extreme ทำให้กระบวนการ copy ต่างๆ ลื่นไหลไม่มีสะดุด ไม่กิน CPU เพราะการ์ดจอมันสร้างมาเพื่องานนี้โดยเฉพาะ

บน OS X 10.4 ระบบวาดหน้าจอ Quartz 2D ย้ายมารันบนการ์ดจอเช่นเดียวกัน ทำให้ระบบกราฟฟิคของแมคใช้การ์ดจอเร่งความเร็วได้ทุกส่วน

Windows Vista

หลังจาก Apple มี Quartz ในปี 2001 แล้ว ในปี 2007 Microsoft ก็หันมาใช้ composite บ้างใน Windows Vista ที่ผู้ใช้งานอาจจะรู้จักกันในชื่อ Aero โดยโปรแกรมที่ทำหน้าที่แทน Quartz Extreme บน Windows เรียกว่า Desktop Window Manager (DWM)

ลูกเล่นต่างๆ ใน Aero ที่เราเห็นนั้นก็ทำได้เพราะ Compositing ทั้งนั้น เช่น

  • กรอบหน้าต่างโปร่งใส (ใช้การ์ดจอเบลอภาพ)
  • Aero flip (เอาภาพหน้าจอมาหมุน)
  • Thumbnail บน Taskbar เวลาชี้หน้าต่าง
  • Alt-Tab แล้วมี Thumbnail ของโปรแกรม
  • Fade หน้าต่างออกเวลาทำ Aero peek

จุดที่น่าสนใจคือในยุคนี้เกมเริ่มมีโหมด Borderless Fullscreen ซึ่งหลายๆ คนชอบใช้งาน ในโหมดนี้ภาพของเกมจะวาดนอกหน้าจอเหมือนโปรแกรมอื่นๆ แล้ว DWM ค่อยคัดลอกมาแสดงผลบนหน้าจอ ทำให้สามารถสลับโปรแกรมได้อย่างรวดเร็ว

ข้อเสียคือเนื่องจาก DWM เป็นคนกำหนดการตั้งค่าของหน้าจอทำให้เกมไม่สามารถปรับค่าได้ เช่น Resolution, Refresh rate และแม้เกมจะรันเร็วแค่ไหนแต่จะถูกจำกัดโดยเฟรมเรตของ DWM แทน ซึ่งจะต่างกับ mode full screen จริงๆ ที่ตัวเกมสามารถควบคุมทั้งหน้าจอได้เลย สามารถกำหนดอะไรก็ได้ แต่พอเรา tab กลับมาที่ desktop ปกติแล้วระบบจะต้องเสียเวลาปรับ setting ต่างๆ กลับให้เหมือนเดิม

Compiz

แค่หน้าต่างโปร่งใสยังถือว่าเด็กน้อย เมื่อ Composite มาถึง X11 ในปีเดียวกัน

Compiz น่าจะเรียกได้ว่าสุดยอดแห่งอาหารตาชนิดที่ว่าคนใช้ OS อื่นๆ ยังต้องยอมย้ายมา Linux เพื่อจะลองเล่น ไม่ว่าจะเป็นหน้าต่างพับเป็นจรวดตอน minimize, โย้เย้เป็นเจลลี่ตอนย้าย ไปจนถึง Desktop หมุนเป็นลูกบาศก์

Welcome to the future with GPU

นี่คือเหตุผลที่ชอบศึกษา OS มากๆ เพราะมันอธิบายปรากฏการณ์ในชีวิตประจำวันได้ดีมากๆ

สมัย Windows Vista ออกมาแล้วบอกว่าพิมพ์งาน Word ต้องซื้อการ์ดจอ ตอนนั้นก็ว่าบ้า กินสเปคเครื่องโคตรๆ แต่มันไม่มีใครบอกเลยนะว่ามันคือจุดจบของ Fraps แล้วที่เราจะสามารถอัดหน้าจอด้วยโปรแกรมอะไรก็ได้

ก็ไม่แน่ว่าในอนาคตพิมพ์สูตรใน Excel อาจจะต้องอัพการ์ดจออีกแล้วนะ