ひらめの日常

日常のメモをつらつらと

『プロダクトマネージャーのしごと』を読んだ

積んでいた『プロダクトマネージャーのしごと』を読み終わったので、内容を簡単にまとめます

å‹•æ©Ÿ

エンジニアとしてプロジェクトをリードする中で、プロジェクトマネジメントのノウハウをもっと取り入れ、エンジニアの枠を超えた働き方を目指したいと考えている。

また、プロダクトマネージャーという役割について、「具体的にどのような業務を担当するのか」「どのような姿勢で取り組むべきか」といった点を理解することで、プロダクトマネージャーとの円滑なコミュニケーションに活かしたり、エンジニアとしてより効果的にサポートできるようになりたいと思い、この本を手に取った。

印象に残ったところ

第1章 プロダクトマネジメントの実践

プロダクトマネージャーという役割には常に曖昧さが伴い、あらゆることをこなす必要がある

  • 責任は大きいが権限は小さい。チームが締切を守れなければプロダクトマネージャーの責任である
  • チームとプロダクトの成功のために、終わらせる必要なることはなんでもするのがプロダクトマネージャーの責任である
  • プロダクトマネージャーはあらゆることの中心となる
  • 優れたプロダクトマネージャーとは、人やものを繋げる中心にいる人である

第2章 プロダクトマネジメントのCOREモデル

プロダクトマネージャーに必要なCOREモデルとは、次のものである

  • Communicate: ステークホルダーとコミュニケーションする
    • 心地よさより明確さが行動指針。心地悪さは明確さの欠如の兆候であることが多いので、自ら心地悪いコミュニケーションをとっていく必要がある
    • やっていることとその理由をチームが明確に理解しているか会話の中で確かめる
  • Organize: 持続的に成功するチームを組織化する
    • 自らを不要とせよが行動指針。組織化に優れたプロダクトマネージャーは自己継続的な仕組みを作る
    • 今何しているのか、そしてそれはなぜかという問いにチーム全員が答えられるような状態にする
  • Research: プロダクトのユーザーのニーズとゴールをリサーチする
    • ユーザーの現実に生きよが行動指針
    • 全てのプロダクトに対する意思決定は、ビジネスゴールとユーザーニーズに紐づいている必要がある
  • Execute: プロダクトチームがゴールに到達するための日々のタスクを実行する

第3章 好奇心をあらわにする

  • 好奇心を示すというシンプルな行動は、プロダクトマネージャーとしての仕事に大きなプラスの影響を及ぼす。他人に何かを求める前に関係性築いておく必要がある
  • 職場の同僚を訪ねて、仕事をもっと詳しく教えてもらえないか声をかけてみる
  • 同僚同士が学び合うように促し、お互いのスキルを学びたい人をペアにする

第4章 過剰コミュニケーションの技術

  • 失敗するなら、コミュニケーション過剰の方に倒しておくほうが良い。
  • 自分が当たり前だと思っていることも、他の人には当たり前でないことがある。当たり前のことを問うことを恐れないこと
  • 心地よい言い回しをしてまわりくどい言い方をしがちだが、曖昧にするのは良くない。これは責任回避であり、悪いやつだと思われずに結果を得ようとする受動的攻撃生の表れ
  • Disagree & Commit
    • 集団での合意形成の際には、参加者全員の「進めてよい」という積極的なコミットメントが必要だ、ということ
    • 沈黙は不合意として扱う

第5章 シニアステークホルダーと働く

上司がバカだった、という発言をすると、それは実質的にチームを壊す。シニアステークホルダーからのどんな要求であっても合理的でないとチームがみなすようになってしまうから。 プロダクトマネージャーの仕事はシニアステークホルダーからチームを守ることではなく、強力的なパートナーである。

第7章 「ベストプラクティス」のワーストなところ

ベストプラクティスを盲信すると、それに従わない人は全て敵に見えるようになってしまう。しかし、ベストプラクティスは様々なプロセス、人、運、タイミングといった様々な要素が絡んでいることを忘れてはならない - 業界トップ企業のケーススタディは、採用のためのプロパガンダであることが多いので、誇張を鵜呑みにしない - ケースバイケースの制約と限界が存在している - ベストプラクティスを実施する前に、組織特有の状況について学ぶための時間を常に取る

第8章 アジャイルについての素晴らしくも残念な真実

プロセスを内省して洗練する時間を作らなければ、形式上のセレモニーのせいで結果的にチームの士気を低下させることになりかねない。アジャイルプラクティスやセレモニーの背景にある本質や本来の目的をドキュメントに残すと良い。

第9章 ドキュメントは無限に時間を浪費する

最高なドキュメントは不完全である。不完全であることは、行動のきっかけとなる

  • 意図的に不完全なドキュメントを共有した時は、チームからの質問や貢献は前に進めるために不可欠
  • 他のメンバーの参加度合いやその質は完璧なドキュメントを共有した時より圧倒的に高い
  • 次のミーティングでは不完全なドキュメントを持っていくことを推奨する
  • 最初のドラフトは1ページで、作るのに1時間以上かけない

第12章 優先順位づけ:すべてのよりどころ

志は大きく、スタートは小さく!

  • どの意思決定もトレードオフ
    • その意思決定が良い結果になるかどうかはやってみないとわからない。小さく始めてフィードバックをもとに軌道修正するのが良い
    • 仮説をもとに物事を進める場合はその仮説を文章化してチームと議論する
  • 単独の機能ではなく、ユーザージャーニーやユーザータスクの全体を考える
    • 他のプロダクトマネージャーやチームとの調整が少なくて済みそうな機能を優先しがち
    • 複数チームの責任領域を跨ぐ昨日や改善こそ、一番インパクトが大きい
    • 小さくはじめる & 作業の依存関係の話ではなく、どのようなゴールを目指すか、というワクワクする話から始める

第13章 おうちでやってみよう:リモートワークの試練と困難

