sidekiq-cronアップデートへの道のり

f:id:yu_suke1994:20181121141855p:plain

こんにちは。ジョブスケジューラーとしてはSidekiqしか触れてこなかった うなすけ (id:yu_suke1994) です。

さて、Railsアプリ開発者の皆さんは定期的な bundle update を何らかの方法で実行していることでしょう。弊社でも最近になって Dependabot を導入することになりました。

今回は、Dependabot を導入する前に、一気に bundle update したときに起こった Sidekiq まわりの問題、それも sidekiq-cron で起こった問題について書いていこうと思います。

「一気に bundle update」 とは何か

弊社サービス、特に今回はCASHのAPI サーバーのRailsについての話になりますが、これまでは気付きベースで bundle update を行なってきました。

さすがに自動でやっていく仕組みを入れたいので、ツール導入のためにもう一度 bundle update を実行し、今後はツールによる差分のみを注意して見ればいいという状況に持っていくことにしました。1

問題なく bundle update できた……と思っていた

f:id:yu_suke1994:20181120171335p:plain

updateされるgemのコードを確認し、staging環境での動作確認も行ない、特にエラーもなかったのでmergeしてdeployしたのですが、本番ではJobの実行時に例外が発生するようになってしまいました。

なぜCronJobは実行されなかったのか

sidekiq-cron は、CronJob が最後にenqueueされた時刻をRedisに保存します。 v0.6.4 でのコードは以下のようになっています。

#enque cron job to queue
def enque! time = Time.now.utc
  @last_enqueue_time = time

https://github.com/ondrejbartas/sidekiq-cron/blob/v0.6.3/lib/sidekiq/cron/job.rb#L47-L74

ここで、 Time.now.utc の結果は、このまま to_s されてRedisに保存されます。

さて、 Redisから最後に queue に入った時刻を取り出す部分の v1.0.4 でのコードが以下のようになっています。

def parse_enqueue_time(timestamp)
  DateTime.strptime(timestamp, LAST_ENQUEUE_TIME_FORMAT).to_time.utc
rescue ArgumentError
  DateTime.strptime(timestamp, LAST_ENQUEUE_TIME_FORMAT_OLD).to_time.utc
end

https://github.com/ondrejbartas/sidekiq-cron/blob/v1.0.4/lib/sidekiq/cron/job.rb#L554-L558

そして、 LAST_ENQUEUE_TIME_FORMAT_OLDLAST_ENQUEUE_TIME_FORMAT は次のようになっています。

LAST_ENQUEUE_TIME_FORMAT = '%Y-%m-%d %H:%M:%S %z'
LAST_ENQUEUE_TIME_FORMAT_OLD = '%Y-%m-%d %H:%M:%S'

https://github.com/ondrejbartas/sidekiq-cron/blob/v1.0.4/lib/sidekiq/cron/job.rb#L15-L16

この辺で勘のいい方は気づかれるかと思いますが、Redisからの last_enqueue_time をparseする際、書式が想定外だと例外が上がります。

つまり、 Time::DATE_FORMATS[:default] を独自定義していた場合、 sidekiq-cron v0.6.4 時代に実行されたCronJobは v1.0.4 にアップデートすると実行できずに例外が上がってきます。

そして案の定、CASHのAPIサーバーでは Time::DATE_FORMATS[:default] が再定義されており、前述の挙動によって sidekiq-cron v1.0.4 が動作しませんでした。

アップデートするためにやること

このとき、無事に v1.0.4 にアップデートするために取り得る手段として、次の3つが候補としてありました。

  1. 正しい書式の時刻をRedisに格納し直す
    • 独自定義した書式からは時刻単位の情報が欠落しているので復元が不可能
    • Redisの値を手で操作したことがなく、不安
  2. sidekiq-cronで使用している key-value の組を全部削除する
    • これまでの実行情報が全て消えてしまう(が、そもそも正確ではないし……)
  3. 独自定義を削除し、正しい書式でRedisに格納されるまで待つ
    • 時間はかかるが安全

