จัดการ lake ในแบบ git style ด้วย lakeFS และ nessie project

Pongthep Vijite
DAMAGeek
Published in
6 min readMar 6, 2022

หมายเหตุ ผู้อ่านสามารถดู table of contents ของ Data Engineering from Noob to Newbie ได้ที่ http://bit.ly/2P7isEw

หากเราจะพูดถึง data lake หรือ lake ที่เราใช้เป็นศูนย์กลางการจัดเก็บ file ข้อมูลทั้งแบบ structured และ unstructured ซึ่งโดยส่วนใหญ่ก็จะอยู่บน distributed object storage ยี่ห้อต่างๆตาม environment ของบริษัทไม่ว่าจะเป็นแบบ on premise อย่าง HDFS, MinIO และ apache ozone หรือจะใช้บน cloud เช่น S3 ของ Amazon หรือ GCS ของ Google และ Azure Blob ของ Microsoft โดยทั้งหมดนี้หลักการใช้งานเพื่อทำ lake ก็จะคล้ายกันคือเราก็จะแบ่ง layer หรือ zone ซึ่งแต่ละบริษัทก็จะมีชื่อเรียก zone แตกต่างกันออกไปอีก แต่หลักๆเราจะสามารถแบ่งออกเป็น 3 zones นั้นคือ landing zoneใช้เก็บ raw data, transformed zoneใช้จัดเก็บ raw data ที่ผ่านการ cleaning มาแล้วเพื่อให้อยู่ในรูปแบบที่พร้อมใช้งาน และสุดท้าย serving zone จัดเก็บ data model ที่เกิดจากการประมวลผลข้อมูลจาก transformed zone ซึ่งนี้คือ batch data pipeline แบบพื้นฐาน และมันก็ดูไม่มีปัญหาใช้งานใดๆ จนเมื่อเราเริ่มมี case ที่ว่าหากเราอยากมี lake มากกว่า 1 environment ละ อย่างการมี prod กับ staging และอยากให้ข้อมูลต่าง environment ยัง up to date อยู่ ซึ่งให้คิดเร็วๆก็คงต้องสร้าง pipeline มารองรับ แต่นั้นก็นำพามาซึ่งภาระการ maintain pipeline ดังกล่าว หรือถ้าเราอยาก implement การ transform ข้อมูลใน env แต่ไม่อยากให้กระทบข้อมูลตั้งต้นเพราะคนอื่นอาจจะมาใช้งานอยู่ ซึ่งกรณีนี้เราก็อาจจะต้อง copy data จาก env นั้นๆมาไว้ที่ location ใหม่ใน lake และจบลงที่เกิดเป็น folder มากมายใน lake รวมถึง data ที่เรา copy มาเป็นแค่ point of period ของ data ที่มีทั้งหมดและนำไปสู่เหตุการณ์ที่ว่า logic ที่เรา apply นั้นอาจจะใช้ได้กับ period ของ data นั้นเท่านั้นพอ deploy production อาจจะเจอ data รูปแบบอื่นที่ไม่เคยเจอใน period ข้อมูลที่ copy มาก็ได้ ซึ่ง case ทั้งหมดที่เล่ามานี้เป็นแค่ส่วนนึงที่ data engineer ต้องเจอในการจัดการ lake ทั้งสิ้น แต่ปัญหาเหล่านี้สามารถถูกจัดการได้ง่ายขึ้นด้วยการใช้งานเครื่องมือที่จะทำให้ lake เป็นคล้ายเป็น git repo อย่าง lakeFS และ nessie project กล่าวคือเราสามารถทำให้ data table ที่อยู่ใน lake มีการแยก branch และ merge กลับเข้ามา branch หลักที่มี data user ใช้งานอยู่ได้โดยไม่จำเป็นต้องทำ pipeline หรือ copy data ย้าย location ไปไหน อีกทั้งยังสามารถทำ version control ได้ในตัว และสามารถใช้งานคู่กับ distributed object storage ที่กล่าวมาข้างต้นได้เลย สุดท้ายยังรองรับการจัดเก็บข้อมูลแบบ lakehouse ไม่ว่าจะใช้ delta lake หรือ apache iceberg แต่ในส่วนของ apache hudi นั้นจะมีแค่ lakeFS เท่านั้นที่รองรับ ณ ปัจจุบัน เรามาลองเริ่มต้นที่การใช้งาน lakeFS กันก่อนเลย โดยการใช้งานที่เราจะมาลองกันในบทความนี้ focus ที่การ setup ผ่านทาง docker เท่านั้น แม้ทั้ง 2 คู่ได้จัดเตรียมชุดการ deploy บน k8s ไว้แล้ว

