Gunosyデータ分析ブログ

Gunosyで働くデータエンジニアが知見を共有するブログです。

SQL: 継続率と獲得数で将来のDAUをさくっと予測してみる

いつものやつ

この記事は Gunosy Advent Calendar 2017、9日目の記事です(フライング)。

qiita.com

はじめに

Gunosyデータ分析部の大曽根です。 好きなギタリストはジミ・ヘンドリクスです。 前日の@ij_spitzに引き続きKPI管理に関しての記事を書こうかと思います。

f:id:dr_paradi:20171208161809p:plain

なぜ将来を予測することが重要か

ニュースアプリの場合には、毎日開いてくれるユーザが何人いるかが非常に重要です(売上 = DAU * ARPUで表現できます)。 そのため、現在のDAUが目標値に達しているのかいないのか、どの程度の割合で達成しているのかをモニタリングすることが必要になります。 予測に対しての達成割合により、

  • 「成長で売り上げ目標が達成可能なのかの判断をする」
  • 「そもそも成長可能なビジネスなのか判断する」
  • 「好調な要因を特定し、施策に反映する」
  • 「達成できない要因を改善する施策を出す」

などの、不確定な状況への対応ができ、目標達成へより近づくことができます。

概要

データ分析においては、ただ集計された数字を眺めるだけではなく「比較すること」が必要です。 前回のブログでは下記のように書かれています。

data.gunosy.io

『Gunosyでは各指標に対して過去の実績値から目標値を日次で設定しているので、目標値も同時にモニタリングできるように可視化しています。 これによって現在のプロダクトがどのような状況にあるのかを数値的に見ることができます。』

例えば、昨日のとあるKPIが「100」という値であった場合に、前日が「90」なら上昇していると判断できます (当たり前)。 また、「100」という数字がいいのか悪いのかを判断するために、時系列のトレンドだけではなく、事業の目標にあっているのかをモニタリングすることが重要になります。 本記事では、SQLで目標値を算出する方法を書いていきます。

準備するもの

例えば、昨日DAUは Σ 特定の日からの昨日までの経過日数における継続率 * 特定日の獲得 で表現できるので、 「特定の日の獲得」と「特定の日からの昨日までの経過日数における継続率」がわかれば、DAUを予測することができます。

  • 日々の獲得の予算
  • 継続率の予算

以下のような感じのテーブルを用意して見ます。

日々の獲得の予算

CREATE TABLE user_acquisition (
  `date` date DEFAULT NULL,
  `budget` int(10) DEFAULT NULL
)

中身

date budget
2018-01-01 1000
2018-01-02 1000
2018-01-03 1000
2018-12-31 1000

継続率の予算

CREATE TABLE retention_rate (
  `date_duration` int(10) DEFAULT NULL,
  `retention_rate` double DEFAULT NULL
)

中身

date_duration retention_rate
0 1
1 0.495
2 0.408
365 0.0098

※ 継続率は[0,1] の単調減少関数なので、  R(i) =  \prod_{i=1}^n a(1 - (\frac{1}{2}) ^ i ) で雑に定義しています (R(0) = 1、iは初回起動からの日数、 a = 0.99の定数) *1。 こんな感じの関数です。

f:id:dr_paradi:20171208164504p:plain

SQLでの算出

それでは、材料を元に将来のDAUをSQLで算出して見ましょう。

昨日DAUは Σ 特定の日からの昨日経過日の継続率 * 特定日の獲得 で表現できます(算数ですね)。

ため、下記のクェリで記述することができます。

基本編

SELECT
    target_date,
    sum(active_users) AS users
FROM
(SELECT
    u1.date AS regist_date,
    u2.date AS target_date,
    TRUNCATE(u1.budget * r.retention_rate, 0) AS active_users,
    datediff(u2.date, u1.date) AS duration
FROM
    user_acquisition u1, user_acquisition u2, retention_rate r
WHERE
    u1.date <= u2.date
    AND datediff(u2.date, u1.date) = r.date_duration) predict
GROUP BY target_date
ORDER BY target_date

Redashで可視化すると下記のようになります。先ほど定義した継続率で、1日平均1000人獲得すると年末のDAUが4万人程度になることがわかります。 これにARPUをかけてあげれば日々の売り上げも予測できますね。 予測により、獲得が足りない、継続率が足りない、などの議論ができるので予測から逆算することは非常に重要です。

