Desktop PC ตัวใหม่ เพื่อปรับการทำงานเป็น Desktop + Notebook แทน

ปีนี้ผมซื้อ Desktop PC เครื่องใหม่ ซึ่งมีค่าใช้จ่ายทั้งหมดประมาณ 38,000 บาท เป็น Desktop PC ในราคา 31,800 บาท และ UPS 6,200 บาท เหตุที่แพงกว่าที่คาดการณ์ไว้ 12,000 บาท เพราะการ์ดจอ และ UPS เป็นหลัก

โดยรวมค่อนข้างประทับใจ จะปวดหัวหน่อยตรง TPM ที่ต้องซื้อเพิ่มเติม กับ M/B ที่สายไฟมันไปบังช่อง 5.25″ เลยใส่ optical drive ของเก่าไม่ได้ และสายไฟที่เดินเข้า M/B มันไปดันช่องใส่ HDD ตัวล่าง เลยใส่ HDD ตั้งนั้นได้ 1 ตัวแทนที่จะได้ 2 ตัว (เพื่ออนาคตมีเพิ่ม) แม้มันจะมีช่องให้แขวน HDD ได้อีกตัว แต่ตอนขันน็อตมันลำบากมาก

สำหรับ power supply เหตุผลใช้เลือกแค่ 650W 80+ bronze เพราะหากไปไกลกว่านี้ UPS จะราคาโดดไปไกลมาก (ต้องไปเล่น pure sine wave ไม่งั้นมันทำงานกับ Active PFC watt สูง ๆ แล้วจะมีปัญหา) โดยรวมโหลดไฟของเครื่องไม่เกิน 500W อยู่แล้ว แต่ด้วยความที่เป็น 80+ bronze ก็ต้องเผื่อไปอีกนิด เช่นเดียวกับ UPS ก็ต้องบวกโหลดสูงสุดจาก power supply ออกไปประมาณ 10-25%

ส่วน monitor, keyboard และ mouse ใช้ของเดิมที่มีอยู่แล้วก็ไม่ต้องจ่ายส่วนนี้

สำหรับ OS ก็ซื้อ Windows 10 Pro OEM มาใช้งาน ราคาก็เกือบๆ 4,700 บาท การจ่ายเงินซื้อ OS เมื่อก่อนจะคิดเยอะ แต่เดี่ยวนี้ เหมือนเดินไปซื้อการ์ดจอ หรือ CPU นั่นแหละ เหตุที่ใช้ Pro เพราะเรื่อง Hyper-V และ BitLocker เป็นหลัก เพราะต้องทำงานกับพวก container orchestration เลยจำเป็นต้องใช้พวกนี้

จากที่ว่ามาทั้งหมด จุดประสงค์ในการประกอบเครื่องเองครั้งนี้ คือปรับแนวการทำงานในปีหน้า เป็น Desktop + Notebook แทนการใช้ Notebook อย่างเดียว เพราะสเปคที่เท่ากัน Desktop ราคาถูกกว่าครึ่งหนึ่ง โดยตั้งต้นสเปคเน้น RAM 32GB และ SSD 512GB เป็นที่ตั้ง ส่วน CPU ก็หาตัวที่เข้ากันได้ ใส่ HDD ที่มีอยู่เดิมเข้ามา เพราะในปีหน้าคงต้องทำงานเป็นแบบ container orchestration มากขึ้น เวลาใช้ Notebook ที่มี RAM 16GB เปิด docker-desktop ทำงานก็โดนจองไป 4GB ขั้นต่ำ เวลา LAB อะไรซับซ้อนมันลำบากต้องไปวุ่นวายบน Cloud ที่แพงพอสมควร ซึ่งจากที่ว่ามา ต้องขอบคุณเทคโนโลยี Cloud Storage + Git repository ที่ทำให้การทำงานระหว่าง Desktop และ Notebook สบายขึ้นมาก

ท้ายสุดก็มีติดเรื่องเดียว LINE นิดนึง เพราะฝั่ง Desktop จะใช้ desktop app ปรกติไม่ได้ ต้องใช้ผ่าน Chrome extension แทน ก็พอถูไถไปได้บ้าง

ในด้าน Hardware โดยรวม และ Software จบไป ก็เป็นเรื่องของการจัดระเบียบสายเชื่อมต่อใหม่ เพื่อให้แชร์ จอภาพ, keyboard, mouse และลำโพง USB ระหว่าง Desktop และ Notebook ได้อย่างสะดวกมากขึ้น โดยหัวใจสำคัญ คือ Ugreen USB 3.0 Switcher 2 PC share 4 port USB (ต่อไปเรียก USB Switcher) ซึ่งทำงานร่วมกับจอ Dell ที่ตัวมันรับ input ได้ 4 input เป็น DP x 2 และ HDMI x 2 พร้อม USB 3.0 Hub 4 port ในตัว

เริ่มต้นที่จอ Dell ทำการต่ออุปกรณ์ที่ต้องการแชร์ไปมาระหว่าง Desktop และ Notebook คือ keyboard, mouse และลำโพง USB เข้ากับ USB 3.0 ของจอ แล้วเอาสาย USB 3.0 ของจอภาพต่อกับ USB Switcher

เสร็จแล้วก็เอาสาย USB Switcher ฝั่ง input PC ไปต่อกับ Desktop และ Notebook เวลาสลับก็กดปุ่ม 1 – 2 สลับไปมา ก็ทำให้เราสลับตัว input ระหว่าง Desktop กับ Notebook ได้แล้ว

สำหรับการแสดงผล จอรองรับ 4 input เราให้ Notebook ต่อเข้า input HDMI ส่วน Desktop ก็ต่อเข้า input DP แทน เวลาสลับจอก็ทำผ่านคอนโซลของจอภาพเอา

