エブリーエンジニアブログ エブリーエンジニアブログ

定期購読の難しいところ

f:id:nanakookada:20210922211918p:plain

定期購読の難しいところ

システム開発部部長の内原です。

今回はバックエンドエンジニア観点で、定期購読(サブスクリプション)を扱う際に問題となるであろう様々なことについてお話しします。

私は現在システム開発部という部署を担当していますが、以前はDELISH KITCHENのプレミアムサービスチームでバックエンドエンジニアとして働いていたので、その経験を元にして実装や運用で難しさを感じたことについて語ります。

はじめに

DELISH KITCHEN においてもプレミアムサービスという定期購読サービスを提供しています。 内容は以下のようなものです。

DELISH KITCHEN プレミアムサービスとは

  • 機能面

    • お気に入り数無制限
    • 人気順検索
    • プレミアムレシピ(ダイエット、ヘルスケア、美容・健康、作りおき)
    • おまかせ献立(1週間ぶんまるごとで献立を提供)
    • プレミアム検索(糖質オフ、塩分控えめ、などの検索条件を利用可能)
  • 価格面

    • 月480円、半年2,400円、年4,500円
    • 初回登録時は1ヶ月無料(キャンペーン時期により2ヶ月無料、3ヶ月無料)

決済システムについて一般的なお話

DELISH KITCHENに限らず、自社アプリに決済機能を設ける場合は、なんらかの外部サービスが提供する決済システムを利用するケースが殆どではないでしょうか。

自前の決済システムを構築するとなると、技術的難易度が上がることもさることながら、それ以上にセキュリティや法律の観点での考慮が重要になりますので、決済システムそのものがコア技術となるようなサービスを開発するのでない限り、費用対効果を考慮して外部が提供している決済システムを利用することが多いと予想します。

DELISH KITCHENでは、決済システムとして以下を利用しています。 そもそも、iOS/Androidについてはアプリ内でデジタルコンテンツを販売する場合はIAP/IABを利用する必要があるため、事実上の標準となっています。

  • iOS
    • Apple In App Purchase (IAP)
  • Android
    • Google Play Billing Library (In App Billing / IAB)
  • 携帯キャリア (DoCoMo/au/Softbank)決済。DELISH KITCHEN WEBで利用
    • 外部決済システム

※クレジットカードや銀行口座、コンビニ払いといった支払い方法には現状対応しておりません

そもそも決済システムは難しい

そもそも、定期購読に限らず消費型(買い切り型)の商品を扱う場合でも、決済システムの構築にはいくつか考慮しなければならない問題があります。

決済プラットフォーム別の調査&実装が必要である

決済プラットフォーム間でいろいろと仕様が異なる部分が存在します。プラットフォーム間での相違から、必要となるデータが得られないというケースもあり、そうすると差分を埋めるために独自に実装が必要になることも多いです。例えば、IAPでは講読更新ごとに講読更新日時が取得できるが、IABだと初回の講読日時しか取得できないといったケースです。

決済処理は処理のフローが不安定なケースがある

ユーザ側の操作として、(初回は決済方法の指定が必要、など)いったん別画面に遷移したりすることが多いので、途中でユーザが脱落したり、通信エラーが発生したりする頻度も自然と高くなります。さらに、決済部分はアプリ外の挙動であり、課金ボタンはタップしたがその後脱落というケースが多くても、その原因は分からないということが多いです。プラットフォーム側で決済は完了したが、正しく通達できていないというケースのことです。

決済状態と内部システムの同期が必要である

上記問題に対応するため、購入情報の同期を行う機能が必要になります(アプリ内では購入情報の復元と表現されます)。

不具合対応やお客様対応の難易度が高くなりやすい

そもそも不具合が発生すること自体が問題ではあるのですが、金銭的損害が絡まない場合に比べて問題が複雑化&深刻化する傾向が高いです。また、お客様のお問い合わせ対応において返金作業が別途必要になるなど、他の問題と比較してお問い合わせクローズまでの時間が長くなりやすいです。

購読の状態が複雑

定期購読は、未購読、購読中、定期購読中止(未解約)、解約済、といった状態が時系列で存在しているため、いったん確定した購読状態が時間経過によって変化することになります。 よってこれらの状態変化を正しく認識する必要があります。

