タイミーのブログ - TECH PLAY

TECH PLAY

タイミー

タイミー の技術ブログ

全321件

イベント概要 2023年12月5日に「Next Year Con for SRE〜来年の登壇を応援する勉強会〜」と題してSREに関するトピックでタイミー、ココナラ、ビットキー、マジックモーメントの4社合同で勉強会を開催しました。 その中でタイミーバックエンドエンジニアの岡野さん( @Juju_62q )の講演をイベントレポートにまとめてお届けします。 チーム分割においていかれたアラートをチームで責任を持てる形に再設計した 自己紹介&想定聴衆 2020年にタイミーに入社し、現在では3年半ほどエンジニアをしている岡野と申します。 主にストリームアラインドチームの機能開発を担当しており、その一方でサイトリライアビリティエンジニアリングも行っています。 想定聴衆 「よくあるアラート」に困っているエンジニア 組織分割を考えているEMやCTO EnablingをやっていきたいSREs 今日お話しないこと どのようなアラートを設定すべきなのか アラートから繋がるオンコール対応周辺の話 アラートとFBサイクルとチーム 私が最も優れていると考えるアラート(右の図)は、問題が発生したら、エンジニアが問題を解決すると宣言し、リカバーするアラートです。理想的には、問題が自動的に解決することが望ましいですが、今回の話の範囲では、アラートの責務を超えていると捉え、理想的なアラートの形をこのように考えています。 次によくあるアラート(左の図)は、CPU使用率がN%を超えると、エンジニアが介入せずとも時間と共に回復したり、500エラーがy回を超えると何もせずとも回復するようなアラートです。このようなアラートは、時間が経過すると自然に解決し、次第に無視されがちです。 本来アラートはアクションを要求するための通知であるべきであり、アクションなしにアラートが鳴ることは無意味です。実際にタイミーにも、このような無駄なアラートが長期間存在していました。 現状、完全な解決には至っていませんが、タイミーがアラートを少しずつ改善した話を共有していきます。 タイミーではどのようにして「よくあるアラート」ができたのか 前提条件 1. 開発と運用を同じチームでやっていること タイミーでは、CTOが開発と運用を一つのチームで行うことを重視しています。CTOが好んでいるチームトポロジーの書籍では、「運用は開発への最大のフィードバック源である」と述べられており、私たちの組織でもこれを大切にしています。 2. エンジニアはアラートがなっていることを好ましく思っていないこと エンジニアはアラートが鳴る状況を好ましく思っていません。行動を起こすかどうかは別として、アラートが鳴っていること自体は好まれていませんでした。 3. エンジニアは機能開発以外にある程度の時間が使えること タイミーのエンジニアは、10%から20%の時間を技術改善に充てることができます。実際には、少なくとも10%の時間をこれに割り当てることが求められています。 2〜3年前、我々のチームは以下のような横割りの構造でした。 バックエンドチーム Webフロントエンドチーム モバイルチーム これらのチーム間では、バックエンドチームがアラートの設定を行い、障害経験を基にアラートシステムが構築されました。この時期には、ユーザー数も少なく、アラート対応の難易度も低めでした。赤く表示されるアラートに対しては、チーム文化として積極的に対応していました。 しかし、この横割り組織では、一つのチームだけで機能を完全に提供することが難しく、これが課題となりました。そのため、職能を横断する縦割りの組織構造への移行を進めました。この新しい形では、モバイルやWebフロントエンドなど、異なる専門分野のメンバーが同一チームに所属するようになりました。 ただし、この組織変更の際に、アラートシステムの見直しは行われず、バックエンドエンジニアが引き続きアラートの責任を担うことになりました。 アラートが鳴った際の反応はどうだったかというと、以下のような反応が一般的でした。 Aチーム「アラート鳴ってるけど、よくわからんな」 Bチーム「アラート鳴ってるけど、うちのチームとは関係無さそう」 チーム間で分断されていると、特定の機能についてどのチームが対応すべきかの判断が難しくなります。特に、人員が増加するにつれて、過去の経緯も失われ、このような問題は増えていきます。 いつも鳴ってるし今日も大丈夫だろう 『いつも鳴ってるし、今日も大丈夫だろう』という感覚が常態化すると、アラートが頻繁に鳴るようになります。アラートが放置され、対応が行われないため、アラートの数は増加の一途をたどります。 改善したいけどハードルが高そう 仮に意欲的なエンジニアが現れても、「何とかしたいが、どう変えればいいかわからない」という状況に陥りがちです。Aチームの同僚は協力的ですが、Bチームは相対的に距離があるため、意見を述べるのが難しいと感じることがあります。同じ会社に属していても、日常的に顔を合わせていない人に対し、合意を得る必要があるかもしれないという懸念が生じます。 なぜこのようなことが起こるのか なぜこのような状況が発生したのか、その原因を考察します。 アラートの作成は元々、バックエンドチームが責任を持って設定していました。当初、アラートに対するフィードバックはバックエンドチームに集中しており、このチームだけでアラートの削除や修正が行われていました。しかし縦割り組織への移行後、アラートに関するフィードバックは複数のチームに分散し、単一のチームだけでの削除や修正が難しくなりました。アラートに対する対応が不明確になり、長期間勤務している経験豊富なメンバーに相談することや、アラートに関する変更の承認を得るまでのプロセスが必要になることもありました。このように、フィードバックが機能しなくなった結果、アラートの数は増加し、特に新入社員にとっては理解しづらいものとなりました。この状況が、「よくあるアラート」の誕生に繋がったのです。 「よくあるアラート」を改善するためにしたこと 結論を述べると Aチーム・Bチームそれぞれにフィードバックと意思決定権を与えました。 具体的な行動 各チームで対応の必要があると思われるアラートを選んでもらう アラートに対してチームメンションをつける アラートはメンションのある単一チームで変更していいと周知する アラート対応に関する振り返りを実施 各チームで対応の必要があると思われるアラートを選んでもらう すべての問題を即座に解決するのは難しいため、Slackに「問題なし」または「問題あり、オンコール対応が必要」というコメントを残すことを最低限行うことにしました。このとき、多くのアラートのうち、選ばれなかったアラートは一旦すべて削除しました。 アラートに対してチームメンションをつける 次に、アラートに対してチームメンションをつけることにしました。以前はアラートにメンションがなく、全てのチャンネル通知をオンにする運用でしたが、これではどのチームが対応すべきか不明確でした。そこで、アラートにメンションを付与し、「このメンションのチームがオーナーです」と伝えるようにしました。 メンションのある単一チームで変更していいと周知する 次に、バックエンドのアラートに関しては、バックエンドチーム全体の許可がなければ変更できないと考えられていました。しかし、メンションのある単一チームでの意思決定による変更を推進しました。これにより、各チームは素早く意見を反映できるようになりました。 アラート対応に関する振り返りを実施 最後に、アラート対応に関する振り返りを実施しました。最初の改善はできるだけサポートするため、メンション付きのアラート対応について1ヶ月後に第1回の振り返りを各チームで行いました。これは、必要に応じて2回、3回と続けました。この流れで、チーム内での改善が少なくとも1回行われるところまで改善できました。 今、どのような変化があったのか 現在、アラートシステムにおいて以下のような変化が生じています。 アラートに反応する人やチームの増加 アラートに反応する人数やチームが明確に増えました。 アラートの定期的な変更や見直し アラートに対応しないと自分たちが困難な状況に陥るため、変更や見直しを定期的に行うようになりました。 Runbookの整備 チームごとにRunbookが整備され、アラート対応のプロセスが民主化されています。この分野にはまだ改善の余地があると感じていますが、良い変化が見られていると思います。 これらの変化により、「よくあるアラート」から徐々に脱出していると感じています。ただし、不要なアラートが多いことや、復旧の自動化がまだ十分ではないことは、今後の大きな改善ポイントです。 まとめ アラートは組織構造とFBを受ける人が不一致の場合に機能しなくなっていく アラートは単一チームで変更の意思決定ができないと硬直化する 組織が変わった際に、アラートの組織に合わせて変更するのが大切 最後に、アラートシステムのまとめです。アラートは、チームの構造とフィードバックが不一致になると、効果を失ってしまう可能性があります。フィードバックを受ける人々が明確でない場合、アラートの機能性が低下する傾向があります。またアラートに関しては、「単純接触効果」のとおり、日常的に顔を合わせる人々に対して意見を伝えやすいことが多いです。そのため、単一のチームで意思決定ができない場合、アラートシステムは硬直化しやすいと思われます。 結論、組織が変化した際には、アラートシステムの構造も組織の形態に合わせて変更することが重要です。 その他の方の発表も気になる方はこちら! www.youtube.com 少しでも興味を持っていただいた方は是非こちらからカジュアルにお話しましょう!
はじめに こんにちは、タイミーでバックエンドエンジニアをしている 新谷 、 須貝 、 難波 です。 2月10日に 広島国際会議場 で YAPC::Hiroshima 2024 が開催されました。タイミーはGold Sponsorとしてブース出展をしており、エンジニアが3名とDevEnable室が3名の総勢6名で参加させていただきました。 どのセッションも興味深かったのですが、この記事では我々が拝見したセッションのうち特に印象に残ったものをいくつかピックアップしてご紹介します。 なお、タイミーには世界中で開催されている全ての技術カンファレンスに無制限で参加できる「Kaigi Pass」という制度があり、エンジニアはこれを使って参加しております。詳しくは下記のリンクをご覧ください。 productpr.timee.co.jp 経営・意思・エンジニアリング speakerdeck.com 普段我々が行なっているソフトウェア開発の延長線上に CTO の仕事があるよ、という発表でした。経営者が物事をどう捉えているのかを垣間見ることができ、個人的にはベスト トーク でした。 ソフトウェア設計と組織設計が相似的な アーキテクチャ であるということは コンウェイ の法則などもあることから直感的に理解できましたが、事業設計とも相似的な アーキテクチャ であるという話はまだピンときていないのでまたどこかのタイミングでお伺いしたいと思っています。 また、意思が重要という話はプロジェクトを成功するためには強い意思を持つ推進者が必要不可欠という点で共感していました。ただ、プロジェクトが短期間で終わるものであれば良いのですが、ある程度期間のかかる類のものは個人の意思では難しいこともあるかと思っていて、そういったものはどう対処しているんだろうとも気になりました。 短期的成果の重力に負けないよう、これからのプロダクト開発を行なっていきたいと思います。 (新谷) 関数型プログラミング と型システムのメンタルモデル speakerdeck.com コンピュータ アーキテクチャ に近い手続き型プログラミングから 関数型プログラミング へメンタルモデルを変えていこう、という発表でした。私は普段 Ruby on Rails でアプリケーション開発をしていて 関数型プログラミング とは距離があるのでとても興味深く聞かせていただきました。 オニオン アーキテクチャ は DI ができるとかモックが可能とかが重要なのではなく、 手続き型言語 の考えに引き摺られがちな I/O の部分を業務ロジックから切り離し、業務ロジックに対して別の パラダイム を適用できるようにするのが本質という話を聞き、なるほどと勉強になりました。 一方、 関数型プログラミング が Web アプリケーションのバックエンドに本当に適用できるのかはまだイメージが湧いていないのが正直なところではあります。質疑応答で質問をさせていただいて RDB の書き込みに相当する I/O の部分は Repository 層に閉じ込めるという話は納得したのですが、Web アプリケーションは本質的に並行処理だと思っていて、リク エス トを処理している最中に状態が書き換わることをどうやって純粋な関数で表現するのかが気になっています。 懇親会で確定申告の話と一緒に質問しようと思っていたのですが残念でした… 「スマンな!確定申告の話はまた今度!」 と、便の都合で先にかえるnaoya氏 #yapcjapan pic.twitter.com/drogfb5b5Q — 941 (@941) 2024年2月10日 (新谷) 変更容易性と理解容易性を支える自動テスト speakerdeck.com 自動テストの目的は 信頼性の高い実行結果に 短い時間で到達する状態を保つことで、 開発者に根拠ある自信を与え、 ソフトウェアの成長を持続可能にすること という結論からスタートし、その結論に向かって一つずつ解説していく内容でした。 この目的は極限まで削れば「ソフトウェアの成長を持続可能にすること」だと私は思います。これだけだと飛躍があるので一つずつ順を追っていくと、まずソフトウェアの持続可能な成長には変更容易性(変更しやすい)と理解容易性(わかりやすい)が必要です(ないとツラい)。そして変更による既存機能への影響を知る手段のひとつに自動テストがあります。また、自動テストには既存コードの理解を助ける役割もあります。もちろんただ自動テストがあればよいわけではなく、 良い自動テスト が必要です。 ではどうすれば良い自動テストを書けるのかという話になるわけですが、ここで私が思い出したのは「 単体テスト の考え方/使い方」(Vladimir Khorikov 著、須田智之 訳)です。 この本の「4.1 良い 単体テスト を構成する4本の柱」で以下の4つの要素が挙げられています。 退行(regression)に対する保護 リファクタリング への耐性 迅速なフィードバック 保守のしやすさ 目的で挙げられた「信頼性の高さ」は「退行に対する保護」( 偽陰性 からプロダクション・コードを守るもの)と「 リファクタリング への耐性」( 偽陽性 の数を最小限に抑えるもの)であり、「短い時間で到達する状態」は「迅速なフィードバック」に相当します。そして「信頼性の高い実行結果に短い時間で到達する状態を 保つ 」ためには「保守のしやすさ」が欠かせません。つまり冒頭の二行に良いテストの条件がすべて凝縮されていたのです。 発表にあった「実行結果は情報」という指摘には良いフィードバックの重要性も感じました。また、 Google のテストサイズの話なども非常に勉強になりました。今回のセッションをきっかけに改めて自動テストの目的に立ち返り、自分たちがより根拠のある自信を持ってコードを書けるようにしていきたいと強く感じています。 最後に余談ですが、発表の中で気になったことを懇親会でtwadaさんご本人に直接質問できたのはオフラインイベントの醍醐味だなとしみじみ感じました。 (須貝) 非同期な開発体制を支えるドキュメント文化 speakerdeck.com 時差のあるメンバー同士でも適切にコミュニケーションを行うために Launchable, inc.で実際に行われている ドキュメンテーション の事例を紹介頂く内容でした。 発表の中で特に印象に残ったものを3つほど挙げさせていただきます。 フィードバックの平等性 検索性の意識 ドキュメントの型化 まず「フィードバックの平等性」についてです。組織にいるメンバーは多様であり聞いた話について素早く考えて話すことが得意な人もいればじっくり考えたい人もいます。また組織内 公用語 として使われている言語が 第一言語 でない人もいます。そういった場合に同期的な MTG でやり取りすると議題に対するフィードバックを行う人が偏る傾向があり、またフィードバックの観点も偏りがちです。非同期にフィードバックをもらうようにすることでこの課題の対策としているというのは良い視点だと思いました。なお、タイミーでも参加者が多い一部の MTG では議事録のドキュメントにフィードバックの欄があり、また MTG の録画を行うことで非同期にフィードバックを行いやすい運用をしていたりします。 次に「検索性の意識」についてです。ドキュメントをしっかり残すことは非同期か否かに関わらず非常に良い文化であり実践している組織も多いと思いますが、ドキュメントが増えると必然的に起きるのが検索性の悪化です。タイミーでは ドキュメンテーション にNotionを使用しているのですが、閲覧したいドキュメントがなかなか見つからなかったり、見つかったドキュメントが実は古いものだったといったことはどうしても発生します。その対策としてまず部門ごとにConfluenceのspaceを分けているとのことでした。他部門の情報が簡単に閲覧できるという透明性は重要ではあるものの、それは権限管理をしっかりやれば解決できることです。また積極的に アーカイブ (≠ 削除)を行うことでデフォルトの検索設定では検索にヒットしないようにするという話もあり、これはその通りだと思う一方でそれを組織文化とするのは一朝一夕では無いなと感じます。 最後に「ドキュメントの型化」についてです。ドキュメントが一定のフォーマットに準じて作られていれば書き手にとっては書くべき情報が漏れにくいですし、読み手にとっても慣れれば慣れるほど要点を理解しやすくなります。また適切なフィードバックを受けるための 30/60/90% framework for feedback についても勉強になりました。これはドキュメントのフェーズに対して適切なフィードバックを行おうというもので、普段から意識してはいたもののそういった名前が付いていたことは知らなかったので今後のコミュニケーションの際に使おうと思います。そして関連資料のドキュメントへの記載です。ドキュメントを書く際は何かしら参考にした別のドキュメントというものがある場合が多いですし、書き手にとっては自明でも読み手にとってはそうでない場合も多いです。こういった資料のリンクを書き手だけでなく、読み手も積極的にドキュメントに残していこうというのは今後より意識していきたいです。 (難波) 杜甫 々さんのキーノート あの日あの場にいたことを自慢し続けようと思います。 (須貝) おわりに YAPC は長く続いているイベントですが、今回エンジニアで参加したメンバーは初参加だったり9年ぶりの参加という感じでした。それでも YAPC の和やかな雰囲気と Perl に関係するテーマもそうでないものも受け入れている包容力で皆大変楽しむことができ、また勉強になったカンファレンスでした。 来年以降もまたスポンサーや参加ができればと思います。 最後にランチとして配られた広島名物のあなごめしの画像を貼って終わりとします。
イベント概要 2023年11月15日に「GENBA #1 〜RubyとRails開発の現場〜」と題してRuby/Railsでの開発に関するトピックでタイミーとエンペイ社合同で勉強会を開催しました。 その中でタイミーバックエンドエンジニアのpokohideさん( @pokohide )の発表「Railsアプリで秘匿情報を環境変数からCredentialsに移行した話」をイベントレポート形式でお届けします。 登壇者紹介 Credentialsとは Credentials は、Rails 5.2から追加された秘匿情報を管理するための仕組み※1 で、Rails 6から複数の環境をサポート※2 しています。 【主な登場人物】 暗号化ファイル: config/credentials/ .yml.enc 復号用の伴: ENV[”RAILS_MASTER_KEY”] or config/credentials/ .key 【セキュアな構成管理】 Railsアプリ起動時に Rails.env に対応する暗号化ファイルと鍵を参照し復号する 復号化が完了すると Rails.application.credentials 経由で取得可能になる 【暗号化と参照方法】 YAML形式 のファイルを暗号化(YAMLの構文に依存) ⇆ 復号 復号化された後は ActiveSupport::OrderedOptions を介してアクセス可能 fetch や dig が使える ▲Credentialsの例 ※1Add credentials using a generic EncryptedConfiguration class #30067 ※2Add support for multi environment credentials. #33521 Credentialsへの移行目的 Credentialsへの移行は、組織内での秘匿情報管理の責任を明確化し、デプロイプロセスを効率化することを目的としています。 手間の削減 以前はECSのタスク定義に環境変数としてパラメータストアのSecureStringを利用していました。秘匿情報を追加する際にパラメータストアに登録し、ECSタスク定義とアプリケーションコードの両方を変更する必要があり、手間がかかっていました。 責任境界の曖昧さ AWSリソースの管理はインフラチームが主導していましたが、その結果、責任境界が曖昧になることがありました。 ⇒ Credentials導入によって、秘匿情報の管理に関する責任の所在が明確になり、責任境界が明確化されました。 デプロイの難易度 複数の場所での操作が必要だったため、デプロイが容易ではありませんでした。 ⇒ Credentials導入によって、アプリケーションコードを変更することで秘匿情報を追加・参照できるようになるため、デプロイも容易になりました。 レビューの困難さ 独自の対話型CLIを使用してパラメータストアを操作していたため、プロセスのレビューが困難でした。 セキュリティの向上 ⇒ Credentials導入の副産物として、セキュリティ向上が挙げられます。RAILS_MASTER_KEYのみを管理することでパラメータストアの操作権限を削減でき、全体的なセキュリティレベルが向上しました。 Credentialsの安全性 Credentialsの安全性は、主に使用される暗号化アルゴリズムとマスターキーの管理方法に依存します。2023年時点で、AES-256-GCM暗号化アルゴリズムを用いた暗号化は、最も安全だといわれています。 しかし、最も重要なのはマスターキーの管理です。マスターキーが流出すれば、暗号化された情報が容易に解読されてしまう可能性があります。そのため、マスターキーの安全な保管とアクセス管理は非常に重要です。 管理方法については、ビジネスの環境やリスクに応じて慎重に検討し、適切なセキュリティ対策を講じる必要があります。 Credentials 移行の手順 移行の手順は、とてもシンプルです。 何を移行するか決める 移行対象の秘匿情報を全てCredentialsに追加する 少しずつ Rails.application.credentials に移行する 1. 何を移行するか決める まず、何をCredentialsに移行するかを決定します。アプリケーションの構造を分析し、環境変数をリストアップします。秘匿情報には、環境変数だけでなく、証明書、秘密鍵、Google Cloudの認証用JSONキーなどが含まれる可能性があります。 また、秘匿情報が本当にCredentialsへの移行が必要かを考えましょう。環境ごとの固有の設定であれば config_for を使用することで解決できるかも知れません。コンテナ化された環境やデータロックサービスのような動的に注入する必要がある情報や、頻繁に更新される情報は、Credentialsへの移行が適切かどうかを慎重に検討します。 2. 移行対象の秘匿情報を全てCredentialsに追加する 秘匿情報を一括でCredentialsに追加することをおすすめします。その理由は、暗号化ファイルのレビューが難しいためです。Credentialsへの移行前もレビューは難しい状況でしたが、暗号化ファイルを扱う現在も同様です。そのため、秘匿情報をまとめて移行し、Railsコンソールでリリース前に値が正しく一致しているか確認することで、プロセスがよりスムーズになります。 3. 少しずつ Rails.application.credentials に移行する Rails.application.credentials への移行は段階的に行います。このプロセスは、多くのプルリクの作成と対応を伴い、障害が発生する可能性もあります。実際に起きた一例として、全角スペースと半角スペースを間違えて登録し、参照時にエラーが発生しました。秘匿情報のファイルレビューは通常、Syntax Highlightが効かない素のVimなどで行われるため、特に細心の注意が必要です。 タイミーでは技術改善のために割り当てられる時間が全体の約20%で、この時間を利用してCredentialsへの移行作業を行いました。全体としては約5ヶ月の期間を要しました。もし環境が異なれば、移行にかかる時間は1ヶ月程度に短縮可能かもしれません。移行の過程では、ステージング環境と本番環境で同じ秘匿情報を使用していたこともあり、そのような点にも対応しながら作業を進めました。 Credentials移行時のTips 1. config.require_master_key 設定を有効にする Railsアプリケーションを起動する際には、Credentialsのマスターキーが必須です。マスターキーがないとアプリの起動に失敗する設定を有効にしておくと良いでしょう。 2. エディタの指定 暗号化ファイルの編集にはエディタの指定が必須なので用意(Dockerを利用している場合はemacsなどでもOK) 3. エスケープ文字の扱い 秘匿情報にエスケープ文字が含まれている場合は、ダブルクォーテーションで囲むことが推奨されます。暗号化ファイルがYAML形式に依存しているため、YAMLの構文規則に従う必要があります。 4. 改行の利用方法 秘匿情報に改行を含めたい場合は、パイプなどのYAMLの構文を利用します。 5. 外部サービスとの認証方法を変更する 外部サービスとの認証方法も変更しました。具体的には、以前は秘匿情報をファイルから読み込んでいた認証方法を、Credentialsで管理する形式に変更しました。 6. バイナリデータの取り扱い YAML形式はテキストベースのデータ形式であり、バイナリデータの直接的な扱いには向いていません。特に、証明書などのバイナリデータをCredentialsで管理する際は、事前にBase64でエンコードする必要があります。アプリケーションでは、このエンコードされたデータを取得し、適切にデコードして使用します。データの識別を容易にするために、YAMLファイル内でエンコードされた値には base64_encoded というプレフィックスを付けると便利です。 7. 秘匿値をコンソールで非表示にする Railsコンソールを使用する際、デフォルト設定では秘匿値が表示されてしまうことがあります。(Rails 7.1からは、この秘匿値を非表示にする機能が標準で実装されています。)この変更によって、Railsコンソールから秘匿情報が誤って露出するリスクを軽減できます。 8. SECRET_KEY_BASEを Credentials に移行 Secretsは Rails 7.1から明示的に非推奨化 されたため、SECRET_KEY_BASEを Credentials に移行しました。各環境の credentials.yml に SECRET_KEY_BASE を移行すればOKなはずです。Rails 5.1以前で secrets.yml を使用して秘匿情報を管理していたアプリケーションの場合、移行プロセスは少し複雑になる可能性があります。 9. SECRET_KEY_BASE_DUMMYの活用 Rails 7.1から SECRET_KEY_BASE_DUMMY が導入 されました。これは、SECRET_KEY_BASE の代わりにダミー値を自動的に設定する SECRET_KEY_BASE_DUMMY です。assets:precompile 実行時に SECRET_KEY_BASE が必要ない場合でも、エラーが発生することを防げます。 10. Heroku Data for Redis これはタイミーではなく個人のアプリでの経験ですが、 Herokuで運用、Heroku Data for Redisを利用してる個人アプリのREDIS_URLをCredentialsに移行したらRedisに接続できなくなりました。自分で管理していない環境変数などを移行する場合は注意しましょう。 移行の結果 Credentialsに移行した結果、下記のような成果を実感しています。 ただし、依然としてレビューが大変です。データが暗号化されているため、プルリクエストを出しても差分が分からず、暗号化ファイルのdiffを確認するには公式の bin/rails credentials:diff を利用できますが、Railsの実行環境からgit操作が必要です。一般的な環境ではないかもしれませんが、当社では開発環境にDockerを使用し、ホスト側でgit操作を行っています。そのため、コンテナにgitを導入する検討をしています。 記事の発表やその他の発表が気になる方はこちら! www.youtube.com 少しでも興味を持っていただいた方は是非こちらからカジュアルにお話しましょう! product-recruit.timee.co.jp
イベント概要 2023年11月15日に「GENBA #1 〜RubyとRails開発の現場〜」と題してRuby/Railsでの開発に関するトピックでタイミーとエンペイ社合同で勉強会を開催しました。 その中でタイミーバックエンドエンジニアの正徳さん a.k.a 神速さん( @sinsoku_listy )の発表「Railsアプリと型検査」をイベントレポート形式でお届けします。 登壇者情報 Railsアプリと型検査 RBSの基本 RBSとは RBS(Ruby Signature)は、Ruby 3.0から導入された言語機能で、Rubyのコードに型情報を追加し、型検査と入力補完を可能にするための言語です。RBSファイルの拡張子は .rbsで、通常はプロジェクト内の sig/ ディレクトリに配置されます。 RBSのメリット RBSの主なメリットは「型検査」と「入力補完」の2つがあります。 型検査とは 型検査には、Steepと呼ばれるツールがあります。Steepを使用すると、RBSの定義に違反するコードを検出する役割を果たします。例えば、関数に指定された引数の数が実際の呼び出しと一致しない場合、エラーが検出されます。この型検査は、Rubyコードを実行する前に問題を発見できます。 下記は型検査(Steep)の実行例です。 入力補完とは VS Codeに soutaro.steep-vscode 拡張をインストールすると、RBSファイルを読み込んで、Rubyコードの入力補完が賢くなります。 IRB v1.9.0で、RBSを使った入力補完がサポートされました。入力補完を有効にするには、gem install prismコマンドでprismをインストールし、IRBを起動するときに-r prismオプションを指定します。 タイミーの導入事例:実際の手順 ここまで一応RBSの基本の話だったんですけど、弊社が実際に導入事例を紹介したいと思います。 RBS導入のきっかけ RubyKaigi 2023やメドピアのブログを読んだことから、RBSの導入への意欲が高まりました。 実際に試してみると、型チェックを無効にして、入力補完に重点を置くことで、比較的簡単にRBSを導入できることが分かりました。すぐに社内での調整を行い、方針を策定しました。 社内の開発者に質問してみたところ、RBSを完全に書くことには乗り気ではないものの、重要な部分だけを一部記述したいというニーズがありました。 しかし、それを別のファイルに書くのは少しつらいという声も聞かれました。 そこで元々弊社で使用していた Yard(ヤード)のドキュメントを活用し、重要な部分に型情報を明示し、コメントからRBSを生成して活用するアプローチを考えました。 実際にプロジェクトで検証した結果、うまく機能しそうだったため、「入れてみてダメだったら消そう」というノリで、試してみることになりました。 1. Gemfile に rbs_rails, steepを追加します 2. Steepfileを追加し、既存の型検査エラーは無視する 3. rbs_collection.yaml を作成する Ruby公式で管理されている rbs-gem-collection というリポジトリに、有志の方が提供している情報を利用し、rbs_collection.yaml ファイルを作成します。 4. RBSを生成するRakeタスクを追加する 様々なコマンドを個別に実行するのは手間がかかるため、一度にタスクを生成できる「RBSセットアップ」という独自のコードを作成しました。 このコードは、rbs-gem-collection からRBSをダウンロードし、自社のプロダクトのコードを解析し、適切な雛形を生成し、全てのクラスとメソッド名が含まれたRBSを生成できるものです。 5. .gitignore でディレクトリを無視する RBSは生成されるものとして扱っているため、 .gitignore ファイルでこれを無視するように設定します。 6. RBSの定義エラーを回避するために最小限の型を書く これまでの作業で、多くの型情報を生成できましたが、やはり一部の型情報は自動生成できない場合があります。そのような場合、手動で最小限の型情報を記述することで、エラーを回避します。例えば、Active Admin など一部のジェムは公式の型情報が提供されていないため、自社で型情報を記述する必要があります。ここまで実施し、RBSの文法エラーがない状態にプロジェクトを持っていきます。 まとめ:最小限のRBSで入力補完に型を使用できる RBS + Sord の導入方法 Sordというジェムを使用しヤードドキュメントからRBSコマンドの型を生成します。 まずSordを導入し、Rakeタスクでsordを実行します。このとき sord のバグは、GSub で書き換えるとうまくいくことがあります。 この手順で、 bin/rails rbs:setup を実行するとRBSが生成され、入力補完が賢くなる世界を作ることができました。 ちなみに、RubyMine や VS Code を使用していると、入力補完が賢くなるケースがあるようです。 型検査の課題 タイミーにおける型検査の課題は、Steepの型定義が少ないため、誤検知が発生する可能性があること、 また、特定のファイルのみを無視することができないため、段階的な導入が難しいことが挙げられます。 実際にどれくらいエラーが検出されるか検証をしてみると、656件ありました。ほとんどが誤検知ですが、そのなかで3件ほど面白いエラーがありましたのでご紹介します。 型検査で見つけたコード例1 型検査では、nilチェックが甘いため、ハーストした後にnilが来る可能性があることを考慮せず、IDなどの値に代入してしまうケースがありました。 型検査で見つけたコード例2 タイミーでは、ハッシュから銀行コードを取得する際に、全銀コードがない場合、nilが返されます。このときエラーを検知してしまいます。これは、Steepが全銀コードが正しい値しか渡されないことを前提としているためです(全銀コードが存在しないケースを想定できていない) 型検査で見つけたコード例3 足し算と掛け算をするときに、右の値をぼっち演算子を使うのですが、このときNilエラーが発生します まとめ 今後、開発がさらに進化し、堅牢なコードを手軽に書ける世界がやってくるかもしれません。特に、入力補完の文脈で型検査を導入することは導入ハードルが低く、Railsアプリケーションの開発に大きな価値をもたらす可能性があります。明日からでも是非、Railsアプリ開発に型検査を取り入れてみてください。 記事の発表やその他の発表が気になる方はこちら! www.youtube.com 少しでも興味を持っていただいた方は是非こちらからカジュアルにお話しましょう! product-recruit.timee.co.jp
タイミーでバックエンドエンジニアをしている新谷 id:euglena1215 です。 今回は社内で決めたコーディングルールに強制力を持たせるために CustomCop を作った話を紹介します。 背景 タイミーの Rails アプリケーションには /app/services ディレクト リがあり、 Service クラスが存在しています。 これまで社内で Service クラスは、なるべく使わない方が好ましいものの、どんな時に使っていいかは特段明言されていない状況でした。 その結果かは分かりませんが、一部の機能では Service クラスを多用し Service クラスが Service クラスを呼んでいるなど複雑になっており、コードリーディングの負荷が高まっていました。 この現状に課題感を持った @rhiroe が以下のような問題提起を行いました。 この問題提起を受け、チーム横断の技術領域ごとのトピックについて話し合う MTG で今後の Serivce クラスの運用方針は以下のように決まりました。 Serviceクラスは基本的に積極的な利用は推奨しない。 Service クラスは Controller や Sidekiq Worker から呼ぶものと位置付ける。それら以外から呼びたくなった場合は Model の作成を検討すること。 方針についてバックエンドエンジニア全体に周知は行われましたが、あくまで「きちんと覚えておきましょうね」に留まり、仮に実装者とレビュアーが思い出すことができなければそのままマージされてしまいます。 そのため「このルールを 機械的 に適用できるといいよね」という話があり、用途に合わせた CustomCop を作ることになりました。 特定のsuffixを持つクラス以外からのサービスクラス呼び出しを警告する cop 早速ですが、CustomCop の実装をまるっと公開します。 # frozen_string_literal: true begin require ' rubocop ' rescue LoadError return end module CustomCops # 特定のsuffixを持つクラス以外からサービスクラスを呼び出した場合に警告します。 # AllowSuffix は config パラメータで設定できます。 # # @example # # bad # # AllowSuffix に Service が含まれていない場合 # class SampleService # def call # Service2Service.new.call # end # end # # # good # # AllowSuffix に Service が含まれている場合 # class SampleService # def call # Service2Service.new.call # end # end # class AllowServiceCallClassSuffix < RuboCop :: Cop :: Base MSG = ' 特定のsuffixを持つクラス以外からサービスクラスを呼び出すことはできません。%<suffixes>s のみが許可されています。 ' # ① 許可されている Suffix を持つクラスの定義かどうか # ② クラス定義の中で更にクラスかモジュールを定義しているかどうか # この cop で重要なのは最も深いクラス定義での情報なのでネストしているものは無視(≒許容)する def_node_matcher :define_allow_class? , <<~ PATTERN { (class (const ... #allow_class_name_suffix?) ...) (class const ... {class module}) } PATTERN # (send (const nil? #service_class_name?) :new ...) => FooService.new(...) # (send (const cbase #service_class_name?) :new ...) => ::FooService.new(...) # (send (const const #service_class_name?) :new ...) => Bar::FooService.new(...) def_node_matcher :call_service_class? , <<~ PATTERN (send (const {nil? cbase const} #service_class_name?) :new ...) PATTERN # on_class はクラス定義が行われた時に呼び出されるメソッド def on_class (node) # 許容されるクラスであれば無視する return if define_allow_class?(node) node.each_descendant( :send ) do |send_node| # サービスクラスの呼び出しをしていれば警告する add_offense(send_node, message :) if call_service_class?(send_node) end end private # AllowSuffix で指定した suffix を警告メッセージに加えたかったので上書きしている def message format( MSG , suffixes : allow_suffixes.join( ' , ' )) end # call_service_class? マッチャで使っている def service_class_name? (name) name.to_s.end_with?( ' Service ' ) end # define_allow_class? マッチャで使っている def allow_class_name_suffix? (name) allow_suffixes.any? { |suffix| name.to_s.end_with?(suffix) } end def allow_suffixes # `cop_config` で rubocop.yml で指定した設定を取得できる @allow_suffixes ||= cop_config[ ' AllowSuffix ' ] || [] end end end rubocop.yml には以下のような記述を行います。 CustomCops/AllowServiceCallClassSuffix : AllowSuffix : - Controller - Worker どんな cop かを軽く説明します。 rubocop.yml で指定した AllowSuffix を suffix に持つクラスからの Service クラスの呼び出しを許容し、それ以外のクラスからの呼び出し時には警告を出します。 タイミーでは、Service クラスに XXXService.call のような特異メソッドを用意せず、 XXXService.new.call というように インスタンス を作成した上で呼び出すことが通例となっているため XXXService の new メソッドが呼び出されたことを検知しています。 ここは、各社の通例に合わせてもいいですし、任意のメソッドを呼び出したことを検知しても良いと思います。 また、 service_class_name? メソッドではクラス名の suffix が "Service" かどうかをチェックしていますが、ここを変更すれば Service クラス以外にも適用可能です。 Model, View, Controller 以外のレイヤを採用している Rails アプリケーションであれば、Service クラス以外でも呼び出し箇所を制限したいことがあるかもしれません。 結果どうなったか 上記の CustomCop をタイミーの Rails アプリケーションに導入した結果、違反している箇所が 67件 見つかりました。違反しているファイルを rubocop_todo.yml から眺めてみると、Model から Service を呼び出しているケースと Service から Service を呼び出しているケースが半々くらいのようです。 2024/01/24 時点では、導入したばかりでこれらの違反を解決するための動きは取れていません。 ですが、増やそうとすると RuboCop に怒られるのでこれ以上増えることはないのではないかと考えています(そう信じています)。 タイミーのプロダクトは 1つの モノリス な Rails アプリケーションの上で動いています。その中での67件が多いか少ないかはみなさんの判断にお任せします。我々はこれが伸びしろだと捉え、がんばっていこうと思います。
はじめに 私自身の事例 日々の学習・研究時間の確保 育児との折り合い パートナーや家族のサポートの重要性 日々の学習や研究の効率化 効率的な勉強法や研究の進め方の文献 社会人の特権のツールへの投資 ストレス管理と心の健康 タイミーという最適な環境 育児への支援制度 自己研鑽への支援制度 DSグループでの働き方 まとめ はじめに こんにちは、株式会社タイミーのデータエンジニアリング部データサイエンス(DS)グループ所属の貝出です。 私は現在タイミーで働きながら、育児しつつ、社会人大学院(修士)に通っています。今回のブログでは、仕事・育児・社会人大学院をどう調整して進めていくかについて書いていきたいと思います。 私自身の事例 簡単な私のプロフィールとしては、以下となります。 妻と一歳の息子の三人家族 妻は2023年度までは育休を取得予定 タイミーで週5リモートワーク勤務 JAIST博士前期課程社会人コースの社会人大学院生(2023年4月から) となります。 もともと大学時代では別分野を専攻していたということもあり、業務を進める上で情報科学の知識や研究経験が十分でないことに課題を感じていた私は、妻の後押しもあり、2022年2月頃に社会人大学院への進学を決意しました。研究室の先生や前職の同僚の助けもあり、無事にJAISTに入学することができました。 ただ、入学することよりも修了することの方が何倍も大変であり、そのためには、育児と仕事をこなしつつ、日々の学習や研究を進める必要がありました。 日々の学習・研究時間の確保 社会人大学院生は、通常の大学院生に比べて圧倒的に時間が足りなくなります。それに加え、育児によりさらに時間がなくなります。そのため、どうにか工夫をして時間を捻出する必要がありました。 時間を確保するアプローチとして、個人的には以下をとっています。 育児との折り合いをつけ、時間を確保する 日々の学習や研究を効率的に進める 育児との折り合い 基本方針としては、子供が寝ている間に学習・研究時間の確保が可能です。そのため、 朝5時に起き、子供が起きるまで集中的に時間を確保する 子供の寝かしつけが終わってからの時間を確保する ができます。私の場合は、早朝の 5:00~7:00 と、夜の 21:00 ~ 22:30 のあたりを確保しています。ただ、子供が6:30ぐらいに起きることもあるので、そのときは早めに切り上げたりしています。 パートナーや家族のサポートの重要性 あとは、週末や休日の仕事がないとき、妻に子供を見てもらっている時間に、学習・研究時間を確保します。ただし、ここについては家庭内で十分コミュニケーションを取り、妻の理解を得ることが必要です。また、夫婦間でバランスが崩れないように調整することも必要です。 自分が子供を見る時間は、一緒に公園や近所の公民館に行き、妻のフリーの時間をなるべく確保できるように努力しています。最近は歩けるようになって、すごく楽しそう! 日々の学習や研究の効率化 時間を確保するアプローチの2つ目として挙げた、「日々の学習や研究を効率的に進める」を実施するために、 効率的な進め方に関する文献を調査し、実践する テクノロジーを活用したツールに惜しみなく投資する を行っています。 効率的な勉強法や研究の進め方の文献 効率的な勉強法や研究の進め方ってそもそもどうやんの?というところを現役の学生になったつもりで再度勉強しました。参考になった本としては、以下の本があります。 「勉強法のベストセラー100冊」のポイントを1冊にまとめてみた。 研究の育て方: ゴールとプロセスの「見える化」 卒論・修論研究の攻略本:有意義な研究室生活を送るための実践ガイド 学習については、「(1)こまめに復習をする と (2) 覚えたことをアウトプットする(口に出す、空で説明する、書く)」を大事にしています。あとは、時間を決めて集中できるようにして取り組むなど。 一年目は講義に集中していたため研究はあまり進んでいないですが、先程挙げた本を参考に研究の効率化も進めればと考えています。 社会人の特権のツールへの投資 社会人と現役の学生の最大の違いは時間とお金だと思っており、社会人は時間がない分、仕事で稼いだお金を使って自身の学習への投資が可能になります。 私のケースだと、論文読みやノートのために iPad Air を購入しました。便利なアプリとしては以下を利用しています。 Good Note 6(電子ノート) Paperpile(論文管理ツール) Kindle(本) これらのツールを用いることで、ノートや本、論文を持ち歩かなくても、どこでも学習や論文読みが可能になります! Good Note 6 は、空白のノートだけでなく講義のスライドも読み込めるので、デジタル上でスライドに書き込めるところがとても便利です。 Paperpile は論文管理ツールですが、講義の Class Note や PDF形式の教科書として読み込んでも便利です。例えば、目次や式の番号をパースしてくれて、リンクとして飛べるようになっているので、読む効率が上がります。自分の認識している範囲だと、Good Note 6にはこの機能がないので、使い分けています。ペンやマーカーなどの書き込む機能については、Good Note 6の方が自由度が高いですね。 ストレス管理と心の健康 日々の限られた時間の中で、仕事と育児、それから社会人大学院とたくさんやることがあり、生活に余裕がなくなってしまい、知らないうちにストレスを溜めてしまうことがありました。それが原因で、眠れなかったり、いらいらしたりしてしまうこともありました。心の健康を保つためにもストレス管理に注意する必要があります。 私が気をつけるようにしていることは、 1,2 時間ぐらい机で作業した後は、10分ぐらいふらっと散歩したりストレッチしたりする。 頭が疲れて回らない場合は、ぼーっと10分ぐらい何もしない、何も考えない(瞑想といえば瞑想)。 眠いときは遠慮せずに寝る 定期的に家族内や友達のイベントを作って、全力で楽しむ ジムに行ったり、ジョギングをしたりなど、定期的な運動の習慣を作る 1日の最後は夫婦でおしゃべりをして、リラックスする です。 また、学習や研究がどうしても予定通りにいかないことがあります。子供が熱で体調を崩したり、急遽別の予定が入ったりなど。また、自分のタイムマネジメントがうまくいかず、思ったように学習や研究が進まないこともあります。 そのようなときは一旦無理なスケジュールを引いている状態を認識して、緊急度と重要度のマトリクスを作成し、優先度の低いものからは撤退するようにしています。やっぱり無理なものは無理なので。 タイミーという最適な環境 さて、これまでは育児や大学院での過ごし方などプライベートに関する話がほとんどでしたが、冒頭にも書いた通り、タイミーでは育児や自己研鑽への支援制度があり、私のような技術職には大変ありがたい環境となっています。 育児への支援制度 タイミーでは産休・育休とは別に以下の制度があります。 出産休暇:配偶者が出産する際、産休・育休とは別に、出産当日までの間で3日間の特別休暇を付与(有給) 子の看護休:子どもの看護を目的に、子ども1人につき年間5日付与(子ども2人以上は10日が上限) corp.timee.co.jp 自己研鑽への支援制度 また、タイミーではエンジニアの市場価値向上につながる成長支援、学習支援、機会提供、生産性向上に資する取り組みも実施しています。そのための組織として DevEnable 室を設立し、「TDE10(Timee Dev Enable)」という、エンジニアの進化のための10の施策が進められています。 DSグループでの働き方 DSグループでは、フルリモート勤務も可能であり、フレックスタイム制(コアタイム12:00~16:00/所定労働時間8時間00分、休憩60分)であるため、個人の状況にあわせて働き方を調整することができます。実際に、保育園に通っているお子さんがいる社員は、保育園へのお迎えの時間にあわせて就業時間を調整していたりします。 まとめ 今回の事例では、私のケースが育児と社会人大学院の両立というレアなケースでしたが、タイミーでは働きながら育児や自己学習に取り組む社員がたくさんいます。当社は多様な働き方が可能な職場環境を提供し、柔軟でサポートが充実している条件のもと、異なるバックグラウンドやライフスタイルを持つ方達が活躍しています。 現在、タイミーではデータサイエンティストやエンジニアなど、一緒に働く新しいメンバーを積極的に募集しています! また、気軽な雰囲気での カジュアル面談 も随時行っておりますので、ぜひお気軽にエントリーしてください。↓ hrmos.co hrmos.co
自己紹介 部署紹介 一日の流れ 朝のルーチン: 午前中の仕事: 9:00~11:00 フォーカス課題の分析 11:00~11:30 データアナリスト全体のデイリーミーティング 11:30~12:00 他のメンバーのクエリレビュー 昼休憩: 午後の仕事: 13:00~13:30 依頼タスクのヒアリング 13:30~14:30 依頼タスクの対応、納品 14:30~16:30 フォーカス課題の分析 16:30~17:00 定例ミーティング(部署連携の持続PJT) 17:00~18:00 採用面接 18時から中抜け(あるいはそのまま退勤): 20~21時以後: 20:00~20:30 面接の議事録完成、評価の完了 勤務体系 We’re Hiring! 自己紹介 こんにちは、タイミーのデータアナリストチームの一員、胡です。 私の日本でのキャリアは約10年前、早稲田大学院でMBA取得を目指して来日したことから始まりました。 学位取得後、開発ベンダー企業でエンジニアとしてキャリアをスタートし、その後中小企業でシステム統括の責任者、製造業のベンチャー企業での経験を積みました。 これらの職場で、データに関する多くの課題に直面し、自らの手で社内のデータ環境を整備することもありました。 この経験が、データアナリストとしてのスキルを磨く大きな契機となり、最終的にタイミーでのキャリアをスタートさせるに至りました。 プライベートでは、2歳になる娘を持つ三人家族の一員として、忙しい日々を過ごしています。 最近では、趣味と言えばすっかり子育てに夢中になっています。子育ては日々の新しい発見と喜びに満ちており、私の人生にとって大切な部分です。 家庭と仕事のバランスを保ちながら、日々成長を続けていけたらと思います。 タイミーで働くことで、充実したキャリアを築きつつ、家族との時間も大切にすることも可能です。 部署紹介 タイミーは、急速に成長を遂げているベンチャー企業です。その成長の基盤の一つが、私たちデータ統括部です。 データアナリストは、現場のチームと協力しながら、実際の業務に即した分析を行っています。また、経営層との密接な連携を通じて、経営に関する直接的な提案を行うことも可能です。 私はプロダクトアナリティクスチームに所属し、プロダクトの改善と向上に日々貢献しています。同じ部署での同僚であるtakahideさんの記事も参考になるでしょう。> takahideさんの記事はこちら 今日は、タイミーのデータアナリストとしての一日を紹介し、私たちの業務の流れを具体的にお伝えします。それでは、さっそく始めていきましょう。 一日の流れ 朝のルーチン : 朝は7時頃に起きます。家族との朝食は一日の始まりの大切なひととき。 娘を保育園に送り出した後、9時前後に自宅の仕事スペースへと移動し、一日の業務をスタートします。 仕事を始める前に、短い時間を使ってメールのチェックや一日のスケジュールを見直します。 午前中の仕事 : 9:00~11:00 フォーカス課題の分析 直近のフォーカスしている課題として、時給と勤務状況の関係性に関する分析です。 こちらは、プロダクトマーケティング(PMM)部と連携しながら進めており、得られたデータから新たなビジネス洞察を得ることが目標です。 フォーカス課題は、担当する部署の目標から派生した課題や、日々の業務から得た気付きに基づく自由研究も含まれます。 進め方としては、議論を重ねて仮説を立てて、それを検証していくサイクルを回します。この過程に置いて、PMM部との協力は非常に重要です。彼らの市場に対する深い洞察と私たちのデータ分析能力が相乗効果を生み出すことで、最終的には、この分析がプロダクト機能の拡張という形で実現されることを目指しています。 11:00~11:30 データアナリスト全体のデイリーミーティング この時間帯は、データアナリストチーム全体でのデイリーミーティングを行います。 ここでは、チーム全員で共通の連絡事項を共有し、データアナリスト部向けの依頼タスクの対応方法について話し合います。通常、このミーティングは30分間設定されていますが、内容によっては早く終わることもありますし、新しいアイデアや施策につながる議論が盛り上がることもあります。 依頼タスクについて少し詳しく説明します。現在、私たちのチームはマーケティング、ビジネス、プロダクトといったユニットごとに分かれており、各ユニットは通常、特定の課題に集中しています。しかし、全社からのデータ抽出依頼が多数寄せられるため、これらに対応するにはチーム全体でリソースを効率的に調整する必要があります。 Lookerの導入と普及により、多くのデータ抽出は各部署が自力で行えるようになりましたが、複雑なデータ集計やLookerで対応できない項目がある場合は、依然として私たちのチームへ依頼が来ます。 これらのタスクは専門性を要求されることは少ないですが、緊急性が高く迅速な対応が求められます。メンバー一人ひとりがこれらのタスクに取り組む頻度は、週に1回くらいです。 11:30~12:00 他のメンバーのクエリレビュー 一般的に、私たちの日常的な課題や依頼タスクの成果物は、データを抽出した後、そのまま提出して完了となります。 しかし、社外向けの資料や財務に関わるデータなど、特に正確性が重要視される場合があります。こうした場合には、原則としてレビュワーを割り当てて、二重チェックの体制を取ります。 この二重チェックプロセスは、データの品質を保証し、エラーや誤解を防ぐために不可欠です。レビューは、データの正確性だけでなく、使用される分析方法や導かれた結論の妥当性についても検証します。 これにより、提供するデータと情報の信頼性を向上させ、私たちのデータアナリスト業務の品質を高めることができます。 昼休憩 : 昼休憩の時間は、日によって多少前後することがありますが、基本的には冷蔵庫にある食材を使って手早く料理を作り、食事をします。 時間が許せば、10〜15分の短い仮眠をとることもあります。 午後の仕事 : 13:00~13:30 依頼タスクのヒアリング 午前中にアサインされた依頼タスクの取り組みに先立ち、依頼者へのヒアリングを行います。依頼タスクの要件は通常、事前に依頼者によって文書化されていますが、より詳細な情報が必要な場合はさらなる確認が行われます。 このステップは必ず行うわけではなく、要件が明確に定義された場合は、成果物を作成した後に依頼者とイメージをすり合わせたり、結果を直接チャットで伝えたりすることもあります。 使い道の確認 抽出データがどの程度の規模で、どれくらいの期間使用されるかを確認し、それに基づいて適切な出力形式を提案します。また、抽出データが他の目的にも汎用的に使用できる場合、今後の依頼タスクでの流用の可能性を検討して作成していきます。 項目の定義のすり合わせ 項目によっては、部署ごとに基準が異なることがあります。そのため、集計条件や項目の定義を明確にしておきます。 抽出項目の提案 時に依頼者がデータに関するリテラシーが低い場合や、要件が十分に記載されていない場合があります。このような状況では、実際に求められている内容を詳しくヒアリングし、最適な出力方法をアドバイスします。 13:30~14:30 依頼タスクの対応、納品 先ほどヒアリングで得た結論に基づいて早速作業に取り組みます。タスクの難易度にはばらつきがあるため、完了までの時間はさまざまです。通常は30分から60分で完了するタスクもありますが、依頼者との段階的なやりとりが必要な場合は、全体として数時間を要することもあります。 14:30~16:30 フォーカス課題の分析 午前中に取り組んでいたフォーカス課題の分析作業を続けます。この作業には、集中を要するため、中断されることなく継続的に取り組むことが重要です。そのため、あらかじめスケジュールをブロックしておくことで、分析に専念する時間を確保できます。 16:30~17:00 定例ミーティング(部署連携の持続PJT) データ統括部以外の部署と協力して進めている持続的なプロジェクトの定例ミーティングを行います。プロジェクトの性質によって、その期間は通常数週間から数ヶ月に及び、状況に応じて定例ミーティング以外の方法での連携も行われることがあります。 この定例ミーティングでは、私が社内でリリースした営業用ダッシュボードの利用状況と改善点に重点を置いています。このダッシュボードは社外向けで、営業活動における重要なツールとして利用されています。現在、社内で最も頻繁に参照されるダッシュボードの一つであり、営業効率の向上と営業レベルの平準化に大きく寄与しています。 17:00~18:00 採用面接 急速に拡大しているタイミーでは、新たな才能とスキルを持つ仲間を増やすことが非常に重要です。 面接では、候補者の技術的能力やチームとの相性、将来のポテンシャルを評価します。これには、専門的なスキルだけでなく、組織文化への適合性やコラボレーション能力も含まれます。私たちは、優秀な人材を見極め、チームと会社全体の成長を支える人材を採用することを目指しています。 18時から中抜け(あるいはそのまま退勤) : 一日の仕事を終えた後の夕方は、家族との大切な時間です。 最初に保育園に娘を迎えに行き、その後は家で夕食の準備に取り掛かります。家族みんなで食事を共にし、その後は子供と一緒に楽しいバスタイムを過ごします。 毎晩ではないものの、娘が就寝する時間が近づくと、その準備は妻が担当し、私はその間に追加の仕事に取り組むことがあります。 20~21時以後 : 20:00~20:30 面接の議事録完成、評価の完了 行われた面接に関する議事録のまとめや、面接評価を行います。 また、この時間を使って追加分析やタスクの整理を行うこともあります。あるいは、そのまま切り上げてプライベートで映画や読書などに充てることもあります。 こちらは私のある一日の過ごし方です。タイミーでデータアナリストとして働く中で、仕事とプライベートのバランスを保ちつつ、様々な課題に取り組む毎日を送っています。 いかがでしょうか。少しでもタイミーでのデータアナリストとしての仕事のイメージをつけたら幸いです。 勤務体系 データ統括部では、フルリモートワークが可能であり、始業時間も柔軟に設定できます。これにより、私たちは自分のライフスタイルに合わせた働き方を選ぶことができます。 私の場合、育児のため、18時には仕事を切り上げることができます。これにより、家族との時間を大切にしながら、効率的に業務を進めることが可能になっています。 さらに、最近は娘が月に一度程度熱を出すことがあり、その都度スケジュールを調整したり、同僚からのサポートを受けたりしています。タイミーでは、このような状況にも柔軟に対応しやすい環境が整っています。 We’re Hiring! 私たちは、ともに働くメンバーを募集しています!! product-recruit.timee.co.jp
タイミーのyama_sitter、須貝、小林です。 国内最大級の アジャイル 、 スクラム 関連のイベント「Regional Scrum Gathering Tokyo 2024(RSGT2024)」 が1/10〜1/12の3日間にわたって開催されました。 2024.scrumgatheringtokyo.org タイミーには世界中で開催されるすべての技術系カンファレンスに無制限で参加できる「Kaigi Pass」という制度があります。 productpr.timee.co.jp こちらの制度を利用して今年は スクラム マスター3名、エンジニア3名が参加してきました。 前編 に続き、後編の今回はエンジニア3名から参加レポートをお送りします。 SM/EMからエンジニアに視点を変えて参加 エンジニアの yama_sitter です。 参加は今年で2回目になります。前回は前職からSM/EMの立場として参加しましたが、今回はタイミーの現場エンジニアという立場で参加する形となりました。 「エンジニアからSMになり、EMを経てエンジニアに戻った人」という視点で、特に印象に残ったセッションが3つありましたので紹介します。 Badプラクティスを選んで失敗しながら進めた新規プロダクト開発 自分はSM時代に「形」に捕らわれ過ぎて色々な失敗を重ねてきました。一方で今所属するチームではいわゆる スクラム の形式では仕事をしておらず、それでもちゃんと成果が出せている状態です(※1)。これらの経験から、この1年ほどは「 アジャイル は形じゃない」というのを自分なりに理解し、また考え続けていました。(この間、世に出回る「べき論」や「プ ラク ティス」みたいなものをとにかく疑ってかかっていました) そんな中見たのがこの「Badプ ラク ティスを選んで~」のセッションです。ここでは世間的にはNGとされるプ ラク ティスを敢えて用い、(もちろん苦しみながらも)ちゃんと成果を出した事例が語られていました。Badと言われるものだと分かった上で、そのリスクを理解し取り入れ、結果に結び付ける。このあり方は教科書的でこそないものの、ちゃんとした一つの解だなぁ、と。 つまるところ「価値に向き合い、自分たちが考えうる/持ちうる最大の力で成果を生み出す」ことが重要なのであって、形ではないなという話です。今の自分が感じていることを後押しし更に前に進めてくれたこのセッションは非常に有意義なものでした。 こちらはスライドもあるので是非ご一読を。 speakerdeck.com ※1 あくまで会社全体としては スクラム に力を入れており、自チームは特殊なケースです Lack of curiosity: The silent killer of agile 「好奇心の無さはアジリティを殺す」という話です。内容は勿論ですが、特にこのタイトルが強く印象に残っています。 入社して半年、以前考えていた抽象的な問いからは離れて今はひたすらに具象と向き合っています。入社直後に感じていた様々な疑問も少しずつ薄れてきました。アウトプットこそ出せているとは思います。が、少しずつ「疑問に思う力」や「ワクワクする力」がマヒしていっているのを感じています。 これだと、本当に向き合うべき課題に気付き向き合うことができないんですよね。セッションでも述べられていましたが、「人間はルーチンが好き」なので尚更です。 自分や目の前のコードとひたすら向き合うだけでは高い視点からは物事を考えられません。疑問に思わなければ脳が刺激を受けずに鈍化していきます。ワクワクしていなければ途中で力尽きます。結果として気付ける課題、向き合う課題がすごくミニマムになっていく。 現場にフォーカスするだけだと、巡り巡って良いエンジニアにはなれないなぁ、ちゃんと好奇心を持ち続ける努力をしないとなぁ、と感じさせるセッションでした。 Solving The Value Equation 「レガシーコード改善ガイド」の著者であるMichael Feathersさんのキーノートです。 めちゃくちゃためになる話ばかりでしたが、個人的に刺さったのは「プロセス、 アーキテクチャ 、チームの能力、組織構造といった各レイヤーの目標は、 “価値に至るまでのプロキシ” でしかない」という話です。 “プロキシ” というのが特に良くて、これを意識すると「今の取り組みとその結果は、必ず何らかの価値に転換されるために存在する」という考え方ができます。当然のことではあるんですが、目の前のことにフォーカスしたり「べき論」に囚われたりすると忘れがちだなぁ…と思ったりします。 余談ですが、前職のSM最初期の頃は「それっぽいメトリクス」を見つけては右往左往していました。あれはあれで必要なプロセスだったものの、できればこの “プロキシ” の考え方を持っておきたかったです。 視点を変えての参加となりましたが、選ぶセッションも見るスタンスも感じることも得られるものもかなり違うし、得るものの幅が増えていることに気付いて面白かったです。一度全力でSMをやってからエンジニアに戻ると面白いですね。 とまぁここまで色々書きましたが、正直セッションよりも「素晴らしいセッションを見たあとに同じ熱量を持ったタイミーメンバーと集まり、酒を飲みながら議論する」時間が一番良かったです。こういった時間のために行っているフシすらある。 総じて、RSGTは今年も良いイベントでした。企画/運営の皆さま、ありがとうございます。 メンバーと自分たちの組織について語り合うきっかけに エンジニアの 須貝 (CSM保持)です。 RSGTは今回が初参加でして熾烈なオンサイトチケットの争奪戦を勝ち抜いて現地に行ってまいりました。自分は現在はマネージャーでも スクラム マスターでも無いのですが、以前からチーム・組織で成果を出すにはどうすれば良いかといったテーマに興味があったので参加しました。 基本はセッションを聞くことを中心に、合間の休憩時間などで知り合いの方とお話をして過ごしました。中でも印象に残ったセッションのひとつはJoe Justiceさんの「 ジョーが語る、Teslaでの衝撃的な開発スピード 」です。電気自動車メーカーTeslaの開発サイクルのあまりの速さに本当に自動車の開発なのか!?と驚きの連続でした。と同時にハードでこのスピードでできているのだからソフトウェア開発をしている自分たちももっとやれるのでは、勝手にできないと思っているだけなのでは、という反省もありました。 オフトピックなところだと会場内でプロのカメラマンに写真を撮ってもらえるフォトブースがあったのが良かったです。Global Scrum Gathering Amsterdamで行われていたものが日本にも輸入されたそうです。 プロのカメラマンに撮影されるりっきーさんとShinoPさん 自分たちは普段フルリモートなので、弊社のメンバーと自分たちの組織などについて対面でじっくり話せたのも非常に良い体験でした。チャンスがあればまた参加したいと思います。 RSGTは気づきと学びの場 こんにちは!タイミーでQA スペシャ リストを担当している小林依光です。 RSGTはオンラインで参加しましたが、今回も「やっぱりそうだったんだ」と自分の知識や経験を後押ししてくれる発表内容に、感謝の気持ちでいっぱいになりました。 事例/実績、考え方の共有は気づきと学びの場だと、改めて思いました。 少しですが、私が気になったセッションについて、紹介したいと思います。 「 ベロシティ Deep Dive 」ではベロシティを生産性に活用することをやめましょう!ということを多様な角度から説明していました。どの角度からの説明も納得感のあるものばかりでしたが、一番印象的だったのが、“ アジャイル や スクラム が目指すのは価値の実現であり、この文脈での生産性は「付加価値生産」である”という説明でした。 アジャイル を採用し探索的/実験的にプロダクトを開発し価値を提供するという基本を忘れてはいけないと学べました。 「 プロダクトをあきらめるとき 」のセッションは一般的な スクラム の話ではありませんでしたが、実際のプロダクト開発で遭遇する実例でした。特に“プロダクトを終了させるときの費用”をプロダクト バックログ の順位の下の方に入れておくという考えには共感でき、あきらめる判断やサービス撤退に必要な洗い出しは、プロダクトを止めるときでなく開発を始める際に決めておく必要があると何度か痛感していたので、改めていままでの経験は役に立つことがあると思いました。 紹介したセッション以外からも多くを学べたRSGT、来年は現地で参加できるようにしたいと思いました。 まとめ いかがでしたでしょうか。 参加して満足して終了では意味がない!ということで後日参加者全員で振り返り(Fun Done Learn)も行いました。しっかりと ネクス トアクションも生まれたので、ここからチーム・組織に今回得た学びを還元していければと思います。 来年もまたGatheringしましょう!
タイミーのmaho、ShinoP、りっきーです。 国内最大級の アジャイル 、 スクラム 関連のイベント「Regional Scrum Gathering Tokyo 2024(RSGT2024)」 が1/10〜1/12の3日間にわたって開催されました。 2024.scrumgatheringtokyo.org タイミーには世界中で開催されるすべての技術系カンファレンスに無制限で参加できる「Kaigi Pass」という制度があります。 productpr.timee.co.jp こちらの制度を利用して今年は スクラム マスター3名、エンジニア3名が参加してきました。 その参加レポートとして印象に残ったセッション、ワークショップや学びなどを参加者それぞれの視点で共有できればと思います。今回は前後編の前編として スクラム マスター3名のレポートをお送りします! 初参加で OST のホストに挑戦 スクラム マスターをしているmahoです。RSGTは今回が初参加で緊張していましたが、一緒に現地参加するメンバーがいたので心強かったです。 私はまだまだ スクラム マスター初心者ということもあり、セッションを聴いて学びを深めることを一番の目的にしていました。どのセッションも興味深い内容でしたが、全体を通して感じた共通点は以下の三つです。 チームの基盤を支える重要な柱のひとつは関係性であること 変化は時間がかかることを理解して忍耐を持つこと システム全体を捉えるよう努めること 私もこれが体現できるように、学びを実践しながら精進していきたいと思います。 また、三日目の OST には話したいテーマを発表し、ホストとしての参加に挑戦してみました。不安でいっぱいの中、いざ始まってみると集まってくださった方々と予想以上に議論が盛り上がり、とても楽しい時間を過ごすことができました。セッションを聴くだけでなく、Gatheringとしてもよい体験ができたので、もし次回も参加することになれば、より積極的に話す機会を作ってみたいと思いました。 どう社内に持ち帰るかを話す時間は何事にも変え難い タイミーで スクラム マスターをやっているりっきーと申します。昨年に引き続き2回目の現地参加になります。 私は毎年年末にテーマを決めていまして、1年かけて達成したかどうかを振り返っています。 その中で、RSGTは年初一発目ということで年末に決めたテーマについて知見を得たり、ディスカッションをできるので非常に助かっています。 私の本年度のテーマは「 スクラム マスターとして成長できる・やってみたくなる環境を作る」です! テーマに最も関連していたので以下を紹介いたします。 「 スクラムマスターを職能にする挑戦 - 健全なチームを増やし組織をチームワークであふれさせる道のり 」 スクラム マスターをどのように増やしていったか、どのような仕組みを作っていったかは大変参考になり、勇気がもらえました! 1on1や コーチン グ、メンタリング活動は弊社としても取り組み始めていたのですが、給与レンジの検討や成長支援の予算などは検討できてない部分なので弊社ならではを組み合わせつつ、一つの事例として社内に紹介したいと思います。 また、メンターとしては初心者の方もRSGTで楽しめるように社外の人との会話に入ってもらったり、 OST に参加するように促したりを 裏目 標として持っていましたが、楽しめたようで何よりでした! 皆さんも書いてますが、同じものを見て聞いてそれについてどのように社内に持ち帰るかを話す時間は何事にも変え難いので、来年も参加したいなと思いました。(オフライン参加は難しいかもしれませんが) RSGTのオススメの歩き方 こんにちは!タイミーで スクラム マスターをしている ShinoP です! 今回のRSGTは現地参加としては2回目なのですが、前回はセッションを聴くことに夢中になっていて、本当の意味でのGatheringができませんでした。 その中でも OST のホストをやった事で、参加者同士の触れ合いを体験しGatheringの本当の意味を理解しました。 ですので、セッションに関しての感想というよりは、RSGTのオススメの歩き方を紹介したいと思います! 今回のRSGTでの立ち回りはセッションを聴くだけでなく… 「廊下で初めて会った人と話す」 「お昼に社外の人と話す」 「研修やコミュニ ティー で面識のある人と話す」 「登壇者と話す」 「飲み会に参加する」 「お昼の30秒宣伝コーナーで話す」 などを積極的に行った結果、同じような悩みを抱えている方と話すことによってヒントを得られたり、登壇者の方と話してアド バイス をもらったり、面識のある方々とワイワイできたり…このような歩き方も学びがあるので是非試してみてください! また、RSGTや スクラム フェスは人に話しかけやすい構造になっていると思いますので、隣に座っている人とかに話しかけたりすると繋がりが広がっていくと思っていますので、是非思い切って話しかける事がオススメです! 今回のレポートは以上となります。 続きの後編は「エンジニア編」として近日公開予定です。
こちらは Timee Advent Calendar 2023 シリーズ1の25日目の記事になります。 昨日は @tomoyuki_HAYAKAWA による Swift Concurrency AsyncStreamを使ってみる #Swift - Qiita でした。 タイミーでバックエンドエンジニアをしている id:euglena1215 です。 メリークリスマス🎄 みなさんの手元にはプレゼントは届いているでしょうか。 Ruby の世界では Ruby コミッターサンタさんがクリスマスプレゼントとして新しい Ruby バージョンをリリースしてくれます。 今年は Ruby 3.3 ですね。個人的には 3.3 の YJIT がどれだけ速くなるのか楽しみです。 また、新しいバージョンのリリースにはアップグレードがつきものです。アップグレードせずには新しいバージョンの恩恵を受けることはできません。 ということで、今回はタイミーが社内で運用している社内版 Ruby Rails アップグレー ドガ イドを社外に公開します。 社内版の情報を黒塗りしては社内版を公開する意味がありません。そのため、最大限 原文ママ でお送りします。前提として足りない部分は「記事用補足」と追記しています。 社内版 Rails アップグレー ドガ イド このドキュメントは Rails アップグレードのために普段からやること、実際のアップグレードの際にやることをまとめたものです。基本的には一般的な Rails アップグレー ドガ イドと同じですが、QAチェックの必要性など社内特有の事情も考慮されています。 下記のドキュメントは Rails のメジャーバージョン・マイナーバージョンを上げる際に意識すべきことです。パッチバージョンは普段のライブラリアップデート同様に上げてもらって構いません。 🟥 MUST: 安全なアップグレードのために必ず実施しなければいけません。 🟨 SHOULD: 必ず実施しなくてもいいですが、やっておくことを推奨します。 🟩 MAY: 行わなくてもアップグレードは実施できます。関心がある方だけで構いません。 以下の作業は基本的に並列かつ複数人で実施可能です。 社内版 Rails アップグレードガイド 普段やること Rails Edge のキャッチアップ(🟩 MAY) Rails Edge 起因で失敗しているテストの修正(🟨 SHOULD) Rails へのコントリビュート(🟩 MAY) アップグレード前にやること アップグレード先のCIを用意する(🟨 SHOULD) Railsバージョン起因でスキップしているテストをなくす(🟥 MUST) deprecation warning をなくす(🟨 SHOULD) rails app:update の実行(🟥 MUST) Rails アップグレードガイドのバージョンに対応した変更を読んで問題ないことを確認(🟥 MUST) リリース前の手動での動作確認(🟥 MUST) アップグレード作業中に行うこと アップグレードの事前周知(🟥 MUST) rollback 用のコミットハッシュを取得する(🟥 MUST) Revert PR を作成する(🟩 MAY) 各種監視を行う(🟥 MUST) アップグレード後にやること new_framework_defaults_x_y.rbの有効化(🟨 SHOULD) config.load_defaultsの更新(🟨 SHOULD) 参考 普段やること Rails Edge のキャッチアップ(🟩 MAY) #guild_rails_edge には毎日 rails / rails の main branch にどんな変更がマージされたのかが流れてきます。ここで次のアップデートでどんな変更が入るのかをチェックしましょう。日頃からチェックしておくとアップデート時に情報の波に飲まれにくくなります。 (記事用補足:ここで流しているのは y-yagi さんのブログです) y-yagi.hatenablog.com Rails Edge 起因で失敗しているテストの修正(🟨 SHOULD) 通常の Rails バージョンでは動作するが Rails Edge で動かないテストは pending: pending_if_rails_edge をつけて pending してあります。 grep して pending しているテストを見つけて問題を特定し修正しましょう。 (記事用補足:タイミーでは CI で Rails Edge を使ったテストを実行しています。詳しくは以下) tech.timee.co.jp Rails へのコントリビュート(🟩 MAY) 「 Rails Edge 起因で失敗しているテストの修正」の大半はアプリケーションコードに起因する問題であることが多いですが、たまに Rails 本体のバグに遭遇することもあります。その際は Rails にバグレポートや修正 PR を送ることを推奨しています。 リリース済みのバージョンの仕様を変えるのは難しいですが、betaやrc版ではissueやPRを送ることで仕様を変えられる可能性があります。意図しない破壊的変更を見かけた場合は積極的にやりとりしましょう。 参考: Get started with OSS contributions - Speaker Deck アップグレード前にやること アップグレード先のCIを用意する(🟨 SHOULD) アップグレードを検討している頃には既にアップグレード先のバージョンがリリースされているため、 Rails Edge はその次のバージョンになっていることが多いです。そのため、 Rails Edge CI ではアップグレード先のバージョンでのテストは実行できません。 アップグレード先の Rails バージョンでの CI を用意しましょう。そうすることで、アップグレード先バージョンでのテストが普段から実行できるようになるので問題を早期発見できます。 参考 (ここに Rails 7.1 の CI を用意した Pull Request URL が貼ってありました) Rails バージョン起因でスキップしているテストをなくす(🟥 MUST) Rails バージョン起因でスキップしているテストはアップグレード後に動かなくなるテストです。このテストを放置したままアップグレードを実施すると該当機能が動かなくなるので必ずスキップしているテストがないことを確認しましょう。 「 Rails Edge 起因で失敗しているテストの修正」を実施していればスキップしているテストはほとんどなくなっているはずなので、このステップの対応が楽になります。 参考 (ここに Rails バージョン起因でスキップしているテストを修正している Pull Request URL が貼ってありました) deprecation warning をなくす(🟨 SHOULD) ほとんどの問題は「 Rails バージョン起因でスキップしているテストをなくす」によって解消されますが、テスト環境では捉えきれない問題も存在します。それらの問題を洗い出すために、タイミーでは本番環境で発生した deprecation warning をログとして出力するようにしています。出力されたログは Datadog Logs で確認できます。 また、ログが出力されすぎて Datadog のコストを圧迫することを防ぐためにログ出力は確率的としています。deprecation warning が減ってきたら合わせて確率を100%に近づけ漏れなくキャッチできるようにすることを推奨します。 rails app:update の実行(🟥 MUST) Rails では app:update というコマンドが提供されています。 Gemfile に記載されている Rails のバージョンを更新後、このコマンドを実行することで、新しいバージョンでのファイル作成や既存ファイルの変更を対話形式で行うことができます。 $ bin/rails app:update exist config conflict config/application.rb Overwrite /myapp/config/application.rb? (enter "h" for help) [Ynaqdh] force config/application.rb create config/initializers/new_framework_defaults_7_0.rb ... 予期しなかった変更が発生した場合は、必ず差分を十分チェックしてください。 https://railsguides.jp/upgrading_ruby_on_rails.html#アップデートタスク デフォルトに合わせていた方が今後のアップグレードが楽になるため、既存の挙動をなるべく変化させないように更新分を取り込みましょう。 変更差分を知りたい場合は https://railsdiff.org/ から確認できます。 Rails アップグレー ドガ イドのバージョンに対応した変更を読んで問題ないことを確認(🟥 MUST) 公式の Rails アップグレー ドガ イドにはメジャーバージョン・マイナーバージョンのアップグレードに対して破壊的変更など何らかの対応が必要な変更がまとまっています。これらを確認して問題がないことを確認してください。懸念がある場合は #prd_ch_ruby_on_rails で相談するなど適切な対応を行なってください。 例:7.0→7.1 Rails アップグレードガイド - Railsガイド 参考 (ここに確認したログをまとめた Notion リンクがありました) リリース前の手動での動作確認(🟥 MUST) リリース前の手動での動作確認は必須ですが、社内向け管理画面の最低限の疎通確認のみで問題ありません。 背景: (ここに意思決定の証跡として MTG 議事録リンクが貼ってありました) また、社内向け管理画面においても十分なテスト カバレッジ があれば動作確認を省略することができます。社内向け管理画面のテスト カバレッジ を上げるプロジェクトについては(Notion URL が貼ってありました)を確認してください。 (記事用補足:プロジェクトの Notion URL よりもTimee Advent Calendar 2023シリーズ1の5日目記事の方がより詳細にプロジェクトの説明をしています。興味があればご覧ください。) tech.timee.co.jp アップグレード作業中に行うこと アップグレードの事前周知(🟥 MUST) Rails アップグレードのリリース直後に rollback が困難な変更が行われると Rails アップグレード起因で問題が発生した際に rollback が困難になり、問題の復旧が遅れます。そのため、アップグレードを事前に周知しアップグレード後30分間はデプロイ(=マージ)を行なわないよう呼びかけてください。 rollback 用のコミットハッシュを取得する(🟥 MUST) 問題があった際の復旧を早めるために rollback で戻す先のコミットハッシュを用意しておきましょう。 https://github.com/x/x/commits/master から確認できます。 Revert PR を作成する(🟩 MAY) 問題があった際の復旧を早めるために merge 後すぐに Revert PR を作成しておくことを推奨します。先に Revert PR を作っておけば feature branch での CI 待ちをショートカットすることができます。 問題が発生せずに無事リリースできた場合はきちんと PR を close しておきましょう。 各種監視を行う(🟥 MUST) リリース直後は問題を早期発見するために各種の監視を行なってください。15分様子を見て問題が見つからなければ急いでrollbackする必要はありません。 Sentry 普段見かけないエラーが発生していないかどうか Datadog ダッシュ ボード 大きなメトリクスの変化がないかどうか APM レイテンシやエラーレートに変化がないか (記事用補足:Sentry や Datadog など各種 URL が貼ってありましたがさすがに削除させていただきました) アップグレード後にやること new_framework_defaults_x_y.rbの有効化(🟨 SHOULD) Rails をアップグレードしただけでは多くの新しい機能は有効化されていません。 rails app:update で作成された new_framework_defaults_x_x.rb の コメントアウト を1つずつ確認してデフォルトに合わせられそうなものは有効化しデフォルトに合わせ、意図を持ってデフォルトとは異なる設定を行う場合は application.rb に設定を追記しましょう。 config.load_defaultsの更新(🟨 SHOULD) app:update タスクでは、アプリケーションを新しいデフォルト設定に1つずつアップグレードできるように、 config/initializers/new_framework_defaults_X.Y.rb ファイルが作成されます(ファイル名には Rails のバージョンが含まれます)。このファイル内のコメントを解除して、新しいデフォルト設定を有効にする必要があります。この作業は、数回のデプロイに分けて段階的に実行できます。アプリケーションを新しいデフォルト設定で動かせる準備が整ったら、このファイルを削除して config.load_defaults の値を反転できます。 https://railsguides.jp/upgrading_ruby_on_rails.html#フレームワークのデフォルトを設定する 参考 railsguides.jp qiita.com いかがでしたか? タイミーは上記の Rails アップグレー ドガ イドを用いて Rails アップグレードを行なっています。とは言ったものの、このアップグレー ドガ イドは Rails 7.1 アップグレードでやったことを体系的にまとめたもので本当に運用され始めるのは Rails 7.2 アップグレードのタイミングになります。 Rails アップグレードは属人的なタスクになりがちです。特定の人物がアップグレードの番人になるのではなく、誰でもアップグレードにチャレンジできるようにしたかったのがアップグレー ドガ イドを作成した背景になります。 ネットには質の高い Rails アップグレー ドガ イドがたくさん存在します。ですが、どのステップをどんな期待値で実施するか・動作確認はどこまでやるかは各社の状況に依ると思います。社内の暗黙値を減らすためにも社内版 Rails アップグレー ドガ イドを作ってみてはいかがでしょうか。
こんにちは!株式会社タイミーでプロダクトマネージャーをしているAndrewです。 私はオフショ アメンバー が関与するSquadに所属しています。このSquadはエンジニア組織の中でもユニークな環境であり、ここで直面した問題とそれを乗り越えるための取り組みをこの記事で紹介したいと考えています。 オフショ アメンバー とのコラボレーションの課題 約半年前、オフショ アメンバー のいるSquadと関わり始め、このSquadの各メンバーを知るきっかけとなりました。 最初は、日本メンバーとオフショ アメンバー のコミュニケーションがスムーズでないと感じました。ミーティングでは通訳担当の方以外、オフショ アメンバー 全員がほとんど発言しませんでした。 また、ミーティングで話されている内容が彼らが理解できる言語で記録されていないケースが多く、情報の透明性に欠けていました。開発した機能の目的が何かを理解しない場面を見かけたこともありました。 しかし、これは単なる課題ではなく、Squad全体のポテンシャルを引き出すためのチャンスでもあると考えました。 エンジニアは機能を作るだけでなく、プロダクトに関心を持ち、価値を届けることに注力すれば、より多くのアイディアやソリューションが生まれ、良いプロダクトが作れるのではないかと。そのためには、オフショ アメンバー が積極的にディスカッションに参加できるようにする必要がありました。 最初は言語の壁があると感じたため、心配もありましたが、様々な施策を試してみました。その一環として、ドキュメントの整理とオンライン言語交流会を実施してみました。 情報透明性を向上するためのドキュメント 「ドキュメントのメンテナンスが面倒くさい!」、「コードを読む方が早い!」と思う方は少なくないでしょう。 コードにコメントがないと、そのコードが何をしているか理解することが難しくなります。それと同様に、ドキュメントがないと、システムや機能の全体像を把握するのに時間がかかり、理想的な状態と言えません。 コードで説明できない背景や設計の意図、特定の決定の背後にある論理をコメントで説明することで、コードを読む人が理解しやすくなります。しかし、コードのコメントでわかりやすく説明できる内容はテキストベースの情報に限られているということもあり、コードは特定のメンバーしかアクセスできないため、ここでドキュメントが強力な情報共有手段になります。 ドキュメントに対する工夫 ドキュメントを書くときにわかりやすくするためにいろんなテクニックがあります。 テキストに色をつけたり、フォントサイズを変えたり、ページの余白を活かすことで視覚的なメリハリをつけることが重要です。 これにより、複雑な情報も読みやすくなり、重要な情報を強調することができ、逆に強調したくない情報を目立たなくすることもできます。デザインが好きな私にとっては、ドキュメントは良いデザインを表現することのできる優れたツールです。 わかりやすいドキュメントを書くことは簡単ではありません。いろんなドキュメントの種類の中で、エンジニア向けのものもあれば、 ステークホルダー (非エンジニア)向けのものもあるので、伝わるように読み手のバックグラウンドや知識に合わせて書くことが必要です。 難しい単語ではなく、あえてシンプルな単語を選び、物事を説明することで言語の壁をなくせるので、自分の言葉の知識を見せびらかす必要なんてありません。 このような意識をもって書けば、ドキュメントを書くことは単調な作業ではなく、実は楽しい作業なんです。 バランスがすべて 口頭でコミュニケーションが取りづらい状況では、ドキュメントは尚更重要です。 ドキュメントがあることで、メンバーはいつでも情報にアクセスでき、フィードバックや提案もしやすくなります。ただし、あまりにも詳細な内容、たとえばコード実装のレベルの情報まで書いてしまうと、細かなコードの変更が生じるたびにドキュメントを更新しないといけなくなってしまいます。 同様に、テキストだけの長い文章を書いてしまうと、ドキュメントの恩恵が受けられなくなり、わかりやすさが損なわれ、逆に効果が薄れてしまいます。適度な詳細と抽象度のバランスを取ることが大切であり、ドキュメントが管理の手助けとなるよう心がけが必要です。 活用されるドキュメント 実際にこの取り組みを導入して、Squadの振り返りミーティングで「 ドキュメンテーション をよく参照するようになって、仕事において役立った」「ドキュメントが整理されていてわかりやすい」というメンバーからの声がありました。 今後、ドキュメントを書くのは私ではなく、オフショ アメンバー 全員も同じようなケイパビリティを持たせたいので、以上に書いたポイントを彼らに教えました。 今ではオフショ アメンバー 全員がドキュメントを書くようになり、 ステークホルダー から仕様確認の問い合わせが来ても、すぐ回答できるようになりました。これにより、プロジェクト全体の透明性が向上し、知識の共有がスムーズになりました。 長期的な視点で見れば、ドキュメントはSquad全体の知識の蓄積として機能し、メンバーが増えても情報の共有の負担は軽減されます。 オンライン言語交流会 ドキュメント整理が進む中、私たちはSquadメンバーがより積極的に参加し、アイディアを共有できる環境を整えるためにもう一つの取り組みを始めました。それが、オンライン言語交流会です。 知っている人とコミュニケーションを取ることは楽ですが、知らない人となると相手の意図がどのようなものかもわからないし、ミスコミュニケーションの原因にもなります。できるだけオフショ アメンバー とのコミュニケーションを増やすことで、彼らもより気軽に話しかけてくれるだろうと期待しています。しかし、日本語もできず、英語にも自信がないメンバーとどのようにすれば交流できるのか悩んでいました。そこで、オンラインで言語交流会を試してみました。 オンラインで実施しやすいアイスブレイク いきなり何かを英語で話せと言われても難しいですよね。 そこでアイスブレイクゲームを導入し、わざわざ自分でトピックを考える必要がなく、よりカジュアルに話せる環境にしました。最初は簡単なものから始め、英語で自分のカバンに入っているものを紹介したり、謎解きをしたりしました。 カバンに入っているものを紹介する中で、お弁当の中身だったり、意外なエアコン用のリモコンの話も出てきて、会話が弾みました。謎解きをするときもトリッキーな謎が多く、あるメンバーがたくさん正解を回答できて、他のメンバーから カンニング しているだろうと冗談で言われたりしていた覚えがあります (笑)。 これにより、お互いの趣味や考え方を知ることができ、メンバー同士のコミュニケーションが深まりました。 伝わることの喜び オフショ アメンバー と会話を重ね、最初は英語に対して悲観的で、喋ろうとしないメンバーも自然と喋るようになりました。この記事を書く際に彼らにその理由を聞いてみました。 「英語の発音は下手ですが、理解してくれる人がいるので勇気が出る」「私の英語で喋っても笑われていないし、Andrewからの促進で一歩踏み出そうと思うようになって、ディスカッションに関与する重要性を理解できるようになった」という素敵なコメントをいただきました。 これらの言葉を通じて、協力し合いながら成長するプロセスに感謝しています。 このオンライン言語交流会の ファシリテーション は最初は私が担当していましたが、現在はオフショ アメンバー が積極的に ファシリテーション することもあります。今では毎回アイスブレイクゲームだけでなく、単純に英語で雑談することもできる環境になりました。 また、この会の名前の通り、英語に限らず、最近私が彼らから言語を学ぶ機会も得ました。 Squadの成長と今後 まだ完璧な状態には達していませんが、Squad全体がどんどん成長している実感があります。オフショ アメンバー が積極的にコミュニケーションに参加し、自ら意見を言うようになったことは、大きな進歩と言えます。 異なる文化やバックグラウンドの人と仕事することは、チームが結束するまでには時間がかかりますが、結束ができた際には異なる視点から物事を判断でき、それによるメリットも大きいです。 私の過去の経験では、さまざまなバックグラウンドを持つ人々が集まれば、クリエイティブなアイディアが生まれやすくなります。そのため、このような多様性を大切にしていきたいです。 最後に、今後もSquad全体で協力し、より良いプロダクトを生み出すために努力していきたいと思います。
こちらは Timee Advent Calendar 2023 シリーズ1の19日目の記事です。 はじめに こんにちは、タイミーで Android エンジニアをしているsyam( @arus4869 )です。 この記事では、タイミーで実際に利用しているBitriseとFirebase App Distributionを用いた Android アプリの配布方法を実例を交えながら紹介していこうと思います。 この記事を通じて、同じようなツールを利用している他の開発者の皆さんにとって、参考になる情報を提供できればと思います。 BitriseとFirebase App Distributionについて Bitriseは、モバイルアプリのCI/CDプロセスを自動化するプラットフォームです。ビルドからデプロイまでを簡単に管理でき、開発サイクルを効率化します。 Firebase App Distributionは、アプリのベータ版やテスト版をテスターに迅速に配布するためのツールです。新しいビルドをテスターに届け、フィードバック収集が可能です。 これらのツールを組み合わせることで、 アプリ開発 の流れをスムーズにしてくれます。 事前準備 BitriseとFirebase App Distributionを使用する前に、いくつかの事前準備が必要です。 まず、BitriseとFirebase、そして Google Cloud Platform( GCP )のアカウントを準備します。 それぞれのツールを利用するには適切なアクセス権と設定が必要です。事前に準備しましょう。 詳細なアカウント作成方法については、それぞれの公式を参照してください。 参照URL Bitrise Firebase Google Cloud Platform(GCP) 次は具体的な手順へ進めていきます。 手順1: Firebase App Distributionのグループとテスターの設定 Firebase App Distributionを使用してアプリを効率的に配布するためには、適切なグループとテスターの設定が必要です。 以下のステップに従って設定を行いましょう。 Firebaseプロジェクトにアクセス Firebaseコンソールにログインし、対象のプロジェクトを選択します。 テスターグループの作成 「App Distribution」セクションに移動し、「テスターとグループ」タブを選択します。 「新しいグループを作成」をクリックし、グループ名(例:product_team)を入力します。 テスターの追加 新しいグループを選択し、「テスターを追加」をクリックします。 テスターのメールアドレスを入力し、グループに追加します。 テスターは開発用と本番用のアプリに分けてグループ化すると管理がしやすくなります。 この手順により、特定のテスターグループに対して、アプリのテストビルドを簡単に配布できるようになります。 グループとテスターの設定が完了したら、次にFirebaseの認証用サービスアカウントの作成をしていきます。 手順2: Firebaseの認証用サービスアカウントの作成 Firebase App Distributionでアプリを配布するためには、Firebaseの認証用のサービスアカウントを設定する必要があります。この手順では、その設定方法を説明します。 Google Cloud Platform( GCP )でサービスアカウントを作成 GCP の ダッシュ ボードにアクセスし、「サービスアカウント」ページを開きます。 「サービスアカウントを作成」を選択し、アカウントの詳細を入力します。 必要なロールを割り当て サービスアカウントに以下のロールを割り当てます: Firebase 品質管理者 Firebase App Distribution Admin SDK サービス エージェント これらのロールにより、アカウントはFirebase App Distributionでのアプリ配布に必要な権限を持つことになります。 秘密鍵 の生成と保存 サービスアカウントに対して新しい 秘密鍵 を生成します。 生成された 秘密鍵 を安全な場所に保存し、後のBitriseの設定で使用します。 次は、このサービスアカウントを使用して、Bitriseでのプロジェクト設定を行います。 手順3: Bitriseの設定 次は、BitriseからFirebase App Distributionを通じてアプリを配布するためには、Bitrise上での適切な設定が必要です。以下のステップに従って、Bitriseでの設定を行いましょう。 Bitriseプロジェクトのセットアップ : Bitriseにログインし、新しいプロジェクトを作成または既存のプロジェクトを選択します。 プロジェクトのビルド設定を確認し、必要に応じて調整します。 Firebase App Distributionステップの追加 : プロジェクトのワークフローを編集し、「[BETA] Firebase App Distribution」ステップを追加します。 このステップにより、ビルドが成功すると自動的にFirebase App Distributionを通じてアプリが配布されます。 認証情報の設定 : GCP で生成したサービスアカウントの 秘密鍵 をBitriseの「Code Signing & Files」タブにある「GENERIC FILE STORAGE 」 セクションにアップロードします。 「[BETA] Firebase App Distribution」ステップの設定で、Service Credentials Fileの項目に「GENERIC FILE STORAGE」セクションにアップロードしたサービスアカウントの 秘密鍵 のパスを設定します。 これらのステップにより、BitriseはFirebase App Distributionと連携し、アプリのビルドと配布を自動化できるようになります。設定が完了すれば、BitriseからFirebase App Distributionを通じてアプリをテスターに配布する準備が整います。 参考程度に「[BETA] Firebase App Distribution」に設定している項目を記載します。 項目 説明 例 Service Credentials File Bitriseのアップロードした 秘密鍵 のパス $BITRISEIO_development_service_account_key_URL App Path APK及びAABファイルが格納されているパス $BITRISE_APK_PATH Firebase App ID Firebaseコンソールのプロジェクトの設定から見れるアプリ ID 1:1234567890: android :0a1b2c3d4e5f67890 Release Notes テスターが見るリリースノート メインブランチにマージされたビルドです。 Test Groups 手順1で設定したテスターグループ product_team まとめ この記事では、BitriseとFirebase App Distributionを活用して Android アプリを配布するための手順を紹介しました。以下は、ポイントのまとめです。 事前準備が鍵 :BitriseとFirebase、 GCP のアカウント設定から始め、必要なツールと情報を確認することが重要です。 Firebase App Distributionの設定 :適切なテスターグループとテスターの設定を行い、アプリのテスト版を簡単に配布できる環境を作ります。 Bitriseの自動化設定 :ビルドプロセスをBitriseで自動化し、Firebase App Distributionを通じてアプリを配布する設定を行います。 このプロセスを通じて、テストアプリの配布が可能となります。あとはテストアプリをどのように使ってもらえるか、開発者以外のメンバーにもフィードバックをもらえるようにドキュメント化することをお勧めします。本記事が、みなさまの開発に役立つことを願っています。
こんにちは、データ統括部BIグループ所属のtakahideです。 本記事では、BIグループで取り組んでいる全社的なデータ活用に関してご紹介します。 この記事を通して、少しでも「社内のデータ活用を進めたい」と思っている方のお役に立てたら幸いです。 ※ Timee Advent Calendar2023 の12月18日分の記事です。 課題感 データ活用に役立つスキル 講習会の開催 少人数の相談会 データ活用スキルの指標化 おわりに We’re Hiring! 課題感 まずは、データ活用を進めるに至った経緯を簡単に説明させてください。 タイミーは、データを用いた意思決定を大切にしているのですが、 近年の組織拡大にともない、データ活用を全社的に推進するニーズが高まっていました。 一方で、データを利用するメンバーのスキルとニーズとの間に乖離が存在していることが分かってきました。 そこで「データ活用を全社的に推進するPJT」をBIグループ中心に立ち上げることになりました。 データ活用に役立つスキル データ活用といっても人によって解釈が異なるため、 データアナリストが日々の業務で用いているスキルの言語化から始めました。 機械学習やプログラミングといった一部の職種で使われるスキルは除外して、 どの職種の方でも役に立つ汎用的なスキルを選択しています。 「課題の発見→課題の解像度の向上→検証に必要なデータの解釈→結果のまとめ」といった流れを意識しています。 講習会の開催 スキルの整理ができたので、次にスキルの意義や使い方を伝える講習会を行うことにしました。 データの価値を実感してもらうことを優先して「データ抽出」の講習会を実施して、 顧客の行動を数値で見る体験をしてもらいました。 具体的には、簡易にデータを扱えるツールとしてLookerの講習会を開催しました。 講習会をキッカケにLookerの利用者が増え、データを見る習慣が作られたと考えています。 最初はお試しで始めた講習会も、現在は毎月実施していて、新入社員の方がデータに触れる機会を作っています。 (詳細は こちらのブログ をご覧ください) 次に行ったのが「論理的思考」「データ構造の理解」「データの解釈と可視化」の講習会です。 これらはデータ抽出の前後に用いることが多く、「データ抽出」の後に実施することで理解が深まると考えました。 下記のように、First Step→Second Stepと段階を踏んで進めてきました。 少人数の相談会 ここで、講習会を通じて見えてきた課題とその対応をお伝えできたらと思います。 課題として分かったことは大きく二つで、 「講習会の難易度」と「質疑応答のしづらさ」になります。 「講習会の難易度」ですが、 講習会では必要最低限の内容をコンテンツに盛り込んでいるものの、 参加者の習熟度の違いを吸収しきれていませんでした。 また、「質疑応答のしづらさ」ですが、 講習会の参加人数が50~100人になるため、質問しづらい雰囲気がありました。 特に、どのレベルの質問をして良いかが分かりづらいという声が挙がりました。 これらを踏まえて、少人数の相談会を進めようとしています。 テーマを絞った数人〜十数人の会にすることで、活発な意見が出ることを期待しています。 全社向けの講習会を定期開催しつつ、補完的に相談会を実施することで、 講習会の内容の充実にも繋がると考えています。 データ活用スキルの指標化 最後に、データ活用の測定に関してお話しできたらと思います。 全社のデータ活用を推進する上で、データ活用スキルの習熟度の把握が重要になります。 タイミー全体でデータ活用がどの程度進んでいるかが分かることで、 データ活用のPDCAが回しやすくなり、スキル習得のモチベーションにも繋がると考えます。 おわりに この記事では、BIグループが実施している、データ活用の取り組みを紹介しました。 改めて「社内のデータ活用を進めたい」と思っている方のお役に少しでも貢献できていたら嬉しいです。 今回は全体の紹介で、個別の詳細はお伝えできていなかったので、 そちらも機会がありましたらお話しできたらと思います。 We’re Hiring! タイミーのデータ統括部では、ともに働くメンバーを募集しています!! product-recruit.timee.co.jp
好きな水風呂の温度は16℃でお馴染み edy69x です。 Timee Advent Calendar 2023 の16日目を担当します。 本記事では今年完遂したUIリニューアル(SPA化)を通してタイミーで実施した工夫や学びを普段バックエンドの開発を担当する私の視点からお伝えします。 先日のイベントでの登壇内容を補完した内容となっています。気になる方は下記資料もご覧ください。 speakerdeck.com イベントの方はプロジェクト終盤での断捨離やリファクタリングなどがテーマになっていたので本記事ではプロジェクト進行過程全般での知見をシェアしていきます。 プロジェクト概要 まずプロジェクトの概要です。大雑把に言うとフロントエンドの技術基盤を移行しながらUIリニューアルを実施しました。 それだけだと「何のことだ?」となるので前提からご説明します。 タイミーでは単発のアルバイト求人の掲載を行う事業者用のWeb管理画面を提供しています。 リニューアル前はRailsエンジニアがテンプレートエンジンのSlimを活用し、フロントエンド開発も担っていました。Railsで作られたサーバは動的に生成したHTMLをレンダリングする責務も持っていました。いわゆる Server Side Rendering(SSR) です。タイミー創業当初から変わらない技術スタックで構成されていて価値も生み出し続けていました。 しかし、サービス拡大にともない、古くから存在する Model や Controller への継ぎ足しの実装などが生まれ、Railsアプリケーションのバックエンドとフロントエンドの密結合なロジックが増えたり Fat Controller に頭を悩ませられるシーンが発生し始めました。 一方のフロントエンドでは保守性や情報設計(Information Architecture)を始めとして品質向上を狙ったり、jQueryに代わるモダンWebフロント技術の導入、またフロントエンド専任のエンジニアの採用拡大も狙っていました。 上記の理由などからUIリニューアル及びフロントエンドの技術基盤を刷新するプロジェクトがスタートしました。 フロントエンドの技術は下記の構成で、Railsのリポジトリとは別で切り出しました。 言語: TypeScript ライブラリ: React フレームワーク: Next.js APIドキュメント: OpenAPI (Swagger) Webページ構成やレンダリング手法もこのタイミングから Server Side Rendering(SSR) から Single Page Application(SPA) に変更しました。 いざ始まったUIリニューアル UIリニューアルを進める上で不確実性を大いに孕んでいたので管理画面全体を一気にビッグバンリリース的に旧バージョンから新バージョンに切り替える方針は見送りました。 方針としてはハレーションを抑えるためにアクセス数の少ない画面から徐々にリプレイスしていき、顧客が最も利用する事業のコア機能を終盤にリプレイスする計画を立てました。 同時にDatadogやGoogleAnalyticsなどの監視・分析ツールを用いて、アクセス数の解析、利用デバイス比率の特定をしたり、売上規模の上位を占める事業者を選定し、移行に際して障壁となるポイントが無いかを探っていきました。 従来デザイン使い続けたい問題 進行する中で発見されたのが「従来のデザインを使い続けたい」というユーザーニーズです。 背景には、既存UI/UXに対してのメンタルモデルが形成されているので変化を忌避してしまったり、画面構成やボタン単位で指定オペレーションを厳密に組んでいるケースなどがありました。 強行突破する方法もあると思いますが、打ち手として新デザインのリリース後も従来の旧UIに切り替えられる仕組みを導入しました。デバイス変更やセッション切れ等で、従来デザインを選択したユーザーが新デザインに切り戻されることを回避したり、データ分析観点で活用するために従来デザインを選択した状態はDBに永続化しました。 プロジェクト終了時には削除する前提だったので明確さやアクセスのし易さを優先しました。フロントエンドは使用状況をAPI経由で取得する方針を採用しています。 [従来のデザインに切り替える] ボタンを配置 この仕組みのお陰でリリース後に利用者に多少影響があっても完全にロールバックすることなく、利用者ごとの判断に委ねることが出来ました。新デザインを許容できる利用者には新デザインを提供し、どうしても従来のバージョンを利用したい方には一定期間の従来バージョン利用を許容する運用をしました。 事前告知による期待値調整も勿論大事だと思うのですが、石橋を叩きすぎずにリリース後の ”動くソフトウェア” を通しての実際のユーザーのフィードバックから次の意思決定に繋げられるアプローチを経験できたのは大きな財産になりました。 画面移行の約80~90%程度が終わる頃には新デザインがマジョリティになったので、上記機能はほぼ役目を終え勇退しました。 影響範囲を限定する話での余談ですが、カナリアリリースの仕組みがあると影響を抑えられた障害などもあったので今思い返すと導入しておけると良かったです(プロジェクト中には追加出来なかったので今後導入していきたい)。 ユースケースロジックを切り出すFormObjectを活用 プロジェクトも佳境に差し掛かった中で、とあるエンドポイントに対応するAPIを実装する工程が最も難を極めました。タイミーのコアドメインに関わる機能を司っている Controller のリプレイスです。元々が Fat Controller だったケースでして既存の仕様理解や根本的なリファクタリングが求められたのを覚えています。 リクエストを受けてからHTMLを返却する一連の処理の中に、データベースへの書き込みや外部APIへの通信、ビジネスロジックに関係する処理など様々な責務を Controller が抱えていました。テスタビリティも低く、RSpecで書いていた controller spec では仕様は網羅されていませんでした。 その際に Controller の責務を一部切り出す方法として FormObject を採用しました。ユースケースに対応したロジックを FormObject クラスに閉じ込め、Controllerはクラスのパブリックメソッドの呼び出しとリクエストの受付、及び適切なレスポンスの返却に特化する様な作りにしました。 FormObject には単に移行するだけではなく「テストピラミッド」も意識し、テストケースの割合が結合テスト < ユニットテスト になるように必要に応じて責務の見直しも行いました。 テストピラミッド 結果的に Fat Controller は消え去り、元々はAPIリクエストの受付からレスポンスまでの過程を通して結合テストでカバーしていたテストケースをユニットテストで再現できるようになりました。Controller は RSpec の request spec にて受け付けるリクエストとレスポンスを明示し、副作用のうち代表的なケースをテストすることで不要に FormObject 側のユニットテストと重複することも回避しました。 Controller の上部に君臨し続けていたTODOコメントを削除できたときは非常に感慨深かったことを今でも覚えています。 古来からタイミーを見守ってきた長老の様なTODOコメント プロジェクトの中断 これは少しオフトピック的な話ですがプロジェクト進行のメタ的な話として触れておきます。 一般的にプロジェクトのみならず仕事を進める中で優先度が高い別Issueが割り込まれることはありますよね。今回のUIリニューアルプロジェクトでも起きました。 プロジェクトの性質的には「技術改善」をテーマとし基本的に従来機能を踏襲しながらUIリニューアルを進行する前提でした。そのため、割り込み的な機能開発に対してスイッチングコストが発生したり、リニューアルとの優先順位付けが必要になったりと苦労した覚えがあります。移行期間全体が約3年あったうち合計1年ほどはチーム構成やミッションが根本から変化し、別テーマの開発を担当する時期もありました。 結果として生存期間が短い前提で作られた機構が想定より長く存在し続けることになりました。例えば先に述べたデザイン切り替えの仕組みや、Rails, Next.js それぞれのリポジトリが互いに依存する様な暫定的なロジックなどです。 当時の自分はイレギュラーに対してある種「当たり前」くらいで考えていたのですが、水面下ではコードベース上に認知負荷を高める要因が生まれていました。新規参画メンバーから質問が来ることもありましたが、当時は影響を理解したり、言語化することは出来ていませんでした。 今思い返すと内部品質低下に繋がることから、プロジェクト完遂までのリードタイムを短縮したり、テーマが変化する前に最低限の内部品質改善をするように働きかける余地はあったなと感じます。 プロジェクトが順調に進んだり完了することだけが学びではなく、むしろ想定外の中に自分の枠を超えた学びのきっかけがあると気付かされた瞬間でした。 プロジェクト完遂 過程では様々な事があり、ここでは語り切れないのですが3年の歳月を経てプロジェクトは完遂しました。RailsリポジトリからはHTMLレンダリングにまつわるコードやCSSライブラリなど5万行を超えるコードが削除されました。特に1つのPull Requestで1万行を超えるコード削除をした時は非常に爽快でした。 Webpackerやyarnなどにも別れを告げ、外部顧客向けの機能についてはRailsリポジトリはWebAPIの開発に特化する様な存在になっています。GraphQL化を進める様なチャレンジも生まれました。 WebFrontも1名でプロジェクトがスタートしましたが今では技術コミュニティが生まれたり、技術顧問の方が付いていたりと2020年当時と比べてだいぶ拡大しています。いい話ですね。 おわりに プロジェクトが終われど、フロントエンドにとってはスタートラインに立ったとも言えますし、バックエンド側も保守性が多少増したとは言え、会社の成長を支える面でいうとまだまだ改善余地があります。 会社に興味がある方がいたら、タイミーワーカーで運営が成り立っている THE 赤提灯 という居酒屋が新橋オフィス近くにあるので是非飲みに行きましょう。 一応採用ページも貼っておきます。 product-recruit.timee.co.jp 忘れていましたが、声を大にして改めて言っておきたい。 「タイミーはSPA化やり遂げたぞ!!!!」 完
こちらは Timee Advent Calendar 2023 シリーズ1の15日目の記事になります。 こんにちは、タイミーで Android エンジニアとして働いている @orerus こと村田です。 私は現在タイミーのAndroidChapter(弊社は特定領域のメンバーの集まりのことをChapterと呼称しています)の一員で、喜ばしいことに来年早々にメンバーが大きく増加する予定です・・・! 以前はAndroidChapterのメンバーが全員同じチームで開発を行っていましたが、タイミーのエンジニア組織拡大に伴い全員が異なる開発チーム(弊社ではSquadと呼称しています)に所属する形へと変化しました。その為、各Squadには Android エンジニアが少人数しか所属しておらず、 Android アプリに関するPRレビューについてはSquadを跨いで行うことになります。必然、故も知らないPRレビューが飛んでくる為、PRレビューの負荷が上がり 工数 的にも 心理的 にも辛いものとなってしまいました。 そこで今回は、そういった組織のスケールに伴い発生する課題解決の為の施策の一環として、弊社AndroidChatperで導入した独自のLADR(Lightweight Architecture Decision Records)について紹介したいと思います。 また、導入して得た学びや見えてきた改善点についても共有できればと思います。 施策の背景、課題 独自LADRについて LADRのサンプル LADR導入の経緯 導入後の成果と学び、および見えてきた改善点 AndroidChapterメンバーからのFB 自身の振り返り 学びと改善点のまとめ 当初抱えていた課題は解決できたのか 施策の背景、課題 先述の通り、タイミーでは現在 Android メンバーが全員異なるSquadに所属し、それぞれが異なるプロダクトゴールに向けた機能開発を行っています。 AndroidChapterで毎日10分程度のデイリー MTG は行っていますが、そこでは現在取り組んでいるもの・これから触るコード領域、困りごと等の共有に留まります。(各Squadのデイリー スクラム もある為、あえて軽い内容にしてあります) そのような状態で開発サイクルを回す中で、いくつかの大きな課題が見えてきました。 PRレビュー レビューに入る前に、PRが行っている開発内容を把握する為の多大な コンテキストスイッチ コストがかかる コンテキストを十分に理解できていない為、仕様や設計の妥当性の判断が困難で表面的なレビューになりがち PRという完成形で初めてレビュー依頼が飛んでくる為、仕様や設計で致命的な問題があった場合に後戻りが難しい(修正コストが高い) PRレビューコストが高い為、PRレビューを行う際にまとまった時間を確保する必要がありレビュー完了までのリードタイムが長くなる 改修/影響調査 昔のコードや自身が触っていないコードについて、開発当初の意思決定根拠や設計意図が分からず、影響調査に時間がかかったり変更妥当性の判断が難しい そこで、これらの課題を解決するために実験的に導入を始めたのが独自LADRでした。 独自LADRについて ここでは我がAndroidChapterで定義している独自LADRについて紹介します。なお、ここでいうLADRは弊社AndroidChapter用に独自改変したLADRですが、AndroidChapter内では単に「LADR」と呼称している為、この記事でも以後は単にLADRと記述します。 そんなAndroidChapterで導入している独自LADRですが、株式会社 ラク スの鈴木さんが公開されている LADR-template をベースにさせていただいています。 また、 鈴木さんの記事 にある通り、LADRの本来の用途は アーキテクチャ 選定において「導入しなかった」記録を残す為であると認識していますが、AndroidChapterではLADRの目的や対象をそこから拡大解釈して利用しています。 目的や対象を具体的にどのように拡大解釈しLADRを運用しているかについては、実際に私が作成した「LADRを導入する為のLADR」を以下に添付しますので、こちらをご覧いただければ雰囲気が掴めるかと思います。(「Context - 文脈」「Specification - 仕様、やること、やらないこと」の項目のみご覧いただければ十分です) ご覧の通り、LADRのドキュメント自体は軽量なものになっている事が分かるかと思います。ここがLightweight形式を採用した最大の理由でして、ハードルを下げることでまずはこのドキュメントが書かれることを一番の狙いとしています。 LADRのサンプル 先ほどお見せしたLADRは機能開発を伴うものではなかった為にいくつか項目を省略してますが、機能開発時のLADRでは必要に応じて詳細な仕様を記述したり、実装時の設計についても触れています。 以下、実際に作成されたLADRのサンプルです。(雰囲気が伝わるようにシンプルなものと重めなものを抜粋しています) サンプルA. 機能追加のためのLADR サンプルB. 不具合修正のためのLADR このようなLADRを作成した後、AndroidChapterにLADRのレビュー依頼をPRとして投げ、そこでLADRに書かれたコンテキストや仕様、設計について 開発着手前に レビューを受けるようにしています。 LADR導入の経緯 先述のLADRについての記事を拝見したときに「これだ!」と思い、すぐさま「LADR導入の為のLADR」、および「LADRのテンプレート」を作成し、 Android メンバーにPRという形で投げつけました。LADRについて解説するより、まず実物を見せ、そこから話をするのが早いと感じた為です。 PR上でLADRの狙いや、他ドキュメントとの棲み分けについてなどの多少のラリーはありましたが、好意的に受け入れられすんなりと実験導入が始まりました。 運用を初めて2ヶ月程度ですが、現在以下の18個のLADRが作成されています。 導入後の成果と学び、および見えてきた改善点 AndroidChapterメンバーからのFB まずは2ヶ月運用してきて、AndroidChapterのメンバーはどう感じているのかを探るためにアンケートを実施してみました。 アンケート結果について、AndroidChapterメンバーの中でLADR作成経験済み、作成機会がまだ無い状態の2人の回答を紹介します。 「ありのままを記事に書きたいので率直に書いてね」とお願いをしておいたので、きっちりと本音が書かれているかと思います・・・! ※なお、アンケートの設問はChatGPT先生にご協力願いました その結果がこちらです。 まずは分かりやすく数値で評価してもらいました。 一番右の私の回答分はバイアスがかかりまくっているのでスルーして、他メンバーの評価は以下のような感じです。 満足度はどちらも7と「やや良い」点数 プロセスの効率化や課題解決度合いの感じ方はLADR作成機会があったメンバーは全体的に高め、機会が無かったメンバーは低め Bの方が「LADR導入によって解決された課題の効果度」の項目の3点について、「まだ自身で作成したことがないために自分事感が薄い」とのFBをもらっています 次に、フリーテキストで回答してもらったものを 原文ママ で掲載します。 ※2人分の回答をマージしています まずは 導入による成果 についての質問についてです。 こちらにより以下のことが言えそうです A、Bの両者ともに「コンテキスト共有」については効果を明確に感じている LADRを作成する機会があったメンバーは仕様/思考の整理に効果を感じている LADRを作成する機会がなかったメンバーもPRレビューの効率化には効果を感じている ただし、自身でLADR作成を行っていないと課題解決の実感が薄い 次に 改善点 についての質問です。 こちらにより以下のことが言えそうです LADRの制度自体の課題点はまだ発生していない 一方、ドキュメントの参照のしやすさ、ドキュメント作成のオンボーディングといったLADRの活用方法についての改善点が挙がっている この点は当初から想像していた懸念点であった為、今後の明確な改善点だといえる 組織がスケールしてもコンテキスト共有に関する課題解決の効果が見込めそう 自身の振り返り また、私自身が感じているLADRのメリットデメリットも軽く挙げておきます。 メリット 何にせよ意思決定のログが残るようになった メンテを保証できない仕様書にするより、意思決定のログと割り切って扱っている PR作成時のコンテキスト共有が楽になったように感じた 「実装→ドキュメントを書く」が「ドキュメントを書く→実装」と順序が変わったのが大きいのかもしれない 設計段階の指摘や懸念点の伝達が事前にできるようになった 実装者以外のメンバーのコンテキストの理解度合いが深くなったように感じた デメリット LADRの作成の 工数 がかかる 以前もPR本文に一定のコンテキストを書いていたので、劇的に 工数 が増加した訳では無い LADRのPR、機能開発のPRと、レビューが必要となるPRの数が増える 学びと改善点のまとめ 以上から、学びと改善点について乱暴にまとめると以下のようなことが言えるかと思います。 課題解決に一定の効果は確実に出ており、デメリットを上回るメリットがあるといえる LADRドキュメントの参照性に課題がある LADRドキュメントを作成するまでのハードルがまだ感じられる オンボーディング時に1度作成してもらう等のケアが検討余地としてありそう 当初抱えていた課題は解決できたのか 最後に、当初挙げていた課題がどの程度解決できたかの所感をまとめます。 課題1 : レビューに入る前に、PRが行っている開発内容を把握するための コンテキストスイッチ コストがかかる 解決状況 : ✅ 完全に解決 課題2 : コンテキストを十分に理解できず、仕様や設計の妥当性判断が困難 解決状況 : ✅ 完全に解決 課題3 : PRという完成形で初めてレビュー依頼が飛んでくる為、仕様や設計で致命的な問題があった場合の修正コストが高い 解決状況 : 🤔 様子見 現状では問題なし。設計難易度の高い開発での対応を今後注視 課題4 : PRレビューコストが高く、レビュー完了までのリードタイムが長い 解決状況 : 🟢 一定の解決 PRレビューコスト減少、ラリーの回数も減少 課題5 : 昔のコードや自身が触っていないコードの開発意図が分かりづらい 解決状況 : 🟢 様子見、ただし解決の兆しあり まだ運用期間が短く見返す機会が少ないが、LADRのルールを見直す際に当初作成したLADRドキュメントが役立った 結論として、全体的には解決に向かっていると感じています。ただし、組織のスケールアップに対する対応として導入した施策であるため、今後も組織がスケールした際の変化に注視しつつ、より良い形へと進化をさせ続けていく所存です。
※ Timeeのカレンダー | Advent Calendar 2023 - Qiita の12月13日分の記事です。 はじめに こんにちは okodoooooonです dbtユーザーの皆さん。dbtモデルのbuild、どうやって分割して実行してますか? 何かしらの方針に従って分割をすることなく、毎回全件ビルドをするような運用方針だと使い勝手が悪かったりするんじゃないかなあと思います。 現在進行中のdbtのもろもろの環境をいい感じにするプロジェクトの中で、Jobの分割実行について考える機会があったので、現状考えている設計と思考を公開します!(弊社は一般的なレイヤー設計に従っている方だと思うのでJob構成の参考にしやすいと思います) この辺をテーマに語られていることってあんまりないなあと思ったので、ファーストペンギンとして衆目に晒すことで、いい感じのフィードバックをもらえたらなーと思ってます。 弊社のデータ基盤全体のデザインについて把握してからの方が読みやすいと思うので、そちらをご覧になりたい方は こちらの記事 からご覧ください。 これまでの弊社のデータ更新周りについて これまでの弊社のdbt Job設計の概念図を作るとしたらこんな感じになります。 走っているクエリがフォルダ単位/モデルファイル単位などバラバラな粒度で実行されている リネージュを意識したJobの走らせ方になっておらず更新頻度の噛み合わせが悪い 明確なルールがないので意図しないタイミングで同じモデルがビルドされたりする 明確なルールがないのでビルドから漏れているモデルがひっそり存在している dbtコマンドで示すとこんな具合のカオス感 dbt build --select models/path_to_model_dir1/model1-1.sql dbt build --select resource_name dbt build --select models/path_to_model_dir2/model2-1.sql dbt build --select models/path_to_model_dir3 .... 改善のための試行錯誤 v1 レイヤーごとにtagを付与して以下のようなビルドコマンドが走らせられるようにしました。 dbt build --select tag:staging_layer dbt build --select tag:dwh_layer dbt build --select tag:dm_layer 更新頻度tagの概念を設計して、以下のようなビルドコマンドを走らせられる構成を検討しました。 dbt build --select tag:hourly_00 # 毎時00分にビルドするものに付与するタグ dbt build --select tag:daily_09 # 毎日9時にビルドするものに付与するタグ これらの設計をしてみて、レイヤー単位でJobを走らせることを可能にできたのと、更新頻度をdbt上で管理可能な形にはできました。 しかし、ユーザーのデータ更新ニーズに基づいたJob設計になっていないことから、ユーザーニーズベースでのJob管理方針の検討を進めました。 データ活用ユーザーのデータ更新ニーズの整理 弊社のレイヤー設計とデータ活用ユーザーのアクセス範囲は以下のような形になります。 アナリストはステージング層以降の層に対するアクセス権を保持していますが、現状アナリスト業務はモデリング済みテーブルではなく、3NFテーブルに対するクエリを作成する作業が支配的です(モデリング済みテーブルの拡充とその布教をもっと頑張りたい)。 データ組織外の社内ユーザーはデータ組織が作成したアウトプットを通して分析基盤のデータを活用できるような状態になっています。 ※弊社内ではデータ組織以外のユーザーにBigQueryのクエリ環境を開放しておらず、LookerやLookerStudioなどのアウトプットを経由して社内データにアクセスする状態にしています。 それぞれの層ごとの現在顕在化しているデータ更新ニーズを整理すると以下のようになります。 アナリストと野良アウトプットによる「ステージング層のなるはや更新ニーズ」 アナリストのアドホッククエリ 分析要件は予測ができず、要件次第では最新のデータが必要となるため、なるべく早く更新されることが期待されます。 マートから作成されずステージングテーブルから作られるアウトプット 更新頻度ニーズのすり合わせなく作られて提供されるものも多く、要件次第では最新のデータが必要となるため、なるべく早く更新されることが期待されます。 データ活用ユーザーによる「アウトプット更新ニーズ」 データ活用ユーザーに提供するデータマート経由のアウトプットは更新頻度を擦り合わせられているものだけになっている(今はまだ数が少ないだけだが)ため、hourly,daily,weekly,monthlyなどの頻度ごとの更新設計が可能となっています。 例えば弊社のよく使われるLooker環境はhourlyの頻度で更新が要求されています。 単純なジョブ設計をしてみる 単純な ジョブ実行案① ステージングなるはや更新プラン ソースシステムごとのデータ輸送完了後にそのソースシステムを参照しているモデルを全てビルドするプラン コマンド例 salesforceのデータ輸送完了後のコマンド: dbt build --select tag:salesforce_source+ dbt build --select models/staging/salesforce+ アプリケーションDBのデータ輸送完了後のコマンド: dbt build --select tag:app_source+ dbt build --select models/staging/app_db+ 実装方法 airflowなど何かしらのオーケストレーションツールで転送完了をトリガーに下流一括ビルドのコマンドを実行する必要がある。 この場合の更新ジョブを概念図に表すと以下のようなものになると思います(上流から下流へのビルド時のデータリネージュを黄色の三角形で表現しています)。 メリデメをまとめると以下のようになるかと思います メリット 下流が全てビルドされるのでビルド漏れが発生しない。 常にモデルの鮮度が最新になる = アナリストのstaging最新化ニーズは満たせている デメリット 更新がそこまで不要なモデルに対してまでビルドが実行される。 BigQueryのクエリ料金が高額になる 単純な ジョブ実行案② ユーザーニーズに合わせるプラン ユーザーのデータ更新ニーズに合わせて更新頻度をアウトプットごとに設定。更新ニーズの頻度ごとに最新化されるようなプラン コマンド例 1時間おきにビルドされて欲しいアウトプットを更新するコマンド: dbt build --select +tag:hourly 1日おきにビルドされて欲しいアウトプットを更新するコマンド: dbt build --select +tag:daily この場合の更新ジョブを概念図に表すと以下のようなものになります(下流から上流へのビルド時のデータリネージュを赤色の三角形で表現しています)。 メリデメをまとめると以下のようになります。 メリット:アウトプットの更新頻度が最適化される。 デメリット stagingのモデル作成の重複が発生する。 出力ニーズが存在しないstagingテーブルなどがビルド対象から漏れてしまう ではジョブ分割をどのように設計するか ステージングなるはや更新に合わせてリネージュの上流から実行すると下流で無駄なビルドが発生してしまう。 ユーザーニーズベースでリネージュの下流から実行すると上流で無駄なビルドが発生してしまう。 この二つの問題の折衷案を考える必要があると考えました。 現状のジョブ実行案 更新ジョブの概念図は以下のようなものになります(上流から下流へのビルド時のデータリネージュを黄色の三角形、下流から上流へのビルド時のデータリネージュを赤色の三角形で表現しています)。 ソースデータ ~ ステージング層のビルド ソースデータが連携されたらステージング層までを下流に向かってビルド コマンド: dbt build --select tags:source_a+ --exclude tags:dwh_layer tags:dm_layer DWH層~アウトプットの間のビルド ステークホルダーと定めた更新頻度に合わせて上流に向かってビルド コマンド: dbt build —select +tags:daily_00_00 —exclude tags:staging_layer tags:datalake_layer こうすることで、ステージング層までは常に可能な限り最新化されつつ、アウトプットは要件ごとに更新頻度が最適化された状態となります。 ただ、DWH層やデータマート層に多少のビルドの重複は発生してしまいますが、そこは許容しています。 本当はこうしたい案 アナリストがモデリング済みのDWH層を使うことが常態化するような世界観になってくると、上図のように「レイク~DWHまでは最新鮮度」「それ以降はユーザーニーズベースで更新」って流れがいいのかなと思っています。 また source_status:fresher+ などのstate管理をうまく使って、更新があったものだけをビルドするような方式を模索していきたいです。 おわりに dbtの環境リプレイスとともにこのJob設計も実戦投入しようと考えているので、想定していなかったデメリットが発覚したり、改善点が見つかったら改善していこうと思います! 弊社データ基盤でもストリーミングデータが取り扱えるようになったので、そのデータの使用が本格化すると ストリーミング✖️バッチ のJob構成などを考える必要があり、まだまだ俺たちの戦いはこれからだ。と思います ていうかみなさんどうやって分割して実行してるの!!!教えてほしすぎる We’re Hiring タイミーのデータ統括部はやることがまだまだいっぱいで仲間を募集しています!興味のある募集があればこちらから是非是非ご応募ください。 product-recruit.timee.co.jp
こんにちは、タイミーのデータ統括部、DRE(Data Reliability Engineer)グループ & DS(Data Science)グループ所属の筑紫です。 DSグループではML基盤の構築・運用保守を担当しています。 本記事では、ML基盤を再構築した話を紹介したいと思います。 ※ Timee Advent Calendar2023 のシリーズ 2の12月13日分の記事です。 経緯と課題 DSグループでは、様々なプロジェクトのML基盤を構築しています。 当初は、1つのCloud Composerの上に、全てのpipelineを載せて運用していました。 以下に当初の構成のイメージ図を示します。 ただ、このML基盤についてはいくつか課題がありました。 本番環境しかない 本番稼働しているpipelineが複数ある中、環境が本番環境しかありませんでした。 Cloud Composerも本番の インスタンス 1つしかなく、例えばCloud Composerのimageのupdateが必要な際もupdateして既存本番に影響がないか検証できる環境がない、などの問題が発生しており、保守に耐えられない状況でした。 サービスレベルの異なるpipelineが同じワークフロー上に乗っている 当時ML基盤の予測結果を社外に提供する取り組みも開始している中で、社内利用のML pipelineと社外利用のML pipelineが同じワークフローツール(Cloud Composer)上に乗っている状況でした。 ML基盤でもその結果を社内で使用するものと社外で使用するものでは、サービスレベルが変わってきます。 異なるサービスレベルを持つ複数のpipelineが一つのワークフローに乗っている状況では、あるpipelineの挙動が他のpipelineにも影響を及ぼし、サービスレベルに影響を与える可能性があるため、運用上好ましくありませんでした。 インフラ反映までのプロセスが長い GoogleCloud環境はTerraformで管理していますが、その リポジトリ はDREグループ管理になっており、DSグループがリソースを作成する際に、以下の手順を踏む必要がありました。 DSグループで修正のPullRequest(以降PR)を作成 DREグループにレビューを依頼 DREグループのレビュー通過後にマージ & デプロイされるので、反映結果を確認 このような手順を踏むので、 検証のために、試しにリソースを作成したり変更したりする際に、都度PRを作成し、レビュー依頼しないといけない 他チームのレビューを挟むので、GoogleCloudのリソースを作成するまで、リードタイムがかかりがち という問題があり、DX(Developer eXperience)に影響を与えていました。 ソリューション まず本番環境しかない問題については、開発環境を作り、Cloud Composerを本番環境、開発環境それぞれに配置することにしました。 それに合わせてGoogleCloudのprojectも分けることにしました。 1つのproject上に本番環境と開発環境を構成するよりも、分けた方が事故が起こりにくく、Terraform上の管理もしやすかったためです。 また、サービスレベルの異なるpipelineが同じワークフローに載っている問題について、サービスレベルが大きく異なる ユースケース ごとにCloud Composerを分けることにしました。 コストとの トレードオフ になりますが、運用・保守のしやすさを考えると、社内で使う ユースケース と社外などConsumerなどに提供する ユースケース でCloud Composerを分けた方が良いという判断からこの構成にしました。 結果、下図のような構成になりました。 その上で、上記環境のTerraformのコードをDRE管理の リポジトリ と分けて、DSグループ管理の別 リポジトリ で管理する方針としました。 また、ブランチ戦略を整理して、featureブランチにpushする度に開発環境にデプロイされることにしました。 これにより、デプロイ都度レビューを挟まないといけないフローが、featureブランチにpushするだけでデプロイされるようになり、検証までのリードタイムを改善できたと思ってます。 結果 最終的に、数ヶ月かけてこの新環境の構築とpipelineの移行を行いました。 結果、上記の3つの課題については、解消することができたと思ってます。 特に、開発環境ができたことと、DSグループ管理のTerraform リポジトリ ができたこと、featureブランチのpushでデプロイできるようになったことから、インフラの検証のしやすさは格段に上がったと思っており、DSグループ内のDX向上に寄与できたのではと思っております。 We’re Hiring タイミーのデータ統括部では、データサイエンティスト、DRE、Data Analystなど、様々な職種のメンバーを募集してます! https://hrmos.co/pages/timee/jobs カジュアル面談 からでも対応できますので、少しでも気になった方は申し込み頂けると嬉しいです!
こちらは Timee Advent Calendar 2023 の13日目の記事です。 タイミーでバックエンドエンジニアをしている @Juju_62q です。 記事内でワーカーさんや事業者さんに関して敬称を省略させていただきます。 タイミーは雇用者である事業者に求人を投稿してもらい、労働者であるワーカーが求人を選ぶという形でマッチングを実現しています。ワーカーが求人を選ぶためにはなんらかの形でワーカーが自分にあった求人を見つけられる必要があります。検索はワーカーが求人を見つけるために最もよく使われる経路です。今回はそんな検索機能において今後の開発をスムーズにするための リファクタリング を実施した話を紹介します。 背景 リファクタリングの切り口 実施したリファクタリング before after 結果と所感 終わりに 背景 タイミーの検索は以下の機能です。 ワーカーの状況や希望に合わせて求人を表示しています。求人はサーバーサイドで取得、絞り込みを行っています。この絞り込みはFilterというクラスが一手に担っていました。Filterクラスでは具体的に以下の処理を行なっています。 該当日の全ての求人を取得する ワーカーや事業者の設定した絞り込み条件やタイミーのルールに合わせて絞り込みを行う 前述した通り検索はマッチングが最も生まれている 動線 です。そのために機能追加の圧力も非常に大きく、さまざまな機能が追加されていきました。結果としてFilterクラスは見通しが悪くなっていきました。最大では16の絞り込み処理が1つのクラスで行われていました。 また、このFilterクラスですが利用箇所が検索だけであればいいものの、タイミーのルールに合わせた絞り込みを行なっていたために検索以外にもお気に入り求人やお仕事リク エス トなどさまざまな機能に利用されています。 ただでさえ問題を起こした時に大きくなってしまう検索機能です。コードの見通しが悪く、影響範囲も不透明となると変更するのもどんどん億劫になってきます。大切な機能であるからこそチームとして改修をした方が今後も含めて開発が早くなるだろうと考えて リファクタリング に踏み切りました。 リファクタリング の切り口 見通しが悪いのは絞り込み処理を行なっているFilterクラスなので、Filterクラスに絞って リファクタリング を検討します。Filterクラスがどんな処理を担っているのかをチーム内で話しました。 取得部分を一旦置いておくと、Filterクラスの絞り込みには大きく以下の2種類があるとわかりました。 ワーカーが設定した検索条件による絞り込み(10処理) 良いマッチングを生むためにタイミーが実施している可視性の制御(7処理) また、Filterクラスを使っている箇所を眺めてみても検索では1, 2の両方を利用しているものの、それ以外の箇所では2の「良いマッチングを生むためにタイミーが実施している可視性の制御」しか利用していませんでした。 これを踏まえて3つの要素を満たすような リファクタリング をすることにしました。 「ワーカーが設定した検索条件による絞り込み」と「良いマッチングを生むためにタイミーが実施している可視性の制御」の2つに処理が分かれること 今後の開発で処理の要素を既存処理に影響を与えずに3つ以上にできること 利用するクラスが適用するロジックを選択できること なぜこれらの要素を選択したかを解説します。まず ”「ワーカーが設定した検索条件による絞り込み」と「良いマッチングを生むためにタイミーが実施している可視性の制御」の2つに処理が分かれること”です。まず、ロジックを精査する前にFilterクラスの見通しが悪いということは単一責任の原則に反していると考えていました。実際に話し合ったところ2つの責務が見えてきたので責務が単一になるように分割することを考えます。 次にその他2つに関してはオープンクローズドの原則を考えたものです。タイミーのフィルタリングロジックはまだまだ発展途上で、今後どんどん拡張される可能性があります。その時に簡単に処理を追加・削除できる状況を作ることを考えると重要であると考えました。 実施した リファクタリング 結論から行くと、以下のようなbefore-afterとなっています。 Offeringは求人を表現しているモデルで、Userはワーカーです。 before クラス図 Filterクラスの利用例 offerings = :: Offering :: Filter .new(current_user, search_params) .result after クラス図 Filterクラスの利用例 offerings = :: Offering :: Filter .new( conditions : [ :: Offering :: Filter :: SearchCondition .new(search_params), :: Offering :: Filter :: Viewable .new(current_user) ]) .result よくみるとわかるのですが、ストラテジパターンを用いて リファクタリング をすることとなりました。SearchConditionクラスが「ワーカーが設定した検索条件による絞り込み」を担い、Viewableクラスが「良いマッチングを生むためにタイミーが実施している可視性の制御」を担っています。 この変更によって元々考えていた要素は以下のように達成されました。 「ワーカーが設定した検索条件による絞り込み」と「良いマッチングを生むためにタイミーが実施している可視性の制御」の2つに処理が分かれること → SearchConditionとViewableの2つに分かれた 今後の開発で処理の要素を既存処理に影響を与えずに3つ以上にできること → Conditionのインターフェースを持ったクラスは追加可能、Conditionを追加しても他のクラスに影響はない 利用するクラスが適用するロジックを選択できること → 引数でConditionを選択することで制御が可能 クラスや省略したコードだけ見るとかなり複雑に見えますが、まとまりごとで制御できるので全体としてはすっきりしたと思います。 結果と所感 今回の リファクタリング によって検索にかなり変更を加えやすくなりました。実際に リファクタリング 後に検索を3度ほど触っていますがかなり機能の追加はしやすくなりました。早くロジックが大きくして、いろんなConditionが見つかると良いなと思っています。 ここまでの話で特に話していませんでしたが、この リファクタリング は スクラム 開発の中でプロダクトゴールを最速で達成する一環として実施しています。品質が開発速度に好影響を与えた例としてとても感慨深かったことを覚えています。 ところで、 Ruby でストラテジパターンを実現する時って皆さんどうしていますか?今回は以下のような抽象クラスのようなものを作成しました。ただ、今回の用途で継承を利用するのはToo muchであると思います。何かいい方法があれば教えていただけると嬉しいです。 # frozen_string_literal: true class Offering class Filter class Condition # 一定の検索条件で絞り込むために利用される。 # Offering::Filterクラスで利用するinterfaceとなる。 # # @param [Offering::ActiveRecord_Relation] offerings # @return [Offering::ActiveRecord_Relation] def apply (offerings) raise NotImplementedError , " You must implement #{ self .class } # #{ __method__ }" end end end end 終わりに 今回はタイミーの検索を司るFilterクラスを リファクタリング した話を紹介しました。重要な機能には仕様がどんどん追加されます。開発がしにくいと感じた際には責務や基本的な原則に立ち返って リファクタリング してみるのもよいかなと思います。今後もユーザーへの価値提供をしながら技術品質をどんどん高めていきたいと思います。
この記事は Timee Advent Calendar 2023 シリーズ 3 の12日目の記事です。 qiita.com はじめに DREグループでデータエンジニアをやっている 西山 です。 今回は、データ転送まわりの運用自動化について書きます。 タイミーのアプリログが分析できる状態になるまでのリードタイムが長く、効果検証や意思決定に遅れが出ていた問題に対して、dbtに関連する運用を自動化することで改善しました。 タイミーでのアプリログの転送について タイミーではS3に貯まったサーバーログを定期的にデータ基盤(GCPのBigQuery)へ転送しており、ログがLake層へ追加されるとdbt(data build tool)によって型変換等の加工処理がなされてStaging層へと転送されます。 アプリの機能追加によって新しいログテーブルが追加された場合、dbtの処理追加が必要になるため、GitHub Actionsで定期的にLake層とStaging層でログテーブルの差異をチェックし、Lake層に新しく追加されたテーブルがあれば処理追加を促す通知をslackへ送信していました。 通知を確認したら、以下の対応を行います。 新しく追加されたテーブルのスキーマを確認 dbtでmodelを作成 他のログテーブルのmodelをコピー カラム名やキャスト処理を修正 {{ config( alias={{ テーブル名を書く }} ) }} {% set column_lists = [ {{ カラム名を書く }}] %} {{ production_logs_template({{ テーブル名を書く }}, column_lists) }} , result_record AS ( SELECT {{ カラム名を書く }}, dre_transfer_at, ROW_NUMBER() OVER (PARTITION BY {{ カラム名を書く }}) AS row_number FROM records ), stg_record AS ( SELECT {{ 各カラムの変換処理を書く }}, FROM result_record WHERE row_number = 1 ) SELECT *, {{ set_record_exported_at(column_lists) }} AS dre__record_exported_at, FROM stg_record チーム内レビュー dbtのジョブを実行してStging層にもテーブルを追加 基本的には通知を確認次第、上記の対応を行う方針でしたが、他に優先度の高い障害対応や他チームからの依頼があると後回しになってしまうことも多く、1週間以内にStaging層へ新規ログテーブルを転送する取り決めとなっていました。 しかし、これだとログが分析できるようになるまでのリードタイムが長く、プロダクト開発チームの効果検証や意思決定に遅れが出てしまいます。 そこで、今回はこのリードタイムの短縮を目指すことにします。 実装案 アプリのログに関しては、dbtで実装する加工処理の内容がほぼ決まりきっているため、modelの追加が自動化できそうです。 (例) 末尾にidが付くカラムはINT型へキャスト 末尾にatが付くカラムはJSTのDATETIME型へキャスト これまで新しいログテーブルを検知していたGitHub Actionsのツール内で、dbtのmodel追加のPRを生成することにします。 実装してみた 大まかにやったこととしては以下の通りです。 GitHub Apps によるAPIリクエストの準備 pythonで書かれている監視ツールの修正 dbtのmodel(sqlファイル)とtest(ymlファイル)の生成処理追加 GitHubへのリクエスト処理追加 slackへの通知内容修正 GitHub AppsによるAPIリクエストの準備 今回はログテーブルの監視ツールとdbtプロジェクトが格納されているリポジトリが異なったため、GitHub Appsを作成してAPI操作ができるようにします。 以下の手順に沿ってAppを作成 https://docs.github.com/ja/apps/creating-github-apps/registering-a-github-app/registering-a-github-app Appの秘密鍵を取得 https://docs.github.com/ja/apps/creating-github-apps/authenticating-with-a-github-app/managing-private-keys-for-github-apps 認証に必要な INSTALLATION_ID を生成 https://docs.github.com/ja/apps/creating-github-apps/authenticating-with-a-github-app/authenticating-as-a-github-app-installation#generating-an-installation-access-token 3.で生成した INSTALLATION_ID を使ってinstallation access tokenを発行 今回は監視ツールに以下のクラスを追加しました。 import json import os from datetime import datetime, timedelta import jwt import requests class GitHubAppTokenManager : def __init__ (self) -> None : self.GITHUB_APP_ID = os.environ.get( "APP_ID" ) self.GITHUB_APP_PRIVATE_KEY = os.environ.get( "APP_PRIVATE_KEY" ) self.GITHUB_APP_INSTALLATION_ID = os.environ.get( "APP_INSTALLATION_ID" ) def _generate_jwt (self) -> str : PEM = ( self.GITHUB_APP_PRIVATE_KEY.replace( " \\ n" , " \n " ) if self.GITHUB_APP_PRIVATE_KEY is not None else None ) utcnow = datetime.utcnow() alg = "RS256" payload = { "typ" : "JWT" , "alg" : alg, "iat" : utcnow, "exp" : utcnow + timedelta(seconds= 60 ), "iss" : self.GITHUB_APP_ID, } return jwt.encode(payload, PEM, algorithm=alg) def _generate_jwt_headers (self) -> dict : jwt_token = self._generate_jwt() return { "Authorization" : f "Bearer {jwt_token}" , "Accept" : "application/vnd.github.machine-man-preview+json" , } def _fetch_access_token (self) -> str : url = f "https://api.github.com/app/installations/{self.GITHUB_APP_INSTALLATION_ID}/access_tokens" response = requests.post(url, headers=self._generate_jwt_headers()) response.raise_for_status() return json.loads(response.text).get( "token" ) def generate_token_header (self) -> dict : token = self._fetch_access_token() return { "Authorization" : f "token {token}" , "Accept" : "application/vnd.github.inertia-preview+json" , } ここまでできればAPIのリクエスト準備完了です。 dbtのmodelとtestの生成処理追加 GitHub APIをリクエストする前にコミット対象となるファイル生成の処理を追加します。 まずはmodelのsqlファイルとtestのymlファイルのテンプレートを用意します。 ↓modelのテンプレートです。 dbtで使っているJinjaに反応して意図したところで値が置き換わらなくなるので、一部エスケープしています。 やっぱり多少見づらくなるので他に良い方法はないだろうか・・・ {{ ' {{ ' }} config( alias= ' {{ table_name }} ' , ) {{ ' }} ' }} {{ ' {% ' }} set column_lists = {{ column_list }} {{ ' %} ' }} {{ ' {{ ' }} production_logs_template( ' {{ table_name }} ' , column_lists) {{ ' }} ' }} , result_record AS ( SELECT {{ select_list }}, dre_transfer_at, ROW_NUMBER() OVER (PARTITION BY {{ partition_column_list }}) AS row_number FROM records ), stg_record AS ( SELECT {{ jst_converted_select_list }}, dre_transfer_at, {{ ' {{ ' }} jst_now() {{ ' }} ' }} AS dre_delivered_at, FROM result_record WHERE row_number = 1 ) SELECT *, {{ ' {{ ' }} set_record_exported_at(column_lists) {{ ' }} ' }} AS dre__record_exported_at, FROM stg_record ↓testのテンプレートです。ログの一意性を担保するテストを追加します。 version : 2 models : - name : TABLE_NAME tests : - dbt_utils.unique_combination_of_columns : combination_of_columns : COLUMN_LISTS 次に以下のクラスを追加します。 BQから追加対象テーブルのカラム名を取得して generate_dbt_file_info 関数に渡すことで、先ほど作成したテンプレートを元にファイルが生成されます。 import json import os import re import tempfile from typing import Any, Dict, List, Tuple from jinja2 import Environment, FileSystemLoader from ruamel.yaml import YAML class DBTModelFileGenerator : def _convert_column_with_at (self, column_name: str ) -> str : return f "TIMESTAMP({column_name})" def _convert_column_with_id (self, column_name: str ) -> str : return f "CAST({column_name} AS INT)" def _convert_utc_to_jst (self, column_name: str ) -> str : return f "{{{{ timestamp_utc2datetime_jst('{column_name}') }}}}" def _generate_select_list (self, column_list: List[ str ]) -> str : converted_columns = [] for col in column_list: if col.endswith( "_at" ): converted_columns.append( f "{self._convert_column_with_at(col)} as {col}" ) elif re.search( r"(_id$|^id$)" , col): converted_columns.append( f "{self._convert_column_with_id(col)} as {col}" ) else : converted_columns.append(col) return ", \n " .join(converted_columns) def _generate_jst_converted_select_list (self, column_list: List[ str ]) -> str : converted_list = [] for col in column_list: if col.endswith( "_at" ): converted_list.append(f "{self._convert_utc_to_jst(col)} as {col}" ) else : converted_list.append(col) return ", \n " .join(converted_list) def _generate_staging_sql (self, table_name: str , column_list: List[ str ]) -> str : select_list = self._generate_select_list(column_list) partition_column_list = ", " .join(column_list) jst_converted_select_list = self._generate_jst_converted_select_list( column_list ) # テンプレートのロード file_loader = FileSystemLoader( "テンプレートファイルのパス" ) env = Environment(loader=file_loader) template = env.get_template( "template_stg_production_logs.sql" ) # テンプレートのレンダリング sql = template.render( table_name=table_name, column_list=column_list, select_list=select_list, partition_column_list=partition_column_list, jst_converted_select_list=jst_converted_select_list, ) # tempdirにファイルを保存 output_path = os.path.join( tempfile.gettempdir(), f "{table_name}_stg_production_logs.sql" ) with open (output_path, "w" ) as tmp: tmp.write(sql) return output_path def _generate_staging_test (self, table_name: str , column_list: List[ str ]) -> str : yaml = YAML() yaml.indent(sequence= 4 , offset= 2 ) with open ( "テンプレートファイルのパス" , "r" , ) as template_file: template_content = yaml.load(template_file) template_content[ "models" ][ 0 ][ "name" ] = f "{table_name}_stg_production_logs" test_definition = template_content[ "models" ][ 0 ][ "tests" ][ 0 ] test_definition[ "dbt_utils.unique_combination_of_columns" ][ "combination_of_columns" ] = column_list output_path = os.path.join( tempfile.gettempdir(), f "{table_name}_stg_production_logs.yml" ) with open (output_path, "w" ) as tmp: yaml.dump(template_content, tmp) return output_path def generate_dbt_file_info ( self, new_table_and_column_names: List[Dict[ str , Any]] ) -> List[Tuple[ str , str , str ]]: files = [] for record in new_table_and_column_names: table_name = record[ "table_name" ] record_dict = json.loads(record[ "record" ]) column_list = list (record_dict.keys()) sql_file_path = self._generate_staging_sql(table_name, column_list) test_file_path = self._generate_staging_test(table_name, column_list) files.append( ( f "追加先のdbt modelのパス/{table_name}_stg_production_logs.sql" , sql_file_path, f "Add staging SQL for {table_name}" , ) ) files.append( ( f "追加先のdbt testのパス/{table_name}_stg_production_logs.yml" , test_file_path, f "Add staging test for {table_name}" , ) ) return files GitHubへのリクエスト処理追加 以下のクラスを追加し、 create_pr 関数に前段で生成したファイルの情報を渡すことでPRが生成されます。 認証にはAPIのリクエスト準備で発行したinstallation access tokenを使います。 import base64 import json import re from typing import Any, Dict, List, Tuple import jwt import requests from deleted_logs.adapter.output.github_app_token_manager import GitHubAppTokenManager class GitHubPRCreator : def __init__ (self, session_start_time: str ) -> None : self.OWNER = "XXX" self.REPO = "XXX" self.BASE_BRANCH_NAME = "main" numeric_only_session_start_time = re.sub( r"\D" , "" , session_start_time) self.NEW_BRANCH_NAME = f "feature/add_new_table_of_production_logs_to_staging_{numeric_only_session_start_time}" self.github_app_token_manager = GitHubAppTokenManager() def _is_branch_present (self) -> bool : headers = self.github_app_token_manager.generate_token_header() branch_url = f "https://api.github.com/repos/{self.OWNER}/{self.REPO}/git/ref/heads/{self.NEW_BRANCH_NAME}" response = requests.get(branch_url, headers=headers) if response.status_code == 200 : return True elif response.status_code == 404 : return False else : response.raise_for_status() return False def _create_branch (self) -> None : if self._is_branch_present(): return headers = self.github_app_token_manager.generate_token_header() # ベースブランチのSHAを取得 base_ref_url = f "https://api.github.com/repos/{self.OWNER}/{self.REPO}/git/ref/heads/{self.BASE_BRANCH_NAME}" response = requests.get(base_ref_url, headers=headers) response.raise_for_status() sha = response.json()[ "object" ][ "sha" ] # ブランチ作成 branch_ref_url = ( f "https://api.github.com/repos/{self.OWNER}/{self.REPO}/git/refs" ) data = { "ref" : f "refs/heads/{self.NEW_BRANCH_NAME}" , "sha" : sha} response = requests.post(branch_ref_url, headers=headers, json=data) response.raise_for_status() def _upload_file_to_repository ( self, git_file_path: str , local_file_path: str , message: str ) -> None : headers = self.github_app_token_manager.generate_token_header() with open (local_file_path, "r" ) as file : content = file .read() encoded_content = base64.b64encode(content.encode()).decode() url = f "https://api.github.com/repos/{self.OWNER}/{self.REPO}/contents/{git_file_path}" # 同様のファイルが既に存在するか確認 response = requests.get(url, headers=headers) sha = None if response.status_code == 200 : sha = response.json()[ "sha" ] data = { "message" : message, "content" : encoded_content, "branch" : self.NEW_BRANCH_NAME, } if sha: data[ "sha" ] = sha response = requests.put(url, headers=headers, json=data) response.raise_for_status() def _push_files (self, files: List[Tuple[ str , str , str ]]) -> None : self._create_branch() for git_file_path, local_file_path, message in files: self._upload_file_to_repository(git_file_path, local_file_path, message) def _generate_pr_title (self, table_names: List[ str ]) -> str : return f "production_logs新規テーブル({','.join(table_names)})追加" def _generate_pr_body (self, table_and_column_names: List[Dict[ str , Any]]) -> str : header = "このPRはdeleted_logsによって自動生成されたものです。 \n\n 以下のテーブルのステージング処理を追加しています: \n " lines = [] for record in table_and_column_names: table_name = record[ "table_name" ] record_dict = json.loads(record[ "record" ]) columns = ", " .join(record_dict.keys()) lines.append(f "**{table_name}** \n Columns: {columns} \n " ) return header + " \n " .join(lines) def create_pr ( self, table_names: List[ str ], column_names: List[Dict[ str , Any]], files: List[Tuple[ str , str , str ]], ) -> str : self._push_files(files) headers = self.github_app_token_manager.generate_token_header() url = f "https://api.github.com/repos/{self.OWNER}/{self.REPO}/pulls" title = self._generate_pr_title(table_names) body = self._generate_pr_body(column_names) data = { "title" : title, "body" : body, "head" : self.NEW_BRANCH_NAME, "base" : self.BASE_BRANCH_NAME, } response = requests.post(url, headers=headers, json=data) response.raise_for_status() return response.json()[ "html_url" ] こんな感じのPRが生成されました。 ※テストで作成したものなのでクローズしてます。 slackへの通知内容の修正 前段で生成したPRのURLを通知メッセージに追加します。 ↓テストで飛ばした通知はこんな感じです。 ここで通知されたPRのレビューとマージを行うことで、Staging層のdbt modelが作られるようになりました。 まとめ 今回dbtのmodel生成を自動化したことで、2つの効果が得られました。 1つ目は、トイルの削減です。 これまで以下のフローで対応していましたが、 既存のdbt modelをコピペしてPR作成 → レビュー依頼 → マージ 自動化したことで、 PRを確認してマージ だけででよくなりました。 元々大した工数はかかっていなかったものの、繰り返し発生する価値のない定型作業を減らすことができました。 そして2つ目は、Staging層へ新規ログテーブルが反映されるまでのラグ短縮です。 自動化したことで、他の対応でひっ迫している際に後回しにされることがなくなり、 これまではログ出力し始めてから分析可能になるまで最長7日かかっていたところ、導入後は大体1日以内で分析可能な状態になりました。 個人的には、タイミーにジョインするまでDevOps的なことがやれていなかったこともあり、運用は地味でつまらないイメージがあったのですが(すみません)、こうやって改善がまわせると運用も面白いなと最近思うようになりました。 We’re Hiring DREグループではまだまだやっていきたいことがたくさんあるのですが、まだまだ手が足りておらず、ともに働くメンバーを募集しています!! product-recruit.timee.co.jp
この記事は "Timee Advent Calendar 2023" の11日目の記事です。 qiita.com こんにちは、タイミーのデータ統括部データサイエンス(以下DS)グループ所属の菊地です。 今回は、定例会議の議事録をNotion DBで構造化して、いい感じにした話を紹介したいと思います! 前提 タイミーでは社内ドキュメントツールとしてNotionを採用しており、私が担当しているプロジェクトで週1回開催される定例では、議事録をNotion DBとして管理しています。当初は以下のような定例議事録用テンプレートを作成して運用していました。 定例の内容としては、プロジェクト進行上同期的に議論すべき アジェンダ を定例出席者が持ち寄って議論し、決定事項とToDoを記載していくような内容となっています。 旧定例議事録テンプレート 上記の定例議事録で感じていた課題 上記の定例議事録用テンプレートから定例の議事録を作成して運用していた際に、以下のような課題を感じていました。 「 アジェンダ 」パート アジェンダ が多いと一覧性が悪く、優先度の高い アジェンダ を先に話すなどの判断がしづらい アジェンダ に関する議論が複数回にわたる際に、議論内容が複数ページに渡り、情報が追いにくい アジェンダ が多く、その回の定例で話せず持ち越しになった場合に、次回の議事録に転記する必要がある 「決定事項」パート 何の アジェンダ について、どんな意思決定が行われたのかが追いにくい ある決定事項がどこに書かれていたかを調べたい際に、決定事項が書かれた会の議事録のページを探し出す必要がある 「ToDo」パート 前回の定例で決まったToDoを確認するために、前回の議事録を参照or次回の議事録に転記する必要がある 進行状況が分からない 期限が不明瞭 やったこと 上記で感じていた課題を解消するために、下記のように「構成要素」と「構成要素間の関係」をNotion DBで整理(構造化)し、合わせてNotion DBテンプレートページの作成を行いました。 「 アジェンダ 」パート アジェンダ を管理するNotion DB(Agendas)を作成し、議事録ページからはLinkded viewで参照する 「決定事項」パート 決定事項を管理するNotion DB(Decisions)を作成し、議事録ページからはLinkded viewで参照する Agendas DBとrelationsを持たせる。 「ToDo」パート プロジェクトのタスク管理で使用しているNotion DB(ここでは「Tasks」とします)をそのまま流用し、議事録ページからはLinkded viewで参照する 議事録から参照する際は、定例で作成されたToDoタスクだと判別できるようにtagをつけてフィルターを設定して絞り込む Agendas DBとリレーションを持たせる Notion DBのリレーション Notion DBのリレーションは以下のような構成になっています。 Meetings: 議事録を管理するNotion DBで、Agendas、Decisions、TasksをLinked Viewで参照する Agendas: 会議の アジェンダ を管理するNotion DB Decisions: 会議の決定事項を管理するNotion DB Tasks: プロジェクトのタスクを管理するNotion DB erDiagram Meetings Agendas ||--o{ Tasks: "1つのAgendaは0以上のTaskを持つ" Agendas ||--o{ Decisions: "1つのAgendaは0以上のDecisionsを持つ" Notion DBテンプレートページ 定例議事録と、定例議事録内の各 アジェンダ のNotion DBテンプレートページを以下のように作成しました。 「Meetings」DBの定例議事録テンプレート 「ToDo」パートの「Tasks」は、「ステータス」が「完了」以外を表示するようにフィルタを設定 「 アジェンダ 」パートの「Agendas」は、「解決済み」が「チェックなし」のみを表示するようにフィルタを設定 「決定事項」パートの「Decisions」は、決定事項が定例会議実施日のみに作成されるという想定の元、「作成日時」のフィルタ条件として、定例実施日に設定する運用にする 新定例議事録テンプレート 「Agendas」DBの アジェンダ テンプレート 「ToDo」パートの「Tasks」は、フィルタ条件「Agendas」をテンプレートページに設定しておくことで、テンプレートから作成されたページが自動で設定されるように 「決定事項」パートの「Decisions」は、フィルタ条件「Agendas」をテンプレートページに設定しておくことで、テンプレートから作成されたページが自動で設定されるように アジェンダ テンプレート 結果 定例議事録ページで感じていた課題は、議事録の「構成要素」と「構成要素間の関係」をNotion DBで整理(構造化)することで、以下のように解消することができました。 「 アジェンダ 」パート アジェンダ の一覧性が悪く、優先度の高い アジェンダ を先に話すなどの判断がしづらい → アジェンダ がDBのページとして表現されることで、一覧として表示することができ、一覧性が向上 → DBは一覧の並び替えができるので、優先度の高い アジェンダ を上に持ってくることが可能になった アジェンダ に関する議論が複数回にわたる際に、議論内容が複数ページに渡り、情報が追いにくい → アジェンダ に関する議論は、 アジェンダ ページに集約されるので、議論内容が追いやすくなった アジェンダ が多く、その回の定例で話せず持ち越しになった場合に、次回の議事録に転記する必要がある → 解決していない アジェンダ は自動的に次回に持ち越されるようになり、転記が不要になった 「決定事項」パート 何の アジェンダ について、どんな意思決定が行われたのかが追いにくい → アジェンダ に紐づけて決定事項をDBとして管理することで、意思決定が追いやすくなった ある決定事項がどこに書かれていたかを調べたい際に、決定事項が書かれた会の議事録のページを探し出す必要がある → 決定事項はDBにまとまっていて、 アジェンダ も紐づいているので、情報の検索性が向上し、素早く決定事項にたどり着けるようになった 「ToDo」パート 前回の定例で決まったToDoを確認するために、前回の議事録を参照or次回の議事録に転記する必要がある → ToDoはLinked viewとして次回定例に引き継がれるので、前回の議事録を参照する必要性も、次回の議事録に転記する必要性もなくなった 進行状況が分からない → DBとして表現することで進行状況をpropertyとして表現することが可能になり、進行状況を追えるようになった 期限が不明瞭 → DBとして表現することで、期限をpropertyとして表現することが可能になり、期限が明確になった まとめ 今回は、定例会議の議事録をNotion DBで構造化して、いい感じにした話を紹介しました。 タイミーではNotionをフルに活用しており、私が所属しているデータ統括部DSグループでも、 スクラム 運用や開発チケット管理など、さまざまな場面でNotionを活用して業務を効率化しています。 今後も定期的に、Notionの活用について発信していきたいと思います! We’re Hiring! タイミーのデータ統括部では、ともに働くメンバーを募集しています!! 現在募集中のポジションは こちら です! 「話を聞きたい」と思われた方は、是非一度 カジュアル面談 でお話ししましょう! mermaid.initialize({startOnLoad: true});