🐸

lakeFS入門

2022/07/24に公開

https://docs.lakefs.io/

この文章

  • lakeFSおもしろそうなので調べてみたよ
  • データをバージョン管理できるよ
  • (おおよそ)S3互換のAPIでアクセスできるよ。S3アクセスするクライアント・ライブラリ使えるよ

lakeFSとは

公式ドキュメントでは、

lakeFS transforms object storage buckets into data lake repositories that expose a Git-like interface. By design, it works with data of any size.

と紹介されています。意訳すると、

  • オブジェクトストレージを、Git likeなインターフェイスで操作できるようにするよ
  • 大きなサイズのデータも大丈夫だよ

というわけです。

「Git like」なる用語がでましたが、lakeFSでは具体的には、

  • ファイルをオブジェクトストレージにアップロード(gitの作業ディレクトリに置くことに相当)
  • コミット
  • ブランチで変更を分離
  • 履歴の閲覧
  • revertで履歴に戻す

といったGitっぽい操作を行うことができます。これにより、

  • 実験やデバッグでデータ変えるときは、本番のデータと分離し影響が起きないように
  • データのチェックを行った後に本番に反映する
  • データに問題があった時に、大丈夫な時点までの巻き戻し

などができるようになります。

アーキテクチャ

lakeFSのアーキテクチャをドキュメントで確認することができます。lakeFSは以下のコンポーネントで構成されています。

  • API Gatway(S3互換APIとlakeFS固有API)
    • Hadoopエコシステムは別口があり(初回以降はオブジェクトストレージ直接)、大規模データ時にパフォーマンスが悪化しないようにしているらしいです
  • Web UI
  • ストレージアダプター
    • S3/GCS/Azure Blob Storageとの変換
  • Graveler
    • バージョニングされたオブジェクトを、オブジェクトストレージの実際の場所にするコンポーネントです
  • 認証・認可
  • Hook
  • PostgreSQL
    • Gravelerや認証・認可の情報を保存します

試してみる

(以下Window10 + WSL2 + Ubuntu20.04で試しました)

前述のアーキテクチャ図にある通り、lakeFSでは

  • ファイルを実際に保存するオブジェクトストレージ
  • APIサーバー
  • メタデータを保存するデータベース

などのコンポーネントが必要です。lakeFSのドキュメントでは

がQuickStartのセットアップ方法として紹介されています(本番のセットアップは別に紹介)。

クラスメソッドさんの記事では、Docker Composeで試していましたので、ここではPlayground試してみます。Playgroundではリモートに必要なコンポーネントをリモートの設定してくれるため、上記の方法では一番簡単に始められると思います。

Playground

  1. Demoページを開く
  2. 少し待つと、Playground URL、Access Key、Secret Keyが表示されます
  3. PlaygroundのURLにアクセスすると、管理画面が表示されます。デフォルトで「my-repo」なるリポジトリが作成されています

七日間維持されるらしいです。

Playground URLを開くとこんな画面が表示されます。ファイルのアップロードやコミットもできます。

ファイルのアップロードや取得はAWS CLIから行います(他のS3クライアントでも可)。必要な設定を行うと、AWS CLIでs3コマンド使えるようになります。

$ aws configure set --profile lakefs aws_access_key_id アクセスキー
$ aws configure set --profile lakefs aws_secret_access_key シークレットアクセスキー
$ aws s3 --profile lakefs --endpoint-url PlaygroundのURL ls
2022-07-23 11:10:59 my-repo
# cpコマンドとかも使えます
$ aws s3 --profile lakefs --endpoint-url PlaygroundのURL cp s3://my-repo/main/README.md -
Welcome to lakefs
For your convenience, we've created a first repository with some sample data
(以下省略)

コミットやプッシュなどはlakectlというコマンド(もしくはWeb UI)から行います。lakectlをインストールします。

$ sudo wget http://treeverse-clients-us-east.s3-website-us-east-1.amazonaws.com/lakectl/0.68.0/linux_amd64/lakectl -O /usr/local/bin/lakectl
 
 $ echo "credentials:
 access_>     access_key_id: アクセスキー
    secr>     secret_access_key:シークレットアクセスキー
> server:
>     endpoint_url: https://PlaygroundのURL/api/v1" > ~/.lakectl.yaml

$ sudo chmod a+x /usr/local/bin/lakectl
# コマンド実行できることの確認
$ lakectl repo list
+------------+-------------------------------+------------------+-----------------------------------------------------------------------------+
| REPOSITORY | CREATION DATE                 | DEFAULT REF NAME | STORAGE NAMESPACE
                          |
