2歩戻ったら2.5歩進みたい

関東で働くweb developerのブログ

ジェームズ・クリアー式 複利で伸びる1つの習慣 を読んだ

感想

ここ1年で読んだ本の中で1番良かったかも。最近新しいことを始めようとしては続かなくて、いつのまにかやめてしまうことが多かったので悩みに突き刺さったのもあるけど。

習慣が続かないことに気づいた時、「あぁ、俺こんなことも続けられないってダメだなぁ...」みたいに考えてしまうんだけど、この本は「習慣の失敗はあなたの意志力の弱さじゃなくて環境が整ってないからだよ!」って1冊通してずっと言っている。 この本では習慣が定着する時のサイクルをきっかけ、欲求、反応、報酬の4ステップに分けている。それぞれのステップをどういう風に改善すれば習慣を定着させられるかがめちゃくちゃ具体的に書いてるので、読みながら過去の経験と照らし合わせて「あの時机に向かい続けられたのはこういう要因があったからなのか〜」と納得したり、これからやりたいと思っていることをどういう風に計画すれば上手く習慣化できるかワクワクしながら読んでしまった。啓発書よりもマニュアルと読んだ方が正しいぐらいのレベルなので習慣化に失敗した経験のある人類は全員これを読んで欲しい。それぐらいオススメ。

本筋からやや離れはするんだけど、なんか新しいことを始めようとしてはやめてきたマンとしては、付録にあった「新しいことを始めるチョイスがいつもキラキラしているのは予測するための土台となる経験がないから」という内容の文章が忘れられない。今まで毎回行き詰まると新しいことを始めようとする(そしてすぐやめてしまう)のかが分からなくて自分にモヤモヤしてたんだけどその理由が完璧に説明されていて舌を巻いてしまった。他にもたくさん「なるほどっすなぁ〜〜〜〜〜〜〜〜!」って言いたくなるような、自分が今までやってきた習慣、あるいはやれなかった習慣について説明が書いてあるのでぜひ読んでみてほしい。良書でした。

以下要約をどうぞ。


1章

  • 達成したい目的をベースに週間を作るのは間違っている
    • なぜなら目的の達成が境界線になって成功と失敗という安易な二元論で行動の結論を出してしまうから
      • 達成以外はモチベーションにならないし、長期的な習慣として根付くのを阻害している
  • 成果はあくまで行動を積み重ねた結果振り返って観測できる事象なので、成果ベースの評価は習慣化を妨げる
  • ほしい結果が得られるまでには時間がかかる(プラトー)ことは驚くほど認識されていない。

    学習や作業の進歩が一時的に停滞する状態。練習曲線の横ばいとして現れる。心的飽和や疲労などが原因で起こる。

    • 習慣化に失敗する人の多くがこの潜伏期間を耐えきれずに投げ出してしまう
  • 習慣化は成果・行動・アイデンティティという3要素で説明できる
    • アイデンティティとは、「自分は〇〇な人間である」という認識。
    • アイデンティティが確立できていると行動を起こすまでに必要なエネルギーが少なくなるので、習慣化のためにはここをまず押さえるのが大事
      • e.g. 「今禁煙してるんですよ〜」って言ってる人はアイデンティティが喫煙者なので禁煙することに対して凄くエネルギーを使っている。
        • 極論「タバコ吸わないんですよ」って言ってる人は断るのが当たり前になってる
    • アイデンティティの形成のためには行動を積み重ねることが大事
      • 行動によって自分のアイデンティティが強化される
        • 投票みたいなもので、なりたい姿に近づくために行動を積み重ねることが大事
    • ではどうやったら行動を起こしてアイデンティティの形成を素早く行うことが出来るだろうか?
  • 習慣化のサイクルには4つのステップが存在し、フィードバックのループとして常に回り続けている。
    1. きっかけ
    2. 欲求
    3. 行動(反応)
    4. 報酬
  • これらをハックして習慣化を成功させるにはそれぞれ以下のようなアプローチが有効
    1. はっきりさせる
    2. 魅力的にする
    3. 簡単にする
    4. 満足できるものにする

2章 はっきりさせる Make it obvious

  • 行動変化のプロセスは自覚から始まる
    • 一日の中で自分の行動を記録することで自覚できる
      • 目が覚める、携帯を開く、布団から出る、携帯を見ながらトイレに入る...
      • 大事なのは記録する時点では善悪の判断をせずにただ観察すること
  • 「私はいつどこで〇〇をする」という宣言をさせると実行できる確率が上がる(実行意図)
    • 「本をもっと読む」「運動習慣を作る」というざっくりした計画は役に立たない
    • 「寝る前の10分間、ベッドで本を読む」「夕食後、公園の周りをランニングする」という具体性が行動の確率を上げる
  • 「次に何をするかはやり終えたことに基づいていることが多い」

    ディドロ効果とは 意味/解説 - シマウマ用語集

    • これを利用して、既存の習慣に紐付けて新しい習慣を作ることが出来る
    • コーヒーを飲んだら(既存の習慣)瞑想する(新しい習慣)
    • きっかけははっきりしていればしているほど良い。
    • 昼休みの終わりに...よりも昼ごはんの皿を洗い終わったら...のほうがはっきりしている
  • 引き金を引くきっかけが無い環境は挫折を生みやすい
    • 自分で行動していると思いこんでいるけれど実は環境によって行動が引き起こされているだけなのをハックする
  • 習慣を引き起こすきっかけは背景ではなく背景との関係性によって変わる
    • e.g. ベッドでテレビを見ていると「ベッドはテレビを見る場所」というコンテキストが出来上がり、すぐに眠れなくなる
    • 新しい場所だと新しい習慣を作りやすいのはこの理屈(既存のコンテキストに引っ張られない)
  • 自制心が習慣を作るのではなく、環境が習慣を作る