リモートワークにおいてすぐに実践できるtipsが多く書かれていたため、多めに内容をまとめる

  • 日々のコミュニケーションに対する期待があっていないことが、しばしチームの信頼関係の1番の障害になっている。コミュニケーションマニュアルを作成し、コミュニケーションの緊急度合いに対する期待値を揃えると良い
    • それぞれのチャネルの非同期メッセージにどれくらい素早く反応することを期待するか?
    • お互いに仕事をお願いするときに、明示すべき基準は?(例:どれくらいの期間お願いするのか、納期は、遅れることのリスクは)
    • 個人とチームの仕事時間は?仕事時間以外に送受信したメッセージの扱い方は?
    • まずはチームのメンバーからメッセージを受け取ったらどれくらい早く返信が来ることを期待するか、という質問から始めると良い
  • 非同期コミュニケーションでは、送信する前に時間をかける
    • 送信するのが一番簡単な非同期メッセージは、返信する際に多くの時間を要する
    • メッセージを受け取った人は、10秒以内にとって欲しいアクションがわかるか?
    • 期待する結果と納期が明記してあるか?
    • メンションされている場合、なぜメンションされているかが明確か?(ccに入れられている理由など)
    • フィードバックを求める場合は、どんな種類のフィードバックを求めているのか、そしてそのフィードバックを求める理由が明確になっているか?
    • フォローアップや確認の場合には、どんなタイプの反応やリアクションが欲しいかを明確にしているか?
  • 同期サンドイッチのススメ
    • ミーティングの1日前までには事前に読む資料を非同期で送る
    • タイムボックスを設定した同期ミーティングをファシリテーションし、判断を下してドキュメントを一緒に作成し、事前資料で説明された問題を解決する
    • 同期ミーティングから1日以内に、次のアクションを含むフォローアップを非同期で送る

第14章 プロダクトマネージャーのなかのマネージャー(プロダクトリーダーシップ編)

プロダクトを所有しようと意地になったり、機能の押し付け合いになったりすることが多い。次の文章を本からそのまま引用する

あなたの仕事はビジネスとユーザーのためのアウトカムを届けることで、プロダクトのなるべく多くを「所有」することではないことを忘れないでください。いちばん合理的な道に集中し、「あなたが持つか、私が持つか」という二元的な考え方の枠をなるべく早く越えようと努力してください。ビジネスゴールに対して最善を尽くすために、両チームがどのように協力できるのかを話し合うために、フォローアップの会話の機会を提案することを検討してください。

感想

プロダクトマネージャーは責任が大きいが権限は小さく、プロダクトを成功させるためにあらゆることをやる人物だというのは、ある意味では酷だな、と感じた。しかし、あらゆる人をつなげてプロダクトを成功させることに熱意を感じる人であればそこに強いやりがいを感じるのだろう。

自分としては、エンジニアの立場としてプロダクトマネージャーを支えるように関わっていきたいと感じた。エンジニアとしてもプロダクトマネージャーの行動の背景・意図を知ることでよりスムーズにコミュニケーションができ、より大きなインパクトを最終的に残せるようになると思う。場合によってはプロダクトマネージャーが足りていないところに手を貸すような動きもできるかもしれない。

明日から実践できることとして、次のようなことを意識していこう

  • コミュニケーションでは心地よさより明確さが重要。心地悪いコミュニケーションを率先して取っていき、明確さに欠ける部分を埋めていくようにしたい
  • 失敗するならコミュニケーション過剰になる方が良い。当たり前を問うことを恐れない
  • ベストプラクティスは様々な要因が重なって作られているので、盲信せずに自分の組織特有の状況を見定めたい
  • 最高なドキュメントは不完全。チームに対して不完全なドキュメントを作成し、それを皆で埋めていく作業を行う
  • チーム内で、非同期メッセージにどれくらい素早く反応することを期待するか聞いてみる
  • メッセージには取って欲しいアクション、期待する結果と納期を明確にする

次に読みたいもの

本の中で紹介されていた『Agile Retrospectives』を読んでみたい。振り返りを行い、チームとして学び続けることで改善し続けたい。今もレトロスペクティブは行っているが、KPTを回しているだけの状況になりがちなので、体系的に学ぶことでより効果的なレトロスペクティブを開催していきたい。

『小さな習慣』を読んだ

『小さな習慣』を読み終わったので、感想を簡単に書きます。

å‹•æ©Ÿ

習慣化したいことがいくつかあるのだが、それがどれも三日坊主に終わってしまったり、いつの間にかやらなくなっていることに気づいた。どうやったら努力を継続していけるかについてのヒントを得られると良いと思い、この本を読むことにした

印象に残ったところ

  • 小さな習慣の基本は、こんなに簡単でも良いのかと自分でも疑うほど簡単な課題を設定し、それを本当に小さな意志の力によって実行するというもの
  • そして何度もその行動を繰り返して、その行動専用の神経回路を強化していけば良い
  • モチベーションを上げてモチベーションに基づいて行動を習慣化することは信頼できない
    • なぜなら、モチベーションは感情に基づくものだから。人間の感情は変わりやすく、モチベーションがコンディションによって大きく変わってしまう
    • つまり、モチベーションを使う方法が上手くいくのは、エネルギーが有り余っている時、健康的な考え方をしている時、他に大きな誘惑がない時に限る
    • 小さな習慣はその逆で、先に行動をとり、モチベーションがその後を追いかけてくる仕組みになっている
    • 課題がほんの小さなものなので自分の気分に関係なく実行できる
  • どんな課題でも、始めること自体が最初の抵抗を引き起こし、最初の壁となる
  • 小さな習慣の8つのステップ
    • 小さな習慣とプランを選ぶ。ここで重要なのは、小さすぎて失敗するはずがない行動にまで細かくしていくこと
    • 「なぜドリル」を使って習慣を身に付けたい理由を掘り下げる
    • 行動開始の合図を決める。時間ベースもしくは行動ベースで行動開始のトリガーを考える
    • 報酬プランを考える
    • すべてを書き留めておく。大きなカレンダーに書き留めたり、スマホアプリに記録をつけたりする
    • 小さく考える
    • スケジュールを着実にこなし、期待しすぎない
    • 習慣になる兆しを見逃さない