f:id:dr_paradi:20171208164808p:plain

サブクェリでは特定の日(regist_date)に初回起動(登録)をしたユーザ群がn日後の特定日(target_date)に何人になっているか計算しています。

SELECT
    u1.date AS regist_date,
    u2.date AS target_date,
    TRUNCATE(u1.budget * r.retention_rate, 0) AS active_users,
    r.retention_rate
FROM
    user_acquisition u1, user_acquisition u2, retention_rate_test r
WHERE
    u1.date <= u2.date
    AND datediff(u2.date, u1.date) = r.date_duration
ORDER BY
    regist_date, target_date

結果はこのようにないります。仮定した継続率では、1000人獲得したユーザが、1年後には9人になってしまう計算になります。 悲しいですね。

regist_date target_date active_users retention_rate
2018-01-01 2018-01-01 1000 1.00
2018-01-01 2018-01-02 495 0.495
2018-01-01 2018-01-03 408 0.408
2018-01-01 2018-12-31 9 0.0098

また、月別獲得数などもキャンペーンなどの影響での獲得チャネルの変動を確認する上で重要になります。 登録月でグループ化し、可視化することでより影響がわかりやすくなるでしょう。

SELECT
    target_date,
    date_format(regist_date, '%Y-%m') AS regist_month,
    sum(active_users) as users
FROM
(SELECT
    u1.date AS regist_date,
    u2.date AS target_date,
    truncate(u1.budget * r.retention_rate, 0) AS active_users,
    datediff(u2.date, u1.date) AS duration
FROM
    user_acquisition u1, user_acquisition u2, retention_rate r
WHERE
    u1.date <= u2.date
    AND datediff(u2.date, u1.date) = r.date_duration) predict
GROUP BY target_date, regist_month
ORDER BY target_date, regist_month

f:id:dr_paradi:20171208165514p:plain

もし、ここで「年末のDAU低くない?」と思ったりした方は獲得や継続率をいじってみましょう。

継続率を  R(i) = \prod_{i=1}^n a(1 - (\frac{1}{3}) ^ i ) にしてa = 0.995にして見ましょう。 継続率を変更した場合の結果が以下の図になります。

f:id:dr_paradi:20171208165211p:plain

なんと年末のDAUが9万人を超えました。倍以上です。

このように皮算用ができます*2

応用

例えば下記のようなテーブルを用意すれば都度のキャンペーンにも対応できます。 また、獲得チャネルごとに予算を用意することもできます。継続率も同様です。

CREATE TABLE user_acquisition_by_channel (
  `date` date DEFAULT NULL,
  `channel_name` varchar(32) DEFAULT NULL,
  `budget` int(11) DEFAULT NULL
)

dailyだけでなくweekly、monthlyでの指標に関しても同様の予測が可能です。

おわりに

本記事では、日々のKPI管理のために予算テーブルから、SQLで予測されたDAUを算出する方法を記述しました。 目標から逆算し、何が足りているか、いないのかを考え、意思決定をすることは非常に重要です。

本記事ではあまり言及しませんでしたが、予算テーブルの継続率や獲得も、過去ログから近似することもできます。 より精度の高い予測をすることで、意思決定の障壁となるノイズなどを除去できるでしょう。 *3

*1:aは(1 - 日々の離脱率)のようなイメージです。

*2:予算に組み込む際にはぬか喜びになりかねないので注意してください。継続率はそう簡単にはあがりません

*3:その分管理も大変そうですが。

Gunosyを支えるKPI管理

この記事は Gunosy Advent Calendar 2017 8日目の記事です。 qiita.com

今日話すこと

こんにちは、データ分析部の @ij_spitz です。 つい昨日誕生日を迎えて25歳になりました(もうお◯さんですね)。 Gunosyではプロダクト開発の様々な場面でデータを活用しています。

Gunosyにおけるデータ活用は

  • KPI管理
  • プロダクト開発
    • 仮説出し、優先度付け、効果測定、意思決定
  • 記事配信アルゴリズム
    • どのユーザーにどの記事を配信するか

の3つに大きく分類できます。 今回はこの中でもKPI管理に焦点を当てて、書いていきます。

KPI管理とは

