Tutorial

หยุด Dump Data ลง CSV ก่อน เรามีวิธีที่ดีกว่านี้

By Arnon Puitrakul - 08 มิถุนายน 2022 - 2 min read min(s)

หยุด Dump Data ลง CSV ก่อน เรามีวิธีที่ดีกว่านี้

เวลาเราทำงานกับข้อมูลที่เป็นพวกตัวเลข หรือข้อความ ในหลาย ๆ เวลา ที่เราต้องการที่จะ Export หรือ Import Data เข้ามา เราก็มักจะเลือก Format อย่าง CSV หรือ TSV เพราะมันเป็น Format ที่เป็น Textual Format และ โครงสร้างมันง่ายทำให้เราทำงานกับมันได้ง่ายมาก ๆ แต่เราจะบอกว่า ใน CSV และ TSV เป็นอะไรที่โคตรง่าย ไม่ต้องใช้ Library อะไรเลย เราก็อ่านเขียนเองได้ แต่มันมีปัญหาบางอย่างอยู่ และ วันนี้เราจะมานำเสนอตัวเลือกอื่นกัน

ปัญหาของ CSV

ปัญหาของ CSV จริง ๆ เท่าที่เราเจอมาหลัก ๆ เราจะเจออยู่ 2 เรื่องใหญ่ ๆ ด้วยกันคือ Format ของ Data และ Performance

เอาเรื่องแรกกันก่อนคือ Data Format ตัวนี้เรียกว่า สร้างความชิบหายให้กับเราตอนแรก ๆ เป็นอย่างดีเลยละ เราต้องเข้าใจก่อนว่า CSV และ TSV พวกนี้ มันมีลักษณะการเก็บข้อมูลแบบ Table-Like หรือ เก็บเป็นลักษณะของตาราง มีการแบ่งเป็น Column เรื่อย ๆ อย่างใน CSV ก็ตามชื่อเลยคือ Comma Seperated Value และ TSV คือ Tab Seperated Value นั่นแปลว่า เวลาเรา Parse ข้อมูลออกมาใช้งานจริง เราจะใช้ Comma และ Tab เป็นตัวกำหนด

เราก็จะอ่านไปเรื่อย ๆ จนกว่า เราจะเจอ Comma สำหรับ CSV หรือ Tab สำหรับ TSV แล้วเราก็จะเอา String ที่อ่านได้มาเป็นข้อมูลของอีก Column ในแต่ละ Row นั่นเอง เราก็จะทำแบบนี้ไปเรื่อย ๆ อะ อ่านดูแล้ว มันไม่น่าจะมีปัญหานะ

งั้นเรามาลองเจอข้อมูลที่อาจจะมี Comma หรือ Tab ประกอบอยู่ในข้อมูลดูสิ เอาละทีนี้แหละ เวลาเราอ่านออกมา มันก็จะทำให้ข้อมูลที่ได้มา ผิดไปหมดเลย ตัวอย่างง่าย ๆ เลยนะ ถ้าเรา Export Content ของเว็บนี้ออกมาเป็น CSV เพื่อหวังว่าจะไป Import ใส่ CMS ตัวอื่น ๆ บอกเลยว่า โอกาสแตกสูงมาก เพราะเว็บเรามี Content ที่เป็น Code Block ซึ่งแน่นอนว่า มีทั้ง Comma และ Tab ยั่วเยี้ยเต็มไปหมดเลยละ รอบก่อน เกือบตาย

อะ ถามว่า มันพอจะมีวิธีการ Workaround มั้ย มันก็มีนะ คือ พวก String ทั้งหลาย เราอาจจะต้องใส่พวก Escape Character อะไรพวกนั้นเข้ามาช่วย หรืออาจจะใส่ Double Quote เข้ามาช่วย แต่เราจะการันตีได้ยังไงว่า ในข้อมูลเราจะไม่มี Double Quote อีก คือ เอาเป็นว่า บนข้อมูลที่ซับซ้อน โดยเฉพาะเป็น String คือ ปวด หัว สัส ๆ เลยละ