感想

小さな課題は意志力を使わずに取り組める、という点が何度も述べられており、それには納得した。意志力には限りがあるため、決断を繰り返すと、強い意志で物事を進める力が徐々に失われてしまう。

そのため、小さな課題に取り組みながら、気が向いたら追加の課題を少しだけやってみるという考え方は、ぜひ実践してみたい。振り返ると、昔AtCoderの問題を毎日1問解く習慣を続けていたときも、A問題だけでも解き続け、気が向いたときには追加で数問解いていた。その際、一番のハードルは「解き始めること」だったので、この「最初のハードル」をどれだけ低くするかが大事だと改めて感じた。

意志力については『スタンフォードの自分を変える教室』で詳しく言及されていたので読み返してみたい。

hiramekun.hatenablog.com

自分だったらこんな習慣になるだろうか

目標 小さな習慣
健康的で活力のある毎日を過ごしたい 1日1回ダンベルを持つ or ジムに行く(行くだけ)
エンジニアとしてコーディング力を磨きたい 毎日LeetCodeのEasyを1問解く
知識をインプットして社会にインパクトを残せる人になりたい 寝る前に本を2ページ読む
歯磨き中にKindleで本を読む
感情に動かされるのではなく、感情をコントロールしてマインドフルに生きたい 朝起きたら2分瞑想する

また、習慣をきちんと記録することも始めてみたい。プログラミングではGitHubで履歴が可視化されるので進捗がわかりやすいが、他の習慣については記録していない。まずはカレンダーを用意し、そこに毎日印をつけていくことから始めてみようと思う。

次に読みたいもの

意志力について、自分のまとめを読み返す hiramekun.hatenablog.com

Soft Skills にも毎日のinputやコードを書くことについて何かしら書かれていたような...忘れてしまった

休職してました

お久しぶりです。実は3ヶ月間ほど休職していました。

振り返ってみると、休職前にはすでに兆候があったように思いますが、それに気づかずに働き続けていました。休職中には、自分を見つめ直す時間があり、その中で様々なことを振り返り、自分自身について考える機会が増えました。そして今は復職してしばらく経ち、それぞれの経験を通して感じたことや学んだことを、ここで整理して共有したいと思います

要約

  • 転職による環境の変化が負担となり、無理を重ねた結果、休職に至った
  • 休職中に家族や友人の大切さを改めて実感した。周りの人は大切...!
  • 復職後は、自分の限界を知り、無理をしない働き方の重要性を学んだ

休職前の状態と休職に至った原因

昨年の9月に、自分は新卒で入社したベンチャーから、より大きな会社に転職しました。転職の大きな理由の一つとしては、大企業で標準とされている技術や開発体制に実際に触れて学びたいという思いがあったからです。

転職後の会社は素晴らしい環境だったのですが、やはり規模が大きくなることでの環境変化があり、最初は苦労しました。

  • 仕事の公用語が日本語から英語になったことで、微妙なニュアンスを伝えるのに苦労しました
  • 会社規模の違いによって、自分の影響範囲が狭くなり、関係者が増えたため、コミュニケーションコストが増えたことがストレスとなりました

さらに転職後は即戦力として期待され、プレッシャーがかかっていました。これまでは「新卒だから」と自分に甘えていたことを痛感し、転職後はその甘さを見直して、シニア的な役割を果たそうと頑張っていました。結果として、自分を大きく見せようとして無理をすることが増えていきました。

最初のプロジェクトではマイクロサービスの立ち上げを担当しましたが、スケジュールが楽観的で、何度かリリースが遅れることになりました。さらに、相談できずに一人で抱え込む癖がある自分は、問題を解決しようとするも、結局悪循環に陥っていました。

  1. 相談できずに自分一人で解決しようとする
  2. 結果として直前になり、スケジュール遅延をお願いする
  3. 責任感と罪悪感が重なり、さらに挽回しようとして無理をする

その時期ちょうど深夜のオンコール対応も重なり精神的に少し疲労が溜まってしまったため、通院と保健師との面談を経て早めに休職することを決断しました。自分一人では決して休職の決断はできなかったと思います。相談に乗ってくれた人皆に感謝です...!

休職中の過ごし方

最初の1ヶ月は、ほとんど家で寝て過ごす日が多かったです。仕事を休むことで一気に時間ができたにも関わらず、やる気が出ずただ時間が過ぎていきました。家事を少しやっただけで「自分頑張った!!!」と褒めていました。

その後、徐々に外出する日を増やし、友人と会って話すことを意識しました。友人とご飯に行く約束をたくさん入れ、家族や友人の大切さを改めて実感しました。周囲の人たちは、焦って復職しようとする自分に対して冷静な意見をくれ、無理せず休むことを勧めてくれました。長い人生の中の数ヶ月なんてちっぽけなものだし、この数ヶ月で健康になれれば安いものだと考えられるようになりました。また、休職のことを伝えていない友人からも刺激をたくさんもらいました。本当に感謝しています!

他には、

  • コメダ珈琲でモーニングをして、本を読んでリラックスする
  • 1週間ほど御殿場に旅行に行ってリフレッシュする
  • パーソナルジムに通い、運動を定期的に行う

などして、意識的に外に出るようにしました。この頃から休職前の出来事や、自分の課題を整理する時間が取れ、自分と向き合うことができました。

3ヶ月が経つ頃には復職の意欲が湧いてきて、生活リズムを取り戻すためにも復職する決断をしました。

また、現金は予想以上に大事だということを痛感しました。傷病手当金は申請してから受け取るまで2〜3ヶ月かかり、収入がない状態が続きました。その間、社会保険料は納め続けなければならないため、最終的に個別株を売却して生活費に充てることになりました。休職するにあたってある程度の現金を持っておくことは非常に重要だと感じました。

