AWS LambdaでClojureを使いたかった

はじめまして。analysisチームの河野です。ここ2年ぐらい趣味でLispを勉強しています。
最近はClojureでwebサーバみたいなものを作ったりして遊んでいます。

業務ではTypeScriptとAWS Lambdaでサーバレスなアプリケーションを作っていますが
AWS LambdaでもClojureを使いたい、Javaも使えるのだからClojureも使えるはずと思ったので、調べてみると、古い記事ですが、公式ブログで解説されていました。
https://aws.amazon.com/jp/blogs/compute/clojure/

ちょっと大変そう。。。

Nodeが実行できるのだから、ClojureScriptも使えるのでは?
調べてみると、Serverless Frameworkのプラグインがありました。
https://github.com/nervous-systems/serverless-cljs-plugin

ClojureScriptは使ったことはないのですが、とりあえずREADMEにしたがって進めてみます。

$ lein new serverless-cljs example
$ cd example
$ lein deps

依存関係の解決ができたらひとまずデプロイします。

$ serverless deploy

デプロイに成功したら、エンドポイントが表示されるの試してみます。 おそらく、echoというハンドラ名からして受け取った値をそのまま返すのだと思います。

$ curl -X POST -d '{"hello": "world"}' https://xxxx/dev/echo
-> {"hello": "world"}

bodyの値がそのまま返ってくる。jsonでなくても良いみたい。

$ curl -X POST -d "hello" https://xxxx/dev/echo
-> hello

ひとまずコードを見てみる。

(defgateway echo [event ctx]
  {:status  200
   :headers {:content-type (-> event :headers :content-type)}
   :body    (event :body)})

おそらく、defgatewayというのでハンドラを定義できるのでしょう。 中身はMapになっており、キーが、status, header, bodyなので、HTTPと対応しているように見えます。 よくわからないですが、bodyの値に何か別の値を入れれば、別の値を返せるっぽいので試してみます。

core.cljsにハンドラを追加します。

(defgateway hello [event ctx]
  {:status 200
   :headers {:content-type (-> event :headers :content-type)}
   :body "hello world"})

serverless.ymlにも設定を追加。 functionshelloを追加。

functions:
  echo:
    cljs: example.core/echo
    events:
      - http:
          path: echo
          method: post
  hello:
    cljs: example.core/hello
    events:
      - http:
          path: hello
          method: get

デプロイ。

$ serverless deploy

成功したらhelloのエンドポイントが表示されるので、ブラウザからアクセスしてみます。

f:id:t-kono0912:20190215162240p:plain
hello_wolrd

表示されてる。

せっかくなので、クエリストリングの値を返してみましょう。 こんな感じで送って

curl https://xxxx/dev/hello?name="your-name"

こんな感じで返したい。

{hello: "your-name"}

クエリストリングがなければ"world"と返す。

{hello: "world"}

helloハンドラを修正します。

(defgateway hello
  [event ctx]
  (let [name (get-in event [:query :name])]
    {:status 200
     :headers {:content-type "application/json"}
     :body (JSON/stringify (clj->js {:hello (or name "world")}))}))

serverless.ymlも修正。

  hello:
    cljs: example.core/hello
    events:
      - http:
          path: hello
          method: get
          request:
            parameters:
              querystrings:
                name: false

試してみる。

# パラメータなし
$ curl https://xxxx/dev/hello -v
-> {"hello": "world"}
# パラメータあり
$ curl https://xxxx/dev/hello?name="hoge" -v
-> {"hello": "hoge"}

できてる。 次回はパスパラメータを取れるようにしたいです。

コネクトムではエンジニアを募集しています。 https://open.talentio.com/1/c/connectom/requisitions/550

AWS S3のオブジェクトの ETag を MD5ハッシュにしたい

S3にアップロードされたファイルの同一性をチェックしたりするために、ハッシュ値を使いたく、 いろいろ調べたのでまとめます。

先に結論

ETagがMD5になる