GunosyではメインのKPIをいくつかの指標に分解してモニタリングしています。 コンサルの間でよく使われるロジックツリーと考え方は同じで、よくある例だと1日の売上は客数と客単価の掛け算に分解できるというものです(アプリでよく使われる用語に置き換えるとDAUとARPUでしょうか)。

  • 売上
    • DAU
      • 新規獲得数
      • 継続率
    • ARPU(Sales/DAU)
      • ...

Gunosyでは各指標に対して過去の実績値から目標値を日次で設定しているので、目標値も同時にモニタリングできるように可視化しています。 これによって現在のプロダクトがどのような状況にあるのかを数値的に見ることができます。

ダッシュボード

ダッシュボードは本ブログで何度も登場しているRedashを使用しています(まじ便利)。 構成は先ほどのロジックツリーを意識していて、メインのダッシュボードには木の上の方にある数値が並んでいます。 Redashのダッシュボードにはリンク付きのテキストを埋め込むことができるので、リンクを辿っていくと枝葉の数値まで辿れるようになっています。

f:id:ishitsukajun:20171208110737p:plain

数値を見る仕組み

せっかくKPIを分解してダッシュボードを作成しても、その数値を毎日定常的に見なければなんの意味もありません。 Gunosyでは数値を見る仕組みとして、数値確認朝会とSlack通知という2つの手段を用いています。

数値確認朝会とはデータ分析部で実施している、プロダクトの各指標を毎朝確認するための朝会です。 各メンバーが週替りで担当の指標を決めていて、その数値がどんな状態なのかをレポートしていきます。 朝会用の資料にはQiita Teamを使用していて、ダッシュボードへのリンクや主要なグラフが埋め込まれている投稿が毎朝自動で投稿されるようにしています。

f:id:ishitsukajun:20171208122443p:plain

数値確認朝会で気になった数値があれば、朝会後調査を行うという流れになります。 例えば、1日後の継続率が下がったのであれば特定の経路で下がっているのか、ABテストや新しいアプリのリリースがあったかなどを調べていきます。 その際にプロダクトの変更点がまとまっていると便利です。

data.gunosy.io

数値確認朝会には以下のメリットがあります。

  • 毎朝必ずKPIを確認するので、数値の全体的な傾向がわかる
    • プロダクトがどんな状況にあるのか把握できる
  • 意図しない数値低下を防げる
    • 先ほどの例だと知らないうちに1日後の継続率が下がっていた等

Slack通知はそのままなので、あまり特筆すべきことはないのですが、SlackのUIに適した速報値などはSlackで通知することが多いです。 また、日次の数値も通知していて、分析部以外の開発メンバーや営業メンバーが見ることが多くなるので、数が多くなりすぎないように主要な数値を通知するようにしています。

おわり

駆け足ではありましたが、今回はGunosyでのデータの活用方法の1つであるKPI管理についての話でした。 その他のデータの活用方法(仮説出し、優先度付け、効果測定、意思決定)についてもいつか書きたいなと思ってます。

弊社ではエンジニア絶賛採用中なのでプロダクト開発におけるデータ分析に興味がありましたら、気軽にランチでもいきましょう(下のリンクは僕のお気に入りのパスタのお店です)。

www.wantedly.com

tabelog.com

プロダクトの変更ログを記録することと、Slack+Zapier+Google Calendarを利用した記録の自動化について

グノシー開発部の@cou_zです。最近はPUNPEEのアルバムをよく聴いています。

日々、KPIを追っていると、意図せずにトレンドが変化することが良くあります。

なぜトレンドに変化があったのかを調査するためには、その時に何が起こっていたのかを知っている必要があります。「何が起こっていたのか」を全て覚えておくことは不可能なので、後で振り返られるようにログを残しておくと便利です。

GunosyではGoogleカレンダーで個人の予定を管理しているため、アプリの変更・出来事もGoogleカレンダーに「グノシープロダクトカレンダー」を作り、影響のありそうな出来事を登録しています。

かつては、手動でカレンダーに登録していましたが、定期的に発生するイベントの登録はSlack + Zapierを使って自動化しました。今日は、Googleカレンダーを用いてプロダクトのログを残すということと、Slack + Zapierを用いた自動化について紹介します。

よく起こること、KPIのトレンドの変化

Gunosyにおけるプロダクト改善は、KPIの可視化による現状把握から始まると考えています。 KPIの可視化やトレンド抽出については、以下のブログで紹介しました。 data.gunosy.io data.gunosy.io