สำหรับ Notebook มีการต่อ external HDD 2TB x2 ก็ต่อผ่าน USB 3.0 Hub แบบมีแหล่งจ่ายไฟแยกอีกช่องแยกออกไป เพราะเวลาสลับผ่าน USB Switcher จะได้ไม่สลับไปด้วย

จากทั้งหมดที่พยายามทำมา ก็ใช้เวลาอาทิตย์กว่า ๆ ในการทยอยปรับแก้จนออกมาได้อย่างที่เห็น ปีใหม่ในปีหน้า น่าจะทำให้การทำงานสะดวกราบรื่นขึ้นไปอีก

เบื้องหลัง music48voter.com

เว็บ music48voter.com เป็นเว็บลูกของ music48project.com สร้างเพิ่มเติมขึ้นมาเพื่อเป็นช่วยในการรวบรวมคะแนนเพื่อวัดผลในการดำเนินกิจกรรมของกลุ่มแฟนคลับ Music BNK48 ต่อไป

ตัวระบบก็ปั่น “backend กึ่ง front-end” ช่วงปีใหม่ 3-4 วัน แม้ว่าจะทำเก็บมาเรื่อย ๆ แต่ว่ามันแค่โครง ๆ เป็นไอเดียในระดับ user management แล้วลองของเรื่อง .NET Core อีกหนึ่งตัว (มีอีกหลายตัวที่กำลังคิดว่าจะย้ายมาใช้ เพราะมันมีอนาคตกว่า) โดยจากการทำโครงการเล็ก ๆ ตัวนี้ เจ้า .NET Core ตั้งแต่ 2.x มาเป็นต้นมา มันเขียนสะดวก และง่ายขึ้นมาก ความสามารถต่าง ๆ ตาม .NET Framework เกือบทันแล้ว (บางจุดก็ใช้แรงหน่อย แต่รวม ๆ ก็พอไหว) เลยใช้แทน PHP ที่หลัง ๆ ยุ่งกับมันน้อยลงเยอะ

ตัวเว็บนี้ใช้ .NET Core version 2.1 ใช้ร่วมกับฐานข้อมูล MySQL version 5.7 ซึ่งตัวเดิม ๆ ที่ new project มามันไม่รองรับหรอก แต่เราใช้ Class provider อย่าง Pomelo MySql ที่เป็น Class provider ที่ on top บน Entity Framework Core มาทำงานแทน ทำให้ไม่ต้องเขียนคำสั่ง SQL จริง ๆ แต่ทำผ่าน LINQ และ ORM แทนซึ่งจบในตัว

สำหรับ background woker ใช้ Hangfire ตัว Free ก็เพียงพอ สำหรับใช้พวกส่งอีเมล เพื่อจัดคิวในการส่ง และแก้ปัญหาเรื่อง blocking UI คือกดแล้วรู้เลยว่าสมัครแล้ว ส่วนส่งอีเมลยืนยันการสมัครที่เราไปต่อ 3rd party SMTP ภายนอกที่มักทำงานช้า ในระดับหลักเกือบวินาที มันทำให้ฝั่งเว็บค้างไปแป็บนึงมาช่วยให้มันไม่ต้องรอจุดนั้น แถมยังลดปัญหา waiting ที่ thread ช่วงส่งเมล ส่วนในหน้า Ranking ตัว report เนี่ย มันใช้พลังในการ summary เยอะ ก็ใช้ schedule woker ที่อยู่ในตัว Hangfire อยู่แล้วทำ ให้มันทำงานทุก ๆ 5 – 10 นาทีเพื่อสร้างข้อมูลใหม่ใน report table แทน แล้วเวลาแสดงผลก็ให้มาดึงที่ตัวนี้แทน การแสดงผลตารางดังกล่าวจะได้ไม่หนักฐานข้อมูล

ในส่วนของ Front-end Lib ก็ใช้ Bootstrap 4 เอาง่าย ๆ โดยจะมึนหัวนิดนึงตอน upgrade มาใช้ เพราะตัว default ของ .NET Core 2.1 คือ Bootstrap 3 ก็เลยต้องใช้ bower มาช่วยในการจัดการตรงนี้ แก้ default style ที่มันค้างใน Framework อยู่เยอะพอสมควร แต่แลกกับการทำงานดีง่ายกว่ามาก สำหรับทีมออกแบบหน้าเว็บ ที่เป็นอีกทีมหนึ่ง ที่เค้าใช้ Bootstrap 4 เข้ามาช่วยกันแก้ไข และตบแต่งเพิ่มเติมให้ออกมาสวยงามขึ้นอีก ทั้ง layout และ stylesheet ถ้าใครติดตามตัวเว็บช่วงนั้นจะเห็นว่าเราปรับปรุงตลอดเวลา ยิ่งช่วงวันท้าย ๆ เรามีการปรับโทนสีของเว็บใหม่ทั้งหมด เพื่อให้เข้ากับแนวทางการทำกิจกรรมช่วงสุดท้าย

ตัว Binary ตอน deploy ขึ้นทำงานบน CentOS 7 ทั้งฝั่ง App และ Database โดยตัว App วิ่งผ่าน HAProxy ที่ทำหน้าที่ reverse proxy อีกที (เป็น best practice ที่ควรทำ เพื่อสำหรับ HA และทำ hot deploy ได้)

สำหรับขั้นตอนการตรวจสอบโค้ดโหวตในฝั่งแอปก็ไปแกะเอามาจาก javascript ของฝั่งเว็บโหวตเพื่อเอามาตรวจสอบอีกรอบก่อนเอาลงฐานข้อมูลเพื่อลดงานหลักบ้าน ส่วนงานตรวจสอบอีกชั้นว่าโหวต หรือไม่ก ็ทำระบบหลังบ้านให้ทีมงานมาตรวจสอบจาก screen recorder อีกที ซึ่งยุ่งยากหน่อย แต่มันเป็นทางเดียว เพราะจะให้แฟนคลับแต่ละคนมากรอก username และ password เพื่อให้เราไปดึง token ผ่าน OAuth2 แล้วเอาไปดึงข้อมูลจาก API ออกจากเว็บโหวตโดยตรงคงมีไม่กี่คนจะมาใช้

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

