Amazon SNS で Push 通知を一斉配信するときのフィルタリングについて考える

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

ある特定のユーザーに絞って Push 通知を送りたい

Amazon SNS Mobile Push を利用すると、iOS や Android といったモバイルデバイスに対して非常に簡単に Push 通知を送ることができます。

例えば 1 つのアプリに対して一斉に Push 通知を送りたい場合は Endpoint を Topic にまとめて Publish すれば良いだけなので、簡単です。しかし、アプリやサービスによってはアプリを利用しているある特定のユーザーのみに Push 通知を送りたいという要件もあると思います。つまり、ユーザーを特定の条件でフィルタリングしたいというわけです。

ということで、Push 通知を送るユーザーのフィルタリングについて、目的別に実現方法を考えてみました。

Push 通知の一斉配信に求められること

Push 通知が関連したサービスで、次のような要望をよく聞きます。

  • 安定性のある Push 通知を送りたい
  • Push 通知を受け取る時間を出来る限り揃えたい
  • 数百万人規模にサービスがスケールしても、Push 通知のパフォーマンスは落としたくない

SNS を利用すれば、数万人規模のユーザーに対して Push 通知を一斉配信することが可能です。しかし、ここにフィルタリングという要素を加えてしまうと、どうしてもパフォーマンスが出しづらくなってしまいます。

ユーザー情報を変更するには、今までであればサードパーティ製のサーバーに API を用意し、モバイルからはその API に対してリクエストするという構成が主流だったと思います。 このような構成はサーバー側のパフォーマンスに依存するため、ユーザー情報の読み書きを行う処理のボトルネックになってしまう可能性があります。

sns-publish-filtering01

そこで活用すべきは AWS Mobile SDK です。 AWS Mobile SDK を利用すると、例えば DynamoDB のレコードをモバイルから直接読み書きしたり、SNS の Topic の Subscribe を直接リクエストしたりできます。

sns-publish-filtering02

AWS の各サービスを出来る限り直接的に利用することによって、AWS の持ち味である高可用性を損なうことなく、安定した運用が実現できるはずです。また EC2 インスタンスのコストもかからなくなるので、低コスト化も見込めます。

どのようなフィルタリングを行いたいか?

まず Push 通知を送るユーザーに対してどのようなフィルタリングを実施したいか考えてみましょう。フィルタリングの材料となるのは、コンテキストや設定値といった、ユーザーそれぞれが持っている情報です。例として、サービスの要件としてありそうなフィルタリングをいくつか挙げてみましょう。

  • あるカテゴリに関心があるユーザーに対して情報を配信したい ... A
  • お気に入りのショップに登録しているユーザーに情報を配信したい ... A
  • ある年齢層に絞って特定の情報を配信したい ... B
  • ある範囲内に住んでいる人に絞って情報を配信したい ... B

上記のフィルタリングを実装するにはどうすれば良いか考えていくと、条件によってフィルタリング方法が異なりそうということが分かります。その条件を実装寄りの表現にすると、次のようになると思います。

  • A : 「ある特定の値にマッチする」というフィルタリング
  • B : 「ある特定の値の範囲内に含まれる」というフィルタリング

ということで、今回はこの2つのフィルタリングを実現する方法を考えてみたいと思います。

「ある特定の値にマッチする」というフィルタリング

これは、ユーザー情報のある属性値が、Push 通知を送る条件となる属性値とマッチしているかどうかというフィルタリングです。

例えば、ビデオショップ専用アプリで「お気に入り店」を登録する機能があったとします。ユーザーはよく行く店舗をお気に入りに登録するわけですが、この店舗限定で開催されるキャンペーン情報のお知らせをユーザーに通知したい場合があります。この場合「ある特定の店舗をお気に入りに登録しているユーザーのみフィルタリングする」という機能が必要になります。

このフィルタリングは、ある特定の値を Topic とし、この Topic に対して配信したい Endpoint を Subscribe していくように構成すれば良さそうです。

sns-publish-filtering02

特定の Topic を Subscribe / Unsubscribe する処理自体は、AWS Mobile SDK を利用する *1ことでサーバーを介さず SNS に直接リクエストすることができます。なお、店舗情報には Topic の Arn が設定されている必要があるとともに、モバイルから直接アクセスできるようにしておく必要があります。したがって、店舗情報は DynamoDB で管理しておくほうが良いでしょう。

イメージを具体化したいので、簡単なコードを載せます。以下は iOS アプリから Topic を Subscribe する例です。

