LoginSignup
12
1

More than 3 years have passed since last update.

Datomic Ions ワークショップに参加した

Last updated at Posted at 2019-12-16

Clojure/Conj 2019

昨年に引き続き、Cognitect社が本拠を置くノースカロライナ州ダーラムにて開催されたClojure/Conj 2019に参加してきました。2回目ということで変化をつけるべく、カンファレンスの前日に開催されたDatomic Ionsワークショップに参加してきました。Cognitect社の社長であり、Datomicの主要開発者であるStuart Hallowayが講師としてハンズオンで教えてくれる形式でした。本稿では質疑応答で学んだことを混じえながらIonsを紹介します。

Datomic Ionsとは

一文でまとめると、Datomic Cloud上で、Datomicのデータを必要とするビジネスロジックを、ネットワーク越しにクエリするオーバーヘッドなしに実行するための仕組みです。

本稿はDatomic Cloudをある程度理解していることが前提となります。初めての方はDatomic on AWSをご参照ください。

Ionsが登場した経緯は、Datomic APIの変遷をたどることで理解することができます。

Datomic APIの変遷

Peer API

Datomicは当初、Peer APIのみがサポートされていました。Peer APIは、各々のアプリケーションのクラスパスに、DynamoDBなどのストレージにアクセスするためのライブラリと、Datalogのエンジンを含めることで、アプリのプロセス内でクエリを実行する仕組みになっていました。利点として、一つのDatalogクエリで複数のDBを対象に実行できること、取得したデータや新たにトランザクトされたデータがプロセス内のHeapにキャッシュとして残るため、同一のデータを対象にしたクエリ(SQLと異なり、前回実行したクエリに限りません)はストレージにアクセスする必要がないため、実質インメモリデータベースとしての速度を得られる点が挙げられます。

Client API

しかし、Peer APIはDatomicとアプリケーションが深く結合しているモデルなので、Datomicとアプリケーションを分離して管理することができません。また、マイクロサービスアーキテクチャやサーバーレスでは、プロセスの寿命が短いため、そのたびにキャッシュを捨ててコールドスタートとなります。プロセスの数が増えると、ストレージサービスへのコネクション数も増えることになり、選択したストレージの種類によってはコネクション管理のオーバヘッドが問題になる場合もあるでしょう。
それらの短所を補うために、Peer Serverという、Peer API部分だけを独立したプロセスとして分離し、トランザクションとクエリをHTTPプロトコルで受け取り、結果をレスポンスとして返すサーバを提供するとともに、そのサーバと対話するためのHTTPクライアントベースのライブラリをClient APIとして提供するようになりました。

Client APIの欠点

アプリケーションからPeer ServerへのHTTP問い合わせが増えた分のパフォーマンスオーバーヘッドが生じるようになりました。
また、クエリはPeer Server内で実行されるため、Peer APIでは可能だった、JavaとClojure Core以外の関数を使ったカスタムロジックをトランザクションやクエリに含めることができなくなりました。

PeerServerはDB名を引数に起動しているため、複数DBにまたがってのクエリができません。この点についてStuart Hallowayは、Client API設計時の考慮モレで、理論的には容易に対応できるが、既存コードのリファクタリングが必要となるため、技術的負債として認識しているとのことでした。

Classpath Functions

カスタムロジックの問題に対処するため、Datomic TransactorやPeer ServerにカスタムJARファイルを含める手段が提供されるようになりました。
Datomic OnPremではユーザーが実行環境を管理しているので、カスタムJARファイルを追加するのは自分の責任で行うことができますが、Datomic Cloudでは環境がCloudFormationで標準化しているため、Cognitect社としてはカスタムJARファイルを、マネージドサービスとして追加する手段を講じる必要がでてきました。

Ionsの登場

Datomic Cloudのユーザーである開発者が、カスタムJARファイルをトランザクションやクエリのためにCloud環境にデプロイする仕組みをさらに発展させ、ビジネスロジックそのものも同じ仕組みの上で動かそうというアイデアがIonsになります。つまり、Client APIの短所であった、HTTPリクエストのホップを1段減らし、ビジネスロジックとPeer Serverを同一のサーバ上で動作させることでパフォーマンスの向上を狙うソリューションとなっています。

