ピグパーティのサーバサイドを担当しています、新卒エンジニアの宮下 優希です。
最近ハマっている食べ物は、マックシェイクです。 程よい甘さで、冷たく美味しいですよね。

はじめに

ピグパーティは、アバターを介してトークを楽しむスマートフォン向けアプリです。
自分の好きな服にきせかえたり、自分の理想の家にもようがえをしたり、人狼で遊んだり、さまざまな機能を楽しむことができます。
今回、ピグパーティで初のABテストの実装を担当させていただきました。本記事では、ABテストの実装について書かせていただきます。

今回のABテストの目的

ABテストとは、施策判断のため、ある特定の期間、ユーザを2つのグループに分け、それぞれの施策を行い比較するものです。今回は、ユーザ同士のマッチングについての施策でした。
ピグパーティには、いくつかのエリアが存在し、おでかけをして、他のユーザとコミュニケーションを取ることができます。エリアは、さらにサブエリアに分かれており、各サブエリアには、001, 002, 003, … 050といったように、インデックスが付けられています。ユーザは、好きなサブエリアに移動することができます。

今回のABテストでは、

  • Aグループのユーザのサブエリアには、テーマを付けます。例えば、ラーメン好き集まれ 001、フレ募集してます! 002、などです。
  • Bグループのユーザのサブエリアには、既存と同じようにインデックスだけ表示させます。

これを一定期間行い、AグループユーザとBグループユーザの発言数やエリアの滞在時間などを集計し、サブエリアにテーマがあった方がユーザのマッチ率が向上するか比較を行います。

これらを実現するには、以下の機能が必要となります。

  • ユーザを2つのグループに分ける機能
  • AグループのユーザかBグループのユーザかで別の機能を提供するエンドポイント
  • 施策判断をするためのログ

の3つとなります。

一番難しかったのは、ユーザを2つのグループに分けることです。今回のABテストでは、エリアに対しての施策なので、エリアを2つのグループに分けなければなりません。それはユーザのコミュニケーションに関わる部分の検証だからです。UIや推薦システムのABテストとは異なり、ユーザ単位で独立に検証することはできません。そのためユーザの行動範囲を分断することにしました。これによりユーザ単位ではなく “2つの社会” の比較をすることができます。

しかし、ピグパーティでは、様々なエリア入室方法があります。

  1. ランダムでエリア入室する方法
  2. リストから選んで入室する方法
  3. フレンドのいるエリアに追っかけて入室する方法

3つのエリア入室方法

これら全てを修正する必要がありました。

ABテストのログの集計結果は、弊社秋葉原ラボ高野が集計、解析を行います。
サイバーエージェントには、秋葉原ラボという、データ解析やデータマイニングなどを専門とするエンジニアが在籍する研究開発組織があります。
秋葉原ラボメンバーとコミュニケーションを取りながら、ABテストの設計を行いました。

ピグパーティのアーキテクチャ

ピグパーティのサーバサイドのアーキテクチャについて簡単に説明したいと思います。ピグパーティの主なサーバとして、2種類存在します。

  • ショップ、きせかえ、ガチャなどの基本的な機能を提供するWeb APIサーバ
  • チャット、アバターの座標移動などのリアルタイム性が必要な機能を提供するチャットサーバ

Web APIサーバは、バージョン管理されており、新しい機能が追加されるたびにバージョンを上げます。バージョン管理することによって、古いアプリのバージョンを許可したり、アプリを強制アップデートさせることができます。

実装

今回のABテストの実装では、次のことを意識して、実装しました。

  • 最小限の工数で実装する。
  • サービス層ではなく、コントローラー層にロジックを集中させる。

1つ目の理由として、施策判断のための改修であるのに、実際の機能改修程の工数をとってしまうと本末転倒となってしまいます。 また、施策判断のため、スピーディーな開発が必要でした。

2つ目の理由として、もし、ABテストの結果が既存の方がよかった場合、コードをもとに戻さなければなりません。 そのために、Web APIサーバのバージョンで切り替えられるようにするため、サービス層にロジックが集中しないようにし、コントローラー層にロジックを集中させました。 バージョンを切り替えることで、ABテストの実装を切り捨てることができます。
コントローラー層のコードを一部紹介します。アンチパターンかもしれませんが、ABテストの対象エリア判定、AグループのユーザかBグループのユーザかの判定など、コントローラー層に記述しました。

  // ABエリア対象エリアか判定
  if (service.area.isAbTestTargetArea(category)) {
    // ABテスト対象エリアだった場合
    // ABテスト設定を取得
    var abTestSetting = service.area.getAbTestSetting();
    // ABテストのロジック
    service.area.selectAreaCodeAbTest(user, category, abTestSetting, function(err, areaCode) {
      if (err) {
        return callback(err);
      }

      ...
    });
  } else {
    // ABテスト対象エリアでない場合
    // 既存のロジック
    service.area.selectAreaCode(user, category, function(err, areaCode) {
      if (err) {
        return callback(err);
      }

      ...
    });
  }

ABユーザの判定

ユーザがAグループのユーザか、Bグループのユーザかは、ユーザIDの末尾の数字が、偶数か奇数かで判定しました。ユーザIDは、16進数で構成されています。(例: 0123456789abcdef

  • ユーザIDの末尾が、偶数の場合、Aグループのユーザ
  • ユーザIDの末尾が、奇数の場合、Bグループのユーザ

とし、以下のような、Aグループか判定する正規表現を持つようにしました。

{
  "splitRegExp": "(0|2|4|6|8|a|c|e)$",
  ...
}

この正規表現用いて、Aグループのユーザか判定する関数を作成しました。

/**
 * AテストユーザかBテストユーザか判定する
 * @param {string} userId - ユーザID
 * @return {boolean} - Aユーザかどうか
 */
AreaService.prototype.isAUser = function(userId) {
  var abTestSetting = settingCache.get(AB_TEST_SETTING);
  var reg = new RegExp(abTestSetting.splitRegExp);
  return reg.test(userId);
};

エリアの分断

ピグパーティのエリアをどのように、2つのグループに分けたのかの説明をさせていただきます。
ピグパーティのエリアは、サブエリアIDが001から050の50個のサブエリアが存在します。 ABテストの期間中このエリア数を50から100にし、AグループのユーザとBグループのユーザに分ける閾値を設定します。ここでは、その閾値を50とします。

  • もしAユーザだった場合、そのままのサブエリアIDとして扱います。
  • もしBユーザだった場合、閾値を足したサブエリアIDとして扱います。

例えば、Bユーザで、サブエリアIDが001の場合、サーバでは、サブエリアIDを051として扱うようにします。

このようにすることで、内部的には、既存のロジックを使用しながら、AグループのユーザとBグループのユーザに分断させることができます。

AグループのユーザのサブエリアとBグループのユーザのサブエリア

さいごに

今回の改修では、他の部署と関わるような、大きな仕事を任せていただき、とても感謝しています。 新卒でわからないことばかりで、たくさん迷惑をかけたと思いますが、成長することができました。
最後まで、読んでいただきありがとうございます。