3章 魅力的にする

  • 動物の欲求はドーパミンの量である程度定量的に測定できる
    • ドーパミンが大きく放出されるタイミングは、報酬が得られた時と報酬が得られると予測した時
    • 最初の報酬が与えられた時点でドーパミンが放出され、次回以降はきっかけが与えられて欲求が発生する直前で大きくドーパミンが放出される。
      • また、きっかけによって欲求が発生し、行動したのにも関わらず報酬が得られないときにはドーパミンの量が大幅に低下する。
      • しかし、報酬が期待したタイミングで得られない状態の後に報酬が与えられた場合はそれまで以上に大量のドーパミンが分泌される。
    • 行動を起こすのは報酬の予測。まとめると習慣自体を魅力的にするかがいかに大事かという話。
  • 行動を引き起こす確率を上げる、魅力的にするための方法の一つとして魅力的な報酬と抱き合わせるという手法がある
    • 要は宿題をやったらゲームをやっていい的な動機付け
  • 人間は近くの人と同じ様になりたい生き物なので、身につけたい習慣を当たり前に持っているグループに属すると身につきやすい。
    • かつ既に他の面で共通の要素を持っていると連帯感が生まれるのでよい
      • 運動をするオタクのグループに混ざる、みたいな
      • 同じオタクだから盛り上がれる上に彼らは運動してるので俺も頑張る!ってなりやすい
  • 悪い習慣はその先にある欲求に着目することで行動する確率を減らせる。
    • 承認されたいとか体に悪いけど美味しいものを食べたいとか
    • 行動した先に何があるか想像できるとブレーキになる

4章 易しくする(Make it easy)

  • どれだけ低コストで行動できるようになるかが習慣化の鍵
    • 習慣化のために必要なのは時間ではなく回数
    • 筋トレ1時間を週1回やるよりも10分を6回やるほうが習慣が付きやすい
      • 週1回の筋トレはすごくやる気が必要だけど10分を6回こなすとある程度脳死で出来る
        • 脳死で出来るようになったらこっちのもの
  • 低コストで行動するためには環境が大事。やる気ではない。
    • スマホを触ってしまうのならしばらく遠くに置いてみる。
      • 実際置いてみたらはかどってワロタ
    • できるだけ始めるのが簡単になるようにしよう。
      • 必要なものが出来るだけすぐ手に取れるように配置しよう。
  • 2分間ルール:習慣の行動は2分間で絶対終わるような長さで始める
    • 習慣は質を上げる前に定着させるのが大事
    • 始めたては2分経ったら確実にやめる。
      • 上手くいったからといって長時間続けると、次回行動する時の期待値が上がりすぎるので
  • テクノロジーで習慣を自動化させたり仕組み化出来るのでどんどん使うとよい

5章 満足いくものにする

  • 行動が習慣として定着しやすいのは行動によって与えられる報酬がより満足出来るものだった時
    • この章ではどうやったら再び行動を起こす確率を上げられるかを考える。
  • ただし難しいのは好ましいとされる習慣によって得られる報酬の多くが遅延報酬だということ
    • 実行した直後に報酬が得られにくいので習慣になりにくい
    • ので、即時報酬を行動の直後に供給するハックが有効
  • やったことや実績が明確に分かる仕組みが即時報酬になる
    • 例えば:
      • 〇〇やったつもり貯金(我慢したことが金額として分かる)
      • 行動した日はカレンダーにバツを付ける
      • 行動した回数だけ瓶にクリップを入れる
    • こういった仕組みは習慣トラッカーと呼ぶ
      • レコーディングダイエットもこの一種
        • トラッカーによって記録することでバイアスを排除できる。
        • 自分の行動に対しては得てして甘く見てしまいがちなので...
      • ただしトラッカーを付けるのが面倒になって習慣の定着を阻んでしまったら本末転倒なので「毎日付ける必要はないこと」「なるべく自動化すること」を意識する
      • また、トラッカーが示す数字はあくまでKPIの一つでしかないのでそれ自体にとらわれないように注意する必要がある。
        • もともと達成したいことを見失わないように。
  • 習慣が途切れそうになったら
    • 1日サボるのは許容する。2日連続のサボりは習慣の消滅を定着させてしまうので気合でやろう。
  • 誰かが自分を見ている、という認識は習慣の定着に大きく貢献する

6章 改善するだけではなく、本物になるには

  • とは言っても改善を続けるだけではなくて抜きん出るには習慣だけでは難しい
    • 他の人よりも有利かつ好きな分野を探して頑張るのが一番早い
    • 平たく言ってしまうと、本当に抜きん出るには本人の特性(遺伝子など)によるところが大きいので...
    • クリフストレングステストのような診断系テストはこういった資質を測るのに適している
  • ではどういって自分に向いた領域を探すのがよいか?
    • 周りは苦に思うのに自分は楽しく出来ること
    • 周りの人より特異なこと
  • モチベーションを失わないためにはコンフォートゾーンをちょっとだけはみ出る事が大事
    • 同じルーチンの繰り返しで退屈になっても、難しすぎてパニックになってしまってもいけない
    • 出来るか出来ないかギリギリの領域で達成することを続けようとするのが大事
  • モチベーションが無い時、退屈な習慣にも耐えられるかがプロとアマの違い
  • 習慣が身についた後の落とし穴
    • 些細なミスに気を配らなくなる
      • 習慣 + 計画的な練習 = 熟練
    • 自分は「〇〇(肩書)である」というアイデンティティを確立した後それに固執する
      • 肩書に固執するあまり、肩書を失うような失敗を恐れることで成長が止まる
      • 肩書ではなくて「粘り強く情報を集めながら判断できる」という性質にウェイトを置くほうがよい