มีเรื่องฮา ๆ และตื่นเต้นคือ ตอนเปิดให้สมัครวันแรก คนเข้ามาเว็บค่อนข้างเยอะมาก หลักพันกว่าคน แล้วตอนนั้นหน้าลงทะเบียนดันมี bug เกิดขึ้น เลยต้อง hot deploy ใหม่ในช่วงนั้นไป ทำเอาคนสมัครตอนนั้นบ่นกัน timeline ไหม้เลย (ToT)/~~~

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

อันนี้บันทึกเท่าที่คิดออก ณ ตอนนี้ ไม่รู้ตกหล่นอะไรไหมนะ ԅ(¯﹃¯ԅ)

โครงการ Music We Choose You เราส่งรหัสโหวตเลือกตั้งด้วยวิธีอัตโนมัติอย่างไร

ก่อนอ่านผมอยากทำความเข้าใจก่อนว่าผมเป็นเพียงหนึ่งในทีมงาน Music We Choose You กว่าหลายสิบชีวิตเท่านั้น มีทีมงานส่วนอื่น ๆ ที่ต้องอ่านบัตรโหวต และกรอกด้วยมือโหวตลงไปบนเว็บเองอีกหลายพันโหวต ที่ไม่ได้เกิดจากกระบวนการนี้ และใน blog นี้ อาจไม่ได้กล่าวถึงชื่อทีมงานหลังบ้านเป็นรายคนได้ เพราะเพิ่งได้เจอหน้ากันก็ตอนวันลุ้นผลคะแนนเสียงเลือกตั้ง และบางคนที่เป็นทีมงานฝั่งญี่ปุ่นก็ยังไม่เจอหน้ากันจนวันนี้ แต่ทุกคนทำงานอย่างหนัก เพื่อให้ได้ซึ่งรหัสโหวตเลือกตั้งมาเข้ากระบวนการ และสุดท้ายคือผู้สนับสนุนเงินที่ร่วมกันโดเนทเข้าโครงการเพื่อให้มีเงินจำนวนมากในการจัดหารซีดี และรหัสโหวตเลือกตั้งมา นั้นหมายความว่า 18,502 คะแนน เกิดจากความร่วมแรงร่วมใจกันของทีมงาน และผู้สนับสนุนทุกคน ผมไม่ขอรับเครเดิตเหล่านี้ไว้เพียงคนเดียว ถ้ามีรายชื่อที่แน่ชัด เดี่ยวจะนำลงมาใส่ให้อีกครั้ง

ในส่วนของกระบวนการนี้ไม่ได้เป็นสิ่งใหม่อะไร แต่เป็นการสังเกต และมีต้นแบบจากคลิปจากแฟนคลับฝั่งญี่ปุ่นที่ได้ลงแนวทางปฏิบัติไว้ เรานำมาปรับปรุงและเปลี่ยนแปลงในหลาย ๆ ส่วนเพื่อให้สอดคล้องกับการทำงานในทีมอีกรอบ (ใน blog นี้บางขั้นตอนเรามี source code เปิดเผยให้ลองนำไปศึกษาต่อด้วย)

ฉะนั้นการเล่าเรื่องของกระบวนการในส่วนนี้จึงเป็นเหมือนบันทึกความทรงจำ เป็นข้อมูลอ้างอิงในอนาคตสำหรับนำไปปรับปรุงกระบวนการ และสำหรับบุคคลที่สนใจ

ตัวอย่างกระบวนการ และสิ่งที่ผมนำเป็นต้นแบบจากฝั่งญี่ปุ่น

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

การติดต่อประสานงานระหว่างทีมงานหลัก ๆ คือใช้ LINE และ Discord สำหรับ LINE จะใช้เพื่อสื่อสารแบบเร่งด่วน และสื่อสารกับทีมฝั่งญี่ปุ่น ส่วน Discord จะใช้ประสานงานช่วงส่งรหัสโหวต และพูดคุยแก้ไขรหัสโหวตในช่วงกลางคืนตั้งแต่ประมาณ 3 ทุ่มถึงตี 2 (มีบางวันตี 3) เหตุผลที่เราใช้เพราะการสื่อสารด้วยเสียงมันไม่ต้องพิมพ์ เพราะมือพิมพ์รหัสโหวต หรือแก้ไขข้อมูลกันอยู่ การพิพม์ LINE ทำให้งานส่วนแก้ไขรหัสโหวตหยุดชะงักได้ แล้วคุณภาพเสียงของ Discord ค่อนข้างดีไม่ค่อยเจอดีเลย์อีกด้วย

เริ่มด้วยการแสกนบัตรโหวตด้วย Fujitsu ScanSnap iX500 ซึ่งเป็น Sheet Fed Scanner ยอดนิยมมากสำหรับงานแบบนี้ โดยเราแสกนออกมาเป็น JPEG ไฟล์ โดยเหตุผลที่เราไม่แสกนออกมาเป็น PDF เพราะ ไฟล์รูปภาพบริหารจัดการง่ายกว่า หรือนำไปเปิดไฟล์เพื่ออ่านกับอุปกรณ์หลากหลาย

ScanSnap iX500_006
ScanSnap iX500_006 @ flickr

