はじめに
2024/3/26 の Livebook ブログで紹介されている Tigris を使ってみます
Tigris は Livebook のスポンサーにもなっているので、私としても応援したいですね
実装したノートブックはこちら
Tigris とは
Tigris is a globally distributed S3-compatible object storage service that provides low latency anywhere in the world, enabling developers to store and access any amount of data for a wide range of use cases.
和訳
Tigrisは、グローバルに分散されたS3互換のオブジェクトストレージサービスで、世界中どこでも低遅延で利用できます。これにより、開発者は多様な用途に応じてあらゆる量のデータを保存およびアクセスすることが可能です。
Tigris は Amazon S3 と互換性のあるストレージサービスです
そのため、 AWS CLI や、各言語の AWS SDK から S3 と全く同じように使うことができます
S3 互換という点では以前紹介した Cloudflare R2 と同じですね
Elixir の場合は ExAWS から操作できます
最大の特徴は CDN (Contents Delivery Network)がデフォルトで付いている点です
ファイルは地理的に最も近いデータセンターからダウンロードされ、2回目以降のアクセスではキャッシュが使われるため高速にアクセス可能です
S3 の場合は CloudFront を使う必要がありますが、 Tigris は特に何もしなくても CDN が付いてきます
Tigris の料金体系
Tigris では保存するデータ容量、リクエスト数で従量課金されます
私の感覚としては、他のサービスと比べても安くなっているように思います
項目 | 料金体系 |
---|---|
保存容量 | $0.02/GB/month |
PUT, COPY, POST, LIST リクエスト | $0.005/1000 requests |
GET, SELECT, その他のリクエスト | $0.0005/1000 requests |
データ転送 | $0.00/GB |
データ転送で課金されない点も要注目です
他のストレージサービスでは「どれくらいアップロード・ダウンロード・リージョン間移動したか」でも課金されますが、ここが 0 なのは珍しいです
また、無料利用枠も用意されています
- 毎月5GBのデータストレージ
- 月間 10,000 件の PUT, COPY, POST, LIST リクエスト
- 月間 100,000 件の GET, SELECT, その他のリクエスト
Tigris のはじめ方
公式サイトの右上、 "Dashboard" をクリックします
Fly.io のサインインを求められます
実は Tigris には Fly.io のアカウントでサインインするようになっています
サインアップ、サインインすると Tigris のダッシュボードが開きます
S3 と同じようにバケット内でオブジェクトを管理します
Create a new bucket
をクリックしてバケットを作ってみましょう
モーダルでバケット名を入力し、 Create
をクリックします
バケットが作成されました
アクセスキーの作成
左メニューの "Access Keys" からアクセスキーの一覧を表示します
右上 "Create New Access Key +" をクリックするとアクセスキー作成モーダルが開きます
アクセスキー名を入力し、バケットに対する権限を選択してから "Create" をクリックします
下画像の例では全バケットの更新権限を与えています
アクセスキーが作成され、以下の情報が表示されます
- Access Key ID
- Secret Access Key
- Endpoint URL S3
- Region
注意文言にある通り、二度と Secret Access Key は表示されないため、どこか安全な場所に保存しておきましょう
閉じるとアクセスキー一覧に戻ります
ブラウザからのオブジェクト操作
バケット一覧でバケット名をクリックすると、バケット内のファイル一覧に遷移します
最初は何もファイルがない状態です
"Upload" ボタンをクリックするとモーダルが開きます
ファイルをドラッグ&ドロップして選択し、右下 "Upload" ボタンをクリックします
一覧にファイルが追加されました
ファイル名の右にあるアイコンから以下の操作が可能です
- アップロード用署名付リンク作成
- ダウンロード用署名付リンク作成
- ダウンロード
- 削除
リンクを作成する際やダウンロードを実行する際はアクセスキーを選択します
まだアクセスキーを作っていない場合は作成します
AWS CLI からオブジェクトを操作する
以下のように環境変数を設定します
コマンドは macOS や Linux の場合です
export AWS_ACCESS_KEY_ID=<Tigris のアクセスキー ID>
export AWS_SECRET_ACCESS_KEY=<Tigris のシークレットアクセスキー>
export AWS_ENDPOINT_URL_S3=https://fly.storage.tigris.dev
export AWS_REGION=auto
以下のようにバケット一覧が取得できます
$ aws s3api list-buckets
{
"Buckets": [
{
"Name": "rwakabay",
"CreationDate": "2024-06-28T04:46:02+00:00"
}
],
"Owner": {
"DisplayName": "tid_XXX",
"ID": "tid_XXX"
}
}
その他も S3 と同様に操作できます
アップロード
$ aws s3 cp ~/Downloads/ryo-000.png s3://rwakabay/
upload: Downloads/ryo-000.png to s3://rwakabay/ryo-000.png
一覧取得
$ aws s3 ls s3://rwakabay/
2024-06-29 09:07:08 249972 ryo-000.png
ダウンロード
$ aws s3 cp s3://rwakabay/ryo-000.png .
download: s3://rwakabay/ryo-000.png to ./ryo-000.png
Livebook からのオブジェクト操作
Elixir から操作する場合も S3 や R2 と全く同じに扱えます
セットアップ
S3 を操作するときと同じく、 ex_aws
と ex_aws_s3
をインストールします
また、ファイル一覧などを表形式にするため Explorer 、画像ファイルを画像処理するために Evision などもインストールします
Mix.install([
{:ex_aws, "~> 2.5"},
{:ex_aws_s3, "~> 2.4"},
{:poison, "~> 5.0"},
{:hackney, "~> 1.20"},
{:sweet_xml, "~> 0.7"},
{:explorer, "~> 0.8"},
{:evision, "~> 0.2"},
{:req, "~> 0.5"},
{:kino, "~> 0.13"}
])
エイリアスやマクロの準備をしておきます
alias ExAws.S3
alias Explorer.DataFrame
alias Explorer.Series
require Explorer.DataFrame
認証情報の入力
認証情報入力用の UI を作ります
Livebook の Secrets を使っても OK です
access_key_id_input = Kino.Input.password("ACCESS_KEY_ID")
secret_access_key_input = Kino.Input.password("SECRET_ACCESS_KEY")
endpoint_host_input = Kino.Input.text("ENDPOINT_HOST")
[
access_key_id_input,
secret_access_key_input,
endpoint_host_input
]
|> Kino.Layout.grid(columns: 3)
表示された入力エリアに Tigris 用のアクセスキーID、シークレットアクセスキー、エンドポイントのホスト名(エンドポイント URL の http://
を除いたもの)を入力します
以下のコードで接続用の認証情報を設定します
auth_config = [
access_key_id: Kino.Input.read(access_key_id_input),
secret_access_key: Kino.Input.read(secret_access_key_input),
host: Kino.Input.read(endpoint_host_input)
]
Kino.nothing()
ここから先は S3, R2 と全く同じコードです
バケット一覧の取得
S3 のときと同様、 S3.list_buckets() でバケットの一覧が取得できます
S3.list_buckets()
|> ExAws.request(auth_config)
実行結果
{:ok,
%{
body: %{
owner: %{
id: "tid_XXX",
display_name: "tid_XXX"
},
buckets: [%{name: "rwakabay", creation_date: "2024-06-28T04:46:02Z"}]
},
headers: [
{"Access-Control-Allow-Headers", "*"},
{"Access-Control-Allow-Methods", "*"},
{"Access-Control-Allow-Origin", "*"},
{"Access-Control-Expose-Headers", "*"},
{"Content-Length", "353"},
{"Content-Type", "application/xml"},
{"Server", "Tigris OS"},
{"Server-Timing", "total;dur=246"},
{"Strict-Transport-Security", "max-age=63072000; includeSubDomains; preload"},
{"X-Amz-Request-Id", "1719620781158576483"},
{"Date", "Sat, 29 Jun 2024 00:26:21 GMT"}
],
status_code: 200
}}
以下のようにしてバケット一覧を表形式で表示できます
S3.list_buckets()
|> ExAws.request!(auth_config)
|> then(& &1.body.buckets)
|> DataFrame.new()
|> DataFrame.select(["name", "creation_date"])
|> Kino.DataTable.new()
ファイル一覧の取得
バケット名を入力します
bucket_name_input = Kino.Input.text("BUCKET_ANME")
S3.list_objects_v2
でファイル一覧が取得できます
max_keys
で一度に取得するファイル数(1 ページ分のファイル数)を指定しています
{contents, next_continuation_token} =
bucket_name_input
|> Kino.Input.read()
|> S3.list_objects_v2(max_keys: 10)
|> ExAws.request!(auth_config)
|> then(&{&1.body.contents, &1.body.next_continuation_token})
実行結果
{[
%{
owner: %{id: "", display_name: ""},
size: "249972",
key: "ryo-000.png",
last_modified: "2024-06-29T00:07:08Z",
storage_class: "STANDARD",
e_tag: "\"d10f45f7c326ecbd54b34db3b2055b5e\""
},
...
],
"xxx"}
取得した next_continuation_token
を使って、次のページを取得することができます
bucket_name_input
|> Kino.Input.read()
|> S3.list_objects_v2(max_keys: 10, continuation_token: next_continuation_token)
|> ExAws.request!(auth_config)
|> then(&{&1.body.contents, &1.body.next_continuation_token})
全ファイルの一覧を取得するモジュールは以下のようになります
defmodule S3LS do
def get_contents(continuation_token, bucket_name, auth_config) do
bucket_name
|> S3.list_objects_v2(max_keys: 10, continuation_token: continuation_token)
|> ExAws.request!(auth_config)
|> then(&{&1.body.contents, &1.body.next_continuation_token})
end
def get_contents_cyclic(continuation_token, bucket_name, auth_config) do
{contents, next_token} = get_contents(continuation_token, bucket_name, auth_config)
case next_token do
# 空であれば次ページを取得しない
"" ->
contents
# 空以外の場合は次ページを取得する
_ ->
contents ++ get_contents_cyclic(next_token, bucket_name, auth_config)
end
end
def get_all_contents(bucket_name, auth_config) do
get_contents_cyclic(nil, bucket_name, auth_config)
end
end
ファイルアップロード
アップロードする画像ファイルを Web からダウンロードしておきます
ryo_path = "ryo-wakabayashi.jpg"
"https://www.elixirconf.eu/assets/images/ryo-wakabayashi.jpg"
|> Req.get!(into: File.stream!(ryo_path))
ローカルファイルのアップロード
S3.Upload.stream_file
と S3.upload
でファイルをアップロードできます
ryo_path
|> S3.Upload.stream_file()
|> S3.upload(Kino.Input.read(bucket_name_input), "ryo-wakabayashi.jpg")
|> ExAws.request!(auth_config)
インメモリのアップロード
画像ファイルを evision で読み込んでおきます
mat = Evision.imread(ryo_path)
S3.put_object
でインメモリのバイナリデータをアップロードできます
bucket_name_input
|> Kino.Input.read()
|> S3.put_object("ryo_2.jpg", Evision.imencode(".jpg", mat))
|> ExAws.request!(auth_config)
ファイルダウンロード
ローカルファイルへのダウンロード
S3.download_file
で R2 上のファイルをダウンロードできます
bucket_name_input
|> Kino.Input.read()
|> S3.download_file("ryo-wakabayashi.jpg", "ryo_downloaded.jpg")
|> ExAws.request!(auth_config)
インメモリへのダウンロード
S3.get_object
でバイナリデータとしてダウンロードできます
bucket_name_input
|> Kino.Input.read()
|> S3.get_object("ryo-wakabayashi.jpg")
|> ExAws.request!(auth_config)
|> then(&Evision.imdecode(&1.body, Evision.Constant.cv_IMREAD_COLOR()))
まとめ
Tigris を S3 や R2 と全く同じように操作することができました
料金も安く、 CDN も兼ねているため、画像などのコンテンツ配信には便利そうです