付録

  • 満足 = 結果 - 欲望
    • 何回も繰り返す行動がマンネリ化するのは、あり得る結果のパターンが予測出来てしまうから
    • 新しいことを始めるチョイスがいつもキラキラしているのは予測するための土台となる経験がないから

まとめ

  • 習慣が定着しない時、原因は意志の弱さではなく環境にある
  • 大きな変化を起こすのではなく、ほんの小さな1つの行動を続けることを重視する
  • 習慣とは改善の持続的なプロセスである。改善をやめないことにこそ習慣の価値がある。

新しいLinuxの教科書を読んだ

読みました。EC2とかDockerでなんとなくLinux触ってるのが気持ち悪かったので、何が分からないのかわかるようになるために読んでみた。この本が対象とするのはLinuxを触ったこともないような、あるいはこれから初めて触るような人に向けた内容だったので、前半は割と知ってる内容が多かった(ファイル操作とかviとか)。後半はパーミッションやプロセス、標準入出力やシェルスクリプトなど、自分が何となく知ってるけどあんまり意識して触ってこなかったような概念についての説明だった。

全体通して非常に文章が分かりやすかったので割とスラスラ読めた。ただまぁsedとかawkとかのテキスト処理やシェルスクリプトは正直苦手意識がある。多分サーバー構築の自動化なんかでシュッと書けると便利なんだろうなーぐらいの感覚なので今ここにコミットしなくてもいいかなーと言う感じ。

とりあえずこの本を読んで「Linuxが何か知っている」ぐらいのレベルにはなったのではないかなーと思う。これで例えばwebアプリケーションの運用がやれるかというとそんなことはないので流石にもうちょっと詳しい本を読むなどしたい。

雑に調べたらLinuxの認定試験のLinucのレベル1が割とちょうど良さそうなのでやってみてもいいかも?と思っている。

linuc.org

章ごとの感想

  • Chapter 01 Linuxを使ってみよう
  • Chapter 02 シェルって何だろう?
    • カーネルとシェルの関係について、など。理解がふわふわしてたので良かった。
  • Chapter 03 シェルの便利な機能
  • Chapter 04 ファイルとディレクト
  • Chapter 05 ファイル操作の基本
  • Chapter 06 探す、調べる
  • Chapter 07 テキストエディタ
  • Chapter 08 bashの設定
    • .bashrcと.bash_profileと/etc/profileの関係についてなど
  • Chapter 09 ファイルパーミッション、スーパーユーザ
  • Chapter 10 プロセスとジョブ
  • Chapter 11 標準入出力とパイプライン
    • ><2>&1
  • Chapter 12 テキスト処理
    • sort, uniq, wc, cutなど
  • Chapter 13 正規表現
  • Chapter 14 高度なテキスト処理
    • sedawk。ムズすぎワロタ
  • Chapter 15 シェルスクリプトを書こう
    • #!/bin/bashがおまじないでなくなった
  • Chapter 16 シェルスクリプトの基礎知識
  • Chapter 17 シェルスクリプトを活用しよう
    • この辺を読んで雑なテキスト処理とかターミナルから実行する運用系のコマンドはシェルスクリプトにして見たりすることが出来た
  • Chapter 18 アーカイブと圧縮
    • tarってアーカイブだけで圧縮じゃないんですね。知らなかった。
  • Chapter 19 バージョン管理システム
  • Chapter 20 ソフトウェアパッケージ

2021年 ふりかえり

月別ざっくり振り返り

やったこと
1月 個人開発アプリのリリースに向けて頑張る
2月 個人開発アプリをリリースする
3月 個人開発アプリの運用・改善する①
4月 個人開発アプリの運用・改善する②
5月 個人開発で燃え尽きる①
6月 ゲームしたり読書したり引越し準備したり(個人開発で燃え尽きる②)
7月 ゲームしたり読書したり引越し準備(個人開発で燃え尽きる③)
8月 引っ越し
9月 家を整える
10月 スマブラと読書
11月 転職活動
12月 転職活動

個人開発

今年の前半は個人開発に注力していました。2020年の9月ぐらいから取り組んでいたので足掛け半年ぐらい開発をしていたことになります。

zenn.dev

リリースまでは朝早起きして1時間コードを書き、仕事を定時で終わらせたらご飯を食べて寝るまでコードを書くような生活を続けていました。この頃はモチベーションに満ち満ちており、個人開発でアプリを出して小銭を稼ぐぐらいまでは行ってやるぞ、と思っていました。

無事2月にリリースにこぎつけたのですが、残念なことにリリース後のトラフィックはほぼ全くの無風でした。もちろんTwitterで潜在ユーザーに直接宣伝してみたり、既存のユーザーにヒアリングしてみたり、改善はやったつもりでしたがそれ以上にモチベーションが保ちませんでした。

