site-logo

เบื้องหลังการทำ Live Blog แบบไฟแลบใน 4 ชม

by arnondora on May 14, 2018

หลาย ๆ คนที่เข้ามาในเว็บตอนนี้น่าจะเห็น Feature ใหม่ในเว็บนี้คือ Live Blog และก็คิดอีกว่า นี่ซุ่มทำมาสักพักแล้วใช่มั้ย ? ก็บอกเลยว่า เปล่า !! จริง ๆ แล้วจุดเริ่มต้นที่ทำให้อยากได้ Live Blog ในเว็บของตัวเองคือ ก่อนหน้างาน Google I/O 2018 ที่พึ่งผ่านไป พี่ ๆ GDE ก็มีเว็บที่เป็น Live Blog ที่จะไปเก็บบรรยากาศในงาน Google I/O มาให้ได้ดูกัน พอนี่มาเห็นปุ๊บ ความอยากมันก็เข้าแทรกทันที และทั้งหมดนี้ที่เห็นใน Live Blog คือใช้เวลาทั้งหมดราว ๆ 4 ชั่วโมงเท่านั้น วันนี้เลยจะมาสาวไส้กันว่า กว่าจะมาเป็นหน้าที่เราเห็น ใน 4 ชั่วโมงมันเกิดอะไรขึ้นบ้าง

Design

ก่อนที่เราจะเขียน ถ้าเรายังนึกไม่ออกว่าเราจะเขียนออกมายังไง เราก็จะไม่รู้เลยว่าเราต้องใช้อะไรบ้าง ต้องเขียนอะไรบ้าง ฉะนั้นในขั้นตอนแรก เราก็ต้องมาออกแบบหน้ากันก่อน

ผมก็จะเริ่มจากการต้องคิดก่อนว่า หน้าเราจะต้องแสดงอะไรได้บ้าง

  • Live Post ที่ต้องแสดงเป็นในรูปแบบของเวลา ที่ทำให้เราเห็นได้ว่า Post ไหนมาก่อนหลัง นอกจากนั้นยังต้องแสดงผลได้ทั้ง ข้อความ และ รูปภาพได้ด้วย เพราะ Post ของเรา อาจจะมีทั้งรูปภาพและข้อความได้ แต่ยังไม่เอาวีดีโอได้ นอกจากนั้น Post จะต้องอัพเดทได้เองเมื่อผม Post เพิ่มเข้าไปในระบบ
  • Live Feed ที่ต้องแสดงวีดีโอที่ Live สดจากงานได้ และแน่นอนว่าเราต้องทำให้มันสามารถที่จะดูไปพร้อม ๆ กับ Live Post ได้ ไม่งั้นเวลาคนที่มาเปิดดูก็ต้องเลื่อนไปเลื่อนมาเพื่อดูทั้ง Post และ Live พร้อม ๆ กัน ทำให้ผู้อ่านน่าจะหงุดหงิด
  • ชื่องาน และ สถานะ ที่ต้องแสดงว่า งานนี้คืองานอะไร เรายังอัพเดทอยู่รึเปล่า เพราะถ้าเราไม่มีเนี่ย ผู้อ่านก็จะไม่รู้ว่า Live Blog อันนี้ยังอัพเดทอยู่หรือไม่ งานเริ่มหรือยัง งานจบหรือยัง ทำให้ไม่รู้ว่าควรจะต้องทำยังไงกับหน้านี้ดี

ทั้ง 3 อย่างคือ Component หลัก ๆ ที่เราต้องแสดงให้ผู้อ่านเห็นในหน้า หลังจากนั้น ผมก็มาเริ่มทำแบบกัน โดยเริ่มจากการดูว่า สิ่งที่เราก็จะมาเรียงลำดับความสำคัญกันก่อนว่า สิ่งใดที่ผู้อ่านน่าจะต้องเห็นก่อนหลัง โดยผมให้ลำดับความสำคัญเป็น ชื่องาน > Live Post > Live Feed ตามลำดับ