以下に購読ライフサイクルの例を挙げます。 特にユーザが操作をしていなくても、時間経過によって購読状態が変化する場合があるのが分かります。

購読、解約

- 登録直後 0.5ヶ月後 1ヶ月後 1.5ヶ月後 2ヶ月後 現在
購読中 無料期間 - 更新1回目 - 更新2回目 購読中
購読後解約A 無料期間 - 更新1回目 定期購読中止 解約 解約済
購読後解約B 無料期間 定期購読中止 解約 - - 解約済

解約後、再購読

※過去に購読→解約を経験しているため、無料期間が存在しない

登録直後 1ヶ月後 1.5ヶ月後 2ヶ月後 6ヶ月後 現在
無料期間 更新1回目 定期購読中止 解約済 更新通算2回目 購読中

購読後、商品切替(1ヶ月→半年)

※購読している商品の購読期間を変更することが可能

登録直後 1ヶ月後 1.5ヶ月後 7ヶ月後 現在
無料期間(1ヶ月) 更新1回目(1ヶ月) 購読切替(半年) 更新2回目(半年) 購読中

分析要件の難しさ

「どのような理由で購読/解約したのか?」、「どのような施策が売上に効いている/効いていないのか?」、「キャンペーン内容は適切であるか否か?」といった様々な観点での分析が必要となります。

前述の通り、購読状態は時間とともに複雑に変化するため、時系列での評価を行わなければならなくなります。

なにを見たいか

  • 課金ページ閲覧数、課金数
  • 新規購読UU
  • 全体購読UU
  • 課金転換率
    • 無料期間によって対象期間は変わる
  • 解約率
    • 購読開始してから何日めか、が重要

なんの軸で集計するか

  • 購読期間別
    • 1ヶ月、6ヶ月、1年
  • 無料期間別
    • 1ヶ月、2ヶ月、3ヶ月、6ヶ月
  • 登録日別
    • キャンペーンやプロモーションの影響を分析する
  • 訴求内容別
    • 献立、ダイエット、ヘルスケア、など
  • 上記の組み合わせ

過去の状態を判定

分析観点において、特定ユーザのN日前の購読状態を知りたいというニーズがあったとしても、状態遷移としては購読開始、購読更新×N、定期購読中止(未解約)、解約済、という起点があるだけです。単にN日前のデータを見ただけでは判別ができないということになります。 よって、時系列で購読状態を解釈する必要性があります。

プラットフォーム差異

前述したように、プラットフォームごとに提供される機能やデータ種類に差分がありますが、DELISH KITCHENシステムとしてはなるべくプラットフォーム差異を意識しないで済むようにしたいので、これらの差分を吸収する実装を行う必要が出てきます。 とは言え、そもそもの仕様が異なる関係で、完全に吸収することは難しいものも存在します。

例えば以下のように、IAP/IABでは仕様が異なる部分があり、いずれかに合わせる、または代替となるデータを自前で算出する機構を実装するなどの対応が必要になります。

- 価格 無料期間 購読履歴 定期購読中止日 購読商品切替時
IAP 価格帯から選択 期間帯から選択 取得可能 取得不可 現購読完了後に切替
IAB 1円単位で指定 1日単位で指定 取得不可 取得化 即時切替

本番での課金テストの辛さ

検証環境でのテストを十分に行っていたとしても、本番環境での検証を一切しないままだと不安が残ります。 よって、検証環境ほどの作業項目ではないにしても、最低限の動作確認は本番環境でも行っておきたいところです。 ただそれについても困難が伴います。

初回無料に関するテスト

初回無料が有効になるのは、ストアアカウントごとに1回ずつというのがIAP/IABにおける仕様です。 つまり、初回Nカ月無料が本当に正しくシステムで捕捉できているかといったようなテストを行う際は、都度ストアのアカウントを作り直さないといけないということになります。

解約済に関するテスト

いったん購読開始した後、定期購読を中止して解約済になった状態でテストする必要が出てきたとします。