今振り返ってみるとニーズの捉え方が甘く、結局はユーザー(自分含め)が本当に欲しい物をわかっていなかったことが成功できなかった原因だったと思います。まぁそれはそれとして、自分が全力で出したアウトプットのクオリティの低さに耐えられなかったのもあり、燃え尽き症候群のようになってしまいました。結局そのままアプリは放置しています。どこかのタイミングでモチベーションが戻ってきたらまたやってもいいのですが、しばらくは別のことに時間を使おうかなと思っています。

本とかゲームとか

5~6月は余暇の時間を読書やゲームに向けました。

モンハン。PSP以来だったのでついていけるか不安だったけど、とりあえずソロで全部回れはした。アクションがめっちゃ進化してて楽しかったです。

FE風花雪月。リシテアちゃん可愛いよ。

発売当初にリトルマックでVIPに入って満足してたんだけど、友達が本格的にやり始めたので巻き込まれる形でまたやり始めた。オンライン対戦のレベルが上がりすぎてて怖い。来年はちゃんとVIPに入れるようになりたい...。

展示を見に行く度に歴史を知らないのは勿体ないような気がしていたので読みました。印象派以降の流れが分かったので色んな画家に対して親近感を持てるようになった。嬉しい。

あとこの辺。もうちょっと読んだ気もするけど...。

canisterism.hatenablog.com

canisterism.hatenablog.com

引っ越し

今年の後半に入ってからは引っ越しが大きなトピックでした。今の彼女と同棲することにしたので大田区から川崎に引っ越しました。23区に比べると家賃がバカみたいに安くなりました。

引っ越しを経験するのは人生で3回目ですが何回やっても大変ですね。特に今回は初めての同棲だったので、家具家電をほぼ全部新調するために2人で検討するのが大変でした。必要な家具家電を全部洗い出して暫定の予算を書き込んだ上で必要なタイミングを考えながらいつ買うのかを決めるためにスプレッドシートに作ったり、引っ越しのタスク整理のためにNotionで看板を作ったりしました。ほぼ仕事でしたが普段プログラマーとして働いているのでプライベートでPMっぽい仕草をするのは楽しくもあったので良かったです。

もう語り尽くされてますが、ドラム式洗濯機とロボット掃除機を入れたのは今でも正解だったと思ってます。この辺の話をどこかで書きたい。

f:id:canisterism:20211228153544j:plain
PanasonicのNA-VX700BL

この動画がすごく参考になりました。

【コスパ最強】洗濯乾燥機をスペックで選ぶのは危険!下位機種で良い理由!おすすめが1つだけありました - YouTube

実際に引っ越したのは8月でしたが、9月〜10月まで家具選びを頑張っていた記憶があります。ソファとか棚とかテレビとか。また、自室の環境も引っ越しに伴っていい感じにしたいと思っていたので思い切って電動昇降式机を導入しました。好きな時に立ったり座ったりできるのは最高。

お仕事

仕事に関して、今年はチームで上手くバリューが出せなくてずーっと悩んでいたような気がします。親しくしていたメンバーの方がチームから抜けたり、チームにおける自分の存在意義が上手く見いだせなかったりしてモチベーションがうまく継続できませんでした。

また、Flutterという技術を自分のキャリアにおいてどういう位置づけにするのか?という問いをずっと考えていました。

現職ではFlutterを使ってモバイルアプリエンジニアとして働いていますが、このままモバイルアプリエンジニアとしてキャリアを積んでいくのか?という疑問が今年の中盤からずっと頭の中で渦巻いていました。

Flutter自体は素晴らしいフレームワークですが、2年間触った感想としてネイティブのレイヤーに近くなれば近くなるほどFlutterによるデメリットが浮き彫りになっていくように思います。単純なJSONのやり取りだけであれば良いですが、Webviewやテキストフォーム、カメラなどの機能を使うことを考えるとつらみが多くなってくるのではないでしょうか。(Google謹製のアプリのうちいくつかはFlutterベースに書き換えられているのを見ると単純に僕のスキル不足によるものである可能性も否めないですが)

つまり(?)、今時点の自分のFlutterに対する認識はこんな感じです。

  • クロスプラットフォーム開発のためのフレームワークとしては素晴らしい
  • ただし、ネイティブのAPIに近いことをやろうとすると結局ネイティブのコードを書く必要がある
  • 自分はネイティブのコードが書けない(し、あんまり書くつもりがない)
  • ネイティブのコードが書けないFlutterエンジニアがマッチするのは「(複雑なことはせずに)とりあえず両プラットフォームでモバイルアプリが欲しいプロジェクト」である

なので、Flutterをキャリアの中心に置き続けるには、ネイティブのコードが書けるようになるか、ネイティブのコードが書けなくても問題ないようなアプリを書き続けるかのどちらかになると考えました。自分はモバイルアプリを作り続けたいわけではなく、一人のwebエンジニアとしてプロダクトを出すための力をつけていきたいと思っています。こういった考えから、今のチームでFlutterを書き続けるのはやめて、別の会社でwebアプリケーションを作るエンジニアとして働く決断をしました(書いてみると決断が一足飛びな気もしますが、まぁ書きにくいことも色々あるのでお察しください)。

注:Flutterを書いてる/好きな方やFlutter自体を貶したい訳ではないです。個人のキャリアとしての観点なので悪しからず。

