กว่าจะมาเป็น BKK Election 2022
Monorepo, Tech Stack, กระบวนการทำงาน และสิ่งที่ได้เรียนรู้ ✨
สวัสดีครับ ลูกคิดครับ เป็น Developer อยู่ที่ PunchUp และเป็นหนึ่งในทีมดูแลหลักของโปรเจค WeVis ที่ทำเรื่องเกี่ยวกับการเมืองและ Civic Tech ของไทย
วันที่ 14 มีนาคม 2565 กกต. ได้ประกาศให้วันที่ 22 พฤษภาคม เป็นวันเลือกตั้งผู้ว่ากรุงเทพฯ และนั่นเองก็เป็นวันที่ผมได้รู้ว่า เรามีเวลาในการทำเว็บให้ข้อมูลเกี่ยวกับการเลือกตั้งและรายงานผลคะแนนในเวลา 2 เดือนกับ 1 สัปดาห์เท่านั้น
นั่นจึงเป็นจุดเริ่มต้นของการรวมทีม BKK Election 2022 เฉพาะกิจของพวกเรา ที่ประกอบด้วย Project manager 1 คน, Designer 3 คน, Developer 6 คน (ซึ่งเป็น part-time 2 คน intern 1 คน และอาสามาช่วยทำนอกเวลางานอีก 3 คน) ที่รับผิดชอบเรื่องเว็บไซต์ตรงๆ และยังได้ทีม The Standard และ Wisesight มาช่วยทำเรื่องข้อมูลเกี่ยวกับการเลือกตั้งอีกแรง
เป็นอะไรที่ท้าทายมากในระยะเวลาและกำลังคนที่เรามี และเงื่อนไขตรงนี้ก็มีผลอย่างมากในการเลือก Tech Stack ต่างๆ ของเรา เวิร์คบ้างไม่เวิร์คบ้าง ผมเลยอยากเขียนถอดบทเรียนเอาไว้ เพื่อเป็นกรณีศึกษาสำหรับงานต่อๆ ไปในอนาคต
สำหรับใครที่ยังไม่เคยเห็นงาน BKK Election 2022 ของพวกเรา สามารถเข้าไปชมได้ทางนี้ครับ
ตัวบทความค่อนข้างยาว โดยจะแบ่งเป็นส่วนๆ ตามนี้ครับ
- 🍱 I: Architecture and Tech Stack
- 👨👩👧👦 II: Too much work? Divide and conquer
- 🦥 III: Too much data? Optimize load time
- 🫣 IV: We write no test, and some dirty code too!
- 🚨 V: The Incidents: A single point of failure
- 🌈 VI: The Conclusion
- 🏃♂️ Extra: We have a bright future, but pushing civic-tech is like running a marathon
🍱 I: Architecture and Tech Stack
เราเลือกใช้ monorepo เพราะมันช่วยให้เราแบ่งเป็นโปรเจคย่อยๆ ที่แยกออกจากกันได้ชัดเจน แต่ละโปรเจคสามารถใช้คนละ framework ตามที่คนทำถนัด และส่วนที่จำเป็นต้องแชร์กันก็แยกออกมาเป็น package ได้ ซึ่งทั้งหมดจะอยู่ใน repository เดียวกัน
The Monorepo
เราใช้ Yarn Workspace และ Turborepo ในการช่วยจัดการ monorepo ซึ่งภายในจะประกอบด้วย
Apps
คือกลุ่มของ Static site application ที่จะถูกรวมเป็นเว็บเดียวกันในภายหลัง ได้แก่
- Landing (SvelteKit) คือหน้าหลักและหน้าเกี่ยวกับโปรเจค
- Candidate (NextJS) คือส่วนที่เกี่ยวกับข้อมูลของผู้สมัครผู้ว่ากทม. และ สก. ส่วนนี้เราได้ความช่วยเหลือด้าน content จากทีม The Standard ที่ช่วยทำข้อมูลและวีดีโอบทสัมภาษณ์แบบเท่ๆ เราใช้ NocoDB ซึ่งเป็น Spreadsheet ที่มาพร้อมกับ API ในตัว (เจ้าตัวบอกว่าเป็น Airtable open source alternative) ช่วยให้ทีม content สามารถเพิ่มข้อมูลลงในตารางได้ง่ายๆ และทางทีม dev ก็ดึงข้อมูลผ่าน API มาใช้ได้เลย
- Social Trend (NuxtJS) คือส่วนที่นำข้อมูล engagement ในโลกออนไลน์มา visualize ให้เห็นความเปลี่ยนแปลงในแต่ละช่วงเวลา ส่วนนี้เราได้รับความช่วยเหลือจาก Wisesight ที่เตรียม API สำหรับข้อมูล engagement ไว้ให้เป็นอย่างดี
- Map (React/Preact) คือส่วนแสดงผลการเลือกตั้งของในอดีต, โพล, คะแนน real-time และคะแนนอย่างเป็นทางการ ผ่าน visualization ในรูปแบบต่างๆ ในส่วนของผลเลือกตั้งแบบ real-time นั้น เราตั้ง fetch interval ไว้เพื่อดึงข้อมูล JSON ที่เราเตรียมไว้บน Election data server ในทุกๆ 15 วินาที (จะมีอธิบายต่อในส่วนถัดไป) Map เป็น app เดียวที่ไม่ได้ใช้ Static Site Generator (SSG) แต่เราเลือกใช้ Single Page App (SPA) ไปเลยเพราะว่าเป็นส่วนที่มีความ dynamic สูงมาก ขึ้นอยู่กับข้อมูลการเลือกตั้งที่จะโหลดขึ้นมาทางฝั่ง client
Packages
ส่วนที่ถูกแชร์กันระหว่าง app ได้แก่
- WordPress API เป็น Typescript library ที่เขียนเพื่อใช้ fetch บทความจาก Wordpress REST API ของ The Standard และ WeVis
- Tailwind เป็น Tailwind config ตาม design system
- UI เป็น Web Component library เช่น navigation bar, footer และ sharer ผมเลือกใช้ Web Component ที่นำไปใช้กับ framework ใดๆ ก็ได้ ข้อเสียคือมันจะต้องถูก render ฝั่ง client side ทำให้เสีย performance และ SEO เล็กน้อย ผมเลือกใช้ SolidJS เขียน เพราะ support web component, ใช้ JSX ที่คุ้นเคย และ bundle size เล็กมากเมื่อเทียบกับตัวเลือกอื่น
สังเกตว่าผมใช้ Vite เป็น build tool ตัวหลัก เพราะมันเร็วและง่ายมาก
Moderator
ส่วนสำคัญที่ร้อยเรียง apps, packages และ static assets (เช่นพวกรูปภาพ) ให้เป็นเว็บเดียวกัน ซึ่ง moderator ทำงานใน 2 โหมด
- Development mode เวลา dev รันในเครื่องตัวเอง Moderator คือ ExpressJS ที่ทำหน้าที่เป็น reverse proxy ส่ง request แต่ละ path ไปยัง dev server ของแต่ละ app เช่นถ้าเราเข้า / มันก็จะส่งเราไป Landing (SvelteKit) แต่ถ้าเราเข้า /candidate มันก็จะส่งเราไป Candidate (NextJS)
- Build mode เวลาเราต้องการ build static site เพื่อขึ้น staging หรือ production Moderator คือ script ที่ copy build output จากแต่ละ app และ package ไปวางไว้ใน path ที่ถูกต้องเพื่อรวมกับเป็น static site เดียวกัน เช่นของจาก Landing จะถูกวางไว้ที่ /build/ และของจาก Candidate จะถูกวางไว้ที่ /build/candidate/ เพื่อเราจะสามารถนำโฟลเดอร์ build ไปวางไว้ที่ static site hosting ได้ทันที
และจิ๊กซอว์ส่วนสุดท้ายก็คือ Turborepo ครับผม Turborepo คือคน(?)ที่เข้าใจ dependency ของแต่ละ project และส่งคำสั่งลงไปยังโปรเจคนั้นๆ ตามลำดับที่เหมาะสม ด้วย config ที่ไม่ซับซ้อน เช่นถ้าสั่ง turbo run build ตัว Turborepo จะสั่งให้ build พวก packages ก่อน จากนั้นก็จะสั่ง build พวก apps และสุดท้ายสั่ง moderator ให้นำ build output ทั้งหมดมารวมกัน นอกจากนี้ caching มันยังเทพมาก ถ้าโปรเจคไหนที่ไม่จำเป็นต้อง build ใหม่ มันก็จะดึง cache มาใช้ ประหยัดเวลาและทรัพยากรไปได้เยอะ
สามารถเข้าไปส่อง monorepo ของเราได้ที่นี่ครับ
Deployment
เรา deploy ของที่เกี่ยวกับโปรเจคไว้ทั้งหมด 3 ที่ครับ
- Staging server ทุกครั้งที่มีการ push ขึ้นไปบน Github Repository เราตั้ง Github Actions ไว้ให้ build code และ deploy ลง Github Pages เป้าหมายของ staging environment คือการเป็นพื้นที่ในการให้คนในทีมได้เห็นความคืบหน้าของงาน ณ ขณะปัจจุบัน เพื่อช่วยกันตรวจสอบก่อนปล่อยงานออกสู่สาธารณะ
- Production server คือ server ของฝั่ง WeVis ที่เป็น Monolith ข้างในประกอบด้วย WeVis Wordpress, NocoDB, Plausible (เราใช้แทน Google Analytics เพราะมัน GDRP/PDPA Compliance) และ Caddy ที่ทำหน้าที่เป็น reverse proxy และ static file server สำหรับโปรเจคต่างๆ ของ WeVis เราใช้ AWS Lightsail ซึ่งเป็นบริการ Virtual Private Server (VPS) ราคาไม่แพงของ Amazon ร่วมกับ Cloudflare
- Election data server คือ Server ที่มี Election data update service (เขียนด้วย Node-cron และรันบน Docker) คอยดึงข้อมูลเลือกตั้งแบบ real-time จาก Election Result System ที่พัฒนาโดย Vive Digital และแปลงเป็น JSON ก่อน serve ผ่าน Caddy นอกจากนี้ เรายังมี JSON สำหรับ config preset ของคะแนนเลือกตั้งอีกทีเพื่อให้เพิ่มลด preset ได้ตามสถานการณ์โดยไม่ต้อง deploy เว็บใหม่ เช่นก่อนเลือกตั้งจะมีแค่ผลในอดีต แต่ในวันเลือกตั้งจะมีผลโพลและคะแนน real-time เข้ามาเพิ่ม ในส่วนนี้เรา ตั้ง caching ของ Caddy และ Cloudflare ให้ cache JSON ไว้ 15 วินาที เพื่อช่วยลด request ที่จะถูกยิงเข้า server เราตรงๆ ในขณะที่ข้อมูลก็ยังคงความ real-time ในระดับหนึ่ง ตรงนี้ต้องกราบขอบพระคุณทีมที่เคยทำ ELECT Live ที่เขียนบทความเกี่ยวกับเรื่องนี้ไว้
เนื่องจากทางกทม. ไม่มี Official API สำหรับการเปิดเผยข้อมูลคะแนนเลือกตั้งแบบ real-time สมาคมโทรทัศน์ระบบดิจิทัล (ประเทศไทย), สมาคมผู้ผลิตข่าวออนไลน์, องค์กรวิชาชีพ, สถาบันวิชาการ, มหาวิทยาลัยต่างๆ ตลอดจนองค์กรภาครัฐและเอกชน จึงการร่วมกันจัดหาอาสาสมัครร่วมรายงานผลการนับคะแนนการเลือกตั้งผู้ว่าฯ กทม. และ ส.ก. อย่างไม่เป็นทางการจากหน้าหน่วยเลือกตั้ง จำนวน 2,500 หน่วยเลือกตั้งใน 50 เขต หรือคิดเป็น 36.67% ของหน่วยเลือกตั้งที่มีอยู่ทั้งหมด 6,817
Fun Fact: Dev is not needed for everything
จริงๆ แล้วเรายังมี project ย่อยอีกอันนึงที่ทำในช่วงเดียวกันคือ WeVoteBKK: (ไม่ใช่) คน กทม. ขอเลือกด้วย ที่เป็นโพลให้คนต่างจังหวัดมาลองโหวตกันว่าอยากได้ผู้ว่าเป็นใคร แต่เนื่องจากเรามี dev ไม่พอแล้ว ทีม designer และ content เลยทำเองซะเลย โดยใช้ no-code platform อย่าง WebFlow ในการทำ ซึ่งมันก็ออกมาดูดีมาก แถมยังเป็นหนึ่งใน project ที่ popular ที่สุดด้วย สามารถเข้าไปดูผลลัพธ์ได้ที่ https://www.facebook.com/wevisdemo/posts/164669142691731
👨👩👧👦 II: Too much work? Divide and conquer
ยิ่งเป็นงานที่ใหญ่และมีเวลาจำกัด การแบ่งงานเป็นอีกประเด็นที่สำคัญมาก ในช่วงเวลา 2 เดือนและ 1 สัปดาห์ เราแบ่งงานออกเป็น 5 ช่วงหลักๆ
- Setup ผม config monorepo, packages, moderator และ CI/CD ต่างๆ เพื่อเตรียมพร้อมให้เพื่อนๆ มาช่วยทำ app ต่างๆ ต่อไปได้ในระหว่างที่ทีม designer และ content เตรียม UI บน Figma และข้อมูลต่างๆ นานา
- Pre-election release 1 Landing, Candidate และ Social Trend จะถูกปล่อยออกไปก่อน ในช่วงนี้เราแบ่งงานกันง่ายๆ 3 คน คนละแอพทำขนานกันอย่างอิสระ และเลือก framework ที่อยากใช้ได้ตามถนัด ผมทำ Landing ด้วย SvelteKit เพชรทำ Candidate ด้วย NextJS และพี่แมกซ์ทำ Social Trend ด้วย NuxtJS โดยเราใช้ Slack และ Staging environment เป็นช่องทางหลักในการสื่อสารกันในทีม
- Pre-election release 2 เราต้องปล่อย Map ในเวอร์ชั่นที่เป็นผลเลือกตั้งของปี 2556 ในอดีต ในส่วนนี้เรา dev กัน 4 คน ผม ฟีน และพี่บาสช่วยกันทำ front-end ซึ่งเราเขียนด้วย React ที่ทุกคนสะดวก (ก่อน build ด้วย Vite และ replace ด้วย preact/compat เพื่อลด bundle size) ในขณะที่พี่มิกซ์จะช่วยเตรียม docker file สำหรับ Election data update service ที่เราจะ deploy ลง Election data server เนื่องจากเรามีหลายคนทำในส่วนของ front-end ของโปรเจคนี้ ผมเลยใช้ Github Issue ในการแบ่งงานเป็น component ย่อยๆ เพื่อที่แต่ละคนจะสามารถเข้ามา assign ตัวเองเข้าไปทำได้
- Election day ตามแผนที่วางไว้ วันนี้เราไม่ได้มีโค้ดใหม่ที่ต้องเขียนมากนัก แต่เราต้องเตรียมแปลงข้อมูลผลโพล (ที่ไม่รู้จะมาเมื่อไหร่ในรูปแบบใด) รวมถึงคอย monitor ตอนรายงานผลคะแนนสด และแก้ไขเฉพาะหน้าต่างๆ ตามสถานการณ์
- Post Election day ปิด Election data server แปลงผลคะแนน official และย้ายข้อมูลการเลือกตั้งและ preset ทั้งหมดไปไว้ใน repository เพราะเราไม่จำเป็นต้องอัพเดตข้อมูลอีกแล้ว (อย่างน้อยก็ในอนาคตอันใกล้)
สิ่งหนึ่งที่ผมได้เรียนรู้ในการ lead โปรเจคนี้คือการเชื่อมั่นคนในทีม ผมยอมรับเลยว่ามีโค้ดหลายส่วนที่ไม่เคยอ่าน ไม่รู้ว่าบางส่วนมันทำงานอย่างไร เพราะโปรเจคใหญ่มาก แต่ที่สำคัญคือเมื่อเราแบ่งงานกันอย่างเป็นระบบแล้ว ผมรู้ว่าถ้าเจอปัญหาแล้วผมจะต้องถามใคร และทุกคนก็พึ่งพาได้จริงๆ
🦥 III: Too much data? Optimize load time
เรามีข้อมูลเยอะมากจากหลายๆ source ในเวอร์ชั่นแรกๆ เว็บเราโหลดช้ามาก เราเลยค้นพบหลายๆ วิธีที่ช่วยให้เว็บโหลดเร็วขึ้น
ข้อมูลที่ไม่ต้องอัพเดตบ่อยก็ดึงมาใช้ตอน Build time
จากที่เกริ่นไปว่าส่วน Candidate เราดึงข้อมูลผู้สมัครมาจาก NocoDB ที่ทีม content เตรียมไว้ เราดึงมาตอน build time ด้วยความสามารถของ NextJS SSG เพื่อประโยชน์หลักๆ 2 ข้อ
- เพิ่มความเร็วในการโหลด ด้วยการลด request ที่จะถูกยิงไปยัง NocoDB ตรงๆ
- เพื่อ SEO ที่ดีขึ้นเพราะข้อมูลจะถูก pre-render ตั้งแต่ตอน build
ข้อเสียคือถ้าหากเกิดการอัพเดตข้อมูลใน NocoDB เราก็ต้องสั่ง build ใหม่ ซึ่งในเคสนี้เกิดขึ้นไม่บ่อยนัก จึงเป็นวิธีที่โอเค
ข้อมูลที่โหลดจากฝั่ง Client พยายาม Lazy-load ถ้าเป็นไปได้
ข้อมูลบางส่วนจำเป็นต้อง load จาก client-side เราก็พยายาม Lazy-load มันซะ เพื่อให้หน้าเว็บแสดงได้เร็วที่สุดก่อน แล้วส่วนที่มาทีหลังได้ก็ค่อยตามมา
Lazy API call
ในหน้าแรกมีส่วนที่เราต้องยิง Wordpress API เพื่อขอบทความ แต่เนื่องจากมันอยู่ด้านล่างเลยไม่มีความจำเป็นต้องโหลดตั้งแต่ต้น ตรงนี้เราใช้วิธีว่า ถ้า scroll มาถึงแล้ว (detect ด้วยเครื่องมือเช่น enter-view, scrollama หรือ Intersection Observer API) ค่อยยิง API
Lazy-load Embed YouTube video
การ embed YouTube video player ด้วย iframe พร้อมๆ กัน 20 กว่าวีดีโอในหน้าผู้สมัครผู้ว่ากทม.ทำให้เว็บโหลดช้ามากๆ เราเลยค้นพบวิธีโคตรจีเนียสที่ไม่รู้ใครเป็นคนคิด คือการแสดงรูป cover video พร้อมกับปุ่มกดที่ใช้ HTML + CSS เนียนๆ ขึ้นมาก่อน พอกดปุ่มปุ๊ปค่อยโหลด iframe ของวีดีโอนั้นขึ้นมา มี library ที่ช่วยทำตรงนี้เยอะมากเช่น lite-youtube ที่เป็น Web Component
Code splitting and Lazy-load React component in SPA
ส่วน Map ที่เป็น SPA ตอนแรกเราพบปัญหาว่า JavaScript bundle file ที่ถูก build ออกมาใหญ่มาก ทำให้เว็บโหลดนานมาก เราเลยใช้ React.lazy กับ dynamic import เพื่อแยก JavaScript เดิมออกเป็นออกเป็นไฟล์ย่อยๆ ที่แยกกันโหลด รวมถึงโหลดทีหลังเมื่อจำเป็นต้องใช้ (เช่นเวลากดเปลี่ยนประเภท Visualization)
Goodbye PNG, long live WebP
WebP เป็นมาตราฐานการบีบอัดรูปสำหรับเว็บแบบใหม่ที่ web browser ส่วนใหญ่ support กันแล้ว ซึ่งทำให้ขนาดภาพเล็กลงมากๆ โดยที่ไม่ได้สูญเสียคุณภาพภาพมากนัก นี่คือตัวอย่างขนาดของภาพพื้นหลังในหน้าแรกที่ถูกแปลงจาก PNG มาเป็น WebP ด้วยคุณภาพ 90%
🫣 IV: We write no test, and some dirty code too!
เรามองว่าการเขียน test และ clean code เป็นเรื่องที่ดีนะ แต่สุดท้ายมันก็เป็นเรื่องของ priority ว่าอะไรสำคัญที่สุดในเวลาที่จำกัด
สำหรับผม test ในโปรเจคนี้ไม่ได้สำคัญเป็นอันดับต้นๆ เพราะ
- ลักษณะงานที่ค่อนข้าง static อะไรพังส่วนใหญ่ก็รู้ได้เลยตั้งแต่ build time
- ไม่ได้รับ User input อะไรเลย ซึ่งส่วนใหญ่นี่แหละคือจุดที่มักทำเว็บพัง
- ความถูกต้องของข้อมูลหรือ visualization ก็ยังใช้คนเช็คง่ายกว่า test
- พอจบเลือกตั้ง มันก็จะกลายเป็น static website ธรรมดาที่ไม่โดนอัพเดต เลยไม่ต้องมีการ maintenance อะไรมากนัก
ส่วนที่น่าเขียน test ที่สุดก็คือ Election data update service ครับ แต่ด้วยข้อจำกัดของเวลา และความ simple ของ service เราเลยตัดสินใจที่จะข้ามการเขียน test ไป
เรื่อง clean code ก็เช่นกัน เราตั้งใจเขียนให้ clean แต่ต้น และก็พยายาม feedback กับคนในทีมเพื่อพัฒนาฝีมือซึ่งกันและกัน แน่นอนว่าเขียนๆ ไป มันก็มีก็มีหลายส่วนที่รู้ว่า refactor ได้ แต่เราก็ตัดสินใจทิ้งมันไว้เพื่อทำสิ่งที่สำคัญกว่าก่อน (ยอมรับว่าขัดใจความ perfectionist ในตัวเองพอสมควร) คนที่เข้ามาดูเค้าไม่รู้หรอกว่าโค้ดเราสวยหรือเปล่า และเมื่อมันเป็นโปรเจคระยะสั้น การมี tech debt เหล่านี้ก็ไม่ได้เจ็บนัก ถ้ามีวันไหนที่เราจะกลับมาใช้โค้ดพวกนี้อีกครั้ง ค่อย refactor ตอนนั้นก็ยังไม่สาย
🚨 V: The Incidents: A single point of failure
1 วันก่อนเลือกตั้ง
ทุกอย่างเป็นไปได้อย่างราบรื่นจนกระทั่งวันก่อนเลือกตั้ง เราลอง deploy ส่วน Election data update service ลงไปใน production server (ตอนนั้นเรายังไม่ได้แยก server ออกมา ก็คือ monolith สุดๆ) เราเปิด map ให้คนทั่วไปเข้าชม รวมถึงทาง The Standard ก็โปรโมตเว็บอย่างเต็มที่ เป็นวันที่คนเข้าเยอะที่สุดเท่าที่เปิดโปรเจคมา
อยู่ดีๆ server เราก็ล่มไปครับ CPU usage ของ AWS Lightsail เราพุ่งพรวดขึ้นมาจนใช้ โควต้า CPU burst capacity จนหมดและเครื่องก็ค้างไป นั่นคือ incident แรกที่เราเจอ
ผมสั่ง restart VPS แล้วก็กลับมาเป็นปกติ วันนั้นผม panic อยู่พอสมควร แต่ก็ต้องขอบคุณพี่มิกซ์ที่ช่วยกันหาสาเหตุและวางแผนรับมือสำหรับวันจริงในพรุ่งนี้
เราพบว่าระบบ logging เรากากเกินกว่าที่จะฟันธงได้ว่าสาเหตุเกิดจากอะไร เพราะคนใช้ที่มากขึ้นมีผลต่อ CPU ก็จริง แต่ก็ไม่ได้ทำให้พุ่งขึ้นมาโดดๆ จุดเดียวแบบนั้น เราเลือกผู้ต้องสงสัยเป็น Election data update service ที่รันไว้ เพราะเราเจอ error แปลกๆ บางอย่างจากการยิง request ไปที่ ERS server ในช่วงเวลาที่ CPU burst ขึ้นมาพอดี ซึ่งเราก็ไม่สามารถ re-produce error ที่ว่าได้อีกเลย ได้แต่สงสัย
เราตัดสินใจทำ 3 อย่าง
- แยก Election data update service ออกมาอีก server ถ้าหากเกิด incident นั้นอีก อย่างน้อยเว็บหลักก็จะไม่ล่มตาม
- ผมอัพเกรด spec ของ production server (vertical scaling) เพื่อเผื่อโหลดที่อาจจะเข้ามามากขึ้นในวันเลือกตั้งตอนรายงานข้อมูลแบบ real-time
- ผมตั้ง Uptime Kuma ไว้ในเครื่องตัวเองเพื่อคอยเช็คในทุกๆ นาทีว่าเว็บเราล่มหรือยัง ถ้าล่มแล้วมันจะแจ้งเข้าไปใน Slack ของ WeVis
ในวันเลือกตั้ง
เป็นวันที่วุ่นวายตามที่เราได้คาดไว้ ทีมเรา standby ช่วยกันเตรียมรับมือปัญหาต่างๆ นานา ทั้งการแก้ไขข้อความต่างๆ ที่คลุมเคลือ ข้อมูลที่ผิดพลาด (ขอบคุณชาวทวิตเตี้ยนที่ช่วยกันบอกเราเข้ามา) การเพิ่มข้อมูลโพล และข้อมูลสดของ ส.ก.ที่เราไม่ได้เตรียมตัวเอาไว้ จึงเกิดการ deploy ใหม่หลายรอบ ในวันนั้น staging environment โดดเด่นมาก เพราะมันทำให้เราสามารถ test on staging ได้อย่างอุ่นใจทั้งในทีม WeVis และ The Standard แทนที่จะ test on production ที่มีคนนับแสนเข้าชมในวันนั้น 🥶
เว็บเราล่มอีกครั้งในช่วงรายงานผลคะแนนแบบ real-time ตอนค่ำๆ ซึ่ง active user สูงทำลายสถิติเดิม ที่ตลกที่สุดคือ เพื่อนผมทักแชทมารัวๆ ตอน server ดับ เร็วกว่า Uptime Kuma ที่ตั้งไว้เสียอีก
เรา restart เครื่องหนึ่งที หายเหมือนเดิมครับ (It’s a magic) พอ restart กลับมา CPU ก็ดรอปลงมาเล็กน้อย อย่างไรก็ตาม CPU ยังถูกใช้เยอะและกิน CPU burst quota เราไปเรื่อยๆ ผู้ต้องสงสัยรอบนี้ตกเป็น Plausible Analytics เราพบว่า process RabbitMQ ของ Plausible กิน CPU แทบจะครึ่งนึงของทั้งหมดซึ่งแปรผันตรงกับจำนวน active user ที่เข้ามา เราดูสถานการณ์และวางแผนว่าถ้าโควต้าเราเหลือน้อยจริงๆ เราจะยอมดับ Plausible และเสียข้อมูลจำนวนคนเข้าใช้ส่วนหนึ่ง อย่างน้อยก็ขอไม่ให้เว็บหลักล่ม สุดท้ายเป็นโชคอันดีที่คะแนนนับเกือบครบแล้วและคนก็ค่อยๆ ซาลงจน CPU อยู่ต่ำกว่า burst zone ในที่สุด (หรือเค้าหนีไปดูเจ้าอื่นหลังเว็บล่มกันนะ 🤔)
🌈 VI: The Conclusion
เวลาทำ retrospective เราจะพูดกันว่า “ขอให้ระลึกเสมอว่าสิ่งที่เกิดขึ้นนั้น ทุกคนได้ทำเต็มที่แล้วตามกำลังและเงื่อนไขที่มี เพราะฉะนั้นเราจะโฟกัสว่าในรอบหน้าจะทำให้ดีขึ้นได้อย่างไร” โปรเจคนี้ผมก็เชื่ออย่างนั้นเช่นกัน ถึงแม้เว็บเราจะล่มไปสองครั้ง แต่โดยรวมมันเป็นความภาคภูมิใจของทุกๆ คนในทีมนะ เราต่างรู้กันว่าทุกคนเต็มที่กับโปรเจคนี้ขนาดไหน (หาทำกันทั้งนั้น) และนี่ก็เป็นโปรเจคที่ใหญ่ที่สุดที่ผมเคย lead มาเลย
ผมรู้สึกว่าทีมเรามี data storytelling และ visualization เป็นจุดแข็งอยู่แล้ว นี่ก็ถึงเวลาที่เราต้องยกระดับฝั่ง technical และ IT infrastructure ขึ้นเช่นกัน นี่คือสิ่งที่เราตั้งใจจะทำหลังจากนี้ครับ
- ยกระดับ IT infrastructure หลังบ้าน ผมยังมองว่าการเปลี่ยนเป็น micro-services หรือ K8s ทั้งหมดมัน overkill เกินไป เพราะ monolith มันก็ตอบโจทย์งานเรามาตลอดจนกระทั่งงานเลือกตั้งครั้งนี้ ในเบื้องต้นผมคิดว่าเราต้องแยก internal tools (Plausible, NocoDB, etc.) และ client service (Wordpress และ WeVis static sites) ออกจากกัน หาก internal tools ล่ม คนใช้ก็ไม่ควรจะได้รับผลกระทบ และถ้าเจอเคสที่ต้องรับ load คนเยอะจริงๆ ก็สามารถแยก service ออกมาอีกตัวที่สามารถ scale ได้เฉพาะกิจไปเลย
- ทำเรื่อง monitoring และ logging อย่างเป็นระบบ เพื่อจะได้เข้าใจสาเหตุของ incident ที่เกิดขึ้น
- ผมต้องไม่เป็น single point of failure เสียเอง ต้องทำให้คนอื่นสามารถ deploy code ลง production ได้ และต้องแบ่งความรู้หลายๆ ส่วนออกมาให้เพื่อนร่วมทีม เผื่อเกิดเหตุอะไรฉุกเฉิน
- ทำ repository ให้มีมาตราฐานระหว่าง dev มากขึ้นด้วย lint-staged + eslint+prettier (ผมไม่เคยใช้กับ monorepo) และ Conventional commit message (อันนี้เพิ่งเจอทีหลัง แล้วพบว่ามันช่วยให้อ่าน commit message ง่ายขึ้นมาก ยิ่งสำหรับ monorepo ที่เราใส่ app/package เป็น scope ได้)
- พัฒนา flow ระหว่าง NocoDB และ build process ให้มีความ automate มากขึ้น และปัจจุบันก็ต้องสั่ง Turborepo ให้ bypass cache หลังจากอัพเดต NocoDB เพราะตอนนี้มันเป็น external dependency ที่ Turborepo ไม่อาจรู้ได้ว่ามีความเปลี่ยนแปลงหรือไม่
และถ้าหากใครมี feedback และข้อแนะนำใดๆ เพิ่มเติม พวกเรายินดีมากๆ เลยครับ
🏃♂️ Extra: We have a bright future, but pushing civic-tech is like running a marathon
เราทุกคนในทีมรู้สึกประทับใจกับสิ่งที่เกิดขึ้นในการเลือกตั้งครั้งนี้ เราเห็นภาคประชาชนที่ตื่นตัวกับการเลือกตั้ง เราเห็นสื่อและภาคธุรกิจหลายสำนักทำ visualization และ content เจ๋งๆ แถมยังรวมตัวกันลงขันทำระบบข้อมูลเลือกตั้งแบบ real-time
อย่างไรก็ตาม การตบมือข้างเดียวมันไม่ดังนักหรอก ยังไงภาครัฐก็ยังเป็นตัวละครที่สำคัญอีกตัวละครหนึ่ง เช่น API ข้อมูลเลือกตั้งแบบ real-time ก็ทำได้ง่ายและครบถ้วนที่สุดหากทำโดยภาครัฐ และการเปิดเผยข้อมูลผลเลือกตั้งด้วย PDF ที่เป็นเลขไทยก็ทำให้เราเอาไปใช้ต่อได้ยากมากๆ (ต้องขอบคุณ ทีม OpenDream ที่ช่วยถอดออกมาให้ใช้กันครับ🙏)
เพราะฉะนั้นเราก็ยังต้องผลักดันกันต่อไป ให้เกิดความร่วมมือจากทางภาครัฐ เช่น Open Data ที่เป็น Machine-readable format เพื่อสนับสนุนการสร้างการเมืองที่โปร่งใส่และมีส่วนร่วมกันในทุกภาคส่วน
ติดตามโปรเจคต่างๆ ของ WeVis ได้ที่ Website, Facebook, Twitter และ GitHub ของพวกเราครับ
Special Thanks
ถ้าไม่มีทีม สิ่งที่เราทำได้คงเป็นแค่เรื่องเล็กๆ เท่านั้น งานนี้จะเกิดขึ้นไม่ได้เลยหากไม่มี
- Project manager และ co-founder PunchUp อย่างพี่กิ๊ง
- เพื่อนชาว Dev พี่แม็กซ์ เพชร พี่มิกซ์ พี่บาส และฟีน
- Designer อย่างพี่จั๊ก พี่แต๊ง และเต้ (ที่นับเป็น dev ครึ่งคนเพราะบางครั้งก็ push commit แก้ UI ขึ้นมา)
- ทีม The Standard และ Wisesight ที่ช่วยให้เรามี content แบบปังๆ
- เพื่อนๆ ชาว PunchUp ทุกคนที่คอย support
- ผู้ชมทางบ้านผู้คอย report bug หรือเวลาเว็บล่ม
- Contributor ทางบ้านที่เปิด PR มา
- ทุกๆ คนที่ช่วยกันตรวจดราฟบทความนี้
- ประชาชนชาวไทยที่เปี่ยมไปด้วยอุดมการณ์และยังไม่หมดหวังกับประเทศนี้