Ions開発の流れ

実践する前に、開発の大まかな流れを押さえておきましょう。
Ionsアーキテクチャ

開発

JARレベルだけでなく、namespaceレベルでも依存関係を分析し、ソースコードを差分としてS3にアップロードするため、LeiningenやBootではなく、tools.depsをプロジェクト管理ツールとして用いる必要があります。
tools.depsを使うにあたってのTipsをまとめてあるので、ご参照ください。
Clojure CLI (tools.deps)を使いやすくするためのTips

com.datomic/ion-dev:ions-devエイリアスの依存関係として定義し、このエイリアスを使ってpush, deployなどの作業を行います。(例: clojure -A:ions-dev '{:op :push}')

datomic/ion-config.ednをresourcesに作成します。

datomic-ion-config.edn
{:app-name "myapp" ;; <- 必須。Lambdaの関数名の一部として使われる
 :lambdas          ;; <- CloudWatch Eventなどから直接呼び出す関数
   {:hello-world {:fn myapp.lambda/hello-world 
                  :description "Hello World"}}
 :http-direct      ;; <- API Gateway経由で呼び出すHTTPハンドラ
   {:handler-fn myapp.http/hello-world}
 :allow            ;; <- Transact/Query fnとして呼び出せるようにする
   [myapp.db-fn/valid-name?]}

Push

開発が一段落ついた時点で、コードをS3に保存することができます。このPushはGit pushとは異なります。IonsはGitに依存していますが、GitHubなどのリモートリポジトリには依存していません。

まず、コードをローカルのGitリポにコミットします。Git Stageがクリーンな場合のみIons pushを実行できます。Ions pushは現時点でのソースコードと依存関係をS3 bucketにアップロードします。2回め以降は変更部分のみが差分としてアップロードされます。

deps.edn内で、依存関係のprocurerに:local/rootを使うこともできます。それにより、開発途上のライブラリを含めてデプロイすることが可能です。ただしこの場合、ソースコードがimmutableではないので、ランダムなsuffixがバージョン末尾についてユニーク性を担保します。

clojure -A:ions-dev '{:op :push}' を実行すると下記のようなメッセージがレスポンスとして送られてきます。

...
:deploy-command
 "clojure -A:ion-dev '{:op :deploy, :group myapp-Compute-RANDOM_ID, :rev \"d38bb30be124d878b37eae5b50ae32201fd459ef\"}'",
 :doc
 "To deploy to myapp-Compute-RANDOM_ID, issue the :deploy-command"}

このコマンドをコピーペーストしてDeployすることができます。

Deploy

実行するとstatusを取得するためのコマンドが表示されるので、コピーペーストしてデプロイが完了したかどうかを確認することができます。

{:execution-arn
 arn:aws:states:us-west-2:000000000000:execution:datomic-myapp-Compute-RANDOM_ID:myapp-Compute-RANDOM_ID-d38bb30be124d878b37eae5b50ae32201fd45-1574279299988,
 :status-command
 "clojure -A:ion-dev '{:op :deploy-status, :execution-arn arn:aws:states:us-west-2:000000000000:execution:datomic-myapp-Compute-RANDOM_ID:myapp-Compute-RANDOM_ID-d38bb30be124d878b37eae5b50ae32201fd45-1574279299988}'",
 :doc
 "To check the status of your deployment, issue the :status-command."}

ステータスを確認します。

clojure -A:ion-dev '{:op :deploy-status, :execution-arn arn:aws:states:us-west-2:000000000000:execution:datomic-myapp-Compute-RANDOM_ID:myapp-Compute-RANDOM_ID-d38bb30be124d878b37eae5b50ae32201fd45-1574279299988}'

{:deploy-status "RUNNING", :code-deploy-status "RUNNING"}

clojure -A:ion-dev '{:op :deploy-status, :execution-arn arn:aws:states:us-west-2:000000000000:execution:datomic-myapp-Compute-RANDOM_ID:myapp-Compute-RANDOM_ID-d38bb30be124d878b37eae5b50ae32201fd45-1574279299988}'

{:deploy-status "SUCCEEDED", :code-deploy-status "SUCCEEDED"}

Ionsの利点