というわけでなし崩し的に転職エントリになってしまいましたが、来年の1月から別の会社でお世話になります。現職には3年とちょっと在籍したので、そろそろ別の会社を見てみたかったのもあります。

次の会社ではおそらくまずはRailsでバックエンド+インフラもやることになる(というかやりたい)と思っています。今までどちらかというとUI寄りの開発が多かったのですが、ゼロイチでサービスを作れるようになりたいので新しい挑戦をしたいと思います。

総括

なんだかまとまりのないエントリになってしまいましたが今年の振り返りはこんな感じです。全体的に集中力が下がってしまって、まとまったインプット/アウトプットが出なくて悔しい感じになってしまいました。いろんな失敗を経験したのでもうちょっとちゃんと振り返りをして次に繋げたい...。

キャリアに関して、正直今でも明確な回答が出てる訳ではないんですが、自分なりに考え抜いた結果出た答えなので、来年は修行だと思ってひたすらコードを書いていきたいと思います。来年の目標もサクッと書けるといいなー。それでは良いお年を!

f:id:canisterism:20211228174144j:plain
武蔵小山「てりや」の のどぐろ。なんとなくめでたそうなので。

DNSがよくわかる教科書 を読んだ

動機

最近の中長期目標が「webエンジニアとしての基礎を作る」なので今まで避けてきたアプリケーション以外の技術に触れてみる一環で読んだ。直前に読んだ本がマスタリングTCP/IPだったのでネットワークつながりで続けて読むことにしたのだけれど結果的に良い判断だったと思う。

canisterism.hatenablog.com

感想

実務でもDNSのレコードを直接操作したり、名前解決のプロセスを辿ってトラブルシュートしたりすることはなかなか機会に恵まれなくてどうにも苦手意識があったのだけれど、少なくとも概観を掴めたのでDNS何もわからん!という状態ではなくなった。

基礎編ではDNSの構成要素や名前解決の仕組み、委任などの説明を交えながら具体的な動作の説明に入っていく。教科書と銘打ってる通りゼロベースで説明してくれてるので自分のように理解があやふやな人にも優しくて助かった。

8章のdig他DNS周辺のCLIコマンドを実際に叩きながらフルリゾルバとして名前解決をしてみるセクションがすごく理解を助けてくれた。digコマンド自体は知ってるけどあんまり叩く機会がないので...(一方で実際に手を動かすパートはここぐらいしか無いので理解を深めるためには別の書籍をあたったほうが良さそう)

読んでてピンとこなかったことは検索しながら読み進めたのだけれど大体jprs.jpやnic.ad.jpのサイトに詳しい解説が載っている。

jprs.jp

www.nic.ad.jp

DNS周辺、いざって時に触れたり分かってたりすると役に立つタイプのものだと思うので、これを読んで苦手意識を消すレベルまで引き上げられて良かった。

マスタリングTCP/IP 入門編を読んだ

webエンジニアになりたての時に「エンジニアにおすすめ!」みたいな記事を読んで買ったんだけど当時何が書いてるのか全く分からなくて積んでしまっていた本。

3年ぐらい経った今、ぼんやりエンジニアとしての基礎が足りないと感じていたところにふと目についたので手にとって読んでみた。

感想

序盤の章ではインターネットの歴史からOSI参照モデルTCP/IPにおける簡単な通信の流れをさらって、以下の章では各層のプロトコルを順番に解説していた。

ネットワーク周りがずっと苦手で避けてきたので前提知識がほぼ無いような状態で挑んだ割に面白く読めたと思う。プロトコルを普段の生活に例えて説明してくれるところが読みやすかった。例えばTCPとIPにおいて目的のホストに辿り着くまでの経路解決が「駅員に目的地だけ伝えたら次はどこに行けばいいか分かる」という説明をされていて分かりやすかった。

自分のようなネットワーク何もわからんマンには序盤の章でざっくりした全体図を示してくれるのはすごく助かった。あと全然関係ないんだけどTCP/IPの標準化までのプロセスは実際に役に立つ仕様を作成するための仕組みという感じがして面白かった。

3章以降は各層のプロトコルに関してもう少し突っ込んだ説明がなされていて、こっちに関してはふんわりとわかった気がする...というのも、読んだ時は理解してるんだけど応用するための知識として引き出すには足りないのでもうちょっと実務で使うなり深めるための学習が要るような気がする。とはいえ一回分かってればあとは引き出すことは出来そうなのでそういう意味で読めてよかった。

本筋とは別の話で面白かったこととして、全体通してプロトコルが発展する時の思想としてひたすら「実践的かどうか」を問いながら進んでいるのが面白いなと思った。プロトコルを先に策定して広めるのではなくて、提案をベースにして実験と議論を行い、主要メンバーが承認すると実装され始め、その上で広く運用されると晴れてStandardになる...というプロセスを踏むので標準として策定されたときには十分に洗練された状態になっている。ただインターネットの偉人たちによってこれだけ標準化までのプロセスを洗練させても、長い時間が経つと想定外の事が起こってるのを見ると事前に起こる問題を予想し切るのは無理なんだなぁ...と思った。

話が逸れたけど、短期的に見ると効果が薄いけど長い目で見たら読んだほうが良いタイプの本をちゃんと読み切れたのはすごく良かった。このベースがあると今後の実務レベルの知識が入ってきやすくなると良いな〜〜〜〜〜。終わり。

プッシュ通知を作る前に知りたかった事のメモ(Firebase Cloud Messaging + AWS SQS & Lambdaでプッシュ通知を作った)