上記のブログ記事のようにKPIを可視化していると、サービスがどういった状況にあるのか、を数値的に把握することが出来ます。 最近ではKPIの変化に素早く気付けるために、トレンド抽出や異常検知の自動化を進めています。 これらの取り組みは、KPIの変化に対する気付きを早めたり正確にすることは出来ますが、その変化の要因が何なのかまでは教えてくれません。

例えば、グノシーのKPIの変化に影響がありそうな出来事としては、以下のようなものがあります。

  • デプロイ、新バージョンのリリース、ABテスト等のプロダクトの変更
  • 大きな事件やニュース、速報プッシュ等の外的要因
  • 障害、バグ
  • 大規模プロモーション

KPIの変化が、プロダクトの変更により意図したものであれば良いですが、意図したものでない場合には、過去のログを遡ったり調査を行う必要があります。

Googleカレンダーで出来事管理

グノシーでは、上記のような調査を迅速に行うために、メンバー間で共有可能なカレンダーを作り、日々起こったことを登録していっています。

f:id:cou_z:20171203150133p:plain

これで過去の出来事を振り返ることが楽になりました。

Slack + Zapier + Google Calendarを利用した自動化

かつては、上記のカレンダーの運用は手動で行っていました。 しかし、登録が面倒であったり、登録漏れがあったりしてしまうため、フォーマット化出来るものは自動化しました。

例えば、「デプロイやABテスト等のプロダクトへの変更」は元々Slackで共有することにしていたため、Slackへ投稿すると自動でGoogle Calendarに登録するようにしました。

Slackへの投稿をトリガーにGoogle Calendarにイベントを登録するのはZapierを利用しました。 Zapierというのは、複数のサービスを連携させた作業の自動化ができるサービスです。例えば、Zapierのトップページには、Gmailにメールが届いたら、添付ファイルをDropBoxに上げて、DropBoxに新しいファイルが出来たことをSlackに通知できると載っています。 会社でZapierを利用することに、Zapier for Teamsを会社で契約して使ってみた - Qiitaが詳しかったです。

Zapierは上記のような多段の連携や、PythonでFilter処理を書くことが出来ます。元々Slackで共有していたフォーマットは変更無しに、「デプロイやABテスト等のプロダクトへの変更」のみをGoogle Calendarに登録する際に、Pythonで柔軟にFilter処理を書くことが出来る機能が便利でした。

処理の流れは、

  1. (今まで通り)Slackに「デプロイやABテスト等のプロダクトへの変更」を共有する。
  2. ZapierがSlackの投稿から「デプロイやABテスト等のプロダクトへの変更」のみをFilterし、Google Calendarに登録する。
  3. Google Calendarに登録されたイベントのURLを 1 の投稿に返信するかたちでSlackに投稿する。

のようになっています。

1. (今まで通り)Slackに「デプロイやABテスト等のプロダクトへの変更」を共有する。

ここは何も変更無く、今まで通り以下のようにSlackで「デプロイやABテスト等のプロダクトへの変更」を共有していました。

f:id:cou_z:20171203150145p:plain

2. ZapierがSlackの投稿から「デプロイやABテスト等のプロダクトへの変更」のみをFilterし、Google Calendarに登録する。

Zapierを利用した流れは以下のようになっています。

  1. 特定のSlackチャンネルへの投稿をトリガーにzapを起動する。(zapというのはこの一連の処理のことを言う。)
  2. 次のステップ3のFilterで使用する変数を投稿から抽出する。
  3. 投稿がGoogle Calendarに登録する条件を満たしているかFilterする。
  4. Google Calendarにイベントを登録する。
  5. Google Calendarに登録されたイベントをSlackに通知する。

f:id:cou_z:20171202202252p:plain

1の出力では、 text という値に

【日時】
xxxxx

【内容】
yyyyy

【影響】
zzzzz

のようにSlackへの投稿内容が入っています。 ステップ2では、ステップ3でこの投稿がGoogle Calendarに登録する条件を満たしているか判定するための変数をPythonコードで定義します。 Pythonは2.7のため、日本語を扱う場合はu'ほげほげ'とする必要があるのが注意点です。

output = {
    'is_deploy_report': input_data['text']がデプロイについての投稿かどうか判定してtrue or falseを入れる,
    'summary': input_data['text']からタイトルぽいところを抽出する,
    'description': 全文ぽく、input_data['text']とinput_data['permalink']を入れる,
    'datetime': input_data['text']内のu'【日時】'の文字列をいれる,
}