เมื่อได้ไฟล์รูปภาพ ทีมงานฝั่งญี่ปุ่น-ไทยตกลงกันว่าจะอัพโหลดรูปภาพผ่าน Google Drive จากญี่ปุ่นมาไทยโดยการแชร์ Folder กลางตัวหนึ่ง เพื่อใช้สำหรับการส่งไฟล์รูปภาพบัตรโหวต และเอาไว้ค้นหาสำหรับกรอกทบทวน-แก้ไขรหัสโหวตในกรณีที่นำไฟล์รูปดังกล่าวไป OCR (Optical character recognition – การแปลงไฟล์ภาพเอกสารที่เป็นรูปภาพ ให้เป็นไฟล์ข้อความตัวอักษรโดยอัตโนมัติ) แล้วไม่ได้รหัสโหวตออกมาอย่างถูกต้อง โดยเราจัดแบ่ง Folder ตามรอบที่ส่งมาจากฝั่งญี่ปุ่น โดยคนที่กำหนดชื่อเป็นฝั่งญี่ปุ่น เพราะต้องให้ทางนั้นกำหนดรหัสอ้างอิงที่เข้าใจง่ายสำหรับคนทำงานฝั่งต้นทางที่มีจำนวนแผ่น และต้องดูแลจัดการบัตรโหวตจำนวนมาก เพราะจำนวนคนฝั่งญี่ปุ่นน้อยกว่าฝั่งไทยมาก เราต้องคิดถึงการจัดการหน้างานฝั่งนั้นเป็นหลัก

ในโครงการนี้ในส่วนของการโหวต เรามีทีมงาน 2 ส่วน คือทีมกรอกมือ และกดโหวต แล้วนำรหัสโหวตที่โหวตเสร็จแล้วมาใส่ใน Google Sheets ส่วนอีกทีมคือทีมที่ผมลงมาช่วยเป็นส่วนหนึ่งของทีมงาน ซึ่งทำผ่าน OCR โดยจุดรวมของรหัสโหวตทั้ง 2 ทีมนี้ คือใช้ Google Sheets ตัวหนึ่ง เพื่อใช้สำหรับทำงานร่วมกันเพื่อแชร์ข้อมูลรหัสโหวต และการแก้ไขข้อมูล โดยทั้ง 2 ทีมมีคนกว่า 10 คนช่วยกันดู ช่วยกันแก้ไขรหัสโหวต เราเก็บรหัสโหวตต่างๆ พร้อมกับชื่อไฟล์รูปจากใน Google Drive เพื่อช่วยอ้างอิงย้อนกลับไปยังรูปต้นฉบับที่รหัสโหวตนั้นถูกกรอกมา เพราะหากรหัสโหวตนั้นใช้ไม่ได้ด้วยเหตุผลใด ๆ เราสามารถกลับไปค้นหาได้ง่าย

ในรอบส่งรหัสโหวตก่อนประกาศผลด่วนเมื่อเราได้รับการยืนยันรูปที่จะทำ OCR ได้แล้ว เราเอารูปเหล่านั้นไปเข้า OCR ทันที แล้วพบว่าความเร็วและข้อผิดพลาดมีเยอะพอสมควร ในระดับความผิดพลาด ~35% และความเร็วควรจะเร็วได้มากกว่านี้

แต่หลังจากรอบผลด่วนเราปรับปรุงกระบวนการเพิ่มด้วยการใช้โปรแกรมที่ชื่อ XnView เข้ามาเพื่อทำ resize/crop แบบ batch (GitHub – Source Code) ในส่วนของรูปก่อนส่งเข้า OCR เพื่อให้ตัว OCR มันอ่านเฉพาะจุดที่จำเป็นต้องอ่าน และได้ผลตอบกลับที่เร็วขึ้น โดยรูปหมื่นกว่ารูปผ่านกระบวนการ batch นี้ เพื่อช่วยเรื่องลดความผิดพลาดจาก OCR ให้ได้มากที่สุด และยังลดจำนวนข้อมูลที่จะต้องส่งขึ้นระบบของ Google Cloud Vision API ได้ด้วย เราเคยจับเวลา รูปบัตรโหวตกว่า 2,000 รูป เราใช้เวลาไม่ถึง 15 นาที โดยนับเวลาตั้งแต่ resize/crop รูปบัตรโหวตจำนวนดังกล่าว มาจนได้ตัวเนื้อ Text กลับออกมาจาก Google Cloud Vision API กว่า 1,800 รายการ ซึ่งเกือบทั้งหมดที่สามารถนำไปส่งรหัสโหวตเข้าระบบได้ทันที ระดับความผิดพลาดลดต่ำลงเหลือ ~20% นั้นทำให้เราได้ประโยชน์จากการใช้ไฟล์รูปภาพแทน PDF เพราะเราสามารถหาชุดโปรแกรมที่สามารถทำการ resize/crop ได้ง่ายกว่า

รูปภาพต้นฉบับ

จะได้รูปภาพที่โดน resize/crop แบบด้านล่าง

 

สำหรับ OCR ที่เราใช้คือ Google Cloud Vision API  ด้วยเหตุผลตั้งต้นดังนี้

  1. การหา OCR Library แบบ Offline ไม่น่าจะคุ้มค่า เพราะคิดบนฐานค่าใช้จ่ายหากซื้อ License หรือหากใช้ Open source ก็ต้องแน่ใจว่าจะใช้งานได้จริง เราลองผิดลองถูกจุดนี้ไม่ได้มากนัก คำตอบของโจทย์จึงตกมาที่ Cloud Service แทน
  2. การเชื่อมต่อกับ Google Sheets API บน Credential ตัวเดียวกันได้ ทำให้การพัฒนาส่วนนี้เร็วขึ้น

แต่สุดท้ายในเหตุผลข้อที่ 2 ไม่ได้ถูกนำไปใช้หลังจากวันประกาศผลด่วน แม้จุดประสงค์คือส่งรหัสโหวตขึ้น Google Sheets ให้ทีมงานคนอื่นเห็นรหัสโหวตชุดนั้นร่วมกันได้ทันที แต่เจอปัญหาใหญ่คือติด rate limit API และทำเรื่องของ request จากทีม support ของ Google แล้วการตอบสนองไม่ทันต่อการใช้งาน (รอตอบกลับ 3 วัน) ผมจึงตัดสินใจย้ายไปใช้ฐานข้อมูลอย่าง MariaDB บน Cloud แทน แล้วเอารหัสโหวตที่ได้จาก OCR ใส่ย้อนหลับไปบน Google Sheets ด้วยมือ (Google Sheets API – Usage Limits = 500 requests per 100 seconds per project, and 100 requests per 100 seconds per user.)