この記事について

プッシュ通知の基盤を作る機会があったのですが、前提となる知識や知見が足りなくて調査や検証に時間がかかったので、設計する前に知りたかったことや作り始めてから気づいたことなどをざざっとまとめました。 これからプッシュ通知を作る人に向けて参考になれば幸いです。

前提

具体的な説明に入る前に、前提となる要素と今回の開発で求められた要件を並べます。

対象のサービスやアーキテクチャについて

webアプリとモバイルアプリが共存しているサービスで会員数は100万人程度、webアプリケーションはRails製です。

このRailsのアプリケーションはwebアプリとして振る舞う以外にもモバイルアプリのAPIサーバーとしても振る舞っています(厳密にはサーバーが別れてますがコードベースは同じです)

モバイルアプリはiOS, AndroidともにFlutterで書かれていて、Railsのアプリケーションをリソース(API)サーバーとして見ています。

配信の要件

配信の要件はだいたい以下のような感じで、いわゆるモバイルアプリのプッシュ通知が出来ることです。

  • アプリの全ユーザーに対してアドホックにプッシュ通知が出来る
  • アプリの任意のユーザーに対してアドホックにプッシュ通知が出来る
  • 特定の条件に合致したユーザーに対してバッチでプッシュ通知が出来る
    • クーポンが期限切れになる前にプッシュ通知でリマインドする、みたいなやつです
  • アプリケーションの特定のイベントをトリガーとしてプッシュ通知が出来る

分析の要件

加えて分析やマーケティングを担当するチームが居るので最低限以下のような要件が求められます。

  • 送信したプッシュ通知の一覧が確認できる
    • 送信した通知に関して開封された数が確認できる
  • 任意のユーザーに送信された通知の一覧が確認できる
  • 任意のユーザーの開封後の行動をトラッキングできる

技術スタックについて

今回の開発では上記の機能要件に加えて、非機能要件として頭が痛くならない程度の予算で運用できることや配信サーバーの面倒を見ないでも運用できることを念頭に置きました。

既存のシステムとの整合性も考えた結果、今回はFirebase Cloud Messaging + AWS SQS & Lambdaで構成することにしました。

それぞれの技術について&選定の理由を説明します。

Firebase Cloud Messaging(FCM)

firebase.google.com

まずプッシュ通知を実現するためのサービスを選びます。アプリがFlutterでできている時点でほぼすべてのプッシュ通知サービスがSDKを用意していないという理由で選べませんでした。(こういう検索で出てくるサービスです↓)

www.google.com

ReproやKARTEはFlutterのSDKが用意されてました(すごい)が、現時点で料金面が折り合わないことやマーケティングツール以外からの起因で配信することを考えると初手でそこを選びに行けませんでした。(とりあえず自前で一通り作ってからマーケティングツールも入れる...みたいなのはできそうだと思ってるけどぶっちゃけあのへんのツール群をよく分かっていない)

という訳でFirebase Cloud Messagingが選ばれました。FlutterとFirebaseはすごく相性が良くてSDKが整備されているので正直モバイルアプリがFlutterな時点でFCM一択な感じはありました。 単純に他と比較しても料金が1円もかからないことやBigQueryとのデータ連携、ドキュメントの豊富さなどを鑑みても良いツールだと思います。

補足:FCMの配信の方式について

FCMの配信の方式は大きく分けると2つあります。

トークン指定配信

前提として、プッシュ通知を行うためにはプッシュ通知のトークンが必要になります(FCMだとregistraion tokenと言います)。これはプッシュ通知を許可した端末から払い出されるトークンで、これを指定してAPIを叩くとトークンを払い出した端末に対してプッシュ通知を送ることができます。

トークン指定配信(と勝手に呼んでるけどドキュメント上は特定のデバイスにメッセージを送信するという表現のみ)は直接配信先としてこのトークンの文字列を指定する配信です。

firebase.google.com

トピック配信

トークン指定配信は通知対象を直接指定するので分かりやすいですが、一回のAPIコールで指定できるのは500トークンまでです。仮に全ユーザーに対して配信するケースが存在した場合、(会員数にも寄りますが)配信に時間がかかったりサーバーから通知している場合はマシンリソースを逼迫させる可能性があります。

この弱みを解決してくれるのがトピック配信です。トピックというのはトークンを紐付けることの出来る対象で、トピックに対してメッセージを送ると事前に紐付けたトークンを持つ端末に対して通知を送ることが出来るというものです(トピックへの紐付けはクライアント/サーバーのどちらからでもできます)。

つまり、事前に配信したい対象が決まっているなら紐付けさえやっておけば、トピックに対する紐付け上限はない(はず)なので1リクエストで多くの端末に対して配信することができます。

ただし配信の遅延が最大で24時間(ドキュメント曰く)だそうなのでその辺りは理解して利用するのが良いと思います(恐らく紐付けたトークンの件数にも左右されると思いますが未確認です。手元で100個以下の紐付けならほぼ一瞬で配信されました)。

また、トピックを使う場合はトークンのライフサイクルと合わせた更新の設計が重要になります。例えば男性や女性と言ったセグメントでトピックを切った場合はトピックに紐付いたトークンを操作するのはトークン自体がrevokeされたり再生成されたりするタイミングだけですが、年齢のようなセグメントでトピックを生成した場合だとトークン自体のライフサイクルに加えて年一回定期的に変更するバッチを作る必要があります。

