Tutorial

CSV อื้ม... ใหญ่ไป เรามี Parquet

By Arnon Puitrakul - 09 กันยายน 2021

CSV อื้ม... ใหญ่ไป เรามี Parquet

เวลาเราทำงานกับข้อมูลส่วนใหญ่ หรือ แม้กระทั่งไปเราโหลดข้อมูลจากเว็บต่าง ๆ เราก็จะได้ข้อมูลมาในรูปแบบของ CSV (Comma Seperated Values) สำหรับข้อมูลเล็ก ๆ มันก็ไม่น่าจะไม่มีปัญหาเท่าไหร่ เราจะ Query หรือ Reduce อะไรก็ไม่น่าใช่ปัญหา แต่ ถ้าข้อมูลของเราใหญ่ขึ้นละ นั่นแหละ การใช้ CSV มันเริ่มจะเป็นปัญหาแล้ว วันนี้เรามีของเล่นตัวนึงมาเพื่อจัดการกับปัญหาพวกนี้เลยคือ Parquet บอกเลยว่าใช้ไม่ยาก และโหดใช้ได้เลย

รู้จักกับ CSV กันก่อน

ก่อนที่เราจะไปรู้จักกับ Parquet เราอยากจะพามาทำความรู้จักกับไฟล์ที่เราใช้เก็บข้อมูลกันบ่อย ๆ อย่าง CSV กันก่อน อย่างที่เราบอก มันย่อมาจาก Comma Sperated Values สิ่งที่มันทำ มันก็ทำตามชื่อเลยคือ การคั่นค่า (Value) ด้วยเครื่องหมาย Comma ทำให้ลักษณะข้อของข้อมูลก็จะเป็น หลาย ๆ บรรทัด โดยที่แต่ละบรรทัด มันจะเป็น ข้อมูลใน Column นั้น ๆ และคั่นด้วย Comma เป็นอันจบ เราเรียกการเก็บข้อมูลแบบนี้ว่า Column-Based Storage

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

input_file = open('sample.csv', 'r')

results = []
line = input_file.readline()
headers = line.rstrip().split(',')
target_col_index = headers.index('target_column')

while (line := input_file.readline()) != '' :
    
    items = line.rstrip().split(',')
    results.append(items[target_col_index])


input_file.close()

วิธีที่เราจะดึงข้อมูลสัก 1 Column ขึ้นมา เราจะต้องไล่อ่านข้อมูลทีละ Record หรือก็คือ ทีละบรรทัด และ ตัดเฉพาะส่วนที่เราต้องการออกมา ทำให้การอ่าน CSV ขนาดใหญ่ ๆ สัก 1-2 Columns เป็นเรื่องที่มีราคาแพงมาก ใช้เวลาในการทำงานนานมาก ๆ ทำให้มันเป็นปัญหาของ CSV เลยก็ว่าได้

Parquet มาช่วยแล้ว !

Parquet เป็นรูปแบบของการเก็บข้อมูลแบบนึงที่อยู่ใน Apache สิ่งที่มันต่างจาก CSV อย่างสิ้นเชิงเลยคือ วิธีการเก็บข้อมูล มันใช้การเก็บข้อมูลแบบ Columns-based ถ้าเราบอกแค่นี้ หลาย ๆ คนก็อาจจะคิดว่า แล้วมันจะต่างกันยังไงละ แค่เปลี่ยนวิธีการเก็บเท่านั้นเอง สุดท้ายข้อมูลมันก็ต้องมีขนาดเท่ากันสิ มันจะมาช่วยเรื่องขนาดอะไรได้ยังไง

ก่อนเราจะไปคุยเรื่องขนาด เราอยากมาพูดถึงวิธีการเข้าถึงข้อมูลก่อน ด้วยความที่ Parquet มันเป็น Column-based Storage ทำให้เวลาเราจะเข้าถึงข้อมูลเป็น Column มันจะทำได้อย่างเร็วเลยละ เพราะมันไม่ต้องมานั่ง Loop แล้ว Filter ของที่เราไม่ต้องการออก แต่มันสามารถดูดสิ่งที่เราต้องการมาให้ได้เลยตรง ๆ ทำให้เร็วกว่า