CD(Continuous Delivery)機能

Datomic IonsはAWS S3, CodeDeploy, Step Functionsを活用したCDパイプラインを提供しています。開発者はトランザクション・クエリファンクションやビジネスロジックをClojureの関数として開発することだけに集中し、pushdeployを行うCLIコマンドを実行するだけでコードをAWS環境にデプロイすることができます。

データとビジネスロジックの距離の近さ

データアクセスを行うビジネスロジックとPeer Serverが同じサーバ群で動作するので、例えば業務種別ごとに専用のComputing Groupを割り当てることで、必要となるデータを集中的にキャッシュするローカリティを実現することができます。

カスタムJARによるトランザクション・クエリ関数の拡張

上で述べたように、サードパーティライブラリを使ってDatomicの動作を拡張することができます。

CloudWatch Eventとの連携

デプロイされたビジネスロジックは、Lambdaを経由して実行することができるので、様々なCloudWatch Eventタイプをトリガーとして利用することが可能になります。

API Gateway経由のHTTPエンドポイント

ビジネスロジックはLambda経由で実行できるので、API Gatewayを経由してHTTPエンドポイントとして公開することが可能です。

ビジネスロジックとPeer Serverのオートスケール

ビジネスロジックとPeer Serverが同じサーバ群で動作するので、機能別にグループ分けしておけば、特定の機能の需要が高まった場合に、まとめてスケールさせることができます。

やってみる

Datomic Cloud環境の立ち上げ

Datomicのサイトに行くと、AWS Marketplaceのバッジがあるので、ここからスタートします。
Datomic_-_Overview.png

Ionsを利用するためには、
Setting upの指示に従って設定してください。AWS Marketplaceの登録情報が間違っていたり、最新になっていない場合があるので、設定確認画面で、最新のリリースが選択されているか確認してください。

AWS_Marketplace__Datomic_Cloud.png

Ionsを動かすには新しいClojure CLIが必要ですので、バージョンを確認して、必要であれば最新版に更新してください。

チュートリアルを動かしてみる

学習用のリポジトリがあるのでクローンします。
https://github.com/datomic/ion-starter

deps.edn

deps.edn内でion-devを依存関係として定義します。このJarはデプロイ作業のみに使われるだけで、アプリケーション実行に必要なランタイムの依存関係としては必要ありません。つまり、Ionsとしてデプロイする関数はIons固有のnamespaceを依存関係として定義する必要がないということです。

{...
 :mvn/repos {"datomic-cloud" {:url "s3://datomic-releases-1fc2183a/maven/releases"}
              "central"      {:url "https://repo1.maven.org/maven2/"}
              "clojars"      {:url "https://clojars.org/repo/"}}
...
 :aliases {:ion-dev {:deps {com.datomic/ion-dev {:mvn/version "0.9.247"}}
                     :main-opts ["-m" "datomic.ion.dev"]}}
...}

resources/datomic/ion/starter/config.edn

このconfig.ednはDatomic Client APIのclientを生成するために使われます。

{:server-type :ion ;; <== :cloudではなく:ionを指定
 :region "ap-northeast-1" ;; <== AWSリージョン
 :system "advent-2019" ;; <== Datomic cloud設定時に指定したシステム名
 :endpoint "http://entry.advent-2019.ap-northeast-1.datomic.net:8182" ;; <== システム名とリージョンを上記と一致させる
 :proxy-port 8182 ;; <== SOCKS proxyのポート
}

resources/datomic/ion-config.edn

ionsの動作を定義するファイルです。Ionには下記の4タイプがあります。

Ionタイプ 引数 返り値
トランザクション関数 db + データ トランザクションデータ
クエリ関数 データ データ
lambda関数 :input JSON, :context map String, InputStream, ByteBuffer, Fileのいずれか
web関数 httpリクエスト httpレスポンス

定義の例です。

{:allow [datomic.ion.starter.attributes/valid-sku?]
 :lambdas {:get-schema
           {:fn datomic.ion.starter.lambdas/get-schema
            :description "returns the schema for the Datomic docs tutorial"}
           :get-items-by-type
           {:fn datomic.ion.starter.lambdas/get-items-by-type
            :description "return inventory items by type"}
           :get-items-by-type-lambda-proxy
           {:fn datomic.ion.starter.http/get-items-by-type-lambda-proxy
            :description "lambda proxy integration entry point for get-items-by-type"}
           }
 :http-direct {:handler-fn datomic.ion.starter.http/get-items-by-type}
 :app-name "advent-2019"}