このため、私は初期フェーズでは全配信のためだけにトピックを使用することとし、配信の単位が固まってきたタイミングで再度トピックの設計を行うことにしました。

AWS SQS + Lambda

次にプッシュ通知のAPIを叩く君が必要になるのでそこの技術スタックを決める必要があります。

具体的にはアプリケーションの任意のイベントをトリガーにして対象のユーザーにプッシュ通知のAPIを叩いたり、アドホックなお知らせの配信をするために任意のタイミングで通知のAPIを叩いたりするコンポーネントです。

ざっくり言うとアプリケーションサーバーから直接FCMのAPIを叩く or 別の層を挟む のどっちにするのか、という議論です。現時点ではモバイルアプリの会員数はあまり多くない(~10万)のでマシンリソースのことは考えなくて良いのですが引き返しにくい判断(構成を変えたくなった時の難易度が高い)なので、サーバーを持たずにできるだけシステム間の結合が疎になるようにSQSを挟んでLambdaからAPIを叩くことにしました。

なお、Lambdaはnode.js + typescriptで作っているのでFCMのadmin SDKが使えます。(Railsから叩きたくなかった理由としてFCMのRubySDKが存在しなかったから、というのもあります)

システム全体図

という訳で改めて作ったシステム全体の図を示します。

f:id:canisterism:20211002010022p:plain

これを踏まえて、プッシュ通知が実際に行われるまでの大まかな流れは以下のような感じです。

トークン登録編

  1. アプリの起動時にトークンが払い出される
  2. アプリケーションサーバーにAPI経由でトークンがDBに登録される
  3. トークンを全体配信用のトピックに紐付けるためにSQSにキューイングする
  4. SQSからLambdaがキューを取得してFCMのトピック紐付けのAPIを叩く

全体配信編

  1. 配信用SQSに通知のデータをキューイングする
  2. SQSからLambdaがキューを取得してFCMのトピック配信のAPIを叩く
  3. ユーザーに通知が届く

ユーザー指定配信編

  1. DBから配信対象のユーザーを抽出し、ユーザーが持つトークンを取得する
  2. 配信用SQSに通知のデータをキューイングする
  3. SQSからLambdaがキューを取得してFCMのトークン配信のAPIを叩く
  4. ユーザーに通知が届く

設計上の注意点や気をつけたこと

上記の流れはそこまで複雑なことをやってないのですが、僕が設計する時にもっと早く知りたかったことや気をつけたことなどをざっと書いてきたいと思います。

アプリ側のトークン払い出し周辺の知識

先程も説明したように、プッシュ通知がなされるためには最初にユーザーの端末で通知トークンを払い出す必要があります。最初は「まぁダイアログ出して終わりやろ」と思ってたのですが意外に複雑でした。だいたいはFirebaseのドキュメントかFlutterならFlutterFireとfirebase_messaging | Flutter Packageに書いてありますのでよく読みましょう。(下に書いてるのはFlutterのAPI特有だったりするかもしれないので差っ引いて見てください)

  • 通知を許可するかユーザーに聞くダイアログが出るのはiOSのみ。Androidは何も出ずに許可されたのと同じステータスになる。
    • 1回でもダイアログを出すと2回目は出ない(ダイアログを出した後強制的にアプリを終了すれば2回目が出るかも?未検証です)。
      • そのため、「公式のダイアログを出す前に通知のメリットを説明する独自のダイアログを出す」みたいなことをしているアプリは多いです。仕様検討の際には気をつけたいですね。
  • 通知の許可ステータスはauthorized, denied, provisional, notDeterminedの4つ。
  • Flutterのfirebase_messagingで単純なプッシュ通知を行うだけなら両OSともgetToken()だけでよい(getAPNSToken()は使わないでOK)。
  • FCM via APNs Integration | FlutterFireに記載があるAPNのkeyはbundle IDに紐付いてないので環境別に分けなくてもよい(分けても良いと思いますが)

トークンのモデリングやライフサイクルにまつわる話

  • トークンは端末に対して払い出すので、アプリ上のユーザーと対応させたいなら工夫が必要
    • 例えばユーザーAがログインしている状態でトークン(aとする)を払い出した後にログアウトしてユーザーBでログインし直した時にaのトークンをどう扱うか?という話。
    • Twitterのように複数ユーザーの同時ログインを前提としているならそのまま通知して良いと思いますが、同時に1ユーザーのみログインを前提としている場合はログアウトしたらトークンを無効とする処理を入れるなどしないとユーザーAへの通知がユーザーBに行ってしまうので考慮する必要があります。ややこしいですね!
  • トークンはDB上で永続化するべし
    • 当たり前過ぎて書くか迷ったのですが作る前はこの辺も迷ったので書いておきます。
    • アプリ側でトークンが払い出されたらDBに永続化しましょう。でないとサーバー側のイベント起因で配信ができないので...
    • この時、ユーザーとトークンの関係は1:1ではなくて1:nであることに気をつけましょう。単純に複数台端末を持ってる可能性がありますからね。

開封率などデータ分析の話

この辺に書きました。 canisterism.hatenablog.com

トピックに纏わる話

  • 記事執筆時点でFCMのトピックは削除することが出来ません。一回作ったトピックを削除するためには紐付けたトークンを全部unsubscribeする必要があります。
  • トピックの一覧やトピックに紐付けたトークンを取得する方法がないため、管理したい場合は自前のDBで紐付けたトークンとトピックを管理する必要があります。

stackoverflow.com