ということで、「独自定義を削除し、正しい書式でRedisに格納されるまで待つ」ことにしました。CronJobのなかには、毎月1回実行されるというものがあります。なので、 Time::DATE_FORMATS[:default] の設定を削除してから1ヶ月待ち、Redisに格納されている last_enqueued_at の書式が全て正しいものになっているかどうかを確認したうえで、sidekiq-cronのアップデートを行いました。

まとめ

という訳で、継続的bundle updateの体制が整いました!流れでRailsのバージョンも5.2.1になりました。

引き続き、やっていきましょう。


  1. 今考えると、そのようなことをする必要はなかったんじゃないかと思いますが……

【開催レポ】BANK engineer night #02 〜狂ったようなことをしよう〜

こんにちは。BANK広報の磯田です。

2018年10月11日に『BANK engineer night #02 〜狂ったようなことをしよう〜』を開催いたしました。BANKではBANK engineer nightを通してBANKのメンバーが、普段どのようにスピリットを持ちながらサービスを開発しているのかを、 多くの人に知ってもらいたい、という想いから今回のイベントを企画いたしました。 f:id:bankinc:20181030102342j:plain

現在BANKは、『CASH』と『TRAVEL Now』の2つのサービスの開発・運営を行なっています。 今回のイベントでは、2つのサービスを世に出し、育てていく上で、いかに狂ったように振り切ってきたのかをエンジニア4名がLTでお話しました。

眼の前のアイテムが一瞬でキャッシュに変わる『CASH』 https://cash.jp/ f:id:bankinc:20181030105011p:plain

あと払い専門の旅行代理店アプリ『TRAVEL Now』 https://travel.app/ f:id:bankinc:20181030105014p:plain

発表プログラム

CEO光本からご挨拶

はじめに、CEO光本からみなさんにご挨拶です。会社紹介と、運営している2つの事業についてお話させていただきました。 『エンジニアがうらやましい。まだ世の中に無いサービスを作り出すことができるから。』という話をしていて、みなさん深く頷かれていました。 f:id:bankinc:20181030102258j:plain

魅力品質をアゲて見たことないサービスを認知してもらう

iOSエンジニアの高橋からは、今回初めて開発秘話が話される「TRAVEL Now」のお話でした。 f:id:bankinc:20181030102447j:plain

speakerdeck.com

ユーザーの心をつかむ「魅力品質」と絶対に必要な「当たり前品質」の両方を落とさずに、新規事業の開発についてお話しました! 今までにないサービスを世の中に出すことは、受け入れられるのか怖いけど、だめならブラッシュアップして直せば良いという気持ちで開発に臨んでいたそうです。

スピード狂エンジニアたちの開発速度を保つインフラとは

サーバーサイド兼インフラエンジニアの高橋からは、スピードのお話。 f:id:bankinc:20181030102453j:plain

speakerdeck.com

入社してからどんどんメンバーが増えていく中で、数人規模で開発していたときのスピード感を落とさずに開発できる環境を整えたお話でした。 「インフラはあくまで裏方」という言葉が印象的でした!

なぜBANKは名刺でお金をばらまくのか

Androidエンジニアの遠藤より、名刺でお金をバラまいたお話をさせていただきました。 f:id:bankinc:20181030102323j:plain

speakerdeck.com

なぜ名刺なのか。おもしろさと有用性の伝え方を開発者目線からお話しました。 遊び心を忘れないバンクのエンジニアらしいアイデアですね。

Knativeで限界突破 〜Push通知をバラ撒くために〜

サーバーサイド(Ruby)エンジニアのうなすけからもバラまきのお話。こちらは「Push通知」をバラまいたようです。 f:id:bankinc:20181030102321j:plain

speakerdeck.com

CASHのユーザーに数十万件単位で送ったプッシュ通知についてお話しました。 実際に会場で、デモをして盛り上がりました!

懇親会

