RoomClip 開発者ブログ

AWS SDK for Ruby V3 Pinpointを試した

お久しぶりです。

ルームクリップ株式会社でエンジニアをしています、仲本です。

当社は、2018-05-02をもちまして、ルームクリップ株式会社になりました。 今後ともなにとぞよろしくお願いいたします。

今回は、RubyのSDKでPinpointを使ったので、ポストしたいと思います。 (ここ最近社内でWEBアプリケーションの言語はRubyで統一しようという流れになっているのもあり)

やること

  1. PUSH通知対象のJSONを作成
  2. S3にJSONをアップロード
  3. セグメントを作成
  4. キャンペーンを作成
  5. PUSH送信

PUSH通知対象のJSONを作成

Pinpointでは、セグメントに登録するエンドポイント(device_tokenとかの事ですね)をJSON形式でS3にアップロードしておいて、それをセグメント作成時に読み込ませる事が出来ます。

サンプルとして次のようなJSONを作成します。

xxxxxxxxxxxにはテスト端末のdevice token(iOS)もしくはregistration id(Android)を入力してください。

test.json

{"ChannelType":"APNS","Address":"xxxxxxxxxxx","Demographic":{"Platform":"iOS","Make":"Apple"},"Attributes":{"userName":["taro"]}}
{"ChannelType":"GCM","Address":"xxxxxxxxxxx","Demographic":{"Platform":"Android","Make":"Google"},"Attributes":{"userName":["jiro"]}}

S3にJSONをアップロード

Endpointを固めたJSONをS3にアップロードする処理を追加していきます。

AWS-SDKを使います

Gemfileに追加します

gem 'aws-sdk-s3', require: false

実際に使うソースで読み込みます

require 'aws-sdk'

s3のインスタンス生成

s3 = Aws::S3::Resource.new(
  region: 'us-east-1',
  credentials: Aws::Credentials.new('YOUR_ACCESS_KEY', 'YOUR_SECRET_KEY')
)

JSONをアップロード

こんなエンドポイントになる想定
s3://YOUR_BUCKET_NAME/pinpoint/test.json

file_path = 'test.json'
key = 'pinpoint/test.json'
File.open(file_path, 'rb') do |file|
  s3.client.put_object(bucket: 'YOUR_BUCKET_NAME', key: key, body: file)
end
's3://YOUR_BUCKET_NAME/' + key

セグメントを作成

Pinpointインスタンス生成

pinpoint = Aws::Pinpoint::Resource.new(
  region: 'us-east-1',
  credentials: Aws::Credentials.new('YOUR_ACCESS_KEY', 'YOUR_SECRET_KEY')
)

create_import_jobでセグメント作成リクエスト

create_import_jobをすると、AWSコンソールのセグメント一覧にYOUR_SEGMENT_NAMEとして作ったものが出てきます。今回のサンプルは小さいのですぐにインポートが完了します。

pinpoint.client.create_import_job(
  application_id: 'YOUR_APPLICATION_ID'
  import_job_request: {
    define_segment: true,
    format: 'JSON',
    register_endpoints: false,
    role_arn: 'arn:aws:iam::xxxxxxxxxx:role/xxxxxxxxxx',
    s3_url: s3_url,
    segment_name: 'YOUR_SEGMENT_NAME'
  }
)

パラメータ

  • application_id
    PinpointのProjects一覧にIDが記載されています
  • define_segment
    trueにしてセグメント取り込みで新規作成します
  • format
    JSONとCSVが選べます。今回はJSONです。
  • register_endpoints
    取り込みと同時にエンドポイントを保存する場合はTRUE
  • role_arn
    S3に読み書き出来るARNを指定
  • s3_url
    先程アップロードしたS3エンドポイントです。
    s3://YOUR_BUCKET_NAME/pinpoint/test.json
  • segment_name
    セグメントに付ける名前

キャンペーンを作成

セグメントが完成したら、セグメントIDを使ってキャンペーンを作成します。