復職後の新たなスタート

復職にあたっては不安もありました。休んでいたことに対する後ろめたさや、同僚が自分をどう迎えてくれるのかという心配がありましたが、ただの杞憂でした。復職後は、会社のメンバーが暖かく迎えてくれ、感謝の気持ちでいっぱいです。

復職して気づいたのは、仕事が自分にとって大切な存在であるということでした。技術を学び、新しい知見を得ながら仕事をすることは、自分にとって日々の活力となっています。仕事に戻り、同僚との雑談やプロジェクトに取り組むことがとても楽しいと感じました。

現在は、定期的に関係者との1on1ミーティングを設定し、相談しやすい環境を作るよう心がけています。これにより、プロジェクトの進行中でも不安や疑問を一人で抱え込むことなく、早めに相談ができるようにしています。また、必要以上に自分を大きく見せようとせず、現実的なスケジュールを組むことを意識しています。特に、他のメンバーと一緒に見積もりを立てることで、より現実的で負担の少ないスケジュールを作成し、無理をしない働き方をするように意識しています。

休職は一見ネガティブな経験に思えますが、振り返ってみると、自分にとってはとても価値のある時間でした。

  • 自分のキャパシティを知ることができ、限界を超えない働き方の大切さを学んだ
  • 自分の弱点と向き合い、それをどう補うかを考えることができた
  • 無理をしないことの重要性を再確認した。仕事がうまくいかなくても、人生のすべてが終わるわけではないと感じられるようになった
  • 適度に責任感を持ちつつ、必要以上に重く捉えすぎないよう意識するようになった。自分の責任にしがちな認知の歪みを自覚するようになった
  • 周りの人を大切にしたいと思うようになった。支えてくれた家族や友人に感謝し、自分も周りに還元していきたい

最後に

今回の休職と復職を通じて、自分を見つめ直す貴重な時間を得ることができました。働きすぎず、適度に自分を大切にしながら前に進むことが大事だと実感しています。もしこの記事を読んでいる方の中で、同じような悩みを抱えている方がいれば、少しでも参考になれば嬉しいです!

Mercari Dataflow Template を使って、BigQueryからPubSub Topicへデータを転送する

はじめに

Cloud Dataflow に興味があったため、休日を使って少し遊んでみました。 この記事では、0の状態から、Mercari社が提供している Dataflow Template を使って、BigQueryからPubSub Topicへとデータを転送することをゴールとしています。 具体的には次のようなことを行います。

  • Mercari Dataflow Templateを利用し、BigQueryからPubSub Topicへとデータを転送する
  • PubSub TopicへはProtobufの形式で送信する
  • Dataflow Job用のService Accountを作成し、IAMの設定をする

まず前準備として必要なツールとサービスのセットアップから始め、その後、Dataflowのテンプレートの使い方について詳述します。最後に、実際にDataflowジョブを実行してその過程を確認します。

構成としては次のようになります。至ってシンプルですね

Dataflow Templateとは

本来であれば、Dataflow のパイプラインを構築しようとすると Apatch Beam などを用いて実装が必要になります。Dataflow Template を用いることで、コードを書くことなくパラメータなどを設定してパイプラインをデプロイすることが可能になります。

Dataflow templates allow you to package a Dataflow pipeline for deployment. Anyone with the correct permissions can then use the template to deploy the packaged pipeline. You can create your own custom Dataflow templates, and Google provides pre-built templates for common scenarios.

ref: Dataflow templates  |  Google Cloud

自分で組んだパイプラインをテンプレートとして配布することはもちろん、Google社が提供しているテンプレートもあるため、それを用いてパイプラインを動かすことができます。

Mercari Dataflow Template では、公式が提供しているより多くのデータ入出力先をJsonファイルで指定・実行できます。

The Mercari Dataflow Template enables you to run various pipelines without writing programs by simply defining a configuration file.

ref: GitHub - mercari/DataflowTemplate: Mercari Dataflow Template

前準備

関連リソースのセットアップ

次を準備していきます。

  • gcloud コマンド
  • Google Cloud プロジェクト
  • BigQuery
  • Cloud PubSub
  • Cloud Storage
  • Cloud Artifact Registry

ではまず、gcloud コマンドをインストール、セットアップします。

cloud.google.com

続いて、Google Cloud プロジェクトを準備します。これから先、プロジェクトのIDが変数 PROJECT_ID に設定されているとします。

$ gcloud projects create $PROJECT_ID
$ gcloud config set project $PROJECT_ID

データ転送元のBigQueryにサンプルとして使うデータを用意します。今回はBigQueryのQuickStartに従ってサンプルデータを用意します。

cloud.google.com

これに従ってデータを用意することで、PROJECT_ID.babynames.names2010 データが準備できるはずです。

そしてCloud PubSub のセットアップです。BigQueryからデータを連携する先である Topic と Subscription を準備しておきましょう。

$ gcloud services enable pubsub.googleapis.com
$ gcloud auth application-default login
$ gcloud projects add-iam-policy-binding $PROJECT_ID --member="user:$EMAIL_ADDRESS" --role=roles/pubsub.admin
$ gcloud pubsub topics create my-topic
$ gcloud pubsub subscriptions create my-sub --topic my-topic

ref: Quickstart: Publish and receive messages in Pub/Sub by using a client library  |  Pub/Sub Documentation  |  Google Cloud

これで、Topic my-topic とSubscription my-sub が作成できました。

最後に、Mercari Dataflow Template を実行する上で必要なリソースをセットアップしていきます。 まずは、Cloud Artifact Registry をセットアップしましょう。Mercari Dataflow Template では、ビルドしたパイプラインを Cloud Artifact Registry の docker repository にアップロードして利用します。下記コマンドで docker repository を作成できます。

$ gcloud artifacts repositories create quickstart-docker-repo --repository-format=docker \
    --location=us-central1 --description="Docker repository" \
    --project=$PROJECT_ID