ETagの値は、次の条件の場合 MD5ハッシュ が設定されます。

  • PUT、POST、またはCOPYで作成された、SSE-S3または平文で暗号化されたオブジェクト

ETagがMD5にならない

次の場合は MD5ハッシュにはなりません。

  • マルチパートアップロードでオブジェクトを作成した場合
  • 暗号化タイプが SSE-C または SSE-KMS の場合
  • 16MB以上のファイルを、AWSマネジメントコンソールで「コピー」->「貼り付け」した場合

マルチパートアップロードしたファイルの ETag にMD5を設定するには?

s3apiの CopyObject を実行するとよいです。

$ aws s3api copy-object --copy-source mybucket/myobject --bucket mybucket --key myobject --metadata-directive REPLACE --metadata a=b

ただし、単純な同一名での上書きはできず、次のいずれかの変更が必要となります。

(なので適当なa=bメタデータに設定してます。)

  • metadata
  • storage class
  • website redirect location
  • encryption attributes.

ちなみに上のコマンドをメタデータの変更をせずに実行すると...

$ aws s3api copy-object --copy-source mybucket/myobject --bucket mybucket --key myobject --metadata-directive REPLACE --metadata copied=true

An error occurred (InvalidRequest) when calling the CopyObject operation: This copy request is illegal because it is trying to copy an object to itself without changing the object's metadata, storage class, website redirect location or encryption attributes.

このようにエラーになります。

その他もろもろ

AWSマネジメントコンソール でのコピーの挙動

(2018-12 時点で) AWSマネジメントコンソールで「コピー」->「貼り付け」をした場合は、 CopyObject を実行せずに、16MBのパートに分割して内部でマルチパートアップロードしているようです。

16MBを超えるファイルは、コピー元のETagが MD5ハッシュ であっても、ETagの値がハイフン付きの値に変更されてしまいます。

aws-cli のファイルコピーについて

8MBを超えるファイルについては、8MBのパートに分割するようです。 つまり8MBを超えるファイルを s3 cp でアップロードしたらETagの値がハイフン付きの値になります。

これを避けるには、s3apiのPutObjectを使うとよいでしょう。

aws s3api put-object --bucket mybucket --key myobject --body myobject

マルチパートアップロード時のETagの値って??

公式な文書は見当たりませんが、次のように作成しているようです。

アップロードした各パートのMD5ハッシュを連結したもののMD5 + '-' + パート数

https://stackoverflow.com/questions/12186993/what-is-the-algorithm-to-compute-the-amazon-s3-etag-for-a-file-larger-than-5gb/19896823#19896823

参考

https://docs.aws.amazon.com/AmazonS3/latest/API/RESTCommonResponseHeaders.html

Apache CarbonData を試す

今回はSparkからCarbonDataを作成して UPDATE と DELETE をしてみたいと思います。

CarbonDataの UPDATE と DELETE の動きは次のWikiにまとめられています。QuicStart を参考に試してみます。

Update and Delete Support

前準備

サンプルデータ作成

サンプルのCSVを作成します。

cd carbondata
cat > sample.csv << EOF
id,name,city,age
1,david,shenzhen,31
2,eason,shenzhen,27
3,jarry,wuhan,35
EOF

SparkShell起動

Downloadから apache-carbondata-1.4.0-bin-spark2.2.1-hadoop2.7.2.jar をダウンロードして、spark-shellを起動します。

$ spark-shell --jars apache-carbondata-1.4.0-bin-spark2.2.1-hadoop2.7.2.jar

CarbonSession作成

import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.CarbonSession._

// SparkのHiveMetastoreと被らないように ./catbon-meta 配下にmetastore_dbが作られるようにする
// データ類は carbon-warehouse ディレクトリに作成されるようにする
val carbon = SparkSession.builder().config(sc.getConf).getOrCreateCarbonSession("carbon-warehouse", "carbon-meta")

TABLE 作成

DATABASE と TABLE を作成します。

carbon.sql("CREATE DATABASE IF NOT EXISTS mydb")
carbon.sql("CREATE TABLE IF NOT EXISTS mydb.test_table(id string, name string, city string, age Int) STORED BY 'carbondata'")