しかし、実際に提供している商品の購読期間は1カ月や6カ月といった単位なので、定期購読中止してもその期間内はまだ購読済みのままです。 解約済み状態にするには最低でも1カ月前には準備しておかなければならないということを意味します。

終わりに

定期購読(サブスクリプション)に関する実装、運用の観点からの難しさについて記述しました。 今後の参考になりましたら幸いです。

MetabaseとDatabricksを接続する方法

f:id:nanakookada:20210910173109p:plain

はじめに

エブリーでは日々大量のデータをDatabricksで処理し、MetabaseなどのBIツールで可視化や分析を行っています。
MetabaseとDatabricksのデータベースを接続する方法がまとまっている記事があまりなかったので、ここにまとめたいと思います。

手順

1.Databricks用のJDBCドライバー入りのMetabaseイメージを作成

2.MetabaseとDatabricksを接続する

1. databricks用のJDBCドライバー入りのMetabaseイメージを作成

こちらのフォークリポジトリを使って、ローカルにDatabricks用のJDBCドライバー入りのMetabaseイメージを作成します。

git clone https://github.com/rajeshkumarravi/metabase-sparksql-databricks-driver.git

cd metabase-sparksql-databricks-driver

curl -L "https://github.com/rajeshkumarravi/metabase-sparksql-databricks-driver/releases/download/v1.2.0/sparksql-databricks.metabase-driver.jar" -o sparksql-databricks.metabase-driver.jar

Dockerfileの一部を修正します。

FROM metabase/metabase:v0.37.0.2

ENV MB_DB_CONNECTION_TIMEOUT_MS=60000

#コメントアウトor削除
#COPY ./target/uberjar/sparksql-databricks.metabase-driver.jar /plugins/

COPY sparksql-databricks.metabase-driver.jar /plugins/

「metabase-blog」と名前をつけたMetabaseイメージを作成します。

docker build -t metabase-blog .

コンテナを作成し、http://localhost:3000/ にアクセスしてMetabaseの画面が表示されれば成功です。

docker run -d -p 3000:3000 --name metabase metabase-blog

2. MetabaseとDatabricksを接続する

Metabaseの管理画面から「データベース」→「データベースを追加」をクリックします。

DatabricksからMetabaseと接続したいクラスターの「configuration」に進み、「Advanced Options」から「JDBC/ODBC」に表示される値を確認します。

f:id:kthimuo:20210907124909p:plain

これらをMetabaseに入力していきます。

「データベースのタイプ」→ Spark SQL (Databricks)

「ホスト」→ databricks画面の「Server Hostname」

「データベース名」→ default

「ユーザー名」→ token

「パスワード」→ Databricksのアクセストークン

「追加のJDBC接続文字列オプション」→ Databricks画面の「JDBC URL」の <personal-access-token> を1つ上の「パスワード」に置き換えたもの

「簡単なフィルタリングと要約を行うときに自動的にクエリを実行する」→ False

「データベースが大きいため、Metabaseの同期とスキャンのタイミングを選択します」→ True

f:id:kthimuo:20210907124959p:plain

これでデータベースとの接続は完了です。

3. 確認する

最後にDatabricksのサンプルデータベース「default」にあるテーブル「smsdata」にクエリを投げてみます。

f:id:kthimuo:20210907125020p:plain

ちゃんと接続されているようです。

以上です。最後まで閲覧いただきありがとうございました。

カンファレンススタッフに参加してみよう!

f:id:nanakookada:20210906185641p:plain

はじめに

はじめまして。最近創立されたSite Reliability Engineering(SRE)チームに所属している吉田です。

今回は「カンファレンススタッフ」についてお話しようと思います。

”カンファレンススタッフをやってみたいけど、まだまで全然プログラミングできないから貢献できないかも…”という方がいらっしゃったらぜひ読んでいただければと思います!

カンファレンススタッフって何?

名の通りカンファレンスのスタッフをします。

といっても何をしてるの?という疑問が出てくると思うので、実際に僕がメインで参加しているPyConJPを例にご紹介します。