ref: Quickstart: Store Docker container images in Artifact Registry  |  Artifact Registry documentation  |  Google Cloud

そしてテンプレートファイルのアップロードやJsonの設定ファイルの保存先として、Cloud Storage のセットアップも必要です。バケットを作成しましょう。

$ gcloud storage buckets create gs://BUCKET_NAME --location=BUCKET_LOCATION

ref: Create buckets  |  Cloud Storage  |  Google Cloud

ここまでで、依存しているインフラ周りの設定が完了しました。次のセクションで Service Account 周りの設定をしましょう。

Service Account の設定

Dataflow Job を実行するための Service Account を設定します。デフォルトでは Compute Engine のデフォルトサービスアカウントを利用するそうなので、自分で用意した Service Account を設定します。

こちらの公式ドキュメントに従ってセットアップすれば良いです。

cloud.google.com

概要を説明すると、それぞれの Service Account に対して次のような権限を付与します

  • 自前の Service Account
    • roles/dataflow.worker
    • roles/dataflow.admin
  • Dataflow Service Account と Compute Engine Service Agent
    • iam.serviceAccountTokenCreator
    • iam.serviceAccountUser

これで、Dataflow Job を自分が作成した Service Account で実行できるようになりました。

追加で、そのDataflow Jobを実行する際にアクセスするリソースに対する権限が必要です。

  • roles/artifactregistry.reader
  • roles/storage.objectAdmin
  • roles/bigquery.dataViewer
  • roles/bigquery.readSessionUser
  • roles/pubsub.publisher

それぞれ、次のコマンドで ROLE の部分を置き換えて付与していきましょう。

$ gcloud projects add-iam-policy-binding $PROJECT_ID --member="serviceAccount:SERVICE_ACCOUNT_EMAIL" --role=ROLE

設定ファイル等の作成

Protoファイルの用意

まずはPubSubに送信する際のProtoファイルを作成しましょう。bq show --schema babynames.names2010 によって対象となるBigQueryのスキーマを確認できます。

[{"name":"name","type":"STRING","mode":"NULLABLE"},{"name":"assigned_sex_at_birth","type":"STRING","mode":"NULLABLE"},{"name":"count","type":"INTEGER","mode":"NULLABLE"}]

name, assigned_sex_at_birth, count の三つのフィールドがあるようですね。それと同じフィールド名のProtoファイルを作成しましょう。特にデータ変換などを行わない場合は、Profoファイルの各フィールド名とBigQueryのスキーマの各フィールド名は一致している必要があります。今回SubscriberはGo言語で書くので、go_packageも指定しています。

syntax = "proto3";
package proto;

option go_package = "github.com/hiramekun/pubsub-sample/proto;proto";

message BabyName {
  string name = 1;
  string assigned_sex_at_birth = 2;
  int64 count = 3;
}

Jsonの設定ファイルの作成

Mercari Dataflow Template の設定ファイルを書いていきます。今回はモジュールとして BigQuery Source Module と PubSub Sink Module を利用します。その名の通り、source がデータの出てくる元で、sink がデータを送信する先です。

PubSub Sink Module のドキュメントに書いてあるように、パラメータとして次の値を設定する必要があります

  • format に protobuf を指定
  • protobufDescriptor でGCSにアップロードしたdescriptorへのパスを指定
  • protobufMessageName でpackage nameを含めた対象となるメッセージ名を指定

なので、protocコマンドで先ほど作ったProtoファイルをdescriptorへと変換してGCSにアップロードしましょう。--descriptor_set_out オプションを使ってdescriptorを出力できます。

$ protoc ---descriptor_set_out=message.pb message.proto

テンプレートが読み込むための設定ファイルは次のようになります。

{
  "sources": [
    {
      "name": "bigqueryInput",
      "module": "bigquery",
      "parameters": {
        "table": "PROJECT_ID.babynames.names2010"
      }
    }
  ],
  "sinks": [
    {
      "name": "pubsubOutput",
      "module": "pubsub",
      "input": "bigqueryInput",
      "outputAvroSchema": "gs://BUCKET_ID/output_schema.avsc",
      "parameters": {
        "topic": "projects/PROJECT_ID/topics/my-topic",
        "format": "protobuf",
        "protobufDescriptor": "gs://BUCKET_ID/message.pb",
        "protobufMessageName": "proto.BabyName"
      }
    }
  ]
}

実行

ここまで準備ができたら、Mercari Dataflow TemplateのREADMEに従って実行します!一点違うところは、--service-account-email で先ほど設定した Service Account で実行するように指定する点です。

github.com

$ gcloud auth configure-docker us-central1-docker.pkg.dev
$ mvn clean package -DskipTests -Dimage=us-central1-docker.pkg.dev/$PROJECT_ID/{template_repo_name}/cloud:latest
$ gcloud dataflow flex-template build gs://{path/to/template_file} \
  --image "us-central1-docker.pkg.dev/$PROJECT_ID/{template_repo_name}/cloud:latest" \
  --sdk-language "JAVA"
$ gsutil cp config.json gs://{path/to/config.json}
$ gcloud dataflow flex-template run {job_name} \
  --template-file-gcs-location=gs://{path/to/template_file} \
  --parameters=config=gs://{path/to/config.json} \
  --service-account-email=SERVICE_ACCOUNT_EMAIL

https://console.cloud.google.com/dataflow/jobsから実行結果やパイプラインの状況を確認することができます。

一応PubSubのTopicにメッセージが送信されてるかを確認しましょう。簡単なSubscriberをGoで書いてローカルで受信していることが確認できると思います。

func main() {
    projectID := os.Getenv("GCP_PROJECT_ID")
    subID := os.Getenv("PUBSUB_SUBSCRIPTION_ID")
    if err := pullMsgs(projectID, subID); err != nil {
        fmt.Println(err)
    }
}