ส่วนเรื่องขนาด บอกเลยว่า การทำ Columns-based ทำให้ขนาดต่างได้แน่นอน ย้อนกลับไปที่เรื่องของการทำ Data Compression ที่ถ้า Entropy ของข้อมูลยิ่งเยอะ ก็ทำให้เรามีโอกาสที่จะได้ Compression Ratio สูงมากขึ้นเท่านั้น ใช่แล้ว ใน Column เดียวกัน มันเป็นข้อมูลเรื่องเดียวกัน นั่นแปลว่า ค่ามันก็น่าจะมีความใกล้เคียงอะไรอยู่บ้างแหละ ทำให้มี Entropy ที่สูง สูงกว่าการเก็บพวก CSV เยอะมาก ๆ เลยละ เลยทำให้ Parquet ยังไง ๆ ก็มีขนาดที่เล็กกว่า CSV แน่นอน

Hand-on กับ Parquet

ในการทำงานกับ Parquet บน Python สามารถทำได้ง่ายมาก ๆ โดยเฉพาะถ้าเราทำงานกับ Pandas อยู่แล้ว เพราะใน Pandas มันมีคำสั่งสำหรับการอ่าน และ เขียน Parquet Format มาให้เราเลย เราไม่ต้องไปสนใจอะไรแล้ว เรามาลองด้วยการ สร้าง ข้อมูลมั่ว ๆ ขนาดใหญ่ ๆ ดูกันดีกว่า น่าจะทำให้เห็นได้ว่า ถ้าเราบันทึกเป็น CSV และ Parquet ขนาดมันจะต่างกันได้มากแค่ไหน

import pandas as pd
import random
import time

n = 1000000

sample_df = pd.DataFrame({
   'col_a' : [random.randint(0,10000) for i in range(n)],
   'col_b' : [random.randint(0,10000) for i in range(n)],
   'col_c' : [random.randint(0,10000) for i in range(n)],
   'col_d' : [random.randint(0,10000) for i in range(n)],
   'col_e' : [random.randint(0,10000) for i in range(n)],
   'col_f' : [random.randint(0,10000) for i in range(n)],
})

time_start = time.time()
sample_df.to_csv('sample.csv', index=False)
csv_elapsed = time.time() - time_start

time_start = time.time()
sample_df.to_parquet('sample.parquet', index=False)
parquet_elapsed = time.time() - time_start()

print('Saving CSV' , csv_elapsed)
print('Saving Parquet' , parquet_elapsed)
Saving CSV 2.129817247390747
Saving Parquet 0.1676945686340332

อันนี้เราลองรันใน Google Colab เราจะเห็นได้เลยว่า เวลาที่เราใช้ในการบันทึกข้อมูล เร็วมาก ๆ เร็วกว่า 1170.06% เลยทีเดียว ถือว่าโหดมาก ๆ ความเร็วในการเขียนเรียกได้ว่า ทำเอา CSV ร้องไห้กลับบ้านเลยทีเดียว

import os

print('CSV File Size', os.stat('sample.csv').st_size, 'Byte(s)')
print('Parquet File Size', os.stat('sample.parquet').st_size, 'Byte(s)')
CSV File Size 29336706 Byte(s)
Parquet File Size 10758017 Byte(s)

ถ้าเราลองมาดูขนาดของไฟล์ที่เขียนลงไปบ้าง ไม่ต้องดูอะไรมาก เราก็เห็นเลยตรง ๆ ว่า Parquet มีขนาดที่เล็กกว่าอย่างเห็นได้ชัดมาก ๆ ถ้าคิดเป็น Percentage ขนาดของไฟล์ Parquet เล็กกว่า CSV ถึง 63.33% เลย ถือว่าเยอะมาก ๆ ลองคิดว่า ถ้าเราคุยกับคนที่ถือข้อมูลขนาดสัก 1-10 TB ดูคือจะเห็นความต่างได้ชัดเจนเลยทีเดียว