バンクの他のメンバーも応援に来ていたので、参加者のみなさまとお話することができました!スペースがいっぱいになるくらいの人に来ていただき、懇親会もとても盛り上がりました。 f:id:bankinc:20181030102311j:plain

おわりに

今回のイベントでは、『CASH』と『TRAVEL Now』の開発秘話についてご紹介いたしました。 BANKメンバーがどんなことにこだわり抜いて、狂ったようにサービスを育てているのか伝わるイベントになっていたら幸いです! BANKでは、サービス改善や新規事業に向けて日々取り組んでいるところですが、実現したいことに対して、まだまだ力が足りていません。そこで、一緒に新しいことをやっていただけるメンバーを探しています。

bank.co.jp

f:id:bankinc:20181030102318j:plain

https://bank.co.jp/recruit/

bank.connpass.com

今後のイベント情報についてはConnpassページについて随時更新予定です!イベント更新情報にご興味がある方は、ぜひメンバー登録をポチっとお願いします!

Google Cloud NEXT'18 in Tokyoで登壇しました

f:id:yu_suke1994:20180920144234j:plain

こんにちは。発表では早口になりがちなうなすけ (id:yu_suke1994) です。

さて、以前の記事でも告知していましたように、弊社高橋 (id:takutakahashi)、Google Cloud カスタマーエンジニアであるSokoPさんと共に弊社におけるGCPの活用事例について Google Cloud NEXT'18 in Tokyo で発表してきました。

当日お越し頂いた皆様、本当にありがとうございました。

発表内容

発表スライド、録画は以下になります。

発表については、大きく3つのテーマに分けて話しました。詳しくは資料、もしくは録画を参照していただくとして、簡単に発表した内容についてまとめます。

インフラについて

インフラについては、「開発スピードを最大化させるインフラ」を目指しています。 検証環境を複数立ち上げる、そしてそれを可能な限り早く行なうために ingress-gce をやめて nghttpx ingress controller を採用したり、CI上での docker build とテストを並列実行させたりという、様々な取り組みについて話しました。

アプリケーションについて

様々な「実験」を行なうときに、どのようにGCPのリソースを活用して素早く結果を得られるようにしたか、また開発を進めていくと生じる障害や不整合に対してどのように対処するか、などについて話しました。

開発体制について

成長していく開発組織の足止めを最小限に抑え、新メンバーが素早く開発に参加していけるようにどのようなことを行っているのかについて話しました。

発表してどうだったか

こちらから応募したとはいえ、このような規模のイベントで話す機会はこれまでになく、とても緊張しました。

規模の大きなイベントなので、6月中旬から準備が始まっています。プロフィール提出やスライド作成のための打合せ、実際のスライド作成と通しでの読み合わせなど、いい発表をしたいという思いで結構なリソースを割いて準備しました。発表後、SNSでの反応を見ていると、結構好印象だったのでとても嬉しく、苦労が報われる思いでした。

これから

発表した内容でもっと詳しく「やってやったぞ!」を伝えていきたい内容、とくにインフラに関しては今後ブログに投稿していきたいと思っています。

また、来年のGoogle Cloud NEXTでも採択されるような発表ができるよう、日々切磋琢磨していきたいですね。やっていくぞ!!

f:id:yu_suke1994:20180920152029j:plain

我々はそんなやっていく気持ちがあるエンジニアを募集しています。興味がありましたら、是非お気軽にご応募ください。

bank.co.jp

TRAVEL Now Androidアプリがリリースされました!

f:id:YousukeTezuka:20180927145006p:plain

こんにちは、BANKでAndroidエンジニアをしている手塚です。

Androidユーザのみなさんお待たせしました!昨日TRAVEL NowのAndroidアプリがリリースされました!
これから紅葉など季節の移ろいがたくさん感じられる季節になりますね!ぜひTRAVEL Nowを使って旅行へ行ってみてください!