ในการออกแบบเว็บนี้ผมจะใช้สิ่งที่เรียกว่า Mobile-First นั้นคือ เวลาผมจะสร้างหน้าอะไรก็ตาม ผมจะเริ่ม Design จากขนาดหน้าจอจากเล็กไปใหญ่เพื่อให้เราลดเวลาในการออกแบบลงได้ เพราะเราสามารถคิดในการเลือกที่จะเติมได้ดีกว่าตัด ถ้าเราเลือกออกแบบจากหน้าจอใหญ่ไปเล็ก เราจะต้องคิดถึง Component มากมาย เพื่อให้มันเอามาใส่ในหน้าขนาดใหญ่ได้พอดี แต่ถ้าเราเอาของจำนวนมาก ๆ ไปลงหน้าจอเล็กก็เหมือนกับเราอยู่บ้านหลักเล็ก ๆ แต่ของเต็มไปหมด มันก็ไม่น่าอยู่เท่าไหร่ มากกว่าบ้านที่เล็กเท่ากัน ที่มีพื้นที่ใช้สอยมากกว่า

Low Fidelity Wireframe of Live Blog arnondora.in.th

หลังจากที่เราได้ลำดับการมาก่อนหลังที่เป็น 1D แล้ว เราจะต้องเอาของทั้งหมดมาวางในพื้นที่ 2D กัน โดยใช้ลำดับที่เราได้เรียงไว้เมื่อครู่มาใช้เป็น Reference ก็จะได้แบบภาพด้านบน ลืมบอกไป ในการออกแบบหน้าตรงนี้ผมจะ Draft บน iPad ก่อนนะ

