Episode 1.5: Deep tech dev, To infinity and beyond!!
ง่าย ๆ ไม่ ยาก ๆ ทำ
ตามที่ได้เกริ่นไปใน EP.1 https://bit.ly/BLDProductTeam ว่าสิ่งที่ Blendata ทำนั้น ไม่ใช่บริษัทที่รับ Implement software big data ในตลาด แต่เราเลือกที่จะสร้างเทคโนโลยีขึ้นมาเอง ซึ่งในเชิง Business ระยะสั้นแล้วถือว่าท้าทายมาก เพราะจริง ๆ แล้วหากเลือกที่จะรับ Implement software ในลิสต์ของ Gartner magic quadrant แบบคนปกติทั่วไป ก็จะเป็นทางเลือกที่ปลอดภัยกว่า และ logical กว่าค่อนข้างมาก ด้วยเหตุผลต่าง ๆ ไม่ว่าจะเป็น 1. มีคนคอยเทรนด์ คอยซัพพอร์ตให้ เกิดอะไรยาก ๆ มาก็เปิดเคส ไม่ต้องแบกรับความเสี่ยงทั้งหมดเอง รวมทั้งบริษัทก็หาคนมาทำในส่วนนี้ได้ง่ายกว่า 2. ในเชิงการขาย ไม่ต้องพรูฟ ลงทุนทำ Marketing กับโปรดักส์อะไรมากมาย เพราะลูกค้ามักรู้จักอยู่แล้ว และถ้าแบรนด์ดังเราก็ได้ลูกค้าตาม ซึ่งในทางนี้กินกำไรส่วนต่างและค่าแรงก็อยู่ได้สบาย
แต่แน่นอนว่าทางที่ง่ายนั้น มันขัดกับความเชื่อของเราสองข้อที่ได้เล่าไว้ใน EP ก่อน ก็เลยเลือกที่จะไม่ทำ เพราะรู้สึกว่าไม่คูล (อันหลังนี้ความเนิร์ดส่วนตัวของทีม) และจากประสบการณ์ทั้งของเราและคนรอบข้าง เราเชื่ออีกว่าทางง่ายไม่ใช่ทางที่ยั่งยืนในระยะยาว รวมถึงไม่ได้ Contribute ในการพัฒนาสังคม และจะน่าเสียดายมาก ๆ เพราะในเมื่อเรามีคนที่มีฝีมือที่จะทำของที่มีคุณภาพเทียบเท่าหรือมากกว่า Commercial product ต่างประเทศได้ แล้วทำไมจะไม่ทำล่ะ
เริ่มจากความต้องการของผู้ใช้ หาและพัฒนาเทคโนโลยีให้เหมาะสม
การเป็นผู้ใช้งานรวมถึงผู้ให้บริการโปรดักส์หลาย ๆ ตัว เราจึงได้เริ่มเห็นช่องว่างที่ยังไม่มีใครปิดได้ ซึ่งแน่นอนว่าเป็นจุดเริ่มต้นของการดีไซน์และทำโปรดักส์ให้ตอบโจทย์ตลาด ตอบโจทย์ผู้ใช้งานจริง ๆ สิ่งที่เป็นจุดยากของการพัฒนาจึงเป็นเรื่องเดียวกัน เนื่องจากไม่ใช่แค่พัฒนา Enterprise application หรือเว็บไซต์ ที่มี Best practice แชร์กันเต็มฟีด แต่เป็นการพัฒนาที่มี Constrain ควบคุมเต็มไปหมด เช่นเทคโนโลยีฐานที่ใช้จะต้องง่ายเพียงพอต่อทีมพัฒนาขนาดใหญ่ จะต้องใช้ภาษาที่หาคนมาพัฒนาและดูแลได้ง่าย ดีไซน์จะต้องดีต่อการปรับปรุงและเปลี่ยนแปลงตลอดเวลา แต่ต้องไม่เสียประสิทธิภาพในการเล่นกับข้อมูลขนาดใหญ่ รวมถึงจะต้องง่ายต่อการใช้งานอีก
หลายคนอาจจะสงสัยว่า ไม่ใช่แค่เอาเทคโนโลยีฐานดี ๆ มาใช้ก็จบเหรอ อย่างเราใช้ Apache Spark (ที่ Fork มาแก้เพิ่ม) ก็น่าจะแค่ทำตัวครอบง่าย ๆ นี่นา ที่เหลือก็ปล่อย Spark จัดการไป ก็น่าจะเพียงพอแล้ว.. ซึ่งจริง ๆ แล้ว ถึงแม้ว่าหากสมมติเราจะไม่ได้แก้อะไรตัว Spark และใช้เลยก็ตาม แต่องค์ประกอบรอบข้างก็เป็นสิ่งที่ทำให้โปรดักส์ช้าลงเกิน 50% จนถึงระบบตายได้ง่าย ๆ เลย ยกตัวอย่างเช่น (Technical alert!)
Purpose: โยนข้อมูล 1,000,000,000 records ที่มี 50 columns ให้ Spark ประมวลผลตาม logic SQL ที่ส่งไป และนำกลับมาแสดงผลบนหน้าจอในรูปแบบตารางที่ต้องทำการ Mask ข้อมูล 1 Column
Developer A: Initial spark -> โยน 1000M records เข้าในรูปแบบ CSV แล้วสร้าง table ครอบ -> ส่ง SQL -> เอา Dataframe ที่ return กลับมา วน for loop เข้า array -> access array by index ส่งเข้าฟังก์ชัน Mask -> Convert to json ก่อนส่งให้หน้าบ้านผ่าน Rest api
บางคนก็อาจจะมองว่า ก็เป็นโค้ดปกติดีนี่นา ถึงจะเวิ่นเว้อหน่อย แต่ก็ทำงานได้ แต่สำหรับเรานี่เป็นสิ่งที่ถือว่าร้ายแรงมาก เพราะอย่าลืมว่าทุก ๆ Operation ที่ทำกับข้อมูล มีราคา (Cost) ดังนั้นการเอา Dataframe ที่ได้กลับมาจาก Spark มาวนสร้าง Array แบบ for loop จึงเป็นสิ่งที่อันตรายต่อระบบมาก ๆ เพราะ 1. ช้าแน่นอน การวน for loop คือการทำ 1 by 1 มี 1000m records ก็ทำตามจำนวนนั้น ซึ่ง Java เวอร์ชันหลัง ๆ หรือ Scala มี Map operation ที่ทำ Parallel ได้ ควรใช้มากกว่า 2. 1000m records คือตัวแปรที่สร้างมาใช้ชั่วคราว ซึ่งหาก Record นึงใช้ 1 KB แปลว่าเตรียม Mem เอาไว้ 1TB เป็นอย่างน้อยที่ฝั่ง Client ของ Spark ไม่นับเวลา GC ที่ต้องมาเคลียหลังใช้เสร็จอีก เตรียมไม่พอ หรือทำบ่อย ๆ จน GC เคลียไม่ทัน ก็ตาย ดังนั้น สิ่งที่เหมาะสมกว่าจึงเป็น
Developer B: Initial Spark -> โยน 1000M เข้า ถ้ามีคนมาใช้บ่อยก็เปลี่ยนเป็น Parquet ซะ -> ส่ง SQL พร้อมฟังก์ชันการ mask ไปให้เรียบร้อย -> เอาข้อมูลจาก Dataframe ที่ได้กลับมาโชว์แบบมี Limit ไม่เอาหมด (หรือจะ implement lazy load ก็แล้วแต่ ถ้าคิดว่าจะใช้โชว์หมดจริง ๆ) -> Convert to json หรือ Serialize format แบบอื่น ๆ ถ้าปลายทาง Support ก่อนส่งผ่าน api
บางคนอาจสงสัยว่า B ก็เป็นท่าปกติหนิ แบบ A มีด้วยเหรอ? เราการันตีว่า เราเคยเจอโค้ดยิ่งกว่า A อีก เช่น ดึงข้อมูลจาก Spark แบบไม่ประมวลผลอะไรเลย -> ได้ Dataframe มาแล้ว เอามาวน for loop mkString ก่อนจะ split ด้วยอักขระเดียวกัน เพราะใช้ Data frame ตรง ๆ หรือ Convert data frame to array ไม่เป็น -> สร้างตัวแปรใน for loop ทุกบรรทัดเพื่อเอาค่าบางตัวใน Column ส่งไปคอลฟังก์ชันอื่นอีกรอบนึง -> …เอาเป็นว่ายาว สามารถทำให้ข้อมูลหลักร้อย Records ประมวลผลได้ในหลักนาทีพร้อม Garbage ที่ GC ร้องไห้ทุกนาทีได้เลย
…หยุดพัก Technical ในมุม Developer ไว้เท่านี้ก่อน สิ่งที่เราจะสื่อคือมันง่าย ถ้าแค่ทำให้ใช้ได้ แต่ถ้าทำให้ใช้ได้ดี เสถียร และรวดเร็ว ไม่ใช่เรื่องง่าย และเรากำลังทำเรื่องพวกนี้อยู่
เขียน สร้าง ใช้เทคโนโลยีให้ดีที่สุด
เป็นเรื่องปกติที่เราจะต้องทำการ Research และทดลองเทคโนโลยีต่าง ๆ เพื่อตอบโจทย์ของลูกค้า ในโมดูลที่สำคัญ ๆ เราถึงขนาดต้องทำ Benchmark เปรียบเทียบ Library หลาย ๆ ตัวด้วยตนเอง เพื่อดูว่า Library ตัวไหนเร็วสุด Cost น้อยสุด เป็น Thread safe ที่เร็วสุด ฯลฯ เพื่อให้ตรงต่อความต้องการในการใช้งาน ณ ขณะนั้นของเรา เช่น Serialize json library ตัวไหนดี เน้นเร็ว ไม่ต้อง Thread safe cost ต่ำ ๆ Implement ง่าย ๆ เป็นต้น แต่อย่างที่เกริ่นไว้แต่แรก สิ่งเหล่านี้ไม่ได้เป็นปัจจัยแรกที่เราเริ่มสร้างของแต่ละอย่าง แต่เป็นปัญหาและความต้องการของลูกค้า ที่เราได้เข้าไปช่วยแก้ หรือทำให้ดีขึ้น แล้วค่อยเลือกเทคโนโลยี ซึ่งในหลาย ๆ ครั้ง สามารถเอามา Repeat ในลูกค้าอื่น ๆ ได้ด้วย
ยกตัวอย่างเช่นลูกค้าของเรารายนึงเก็บข้อมูลทุกอย่างไว้ใน NAS และท่าพื้นฐานในการใช้ข้อมูลคือเขียน Script และตั้ง Cron job มาคอยกวาดข้อมูล ซึ่งเป็น Practice พื้นฐาน แต่ไม่ตรงกับความต้องการของลูกค้า เพราะต้องมานั่งคอยให้ถึงรอบกวาดไฟล์ของ Cron กว่าข้อมูลจะไหลตาม Flow ที่ทำไว้ได้ สิ่งที่เราทำเมื่อห้าถึงหกปี ณ ขณะนั้นคือสร้าง Agent ตัวเล็ก ๆ มาทดแทน Script เหล่านี้ โจทย์คือต้องการให้ยืดหยุ่น Scale ได้ ลงได้หลายที่ แต่จะต้องกิน Resource ต่ำจนไม่กวนโปรแกรมอื่น ๆ ในกรณีไปฝากเครื่องชาวบ้านไว้ ถึงแม้ว่าข้อมูลใหญ่สุดที่จะต้องกวาดมาแตกไฟล์ประมวลผล จะใหญ่ถึงหลักสิบถึงร้อย TB ต่อไฟล์ก็ตาม และยังต้องการให้ Dynamic มากเพียงพอที่จะรองรับ Compression ได้ประมาณเกือบ 10 types สามารถเช็คความสมบูรณ์ไฟล์ได้หลากหลายรูปแบบ และต้องการให้ทีมไม่ว่าจะเป็นเราหรือลูกค้า Configure ได้เอง ปรับเพิ่มลด แก้ไขเองได้ทั้งหมด
หกปีที่แล้วเราจึงสร้าง Agent ที่ใช้ภาษา Scala เป็นหลัก เพราะต้องการให้ยืดหยุ่นและลงได้ง่ายหลายที่ (รันบน JVM) เราใช้ Actor model ในการแบ่งงานกันทำเพื่อให้ได้ Performance ที่เร็ว + Footprint ที่น้อย รวมถึงมีความทนทานต่อปัญหาหรือการตายที่เกิดขึ้นได้ (Fault torelance) เพื่อให้ตัวเองมีความสามารถในการ Heal ตนเองได้ในระดับนึง เพราะ Agent ตัวนี้จะต้องทำงาน 24x7x365 และยังทำให้ทุกอย่างสามารถแก้ไขได้ด้วยการคอนฟิกไฟล์ในรูปแบบสมัยนิยม (ในสายนั้น ขณะนั้น) อย่างฟอร์แมต HOCON ซึ่งเป็น Superset ของ JSON ทำให้ใคร ๆ ก็คอนฟิกได้ ส่วนรายละเอียดปลีกย่อยในโปรแกรมนั้น แน่นอนว่าทำการเทสต์ไลบราลีแทบจะทุกตัวเพื่อให้กิน Resource น้อยที่สุด และคงทนที่สุดตามโจทย์
ผลลัพท์ที่ได้คือ Agent ตัวนี้สามารถแตกไฟล์และประมวลผลข้อมูลขนาดหลักสิบถึงร้อย TB วันนึงเป็นร้อย ๆ ไฟล์มาได้กว่าหกปีแล้ว โดยไม่เกิดปัญหาขึ้น (จากตนเอง) เลย รวมถึงยังกิน Resource ต่ำมาก ๆ สมชื่อ จนกลายมาเป็นหนึ่งใน Agent ในคลังของทีม Data ที่ถูกนำไปใช้งานในหลาย ๆ ที่ แก้ปัญหาดั้งเดิมของลูกค้าได้อย่างดี
หากวันนั้นเราตัดสินใจเลือกเทคโนโลยีตามใจชอบ หรือไม่ได้คัดเลือกของที่ใช้ทุกตัว หรือดีไซน์และเขียนไว้ดีเพียงพอ ระบบคงไม่เสถียรและกระทบต่อการใช้งานข้อมูลของลูกค้า หรืออาจจะใช้วิธีง่ายคือเลือกเขียนสคริปใช้ Cron job เอา ซึ่งเซฟสำหรับผู้ให้บริการอย่างเรา แต่ลูกค้าก็จะต้องรอดีเลย์ 15 นาที ถึง 1 ชั่วโมงตามรอบ แม้จะไม่ตอบโจทย์การใช้ข้อมูลของลูกค้า แต่ส่งมอบผ่าน ซึ่งนั่นก็ไม่ใช่สิ่งที่เรายึดถือและเชื่ออีกนั่นแหละ
ลึกให้สุด และไปให้กว้าง เพราะเราเชื่อในการทำโปรดักส์ให้ลูกค้า ไม่ใช่แค่แข่งขัน
เพราะสองสิ่งมักจะไปด้วยกันได้เสมอ หากมีส่วนผสมที่ลงตัว แน่นอนว่าเทคโนโลยีที่เราทำและพัฒนา แม้จะมีหลายอย่างที่อาจไม่สามารถบอกเล่าใน Blog นี้ได้ แต่สิ่งที่เรากำลังทำอยู่ตลอดคือสองแกนนี้เสมอ ตั้งแต่ ลงให้ลึก อาทิเช่น ทำยังไงให้ประมวลผลด้วย Resource เท่าเดิม ให้เร็วขึ้นกว่าเดิมอีก? จนถึงไปให้กว้าง อย่างการหาเทคโนโลยีที่เกี่ยวข้องเข้ามาทำให้โปรดักส์สมบูรณ์ขึ้น เพื่อโจทย์จริง ๆ คือให้ลูกค้าสามารถใช้ข้อมูลของเขาได้ง่ายขึ้น เร็วขึ้น Efficient ขึ้น เช่นเดียวกันกับการ Balance ระหว่างความเนิร์ดทางเทคโนโลยีของเรา ความสนุกในการหาของใหม่ ๆ เจ๋ง ๆ มาใช้ กับการตอบโจทย์ลูกค้าให้รวดเร็วและมีประสิทธิภาพ ใน Effort รวมถึง Cost ที่เหมาะสม