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...

สร้าง Book Tracking Library ด้วย Obsidian

สร้าง Book Tracking Library ด้วย Obsidian

เราเป็นคนที่อ่านกับซื้อหนังสือเยอะมาก ปัญหานึงที่ประสบมาหลายรอบและน่าหงุดหงิดมาก ๆ คือ ซื้อหนังสือซ้ำเจ้าค่ะ ทำให้เราจะต้องมีระบบง่าย ๆ สักตัวในการจัดการ วันนี้เลยจะมาเล่าวิธีการที่เราใช้ Obsidian ในการจัดการหนังสือที่เรามีกัน...

Garbage Collector บน Python ทำงานอย่างไร

Garbage Collector บน Python ทำงานอย่างไร

หากเราเรียนลงลึกไปในภาษาใหม่ ๆ อย่าง Python และ Java โดยเฉพาะในเรื่องของการจัดการ Memory ว่าเขาใช้ Garbage Collection นะ ว่าแต่มันทำงานยังไง วันนี้เราจะมาเล่าให้อ่านกันว่า จริง ๆ แล้วมันทำงานอย่างไร และมันมีเคสใดที่อาจจะหลุดจนเราต้องเข้ามาจัดการเองบ้าง...

ติดตั้ง Zigbee Dongle บน Synology NAS กับ Home Assistant

ติดตั้ง Zigbee Dongle บน Synology NAS กับ Home Assistant

ก่อนหน้านี้เราเปลี่ยนมาใช้ Zigbee Dongle กับ Home Assistant พบว่าเสถียรขึ้นเยอะมาก อุปกรณ์แทบไม่หลุดออกจากระบบเลย แต่การติดตั้งมันเข้ากับ Synology DSM นั้นมีรายละเอียดมากกว่าอันอื่นนิดหน่อย วันนี้เราจะมาเล่าวิธีการเพื่อใครเอาไปทำกัน...

โหลด CSV วิธีไหนเร็วที่สุด ?

โหลด CSV วิธีไหนเร็วที่สุด ?

เมื่อหลายวันก่อนมีพี่ที่รู้จักกันมาถามว่า เราจะโหลด CSV ยังไงให้เร็วที่สุด เป็นคำถามที่ดูเหมือนง่ายนะ แต่พอมานั่งคิด ๆ ต่อ เห้ย มันมีอะไรสนุก ๆ ในนั้นเยอะเลยนี่หว่า วันนี้เราจะมาเล่าให้อ่านกันว่า มันมีวิธีการอย่างไรบ้าง และวิธีไหนเร็วที่สุด เหมาะกับงานแบบไหน...