ในขั้นตอน OCR จนได้รหัสโหวตมานี้ ช่วยลดจำนวนงานหลังบ้านที่จะต้องกรอกรหัสโหวตลงใน Google Sheets ได้เยอะมาก โดยเราเหลือกำลังคนไว้แก้รหัสโหวตที่ผิดพลาดประมาณ 15-20% ที่เหลือแทนได้

ในส่วนของ Script ที่เราใช้ส่งรูปขึ้น Google Cloud Vision API นั้น เราใช้ PHP ที่เขียนแบบ CLI เพื่อส่งไฟล์รูปข้างต้นขึ้น Google Cloud Vision API เพื่อให้ได้ Text ออกมา แล้วใช้ Regular expression กรองรหัสโหวตตาม pattern ที่กำหนดออกมา เป็นไฟล์แบบ CSV ไฟล์ (GitHub – Source Code)

เหตุผลที่เราใช้ CSV เพราะมันสามารถ import เข้าฐานข้อมูลผ่าน HeidiSQL เครื่องมือจัดการฐานข้อมูล MariaDB (ใช้งานกับ MySQL หรือ SQL Server ก็ได้) ได้ทันที และยังสามารถเปิดแก้ไขบน Spreadsheet อย่าง Microsoft Excel หรือ Google Sheets ได้ด้วย

โดยตัว HeidiSQL สามารถ export ตัวข้อมูลย้อนกลับมาเป็น CSV เพื่อนำไปทำงานบน Google Sheets ได้ทันที ทำให้เราไม่จำเป็นต้องใช้ Script อีกตัววิ่งส่งข้อมูลไปอัพเดทผ่าน Google Sheets API ตลอดเวลา และจุดสำคัญ การได้ CSV ไฟล์ดังกล่าว สามารถเอาไฟล์ CSV นั้นไปใช้กับโปรแกรมส่งรหัสโหวตอัตโนมัติที่เราเตรียมไว้ได้ด้วย

บางคนอาจจะคิดว่าทำไมไม่ใช้ฐานข้อมูลกลางเชื่อมต่อกันไปเลย ทำไมใช้ CSV โยนให้โปรแกรมส่งรหัสโหวตอัตโนมัติ?
– เหตุผลคือเรื่องกระจายเครื่องไปหลาย ๆ เครื่องเพื่อส่งรหัสโหวต ตัวโปรแกรมดังกล่าวติดตั้งง่ายไม่ซับซ้อนมาก และคัดลอกตัว Project และ CSV ของรหัสโหวต ปรับค่านิดหน่อย ก็เริ่มต้นทำงานได้แล้ว การเชื่อมฐานข้อมูลกลางอาจจะต้องทำ whitelist IP ฯลฯ อีกหลายตัวเพื่อป้องกันโดน hack อีกชั้น ซึ่งยุ่งยากเกินไป

สำหรับตัวโปรแกรมที่ใช้ในการส่งรหัสโหวตแบบอัตโนมัตินั้น ที่ชื่อ Katalon Studio พระเอกของงานนี้ ซึ่งเป็นเครื่องมือ automation testing solution ของนักพัฒนาซอฟต์แวร์ที่ โดยอ่านข้อมูลรหัสโหวตจากไฟล์ CSV ไปส่งลงฟอร์มของเว็บ AKB48

ตัว script ภายในเขียนด้วย groovy (GitHub – Source Code) ดักจับผลการส่งรหัสโหวตมี 4 สถานะดังนี้

  1. OK – รูปซ้ายบน
    เราดัก Keyword “投票完了” ที่หมายถึง Vote completed
    สถานะลงคะแนนโหวตสำเร็จ เก็บรวบรวมไปทำข้อที่ 2 อีกทีตอนที่ไม่มีรหัสโหวตส่งเข้าระบบเพื่อบันทึกวัน-เวลา และยืนยันผลอีกรอบ
  2. Already – รูปขวาบน
    เราดัก Keyword “入力したシリアルナンバーは既に投票済みです。” ที่หมาย The serial number you have entered has already been voted.
    เป็นสถานะที่บอกว่ารหัสโหวตนี้ถูกใช้งานกับผู้ลงเลือกตั้งคนนี้ โดยมีเวลากำกับ
    โดยเราจะต้องนำผลของหน้านี้ใช้ Regular expression เอาชุดข้อมูลวันและเวลาที่โหวตออกมาใส่ลงฐานข้อมูลด้วยเพื่อยืนยันวัน-เวลา
  3. Error – รูปขวาล่าง
    เราดัก Keyword “エラーが発生しました。” ซึ่งหมายถึง An error occurred.
    เกิดจากเว็บ AKB48 เกิดข้อผิดพลาด โดยถ้าเราได้ข้อความตัวนี้ คือโหวตไม่เข้า และเท่าที่เจอ คือรหัสโหวตใช้งานได้ เราต้องเอาไปเข้ากระบวนการสำหรับส่งรหัสโหวตเข้าเว็บอีกรอบ
  4. Failed – รูปซ้ายล่าง
    เราดัก Keyword “入力されたシリアルナンバーは無効であるか既に投票済みです。” ที่หมายถึง The serial number entered is invalid or already voted.
    ตรงนี้จะมี 2 ส่วน คือรหัสโหวตผิดจริง ๆ หรือรหัสโหวตถูกนำไปใช้กับผู้สมัครคนอื่นแล้ว หากรหัสโหวตถูกต้องโดยเทียบกับบัตรโหวต แสดงว่ารหัสโหวตใบนั้นถูกนำไปโหวตกับผู้สมัครคนใดคนหนึ่งที่ไม่ใช่คนที่เรากำลังโหวตอยู่ วิธีต่อไปคือเอารหัสดังกล่าวไปกรอกให้กับผู้สมัครคนอื่น ๆ ถ้าคนไหนขึ้นสถานะแบบข้อที่ 2. ก็คือรหัสโหวตนั้นถูกใช้โหวตผู้สมัครคนนั้นไปก่อนหน้านี้