func pullMsgs(projectID, subID string) error {
    ctx := context.Background()
    client, err := pubsub.NewClient(ctx, projectID)
    if err != nil {
        return fmt.Errorf("pubsub.NewClient: %w", err)
    }
    defer client.Close()

    sub := client.Subscription(subID)

    ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
    defer cancel()

    var received int32
    err = sub.Receive(ctx, func(_ context.Context, msg *pubsub.Message) {
        var m pb.BabyName
        if err := proto.Unmarshal(msg.Data, &m); err != nil {
            fmt.Printf("proto.Unmarshal: %v\n", err)
            return
        }
        fmt.Printf("Got message: %s, %s, %d\n", m.Name, m.AssignedSexAtBirth, m.Count)
        atomic.AddInt32(&received, 1)
        msg.Ack()
    })
    if err != nil {
        return fmt.Errorf("sub.Receive: %w", err)
    }
    fmt.Printf("Received %d messages\n", received)

    return nil
}

まとめ

今回は、Mercari Dataflow Template を使って、何も設定していないところからBigQueryからPubSub Topicへデータを転送する方法を記載しました。意外と前準備などが多いため、参考になれば幸いです。

これからも色々なDataflow Templateを触って遊んだり、機会があれば仕事で使ってみたりしたいと思います。

『入門 監視』を読んだ

『入門 監視』を読み終わったので、感想を簡単に書きます

å‹•æ©Ÿ

新しいシステムをリリースするにあたって、モニタリングやアラート周りの設定が必要となったが、ベストプラクティスや周辺知識が少ない状態だった。特に、false positive なアラートを最初に設定してしまい、システム運用とともに徐々に改善してきた。こういった自分の場当たり的な対応ではなく、体型的に監視の知識を得たいと思い、今回はこの本を手に取った。

印象に残ったところ

  • 有名企業は彼らが作ったツールによって成功したのではなく、彼らが成功したことによってツールが作られるのである。こうしたツールが作られるに至った長年の試行錯誤は我々から見ることは難しく、他のチームが採用しているツールが必ずしも自分たちを成功に導くとは限らない
  • 監視とは役割ではなくスキルであり、チーム内の全員がある程度のレベルに至っておくべき。全員が本番環境全体に責任を持つ必要がある
  • アラートに対する手順書(runbook)を書く。良い手順書とは、次の項目が書かれているものである
    • これは何のサービスで、何をするものか
    • 誰が責任者か
    • どんな依存性を持っているか
    • インフラの構成はどのようなものか
    • どんなメトリクスやログを送っていて、それらはどういう意味なのか
    • どんなアラートが設定されていて、その理由は何なのか
  • ログを埋め込むためには、アプリケーションの振る舞いから考えると良い。何かがおかしくなった時に、一番最初にする質問とは何なのか。トラブルシューティングや仕組みの説明時にあると便利な情報とは何か

感想

オンコーラーのローテーションやその際の手順については今のチームで実現できている部分も多く、今のやり方に自信を持ててよかった。

監視周りの設定には、自身が管理しているアプリケーションに対する深い理解が必要。他のチームやシステムが導入しているから、というだけの理由で同じアラートやログを真似して導入することは避けていかねばと感じた。

ネットワークやセキュリティ周りの監視は馴染みが薄くて実感が湧きづらいところもあったため、また少し経った後に戻ってきたいと思う。

次に読みたいもの

ツールとしては Datadog を使用することが多いため、具体的な Datadog におけるノウハウを調べてみたいと思った。

booth.pm

techblog.zozo.com

エンジニアが鬼怒川温泉でワーケーションしてきたよ

時間のない人向け

  • Otellを使って4泊5日のワーケーションに行ってきた
  • ワーケーションに適した作業環境がきちんと揃っていた
  • 温泉や周辺の自然でリフレッシュできた
  • 食事には少し困るが事前に調べてから行けば問題ない
  • また他の場所にも行ってみたい

はじめに

去年9月に現職に転職した後3月に大きめのリリースを終えて、ゆっくりと気分転換をしたくなり、4月中旬に以前から気になっていたワーケーションに行ってきました!

重要視していたのはこの辺

  • ネットワーク環境の良いところ
  • 1週間くらいは泊まりたい
  • 東京から遠すぎない場所(3時間以上はかけたくない)
  • 温泉に入りたい...!

ということで自分で色々と探すのは面倒だったので、Otellでワーケーションプラン4泊5日予約して行きました。4泊5日で2万円とかなりリーズナブルで驚きました。

プレスリリースより

今回自分が行ってきたのは、鬼怒川パークホテルズというところです(公式サイト, otellリンク)。

ホテル・部屋の条件としては次のようになっていて、自分の条件は全て満たせそうでした。部屋の詳細はリンク先のサイトを確認してみてください!

  • モニター貸し出しあり
  • TypeCアダプターとスタンディングテーブル台貸し出しあり
  • 客室Wi Fiが早い(サイトによると90Mbps以上出るらしい)
  • 温泉に入れる

結果どうだった?

作業環境

結構捗りました!

WiFiはとても早かったです。サイトに記載している以上で500Mbpsくらい出ていた記憶があります。Webミーティングやサイト表示に関しては何の問題も感じませんでした。

普通のホテルや旅館とは違い作業スペースが広く、ディスプレイとデスクライトのおかげで快適な作業環境でした。マウスとキーボードを持っていったのですが、どちらも問題なく利用できるスペースがありました。普段家ではスタンディングデスクを利用しているため、立って作業ができるのも嬉しかったです。

(仕事だけではなく、プライベートで触りたかった Cloud Dataflow のチュートリアルを二つほど完了することもできました。)

デフォルト設定とカスタマイズ後

周辺環境

自分は朝と夜に温泉に入ってきたのですが、湯加減も良く最高でした。いつでも浸かれる湯船があるって素晴らしいことですね。

作業環境に関連しますが、すぐ近くを鬼怒川が流れているため、川の流れる音や鳥の鳴き声など、適度な自然音を聞きながら作業できる環境でした。個人的にはかなり集中しやすかったです。