และอีกปัญหาคือ Performance ต้องยอมรับว่า หนัก.... ไฟล์ขนาดเล็กไม่กี่ MB เรามองว่า เฉย ๆ ชิวเลย ๆ เลย เวลาอ่านมันไม่ถึงวินาทีอยู่แล้วละ แต่มันจะมีเคสที่บางคนมันล่อออกมาเลยจ๊ะ 2 GB เหยดเข้ !!!!!!!!!! มึงตลกแล้ว ~!!!!!!! %^$@$%^

ถามว่าทำไมเราต้องอุทานขนาดนี้ ต้องพาย้อนกลับไปที่การอ่าน CSV มันจะต้องค่อย ๆ อ่านและ Parse เข้ามาเป็น Sequenctial โดยที่เราไม่สามารถ Parallelise หรือ Seek ได้เลย เช่น ถ้าเราบอกว่า เราอยากได้ Record ที่ 20 เราก็ต้องค่อย ๆ อ่านลงไปเรื่อย ๆ จนเจอ Column แรกของ Record ที่ 20 แล้วค่อยเริ่มอ่านเข้ามาเก็บ หนักเลยละ หรือกลับกัน การเขียนเอง ถ้าเราต้องเขียนบนขนาดใหญ่ ๆ ก็คือ Single Thread Only เลยละ แตกพ่ายยับแน่นอน รอถึงชาติหน้าตอนเย็น ๆ แน่นอน

จะเห็นได้ว่า CSV เอาจริง ๆ มันง่าย มันก็มีปัญหาของมันอยู่เหมือนกัน ทั้งในเรื่องของ Data Type ที่เป็น String และ Performance ในการ Import และ Export เองก็ตามวันนี้เราก็จะมาแนะนำ Format อื่น ๆ กันบ้าง

Format อื่น ๆ

ถ้าเรามาดู Format อื่น ๆ นอกจาก CSV กันบ้าง ง่ายสุด ๆ แบบไม่ต้องลงอะไรเลยคือ Pickle พวกนี้คือ Object Serialisation เก็บได้หมดเลยอะไรที่อยู่บนตัวแปรใน Python ได้ หรือจะเป็น HDF5 ที่นิยมใช้เก็บข้อมูลขนาดใหญ่ ๆ กัน จนไปที่พวก Parquet และ Feather ที่เคลมว่า ของตัวเอง เบามาก ใช้พื้นที่ในการจัดเก็บน้อยมาก ๆ

Benchmark

ในการทดสอบวันนี้เราจะทำการสร้าง Pandas DataFrame ขนาด 2M Rows ด้วยกัน ซึ่งจะประกอบด้วย 3 Column ที่มี Integer, Float และ String ความยาว 50 Character แล้วเราจะเอามายัดใส่ไฟล์ของแต่ละ Format กัน

ซึ่งการ Import และ Export ทั้งหมด เราจะทำผ่าน Pandas ทั้งหมดเลยนะ โดยใช้ Default Parameter ทั้งหมด

เริ่มจาก Export Time กันก่อน อันที่นานที่สุด ไม่แปลกใจเลยก็คือ CSV โดดจากชาวบ้านชาวเมืองเขาไปไกล แล้วตามด้วย HDF5, Pickle, Parquet และเร็วที่สุดก็คือ Feather ที่เราตกใจมาก ๆ คือ Pickle นี่แหละ ไม่คิดว่ามันจะเร็วเลย แต่เอาจริง ๆ มันเป็นการ Serialise Object มันก็น่าจะเร็วอยู่แหละ

กลับกันฝั่งของการ Import กันบ้าง เราก็ทำผ่าน Pandas เหมือนเดิมเลย แน่นอนว่า ตัวที่ใช้เวลาเยอะที่สุดก็คือ CSV นั่นเอง แล้วตามด้วย Parquet, Feather, HDF และ Pickle เหยยยยย Pickle มันก็ไม่เลวเลยนะแกร ทำไมมันดูดีขนาดนั้นฟร๊ะ !!! ใช้ได้เลยนะ ชนะ Parquet และ Feather ไปอีกอะ