resp = pinpoint.client.create_campaign(
  application_id: 'YOUR_APPLICATION_ID',
    write_campaign_request: {
      holdout_percent: 0,
      is_paused: false,
      limits: {
        daily: 100,
        maximum_duration: 60,
        messages_per_second: 400,
        total: 1000000
      },
      message_configuration: {
        default_message: {
          action: 'OPEN_APP', # accepts OPEN_APP, DEEP_LINK, URL
          body: '{{Attributes.userName}}さんにお知らせ♪'
      },
    },
    name: 'YOUR_CAMPAIN_NAME',
    schedule: {
      frequency: 'ONCE', # accepts ONCE, HOURLY, DAILY, WEEKLY, MONTHLY
      is_local_time: false,
      start_time: (Time.now + 1.minutes).iso8601,
      timezone: 'UTC+09'
    },
    segment_id: 'YOUR SEGMENT ID',
    segment_version: 1
  }
)

パラメータ

  • holdout_percent
    PUSHを送信しない確率
  • is_paused
    停止状態でTRUE
  • limits
    • daily
      1日に送信出来る件数
    • maximum_duration
      キャンペーン終了後に送信出来るようになる時間(秒)
    • messages_per_second
      秒間送信数
    • total
      最大送信数
  • message_configuration
    PUSH通知の内容を定義。APNS、GCMなど細かく分ける事が出来ますが、今回は最低限。
    {{Attributes.userName}}はテンプレート変数で、最初に作ったJSONに指定した、メッセージ毎のパラメータ。
  • schedule
    • frequency
      1ショットなのか、どういう頻度で繰り返すのか
    • is_local_time
      時刻のローカライズを行うか
    • start_time
      送信開始日時(iso8601)
      今回は1分後に設定
    • timezone
      タイムゾーン
  • segment_id
    先程作成したセグメントID
  • segment_version
    セグメントに付けられるバージョン。
    抽出軸は同じだが抽出するタイミングで変わる類のものの時に使えそう。

PUSH送信

各々のスタイルでPUSHを待つ。

実際にやってみて

  • SDKの仕様書ではパラメータの仕様が細かいところまで理解しにくかったため、REST APIの仕様書をよく使いました。
  • create_import_jobが完了するまで時間がかかる場合があるため、キャンペーン作成前にwaitした方が良さそうです。

以上となります。ありがとうございました。

参考


この記事を書いた人:仲本

ルームクリップ株式会社のエンジニア 社内SE・サーバーサイド・インフラ周りを中心に担当。 味のあるオッサンを目指して頑張っている。 夜は酒場に居る事が多い。


RoomClipのSolr構成について

こんにちわ、エンジニアの熊谷です。

今回は、RoomClipの検索エンジンで用いているSolrの運用・構成ついて書いてみます。

まずはじめに、

ひとえにSolr構成といっても、組織・サービス規模や要件によって構成が変わると思いますので、対象を以下のように絞りたいと思います。

「小〜中規模のサービスを運用している、少人数のインフラチームで、安定的かつ柔軟なSolr基盤を、簡単に構築したい方」

となります。

また、 「安定的かつ柔軟」についても、もう少し掘り下げておきたいと思います。非機能要件の中で、Solrの特性を考慮した上で、大きく三つに分けて考えたいと思います。

  1. 可用性: Solrインデックスのバックアップや冗長構成。また障害時の復旧方法について。この辺は、体系化されたRDBに比べ、Solr導入時はわりと戸惑うように思います。
  2. 性能・拡張性: Solrの負荷増大時のスケールアウトやスペック増強、またSolrのアップグレードのしやすさも含みます。
  3. 運用・保守性: スキーマー変更やシノニム・辞書の更新。インデックス追加・更新・削除やリインデックスのしやすさについて。実際に、Solrを導入して検索精度を上げようと思うと、試行錯誤を繰り返すことになるため、スキーマー変更やリインデックスが簡単にできる構成が望ましいかと思います。

ついでに、 「少人数のインフラチームで、〜簡単に構築」ともありますが、ここに関しても、もう少し具体的に対象エンジニアを述べると、

上司・クライアントから 「今、運営しているサービスの検索(orレコメンド orランキング)が全然いけてないから、新たにSolr導入して実装しなおしてみてよ。あ、でもSolrの専任は置けないから片手間でね。あ、あと絶対に落ちないような感じにね。」とさらりと言われたエンジニア、となります。

