Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
35
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

@yuki24

andpush という FCM(GCM)のクライアント gem を書いた

Android の世界には FCM (Firebase Cloud Messaing) という、Android 端末に push notification を送るためのプロトコルがある。そして、Ruby の世界には fcm gem という、そのプロトコルを簡単に叩くためのライブラリが存在する。

この fcm という、まるで Google 公式 gem であるかのような名前なのだが、全然公式ではないし、コードを読んでみるとこれがかなりやばい。具体的に何がやばいかというと、

  • 必要もなく httparty gem と json gem に依存している。
  • Persistent connections を使っていないので、とてつもなく遅い。
  • 単にコードの質が低い。

このレベルだとpull request を送って改善するのも無理そうだし(json gem を依存関係から削除するというのは PR 送ったけど)、新しいものを作った方が世界が幸せになれそうだと思ったので、andpush という gem を作った。

The andpush gem

例によって gem 'andpush' するか、gem i andpush すればすぐに使える。

require 'andpush'

server_key   = "..." # Your server key
device_token = "..." # The device token of the device you'd like to push a message to

client  = Andpush.new(server_key)
payload = {
  to: device_token,
  notification: {
    title: "Update",
    body: "Your weekly summary is ready"
  },
  data: {
    extra: "data"
  }
}

response = client.push(payload)

json = response.json
json[:canonical_ids] # => 0
json[:failure]       # => 0
json[:multicast_id]  # => 8478364278516813477

と、使い方は fcm gem と大差ないが、ベンチマークを取ってみるとその差は歴然:

require 'benchmark/ips'
require 'andpush'
require 'fcm'

server_key   = ENV.fetch('FCM_TEST_SERVER_KEY')
DEVICE_TOKEN = ENV.fetch('FCM_TEST_REGISTRATION_TOKEN')

FCM_CLIENT      = FCM.new(server_key)
PAYLOAD_FOR_FCM = { dry_run: true, notification: { title: "u", body: "y" } }

ANDPUSH_CLIENT      = Andpush.build(server_key)
PAYLOAD_FOR_ANDPUSH = PAYLOAD_FOR_FCM.merge(to: DEVICE_TOKEN)

Benchmark.ips do |x|
  x.report("andpush") { ANDPUSH_CLIENT.push(PAYLOAD_FOR_ANDPUSH) }
  x.report("fcm")     { FCM_CLIENT.send(DEVICE_TOKEN, PAYLOAD_FOR_FCM) }

  x.compare!
end

結果:

$ ruby bench.rb
Warming up --------------------------------------
             andpush     2.000  i/100ms
                 fcm     1.000  i/100ms
Calculating -------------------------------------
             andpush     27.903  (± 7.2%) i/s -    140.000  in   5.063236s
                 fcm      7.232  (±13.8%) i/s -     35.000  in   5.020404s

Comparison:
             andpush:       27.9 i/s
                 fcm:        7.2 i/s - 3.86x  slower

(benchmark のために dry_run: true にしているが、Google さんごめんなさい。)

ご覧の通り、4倍近い throughput が出た。これは、短時間に大量の push notification を送りたい場合に非常に重要である。

なぜ速いの?

net-http-persistent gem で提供される persistent connections を使っているので、一番最初のリクエストだけで SSL のハンドシェイク等が行われ、それ以降は実際のリクエストだけがやりとりされるので速い。たぶん。実は HTTP にそこまで詳しくないので詳しい人教えて下さい。

net-http-persistent gem だって依存の一つではないか?

その通りであるが、作者の Eric Hodel は Ruby committer だし、個人的には Net::HTTP と同じような感覚で使っている。中身もざっくり読んで問題無さそうなので、自分で書くよりはこれを使う方がいいだろうと判断した。さらに、net-http-persistent は connection_pool gem というまた別の gem に依存しているが、これは sidekiq の作者 Mike Perham によって作られたものなので、(そこまでじっくりコードは読んでないものの、ある程度)信頼できる。これ以外の依存は無い。

結論

  • 遅い実装には遅い理由があり、速い実装には速い理由がある。
  • Pipelining を使えばもっと速くなるだろうが、レスポンスや例外の処理が悩ましくな流のでそれはまた今度。
  • コードを読まずに名前やスターの数だけで判断して gem を使うのはやめよう。
  • fcm gem よりも andpush gem を使おう。
  • andpush gem のフィードバックお待ちしています。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
35
Help us understand the problem. What are the problem?