+------------+-------------------------------+------------------+-----------------------------------------------------------------------------+
| my-repo    | 2022-07-23 11:10:59 +0900 JST | main             | s3://treeverse-demo-lakefs-storage-production/user_obliging-haddock/my-repo |
+------------+-------------------------------+------------------+-----------------------------------------------------------------------------+

コミット

「Git like」の言葉通り、コミットの概念もあります。コミットとはなんぞやというのも本家Gitと同じで、

A commit is a point-in-time snapshot of the branch. It’s a collection of object metadata and data, including paths and the object contents and metadata.

言及されています

lakeFSでコミットを行うには、

  • ファイルをアップロード(この段階では「uncommitted」な状態)
  • lakectlからコミット

の二段階の手順を踏みます。なお、 下ではAWS CLI使っていますが、lakefsctlやlakeFSの管理画面でも可能です。

まずはアップロードします。endpointの指定がある以外はS3でアップロードする時と同様です。

# アップロード
$ echo hoge | aws  --profile lakefs --endpoint-url https://PlaygroundのURL/  s3 cp - s3://my-repo/main/hoge.txt
# 確認
$ aws s3 --profile lakefs --endpoint-url https://PlaygroundのURL/ ls s3://my-repo/main/
                           PRE sample_data/
2022-04-14 12:42:36       2758 README.md
2022-07-23 12:29:06          5 hoge.txt

ファイルが置かれていますね。

uncommittedになっています。

続けてコミットします。

$ lakectl commit lakefs://my-repo/main -m "first commit"
Branch: lakefs://my-repo/main
Commit for branch "main" completed.

ID: 47340dba20feba6abaa4492001b2baa5bbf7356300e7e3f0e0230755da3ed053
Message: first commit
Timestamp: 2022-07-23 12:32:04 +0900 JST
Parents: 58dd468ba6fadf15ebc1fd7c0e80d4164519b306a4c804c9c95da24351c22690

ログにコミットが追加されています。

$ lakectl log lakefs://my-repo/main

ID:            47340dba20feba6abaa4492001b2baa5bbf7356300e7e3f0e0230755da3ed053
Author:        admin
Date:          2022-07-23 12:32:04 +0900 JST

        first commit


ID:            58dd468ba6fadf15ebc1fd7c0e80d4164519b306a4c804c9c95da24351c22690
Author:        admin
Date:          2022-04-14 12:45:16 +0900 JST

        Added README.md


ID:            c185731f4fc46776d6f7408420aa912a6ea5d239b655804ff30b0390baf35d35
Author:        admin
Date:          2022-04-14 07:33:52 +0900 JST

        Data ingested from https://registry.opendata.aws/daylight-osm/


ID:            eb25458122a9845360a3803e3ca115fa7f3dfc953cfbdaaa6988e117d570a93c
Date:          2022-04-14 06:35:17 +0900 JST

        Repository created

当然uncommittedではなくなっています。

ブランチ・マージ

commitと同様にブランチもあります。ブランチの定義もGitと同様で

A branch is a mutable pointer to a commit and its staging area.

言及されています

試しに、元からあるmainブランチから、新しくnewブランチを作成します。

$ lakectl branch create lakefs://my-repo/new --source lakefs://my-repo/main
Source ref: lakefs://my-repo/main
created branch 'new' 47340dba20feba6abaa4492001b2baa5bbf7356300e7e3f0e0230755da3ed053
notrogue@DESKTOP-6QTOO0L:~/project/lakefs$ lakectl branch list lakefs://my-repo
+--------+------------------------------------------------------------------+
| BRANCH | COMMIT ID                                                        |
+--------+------------------------------------------------------------------+
| main   | 47340dba20feba6abaa4492001b2baa5bbf7356300e7e3f0e0230755da3ed053 |
| new    | 47340dba20feba6abaa4492001b2baa5bbf7356300e7e3f0e0230755da3ed053 |
+--------+--------------------------------------------------

ブランチでファイルが隔離されていることを確認するために、mainブランチにファイル追加します。

# アップロード
$ echo hoge2 | lakectl fs upload -s -  lakefs://my-repo/main/hoge2
# コミット
$ lakectl commit lakefs://my-repo/main --message "Add hoge2"

ブランチで差が生じています

# mainにはhoge2がある
$ lakectl fs ls lakefs://my-repo/main/
object          2022-04-14 12:42:36 +0900 JST    2.8 kB          README.md
object          2022-07-23 12:29:06 +0900 JST    5 B             hoge.txt
object          2022-07-23 19:43:26 +0900 JST    6 B             hoge2
common_prefix                                                    sample_data/
# newにはhoge2がない
$ lakectl fs ls lakefs://my-repo/new/
object          2022-04-14 12:42:36 +0900 JST    2.8 kB          README.md
object          2022-07-23 12:29:06 +0900 JST    5 B             hoge.txt
common_prefix                                                    sample_data/

