เดตาซายแอนซ์·คาเฟ่

ทำไม column store ถึงมีความสำคัญกับงาน analytics?


analytics performance

ผู้เขียนได้มีโอกาสทำงานร่วมกับองค์กรแห่งหนึ่งที่กำลังเริ่มนำ predictive analytics มาเพิ่มประสิทธิภาพให้กับองค์กร ในระหว่างนั้นได้เกิดประเด็นขึ้นมาประเด็นหนึ่งว่าทำไมงานในแต่ละ batch มันใช้เวลาทำงานนานมากๆ วันนี้จึงอยากจะขอสรุปสิ่งที่ได้คุยกันมาเล่าให้ฟังในคาเฟ่

OLTP workload กับ OLAP workload

สำหรับองค์กรทั่วไปงานหลักสำหรับ DBMS ตัวแรกมักจะไม่พ้นเรื่องการเก็บข้อมูลในลักษณะ transaction ไม่ว่าจะเป็นข้อมูลบัญชี, ข้อมูลสินค้าคงคลัง, หรือ ข้อมูลการสั่งซื้อของลูกค้าเป็นต้น ซึึ่งเราสามารถเรียกงานลักษณะนี้ได้ว่าเป็น OLTP (transactional) workload

นอกจากที่นำ DBMS มาช่วยให้องค์กรทำงานได้อย่างมีประสิทธิภาพแล้ว ก็มักจะมีการนำข้อมูลที่มีอยู่มาทำให้เกิดประโยชน์เพิ่มขึ้น เช่นการ ออก report, ทำ BI, สร้าง dashboard, หรือทำ predictive analytics เป็นต้น โดยงานลักษณะนี้จะเรียกว่าเป็น OLAP (analytical) workload

องค์กรดังกล่าวมีแต่ traditional DBMSและเมื่อนำ DBMS ดังกล่าวมาทำงาน analytics จึงเกิดประเด็นขึ้นมาว่าทำไมถึงใช้เวลาในการทำงานนานเกินงาม

เหตุผลหลักเหตุผลหนึ่งก็คือ DBMS ทั่วไปถูกออกแบบมาให้เก่งกับ OLTP workload และปัจจัยหลักปัจจัยหนึ่งที่จะบอกว่า DBMS เก่งกับ workload แบบไหน คือวิธีการเขียนข้อมูลลงใน disk หรือ memory ครับ

Row store กับ column store

table sales_data

rowid | sales_id (pk) | grand_total | cust_id | cust_gender | cust_region | cust_province
001 | S01 | 2000 | A01 | M | CENTRAL | BANGKOK
002 | S02 | 500 | A02 | F | CENTRAL | NONTHABURI
003 | S03 | 850 | A03 | M | NORTH | CHIANGMAI

แม้ว่า logical design ของ table จะมีหน้าตาที่เหมือนกัน คือเป็นตารางที่มี row และ column แต่ physical design ในการเขียนข้อมูลลงใน disk แบบ row store กับ column store นั้นแตกต่างกัน

Row store

001:S01,2000,A01,M,CENTRAL,BANGKOK;
002:S02,500,A02,F,CENTRAL,NONTHABURI;
003:S03,850,A03,M,NORTH,CHIANGMAI;

Column store

S01:001;S02:002;S03:003
2000:001;500:002;850:003
A01:001;A02:002;A03:003
M:001;F:002;M:003
CENTRAL:001;CENTRAL:002;NORTH:003
BANGKOK:001;NONTHABURI:002;CHIANGMAI:003

ตัวอย่างง่ายๆ ของ OLTP workload บน data store ที่ต่างกัน

SELECT * FROM sales_data WHERE sales_id = 'S02'

row store : DBMS ค้นหา index พบว่า S02 อยู่ที่ row 002 แล้วจึงเริ่มอ่านข้อมูลที่ row 002 จนหมด row (48 bytes)

column store : DBMS ค้นหา index พบว่า S02 อยู่ที่ row 002 แล้วจึงทำการอ่านข้อมูลตั้งแต่

  • byte แรกของ sales_id column จนได้ sales id ของ row 002 (16 bytes)
  • byte แรกของ grand_total column จนได้ grand total ของ row 002 ( 17 bytes )
  • byte แรกของ cust_id column จนได้ customer id ของ row 002 ( 16 bytes)
  • byte แรกของ cust_gender column จนได้ customer gender ของ row 002 ( 12 bytes)
  • byte แรกของ cust_region column จนได้ customer region ของ row 002 ( 24 bytes)
  • byte แรกของ cust_province column จนได้ customer province ของ row 002 ( 27 bytes)

รวมทั้งสิ้น 112 bytes ที่ต้องอ่านจาก disk

เรามาลองดู OLAP workload บน data store ทั้งสองแบบของเราบ้าง

SELECT AVERAGE(grand_total) FROM sales_data

row store : DBMS จะ scan ตั้งแต่ row แรกจนถึง row สุดท้ายเพื่อหาค่าเฉลี่ยของ grand_total

  • row 001 (19 bytes)
  • row 002 (17 bytes)
  • row 003 (17 bytes)

รวม 53 bytes ที่ต้องอ่านจากdisk

column store : DBMS อ่านข้อมูลทั้งหมดจาก column grand_total ทั้งหมด 24 bytes

จากตัวอย่างง่ายๆข้างต้นจะพบว่าถ้างานที่สนใจข้อมูลทั้ง row, row store จะอ่านข้อมูลจาก disk น้อยกว่า ในขณะที่งานที่สนใจในบาง column, column store จะอ่านข้อมูลจาก disk น้อยกว่า

และนี่ก็คือหนึ่งในหลายๆเหตุผลที่งาน analytics ที่มักจะ scan data set ทั้งหมดแต่สนใจเฉพาะบาง column จึงทำงานได้รวดเร็วกว่าบนระบบที่เก็บข้อมูลใน column store


ผู้เขียนได้ทดลองแบ่ง unit ย่อยของ analytical workload บน DBMS ขององค์กรดังกล่าวมาลองรันบน Spark SQL (parquet file) พบว่า Spark (single-node) ที่มี core และ memory น้อยกว่า DBMS สามารถประมวลผลได้เร็วกว่าประมาณ 10-15 เท่า