別のシェルでディレクトリの構成を確認します。

$ tree carbon-warehouse/
carbon-warehouse/
└── mydb
    └── test_table
        └── Metadata
            └── schema

空のディレクトリ carbon-warehouse/mydb/test_table がされました。

INSERTとDELETEを試す

サンプルCSVの LOAD

続いて最初に作ったサンプルデータをLOADします。

carbon.sql("LOAD DATA INPATH 'sample.csv' INTO TABLE mydb.test_table")
carbon.sql("SELECT * FROM mydb.test_table").show
+---+-----+--------+---+
| id| name|    city|age|
+---+-----+--------+---+
|  1|david|shenzhen| 31|
|  2|eason|shenzhen| 27|
|  3|jarry|   wuhan| 35|
+---+-----+--------+---+
$ tree carbon-warehouse/
carbon-warehouse/
└── mydb
    └── test_table
        ├── Fact
        │   └── Part0
        │       └── Segment_0
        │           ├── 0_batchno0-0-1532873451153.carbonindex
        │           └── part-0-0_batchno0-0-1532873451153.carbondata
        ├── LockFiles
        │   ├── Segment_0.lock
        │   └── tablestatus.lock
        └── Metadata
            ├── schema
            ├── segments
            │   └── 0_1532873451153.segment
            └── tablestatus

Segment_0が作成されました。INSERTの塊ごとにSegmentでまとめてファイルが作成されます。

INSERTの実施

さらにデータをINSERTしてみます。

carbon.sql("INSERT INTO  mydb.test_table VALUES(4, 'duke', 'menlopark', 28)")
carbon.sql("SELECT * FROM mydb.test_table").show
+---+-----+---------+---+
| id| name|     city|age|
+---+-----+---------+---+
|  1|david| shenzhen| 31|
|  2|eason| shenzhen| 27|
|  3|jarry|    wuhan| 35|
|  4| duke|menlopark| 28|
+---+-----+---------+---+
$ tree carbon-warehouse/
carbon-warehouse/
└── mydb
    └── test_table
        ├── Fact
        │   └── Part0
        │       ├── Segment_0
        │       │   ├── 0_batchno0-0-1532873451153.carbonindex
        │       │   └── part-0-0_batchno0-0-1532873451153.carbondata
        │       └── Segment_1
        │           ├── 0_batchno0-0-1532873814293.carbonindex
        │           └── part-0-0_batchno0-0-1532873814293.carbondata
        ├── LockFiles
        │   ├── Segment_0.lock
        │   ├── Segment_1.lock
        │   └── tablestatus.lock
        └── Metadata
            ├── schema
            ├── segments
            │   ├── 0_1532873451153.segment
            │   └── 1_1532873814293.segment
            └── tablestatus

新しいSegmentが追加されました。

DELETEの実施

では削除を試してみます。

carbon.sql("DELETE FROM mydb.test_table WHERE id = 3")
scala> carbon.sql("SELECT * FROM mydb.test_table").show
+---+-----+---------+---+
| id| name|     city|age|
+---+-----+---------+---+
|  1|david| shenzhen| 31|
|  2|eason| shenzhen| 27|
|  4| duke|menlopark| 28|
+---+-----+---------+---+

削除もできました。

$ tree carbon-warehouse/
carbon-warehouse/
└── mydb
    └── test_table
        ├── Fact
        │   └── Part0
        │       ├── Segment_0
        │       │   ├── 0_batchno0-0-1532873451153.carbonindex
        │       │   ├── part-0-0_batchno0-0-1532873451153.carbondata
        │       │   └── part-0-0_batchno0-0-1532873985129.deletedelta
        │       └── Segment_1
        │           ├── 0_batchno0-0-1532873814293.carbonindex
        │           └── part-0-0_batchno0-0-1532873814293.carbondata
        ├── LockFiles
        │   ├── Segment_0.lock
        │   ├── Segment_1.lock
        │   ├── meta.lock
        │   ├── tablestatus.lock
        │   └── tableupdatestatus.lock
        └── Metadata
            ├── schema
            ├── segments
            │   ├── 0_1532873451153.segment
            │   └── 1_1532873814293.segment
            ├── tablestatus
            └── tableupdatestatus-1532873985129

