ในสภาพแวดล้อมที่มีจำนวนอีเวนต์เฉลี่ยต่อเดือนมากกว่า 10,000 ล้านรายการ มีความจำเป็นต้องวิเคราะห์ข้อมูลให้รวดเร็วเพื่อทำการวิเคราะห์ฟีเจอร์พฤติกรรมผู้ใช้ (Cohort)
(เช่น ผู้หญิงวัย 30+ ที่ใช้จ่ายมากกว่า 100,000 วอนต่อเดือนในแอปของเราในช่วง 6 เดือนที่ผ่านมา → อัตราการกลับมาใช้งานอีกครั้งของคนกลุ่มนี้)
นี่คือเรื่องราวของการลงมือสร้าง datastore ที่เดิมมีแต่ใช้งานมันในฐานะนักพัฒนา
เพื่อทำคิวรีวิเคราะห์พฤติกรรมผู้ใช้ให้ได้…
-
ต้องสามารถคิวรีเมตริกที่ไม่ได้คำนวณเตรียมไว้ล่วงหน้าได้ (+ และต้องรองรับการวิเคราะห์รูปแบบใหม่ได้โดยไม่ต้อง Re-indexing)
-
เมื่อต้อง Group By ข้อมูลอีเวนต์ตามผู้ใช้ ต้องมีคอขวดจาก High Cardinality Shuffle ให้น้อย
ลังเลว่าจะใช้โซลูชันเดิมหรือสร้างเองดี
-
Druid ถูกใช้งานอยู่ที่อื่น แต่มีข้อจำกัดของ Pre-Aggregation (วิธีที่อ่านเฉพาะค่าที่คำนวณไว้แล้ว) จึงไม่เหมาะกับการพัฒนาฟังก์ชันนี้
-
ดาต้าแวร์เฮาส์อย่าง Snowflake หรือ Redshift สามารถรันในสเกลใหญ่ได้ แต่ด้วยความเป็นระบบอเนกประสงค์ ทำให้ต้องใช้คลัสเตอร์ใหญ่เกินเป้าหมายและมีค่าใช้จ่ายสูง
-
หากต้องรองรับความต้องการที่หลากหลาย เช่น Funnel, การจับคู่ ID ฯลฯ ฐานข้อมูลแบบ SQL-based ก็มีข้อจำกัด
สุดท้ายจึงสร้าง datastore ขึ้นมาเอง
-
Luft = ดาต้าสโตร์ที่ออกแบบมาให้เหมาะกับการรันคิวรีวิเคราะห์พฤติกรรมผู้ใช้ที่ Group By ตาม user ID ตั้งแต่ต้นได้อย่างรวดเร็ว
-
สร้างบนพื้นฐานของ Golang
-
วิเคราะห์ข้อมูลผู้ใช้ระดับหลายสิบ TB ได้ด้วยจำนวนโหนดไม่เกิน 5 เครื่อง โดยใช้เวลาเฉลี่ย 3 วินาที ~ สูงสุด 10 วินาที
-
ต่างจาก RDBMS ทั่วไปตรงที่มีความเป็น immutable (หากจำเป็นก็เขียนทับข้อมูลในช่วงเวลาเดียวกัน) → ได้คลัสเตอร์ดีไซน์ที่เรียบง่าย ประสิทธิภาพสูงโดยไม่ต้องทำ page manager ที่ซับซ้อน และสามารถออกแบบฟอร์แมตการจัดเก็บข้อมูลได้ตามต้องการ
เจาะดูพื้นฐานทางเทคนิค
- TrailDB (storage engine) - Rowstore สำหรับเก็บอีเวนต์แบบ time-series ที่เหมาะกับการพาร์ทิชันตาม user ID
→ ทำ dictionary encoding กับค่าแล้วเก็บแค่ ID ของค่านั้น
→ เรียงอีเวนต์ของผู้ใช้ตามลำดับเวลา แล้วเก็บเฉพาะค่าของเวลาที่เพิ่มขึ้นจากอีเวนต์ก่อนหน้าและคอลัมน์ที่เปลี่ยนไป (เพราะคุณสมบัติของผู้ใช้ส่วนใหญ่มักไม่เปลี่ยน)
→ ไม่มีดัชนี ต้อง full scan เท่านั้น
→ แต่มีอัตราการบีบอัดสูงจนน่าตกใจ (CSV 13GB → ~TrailDB 300mb)
→ เนื่องจาก time complexity คือ O(n) จึงมองว่าหากลด space complexity ลงได้ก็น่าจะพอ
- LLVM (query engine)
→ แต่ TrailDB รองรับแค่ equals แบบ OR-AND และต้องส่งคิวรีที่ parse จาก Go ไปยัง C, C++
→ แล้วก็พบว่า PostgreSQL คอมไพล์คิวรีด้วย LLVM JiT
→ คิวรีมีการขยายความสามารถอยู่บ่อย หากเขียนด้วย C, C++ จะเพิ่มต้นทุนการพัฒนา จึงหลีกเลี่ยงปัญหานี้ด้วยการให้ Golang สร้างแค่ LLVM IR แล้วส่งต่อไปให้ฝั่ง C, C++ รันผ่าน JiT compile
- สร้างเลเยอร์ประมวลผลขึ้นมาเอง
→ ปกติมักใช้ MapReduce แต่ใช้ไม่ได้เพราะเลือกใช้ Golang
→ Spark/Hadoop เหมาะกับ Long-running Job จึงได้ประสิทธิภาพไม่ดีแม้จะพยายามเชื่อมต่อแล้วก็ตาม
→ สุดท้ายก็สร้างเอง → https://github.com/ab180/lrmr
→ ใช้การผสมกันของ gRPC + Protobuf + etcd และยืมดีไซน์ที่คุ้นเคยจาก Spark มาเยอะ
→ ยอมทิ้ง Resiliency → ถ้ารีดประสิทธิภาพให้สุด แม้เกิดปัญหาก็เริ่มใหม่ทั้งหมดได้ในเวลาไม่ถึง 10 วินาที
→ มีปัญหา buffer overflow จากการประมวลผลข้อมูลขนาดใหญ่เกิดขึ้นบ่อย (Backpressure) จึงเปลี่ยนเป็น Pull-based Event Stream (แนวทางที่ Kafka, Armeria ฯลฯ ใช้)
- ลงมือทำ sharding เอง
→ shard = historical node
→ ถ้าใช้ช่วงวันที่ของพาร์ทิชันเป็นค่า sharding key ล่ะ?
→ ทุกคิวรีมีเรื่องเวลาอยู่แล้ว → กรองได้ง่าย
→ ในช่วงเวลาเดียวกันข้อมูลมักมีขนาดใกล้เคียงกัน → กระจายข้อมูลได้ง่าย
→ แต่ระบบกระจายไม่สวยงามนัก…
→ ถ้าโหนดล่มหรือมีการเพิ่มโหนดใหม่ล่ะ?
→ ถ้าพื้นที่เก็บข้อมูลเต็มล่ะ?
→ ถ้าเกิดเหตุขัดข้องแล้วโหลดไปกระจุกที่โหนดเดียวล่ะ?
→ จึงปรับแต่ง Cost Function ของ Druid ให้ยิ่งช่วงวันที่ของพาร์ทิชันใกล้กันและทับซ้อนกันมากเท่าไร Cost ก็ยิ่งสูงขึ้น
→ เพื่อให้ shard พร้อมใช้งาน ได้ทำสิ่งต่อไปนี้
→ ตั้ง TTL ให้ข้อมูล shard และรีเฟรชเป็นระยะ (etcd)
→ เก็บพาร์ทิชันไว้ใน S3 และจัดการรายการพาร์ทิชันด้วย DynamoDB
สถานะในโปรดักชันตอนนี้
- สแกนข้อมูล 500GB ได้ภายใน 15 วินาที โดยใช้เพียงอินสแตนซ์ c5.2xlarge 4 เครื่อง
เป้าหมายต่อไป (หรือสิ่งที่ต้องทำ)
-
อยากทำการวิเคราะห์ Funnel แบบเรียลไทม์ด้วยคลัสเตอร์ไม่เกิน 10 เครื่อง
-
วางแผนรองรับ Spark เพื่อเชื่อมต่อกับ ML เป็นต้น
-
กำลังพัฒนา column store ของตัวเอง (Ziegel) เพื่อมาแทน TrailDB
→ ปรับแต่งให้รองรับ SIMD และมัลติคอร์
→ กรองล่วงหน้าตามคุณสมบัติผู้ใช้ด้วย Bitmap Index
2 ความคิดเห็น
traildbสนุกดีนะครับ https://www.youtube.com/watch?v=-oPFxSwn0lM น่าสนใจมาก แม้จะเป็นวิดีโอเก่าแล้ว แต่traildbก็คงไม่ได้เปลี่ยนแปลงอะไรในช่วงนั้นหรอกครับตอนนี้กลับไปดูแล้ว เห็นว่ามีโพสต์ในบล็อกของนักพัฒนาด้วยครับ
https://engineering.ab180.co/stories/introducing-luft
เพิ่งเคยได้ยินชื่อ TrailDB เป็นครั้งแรก แต่เจ้าตัวนี้คือประมาณนี้...
https://github.com/traildb/traildb