start_time = time.time()
parquet_df = pd.read_parquet('sample.parquet')
parquet_elapsed = time.time() - start_time

start_time = time.time()
csv_df = pd.read_csv('sample.csv')
csv_elapsed = time.time() - start_time

print('Loading CSV' , csv_elapsed)
print('Loading Parquet' , parquet_elapsed)
Loading CSV 0.34826040267944336
Loading Parquet 0.08813595771789551

หลังจากเราเขียนลงไปในเครื่องแล้ว เราจะต้องโหลดกลับเข้ามาได้แล้ว เราลองมาเทียบเวลาในการโหลดกันบ้างดีกว่า เช่นเดิม ดูที่ผลลัพธ์แล้วก็คือ เห้อออ เลยทีเดียว เพราะ Parquet ทำได้เร็วกว่า CSV แน่ ๆ ถ้าคิดเป็น Percentage ก็คือลดเวลาลงได้ถึง 74.69% เลย

สรุป

เขียนไปเขียนมา เหมือนมาป้ายยา ให้เปลี่ยนมาใช้ Parquet กันหมด แต่ใจเย็น ๆ ก่อนฮ่า ๆ ถึงแม้กว่า Parquet จะเก็บข้อมูลได้โคตรมีประสิทธิภาพสูงมาก ๆ และสามารถเอาไปใช้งานได้เยอะมาก ๆ แต่ ๆ เอาเข้าจริง เวลาเราใช้งาน ถ้าเก็บข้อมูลง่าย ๆ เราก็จะเก็บเป็น CSV นะ เพราะ CSV มันสามารถเปิดได้ในหลาย ๆ โปรแกรม ทำให้มันเป็น Format กลางเวลาเราจะเอาข้อมูลไปทำงานในหลาย ๆ โปรแกรมอยู่ดี แต่เราจะใช้ Parquet สำหรับการเก็บข้อมูลที่เราจะทำงานแค่บน Python หรือว่าเป็นข้อมูลที่ เราจะเก็บยาว ๆ ก็จะใช้แหละ เพื่อประหยัดพื้นที่

Read Next...

จัดการข้อมูลบน Pandas ยังไงให้เร็ว 1000x ด้วย Vectorisation

จัดการข้อมูลบน Pandas ยังไงให้เร็ว 1000x ด้วย Vectorisation

เวลาเราทำงานกับข้อมูลอย่าง Pandas DataFrame หนึ่งในงานที่เราเขียนลงไปให้มันทำคือ การ Apply Function เข้าไป ถ้าข้อมูลมีขนาดเล็ก มันไม่มีปัญหาเท่าไหร่ แต่ถ้าข้อมูลของเราใหญ่ มันอีกเรื่องเลย ถ้าเราจะเขียนให้เร็วที่สุด เราจะทำได้โดยวิธีใดบ้าง วันนี้เรามาดูกัน...

ปั่นความเร็ว Python Script เกือบ 700 เท่าด้วย JIT บน Numba

ปั่นความเร็ว Python Script เกือบ 700 เท่าด้วย JIT บน Numba

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

Humanise the Number in Python with "Humanize"

Humanise the Number in Python with "Humanize"

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

ทำไม 0.3 + 0.6 ถึงได้ 0.8999999 กับปัญหา Floating Point Approximation

ทำไม 0.3 + 0.6 ถึงได้ 0.8999999 กับปัญหา Floating Point Approximation

การทำงานกับตัวเลขทศนิยมบนคอมพิวเตอร์มันมีความลับซ่อนอยู่ เราอาจจะเคยเจอเคสที่ เอา 0.3 + 0.6 แล้วมันได้ 0.899 ซ้ำไปเรื่อย ๆ ไม่ได้ 0.9 เพราะคอมพิวเตอร์ไม่ได้มองระบบทศนิยมเหมือนกับคนนั่นเอง บางตัวมันไม่สามารถเก็บได้ เลยจำเป็นจะต้องประมาณเอา เราเลยเรียกว่า Floating Point Approximation...