รูปแบบหน้าทั้ง 4 หน้าที่จะเจอเมื่อเราส่งผลโหวต


จากการใช้ Katalon Studio เราจะเก็บผลการทำงานทั้ง HTML ไฟล์ และ Screenshot ของหน้าผลการโหวตเป็น JPEG ด้วย เพื่อใช้ในการตรวจสอบต่อไปหลังจบรอบโหวต

ในช่วงก่อนประกาศผลด่วนเราไม่ได้ตรวจสอบเคสในข้อที่ 3. และ 4. แยกออกจากกัน ทำให้เราพลาดคะแนนบางส่วนไปเพราะเว็บรับรหัสโหวต AKB48 ไม่ได้เสถียรตลอดเวลา จะมีการส่ง “An error occurred.” ออกมาอยู่เรื่อยๆ แม้แต่ช่วงวันท้าย ๆ ก็ยังไม่ได้แก้ไขให้ดีขึ้น มีอยู่หลายช่วงที่เราส่งรหัสโหวตไปกว่า 1,000 รายการ แล้วโดนตีตกด้วย An error occurred. อยู่เกือบ 600 รายการ และสุดท้ายทั้งหมดที่โดนตีตกเป็นรหัสโหวตที่ใช้งานได้ทุกตัว ทำให้เราต้องคัดแยกเพื่อให้ทีมหลังบ้านที่อ่านรหัสโหวตผิดพลาดไม่ต้องไปอ่านข้อมูลที่เกิดจากตัวเว็บ AKB48 แจ้งความผิดพลาดทั้ง ๆ ที่รหัสโหวตไม่ผิดอีก เพื่อลดงานฝั่งคนแก้ไขรหัสโหวตลง

เมื่อรหัสโหวตทุกส่วนส่งครบหมดจะเหลือเฉพาะรหัสโหวตที่โหวตได้ และรหัสโหวตที่โหวตไม่ได้ เราจะเอา log ของการส่งรหัสโหวตครั้งนั้นมาอ่านด้วย PHP ที่เป็น script ตัวอ่าน log แล้วเข้าไป flag สถานะในฐานข้อมูลตามแต่ละรหัสโหวตที่มี เพื่อบอกว่าโหวตสำเร็จหรือไม่ แล้วทำการสรุปรหัสโหวตที่โหวตได้ และรหัสโหวตที่โหวตไม่ได้ แยก Worksheet บน Google Sheets ออกจากกัน โดยในส่วนของ Worksheet ที่ใส่รหัสโหวตที่ส่งแล้วผิดพลาด จะมีทีมงานอีกส่วนเข้ามาช่วยกันอ่านไฟล์รูปบัตรโหวตและกรอกรหัสโหวตที่ถูกต้องลงไปแทน แล้วเมื่อจบการแก้ไขในแต่ละ Worksheet จะรวบรวมแล้วใช้โปรแกรมอัตโนมัติส่งเข้าเว็บโหวตอีกรอบ และในส่วนรหัสโหวตที่โหวตเรียบร้อย จะมีรอบสำหรับทบทวนวัน-เวลาที่โหวตเพื่อยืนยันรหัสโหวตว่าเมื่อโหวตเข้าไปแล้ว

โดยในขั้นตอนทบทวนวัน-เวลา เราจะใช้ script ส่งรหัสโหวตชุดเดิมส่งรหัสโหวตที่ต้องการวัน-เวลาส่งเข้าไปที่เว็บ AKB48 จนได้สถานะตามข้อที่ 2 แล้วใช้ script อีกชุดเข้าไปอ่านวัน-เวลาในไฟล์ HTML ที่บันทึกไว้ เพื่อนำไปอัพเดทลงฐานข้อมูลเมื่ออัพเดทเสร็จครบทั้งสถานะและวัน-เวลาโหวต เราจะเอาผลทั้งหมดกลับขึ้นไปที่ Google Sheets เพื่อสรุปผลการโหวตในแต่ละรอบเพื่อปิดงานรอบนั้น (ในขั้นตอนทบทวนวัน-เวลานี้เราเอารหัสโหวตที่กรอกด้วยมือไปก่อนหน้านี้ของอีกทีมหนึ่ง มาเข้ากระบวนการนี้ด้วยเช่นกัน)

นั้นหมายความว่า ทุก ๆ รหัสโหวตเราส่งเข้าเว็บ AKB48 เราจำเป็นต้องส่งรหัสโหวตเข้าเว็บโหวตอย่างน้อย 2 ครั้ง แต่ผลดีคือ มันช่วยทำให้เราสรุปยอดในแต่ละวันว่าเราส่งรหัสโหวตเข้าไปได้เท่าไหร่ และช่วยให้เราเก็บคะแนนที่ตกหล่นกลับมาโหวตได้อีกพอสมควร

ตัวอย่างการทำงานในการส่งรหัสโหวตด้วย Katalon Studio