- (BFTask*)subscribe:(NSString*)topicArn endpoint:(NSString*)endpointArn
{
    // SNSにEndpointを登録する
    BFTaskCompletionSource* taskCompletionSource = [BFTaskCompletionSource new];
    AWSSNS *sns = [AWSSNS defaultSNS];
    // 対象のTopicにEndpointを登録する
    AWSSNSCreateEndpointResponse *response = task.result;
    AWSSNSSubscribeInput *request = [AWSSNSSubscribeInput new];
    request.topicArn = topicArn;
    request.endpoint = endpointArn;
    request.protocol = @"application";
    [[[sns subscribe:request] continueWithBlock:^id(BFTask *task) {
        if (task.error) {
            [taskCompletionSource setError:task.error];
        } else {
            [taskCompletionSource setResult:@(YES)];
        }
        return nil;
    }];
    return [taskCompletionSource task];
}

SNS ではデフォルトで、Topic は 1 アカウントあたり 3,000 件まで、Subscription は 1 Topic あたり 1,000 万件までしか作成できない点に注意してください *2。フィルタリング条件が 3,000 を超える場合は AWS のサポートに上限の引き上げをリクエストする必要があります。

「ある特定の値の範囲内に含まれる」というフィルタリング

これは、Push 通知を送る条件に範囲を持たせ、ユーザー情報のある属性値がその範囲内であるかどうかというフィルタリングです。

例えば、チラシアプリでタイムセール情報を配信する機能について考えてみましょう。タイムセールなので、対象の店舗の近くに居るユーザーのみに配信できれば良いこととします *3。この場合「現在地がある特定の位置情報の範囲内であるユーザーのみフィルタリングする」という機能が必要になります。

結論から言うと、このような範囲指定はかなり難しいです。数百万人という規模で考えると、フィルタリングした結果によっては一人ひとりに対して Publish するにしても、ある Topic に対して Subscribe させるにしても、処理に時間がかかってしまい、Push 通知を受信する時刻にかなりの差が生まれてしまいます。

範囲指定が実現可能な実装方法について「即時性が必要ない場合」と「即時性が必要な場合」の2通りを考えてみました。なお双方とも、データストアに DynamoDB を利用する前提です。

即時性が必要ない場合

  1. モバイルからユーザー情報 Table の Item の RangeKey の値を逐一書き込む
  2. 配信サーバーは配信時に RangeKey を使って、ある条件の範囲内のユーザーを検索する
  3. 配信サーバーは Topic を作成し、検索した結果の Endpoint を 1 件ずつ登録していく
  4. 配信サーバーは完成した Topic に対して Publish する
  5. フィルタリング条件にマッチしたデバイスに Push 通知が届く

sns-publish-filtering03-2

Push 通知が必要なタイミングで Topic を作成し、条件にマッチしたユーザー情報の Endpoint を登録していきます。不特定多数の Endpoint を 1 件ずつ登録する処理が入ってくるので、Push 通知を実際に送るまでに多少時間がかかります。そのため、この時間のぶん期待している配信時間とのギャップが生まれます。

なおこの方法では RangeKey が重要になってきますが、DynamoDB に位置情報を対象にしたい場合は Geo Library for Amazon DynamoDB を利用しましょう。

即時性が必要な場合

  1. 配信サーバーは配信日時より前に Topic を作成しておく
  2. 配信サーバーはフィルタリング条件 Table の Item を作成し、Topic の Arn を登録しておく
  3. モバイルからフィルタリング条件 Table の Item を取得する
  4. モバイルは条件とマッチするか逐一チェックし、マッチしたときだけ対象の Topic を Subscribe する
  5. 配信サーバーは指定時刻になったら Topic に対して Publish する
  6. フィルタリング条件にマッチしたデバイスに Push 通知が届く

sns-publish-filtering04

フィルタリング条件 Table の Item には、条件の値と Topic Arn を格納します。例えば位置情報のフィルタリングであれば、条件の値にはジオ情報(緯度、経度)と範囲(メートルなど)を入れておきます。

モバイル側では位置情報をモニタリングし、条件にマッチしたタイミングで Topic Arn に Subscribe します。逆に条件に合わなくなったら Unscribe するようにします。こうすることで、各モバイルからの情報をリアルタイムに反映した Topic が出来上がります。

まとめ

今回は、2つの絞り込み条件を題材に、フィルタリングする方法について考えてみました。他にもいろいろなフィルタリングの方法があるかも知れませんが、今回はこの辺までということで。また思いついたらブログで公開していきたいと思います。

脚注

  1. Amazon Cognito を利用した AWS Credential を取得する仕組みも必要です。
  2. よくある質問に記載されています。
  3. お気に入り店舗の情報として配信したい場合もありますが、このような場合は「ある特定の値にマッチする」フィルタリングを使うと良いですね。