(全部はリストアップできないので一部です

  • webサイトの構築
  • タイムテーブル作成
  • プロポーザル
  • グッズ・ノベルティ作成
  • 宣伝
  • 予算組み
  • スポンサー様とのやりとり
  • 進行の台本作成
  • 企画組み
  • keynote決定
  • 通訳業者様とのやりとり
  • ロゴデザイン
  • チケット設計
  • 会場下見・決定
  • 託児所決め
  • etc…

パッと見てわかった人もいらっしゃるかもしれませんが、Pythonを使わないタスクが多いです。Pythonどころかプログラミングの知識が必要なタスクは意外とありません。

もちろんPythonの知識は生かせないというわけではなく、keynoteやプロポーザルなどではPythonの専門知識が必要となります。

お伝えしたいのは「その言語に対しての知識が無いからカンファレンススタッフは無理か」とはならないということです。これはPyConJPだけではなく他カンファレンス様でも同様のケースが多いと感じています。

f:id:yskn-beer:20210903162204p:plain
(photo by PyConJP)

モチベーション

では何故カンファレンススタッフを始めたか、続けているモチベーションは何かという話です。

IT業界に還元したい

僕の場合はプログラマーとして日々インターネットで様々な情報をインプットしています。インプットというのは誰かがアウトプットしているから成り立つものです。

それ以外にも開発でOSSを使用していますが、OSSも湧いて出てくる訳もなく誰かが時間を割いてコミットしてくれたものです。

「その行為に対して何か還元できないか」という気持ちは昔からあったのですが、当時は自分の技術的にOSSコミットは難しいな…アウトプットできるほどの知識も無いな…と思っていてなかなか踏み出せずにいました。しかしたまたまスタッフ募集のお知らせを見かけて自分にもきっとなにかできることはあるやろという精神でエイッと入ったのがカンファレンススタッフになった経緯となります。

実際に入ってみたら本当にプログラミング1mmもわからんという人でもタスクが山程あったので結果オーライでした。

面白い人に出会える

カンファレンススタッフには様々な会社・業種・レイヤー・国籍の人がいます。

個人的にはNOC(Network Operations Center)というタスクを中心に行うことがあり、そこで知り合った方たちとカンファレンス以外でも交流が続き、界隈の情報交換など色々な話が聞け非常に有意義な時間になっています。

これは小話なんですが、カンファレンススタッフの中には複数のカンファレンスを跨いでスタッフをしてる人も少なくないので、交流を持つことで他のカンファレンスのスタッフ求人を紹介してもらって数珠つなぎに色んなカンファレンススタッフに参加しやすくなることが多々あります。僕自身も複数のカンファレンスのスタッフをNOC繋がりでやったこともあり、それぞれのカンファレンス同士でスタッフが手伝いする循環が発生することがあります。

f:id:yskn-beer:20210903162424p:plain
(photo by PyConJP)

市場価値向上

これは入った後に気づいたのですが、カンファレンススタッフをやること自体が個人の実績として有用であり1エンジニアとして市場価値が付与されるというものがあります。

上記にも記載したとおりコミュニティへの貢献という文脈として社内外の評価が上がることがあります。

自分の場合はたまたまメディアスポンサー様のご協力の元カンファレンスレポートを執筆し、無事執筆実績の解除を行うことがありました。感謝します!

自分のペースで進められる/お互いリスペクトしあえる

実際にカンファレンススタッフになってみて一番思うことはカンファレンススタッフというのはあくまで有志のボランティアなので、プライベートの時間に合わせて作業が進められるという点です。

学生は学業が、社会人には仕事があるので時には振られたタスクを締め切りまでにこなすことが難しいときがありますが、そういった時でもキチンと「ちょっと期限までには厳しいです」と言うだけで手が空いている人が率先して手伝いや引き継ぎを行ってくれることが多く、非常に心理的に助かる面があります。

これは互いが本業を持っていること・プライベートの時間を割いていることを自覚していてリスペクトしあっているからこそ為せることだと思っています。

逆に本業が早めに終わったりしてプライベートの空き時間が開いてる時は残ってるタスクや、手一杯になっている他の人の手助けを積極的に行える環境が整っていることが多く無理なく自分のプライベートの時間に合わせて無理なく進めることができます。

スタッフTシャツがもらえる

寝間着が増えます

まとめ

"カンファレンススタッフ"と聞いて腰が重かった方もいらっしゃると思いますが本当にカジュアルにプライベートのスキマ時間を使ってコミュニティに貢献ができます。

「コミュニティ貢献はしたいけどそれでもカンファレンススタッフはまだちょっと…」という方も大丈夫です。コミュニティの貢献は色んな方法があり、これは一例です。財団への寄付やSNSでイベントの宣伝、その言語やフレームワークを使うこと自体で十分貢献できてきます。

イベントをスタッフとして作り上げるのは非常に達成感があります。ぜひ一緒にカンファレンス・コミュニティを盛り上げていきましょう!

f:id:yskn-beer:20210903162517p:plain
(photo by PyConJP)


P.S.

そして何故このタイミングでこういった投稿をしたかと言うと、僕が参加しているPyConJP2021が10月15・16日と開催されます!

https://2021.pycon.jp/

更に更にPyConJPではサポートスタッフを募集中です!

PyCon JP 2021 サポートスタッフ募集開始のお知らせ

Apache AirflowのPoCをした話

f:id:nanakookada:20210820142542p:plain# Apache AirflowのPoCをした話

はじめに

弊社『DELISH KITCHEN』のデータプラットフォーム上では、日々発生するデータをLakehouseプラットフォームに集約しており、Databricks上で処理される多数のETLジョブが存在しています。しかし、現在利用しているジョブ管理ツールでは、Databricksのジョブ同士の依存関係を細かく設定することが出来ず、実行ジョブが肥大化してしまう問題があります。
これらを適切な粒度で依存関係を設定出来るようにするため、DAGによるワークフロー定義が可能なApache Airflowを導入しました。その際に行ったPoCでの所感をお話します。

Lakehouseプラットフォームについてはこちらの記事で紹介されています。
Delta LakeとLakehouseプラットフォームによるデータウェアハウス設計

現状

Databricks Jobsによる管理

弊社ではデータ分析基盤として主にDatabricks を利用しており、ETLバッチジョブの多くをDatabricks Jobs を用いて管理・運用しています。
ETLバッチジョブの各タスクの依存関係や実行順を定義するワークフローは、DatabricksのNotebook上で定義されます。これらのジョブのスケージュール、アラートなどの設定はDatabricks JobsのWeb UI上から行っています。

これらのジョブは次のサンプルコードのように、Notebook上で定義して処理されます。

// Bigqueryからイベントの生ログを転送し、イベント毎に保存するジョブ
// task01,02が順次実行される
dbutils.notebook.run("./bronze/01_ExtractEventDataFromBQ")
dbutils.notebook.run("./silver/02_TransferAppEventToDelta")

次の図のように、Web UI上からスケージュールなどの設定をします。

f:id:ngmk:20210818153922p:plain
Databricks Jobsスケジュール設定画面

実際のアプリケーションのジョブでは、以下のようなデータ処理が実行されています。

  • BigQuery上のApp/Webのイベントの生ログを転送後、イベント毎に分解して保存
  • S3上に保存されるサーバーログをイベント毎に保存
  • 各事業部のKPIを計算

Databricks Jobsによるジョブ管理の問題点

これらのDatabricks Notebookで処理されるジョブには、ジョブ間の依存関係を設定できない問題が存在します。この問題は、処理方式がCRONとなっており、時間制御によるジョブスケジュール管理ツールのため、他のジョブの状態を考慮していないことから発生しています。
ジョブの完了をトリガーとしたジョブの実行ができず、トリガーとなるジョブのワークフロー内に後続ジョブの実行を定義する必要があります。
ジョブのワークフロー内に別のジョブ実行を定義することは、本来ジョブが持つ関心事を曖昧にし、データワークフローの見通しが悪くなるだけでなく、データワークフロー管理の観点やジョブ肥大化の観点などにおける様々な問題を引き起こします。

例えば以下のような3つのジョブがあるとします。

  • アプリのログを保存する
  • アプリ内検索のKPIを計算する
  • アプリ内課金のKPIを計算する

検索、課金のKPIを計算するジョブは、アプリログを保存するジョブの完了を実行条件として要求します。
3つのジョブの流れを図に起こすと次のようになります。

f:id:ngmk:20210818182916p:plain
3つのジョブのワークフロー

各ジョブには論理的な繋がりがなく、関心事毎に個別のジョブとして定義・管理されている状態が望ましいですが、Databricks Jobsでは依存関係の設定ができないため、本来は個別に定義されるべきジョブが1つのジョブとして定義されてしまいます。

例えば、アプリログを保存するジョブの完了をトリガーに、各KPIを計算するジョブの実行が不可能なため、1つのジョブ内でそれぞれのジョブを定義することになります。
次の図はそれぞれのジョブが同一のノートブック内に1つのジョブとして定義されている状態を表します。

f:id:ngmk:20210818183010p:plain
依存関係を設定できない場合のワークフロー

単一のジョブとして定義された各ジョブは、順次実行されるため実行時間が長くなるだけでなく、ジョブ毎に要求される適切な計算資源の割当ができません。そのため最も重い処理に要求される計算資源を長時間使い続けることになります。大きな計算資源を長時間使用することは、ジョブの運用に要求されるコストを増加させるため望ましくありません。

例えば、KPIの計算はアプリログを保存するジョブと比較して計算資源を要求しませんが、ジョブをまとめて定義してしまったことで、必要な計算資源よりも大きな計算資源が割り当てられてしまい、不必要なコストの増加が起こります。

このように、関心事によって適切な粒度でジョブを分割し定義されていない状態は以下のような問題を引き起こします。

  • ジョブの肥大化による実行時間の長期化
  • ジョブの計算量に応じた適切な計算資源の分配ができない
  • 上記2つに起因するコストの増加
  • 将来的なワークフロー変更に対応できない
  • データワークフローの見通しの悪化

Apache Airflowの導入

ジョブ間の依存関係を設定できない問題を解決するため、Apache Airflow を導入します。
Apache Airflowはジョブ間の依存関係を設定できるだけではなく、GCP、AWS、Databricksなどの弊社で利用している様々なサービスへのタスク実行をサポートしているため、検討することにしました。

Apache AirflowではETLバッチジョブのワークフローをDAG(有向非巡回グラフ) として、1つのPythonファイルで定義します。これらのジョブのスケジュール、アラートなどの設定もDAGを定義したPython ファイル内で定義され、ワークフローだけではなくジョブの設定を含めたコードベースでの管理が可能です。

次のサンプルコードのように、Databricks上のジョブを実行するDAGを定義できます。

# need pip install airflow==2.0.2, apache-airflow[databricks]==2.0.2

import datetime

from airflow import DAG
from airflow.providers.databricks.operators.databricks import

DatabricksRunNowOperator, DatabricksSubmitRunOperator
from airflow.utils.dates import days_ago

cluster_settings = {}

with DAG(
        # アラート、スケジュールを設定
        dag_id='example_databricks_jobs',
        default_args={
            'owner': 'admin',
            'depends_on_past': False,
            'email_on_failure': False,
            'email_on_retry': False,
            'retries': 2,
            'retry_delay': datetime.timedelta(seconds=10)
        },
        schedule_interval='@hourly',
        start_date=days_ago(2)
) as dag:
    task1 = DatabricksRunNowOperator(
        task_id='run_now_operator',
        job_id='65',  # databricks > jobs > job ID
        notebook_params={'env': 'prd'}
    )
    task2 = DatabricksSubmitRunOperator(
        task_id='submit_run_operator',
        json={
            'notebook_task': {
                'notebook_path': 'Product/bronze/01_ExtractEventDataFromBQ',
                'base_parameters': {'env': 'prd'}
            },
            'new_cluster': cluster_settings
        }
    )
    # ワークフローを定義
    task1 >> task2

定義したDAGによるワークフローは、ダッシュボード内のGraph Viewによって可視化できます。データワークフローが可視化されることは、依存関係の素早い把握に繋がり、多数のジョブの運用を助けます。

次の画像は上記のDAGによるワークフローが可視化されたもの表しています。

f:id:ngmk:20210818154549p:plain
airflowによるワークフローの可視化

ジョブ間の依存関係の考慮

Apache Airflowでは、ExternalTaskSensorモジュールという他のDAGに定義されているタスクの実行結果(成功/失敗)を検知するモジュールを使用することで ジョブ間(DAG間)の依存関係を設定できます。
このモジュールを後続として実行したいDAGの最初のタスクとして定義すると、アプリログを保存するジョブの終了後にKPIを計算するジョブを実行するなどのジョブの依存関係を設定したワークフローを表現できます。ジョブの実行結果を検知するタスクを後続のワークフロー内に定義することで、トリガーとなるジョブ内に後続ジョブの実行を定義する必要がなくなります。
ジョブのワークフロー内に別のジョブ実行を定義する必要がなくなったことは、ジョブが持つ関心事を明確にし、データワークフロー管理の観点やジョブ運用の観点などに様々な利点をもたらします。

次の図のようにジョブを個別のDAGとして定義できます。

f:id:ngmk:20210818183055p:plain
依存関係を設定したワークフロー

ジョブを個別に定義することで、並行実行による実行時間の短縮や、ジョブごとの適切な計算資源の割当により、不必要な計算資源を長時間使い続けることがなくなります。 必要な計算資源を必要な時間だけ使用することにより、ジョブの運用に要求されるコストが最適化されます。

例えば、アプリのログを保存するジョブには大きな計算資源を、各KPIを計算するジョブには小さな計算資源を割り当て、KPI計算を並行して実行することで、ジョブ全体での実行時間の短縮と計算資源の適切な割当により運用コストを抑えることができます。

このように関心事が分離され、適切な粒度でジョブを分割することで以下のような恩恵を受けます。

  • ジョブの計算量に応じた適切な計算資源配分
  • 上記によるコスト削減
  • ジョブの並列実行によるワークフロー全体で見た実行時間の短縮
  • ワークフロー変更に対する柔軟性
  • データワークフローの見通しの好転

最後に

以上、ETLバッチジョブの管理ツールとしてApache AirflowをPoCした話でした。
Apache Airflowを導入したことで、ジョブ間の依存関係を考慮したワークフロー定義が可能になり、適切なジョブの定義によりで様々な恩恵を得ることができました。
ジョブを関心事毎に適切な粒度で定義することで、各ジョブの見通しがよくなる、コストが最適化されるなどの恩恵を享受でき、多数のETLバッチジョブを長期に渡る管理・運用を可能とすることが期待されます。

参考

  1. Databricks
  2. Jobs | Databricks on AWS
  3. Apache Airflow
  4. Apache Airflow Concepts
  5. 有向非巡回グラフ - Wikipedia

エブリーでインターンをしています

f:id:nanakookada:20210812114528p:plain

はじめに

はじめまして。 2021年2月から、インターンとしてデータ&AIチームでデータエンジニア業務に携わっている金安です。 入社からの約半年間、データに関わる多種多様なタスクを経験させていただきました。 ここではエブリーでのデータ分析の様子を紹介するとともに、業務を通して学んだことを整理しようと思います。

いきさつ

私は大学で情報処理技術・人工知能技術について勉強しており、アカデミックな研究の道と、ものづくりに携わるエンジニアとで進路に迷っていました。 そのような中で、何となく参加した逆求人イベントでエブリーのCTOとお話しする機会があり、インターンでエンジニアとして就業することになりました。 プログラミング歴も浅く、実務経験も全くない私を採用していただき感謝しています。

現在はエブリーで週2日業務に従事しながら、大学で量子熱力学・量子情報科学分野を研究しています。 熱力学に登場するエントロピーと情報理論におけるエントロピーは本質的に等価であることからもわかるように、両者の間には接点があります。 これに注目し、情報理論の知見を物理学に持ち込むことで、非平衡熱力学など未完成の理論の発展に貢献する、というのが当分野の一つの目標です。 私の研究は、近年発見された「熱力学不確定性関係」という不等式にスポットが当てられています。 これはエネルギーと精度の関係を示唆すると解釈することができ、実用的には、量子コンピューターの性能限界の理解に繋がります。 総じて分野横断的な研究であり、情報が物理世界で果たす役割を新たな視点から探ることができます。

エブリーのデータ分析基盤

エブリーでは、毎日大量のデータを集計し、加工・分析して得た知見を施策に反映させるデータ分析のOODAループが回っています。 OODAは、Observe, Orient, Decide, Act の頭文字で、意思決定プロセスにおける4つのステップにあたります。

第一ステップは観察(Observe)で、データを集計・分析し、事実を集めることに相当します。 これをもとに、仮説構築(Orient)を行います。 これは今後の施策の方向性を決める非常に重要なプロセスです。 仮説を立てたら、具体的にどのような施策を行うかを決定(Decide)します。 最後に施策を実行(Act)します。 以上のステップを何度も繰り返すことで、変化し続ける状況の中で、適切な判断を下し、迅速に行動に移すことが可能になります。 これがOODAループです。

このデータによるOODAループを回す根底には、DIKWモデルが前提としてあります。 DIKWは Data, Information, Knowledge, Wisdom の頭文字で、下図のようなピラミッド構造になっています。

DIKWモデルのイメージ
DIKWモデル

(出典:The Knowledge Pyramid: A Critique of the DIKW Hierarchy)

集めてきただけの生のデータ(Data)は、乱雑で整理されていないうえに重複や欠落が含まれている可能性もあり、それだけでは意味を持ちません。 これをクレンジングし、何らかの基準で整理して初めて、情報(Information)として意味を見出すことができるようになります。 さらにここから、特定の観点で情報を凝集させたり、統計的な数量を算出したりすることで、より抽象度の高い知識(Knowledge)や知恵(Wisdom)に昇華させることができます。 これらは施策を行う上での判断の根拠となり、OODAループを回し、データから価値を生み出すことに繋がります。

エブリーでは、これに則ったデータ分析基盤が構築されています。 大量の生データが、Google BigQuery や Amazon S3 といったストレージに集約されます。 これを、databricksというデータ分析プラットフォームを通して加工・分析・凝集し、目的ごとに多様なテーブルを作成しています。 さらにこれをRedashというツールで集計・可視化し、組織の意思決定に役立てています。

最近体験した業務とその振り返り

ここでは、私がインターン生として最近取り組んだタスクについて振り返ってみます。

エブリーが様々な施策やサービスを運用する中で、「施策の定量的な検証・可視化をより高速に行いたい」という課題がありました。 これを達成するため、Redash上で高度な統計処理を行い、よりカジュアルに統計情報を処理できる可視化基盤を作成する、という目標が立てられました。 RedashではもともとPythonスクリプトを実行できますが、通常はRedashのコンソール上で直接コードを入力して実装します。 しかしこの方式には、実装コードがGitHubで管理されない、テストが行われていないなどの問題がありました。 そこで、統計処理用のスクリプトをモジュール化したものを、Redashサーバにデプロイすることを検討しました。

このタスクは、直接データに触れるよりは、サービスを運用するインフラ構成の把握が主だったのですが、周辺知識がほぼ0だった私にとっては、次から次へと学びを得られて非常に新鮮でした。 Redashのサーバが構築されているdockerコンテナに触れてみたり、そのサーバからS3上に通信してファイルを受け渡してみたり、Pythonライブラリが0から作られる様子を見てみたりと、新しい体験の連続でした。

特に印象的だったのが、チームリーダーからの「GitHubでmergeすると、どうしてそのコードが本番環境で動き始めると思う?(意訳)」という言葉です。 私はそこで初めて、GitHub上からサーバにコードをデプロイしている、circleCIというツールの存在を知りました。 確かに言われてみれば、git上でコードのバージョン管理をしても、全然違う場所にあるサービスのコードが勝手に書き換わるわけはないのですが、そんなことは考えたことも疑問に思ったこともありませんでした。

このRedashサーバへのモジュールデプロイは、客観的には特別難易度の高いタスクではないと思いますが、本当に有意義な体験ができたと感じています。

おわりに

エブリーでは日々実力不足を痛感させられていますが、同時に成長を実感することもあり、業務は非常に充実しています。 今後ともよろしくお願いします!


エブリーでは、エンジニア以外にもインターン生を募集しています。 興味を持たれた方は、こちらをご覧ください。