ระยะห่าง การจัดวาง และขนาดของ Component ทุกชนิดในเว็บนี้อย่างที่ได้เคยเล่าไปในตอนที่ออกแบบ PaperTheme ในรอบที่แล้วคือ ทั้งหมดจะถูกออกแบบโดยอาศัย Gestalt Principles และ Fitts’s Law เข้ามาช่วยในการจัดวาง และเว้นระยะห่างให้พอดี (ถ้าใครยังไม่รู้ว่ามันคืออะไร ก็ลองกลับไปอ่าน Post นี้

ทีนี้คือ ที่เรามีคือแปลนที่เป็นขาวดำ เราจะมาลงสีกัน ซึ่งโปรแกรมที่ผมใช้คือ Sketch ที่มีบน macOS และเพื่อให้การ Design ของหน้าเว็บมัน Consistant กันทั้งหมด สีที่เราต้องหยิบมาใช้คือสีฟ้า เพราะเราใช้สีนี้กับทั้งเว็บก็หยิบมาเป็น Primary Colour แต่เว็บเราตอนออกแบบ ได้ออกมาแบบมาใช้สีหลักเพียงสีเดียว ไม่มีสีรองใด ๆ

High Fidelity Wireframe of Live Blog arnondora.in.th

สิ่งที่เราจะได้ออกมาก็คือภาพแบบด้านบนนี้ ที่ Component ได้ถูกจัดเรียงใส่หน้า พร้อมกับลงสีทั้งหมดเป็นที่เรียบร้อย อีกขั้นตอนนึงเพื่อให้ได้ Design ที่สมบูรณ์คือ เอา Component ที่เหลือเช่น Navigation Bar มาใส่ก็เป็นอันจบขั้นตอนนี้

จะจบก็ไม่จบ อีกหนึ่งอย่างที่เราต้องคิดคือถ้าเกิดผู้อ่านต้องรอโหลด หรือ Content ยังไม่มีแบบนี้คือเราจะให้มันแสดงยังไง จากก่อนหน้านี้เราได้ Design หน้าที่ทุกอย่างถูกโหลดขึ้นมาแล้ว แล้วก่อนหน้านั้นละ ระหว่างที่ผู้อ่านรอโหลดหน้า หรือ มันเกิดโหลดแล้วมีปัญหาขึ้นมา เราจะให้มันแสดงอะไร อันนี้ก็ต้องมาเตรียมหน่อย ก็ไม่ได้มีอะไรมาก แค่ทำยังไงก็ได้ให้ Message หรือสิ่งที่เราแสดงออกไปมันเข้าใจได้โดยผู้อ่านของเรา ไม่ Technical มากไปคนจะงงได้ง่าย

นอกจากที่เราจะต้องออกแบบหน้าตาของเว็บแล้ว เรายังต้องมาออกแบบ ข้อมูล ที่จะวิ่งไปวิ่งมาในเว็บนี้ด้วย สำหรับ DB ที่เราเลือกใช้คือ Firebase Realtime DB ที่เป็น NoSQL DB ส่วนเหตุผลก็ลงไปอ่านใน Part ของ Implementation

ถึงแม้ว่ามันจะเป็น NoSQL Schema-less DB แต่เอาจริง ๆ มันก็ต้องมี Schema ที่เป็น Pattern ในการบอกว่า เราจะเก็บอะไรตรงไหน ไม่งั้นเราจะรู้ได้ไงว่าเราจะเก็บอะไร และดูดออกจากตรงไหน ในการ Design ของงานนี้ก็ไม่ยากเลย ถ้าเราลองคิดว่า เรามี Live หลาย ๆ อัน แล้วในแต่ละกันก็มี Detail ของ Live นั้น ๆ และในแต่ละ Live ก็จะมี Post เราก็จะได้ Schema ดังนี้

live :
[
	live_I : {
		posts : [
			post_J {
				post,
				image,
				name,
				timestamp,
			},
		],
		detail,
		live,
		slug,
		status,
		subtitle,
		thumbnail,
		title,
	},
	.
	.
	.
	live_N {},
]

เราก็จะเห็นว่า Schema ที่เราทำออกมามันก็จะไม่มีอะไรเลย ตรง ๆ ไม่อ้อมค้อมใด ๆ

Implement

จากที่เราได้ Design มาแล้วก็ถึงเวลาเอา Design ที่ได้ทำไว้มาทำให้มันเป็นของจริงกัน เราเริ่มจาก เว็บเดิมที่เป็นอยู่นั่นคือ Gatsby ที่เป็น Static Site Generator อยากจะให้โฟกัสที่คำว่า Static นั่นคือ Content ทุกอย่างจะถูกฝังลงมาในไฟล์เลย ไม่ได้ต่อ DB หรือมี Backend ใด ๆ

แต่ Requirement ของเราบอกว่า Post มันจะต้องอัพเดทตลอดเวลาที่ผมกด Post ขึ้นไป ฉะนั้น ต้อง Think out of the box 📦 กันนิดนึง จากเดิมที่เว็บเราจะต้องเอา Content จาก GraphQL บน Local แล้วเอามาเก็บเป็น Props เพื่อให้ React สามารถเรียกได้

ตอนนี้เราจะต้องทำยังไงก็ได้ให้เว็บเราสามารถทำ Dynamic Content ได้ โดยที่ไม่ต้องรื้อเว็บใหม่ที่ง่ายที่สุดที่คิดถึงคือ Firebase Realtime DB 🔥 (เอาจริง ๆ เว็บนี้ก็ใช้ Firebase มันทั้งเว็บแล้วนะ) เพราะเราสามารถที่จะยัด Listener ลงไปในหน้าเว็บเราเพื่อให้เวลามีข้อมูลซึ่งก็คือ Post ใหม่เข้ามาเราก็สามารถเอาข้อมูลมาแล้วมาอัพเดทหน้าเราได้เลย

อีกอย่างคือ Gatsby ก็ใช้ React ในการเขียนและจัดการหน้าทุกอย่างอยู่แล้วซะนั้นการใช้ Firebase Realtime Database ก็จะใช้วิธีเดียวกับที่เราทำ React ปกติเลย ซึ่งก็มี Documents ของ Firebase เขียนวิธีง่าย ๆ มาให้แล้ว ดังนั้นการใช้ Realtime DB นี้ก็จะกลายเป็นเรื่องง่ายไปเลย

ซึ่งมันนำไปสู่ปัญหาต่อไปคือ ถ้าเราให้ Gatsby สร้างโครงหน้าเปล่า ที่ไม่มีข้อมูลอะไรเลย แล้วพอผู้อ่านเปิดขึ้นมาแล้วค่อยโหลดทีหลังมันก็ดูเหมือนจะไม่มีปัญหาอะไรเท่าไหร่กับผู้อ่าน เหมือนกับที่เราเปิดหน้าเว็บที่เป็น SPA (Single-Page Application) ที่เขียนด้วย React แบบปกติ แต่ปัญหามันอยู่ที่เรื่องของ SEO มากกว่า เพราะ Crawler ส่วนใหญ่มันก็ไม่อ่าน Javascript อยู่แล้ว ซึ่งในการดูดข้อมูลมาลงทั้งหมด เราใช้ Javascript ทำให้ Crawler ไม่สามารถอ่านอะไรจากหน้าได้เลย ทำให้หน้าไม่สามารถถูก Craw โดย Search Engine ได้เลย

วิธีการแก้ปัญหาคือ เราจะต้องทำยังไงก็ได้ให้ Crawler สามารถอ่านข้อมูลจากหน้าไปได้ วิธีที่ง่ายที่สุดคือ เราก็ทำเหมือนหน้า Article อื่น ๆ คือเราก็ฝังข้อมูลที่จำเป็นลงไปก่อน เช่น ชื่องาน และรายละเอียดต่าง ๆ ที่เป็นข้อมูลที่เพียงพอเพื่อให้ Crawler สามารถดูดไป Index ได้ ส่วนของที่เหลือ เราก็ดึงจาก Firebase โดยตรงเอา

ดังนั้นเวลาผม Implement ออกมา ผมจะต้องแบ่ง Code ในการดึงข้อมูลจาก Firebase ออกมา ทั้งหมด 2 ครั้งคือ ตอนที่เรา Build เว็บ เพื่อให้เราดึงข้อมูลที่จำเป็นต่อการสร้าง SEO Meta Tags และตอนที่ผู้อ่านเข้ามาในเว็บที่เราต้องดึง Post และข้อมูลอื่น ๆ ที่จำเป็นออกมา

ในการเขียนก็ค่อนข้าง Straightforward เลยละ ก็คือดูดข้อมูลมา แล้วก็เอามาใช้งานต่อตามปกติเหมือนที่เวลาเราใช้งาน Firebase กันปกติเลย เมื่อเราได้ข้อมูลมาแล้ว เราก็เอาไปยัดใส่ Function ที่รับข้อมูลที่พึ่งดึงมาได้ใส่คู่กับ createPage ที่เป็นตัวแปรสำหรับการสร้างหน้าที่อยู่ใน Gatsby

จนถึงตอนนี้เราก็จะได้หน้าที่ Crawler สามารถมาดูดไป Index ได้ในขณะเดียวกันก็สามารถรองรับ Dynamic Content ได้ แต่มันก็มีอีกปัญหาที่ผมค่อนข้างจะเป็นกังวลคือ เรื่องของ Load ที่อาจจะเกิดขึ้น ทำให้มันเกิน Limit ของ Firebase ที่เป็นตัวฟรีไป (ตอนนี้ใช้ Firebase ที่เป็นตัวฟรี) เพราะฉะนั้น การจัดการ Load ที่อาจจะเกิดขึ้นกับ DB จึงต้องลงมาดูหน่อย

จุดแรกที่น่าจะเป็นปัญหาที่สุดคือ Post ที่เวลาเราดึงมา เราอยากให้มันเรียงตามเวลา ซึ่งใน DB เราจะมี Field ที่ชื่อว่า Timestamp อยู่แล้วที่เป็น UNIX Timestamp ฉะนั้นเราสามารถ Order ได้ทันที ถ้าเป็น DBA อยู่แล้ววิธีที่น่าจะง่ายที่สุดคือการที่เราสร้าง Index กับ Timestamp นั่นก็จะทำให้ผู้อ่านสามารถโหลดข้อมูลและเรียง Post ก่อนหลังได้เร็วขึ้น

แต่ผมเลือกที่จะไม่ทำ Index และดึงลงมาทั้งหมดแล้วค่อย Sort ใน Client มากกว่า เพราะไม่อยากให้เกิด Utilisation ตอนที่เราเพิ่มข้อมูลมากนัก เพราะถ้าเราทำ Index เวลาที่เราจะเพิ่มข้อมูลใหม่ DB มันก็จะต้องเอา Post ที่เราพึ่งสร้างใหม่ไปหาที่เสียบที่ถูกต้อง (ไม่รู้ว่าข้างหลังทำงานยังไง สมมุติว่าใช้ Binary Search ก็จะกิน Big-O ไปแล้ว nlog(n) ) นั่นก็จะทำให้มันเกิด Utilisation มากกว่าการเอาข้อมูลไป Append ต่อตูดเฉย ๆ ซ้ำร้ายกว่านั้น Post มันถูกเพิ่มกันในระดับหลักวินาที เผลอ ๆ เราจะเสีย Utilisation มากกว่าที่เราไม่ทำ Index ซะอีก

อีกอย่าง จำนวนครั้งที่ Client จำเป็นต้อง Sort Post จริง ๆ มันก็มีแค่ครั้งเดียวคือตอนที่เปิดหน้าขึ้นมา หลังจากนั้นพอมาข้อมูลใหม่เข้ามา เราก็แค่เขียน Event เพื่อเกี่ยวเฉพาะข้อมูลที่พึ่งสร้างอันล่าสุดถัดไปลงมา Append ลงไปในหัวของ State ที่เราสร้างไว้ และ Re-Render ตรงที่เป็น List ของ Post ก็เป็นอันเสร็จ ทำให้เราการันตีได้ว่า Post ที่ผู้อ่านได้เห็นคือข้อมูลที่เรียงมาแล้วอย่างถูกต้องแน่นอน โดยที่ไม่ต้อง Sort ใหม่และให้ DB Sort ให้ เพราะเราย้อนเวลาไป Post ไม่ได้ เว้นแต่เราจะโกงไปใส่เองใน DB อันนั้นก็จะเป็นอีกเรื่องนึง

แต่ช้าแต่ !!! ใช้ Common Sense พื้นฐานกันว่า หลังงานจบคิดว่า Post จะได้รับการอัพเดทอีกมั้ย คำตอบคือ น้อยมาก ฉะนั้นมันทำให้โอกาสของการ อ่าน ย่อมสูงกว่าการเขียนแบบล้นหลาม เพราะผู้อ่านสามารถกลับมาอ่านย้อนหลังได้ แต่เราคงไม่กลับมาเขียนเพิ่มแน่ ๆ ทำให้การสร้าง Index on Timestamp เลยกลายเป็น Option ที่น่าสนใจขึ้นมาทันที ซึ่งวิธีที่ผมเลือกใช้คือ เมื่อผมกด Status ว่าจบงานแล้ว ตัวเว็บจะยิง Request ผ่าน REST ไปที่ Firebase (อ่านจาก Document ของ Firebase ได้ที่นี่ Managing Firebase Realtime Database Rules via REST - Firebase) เพื่อบอกให้มันสร้าง Index ขึ้นมา กลับเขียนเงื่อนไขไว้ในโปรแกรมหน่อยนึงว่า ถ้า Status ของงานจบแล้ว ก็สามารถใช้คำสั่ง Once แทนที่จะเป็น on เพื่อเรียกข้อมูลทำให้เราไม่ต้องเสีย Concurrent ในเวลาที่เรามีอีก Live อยู่พร้อมกัน และก็ไม่ต้อง Sort Post อีกต่อไป

เพิ่มความแมวเหมียว 😸 ขึ้นไปอีก ว่าถ้า Live Blog ของงาน ๆ หนึ่งมันเก่ามาก ๆ แล้วเราคงไม่กลับไปอัพเดทมันแล้วแน่ ๆ เช่น Google I/O 2015 (ณ ตอนที่เขียนคือ 2018) เราก็มั่นใจว่าเราสามารถฝังข้อมูลทั้งหมดลงในเว็บเลย ทำให้ผู้อ่านไม่ต้องรอโหลดจาก Firebase อีกทอดที่กินเวลามากกว่า ก็เลยเขียนให้ตอน Build เราก็ไปเช็คก่อนว่า ถ้า Post นั้นอยู่ในสถานะ Archive หรือไม่ได้อัพเดทมาสักระยะหนึ่ง มันก็จะเอาข้อมูลของ Post แปะลงไปในหน้าทำเป็น Static Page หน้าโง่วเลย ทำให้ลด Load ของ Firebase เวลาที่มีการเรียกข้อมูลเก่า ๆ ได้และยังทำให้ผู้อ่านไม่ต้องรอข้อมูลโหลดจาก Firebase ที่กินเวลาเยอะกว่าด้วย

คำถามที่หลาย ๆ คนน่าจะถามผมกันในตอนนี้ว่า มันจำเป็นต้องทำขนาดนี้เลยเหรอ กับขนาดเว็บเท่านี้ ? ก็ถ้าเอาจริง ๆ มันก็ไม่จำเป็นขนาดนั้นหรอก แต่ก็นะ อยากทำ ฮ่า ๆ กับเหตุผลจริง ๆ คือผมก็ไม่รู้หรอกว่า วันดีคืนดีเว็บมันจะโหลดพุ่งกระฉูดทำเอาต้องเปลี่ยน Host กันยามค่ำคืน 🌙 ดังนั้นก็ถ้ารู้ว่าปัญหามันอาจจะเกิดขึ้น แล้วไม่ได้กินเวลามากนักก็เขียนกันเหนียวไว้ก่อนก็ดี

ปัญหาที่พบ

สำหรับปัญหาที่พบคือ เรื่องของ Concurrent ที่เข้ามามากเกินไป ณ ตอนที่เขียนแรก ๆ ก็ไม่คิดถึงเรื่องนี้เลย เพราะไม่คิดว่าจะมีคนเข้าเยอะได้เกือบ 200 กว่าคนพร้อม ๆ กันในขณะที่ Firebase ที่เป็นตัวฟรีให้ได้แค่ 100 Concurrent Users เท่านั้น เลยทำให้ผู้อ่านบางคนไม่สามารถดู Post ได้ ตอนนั้นเลยต้องแก้ปัญหาแบบลวก ๆ ด้วย ตอนนี้ก็แก้ปัญหาเฉพาะหน้าด้วย Page Visibility API ที่จะเขียนให้มัน Detach Listener เมื่อผู้อ่านไม่ได้ Focus ที่หน้าต่างของหน้าเว็บ แล้วเมื่อกลับมา Focus อีกครั้งมันก็จะ Attach Listener กลับเข้าไป ทำให้มันเกิดการสลับกันเข้ามาอ่านข้อมูลออกไป และอีกวิธีนึงที่ต้องอาจใช้ในอนาคตคือ เงิน 💰 โดยอัพเกรด Plan เป็น Blaze Plan ที่เป็น Pay as you go เพื่อขยาย Concurrent เป็น 100K ซึ่งเว็บฉันก็ไม่น่าถึงแน่นอน ฮ่า ๆ

สรุป

ทั้งหมดนี่เกิดจากอารมณ์ชั่ววูบทั้งสิ้น… งานอื่นยังกองอยู่ท่วมหัว 😱 ไม่รู้เอาเรี่ยวแรงจากไหนมานั่งทำยาว ๆ ขนาดนี้ แต่ก็แหละนะ ความอยาก มันทำได้ทุกอย่าง ตั้งแต่เสียเงิน ยันอดนอน ฮ่า ๆ อีกอย่างคือช่วงนี้ไม่ได้ลงมาเขียนโปรแกรมที่เป็นโปรแกรมที่ใช้งานจริง ๆ มาสักพักใหญ่ ๆ แล้วมันก็จะอึน ๆ และไฟลุกหน่อย และบัคก็จะเยอะหน่อย ๆ ถ้าเจอก็ทักมาหน่อยจะเป็นบุญมาก หรือเข้ามา Contribute ใน Github Repository ของเว็บนี้ได้

Share this article to social network?
มาลองใช้ StaticQuery ใน Gatsby v2 กันBuilding Blazing-fast static site with Gatsby : Styling a Component EP.2Building Blazing-fast static site with Gatsby : Introduction EP.1How to Hide Your Porn and Sensitive Data

Leave a comment?

Name :
Comment :
Post a comment
Loading Comment(s)
FacebookTwitterGoogle+Email
© 2014-2018 Arnon Puitrakul all right reserved.Code with by @arnondora