トランザクション関数Ion、クエリ関数IonはDatomicの振る舞いを拡張するものです。(RDBMSにおけるストアド・プロシジャ的なもの)これらは :allowキーワードをキーとする配列で定義し、Datomic Client API経由で発行するdatomic.client.api/transactdatomic.client.api/qからアクセス可能にします。

Lambda Ionは、関数をAWS Lambda経由でアクセス可能にするものです。:lambdasマップで定義された関数が、AWS Lambdaに登録され、アクセス可能になっています。

例えば、datomic.ion.starter.lambdas/get-items-by-typeが登録されています。ソースはこのようになります。starterでDatomicのクエリのラッパー関数を呼んでいますが、Datomicを全く使わない関数でも構いません。

lambdas.clj
(ns datomic.ion.starter.lambdas
  (:require
   [clojure.data.json :as json]
   [datomic.client.api :as d]
   [datomic.ion.starter :as starter]
   [datomic.ion.starter.edn :as edn]))
...
(defn get-items-by-type
  "Lambda ion that returns items matching type."
  [{:keys [input]}]
  (-> (starter/get-db)
      (starter/get-items-by-type (-> input json/read-str keyword)
                             [:inv/sku :inv/size :inv/color])
      edn/write-str))

これをpush, deployすると、下記のように実行できるようになります。