アプリの通知のハンドリングの話

  • 後方互換性には気を使ったほうが良い
    • これはどっちかというとプッシュ通知と言うかアプリのバージョンアップで気をつけることですが、通知を受け取ることの出来るバージョンは平等に通知を受け取れることに留意して最初のバージョンをリリースするべきでしょう。つまり、v1.0でプッシュ通知機能をリリースした後、v1.1で新しい画面が追加された&その画面へ遷移するプッシュ通知も送られるようになった、みたいなことが起こるとv1.1で追加された画面への遷移を含む通知がv1.0にも届いてしまいます。ちゃんとフォールバック先を用意するなどの工夫が必要なので気をつけましょう。

まとめ

なんだかまとめきれたのか怪しいですが、僕が設計&実装する前に知りたかったことを詰め込んでみました。プッシュ通知したい時はだいたいどれぐらいのことを設計のスコープとして見込めばよいのかの肌感が伝わってれば嬉しいです。

他にもいろいろ考えた気がしますがちょっと思い出せないので思い出したら追記します。

参考リンク

Firebase Cloud Messaging | FlutterFire

FCMを使ったWEARプッシュ通知基盤リプレイス - ZOZO TECH BLOG

大規模プッシュ通知基盤大解剖 - @IT

Firebase Cloud Messagingで開封周りの分析に必要なデータを整理する

3行で

  • FCMでプッシュ通知の開封率を計算するにはAnalyticsの自動収集イベントが使える
  • 特定のユーザーに対して配信された通知の一覧を取得するにはinstance_idをキーとすれば良さそう
  • instance_idは端末に対してユニークではないので注意されたし

やりたいこと

Firebase Cloud Messagingで作ったプッシュ通知からBigQueryに出力されるデータで「誰にどの通知を送ったのか、どれが開封されたのか」を取って行動分析したい。

もう少し具体的に言うと、以下のようなことをしたい。

  • 一斉に送ったプッシュ通知の開封率を計算する
  • 特定のユーザーに送った通知の一覧を取得する

データの取り方を考える

まず開封率の事を考える。開封率は開封された通知の数 / 送った通知の総数で求められるのでそれぞれをどうやったら取れるか考える。

送信済みのプッシュ通知の情報

まず、FirebaseとBigQueryを連携するとFCMの送信データが自動でBigQueryに連携されるので、送信された全通知のデータはここから取れる。

firebase.google.com

開封した記録

次に「どのユーザーがどの通知を開封したか」というデータを取得する。

iOS, AndroidともにFirebase Analyticsが入ってれば自動でイベントが収集されるんだけど、この中にnotification-openというイベントがある。

これはプッシュ通知の開封を記録したイベントで、パラメータにanalytics_labelとユーザーIDが自動で渡される。

support.google.com

analytics_labelは通知の送信時に送信者側で任意の文字列を設定できるので、「20210924_キャッシュバックキャンペーン通知」みたいなのを設定しておけば開かれた通知の種類が判別できる。

analytics_labelをキーにすれば開封された通知の数 / 送った通知の総数が計算できるので、開封率の計算に必要なデータはこの時点で揃った。

特定のユーザーに対して配信された通知の一覧を取得する

開封率に関しては計算できたんだけど、「特定のユーザーに対して配信された通知の一覧」はもうちょっと手がかかる。

BigQueryに連携された送信済みデータにはユーザーIDが紐付いていないので特定のユーザーに対して送った通知の絞り込みが出来ない。

送信された通知とユーザーIDの紐付け

先程言ったように、開封された通知に関してはユーザーIDが渡されるので紐付けが出来る。ただし、「送信されたけど開封されなかった」通知の一覧が分からないのが問題。

FCMから連携されてBigQueryに格納されたレコードには送信したプッシュ通知のレコードが並んでいるものの、それぞれの通知に対してユーザーIDや通知トークンが入ってるわけではない(もちろんAPIにユーザーIDを送信する口もない)。

ここで役に立つのがinstance_idである。

instance_id

ドキュメントには以下のようにある。

instance_id: メッセージの送信先のアプリのインスタンス ID(利用可能な場合)

読んだ当初、端末に対して一意なIDかと思ったのだけれどドキュメントを漁るとどうやらそうではない。(↓はAndroidのドキュメント)

firebase.google.com

Firebase Instance ID provides a unique identifier for each app instance and a mechanism to authenticate and authorize actions (example: sending FCM messages).

Firebase Instance IDはアプリのインスタンスに対してユニーク、とある。また、

Instance ID is stable except when:

  • App deletes Instance ID
  • App is restored on a new device
  • User uninstalls/reinstall the app
  • User clears app data

とあるので要するに

×:同じ端末に対して払い出されるIDは毎回同じ ○:5回アプリをインストールし直したりすと異なる5つのIDが払い出される

ということである。

また、instance_idは"${instance_id}:${token_body}"という風にFCMのトークンの前半の文字列として使用されている。

つまり、ユーザーIDとinstance_idが何かしらのDB上で紐付いていれば、BigQueryのデータと組み合わせることでユーザーIDからinstance_idをキーにして特定のユーザーに対して送信された通知の一覧が取得できる。めでたしめでたし。

おわりに

FCMのドキュメントが微妙に散らばっていてこの辺の仕様を理解するのにやや時間を要してしまった。間違ってる記述があったら優しく教えて下さい...🙏

参考

BigQueryに連携されるFCMのデータ定義: firebase.google.com