さて本記事では、TRAVEL Now Androidアプリではどんな技術を採用して開発してきたのか一部ですが紹介していきたいと思います!

言語 & 開発環境

100%Kotlinで書かれています。もうJavaを書けない体になってしまいました。
coroutineなどはまだ使えていないため、適切な場面があれば積極的に使っていきたいです。

IDEはアプリをリリースするころにはさすがにStableになってるだろうと 判断して 高を括って、約3ヶ月前に開発を開始した時点からAndorid Studio 3.2を使って進めました。
結局リリースまでにStableにならなかったなと思いながらテストしていましたが、まさかリリース前日にStableになるとは...。

アーキテクチャ

これまで開発してきたアプリではMVVMで書くことが多かったのですが、ViewModelがややfatになってしまうのが悩みで今回はFluxを採用してみました。
現段階では各コンポーネントの責務がより細かく明確でデータフローも単方向になり見通しがよくなった一方、当然ですがクラス数コード数がやや増えたかなといった印象です。
一長一短といった感じで自分の中でもまだまだ試行錯誤中なので、もう少し知見が溜まったらまた発信したいと思います。

デザイン

Material Components

まだまだ対応していないComponentも多いですが、導入するとぐっとMaterial Design感が出せるのでオススメです!

f:id:YousukeTezuka:20180925180823g:plain

Constraint Layout & Motion Layout

本アプリでは随所に細かなアニメーションを付けています。よりリッチなアニメーションを実現するためにアルファ版ではありますがConstraint Layout 2.0を採用しMotion Layoutを活用しています。

これまでデザイナーから「こういう動きってできますか?」と提案されてもなかなか実現できずに心苦しい場面がしばしばありました。
Motion Layoutによってそれに応えられるようになり、触っていて気持ちいい動きをつけられるようになったように思います。

f:id:YousukeTezuka:20180925181905g:plain

さいごに

一部ではありますがTRAVEL Now Androidアプリで採用した技術を紹介させていただきました!

これからも自動テストやCIの整備、Slicesの対応などまだまだやらなければいけないこと、やっていきたいことが山積みです。
BANKではそんな課題をひとつひとつ一緒に超えていける仲間を募集しています。
少しでも興味を持っていただけましたら、ぜひお気軽にオフィスに遊びに来てみてください!

bank.co.jp

大量のPush通知をCloud Functions経由で送信する

実験の様子

こんにちは。ふぁぼ通知が好きなうなすけです。

モバイルアプリでは、Push通知によってユーザーに情報を伝えたり、行動を促したりすることが日常的に行われています。

その通知を送る対象のユーザーが数十人程度の規模なら、愚直に一通一通送信すればいいでしょうが、一気に万単位のユーザーにPush通知を送信したい場合、愚直に送信すると完了までに数時間かかることも往々にしてあります。

CASHでの大量Push通知事例

pull req

以前、ある施策のために42万件のPush通知を送る必要がありました。CASHではFirebase Cloud Messaging(以下FCM)をPush通知の送信に使用しているのですが、このとき、通知1件ごとにAPIを叩いていく方式だと送信完了に1日かかってしまうという事態になりました。

FCM Topic Messaging

そのような場合に使用できるのが、Topicを用いた通知の送信です。Topicを使用すると、そのTopicを購読している端末に一度にPush通知を送ることができます。実際、42万件のPush通知は最終的にTopicによって送信しました。

Topicの利点と欠点

Topicを使用する場合、あらかじめユーザー(というよりはデバイストークン)をTopicに対して登録させなければなりません。Firebaseのドキュメントに記載されている「荒天警報」のような、事前に通知の対象がわかっている場合は使い勝手が良いです。 しかし弊社の場合は、急に「こういうユーザーに対してこういう通知を送ってみたい!」という 実験 が提案され、対象となる大量のユーザーIDが渡される、ということがままあります。そのような場合、Topicを新規に作成しなければならず、それに時間がかかること、Topic自体も作成から使用可能までに最大で1日の遅れが生じることから、「即・大量に!」というユースケースには使い勝手があまりよくありませんでした。