ホテルの目の前が桜並木であったり、すぐ隣に公園があるなど、リフレッシュやお散歩するのに良い環境でした!

窓からの風景と近くの公園

食事環境

行く前にいろんな人のブログを参考にしたのですが、そこに書かれている通り、食事には注意が必要です。そもそも飲食店が少なく、平日にやっているお店も少ないです。特に夜は旅館で食事をする人が多いからか、営業している飲食店がさらに絞られました。事前に行きたい飲食店をある程度調べてから行くことをお勧めします。

自分が調べてまとめたスプレッドシートも共有しますので参考にしてください。
ということで(?)、自分が行ってきた飲食店を一部紹介します!

香雅

中華料理屋さんです。いかにも町中華といった感じで、大きな中華鍋で調理していました。期待していた以上に美味しいチャーハンと餃子をいただきました。餃子は運が良いと見たことのない大きな羽根がついて出てきます。夜19時までやっている貴重なお店でもあるので、ワーケーションの際は是非訪れてみてください。

チャーハンと餃子

https://maps.app.goo.gl/ZN8J7JL5Be7MDBG48maps.app.goo.gl

珈香和cocowa

こちらもカフェです。落ち着いた和カフェのような雰囲気で、コーヒーと共に軽食やパンケーキを食べることができます。最終日に特急まで時間があったので少し長い間お邪魔したのですが、お昼に頼んだ生姜焼きサンドもパンケーキも美味しく、コーヒーも二杯飲んでしまいました。店員さんも朗らかで雰囲気良く、お勧めです!

https://maps.app.goo.gl/H4Qy8MdHhdgqsPAx7maps.app.goo.gl

Tearoom Ciffon

とっても可愛らしいカフェです。メニューから一つ一つのカラトリーや小物まで、オーナーさんのこだわりが感じられて楽しい空間でした。実際の料理はなかなか本格的で、ステーキのようなものからスパゲティまで色々とありました。

自分は蟹のスパゲティを頼みましたが具沢山で最高でした。

スープもついてきます

日によって営業時間が異なるようなので、インスタの投稿を確認することをお勧めします。この辺では珍しく20時くらいまで営業していることも多いようです。

https://maps.app.goo.gl/9fyBEc8DE3UJTjSL7maps.app.goo.gl

参考

Otellのページにリンクされている体験記にはかなりお世話になりました。感謝🙏

次の二つは違うホテルですが、鬼怒川エリアなので周辺情報の参考にさせてもらいました。

その他感想など

生活リズム

飲食店の営業時間を考えると夜早めに仕事を終える必要があったため、必然的に朝型になりました。8時には仕事を開始し、少し長めの昼休憩を取り、17時30分に仕事を終えるようなサイクルでした。普段はゲームをしたり動画を見ていて夜型になりがちなので半ば強制的に朝型になるのは個人的には嬉しかったです。

1日のサイクル

気になったところ・注意した方が良いところ

と、ここまで良いところをたくさん書いてきたので、少し気になったところも書いておきます。同じく鬼怒川パークホテルズでワーケーションをする人の参考になれば嬉しいです。

  • 実は椅子が結構薄いのでお尻が痛くなる。枕を敷いたり、スタンディングテーブル台を借りて対策をするのが良さそう。
  • 壁が少し薄いのでキーボードの音やミーティング中の自分の声が響いていないか少し心配になった。普通のHHKBを持っていったので、次行くときはType-Sの方にしようかな...
  • カーテンの遮光性が低い + 周辺の飲食店やスーパーも閉店時間が早いため、朝型生活を強く推奨。
  • コテージ泊になるので、温泉までは少し外を歩く必要がある。自分は問題なかったが、冬は寒くて浴衣での移動は大変そう。
  • タオルは宿泊日数分あるが、追加で使用したい場合は有料なので注意。朝風呂に入ったりする場合はタオルの計画的なご利用を。
  • 川沿いの茂みに近いため、虫が苦手な人は注意かもしれない。まぁ都会に虫が少なすぎるだけで、場所相応という感覚。

まとめ

色々と書きましたが、今回の4泊5日のワーケーションは、全体的に非常に充実していました。
Otellを見ると色々な場所がプランとして掲載されているので、また他の場所も試してみたいと思います。

※2024/04/30 追記1.
Otellさんより3000円分のギフト券がもらえるアンバサダーコード(招待コード)をいただきました。ワーケーションが気になる方は、こちらのコードを使って体験してみてください!

  • Otellアンバサダーコード入力サイト: https://otell.jp/ambassador/
  • アンバサダーコード: fB5L2W

※2024/04/30 追記2.
日光市のワーケーション補助金というものがあるのですが、自分は申請を忘れていました😭.
ワーケーション予定を書いた書類などの提出が必要ですが、一泊あたり5000円まで補助金が出るようです。次の機会にはぜひ申請してみたいと思います!

Go言語の Cloud Pub/Sub Client ライブラリを使用する上での注意点

はじめに

Go言語はサーバーサイド開発でよく使われる言語ですが、Pub/Subを用いたシステムを扱う際にも便利なライブラリや機能が提供されています。特に、Google Cloud Pub/SubのHigh-Level Client ライブラリは、Go開発者にとって使いやすく、強力な機能を提供してくれます。

前回は言語によらず Cloud Pub/Sub で意識することをまとめました。

hiramekun.hatenablog.com

この記事では技術的な側面に焦点を当て、Go言語でのPub/Sub Client ライブラリの効果的な使用方法について掘り下げていきたいと思います。

Low-Level APIとHigh-Levelクライアントライブラリの違い

Pub/Subシステムには、基本的に二つのAPIレベルが存在しています。Low-Level APIは、メッセージングシステムの細かいコントロールを可能にしますが、その分複雑さが増します。一方で、High-Level Client ライブラリは、開発者がより簡単にシステムを構築できるように設計されています。公式ドキュメントにもあるように、基本的にHigh-Levelクライアントを利用することが推奨されています。