※注1. 勝手な妄想であり、RoomClipではそんな風には言われません。

まぁ要は、

「人的資源を節約しつつ、簡易的に可用性・冗長性を実現したい。」

ということになります。

さて、目指すべきSolr構成がぼんやりと見えてきたところで(?)、それらをどのように実現するかと考えたいと思います。

おそらく、実現方法として、SolrCloudとAWS CloudSearchが、有力候補として上がってくると思います。が、弊社では採用しませんでした。理由ついては、さらりと流します・・。

  • SolrCloud: 実際に本番で運用するには、(当たり前だけど)、SolrCloudやZooKeeperの学習コストが必要な上に、追加でZooKeeperの運用・保守も発生するため、インデックスがクラスタリングしなければならないような規模でないかぎりは、冗長構成として少々ファットで高コスト(な印象)。人と時間を費やせば、フルマネジメントに近い構成は構築できると思うが、今の要件やリソースを踏まえると、現段階では適切ではないと判断。
  • AWS CloudSearch: フルマネジメントは魅力的だけど、機能が限定的でフィールドタイプやトークナイザーなども限られ、どこまで互換があるのかもわかりづらく、新機能のキャッチアップもできない、など諸々の理由で不採用。    

では、RoomClipではどのようなSolr構成にしているか。

先に結論を述べてしまうと、非常にシンプルで、

「Solr DIH(Data Import Handler」+ AWS AutoScaling + GitHub 」

となります。

以下、要点だけかいつまみたいと思います。

1. Solr DIH(Data Import Hander)

SolrにはRDSと連携できるDIHという機能が備わっているので、データの永続性の責務は、Solrではなく、MySQLに任せてしまいました。バックアップやリカバリのことも考えると、Solrにデータの永続性を担保させるのは厳しいなという判断でした。

DIHには、RDSのテーブルの内容を丸ごとインポートする「フルインポート」と、差分だけインポートする「デルタインポート」があるので、インスタンス生成時にフルインポートして、その後の追加・更新・削除分は、デルタインポートで対応するといったことができます。

また、デルタインポートとソフトコミットを組み合わせることで、RDSのテーブルデータとほぼリアルタイム(完全には無理)に近い形で同期することもできます。

Screen Shot 2017-06-27 at 9.54.47.png

2. GitHub連携

Solrの検索精度向上のために試行錯誤する過程で、スキーマーファイルや辞書・シノニムファイル、またDIH設定ファイルなどを度々変更することになるため、よくあるWebアプリケーションのデプロイ構成を参考にしつつ、自動化しています。

注意点として、Solrの場合は、変更箇所によっては、インデックスを作り直す必要があるため、リインデックスする仕組みも組み込んでおくと後々楽です。

実際は、Solrにはリインデックスするための機能はないため、新しくコアを作成して古いコアと入れ替えるという方法をとっています。コアの入れ替えには、SolrのCoreAdminにあるSWAP機能を使うことで、リクエストを止める事なく、ダウンタイム0での入れ替えが可能です。

Screen Shot 2017-06-27 at 9.54.47.png

3 AWS ALB

現在、RoomClipではSolrコアが写真検索用、アイテム検索用など4種類あり、それぞれ負荷に偏りがあるのですが、ALB配下に置いておくことで、簡単に分散することもできます。

Screen Shot 2017-06-27 at 9.55.35.png

4. Redash & Redshift (番外編)

本題とは外れますが、 ログの格納にはRedshift、可視化にはRedashを使っています。

Screen Shot 2017-06-27 at 9.56.16.png

ということで、 ざっくりですが、RoomClipのSolr構成の紹介でした。

すでにAWSを利用している方であれば、比較的簡単に構築できるかと思います。

特に、Solr+EC2だけの単体構成で運用している場合では、なにかと余計な作業が発生しがちだと思いますので、先に基盤を構築しておいて、クエリやスキーマーの調整に集中できる環境を整えておく事で、結果的に、サービス改善が迅速に進むのではと思います。

次回は、Solrのクエリまわりについて書けたらなと思います。

それではごきげんよう。


この記事を書いた人:rkumagai

熊です。RoomClipのインフラ〜サーバーサイドを担当しています。