マージ・コンフリクトさせるとどうなる?

コミットがありブランチがあるので、当然マージもあります
普通にマージ成功する場合はクラスメソッドさんの記事を見ていただくとして、ここではコンフリクトさせてみます。

newブランチの既存ファイルに、「new line」という行を追加します。

# アップロードとコミット
$ cat <(lakectl fs cat lakefs://my-repo/new/hoge.txt) <(echo new line) | lakectl fs upload -s -  lakefs://my-repo/new/hoge.txt
$ lakectl commit lakefs://my-repo/new --message "Add new line to hoge.txt"

# 追加を確認
$ lakectl fs cat lakefs://my-repo/new/hoge.txt
hoge
new line

# mainの方は変わっていないことを確認
$ lakectl fs cat lakefs://my-repo/main/hoge.txt
hoge

同様に、mainの方で同じファイルに別の行(「new line for main」)を追加します。

$ cat <(lakectl fs cat lakefs://my-repo/main/hoge.txt) <(echo new line for
main) | lakectl fs upload -s -  lakefs://my-repo/main/hoge.txt
lakectl fs cat lakefs://my-repo/main/hoge.txt
hoge
new line for main
$ lakectl commit lakefs://my-repo/main --message "Add new line for main to hoge.txt"

マージしてみます。

$ lakectl merge  lakefs://my-repo/new lakefs://my-repo/main
Source: lakefs://my-repo/new
Destination: lakefs://my-repo/main
Conflict found.

失敗しましたね。Web UIでも教えてくれます

lakeFSはコンフリクトは見つけてくれますが、解決はしてくれません。ユーザーが自分でやってねとのことです

https://docs.lakefs.io/understand/branching-model.html

Unlike Git, lakeFS doesn’t care about the contents of an object - if we try to merge two branches that update the same file, it’s up to the user to resolve this conflict.

コンフリクト解決のために、newブランチの内容をmainに合わせてみます

$ lakectl commit lakefs://my-repo/new --message "Change to new line for mai
n in hoge.txt"

今度はmergeできました

$ lakectl merge  lakefs://my-repo/new lakefs://my-repo/main
Source: lakefs://my-repo/new
Destination: lakefs://my-repo/main
Merged "new" into "main" to get "a9b015e0502c98f7feb09830faaa13632ae64763f6a6f0293cb160bcada2804c"

reset

uncommittedな変更(アップロードしてcommitしていない変更)は、gitと同様にresetでなかったことにできます。

# uncommittedなファイルを作る
$ echo 'should be removed' | lakectl fs upload -s -  lakefs://my-repo/main/
$ lakectl fs ls lakefs://my-repo/main/
object          2022-04-14 12:42:36 +0900 JST    2.8 kB          README.md
object          2022-07-23 20:33:47 +0900 JST    18 B            buzz
object          2022-07-23 20:27:38 +0900 JST    23 B            hoge.txt
object          2022-07-23 19:43:26 +0900 JST    6 B             hoge2
common_prefix                                                    sample_data/

# resetすると
$ lakectl branch reset lakefs://my-repo/main  --prefix buzz
# uncommittedなファイルが消えている
$ lakectl fs ls lakefs://my-repo/main/
object          2022-04-14 12:42:36 +0900 JST    2.8 kB          README.md
object          2022-07-23 20:27:38 +0900 JST    23 B            hoge.txt
object          2022-07-23 19:43:26 +0900 JST    6 B             hoge2
common_prefix                                                    sample_data/

他のシステムとの連携

ここまでAWS CLIで操作していましたが、それが可能な理由は、lakeFSはS3互換のAPIを実装(※)しているためです。ファイルの操作(アップロードやダウンロード)を行うことが可能です。

※このリストのAPI以外はサポートしていないので、正確には「S3のAPIのサブセットに互換」?

Integrationのページを見ると、S3を利用するサービス・プロダクトでlakeFSを使う際の設定などを確認できます。
(このページに記載以外は使えないという訳ではないと思います)

  • AWSサービス(Athena, Glue, SageMaker)
  • Hadoop・Sparkエコシステム(Spark, Hive, Presto/Trino)
  • その他ビッグデータ系(dbt, Airflow, DeltaLake)

ためしにPythonでいくつか操作してみました。

boto3

boto3はPythonでAWSサービスを操作するライブラリです。lakeFSのintegrationでも紹介されており、普通に使うことが出来ます。

下記はファイルの一覧を試してみましたが、ドキュメントにはアップロード等の記載もあります

import os
import boto3

s3 = boto3.client('s3',
    endpoint_url=os.environ['LAKE_URL'],
    aws_access_key_id=os.environ['LAKE_ACCESS_KEY'],
    aws_secret_access_key=os.environ['LAKE_SECRET'],
)

list_resp = s3.list_objects_v2(
    Bucket='my-repo',
    Prefix='main/',
    Delimiter='/'
)

for obj in list_resp['Contents']:
    print(obj['Key'])
# 環境変数は適当に設定
python3 boto3_test.py  
main/README.md
main/hoge.txt

lakeFS client

boto3はAWS/S3のライブラリなので、lakeFS固有の処理(コミットとか)はできません。lakeFS固有の操作を行うためには、Pythonクライアントライブラリを使用します。

とりあえずブランチの一覧を試してみます。

import os
import lakefs_client
from lakefs_client import models
from lakefs_client.client import LakeFSClient

configuration = lakefs_client.Configuration()
configuration.username = os.environ['LAKE_ACCESS_KEY']
configuration.password = os.environ['LAKE_SECRET']
configuration.host = os.environ['LAKE_URL']

client = LakeFSClient(configuration)
print(client.branches.list_branches('my-repo'))

ブランチの情報が取れました()

python3 -m pip install lakefs_client
python3 lake_client.py 
{'pagination': {'has_more': False,
                'max_per_page': 1000,
                'next_offset': '',
                'results': 2},
 'results': [{'commit_id': 'a9b015e0502c98f7feb09830faaa13632ae64763f6a6f0293cb160bcada2804c',
              'id': 'main'},
             {'commit_id': '831f1b3307086c236506a7cb2bfcde5e9cfa1ade1f2f9693d75cbac6766ded6d',
              'id': 'new'}]}

本番デプロイ

クイックスタートではdocker-composeによるセットアップが紹介されていますが、本番利用時は別途

毎にドキュメントがあります。いずれのクラウド環境でも

  • PostgreSQL(e.g AWSならCloudSQL)
  • APIが動く環境(e.g. AWSならEC2からECS)
  • ロードバランサー
  • オブジェクトストレージ

を用意するようです。

類似プロダクト・サービス

lakeFSのような、データをバージョン管理するコンセプトのサービス・プロダクトは他にもあります。

オブジェクトジェクトストレージ(S3/GCS)のバージョニング

S3にはバージョニングという概念があります(GCSやAzure Blobも同様)。
バージョニングでは過去のある時点のオブジェクトを復元することができ、この点ではlakeFSと似ている部分もあります。

lakeFSとバージョニングを比べると、以下のような違いがありそうです。

  • lakeFSはファイルの集まり(コミット)単位、オブジェクトストレージのバージョニングはファイル単位
  • lakeFSは明示的にコミット、バージョニングはファイル変更(追加や削除、更新)毎
  • lakeFSは追加のコンポーネント(APIサーバー等)が絡む。バージョニングはオブジェクトストレージのみ

DVC

(DVC詳しくないので間違っていればツッコんでください)

lakeFSは

  • コードと同じように、データを履歴管理したい
  • 大きなファイルやバイナリは通常のGitでは使いにくい

という観点ですが、似たような観点のDVCというプロダクトもあります。

  • データをバージョン管理する
  • Git Likeなコマンド
  • オブジェクトストレージにデータを保存(することもできる)

という点で近いソフトウェアで、「どっちがいいかな?」みたいな比較質問もあります(特に答えはないです)。

色々違う箇所はあると思いますが、lakeFSはデータ取得やアップロードはS3(メインは)互換のAPIというの一番大きな違いな気がします。
lakeFSでは既存のS3クライアントがそのまま使えますが、ミドルウェア(APIサーバやDB)を用意する必要があります。

DeltaLake/Hudi/Iceberg

DeltaLake/Apache Hudi/Apache IcebergはTime Travelと呼ばれる、ある時点でのデータでクエリを実行する機能があります((e.g. Delta Lakeのタイムトラベル機能))

履歴管理という点では、lakeFSと似てなくもないですが、

  • lakeFSはファイフォーマット非依存(画像等も)
  • S3互換 API

といった違いがあります。

雑感

データをバージョニングできること、また、S3互換のAPIがあることが魅力的(な場合もある)とは思いますが、下の点が気になりました。

  • APIレイヤーが増える関係で、オブジェクトストレージ可用性・パフォーマンスがどうしても下がると思います。そこの担保、そのデメリットを補うほどのメリットがあるか
  • オブジェクトストレージ固有の機能(ライフサイクルとか)との兼ね合い。S3互換APIとは言え、使えなくなる機能ありそう
  • DVCとの使い分け

Discussion