ちょっと話題の記事

[AWS][iOS] Amazon SNS で APNs に大量 Publish してみた

2014.08.14

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

APNs に Push する配信サーバーの課題

みなさま、Amazon SNS (※以降 SNS) は活用されていますでしょうか?

APNs (Apple Push Notification service) を利用してサーバーから iOS デバイスに Push 通知を送りたい、という要件はよくあると思います。 しかし次の記事にあるように、APNs の仕様上 エラーが発生したあとの Push 通知が無効になる という問題があります。ヘタすると 10 万ユーザーに送っているつもりが 1,000 ユーザーにしか届いていないという問題にもなりかねません。

Apple Push Notification Serviceのエラー処理について | hagino3000's blog

また、APNs の仕様を読んでみると、次のような記述があります。

If you send a notification that is accepted by APNs, nothing is returned. If you send a notification that is malformed or otherwise unintelligible, APNs returns an error-response packet and closes the connection. Any notifications that you sent after the malformed notification using the same connection are discarded, and must be resent. Figure 5-2 shows the format of the error-response packet.

Provider Communication with Apple Push Notification Service | iOS Developer Library

意訳すると、

  • 通知が有効な場合は何も返さない
  • 通知が不正な場合はエラーレスポンスを返し、接続を閉じる
  • 通知が不正だと判断されたあと、同じ接続の通知はすべて破棄される
  • 破棄された通知は再送信する必要がある

ということになります。つまり、配信サーバーを構築する上で、Push するときに不正なトークンがあると同じ接続の通知は無効になるという課題を解決する必要があります。

一方、SNS は堅牢性・信頼性・スケーラビリティに優れたメッセージングサービスです。そんな SNS なら上記の課題をきっと解決してくれるはず!ということで、SNS から APNs に大量に Publish したとき、正常に通知されるか検証してみました。

検証実験

検証実験の内容

検証実験の内容は以下のとおりです。

  1. 9,995件の無効なエンドポイントを登録
  2. 5件の有効なエンドポイント (iOS デバイス) を登録
  3. これらの10,000件のエンドポイントを登録したトピックを作成
  4. すべてのトピックに対して Publish
  5. 5件の有効なエンドポイント (iOS デバイス) すべてに通知が届けば成功とする

有効なエンドポイントをもっと増やしたかったのですが、用意できる iOS デバイスが 5 台のみでした。

なお、SNS のエンドポイントには Enable という属性があり、この値が true のエンドポイントのみ配信を行われると思われます(SNS の動作の仕組みまでは公開されていないので、このような表現になってしまいますが)。そのため、今回の検証では 9,995 件の無効なエンドポイントはすべて Enable が true の状態で Publish します。

SNS の環境構築

SNS で APNS に Push 通知するまでの環境構築は以下の記事を参照してください。

[AWS] Amazon SNS の新機能「Mobile Push」を iOS で使ってみた | Developers.IO

トピックの作成と有効なエンドポイントの登録

サンプルアプリを作成し、各 iOS デバイスで実行して APNs のデバイストークンを取得、これを SNS にエンドポイントとして登録します。トピックも作成し、上記で作成したエンドポイントが Subscribe するように設定します。

sns_apns01

無効なエンドポイントの登録

次に無効なトークンを作成し、エンドポイントとして登録します。これは手動だと大変なので Ruby で書きました。

create_endpoint.rb

require 'aws-sdk'
require 'securerandom'

sns = AWS::SNS.new(
  :access_key_id => 'YOUR_ACCESS_KEY',
  :secret_access_key => 'YOUR_SECRET_ACCESS_KEY',
  :region => 'us-east-1')
client = sns.client

num = ARGV[0].to_i

num.times do |index|
  # 無効なエンドポイントを作成
  endpoint = client.create_platform_endpoint(
    platform_application_arn: 'arn:aws:sns:us-east-1:************:app/APNS_SANDBOX/SNSSample',
    token: SecureRandom.hex(32),
    custom_user_data: 'Test' + index.to_s)
  # TopicをSubscribeする
  client.subscribe(
    topic_arn: 'arn:aws:sns:us-east-1:************:test_topic',
    protocol: 'application',
    endpoint: endpoint[:endpoint_arn])
end

次のように、実行すると引数で渡した数値ぶんのエンドポイントを作成します。

ruby create_endpoint.rb 9995

…めっちゃ時間かかるので気長に待ちましょう。

sns_apns02

Publish してみる

10,000 件のエンドポイントを1つのトピックに Subscribe させることができました。それでは、このトピックに対して Publish してみます。

publish.rb

require 'aws-sdk'

sns = AWS::SNS.new(
  :access_key_id => 'YOUR_ACCESS_KEY',
  :secret_access_key => 'YOUR_SECRET_ACCESS_KEY',
  :region => 'us-east-1')
client = sns.client

response = client.publish(
  target_arn: 'arn:aws:sns:us-east-1:************:test_topic',
  message: 'レリゴー')

実行すると…タイムラグはあるものの、無事に 5 デバイスすべて通知を受け取ることが出来ました! 2, 3回試してみましたが、最大で1分間くらいのタイムラグはありました。このくらいは全然許容範囲だと思います。

sns_apns03

これは憶測になりますが、各エンドポイントに対する Publish はそれぞれ別々に接続していることが考えられます。そのため、トピック内に無効なエンドポイントがあった場合でも、有効なエンドポイントへの配信が実現できているんだと思います。あくまで憶測ですが。

まとめ

本来であれば、有効なエンドポイントをもっと増やして試してみたかったのですが、検証実験として試せるのはこのあたりでしょうか。しかし、有効な 5 件すべてに配信することが無事にできたので、結論としては SNS はちゃんとやってくれているはず!という信頼が持てるような結果が得られたと思います。自前の配信サーバーを用意しなくても、ここまで信頼性が保てるのはすごいと思います。

また、SNS は100万リクエスト/月が無料で利用できます。超過する場合でも、100万リクエスト毎に $1.0 と安価です。配信サーバーを立てずにここまで安価で利用できるのは非常に魅力的ですよね。稼働中の配信サーバーに問題を感じているのであれば、SNS への乗り換えを検討してみてはいかがでしょうか?

一方で、SNS を使うと分散型の特質と転送ネットワークの状況により、二重でメッセージが配信される可能性があるという問題もあります。この点については、前提条件として考慮し、アプリ側で不整合が発生しないように対処しておく必要があります。

参考