สำหรับหน้ารายงานรหัสโหวต และจำนวนโหวตที่ Actual Vote ช่วงแรกเราใช้ Firebase Realtime Database ซึ่งทำการ Sync ข้อมูลกับ Google Sheetes อีกที ช่วงแรกข้อมูล Realtime มาก กดอัพเดทจำนวนคะแนนฝั่งหน้าเว็บรายงานขึ้นคะแนนใหม่ทันที แต่ด้วยความที่เราไม่ได้ optimize อะไรเลย (คือไม่มีเวลาทำ) สุดท้ายมันก็ไม่ได้ Realtime ตลอด เจออาการ Push ไม่ทำงาน เพราะเราไม่ได้ Index ข้อมูล มันเลยหยุด Push เพราะด้วยจำนวนของ node (รหัสโหวต) เริ่มเยอะขึ้น แถมเราเจอ charge ค่า B/W ของ Firebase เยอะตามไปด้วย เลยเปลี่ยนแผนใหม่เป็นการใช้ไฟล์ JSON data แบบอัพมือแทน โดยก็แค่ Sync จาก Google Sheets ไป export เป็นไฟล์ JSON data เดี่ยว ๆ แล้วฝั่งทีมเว็บส่วนรายงานผลแค่เอาไฟล์ไปวางที่เดิมก็จบงาน ลดค่าใช้จ่ายไปได้เยอะมาก แต่ก็มาหนักฝั่ง client ซึ่งก็เป็นเรื่องที่ช่วยไม่ได้ เพราะเอาเร็วเข้าว่า แล้วช่วง 2-3 วันสุดท้ายเราไม่ได้รายงานคะแนนสุดท้าย เพราะคิดว่ารอสรุปยอดทีเดียวหลังปิดโหวตดีกว่า เพื่อให้ทีมงานทุกส่วนได้โฟกัสกับการหาบัตรโหวต และส่งรหัสโหวตเข้าระบบ AKB48 ให้ได้จนหมดโดยไม่ตกหล่น และยืนยันผลให้พร้อมทุกอย่าง เพราะเราพลาดแบบรอบผลด่วนอีกไม่ได้แล้ว

ในส่วนของเครื่องคอมฯ ที่ส่งรหัสโหวตนั้น เราใช้ทั้งหมด 3 ตัวทำงานสลับกัน มี 1 ตัวทำงานตลอด 24 ชั่วโมง 5 วันติดต่อกันเพื่อโหวตเข้าระบบ และทบทวนผลการโหวตออกมาเป็นวัน-เวลาที่โหวตเพื่อให้แน่ใจว่าคะแนนทุกคะแนนที่ถืออยู่จะเข้าระบบจริง ๆ ซึ่งหากจำนวนคะแนนในมือเยอะกว่านี้ อาจส่งไม่ทัน เพราะจากที่ลองทดสอบค่าเฉลี่ยนในการส่งโหวต สามารถทำความเร็วได้ที่ 600-800 รหัสโหวตต่อชั่วโมง (อย่าลืมว่าเราต้องเก็บตกจากเหตุการณ์เจอหน้าเว็บ Error และทวนวัน-เวลาโหวตอีก) แต่เราก็ได้เตรียมแผนรองรับไว้แล้ว ด้วยการเตรียมเช่า Cloud VPS ที่อยู่ญี่ปุ่นเพิ่มเติมสำหรับส่งรหัสโหวตเพิ่มเติมได้ทันที (คิดว่าภายใน 4-6 ชั่วโมงน่าจะสร้างเครื่อง และพร้อมส่งรหัสโหวตได้อย่างน้อย 4 ตัว หรือเพิ่มความเร็วเป็น 2,000-3,000 โหวตต่อชั่วโมง)

สรุปว่าโครงการนี้เน้นถึกเยอะมาก script อะไรต่าง ๆ บางส่วนก็เผา และ workaround กันแบบสุด ๆ เนื่องจากรีบ และไม่มีเวลามาคิดเรื่อง seamless มากนักในบางขั้นตอน (เพราะงานประจำก็ต้องทำ คะแนนที่ต้องส่งก็เยอะขึ้นกว่าที่คาดหวังไว้ด้วย ต้องเผื่อเวลาตกหล่นไว้อย่างน้อย 1-2 วัน) ตามตารางกราฟด้านล่าง

ช่วงวันประกาศผลเลือกตั้ง ค่อนข้างลุ้นมาก เพราะตอนที่ประกาศแล้วผ่านคะแนนของโครงการไปแล้ว แล้วน้องยังไม่ได้ประกาศชื่อ โดนแซวว่า “พี่ฟอร์ดทำโปรแกรมโหวตผิดคนป่าวพี่” ทีมงานก็ใจคอไม่ดี ผมก็ใจคอไม่ดี เพราะลุ้นมาก แล้วเราทบทวนรหัสโหวตและคะแนนกันเยอะมาก

แล้วสุดท้ายก็เป็นแบบวิดีโอข้างล่างเนี่ยแหละครับ

หากมีอะไรอัพเดทหรือตกหล่น เดี่ยวค่อย ๆ อัพเดทใน blog ตอนนี้เพิ่มเติมอีกที เพราะตอนนี้นึกออกมาได้ประมาณนี้

ทิ้งท้าย ด้านล่างคือสรุป Workflow การทำงานในช่วง 7 วันสุดท้ายของการโหวต

 

 

 

ว่าด้วยการจัดเวลาไลฟ์ของเมมเบอร์ BNK48 ใน VOOV

ถ้าได้ตาม 48G ฝั่งญี่ปุ่นไลฟ์ใน Showroom มาก่อน จะหงุดหงิดกับการจัดเวลาไลฟ์ของน้อง ๆ BNK48 ใน VOOV อยู่พอสมควร เพราะฝั่งญี่ปุ่น การไลฟ์ใน 1 วันของเมมเบอร์บางคนก็มากัน 1-2 รอบก็มี บางคนเดือนนึงจะมาสักครั้ง หรืออาทิตย์นึงเจอกันครั้งนึงก็มี หรือว่าง่าย ๆ จัดสรรเวลากันแบบมาเรื่อยๆ มากันตลอด ไม่ใช่หายไปทั้งวง ผมตามอยู่เกือบ 10 คน วันนึงต้องได้ดู Showroom สักคน แล้วเวลาที่เมมเบอร์ฝั่งญี่ปุ่นไลฟ์เหมือนจะไม่ได้กำหนดระยะเวลาตายตัวว่า 1 ชั่วโมง บางคนยาว 2-3 ชั่วโมงก็มี บางคนมา 10-15 นาทีก็มี แล้วก็มักเจอไลฟ์พร้อม ๆ กันหลายคน ยิ่งช่วงหัวค่ำของที่โน้นนะ เปิด Showroom เข้าไป เจอ 5-10 คนนี่เรื่องปรกติมาก (อันนี้เกิดจากประสบการณ์ที่ได้ติดตาม แน่นอนว่าดีลธุรกิจจริงๆ เป็นอย่างไรก็อีกเรื่อง)