ステップ3では、is_deploy_reporttrueの場合のみステップ4に繋げるようにします。

3. Google Calendarに登録されたイベントのURLを 1 の投稿に返信するかたちでSlackに投稿する。

以下のように自動で返信することによって、Google Calendarにイベントが登録されたことを教えてくれます。

f:id:cou_z:20171203150203p:plain

最後に

簡単にですが、グノシーで実施しているプロダクトカレンダーによるプロダクトの変更ログを記録することと、Slack + Zapier + Google Calendarを利用した記録の自動化について紹介しました。

KPIのグラフに、その日の出来事を重ねて表示出来るようになるといいなあ。

BigQueryのクエリ課金額をslack通知する

f:id:y-abe-hep:20171115200343p:plain:w300

はじめに

こんにちは、データ分析部の阿部です。

Gunosyには社内警察と呼ばれる人がおり、たとえばデータ可視化の際に円グラフを使うと正しい使い方を教えてくれる、母数という言葉の使い方を正してくれる、方々がいます。

tech.gunosy.io

今回はBigQueryで課金額の高いクエリを投げると警告してくれる、課金警察というボットを作ったので紹介します。 BigQueryはクエリで使われるデータ量に対して従量的に課金されるため、クエリ毎の課金額が把握できると便利です。 Gunosyではエンジニア・非エンジニア問わず、インターン生でも自由にクエリを書いて分析できる環境となっているため、知らず知らずのうちに大胆なクエリが投げられることもあります。 そのため、課金警察でクエリ毎の課金額をSlackに通知しお互いに監視して注意しようという意図です。

f:id:y-abe-hep:20171116142308p:plain

どうやってやるか

BigQueryにはクエリの情報を取得するAPIが用意されているので、Google Apps Scriptでそれを呼び出して使います。

https://developers.google.com/apps-script/advanced/bigquery

このJobs: list というAPI

https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/list

のレスポンスで、totalBytesProcessedというのがあるので、これにBigQueryのクエリ課金額($5/TB)を掛けることで算出します。

https://cloud.google.com/bigquery/pricing

タイマートリガー(上にあるツールバーのタイマーアイコンをクリック)で10分ごとにスクリプトが動くようにして、データ使用量が一定以上のクエリをslackで通知します。

f:id:y-abe-hep:20171115200318p:plain

BigQueryのAPIにアクセスするためには、GCPのプロジェクトに権限を与える必要があります。 ツールバーの Resources から Advanced Google Services を選択し、BigQuery APIをONにします。

Google Apps Script

こんな感じのスクリプトを書きます。

function listJobs(projectId, threMinutes, threDataSize) {
  var jobIds = [];
  
  var joblist = BigQuery.Jobs.list(
    projectId,
    {
      'maxResults': 100,
      'allUsers': true
    }
  );
  
  for (var i = 0; i < joblist['jobs'].length; i++) {
    if (!joblist['jobs'][i]['statistics']['query']) {
      continue;
    }
    
    // threMinutes 分以上前のジョブは検知しない
    var now = new Date();
    if (now.getTime() - parseInt(joblist['jobs'][i]['statistics']['startTime'], 10) > threMinutes * 60 * 1000) {
      continue;
    }
    
    // threDataSize GB 以下は検知しない
    if (joblist['jobs'][i]['statistics']['totalBytesProcessed'] / 1000000000 < threDataSize) {
      continue;
    }
    
    jobIds.push(joblist['jobs'][i]['id'].split(':')[1]);
  }
  
  return jobIds;
}

function fetchJobInfo(projectId, jobid) {
  var job = BigQuery.Jobs.get(projectId, jobid);
  var price = parseInt(job['statistics']['totalBytesProcessed'], 10) / 1000000000000 * 5  // 5ドル/TB
  var start_time = new Date(parseInt(job['statistics']['startTime'], 10));
  var query = job['configuration']['query']['query'];
  var user_email = job['user_email'];
  
  return {
    'job_id': jobid,
    'user_email': user_email,
    'price': price,
    'start_time': start_time,
    'query': query
  }
}