👉 aws lambda invoke --function-name advent-2019-Compute-RANDOM_ID-get-items-by-type --payload '"shirt"' /dev/stdout
[[#:inv{:sku "SKU-28", :size :xlarge, :color :green}]
 [#:inv{:sku "SKU-36", :size :medium, :color :blue}]
 [#:inv{:sku "SKU-48", :size :small, :color :yellow}]

:lambdasマップで定義された関数が、AWS Lambdaに登録され、アクセス可能になっています。

Web IonはHTTPリクエストを受け取り、レスポンスを返すclojure関数です。Soloトポロジー(開発・小規模本番環境用の高可用性がないプラン)の場合は、datomic.ion.lambda.api-gateway/ionizeでそのWeb関数をLambda関数に変換し、API GatewayのProxy Serviceを利用することで公開することができます。Productionトポロジーの場合は:http-directにWeb関数を登録し、VPC Linkを経由してDatomic Ionsスタックが起動しているNLBとAPI Gatewayを連携させます。

Web Ionを定義

Web関数
(ns datomic.ion.starter.http
  (:require
   [clojure.java.io :as io]
   [datomic.ion.starter :as starter]
   [datomic.ion.starter.edn :as edn]
   [datomic.ion.lambda.api-gateway :as apigw]))
...
(defn get-items-by-type
  "Web handler that returns info about items matching type."
  [{:keys [headers body]}]
  (let [type (some-> body edn/read)]
    (if (keyword? type)
      (-> (starter/get-db)
          (starter/get-items-by-type type [:inv/sku :inv/size :inv/color])
          edn/write-str
          edn-response)
      {:status 400
       :headers {}
       :body "Expected a request body keyword naming a type"})))

Soloトポロジー用に、Web IonをLabmda Ionに変換する

ionize
(ns datomic.ion.starter.http
  (:require
   [clojure.java.io :as io]
   [datomic.ion.starter :as starter]
   [datomic.ion.starter.edn :as edn]
   [datomic.ion.lambda.api-gateway :as apigw]))
...

(def get-items-by-type-lambda-proxy
  (apigw/ionize get-items-by-type))

ion-config.ednでLambda Ionとしてionizeした関数を登録

lambdas
{:lambdas {:get-items-by-type-lambda-proxy
           {:fn datomic.ion.starter.http/get-items-by-type-lambda-proxy
            :description "lambda proxy integration entry point for get-items-by-type"}
           }
 :app-name "advent-2019"}

API GatewayでProxyエンドポイントを定義
Cursor_and_API_Gateway.png

deployによって登録済みのLambda関数を指定
Cursor_and_API_Gateway.png

Curlで関数の呼び出しが可能

👉 curl -s https://02dl854n3a.execute-api.ap-northeast-1.amazonaws.com/dev/datomic -d :hat |base64 --decode
[[#:inv{:sku "SKU-51", :size :small, :color :yellow}]
 [#:inv{:sku "SKU-19", :size :small, :color :green}]
 [#:inv{:sku "SKU-15", :size :xlarge, :color :red}]
 [#:inv{:sku "SKU-11", :size :large, :color :red}]
 [#:inv{:sku "SKU-55", :size :medium, :color :yellow}]
 [#:inv{:sku "SKU-7", :size :medium, :color :red}]
 [#:inv{:sku "SKU-63", :size :xlarge, :color :yellow}]
 [#:inv{:sku "SKU-31", :size :xlarge, :color :green}]
 [#:inv{:sku "SKU-47", :size :xlarge, :color :blue}]
 [#:inv{:sku "SKU-23", :size :medium, :color :green}]
 [#:inv{:sku "SKU-35", :size :small, :color :blue}]
 [#:inv{:sku "SKU-43", :size :large, :color :blue}]
 [#:inv{:sku "SKU-3", :size :small, :color :red}]
 [#:inv{:sku "SKU-27", :size :large, :color :green}]
 [#:inv{:sku "SKU-39", :size :medium, :color :blue}]
 [#:inv{:sku "SKU-59", :size :large, :color :yellow}]]

まとめ

Datomic Cloudの弱点を補う魅力

Datomic Cloudでできなかった、サードパーティライブラリも含む任意のロジックでトランザクション時に実行するロジックを拡張したり、クエリデータの変換やフィルタリングをIonsで行うことができるようになりました。Datomic Cloudの機能拡張が必要な場合は、Datomic CloudにIonsはすでに含まれているので、すぐにメリットを享受することができます。

LambdaのContinuous Deliveryプラットフォームとしての魅力

Clojure関数をLambdaに登録するためにはmhjort/clj-lambda-utilsjuxt/pack.alphaとAWS CLIを使ってzipファイルをアップロードすることになりますが、IonsではAWS Code DeployとStep Functionsを用いて自動化しており、clojure -A:dev-ions {:op ...}でpushやdeployをすることができます。

また、ユーザが記述した関数が直接Lambdaとして登録されるわけではなく、datomic.ion.lambda.handler.ThunkがProxyとしてLambdaに登録されており、実際の関数はDatomic Cloudが起動したCompute GroupのEC2上で動作するため、コードサイズの上限などLambdaの制限にとらわれずにコードを記述できるのもメリットです。

スケーラビリティ

Datomicを利用したWebアプリケーションを開発した場合、Httpリクエストの増大に伴い、Web関数やLambda関数のキャパシティを増やす必要があります。Query GroupのEC2サーバ上ではWeb関数やLambda関数とともにPeer Serverも作動しているので、Datomicのクエリ側のキャパシティがまとまってスケールするメリットがあります。

コスト面のデメリット

IonsはDatomic CloudのQuery Groupで動作します。IonsはDatomicを利用しない場面でも有用であるにも関わらず、ライセンスコストが発生します。Datomic中心のアプリケーションであればよいのですが、それ以外の機能をIonsで記述し、スケールさせなければいけないときにDatomic Cloudのライセンスコストが増加するのは合理的ではないと思います。

AWSとの密結合

Kubernetesが発展してきている現在、クラウド間のポータビリティが容易になってきているにも関わらず、Datomic CloudとIonsを選択することでAWS一択になってしまうリスクを考慮する必要があります。

最後に

Stuart Hallowayは、今後自分がアプリケーションを書くときには、Datomicを使うかどうかに関わらず、Ionsで開発するだろうと言っていたのが印象的でした。私はDatomicをDBとするWebアプリや、Kinesisなど、Lambdaをハンドラとして用いるアプリなどであれば、Ionsを使うメリットは大きいと思います。

一方で、JUXTのCruxや、KNativeなどのテクノロジーを組み合わせたオープンソースのアプローチもあるのではないかと思っています。機会があれば試してみたいと考えています。

12
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
1