อย่างวันก่อนเปิดตัวโปสเตอร์เลือกตั้ง ระหว่างั้น ก็มีเมมเบอร์ญี่ปุ่น เค้าก็ไลฟ์ซ้อนกับรายการนั้นไม่ได้หยุดไลฟ์แต่อย่างใด แล้วช่วงที่มีไลฟ์เลือกตั้ง มักมีเมมเบอร์เปิดไลฟ์ใน Showroom ไปพร้อม ๆ กับแฟนคลับ พูดคุยกับแฟนคลับไป ดูไลฟ์เลือกตั้งไปด้วยก็มี

จากสิ่งที่พบเจอมา แล้วเอามาเทียบกับฝั่ง BNK48 ก็จะเห็นว่า หากสามารปรับเปลี่ยนให้ไลฟ์แบบรับผิดชอบตัวเอง จัดเวลาเอง มีกลยุทธในการไลฟ์เพื่อดึงฐานแฟนคลับ ดูว่าเวลาไหนเหมาะสมของแต่ละคน มีการนัดแฟนคลับว่าวันนี้วันนั้น ผมว่าการไลฟ์จะดูน่าติดตามกว่านี้ ยิ่งตอนนี้มีรุ่น 1 และ 2 คิดว่าจำนวนเมมเบอร์ก็น่าจะเยอะพอที่จะปรับเปลี่ยนรูปแบบการจัดเวลาในการไลฟ์ได้บ้าง อีกทั้งผลดีก็ตกอยู่กับน้องๆ เอง โดยเฉพาะรุ่น 2 ที่ยังไม่มีฐานแฟนคลับมากนัก เพราะน้องๆ ได้สร้างฐานแฟนคลับได้ง่ายมากขึ้น แล้วไม่จำเป็นต้องวันละ 1 ชั่วโมงพอดี แต่อาจจะนิดๆ หน่อยๆ แต่เก็บให้ครบเดือนละ 1-2 ชั่วโมง ก็น่าจะเป็นทางออก (ถ้าไลฟ์ชนกันก็ไม่น่าจะมีผลอะไรเพราะ VOOV ดูย้อนหลังได้อยู่ดี)

แล้วยิ่งช่วงนี้มีเลือกตั้งเมมเบอร์ฝั่งญี่ปุ่นเค้าไลฟ์กันเยอะมาก มากันทุกวันเลยก็มีบางคน ก็ออกจะเสียดายโอกาศหลายๆ อย่างเหมือนกันนะ

ตัวอย่างกลยุทธในการไลฟ์เพื่อดึงฐานแฟนคลับ ที่น่าสนใจของฝั่งญี่ปุ่น “1 Year Miracle โอนิชิ โมโมกะ เด็กสาวตี5ครึ่ง ปาฏิหาริย์ที่เกิดจากความพยายามในอีกรูปแบบหนึ่ง

ทยอยเปลี่ยนจากใช้ HDD External 3.5″ มาใช้ HDD External 2.5″

ช่วงต้นปี HDD External 2TB 2.5″ ราคาลงมาเหลือประมาณ 2,2xx – 2,3xx บาท (ล่าสุดเหลือ 1,990 บาท) ก็เลยทยอยๆ ซื้อเก็บมาเรื่อยๆ ตอนนี้ได้มา 3 ลูกแล้ว (ได้พื้นที่มา 6TB)

แต่การซื้อ HDD ดังกล่าวไม่ใช่เอามาเก็บข้อมูลเท่านั้น แต่เอามาทดแทน HDD External 3.5″ ตัวเก่าๆ ที่ใช้มานาน (3 ปีบ้าง 5 ปีบ้าง) แม้ทั้งหมดยังคงทำงานได้ดี แต่ก็ด้วยอายุที่มาก ความเสี่ยงก็มากตาม

สำหรับแนวคิดทดแทนของเก่าและใช้ของใหม่คือ ถ่ายโอนข้อมูลจาก HDD ลูกเก่ามาใส่ลูกใหม่ แล้วใช้ของใหม่แทน ส่วนของเก่าทำหน้าที่เป็นที่ backup ข้อมูลลูกใหม่ไป โดยเมื่อสำรองข้อมูลเสร็จแล้ว ก็ถอดลูกเก่าเก็บไว้แบบไม่จ่ายไฟ

ซึ่งลูกเก่าที่กำลังจะเป็นตัวสำรองข้อมูล กำลังคิดว่าจะซื้อกล่อง super lock เพิ่มเติมเพื่อมาใส่ HDD กลุ่มนี้ เพื่อเก็บรักษาสภาพให้เหมาะสม

ปล1. เหตุผลที่ไม่ซื้อ 3.5″ มาแทนตามเดิม แต่ใช้ 2.5″ แทน เพราะเรื่องความคล่องตัวในการใช้งานเป็นหลัก เพราะจากที่ใช้ 3.5″ มา 7 ตัวค่อนข้างเปลืองพื้นที่มาก ยังไม่รวมต้องหาชุดปลั๊กไฟเพิ่มเติมอีก
ปล2. เหตุผลที่ไม่ซื้อ 4TB หรือมากกว่า เพราะเน้นการกระจายความเสี่ยงเรื่องการเสียของ H/W ด้วย (ราคา 4TB ถูกกว่า 2TB x2 อยู่นิดหน่อย)