削除対象のデータがあるSegment0のディレクトリ内に part-0-0_batchno0-0-1532873985129.deletedelta が作成されました。

シンプルなしくみでUPDATEとDELETEを実現していますが便利そうですね。

Apache CarbonData の紹介

こんにちは。コネクトムの児島です。

弊社では広告配信のログなどの分析のために Apache Spark などのOSSビッグデータ分析基盤 を活用しています。

実運用では使っていませんが、列指向フォーマットの CarbonData を調査したので紹介します。

CarbonData って何?

CarbonData はビッグデータ向けの列指向フォーマットです。

公式サイトはこちら

元は2013年から Huawei によって開発が開始されて、2015年にApacheに寄贈され、2017年4月に Apache のトップレベルプロジェクトに昇格しました。*1

他のオープンソースのファイルフォーマットよりも10倍速いと言ってます。

他のフォーマットとの違いは?

同じような列指向フォーマットといえば、同じApacheのプロジェクトだけでも

がありますが、CarbonDataは インデックスを持っているのが最大の特徴です。 インデックスを持っているので、ファイルの中の必要なデータだけにすばやくアクセスできます。

他のすごいところ

UPDATEとDELETEがサポートされてます。

UPDATEとDELETEの際は、既存のファイルを書き換えずに、新規の差分ファイルを作成することで実現しています。

差分のファイルを統合してキレイなファイルにする命令もあります。 (ALTER TABLE テーブル名 COMPACT 'MINOR/MAJOR/CUSTOM')

どうやったら使えるの?

現在(バージョン 1.4.0)のところ

  • Spark
  • Hive
  • Presto

から利用できますが、デフォルトで組み込まれているわけではないので、自分でCarbonDataのJARを読み込んであげる必要があります。

CarbonDataが普及していくためには、これらのプロダクトにデフォルトで組み込まれる必要がありそうです。

次回は

実際に動作させてみて、どのようなファイルができるか見ていきたいと思います。

Japan Container Days v18.04に行ってきました。

はじめまして! コネクトムの草ヶ谷と申します。 これから記事を書いていき、徐々に充実したブログにしていきたいと思っておりますので、どうぞよろしくお願い致します!

で、1発目の記事が勉強会レポートでなにやら微妙な感じもしますが・・・。

4/19(木)にベルサール神田で行われた「Japan Container Days v18.04」というイベントに行ってきました。 そこで聞いてきた内容と、自分の感想をつらつら書いていきたいと思います。

f:id:gyagya1111:20180505134631p:plain

サイバーエージェントにおけるプライベートコンテナ基盤 AKE を支える技

  • アドテクスタジオ独自のプライベートコンテナ基盤(AKE)を作った話
  • 広告に関連するサービス(DSP, SSP, DMP等)を複数展開している
  • 広告業界では「Low Latency」、「High Traffic」、「High Computing」が求められている
  • AKEはOpenStack上に構築されている
  • それらを実現する為に、チューニングを行い高速化もした
  • 自分たちでKubernetesクラスタを作ればなんでも出来るけど、コストが大きい
    • Kubernetes自体に深い理解が必要
    • コンテナに合わせたCI/CDが出来るまでは結構辛かった

セッション全体を通した感想としては、やっていることは非常に高度な事をやっていて刺激を受けた反面、自分の会社でここまでやるのは無理かなと思いました。 受けられる恩恵とプライベートコンテナ基盤を作る労力を天秤にかけると、圧倒的に労力が上回るのではないかと思います。 とはいえ、個人的にはKubernetes使い込んでる感をすごく感じれたセッションだったので、やろうと思えばいろんなことが出来るのがわかり、非常に勉強になりました。