Most subscriber clients don't make these requests directly. Instead, the clients rely on the Google Cloud-provided high-level client library that performs streaming pull requests internally and delivers messages asynchronously. For a subscriber client that needs greater control over how messages are pulled, Pub/Sub uses a low-level and automatically generated gRPC library.

ref: Pull subscriptions  |  Cloud Pub/Sub Documentation  |  Google Cloud

一方で、この抽象化されたAPIを利用する際は、システムの内部動作についての理解不足が予期せぬ挙動やパフォーマンスの問題を引き起こす可能性があります。そのため、High-Level Client ライブラリを効果的に使用するための知識とノウハウについても書いていければと思います。

Receive メソッドについて

goroutine の活用

Google Cloud Pub/Sub Client ライブラリで提供されているReceiveメソッドは呼び出し元をblockし、デフォルトでは内部で複数の goroutine を立ち上げ、Pub/Subからメッセージを非同期に受信します。

Receive calls f concurrently from multiple goroutines. It is encouraged to process messages synchronously in f, even if that processing is relatively time-consuming; Receive will spawn new goroutines for incoming messages, limited by MaxOutstandingMessages and MaxOutstandingBytes in ReceiveSettings.

ref: Cloud PubSub - Package cloud.google.com/go/pubsub (v1.32.0)  |  Go client library  |  Google Cloud

設定を変更することで、内部でgoroutineを一つだけ立ち上げ、同期的にメッセージを受信することも可能です。Receive内部コードだとこの辺でgoroutineの数を制御しています。

var numGoroutines int
switch {
case s.ReceiveSettings.Synchronous:
    numGoroutines = 1
case s.ReceiveSettings.NumGoroutines >= 1:
    numGoroutines = s.ReceiveSettings.NumGoroutines
default:
    numGoroutines = DefaultReceiveSettings.NumGoroutines
}

ack_deadline の自動変更機能

まず、Pub/Sub には Acknowledge deadline を Subscription 単位で設定できます。この deadline をすぎても Acknowledge されないメッセージは再送されます。

ref: Subscription properties  |  Cloud Pub/Sub Documentation  |  Google Cloud

ただし、この deadline は client ライブラリから変更できることに注意が必要です。

The Pub/Sub high-level client libraries provide lease management as a feature that automatically extends the deadline of a message that has not yet been acknowledged

ref: Extend ack time with lease management  |  Cloud Pub/Sub Documentation  |  Google Cloud

Go の Client ライブラリはメッセージを受信した直後に、sendModAck() を通して ModifyAckDeadline リクエストを送ります。コードだとこの辺です。つまり、受信したすべてのメッセージに対して ModifyAckDeadline リクエストしています。実際に延長する時間はこれまで Acknowledge するまでにかかった時間の99%タイルを採用しています。

Message.Nack() の内部挙動

Message には Ack と Nack という2種類の関数があり、Receive関数内でこのどちらかを必ず呼ぶ必要があります。

If received in the callback passed to Subscription.Receive, client code must call Message.Ack or Message.Nack when finished processing the Message.

ref: Cloud PubSub - Package cloud.google.com/go/pubsub (v1.32.0)  |  Go client library  |  Google Cloud

Message.Ack はメッセージを acknowledge しているのだと想像がつきますし、実際に low-level API に Acknowledge というメソッドがあるので、これをラップしていると考えることができます。

一方で、Nackに相当する rpc は存在しません。実は client 側でこのメソッドを呼び出すと、ModifyAckDeadlineRequest を ack_deadline_seconds=0 で送信しています。コードだとこの辺です。

if sendNacks {
    // Nack indicated by modifying the deadline to zero.
    it.sendModAck(nacks, 0, false)
}

つまり、前の節で紹介した ack_deadline の自動変更を利用し、即時に再送するようにリクエストをしていることになります。

MaxOutstandingMessages による flow control

Go言語の Client ライブラリでは MaxOutstandingMessages という設定項目があります。これは Receive メソッドが同時に処理できるメッセージ数の上限を設定します。

MaxOutstandingMessages is the maximum number of unprocessed messages

ref: Cloud PubSub - Package cloud.google.com/go/pubsub (v1.32.0)  |  Go client library  |  Google Cloud

デフォルト設定は1000に設定されているのですが、外部リソースへのアクセスやアプリケーションリソースの様子を見て適宜設定を変更することをお勧めします。

[要確認] バッチによる retry

Pub/Sub メッセージはバッチで送信されるため、バッチに含まれるメッセージが再送される時、そのメッセージのみではなくバッチに含まれるすべてのメッセージが再送される。との記載がありましたが、公式ドキュメントやQAフォームからはその内容を確認することができませんでした。

Messages in Pub/Sub are delivered in batches, so not only the expired messages will be redelivered but also the ones belonging to these messages batches.

stackoverflow.com

ローカルで256件のメッセージを receive するテストを走らせて、 goroutine の数を runtime/trace パッケージで解析したところ、Acknowledge している goroutine は受信したメッセージ数よりも少ないです。(traceパッケージについてはこちらの記事を参考にました。)

コードを確認すると、確かにAcknowledgeする部分はバッチで呼ばれていることが分かります。なのでリトライメカニズムもバッチ処理していている可能性はありそうですが、確証は得られませんでした。もし何かご存知の人がいたら教えてください。

   for len(ackIDs) > 0 {
        toSend, ackIDs = splitRequestIDs(ackIDs, ackIDBatchSize)

        recordStat(it.ctx, AckCount, int64(len(toSend)))
        addAcks(toSend)
        // Use context.Background() as the call's context, not it.ctx. We don't
        // want to cancel this RPC when the iterator is stopped.
        cctx2, cancel2 := context.WithTimeout(context.Background(), 60*time.Second)
        defer cancel2()
        err := it.subc.Acknowledge(cctx2, &pb.AcknowledgeRequest{
            Subscription: it.subName,
            AckIds:       toSend,
        })