lakeFS

lakeFS คือ open source project (oss) ที่พัฒนาโดย treeverse.io เพื่อจัดการ lake ได้ในรูปแบบเดียวกับการใช้งาน git กล่าวคือหากเรามี file ข้อมูลอยู่ใน lake แล้วต้องการแก้ไขข้อมูลแต่ไม่อยากให้การแก้ไขนั้นกระทบกับ file ข้อมูลหลัก แทนที่เราจะต้อง copy file ข้อมูลนั้นออกมาไว้ยังอีกที่นึง เราก็แค่สร้าง branch ข้อมูลแยกออกมาจาก branch หลักหรือ main branch ซึ่งหลังจากที่เราทำแก้ไขเรียบร้อยและต้องการให้การแก้ไขข้อมูลมีผลกับ file ข้อมูลใน main branch สิ่งที่เราต้องทำก็แค่ส่งคำสั่ง merge เพื่อ apply การเปลี่ยนแปลงดังกล่าวกลับมาที่ main branch

lakeFS architecture (อ้างอิง: https://docs.lakefs.io/understand/architecture.html)

lakeFS ประกอบไปด้วย components ต่างๆดังนี้

  • S3 API Gateway: gateway ตัวนี้จะทำหน้าที่เชื่อมกับ S3 API กล่าวคือเราสามารถใช้ api ตัวเดียวที่เราใช้เขียนโปรแกรมเชื่อมต่อกับ AWS S3 เพื่อใช้สำหรับดึงข้อมูลจาก lakeFS ได้เลย รวมถึง aws cli ก็สามารถใช้ได้เช่นกัน
  • OpenAPI Gateway: gateway อีก 1 ตัวใน lakeFS จะทำหน้าที่ติดต่อฝั่ง web api สำหรับรองรับการเรียกใช้งานจากทั้ง user และ lakeFS UI
  • Frontend UI: UI สำหรับ user สามารถจัดการ lakeFS
  • Metadata Index: ทำหน้าที่จัดการ metadata ของข้อมูลต่างๆที่จัดเก็บอยู่ใน lakeFS ซึ่งเค้า claim ว่ารูปแบบการจัดการ index ของเค้าสามารถรองรับการ request การข้อมูลได้เกือบ 500k request ต่อ 1 วินาที แต่ไม่มีการแจ้งว่าทดสอบบน hardware แบบใด (https://docs.lakefs.io/understand/data-model.html)
  • Storage Adaptor: เป็น component ที่ใช้ในการดึงข้อมูลจาก storage backend ที่เรา config ไว้ ซึ่งสามารถเป็นได้ทั้งแบบ local, s3, gcs และ blob

ขั้นตอนการติดตั้งในแบบ docker สามารถใช้คำสั่ง curl https://compose.lakefs.io | docker-compose -f - up -d เพื่อ start ตัว docker compose ได้เลย หรืออีกทางคือ clone lakeFS จาก github (https://github.com/treeverse/lakeFS) แล้วค่อย start ตัว docker compose ด้วยคำสั่ง docker-compose up -d แต่จากการทดสอบ (05/03/2022) การ start docker compose จาก git version พบ error ตามภาพด้านข้าง ฉะนั้นเราจะไปทาง curl แทน

error จากการ start docker compose จาก git

หลังจากเรา start docker เรียบร้อยแล้วก็จะมาถึงขั้นตอนการ set up เบื้องต้นด้วยการใส่ชื่อ admin ไป จากนั้นระบบจะแสดงผล access key และ secret key เพื่อใช้ในการ login ซึ่งสามารถทำตามขั้นตอนได้ดังนี้ https://docs.lakefs.io/quickstart/repository.html และขั้นตอนต่อไปเราก็จะทำการลองสร้าง repo กัน

การสร้าง repo

จากรูปข้างต้นเราจะทำการสร้าง repo retail โดยจัดเก็บในแบบ local ตาม path ดังนี้ /lake/retail คำถามต่อมาคือแล้ว file จริงๆนั้นถูกเก็บไว้ที่ใด ซึ่ง default ของ local storage นั้นจะอยู่ที่ home directory (/home/lakefs/) และด้วยรูปแบบที่เรา setup lakeFS ด้วย docker ข้อมูลดังกล่าวจึงอยู่ใน home directory ภายใน docker container อีกที

directory สำหรับ local storage

ทีนี้เราจะมาลองใช้ pyspark เพื่อเขียนข้อมูลลงใน lakeFS กัน

เขียนข้อมูล user ลงใน repo retail

ซึ่งหากเราไปดูในส่วนของ lakeFS UI ก็จะพบว่ามีข้อมูลที่เราเพิ่มเข้ามา แต่จริงๆแล้วข้อมูลนั้นยังไม่ถูก commit เข้า main branch นั้นเท่ากับว่าหาก user คนอื่นมีการใช้งาน main branch ตอนนี้จะยังไม่พบข้อมูลใดๆ

ข้อมูล userใน main branch
commit change ใน main branch

ต่อไปเราจะมาลองสร้าง branch แยกจากนั้นก็ทำการเขียนข้อมูลเพิ่มเติม

สร้าง dev branch

อย่างที่เราทราบกันหากเราจัดเก็บข้อมูลใน lake ด้วยการใช้ pyspark คู่กับ file format พื้นฐานอย่าง parquet เราจะไม่สามารถอ่านจาก lake/user และเขียนลง lake/user ได้ตรงๆ เราจึงจำเป็นต้องอ่านจาก lake/user จากนั้นเพิ่ม row แล้ว save ไว้ที่ tmp/user จากนั้นค่อยดึงจาก tmp/user กลับมาเขียนใน lake/user

add row เพิ่มจากนั้นเขียนลง dev branch อีก location
commit change ใน dev branch

จากนั้นขั้นตอนสุดท้ายหากเราต้องการให้ข้อมูลที่ทำการเพิ่มใน dev branch มีผลกับ main branch ก็สามารถทำการ merge กลับเข้า main branch ได้จาก UI

merge branch
main branch มีข้อมูลที่ถูกเพิ่มเข้ามาเรียบร้อยแล้ว

เมื่อเราทำการอ่านข้อมูลจาก main branch ก็จะพบว่าข้อมูล row ใหม่ที่เพิ่มเข้ามา

อ่านข้อมูลจาก main branch
ข้อมูลใน user dataframe

ถึงตรงนี้เราน่าจะพอมองเห็นการใช้งาน lakeFS กันในเบื้องต้นแล้วใน part ต่อไปเราจะมาลองใช้ nessie project เพื่อทำงานในลักษณะเดียวกันดูครับ

nessie project

nessie project หรือ nessie คือ oss ในกลุ่มจัดการ lake ให้มีความสามารถของ git ที่พัฒนาโดย dremio ซึ่งเป็นทีมผู้สร้าง Apache Arrow โดยตัว nessie ถูกสร้างเพื่อใช้คู่กับ table format อย่าง apache iceberg และ delta lake เป็นหลัก ประกอบไปด้วย 2 ส่วนคือ nessie service ซึ่งเป็น Java-based REST API server และ DB ที่ใช้จัดเก็บ metadata โดยปัจจุบันรองรับ RocksDB, DynamoDB และMongoDB

การ start nessie ในแบบ docker นั้นสามารถทำได้โดยใช้คำสั่งdocker run -p 19120:19120 projectnessie/nessie

nessie ui หลังจาก start contrainer

จากหน้า ui ของ nessie นั้น เราจะเห็นได้ว่าไม่มีการสร้าง repo ใดๆก่อนการใช้งาน อีกทั้ง ui ทำหน้าที่แค่แสดงผลเท่านั้น ไม่สามารถส่งคำสั่งการใช้งานอย่างการสร้าง branch ได้ ซึ่งเราสามารถเลือกส่งคำสั่งการใช้งานได้ทาง coding และ cli(https://nessie.readthedocs.io/en/latest/cli.html) เท่านั้น โดยตัวอย่างที่เราจะใช้งานกันคือสร้าง table user ใน db ที่ชื่อ retail เช่นเดียวกับที่เราทดลองในตัวอย่างของ lakeFS เริ่มแรกเราจะทำการสร้าง iceberg table ใน main branch กันก่อน โดย full_path_to_warehouse ใน code ด้านล่างจะเป็น path ใน laptop ที่ใช้ run pyspark ไม่ใช่ path ใน docker container เหมือนดังเช่น lakeFS

pyspark เพื่อสร้าง iceberg table ชื่อ user
table user ก็จะแสดงใน ui หลังสร้างเสร็จ

หลังจากสร้าง table user แล้วเราจะทำการอ่าน parquet file ของข้อมูล user แล้วทำการ save ในรูปแบบ iceberg

เขียนข้อมูลลงใน iceberg table
commit history หลังจากเขียนข้อมูล

ต่อมาเราจะทำการสร้าง dev branch จาก main branch

สร้าง dev branch
dev branch ในหน้า ui

ถัดไปเราจะทำการ add ข้อมูลใหม่เข้าสู่ user table ใน dev branch

add ข้อมูลใน dev branch
commit history ของ dev branch

เพื่อให้ข้อมูลที่เราทำการเพิ่มใน dev branch มีผลกับ main branch เราต้องการ merge branch

merge branch
commit history ใน main branch
query table user ใน main branch
ข้อมูล table user ใน main branch

เปรียบเทียบ lakeFS และ nessie (จากการทดลองเล่นใน 1 วัน)

  • การติดตั้ง

ทั้งคู่ติดตั้งง่ายเพราะมี docker version รวมถึงมีชุดการติดตั้งบน k8s มาให้ในกรณีที่เราจะทำไปใช้ในงาน production

  • การจัดการ branch

ตัว lakeFS ส่งคำสั่งการจัดการ branch ได้ทั้งทาง ui และ cli ในขณะที่ nessie สามารถทำได้ผ่านทาง coding และ cli ซึ่งในหัวข้อนี้ผมมองว่าได้เปรียบคนละแบบ โดยที่ ui ของ lakeFS ก็ทำงานง่ายดีแค่ click แต่หากต้องการทำให้เป็น automate อาจจะไม่สะดวก ในขณะที่ nessie สามารถทำ automate ผ่านทาง coding ของ spark ได้เลย แต่ในส่วนของ ui นั้นทำได้แค่แสดงผลข้อมูลจริงๆ

  • การจัดการ storage

lakeFS จะ focus ที่ภาพกว้างของทั้ง lake คือเราต้องทำการเลือกเลยว่า storage ด้านหลังของ lake นี้จะอยู่ที่ไหน จากนั้นก็ใช้งานผ่านทาง lakeFS ที่จุดเดียวเพื่อจัดการข้อมูลของ lake นั้น ในขณะที่ nessie focus ที่การจัดการระดับ table ของ lakehouse อย่าง iceberg หรือ delta lake โดยไม่จำเป็นต้องมีการ config เพื่อบอกว่า storage ที่เราจะใช้อยู่ที่ใด

  • การจัดเก็บข้อมูล

nessie สามารถจัดเก็บข้อมูลในรูปแบบของ table format โดยใช้ iceberg หรือ delta lake คู่กับ spark หรือ flink เท่านั้น ในขณะที่ lakeFS ค่อนข้างเปิดกว้างในเรื่องนี้อย่างมาก ซึ่งจะไม่จำกัดว่าต้องใช้ spark ในการที่ต้องเขียนข้อมูลลง lakeFS เราสามารถ upload object เข้า lakeFS ผ่านทาง ui ได้ด้วยซํ้า

  • การเชื่อมต่อกับ tools อื่นๆ

ในมุมของ lakeFS นั้นมีการทำตัวอย่างการเชื่อมต่อกับ tools ต่างๆไว้มากมาย https://docs.lakefs.io/integrations/ ซึ่งก็แทบจะไม่แปลกใจนักเพราะการใช้งานของ lakeFS นั้นเข้ากันได้กับ aws s3 api ซึ่งหาก tools นั้นเชื่อมกับ aws s3 ได้ก็สามารถเชื่อมกับ lakeFS ได้เช่นกัน ส่วน nessie จะเน้นเฉพาะ tools ที่เข้ากับ iceberg หรือ delta lake ได้เท่านั้น อย่างถ้าเรามี Trino ซึ่งรองรับการ query iceberg ก็สามารถใช้สำหรับ query nessie ได้เลยเช่นกัน

  • ความปลอดภัย

ทั้งคู่มีรูปแบบการป้องการ branch ในรูปแบบเดียวกับ git ทั้งคู่

สรุป

แม้ lakeFS และ nessie จะบอกว่าตัวเองเป็นเครื่องมือในการจัดการ lake ในแบบ git-like ทั้งคู่ แต่เอาเข้าจริงๆทั้งคู่ focus ภาพที่แตกต่าง ซึ่งในมุมมองผมหากเราอยากจะมี lake เช่น s3 แต่รองรับการสร้าง branch ในแบบ git ก็เหมาะที่จะไปทาง lakeFS แต่ถ้าจะ focus ที่การ maintain lakehouse อย่าง iceberg ให้รองรับการทำงานแบบ git การไป nessie ก็ดูจะง่ายกว่าในการใช้งาน

อีกประเด็นที่ผมอยาก point ในส่วนของ nessie และ iceberg คือแม้ iceberg จะเป็นเครื่องมือทำ lakehouse ที่มีการรองรับ ACID transactions อยู่แล้ว โดยบ้างท่านอาจจะสงสัยแล้วทำไมต้องมี nessie ซึ่งผมจะอธิบายแบบนี้ว่าการกระทำการใดๆใน iceberg มันมี version control ก็จริงแต่มันเกิดจากการปรับเปลี่ยน data ของ table นั้นแบบตรงๆ เช่นมีการ add row ใหม่เข้า table และเกิด row ใหม่นั้นเป็นข้อมูลที่ผิดพลาด แม้ว่าเราจะสามารถ rollback ข้อมูลได้แต่ action การ add row นั้นได้เกิดขึ้นกับ table ไปแล้วซึ่งอาจจะกระทบกับ user อื่นหากในจังหวะนั้นมีการเรียกใช้งานก่อนการ rollback เค้าก็จะเห็น row ใหม่ที่เพิ่มเข้าไปอยู่ดี แต่หากเราใช้งานคู่กับ nessie โดยการแยก branch ซึ่งปกติ main branch คือจุดที่เราจะให้ user เข้ามา query ข้อมูล ส่วนในกรณีที่เราต้องการเพิ่มข้อมูลแบบยังไม่อยากให้ไปแสดงผลใน table ที่ user กำลังใช้เราก็สามารถแยก branch ออกมาได้ และค่อย merge กลับไป main branch เมื่อต้องการ

สุดท้ายนี้ถ้าท่านใดมีข้อสงสัยหรือคำชี้แนะใดๆสามารถฝากข้อความได้ที่ https://www.facebook.com/coeffest/ นะครับ ขอบคุณมากครับที่ติดตาม

สำหรับคนที่สนใจ Data Engineer สามารถเข้ามาแชร์ข้อมูลกันที่ได้ที่ https://www.facebook.com/groups/dataengineerth

--

--