マイクロサービスアプリケーションとしての機械学習

  • メルカリの商品出品時に使われている画像認識機能を作った時の話
  • Kubernetesを使った環境上にデプロイされている
  • マイクロサービス化してあると、機械学習を取り入れやすい
  • モデル作成してストレージに保存するまでの部分と、そのモデルを利用する部分とで分けることで、影響範囲の明確化や、依存関係をそこまで気にする必要がなくなる

機械学習は弊社でも導入する方向性になっているので、このセッションで聞いた内容が役に立つのではないかなと思います。 最初はアプリケーションコードと機械学習モデルが共存しており、片方だけ更新したくても両方更新せざるを得ない状況だったのが、それぞれをマイクロサービス化して切り出すことで、片方だけをデプロイ出来るようにしたそうです。 学習済みのモデルを読み込み専用のPersistent Volumesに保存していて、それを入れ替えるだけでリリースが完了するので、非常に簡単にリリースや切り戻しが出来るようになっているのもよい点だと思いました。

"Yahoo! JAPANのKubernetes-as-a-Service"で加速するアプリケーション開発

  • イベントの日に通常の数倍から数十倍のアクセスが来ることが想定されているが、スケールアウトやスケールアップが出来なくてツラい
  • リリース周りが自動化されていなくてツラい
  • パフォーマンステスト用の環境がなくてツラい
  • VM障害があると影響度がでかくてツラい
  • Z Labさんが提供しているKubernetes-as-a-Serviceを利用するようにして、アプリケーションコードもデプロイフローも全て見直すことで、リリースに掛かる負担を削減した。10分程度でリリース出来るようになった。
  • OpenStack側に障害が起きてもサービスダウンしなくなったし、自動復旧するため特に復旧作業は必要なくなった。
  • ログ収集して可視化するところまで仕組みを作ったので、レポート作成にかかる負担が減った

Kubernetesを利用出来るようになるまでのツラさは想像に難くないのですが、それを乗り越えたおかげで受けれた恩恵も非常にでかそうだという印象を持ちました。

Kubernetesセキュリティベストプラクティス

  • 中身がどうやって動いているのかわかっていない状態で使うのはよくない
  • GKEを使うとセキュリティの高いクラスタが作れる

Kubernetesのセキュリティに関するお話が中心でした。 実際に手を動かしながら見せてくれて、画像で概念的な説明がされていたりと、とてもわかり易い内容でした。 こういったセキュリティ系のセッションを聞くのは初めてだったので、今まで漠然とKubernetesってセキュリティリスクとしてどんなものを抱えているのかわかっていなかったのですが、こういうリスクを抱えているのかということが明確化されて、ちょっとスッキリしました。 もちろんここに書かれていることが全てではないと思いますが、このセッションで聞いた内容を自分で手を動かしながら確かめていきたいと思います。

Container Networking Deep Dive

Kubernetesのネットワークがどうなっているのかを解説してくれていたのですが、正直Kubernetes自体もそんなに触れていない自分には難しすぎたかなと思いました・・・。 一言でまとめると、ちゃんと自分たちが使うものはきちんと理解して使いましょうって感じでした。 確かにその通りで、なんとなく使っていると後々痛い目見るので、少しずつ理解していこうと思います。

Kubernetesの運用設計ガイド

  • Kubernetesを使う目的をはっきりさせる
  • Kubernetes自体の狙いと合っているかを確認する
    • インフラ管理の為の手作業の時間を減らす
    • セルフサービス型の運用を可能にする
  • 技術と組織は表裏一体なので、チーム作りからやる必要がある
  • コンウェイの法則「組織の設計するシステムは、その組織のコミュニケーション構造をそのまま反映した設計になる」を逆手に取って、「作りたいシステムの構造を反映したコミュニケーション及び組織構造を作ると、システムが期待した設計になる」

Kubernetesをどうやって使えばいいのかみたいな話がされるのかと思いきや、いきなり組織づくりがどうこうみたいな話が出てきて、びっくりしました。 コンウェイの法則を逆手に取ってみるという考え方は、とても興味深い考え方だと思いました。 実際に運用していく際の細かい話は結構色々書いていて、ここに書くととんでもない量になるので割愛します。