function notify(webhook_url, channel, username, icon_emoji, jobInfo) {
  var message = '```';
  message += 'jobid = ' + jobInfo['job_id'] + '\n';
  message += 'user_email = ' + jobInfo['user_email'] + '\n';
  message += 'start_time = ' + jobInfo['start_time'] + '\n';
  message += 'estimated cost = $' + jobInfo['price'] + '\n';
  message += 'query:\n';
  message += jobInfo['query'];
  message += '```\n';
  
  var payload = {
    'channel': channel,
    'username': username,
    'icon_emoji': icon_emoji,
    'text': message
  };
  
  var options = {
    'method': 'post',
    'contentType': 'application/json',
    'payload': JSON.stringify(payload)
  };
  
  UrlFetchApp.fetch(webhook_url, options);
}

function run() {
  projectId = '<GCP Project ID>';
  var jobList = listJobs(projectId, 10, 100.0);
  
  for (var i = 0; i < jobList.length; i++) {
    var jobInfo = fetchJobInfo(projectId, jobList[i]);
    notify(
      '<slack webhook url>',
      '#hogehoge',
      '課金警察',
      ':money_with_wings:',
      jobInfo
    );
  }
}

Tierに関しては無視してしまいました。 APIレスポンスの中に、billingTierというのがあるのでこれを使えばより正確な課金額を知ることができるでしょう。

悩み

Google Apps Script はGDriveで手軽に書けるので便利ですが、会社の社員アカウントにスクリプトが紐付けされてしまいます。 人に紐付かない形で管理できればいいですが、どうすればよいのでしょう。

おわりに

Gunosyでは課金額を通知して可視化することで必要以上にコストかからないようにしています。 こうすることで安心してクエリを投げることができ、快適に分析が進められるのではないでしょうか。

データ分析部が開発・運用するバッチ アプリケーション事情

はじめに

こんにちは、データ分析部の森本です。
この記事ではGunosyデータ分析部がどのような視点に基づいてバッチアプリケーション(以下、バッチ)を開発・運用しているかしているのかを紹介します。

クライアントアプリ開発やAPI開発と比較してバッチ開発のノウハウなどをまとめたWeb記事の数は少なく感じます。 また、言語に関わらずWebフレームワークの数に対して、バッチフレームワークの数も少数です。
このような点を踏まえると一般的には難易度の高くない(ノウハウを必要としない、フレームワークに頼る必要のない)、もしくはニーズがあまりないなどの印象があるのかもしれません。

一方で我々は日々バッチ開発を行い、数多くの地雷を踏んできました。 これらの経験を踏まえてどのような点に気をつけているのかについて共有します。
理想的には多くの方の経験を共有して、建設的な議論に発展するとうれしいです。

全体像

Gunosyではニュースアプリを開発しています。ニュースアプリ開発には大きく5つの柱があります。

  • iOS, Androidのクライアントアプリ開発
  • クライアントからのリクエストに対応するAPI開発
  • 提携しているメディア様からニュース記事を収集するクローラー開発
  • ニュース記事をプッシュ配信するプッシュ機能開発
  • スコアリングやニュース記事分類などの記事配信アルゴリズム開発

(アドテクノロジーとインフラは別の大きな枠組みであるため、ここでは除外しました)

データ分析部ではデータ分析や機械学習を活用したプロトタイプの実装だけでなく、プロダクションコードの実装、デプロイ、運用まで実施しています。

また、バッチにも複数の種類があると思います。
我々が開発しているバッチは金融機関等の日次、月次処理とは趣が異なります。
ユーザー満足度向上を支えるために機械学習などを活用し、スコアなどを計算するバッチと捉えてください。

開発について

フレームワーク

記事配信アルゴリズムは主にpythonで実装しています。
特に数値計算ライブラリnumpy, pandasや機械学習ライブラリscikit-learn、各種deep learningライブラリの力を借りたいというのがpythonを使用している主な理由です。 一方、バッチフレームワークとしてDjangoを利用しています。
DjangoといえばWebフレームワークに分類されると思いますが、なぜバッチ開発に利用しているのでしょうか。
Djangoをバッチフレームワークと使用するメリットに次のようなものが上げられます。

  • 同期処理/非同期処理を柔軟に実装できる
    • Commandクラスを使用した同期処理、Celeryを使用した非同期処理を用途に応じてDjango上で実装できる
    • Celeryを使用することでmaster/slave形式で分散処理を実現できる
  • MySQL、Redisを始めとした各種データソースへのアクセス処理が充実
    • バッチ開発の基本はInput/Process/Output
    • Input/Outputのデータアクセス層のコードを比較的綺麗かつ隠蔽して書くことができる
  • ロギング処理の充実
  • デバッグが容易
    • python manage.py shell で対話的に実装できる
  • ドキュメントが充実&十分枯れた技術