สุดท้าย คือ Store Size หรือขนาดของไฟล์ที่ทำการเก็บลงไปในไฟล์โดยรวมเราจะไม่เห็นว่ามันแตกต่างแบบโดด เท่ากับ 2 Benchmark ที่ผ่านมา แต่ตัวที่กินพื้นที่เยอะที่สุด ก็เห็นจะเป็น HDF ไล่ลงไปก็จะเป็น HDF, Pickle, Feather และ Parquet ซึ่งถ้าดูจาก Chart ด้านบน เราจะเห็นว่า Pickle กับ Feather มันเท่า ๆ กันเลยนะ แต่จริง ๆ มันห่างกัน 1 MB เท่านั้น โหดชิบหายเลยละ แล้วไล่ลงไปเรื่อย ๆ เลย ซึ่งอันที่เล็กสุดอย่าง Parquet ก็ไม่น่าแปลกใจเท่าไหร่ เพราะเขาก็เคลมอยู่แหละว่า ของเขาเล็กจริงอะไรจริง

เราจะเห็นได้ว่าตัววิธีการเก็บข้อมูล มันไม่ได้มีวิธีไหนที่ดีกว่ากันในทุก ๆ ด้านเลย มันมีดีกันคนละด้านที่เราเอามา Benchmark ให้ดูกัน แต่ถ้าถามเรา เรามองว่า Feather ดูจะเป็นอะไรที่กลาง ๆ สุดแล้วนะ ขนาดในการเก็บ ก็ไม่ได้ใหญ่มาก กลาง ๆ Import Time ก็กลาง ๆ ส่วน Export Time ก็เร็วสุด ๆ เลยละ น่าจะเป็น General Case พอได้อยู่ แต่สิ่งที่สรุปได้แน่นอนคือ CSV แตกแน่นอน ถ้าเราทำงานกับข้อมูลขนาดใหญ่ ดูจากผลการทดลองก็คือจะเห็นเลยว่า CSV แย่ที่สุดในทุก ๆ ด้านที่เราใช้งานกันเลย ทำให้ถ้าเราเอาไปใช้จริง ๆ เราก็จะเสียเวลา และ กินพื้นที่ในการเก็บเยอะกว่าที่ควรจะเป็นไปไกลเลย

Disclamer ตัวใหญ่ ๆ เลยคือ การทดลองนี้สามารถบอกได้นะว่า อันไหนดีที่สุด เพราะขึ้นกับข้อมูลของเราด้วย เช่น ขนาดของข้อมูล หรือประเภทของข้อมูลก็มีผลกับการจัดเก็บด้วยเช่นกัน บางอัน เราสามารถ Apply Compression Algorithm แล้วได้ Compression Ratio เยอะกว่า ก็เป็นไปได้เหมือนกัน

การเลือกประเภทการเก็บข้อมูล

เราต้องบอกก่อนนะว่าในบทความนี้ เราจะไม่ลงไป Detail ของแต่ละแบบในการจัดเก็บที่เราเอามา Benchmark เดี๋ยวยาววววว แต่เราจะเล่าถึง วิธีการเลือกดีกว่า ว่าเราควรจะดูจากอะไร แล้ว ค่อยไปดูใน Spec Sheet ของแต่ละตัวดีกว่าว่า อันไหนมันน่าจะเหมาะกับเรามากกว่า

ในการเก็บข้อมูลที่เป็นตาราง มันก็จะเก็บได้อยู่ 2 แบบด้วยกันคือ Row-Based หรือ Column-Based ถ้าเป็น Row-Based ก็คือ เราเก็บ 1 Row มีหลาย Columns อยู่ด้วยกันหมดเลย กลับกันถ้าเป็น Column-Based ก็คือใน 1 Row เราจะเก็บข้อมูลของ Column นั้น ๆ ในทุก ๆ Row เลย