『コンテナ疲れ』と戦う、k8s・PaaS・Serverlessの活用法

  • 正しいテクノロジースタックの選択が出来る知識があることが大事
  • コンテナ技術は抽象度が低い
  • エンジニアがカバーしないといけない責任範囲が広い
  • 過去を振り返って、未来に何が起こるかを考える
  • より高度に抽象化され、より高度に自動化される
  • 開発者がアプリケーションの開発に専念出来る
  • IaaS、CaaS、PaaS、FaaSにはそれぞれメリット・デメリットがある
  • 目的にあった最適なプラットフォームを選ぶ
  • 色々なものを組み合わせて使うという考え方も大事

コンテナ技術は抽象度が低いし、エンジニアの責任範囲が広いので、そりゃ疲れますよというお話。 コンテナ使うことが重要なわけではないし、きちんとビジネスの目的を達成する為の手段として有用なものを選択(組み合わせて使う)ことが大事ということでした。

総括

ほとんどのセッションは Kubernetes を中心としているお話でしたが、興味深いセッションが多くて楽しかったです。 こういった大きなイベントは体力を精神力を非常に持っていかれるのですが、新しい視点が増えたり、知らない技術の話とか、実際に使っている人の話が聞けてとても楽しかったです。 自分自身、まだまだKubernetesを理解出来ていない部分が多々あるので、地道に勉強をしつつ、いざ仕事で使い始める時にはちゃんとKubernetes自体を理解出来ている状態になっていたいと思いましたし、その為の準備を粛々と進めていこうと思いました。

<ご挨拶> コネクトムの開発者ブログを始めました! (^^)

こんにちは!

株式会社コネクトムで、エンジニアリング組織のマネージャーをしている土屋尾(ドヤオ)と申します。

(↓デブサミ2017を楽しむ私) f:id:takudo_dev:20180428134805j:plain

コネクトムは、feel good という「気持ちのよいお買い物体験をつくる」というミッションを持って2014年にオプトグループの子会社として生まれました。

Webとオフラインを繋いでいくための、 位置情報マーケティング が我々の強みです。

このブログの目的

背景として、 「何をやっているかよくわからない」 という声を聞くことが最近になってよくありました。

また、組織が急拡大している最中なのですが、求職者の方にも より身近な情報を簡単に届けたい と思い、開発者のブログを始めることにしました。

このブログを通じて伝えたいことは、

  • コネクトムという会社におけるエンジニアリングの 普段の様子 や、
  • コネクトムのビジネスを どんなエンジニアリングでコミットしようとしているか

です。

(↓ふりかえりの様子)

f:id:takudo_dev:20180428141113j:plain

どんな人に読んで欲しいか

コネクトムのビジネスがどんなエンジニアリングによって成り立っているか、いままでほとんど情報発信をしてきていませんでした。 ですので、以下のような方に読んでもらいたいと思っています。

  • コネクトムのエンジニア組織の仕事の様子を知りたい方(ロール問わず)
  • どのような技術スタックを使っているかの雰囲気を知りたいエンジニアの方

話の締めとして

挨拶記事ですのでこの記事はこれで終わりますが、今後は社内のエンジニアで代わる代わる記事を投稿していこうと思っています。

皆キャラが立っているので僕も皆がどのような記事を書いていくのか非常に楽しみです(^^)

それでは今後ともよろしくお願いいたします。


付録: 私について

  • プロダクトをつくるための、組織づくりにもっとも関心領域を強くもっている、コネクトムのシステム組織のマネージャーです
  • BtoB・BtoCのエンジニアやフリーランスなどを経て、大きな仕事をしたくてコネクトムに2016年にジョインしました。
  • 「チームプレイ」「位置情報マーケ」 の世界で独自の強みを持てるプロダクトづくりの両輪で組織を拡大していこうとしています
  • お酒は 🍺 だけで最初から最後までいけます 👍