既存の実装で使えるものはないか?

まずは、インターネット上に大量にPush通知を送信するためのプロダクトがないかどうか調べることにしました。すると、Mercariさんでの事例がOSSとして公開されていることを見つけました。

tech.mercari.com

github.com

「これじゃん!」と思い導入しようと、まずソースコードを読んでみました。すると、GaurunはiOSはAPNs、AndroidGoogle Cloud MessagingもしくはFirebase Cloud Messagingを使用するような実装になっていました。 CASHでは、iOSAndroidもFCMを使用してPush通知を送信しています。Gaurunを、両OSでFCMを使用するように変更することは不可能ではありませんでしたが、既存のAPIを変更せずに、ということが結構難しそうだった1ので、何か別のやりかたを探すことにしました。

FaaSを用いたPush通知

そこで、Faasを使うことを思い付きました。もともとPush通知というタスクは、複雑な処理をしない小さなものであり、単発のタスクなので、FaaSに乗せるにはもってこいの題材でしょう。

また、ちょうどいいタイミングでGoogle Cloud FunctionsがNode.js 8 Runtimeをサポートし始めたので、async/awaitも使えるしやってみようということになりました。2

The Node.js 8 Runtime  |  Cloud Functions Documentation  |  Google Cloud

Promiseを用いた並列実行

Cloud Functionsを使うことにしたところで、Push通知1件に対して1 function callでは、以前の問題を解決できているとは言えません。

なので、対象となるデバイストークンを一気に投げて、その大量のトークンを分割したものを自分自身に送信する、という再帰的な処理を行なうことで大量のデバイスに対してのPush通知システムを作成しました。

github.com

結果

幸か不幸か、数万規模の通知を送ることが直近は無さそうした。なので社内の有志を募り、その人の端末に向けて大量の通知を送ってみることにしました。下記のスクリーンショットはそのときの様子です。

このときは14端末に向けてそれぞれに500通、合計7000件のPush通知を送信しました。送信完了までにかかった時間は約1分間でした。

実験の様子

これが高速なのかそうでないのかは、他の通知基盤がどのようなスペックかわからないので何も言えません。しかし手動で実行したrequest自体は1回で、そのresponseは一瞬で返ってきていることを考えると、これまでの手間が大幅に削減されたことは明白です。

問題点

呼び出し上限に達してしまう

そりゃそうだろ、という感じではあるのですが、自身を再帰的に呼び出すので一瞬にして実行数が跳ね上がり、quota limitに引っ掛かります。

Quotas  |  Cloud Functions Documentation  |  Google Cloud

一応、Function invocations per second は申請によって上限を緩和させることができます。しかしなるべくなら初期上限のままで運用できるような仕組みにしたいです。案としては再帰実行前にジッタを挿入して呼び出しタイミングをずらすというものがあります。

他には、API limitation の存在しないFaaSを使用するという手もあります。いや、そんなものがあるのか?となりましたが、Knativeというものがありますね。これは確かに無制限に呼び出せますが……

その辺りの話を今度弊社が開催するイベントで話す予定なので、皆さん是非参加してください。

bank.connpass.com

Push通知の全てのパラメータに対応していない

ひとまずCASHで使用するパラメータに絞って実装したために、必要最小限のパラメータにしか対応していません。

まとめ

今回、FaaSによるPush通知基盤という、ほぼ実験的な手法を試してみました。結果、これまでの作業時間を大幅に短縮することに成功しましたが、API呼び出し回数制限などの新な課題も発見しました。

BANKでは、このような実験的なアプローチを、ビジネスモデルだけでなく、技術的な部分にも実践していく企業です。興味があれば、是非お声掛けください!

bank.co.jp


  1. 独自Forkを自社で運用することもできますが、せっかくなら上流に還元したいし、メンテはしたくないので……

  2. 結局 async/awaitは使いませんでしたが。