逆にここがいまいち

  • ORMコードが書きづらい
    • この点は慣れやエンジニアの技術レベルに依存するとは思いますが、Railsと比較するとなかなかシンプルに書けないと感じています(主観)

さて、バッチフレームワークにDjangoを使用していますが、他の候補としてLuigiやAirflow等が挙げられます。 皆様の中にこれらフレームワークをプロダクション環境でガッツリ使用している方がいらっしゃいましたら、ぜひ情報共有したいです。

どこに記事配信アルゴリズムを書くか

いわゆるサービス層のロジックをどこに書くかとも表現できます。
Input/Outputを担う各種データソースへのアクセスコードは各種データソースごとにappディレクトリを作成しています。
記事配信アルゴリズムコードは散らからないように1appディレクトリ内に閉じ込めるように努めています。 一応この方法でコードが散在することは防げますが、より確かな方法を模索中でもあります。

デプロイ環境

デプロイ環境はプロダクション環境、ステージング環境、サンドボックス環境の3種類あります。
新しいアルゴリズム(例えばニュース記事の分類器やクラスタリングロジックなど)をリリースするときはステージング環境で動作検証します。
一方でスコアリングアルゴリズムではアクセスログも使用します。ステージング環境では十分なアクセスログがありません。そこでサンドボックス環境を用意しました。サンドボックス環境ではプロダクション環境と同様のアクセスログを取得でき、スコアリングアルゴリズムの検証を実施できます。このようにステージング環境とサンドボックス環境の2種類の検証環境があり、用途に応じて使い分けてます。

ABテスト

新しいアルゴリズムをリリースする時、ステージング環境、サンドボックス環境で動作確認できても、いきなりプロダクション環境に100%デプロイすることはありません。ABテストを実施してKPIが向上するか(もしくは想定内の減少をするか等)を必ず確認します。

data.gunosy.io

品質

品質チェックはステージング環境、サンドボックス環境で十分に行います。また最後の砦としてABテストがあります。
一方恥ずかしながら、記事配信アルゴリズム開発については明文化されたテスト計画はありません。
そのため、テストコードの実装は複雑なロジックに対して重点的に書くなどのエンジニア依存の部分もあります。
また、プルリクエストレビューは必ず実施します。比較的即座にレビューしてくれるのでメンバーには感謝です。

実装&デバッグ

組織ではなく個人の感想になってしまいますが、pdbなどのデバッガを使用するよりも python manage.py shellで対話的に実装して同時にデバッグするほうが生産性が高いと思っています。 また、対話的にすすめることでORMコードのSQLも容易に確認でき、重い処理の把握や改善を即座にできるのがうれしいです。
プロダクション環境でしか発現しない問題の原因追求にも python manage.py shellで実施する場合もあります。
ただしこの方法はおすすめできるものではありません。本来はログより確認すべきで、それをログから確認できない時点でログ実装が不十分といえます。
開発生産性を高めるために皆様がどのように実装しているか知恵を借りたいです。

運用について

デプロイ

Gunosyでは全プロダクトを通して、デプロイは主にAWS OpsWorksを使用しています。ボタンポチでデプロイでき、非常にありがたいです。SREチームに頭が上がりません。

f:id:moyomot:20171010141809j:plain

ロギング&監視

データ分析部が運用するバッチは多数あります。そのためリリース直後を除いてINFOログを定期的に監視するということはしません。問題が発生したときのみタイムリーに気づける仕組みを取っています。

  • アプリケーションにERRORログを実装&必要に応じてリトライ処理
  • ログ監理はpapertrailを使用
  • エラーログはpapertrailを通じてwebhookでSlack通知

f:id:moyomot:20171010142905p:plain

おわりに

我々が日々実施していることを比較的かっこよく書いてみました。一方ではpython2系のスクリプト集合体のバッチ群も現役バリバリで生き残っていたりします。
言うは易く行うは難しであり、泥臭い運用に頼っている点もあります。
ベターなノウハウが共有されて、よりよいバッチ開発ライフを実現できることを祈っています。
(バッチ開発運用Nightとかやりたくなってきた)

参考

qiita.com