อ่านแล้ว เอ๊ะ มันต่างกันยังไง สั้น ๆ คือ เราจะเก็บด้านไหนนั่นแหละ มันมีผลกับการเก็บ และ การดึงข้อมูลตรง ๆ เลย ตัวอย่างเช่น ถ้าเราบอกว่า เวลาเราดึงข้อมูล เราเน้นการดึงแบบเป็น Column ไป เพื่อเอาไปทำ Analytics เราก็อาจจะต้องเลือกเป็น Column-Based แทนที่เราจะต้องอ่านทุก Row เพื่อเอาแค่ Column เดียว เราก็อ่านแค่ Row เดียวแต่ได้ทุก Row ของ Column ที่เราต้องการ ก็ทำให้การเข้าถึงมันเร็วกว่าเยอะ ดังนั้น เราควรจะเช็คว่า การทำงานที่เราใช้จริง ๆ มันมีการดึง และ เก็บข้อมูลแบบไหน เราอาจจะต้อง Trade-off หน่อยเช่น โอเคแหละ เราทำทั้งคู่อะ แต่เราอาจะต้องมาดูว่า เราทำอะไรบ่อยกว่ากัน แล้วค่อย ๆ เลือกไป

อีกเรื่องคือ Data Type อันนี้แหละที่หลาย ๆ คนพลาด และอาจจะไม่ได้ให้ความสำคัญด้วย เช่นเราบอกว่ าเราเก็บตัวเลขละกัน เป็นเลขอายุ อะสมมุติว่า เก็บตั้งแต่ 0-100 ปีเลย ถ้าเราไม่ได้สนใจเรื่องพวกนี้เลย เราก็อาจจะต้องใช้พื้นที่ 64 Bits ในการเก็บเลย ซึ่งความจริงแล้ว ถ้าเราลองดูดี ๆ 100 มันใช้อย่างมาก 7 Bits เท่านั้นเอง ในการเก็บ นั่นแปลว่า ถ้าเราเลือก วิธีการเก็บข้อมูลที่สามารถเลือก Data Type ได้ เราก็จะยัดพวก uint8 ลงไปได้ ทำให้เราประหยัดพื้นที่ในการจัดเก็บเข้าไปอีกเยอะโดยที่เราไม่ต้องทำ Compression เลย

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

ดังนั้นเวลาเราจะเลือก เราแนะนำให้เข้าไปดูใน Spec ของแต่ละตัวเลยว่ามันทำงานอย่างไร แล้วเราก็ค่อย ๆ เลือกตัวที่เหมาะกับการทำงานของเรา เราไม่สามารถบอกได้ว่า อันนี้คือ End Game Solution Once and for All แต่เราพอจะบอกได้ว่า อันนี้เหมาะกับงานประเภทนี้นะ อะไรแบบนั้นมากกว่า ไม่งั้น มันก็ตายไปเหลือแต่ตัวที่ดีที่สุดแล้วสิเนอะ

สรุป

CSV มันไม่ได้ผิดอะไรเลยนะ แต่ถ้าเราใช้ให้ถูกเวลา ข้อดีของมันคือ โคตรง่าย และเอาไปเปิดได้ในหลาย ๆ โปรแกรมยัน Excel ที่ทำให้เราทำ Analytics เบื้องต้นได้ง่าย ๆ เลยโดยที่ไม่ต้องไปแปลงอะไรก่อนเลย แต่ถ้าเราบอกว่า เราทำงานกับข้อมูลที่ขนาดใหญ่ขึ้น การใช้ CSV อาจจะไม่ใช่อะไรที่เหมาะเท่าไหร่ จาก Benchmark ที่เราทำให้ดูเบื้องต้น ทำให้เราเห็นว่า มันมีวิธีอื่น ๆ ในการเก็บข้อมูลขนาดใหญ่ ๆ ให้ใช้พื้นที่เล็กลง และ ใช้เวลาในการ Import และ Export ที่รวดเร็วขึ้น ล่นเวลาในการทำงานได้มหาศาล ยิ่งเราบอกว่าข้อมูลเรายิ่งใหญ่ด้วยก็หนักเข้าไปใหญ่เลยใช่ม้าา ดังนั้น ก็เลือกวิธีการจัดเก็บข้อมูลให้เหมาะกับการใช้งานของเราดีกว่า จะได้ประหยัด ทั้งเงิน และ เวลาในการทำงานไปเยอะเลย

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