TECH PLAY

タイミー

タイミー の技術ブログ

274

こんにちは、データ統括部でデータアナリストをしているyuzukaです。 今回は、データ統括部のアナリストを対象に実施した「ストレングスファインダー共有会」について紹介します。 ストレングスファインダーとは? ストレングスファインダー とは、米国のギャラップ社が開発した「強みの診断」ツールです。 WEB上で177個の質問に答えると、34の資質の中から自分の強みや資質を知ることができます。 診断は有料ですが、弊社では会社の経費で受けられます。(ありがたい・・・!) 私の強みTOP10はこんな感じでした。 ストレングスファインダーに着目した背景 個人の強みに着目するコンセプト 職場での性格診断に適したツールとして、MBTIやDISCなど有名どころはいくつかありますが、中でもストレングスファインダーは個人の「強み」にフォーカスしているところが良いと感じました。 ただ相互理解を深めるだけでなく、それぞれの強みを仕事に活かしていこう、という前向きな気持ちになることができます。 実際にストレングスファインダー共有会で利用した資料 実際に取り組んだこと 事前準備 ストレングスファインダー共有会の開催にあたり、準備した内容は以下の通りです。 ストレングスファインダーのアクセスコードを、人数分まとめて購入して配布する 参加者にはあらかじめ診断を受けてもらい、診断結果とひとこと所感を記入しておいてもらう 参加者に負担がかからないかつ、当日の進行がスムーズになるようにロジ周りを意識しました。 ストレングスファインダー共有会の内容 オンラインで集まり、和気あいあいとした雰囲気で強みの共有会を行いました。 ルーレットでランダムに指名して、1人ずつ自分の強みを発表 事前に記入してもらった診断結果と所感を見ながら、4-5分話してもらう みんなでわいわい、感想や質問を言い合う これを人数分繰り返す形式で実施しました。 当日は想定していた以上に盛り上がり、時間があっという間に過ぎ去りました。 「新しいことを始めるのが得意」「学習欲が強い」「目標に向けて、人一倍努力できる」「ゲームのオリジナルルールを考案するのが得意」など、参加者それぞれのユニークな強みを知ることができました。 おわりに ストレングスファインダー共有会の開催からしばらく経ちますが、 通常業務のコミュニケーションの中でも、お互いの強みに言及する機会が増えた ように思います。 データ統括部は今後も積極的にメンバーを増やしていく予定なので、ストレングスファインダー共有会も、メンバーが増えたタイミングでの定期開催を考えています。 We’re Hiring! タイミーのデータ統括部では、私たちとともに働くメンバーを募集しています。 product-recruit.timee.co.jp
アバター
この記事は "Timee Advent Calendar 2023" の2日目の記事です。 qiita.com こんにちは、株式会社タイミーの土川 ( @tvtg_24 ) です。 先日、マネー フォワ ードさんとアナリティクスエンジニアリング周りの合同勉強会を開催しました! 自分が所属するデータ統括部では初めての試みだったんですが、とても良い会になったと思うので少しこの記事で紹介できたらと思います! きっかけ マネー フォワ ードの木宮さん ( @yuu_kimy ) と自分は定期的にご飯に行っていて、いつもお互いのデータ関連の話をたくさんさせていただいてました! そんな中、もっと社内のメンバー巻き込んで情報交換したいね、みたいなお話をしていたところ、ちょうどRettyさんとマネー フォワ ードさんで合同勉強会を開いたとのことでしたので、それを参考に合同勉強会を実施しました。 木宮さんには、当日の アジェンダ 作成など合同勉強会実施のための諸々準備をしていただいて、とても感謝してます! 当日の様子 当日の アジェンダ アジェンダ は以下のようになっていて、発表は各社2つずつ行い、最後に時間が許す限りフリーディスカッションでdbtについて話しました。 会自体はオンラインで行い、お互いの会社から計10名以上が参加する賑やかな会になりました! アジェンダ 発表振り返り 発表はタイミー側から 大越さんと、okodoon ( @miburo_data ) が、それぞれ「データアナリストがdbtを触ってみた」、「dbtジョブ分割実行について色々考えた話」というテーマで発表しました。 マネフォさん側からは、奥野さん ( @RossOkuno )と木宮さんが、それぞれ「マルチテナント分析基盤について」、「dbtをAirflowで動かす道のりは続く」というテーマで発表しました。 発表のダイジェストについてはマネフォさん側の記事 ( https://note.com/yuu_kimy/n/n236c5d5047ad ) で紹介されているので、詳しくはそちらをご覧ください。 マネフォさんとタイミーは使っているデータ基盤サービスは似ているんですが、プロダクトの数や、事業規模が違ったりするため、複数組織でのデータ分析環境の提供についてのノウハウや、苦労したところなどがたくさん聞けて、すごく勉強になりました。また、Cosmosといったタイミーが使っていない新しめの python パッケージについての利用感が聞けたのはとてもよかったです。タイミーでも今後の参考にしていきたいと思いました。 dbt活用に関するフリーディスカッションについて フリーディスカッション 会の最後には、dbt活用に関するフリーディスカッションを1時間ほどしました。 発表自体で消化できなかった質問や、dbt Coalesce で話題になっていたdbt Cloud CLI などを使った、dbt Core, dbt Cloudの使い分けについてなど、様々な議論をしました。 また普段あまりオープンな発表では公開しづらい、各々のデータ モデリング の詳細などについて話せたのはとてもよかったと思います。 終わりに 今回は、初めての合同勉強会でしたが、とても楽しく、有意義な時間になりました。 会社ぐるみで一つのテーマに対して、深く議論する機会は普段なかなかないのと、やはりオープンな場より、一歩踏み込んだ情報をもとに議論ができることが個人的にとても良いなと思いました。 また、より良いデータ分析環境を提供するという同じ目標を掲げて試行錯誤しているマネフォさんをみて、タイミーも追いつけるように頑張らないと!という気持ちになりました! これからも合同勉強会のような機会は増やしていけたらなと思っているので、興味のある会社さんはぜひX (旧Tiwtter) でお声かけください! 採用関連 タイミーは絶賛採用中です!ぜひお力を貸してください! hrmos.co
アバター
はじめに こんにちは、マッチング領域でバックエンドエンジニアをしているぽこひで (  @pokohide  ) です。 タイミーの アドベントカレンダー 2日目の記事です。 今回は、タイミーのプロダクト組織で毎週開催している 技術的な雑談を行うテック トーク の紹介をします。なぜ開催しようと考えたか、どのように運用をしているかなどをお話しします。 はじめに 開催の背景 毎週ゆるく開催するテックトークについて テックトークの仕組み化 会の説明や目的の共有 WINの共有 ポストモーテムの学び共有 雑談タイム やってみて さいごに 開催の背景 タイミーのプロダクト組織では、働き方の柔軟性を担保する観点などから フルリモートという働き方を選択 しています。また、タイミーではチーム トポロジー を採用しており、それに沿ってチーム構成などを考えています。 チーム トポロジー の変遷や取り組みについてはCTOとCPO(発表当時はVPoT)が過去に発表を行っているので詳しく知りたい方はこちらをご覧ください。 チームトポロジー Vol. 2 「組織をチームトポロジーで振り返るメリット」タイミー 亀田 彗 | ITエンジニア向けのトレンド情報 組織をスケールさせるための Four Keys とチームトポロジー - Speaker Deck チーム トポロジー によって、チームが 疎結合 になることでコミュニケーションの複雑性が抑えられたりチームの柔軟性が増すなど利点はありますが、インフォーマルコミュニケーション(業務外の日常的な会話や雑談)が減り仲間意識が薄れる可能性もあります。さらに、フルリモートはこの流れを助長する可能性があります。 疎結合 性を確保しつつも適度なコミュニケーションや協力を促進するバランスが重要と考え、テック トーク という会を開催 *1 しようと考えました。 2023年9月に発売された「GitLabに学ぶ 世界最先端のリモート組織のつくりかたドキュメントの活用でオフィスなしでも最大の成果を出すグローバル企業のしくみ」でもリモート組織を作る観点でですが、インフォーマルコミュニケーションが従業員のパフォーマンス向上や メンタルヘルス の問題解決に重要な役割を果たすという研究結果が紹介されています。 GitLabのカルチャーは、「GitLab Value 」「仲間意識(信頼と友情)」「ワークスタイル」という3つの要素で構築されており、仲間意識を醸成するためにインフォーマルコミュニケーション(業務外の日常的な会話、雑談、何気ないやり取り)が「意図的」に設計されています。 オフィスなしのオールリモートで成長するGitLab社、世界中の2,000人をつなぐカルチャーとは|CodeZine(コードジン) から引用 と、それっぽい理由を書いていますが、他のチームで起きた技術的に面白い事やその時々の興味深いテックニュースなどをゆるく駄弁りたいという個人的な欲求が発端というのもあります。 毎週ゆるく開催するテック トーク について 2023年2月から毎週金曜日に夕方から最長1時間枠で開催している技術的な雑談を行う会です。 SlackのHuddleを利用しており、会話から抜ける時の確認は不要で入退室は自由です。 テック トーク 技術的な話題である事のみが制約で、あとは雑談でもLTの練習場でも何でも自由に使って良い時間としています。 話題は持ち寄り制で、最近の技術ネタや面白いPRなどがあれば自由に書いてもらう形式で運用しています。何もなければその場の雰囲気で会話したり、早めに会を終わらせています。 とはいえ、持ち寄るのも大変なので会の冒頭10分に、1週間にあった技術的なWINやポストモーテムの学びを共有する時間を設けています。ここからさらに深掘りたいものがあれば話題に移して話しましょうといった流れです。 テック トーク の仕組み化 テック トーク を開催するにあたって、参加者に満足してもらいたいのは勿論ですが、それ以前に 継続的に開催する事 が大切と判断し、そちらを重視しました。 そこで運用コストを最小化するためにも内容をシンプルにし、仕組み化にもこだわっています。 開催するからには気軽に参加してもらいたいのでSlackbotのリマインダーを活用しつつ、Slack Huddle上で開催しています。当初は Google Meetを使っていましたが、カレンダーに招待されていないと暗黙的でクローズドな会と誤解されると思ったからです。 時間配分や会の内訳はシンプルです。 会の説明や目的の共有 WINの共有 ポストモーテムの学び共有 雑談タイム 以上です。前半の共有は経験則的に長くとも10分程度で終わります。タイムキーパーを用意していません。あとは適当に駄弁るだけです。 会の説明や目的の共有 会によっては、初めて参加してくれる方もいるので「この会は、テックな話題である事以外は特に制約はなく、入退室も自由なゆるい会ですよ」とお伝えしています。 WINの共有 タイミーのプロダクト組織では、技術的に面白い取り組みやドキュメントの更新、エラー対応、パフォーマンス改善など文字通り WIN! と感じたものに :win: スタンプをつける文化があります。そこで、 :win: スタンプが付いた投稿を特定のチャンネルに集約して、テック トーク で触れやすくしています。 ここでは、WINを共有するだけに留め、パフォーマンス改善などその背景にあったツラミをもう少し深掘りたい場合は、後続の雑談タイムで話すようにしています。 余談ですが、 :win: というスタンプはあまり汎用的すぎる名前で他の部署がつけたWINも収集してしまい収集チャンネルがノイ ジー になっていました。そこで、 メンショングループXに所属するユーザーがスタンプYをつけた時にチャンネルZに通知するSlack BOT を作りました。 この BOT については、またどこかで紹介できればと思います。 この辺も今後はNotionに自動で転記するなどより自動化していきたいですね。 ポストモーテムの学び共有 ポストモーテムをNotionのDBで管理しているので、その週に開催されたポストモーテムだけを表示するようにフィルタリング条件を調整し、準備不要で触れられるようにしています。 ここでも時系列を振り返るのは時間をとりすぎるので、影響範囲や原因、学びのまとめのみを共有しています。 雑談タイム この時間に話す話題をDBの1レコードとして管理しています。最近は参加者も話題も増えたため、気になる人は投票してもらう制度を導入し、どの話題から触れていくかを決めています。 議事録は話題レコードの中に雑に記載しています。話題をDB管理しているのは、テック トーク での雑談からTechBlogや登壇のネタになると嬉しいな考えているからです。これも特に目的として明文化してませんが、この仕組みが機能していく事に乞うご期待です。 Rails 7.1に上げるための残作業 論理削除のつかいどころ テク ノロ ジー レーダー9月号を眺めてみる モジュラ モノリス におけるパッケージ間のDB トランザクション の扱い 技術検証を活性化するためにはどうすればいいか 例ですが、過去にはこういった話題が話されていました やってみて 2023年2月から開催し、開催回数は30回を超えました。 準備も特に必要なく、自分もただの一参加者気分で参加できているので気負わず継続できているのも良いことです。当初考えていた技術的な話題を駄弁りたい欲求も満たされ、他のチームメンバーとも交流ができています。 自分だけ述べても公平性に欠けるので、参加してくださっているみなさんのコメントも欲しかったのですが、 アドベントカレンダー の締切間近すぎて間に合いませんでした。なので、これは自分一人の勝手な妄想かもしれないですが、最近は常に6人以上は集まってくれているので客観的に見て満足はしてくれているのでしょう…(そう信じたい笑) さいごに 今回は技術的な雑談を行うテック トーク の紹介や、継続的に開催するためにどのように仕組み化しているかをお話ししました。 テック トーク の参加者は増えてきています。嬉しい悲鳴ですが、参加者が増えすぎるとこの会の主目的である 雑談 が難しくなります。それは今後の課題なので、テック トーク の フランチャイズ 化やランダムに参加者を分割して ブレイクアウト ルームのようにするなど色々試していければと思います。 また、ここで話された話題からテックブログのネタなどが生まれる事を個人的に期待しているのでそちらも乞うご期待ください。 この活動について、より詳しく知りたいと思った方はカジュアル面談受け付けておりますので、是非お話ししましょう!過去の話題や議事録は残っています笑 product-recruit.timee.co.jp *1 : 定例は増やせばいいという訳ではなく適切に開催することが大切です。過去には、技術改善の文化を根付かせるためにプロダクトミライ会議という技術的負債を扱い、どのように解消していくか話し合う会議体を設けていましたが、技術改善の文化が根付いてきたこともあり、テック トーク 開催と並行して現在はクローズしています。
アバター
この記事は Timee Advent Calendar 2023 シリーズ 1の1日目の記事です。 はじめに こんにちは、タイミーでバックエンドエンジニアをしている須貝( @sugaishun )です。昨年は弊社で アドベントカレンダー に取り組んだか覚えていないのですが、今年はなぜかいきなり3トラックで臨むということで、非常に勢いがあるなと思いました。量と勢いで攻めていくところが弊社らしいなと感じています。全て完走できると良いですね。 さて私はその中のひとつのトップバッターということで、タイミーの Rails アプリケーションについて弊社のシニアなエンジニアたちと雑談した内容を座談会風にお伝えできればと思います。事の発端は弊社Slackのバックエンドエンジニアが集まるチャンネルで「タイミーの Rails アプリケーションの健康度はどのくらいなのか?」という会話をしたことでした。その時の私の感想は「人によってけっこう基準が違うなあ」といったものでしたが、改めて話を聞いてみると自分の中で発見がありました。参加者は以下のとおりです。 参加者プロフィール 難波さん( @kyo_nanba ) シニアバックエンドエンジニア ファッションEC系スタートアップの開発責任者やメドピア株式会社の GM などを経て2022年にタイミーに入社。プラットフォームチームの立ち上げやフィーチャーチームとのコミュニケーション設計に取り組み、直近では会社の技術戦略と現場の技術的ロードマップを接続する役割を担う傍ら Rails アプリケーションの設計やリ アーキテクチャ に関わっている。 神速さん( @sinsoku_listy ) バックエンドエンジニア 2022年11月入社。CTO室に所属し、 Rails アプリケーションの開発効率を向上するために型( RBS )の導入やCIの実行速度の改善などの施策に取り組んでいる。 須貝( @sugaishun ) バックエンドエンジニア 2022年1月入社。スポットワークシステム領域でエンジニアとして活動。 Ruby on Rails ChapterのChapter Leadも務める。Leadとついているが偉いわけではない。好きな言葉は「容赦ない リファクタリング 」 では、ここからが本編です。 タイミーの Rails アプリは60点 須貝:以前Slackでタイミーの Rails アプリケーションが健康かどうかという話をしたと思うんですけど、ここでは健康 = アプリケーションの保守性が高い状態ということにして話をしていきたいと考えています。難波さんはけっこう厳し目の評価をしていたと思うんですけど、主観で構わないので今のタイミーの Rails アプリケーションを100点満点で点数をつけるなら何点くらいなんですか? 難波: 60点 くらいですかね。 須貝:やっぱりけっこう厳しい。 神速: 高専 だと 赤点ギリギリ ですね。 難波:不可ではない、という感じです。入った当初の印象は60点くらいでこの1年で色々取り組んできて65点になったかな、というイメージです。タイミーはプロダクトとしては成長できているし、頻繁に障害が起きているわけでもない。世の中に価値を提供できているのでその点では合格だとは思います。 ただ、ここから上を目指していくためには、まだ我々は合格ラインのギリギリ上くらいなんだぞ、と。そういう想いも込めてこの点数にしています。 須貝:具体的にどの辺りが伸びしろだと考えているんですか? 難波:ひとつは Sentryのアラート ですね。僕が考えるヘルシーなプロダクト組織だったら基本的にはSentryのアラートは鳴らないし、鳴ったらみんながそれを直すべき対象だと思ってできるだけ直す。で、直したらちゃんとresolveして、というのができるようになったらもっと点数が上がるかなと思います。 もうひとつは スロークエリが管理できていない 。スロークエリが出ているということはユーザー体験に直結することなので、 バッチ処理 などであればまた話は別ですけど、現在はきちんと管理できていないのは課題ですね。 最後は 自動テストの信頼性 です。テストが全部パスしているということは本番にデプロイしても大丈夫ですよねという状態を期待しているんですが、テストがパスしているから大丈夫とはあまり思えない。 須貝:たしかにテストの信頼性は僕も課題に感じています。テスト カバレッジ 自体は低くはない *1 んですけど、仕様の カバレッジ が高いかというとちょっと不安なんですよね。 反対に良い点は何でしょうか? 難波:システムが モノリス でやっていけているところは良いところだと思います。マイクロサービスと比べると モノリス *2 のほうが保守性が高いと思っているので。 あとはアプリケーションの保守性に直結するわけではないんですが、CIが速いことでしょうか。CIが速いイコール何かあったらすぐわかるので色々なトライもしやすい。結果的に保守性に寄与する良いところだと思います。 須貝:CIは今何分くらいでしたっけ? 神速:デプロイまで含めると12〜13分くらいですかね。CIだけで見ると8、9分くらいだと思います。 難波:最後にもうひとつ、 github -flowで運用できていることも良い点だと思います。いわゆるgit-flowのようにreleaseブランチ的なものを用意して、QAしてmainブランチにマージしてデプロイというのもそれはそれで良いやり方だとは思います。ただ、mainブランチにマージしたら即デプロイという github -flowはコードの品質をみんなが信頼していないとできないことだと思います。 カナリア リリースが普通、みたいなチームを目指したい 須貝:神速さんは100点満点で点数をつけるなら何点くらいですか? 神速:45とか50点ですかね。 須貝:だいぶ低いですね(笑) 神速:自分の中での100点となると GitHub 社みたいにバンバン本番にデプロイして、 カナリア リリースも当たり前で、みたいな組織です。さらにテストコードも Rails 本体の リポジトリ と同じレベルのクオリティで書く。それが私の中の理想で、それと比べたら今は45点くらいかなと。 須貝:デプロイの話が出てくるあたりが面白いですね。神速さんらしいといいますか。 神速:これは私がインフラもやっているからとかではなく、 顧客へ価値をいかに早く届けるか ですとか、障害が起きた時もいかに早く直すかというのを大事にしていて、デプロイが大事だと思っているのが根底にあります。 須貝:神速さんはどういう状態が健康と考えていますか? 神速:健康さでいうとまず、 Ruby と Rails の最新のバージョンを使うことです。あとはこれに付随して、 Ruby と Rails を最新のバージョンにするためにgemのバージョンを上げ続ける運用があるか。dependabotの整備などもこれにあたります。 あとはテストコードがあるとかレビューをしている、とかでしょうか。難波さんに比べてだいぶ基準が低いんですけど(笑) 須貝:これは過去に経験してきた現場によって基準は変わってきそうですよね。テストを書いているとかレビューしているとか、自分は当たり前だろうと思うんですけど勉強会などで他社のエンジニアと話すと「テストないです」「レビューもないです」というのは普通に耳にします。 神速:ただそこを基準にするよりは もっと上を見たい ですね。 カナリア リリースが普通、みたいなチームを目指したい。 須貝:タイミーの良いところは? 神速:難波さんが挙げてくださった以外だと、 CIでRailsのedgeを使ってテストを走らせている のはレアなのでそこは良いところです。それに付随して弊社のエンジニアが Rails の最新機能を教えてくれる、みたいな福利厚生がある。 後は RBS のような最新機能を導入することにみんなポジティブで、止める人がいない。 やってみようという空気がある のは良い点です。 須貝:やっていきのある人が多いですよね。だから一回やってみましょうという文化がある。逆に伸びしろは? 神速:さっきデプロイ時間の話がありましたけど、私はもっと速くしたい。今はデプロイまでの時間が長いと思っています。 Rails アプリの話でいうと、難波さんと同じでテストの書き方とか保守性はもうちょっと考えたいですね。バグがあったらバグを再現するテストを書くとか。機能を追加する時も既存のテストに手を入れるのではなく、テストを追加して カバレッジ を高めるような考え方は広まってほしいとは思います。 須貝:パフォーマンスの自動テストはちょっと難しそうですね。 神速:でもたとえばコミットログに残すとかはできそうですけどね。パフォーマンスのテストは増やさないにしても、 Rails 本体でもパフォーマンスを計測してコミットメッセージに残したり、 SQL のEXPLAINの内容を貼ったりとできることはあります。「私が速くなったと思うから」ではなくて根拠はほしい。 他の伸びしろを挙げるならレビューの仕方ですかね。「良さそう」でapproveではなく、パフォーマンスや保守性まで考慮したレビューがほしい。それを気にする人が増えてほしいです。ただ良いレビューをする方法を私が教育したいかというとそれは難しいな、という気持ちがあります。 難波:その気持ちはよくわかります(笑)。 須貝:課題感はあるけど自分が率先してやりたいわけではないという。そういったものは Rails に限らず社内にはいくらでもありますよね。 ここまででタイミーでの開発の良いところや伸びしろについて語ってきましたが将来的にこうなっていてほしいというイメージを伺えますか? 難波:なかなかすぐに実現するのは難しいと思うんですけど、 良い設計のシステムにしていきたい ですね。例えば RSpec のcontextがわかりやすいんですけど、contextが多重に 入れ子 になっている時はテストコードの問題というよりはテスト対象クラスの複雑性の問題なんですよね。責務が適切に分解されていないのがテストコードに表出してしまうという。そういうことが起きないようなアプリケーション設計になっていってほしいなと思っています。 須貝:頑張ります。だいたいChapter Leadの僕に返ってくる話ですね。 神速:ただジュニアなエンジニアが頑張っても難しいところはあるので、できる人が教えてあげる環境は何かしら作らないといけないですよね。 難波:そうしたほうが良いというのはすごくわかります。それでいうと今タイミーでは各Squad(チーム)が独立して開発できることを良しとしていて、そこと先輩が教えて知識を伝搬していくことを両方満たそうとすると各Squadに最低一人はミドル〜シニアレベルのエンジニアがいる必要が出てくる。それはけっこう難しいですよね。 須貝:なのでシニアなエンジニアの方、ぜひ弊社に来てくださいという。ちょっとオチがついたっぽい感じになったので今回はこの辺にしておきましょうか。 おわりに いかがでしたでしょうか。弊社のエンジニアは基準が高いなと思いましたね。やっていくしかない。タイミーでの開発についてもっと知りたいという方、ぜひカジュアル面談でお話しましょう。 product-recruit.timee.co.jp 最後になりますが、執筆にご協力くださった難波さん、神速さん本当にありがとうございました。 *1 : 2023/11/27現在、ライン カバレッジ で91.5% *2 : タイミーでは現在モジュラ モノリス 化を進めている
アバター
こんにちは! shun です。私はタイミーのデータ統括部でデータ分析やLooker開発を担当しています。今回は、社内のLooker利用者へのユーザーインタビューを実施し、得られた知見についてお話しします。 背景と目的 データ統括部では、各組織がデータを元に意思決定の質やスピードを向上させビジネス インパク トを生むことを目指して、BIツールとしてLooker上のデータ探索環境を開発、提供しています。 Looker開発依頼の相談窓口の設置や利用者向けの講習会の実施、利用者数のモニタリング等をしていますが、Lookerの使い方や課題感やデータを使った意思決定へのハードルについて、実際の声を聞く機会がなかったため今回のユーザーインタビューを実施しました。 取り組みの全体の流れ 具体事例 ユーザーインタビューを通じて最も顕著な課題としてあがったのが、探索環境内のディメンジョンやメジャーがわかりづらく、意図したデータをスムーズに抽出することが難しいというものでした。 あらゆるディメンジョンやメジャーを開発してニーズへの網羅性を高めることの トレードオフ として、全体のわかりやすさを阻害している現状がわかりました。データ統括部のメンバーはデータへの知見が深く、わかりづらさに気付きづらいという側面もありました。 こちらの課題に対しては開発チームとして、文言を社内 ユビキタス に揃え検索性を上げる、よく使うであろうディメンジョンをまとめる、 ユースケース に沿ったテンプレの ダッシュ ボードを作成するなどの解決を進めている段階です。 工夫したこと インタビュー対象者の選定 Lookerの利用ログを活用して、平均的な利用頻度の利用者を見繕ってアポを取りました。今回の目的としては ボリュームゾーン の方の動向を知りたかったためです。バリバリ使いこなしていたり、ほとんど使っていない利用者だと想定していたインタビューができなかったはずなので、 定量 的な観点から意図した対象者を選定することは大切だと感じました。 実際にデータ抽出をしてもらう 画面共有してもらいながら実際に作業する様子を見せてもらうことで、利用者が詰まる ボトルネック が明確になりました。まさに百聞は一見に如かずな体験でした。 個々のインタビュー結果を持ち寄り、共有知をつくる インタビューをやって終わりではなく複数回実施した後、結果を持ち寄り課題の共通認識を作り、解決のア イデア をブレストする会を実施しました。そして、この場で出たア イデア を開発タスクとして起案し改善するというサイクルを回せました。 今後に向けて タイミーではPMMを中心にユーザー ヒアリ ングを行う風土があり、仕組みやナレッジが蓄積されていたおかげで、横展開を比較的スムーズにできLookerチームでもユーザーインタビューが実施できました。もっと詳しく知りたい方は下記ブログもご覧ください。今後も定期的なユーザーインタビューを実施し、利用者に寄り添ったLookerの環境の開発を進めていければと思っています。 tech.timee.co.jp We’re Hiring もっと具体的に話を聞いてみたい方、データアナリストやLooker開発について、興味を持って頂けた方がいらっしゃればお気軽にお話ししましょう!カジュアル面談からご連絡いただけるとうれしいです product-recruit.timee.co.jp
アバター
はじめに こんにちは! タイミーのデータアナリストのYoです。 今回は、社内で実施した「Looker講習会」を紹介させていただきます。 ご紹介する内容は以下になります。 Looker講習会について なぜLookerか Lookerとは、Google社が提供するBusiness Intelligenceツールです。 弊社では以下の観点から、社内のデータ利活用ツールとしてLookerを採用しています。 SQLを書くことなく、データが確認できる。 閲覧する人によって、利用するデータの定義に相違が生まれない。 一つのBIツールで様々な観点から、データの活用ができる。 なぜ講習会を実施したか 社内のデータ利活用ツールとしてLookerを採用した一方、Lookerを実際に利用できる人が少なく、またどのようなことができるかもわからないという状態になっていました。 そのため、主に以下を目的とし、講習会を実施しました。 新規Looker利用者の増加 既存Looker利用者のスキル底上げ 講習会の内容について 先述の通り、タイミーでは、Looker自体を実際に利用できる人が少なく、どのようなことができるかもわからないというメンバーが多い状態でした。 そのため、Lookerの編集権限を持つ営業組織のメンバーを対象として、以下のような内容で講習会を実施しました。 Lookerを利用する前に、テーブルや、Lookerで利用できるデータの構造について共有しました。 また、上記をベースとしてLookerの利用方法の説明と、ハンズオン形式での演習を実施しました。 実施後の反響について 50名弱の方に講習会へ参加をしていただき、アンケートを通して以下のような声をいただきました。 残課題 社内のデータ利活用に向けて、一定の効果があった一方で、残っている課題もあります。 1回限りの講習会の実施だったため、今後新しく入社した方へLookerが浸透しない。 基礎的な講習会だったため、応用的なLookerの使い方を網羅できていない。 上記の課題の解決に向けて、継続的な講習会の実施や新入社員のオンボーディングへの組み込み、レベル別の講習会実施などを検討していきたいと思います。 まとめ Looker講習会の紹介はいかがでしたでしょうか。 一般的に社内でのデータ利活用が進むと、データを元にした意思決定が広まっていく一方で、意思決定の基礎となるデータのガバナンスが取れないなどの問題が発生します。 その中で、データアナリスト側がコントロールできるBIツールの講習会を実施することで、データアナリストと社内の他のメンバーの両者にメリットがあります。 データアナリストとしては、単純なデータ抽出依頼ではなく、より高度な分析に注力できます。 また、社内メンバーとしては、データアナリストに依頼をすることなく、自身の意思決定に正確なデータを利用することが可能となります。 タイミーは事業、組織ともに拡大傾向にありまして、データアナリストが活躍する機会が多く存在しています。 これからもデータアナリスト発信で社内のデータ利活用に貢献できる仕組みを展開することで、事業の成長に貢献したいと考えています。 We’re Hiring! 私たち、データアナリストと一緒に働く仲間を募集しています。 まだ転職を考えていない方も、ぜひ一度、お話ししましょう! product-recruit.timee.co.jp 最後に、少しでもこの記事を「いいな」と思っていただけた方は、 SNSへシェアをお願いします!
アバター
タイミーの新谷、江田、酒井、正徳です。 Kaigi on Rails 2023 が10月27日、28日の2日間で開催されました。タイミーは去年に引き続きKaigi on Railsのスポンサーをさせていただきました。 また、タイミーには世界中で開催されている全ての技術カンファレンスに無制限で参加できる「Kaigi Pass」という制度があります。詳しくは以下をご覧ください。 productpr.timee.co.jp この制度を使ってオフラインでは4名のエンジニアが参加しました。 参加して聞いたセッションのうち印象に残ったいくつかをピックアップしてご紹介します。 生きた Rails アプリケーションへの Delegated Types の導入 docs.google.com Rails 6.1から導入されたDelegated Typeに関する発表でした。 デプロイ履歴を管理する機能において、AWSのECSやLambdaなどのサービスごとに固有で設定したいデータと、各デプロイ共通のデータ管理をする上で単一テーブル継承(Single Table Inheritance: STI)ではなくDelegated Typeが活用されていました。 タイミーでは一部のテーブル設計でSTIが活用されています。ただ、すべてのサブクラスのすべての属性を持つ単一のテーブルが作られ、nullが避けられない等のデメリットはよくある話だと思いまして、発表の中でも触れられていました。 一方Delegated Typeではスーパークラスの共通データ、サブクラスの固有データを格納するテーブルがそれぞれ作成されます。ポリモーフィック関連をベースにした委譲による実装がなされ、個別データを持つ側のクラスからアソシエーション(今回の発表では has_one)で辿れる仕組みでRails開発者としても直感的に使える仕組みだと感じました。 また各種Gem側での対応も順調に進められており、特にFactoryBotで詰まることなくデータ生成が出来るのは開発・運用の観点でも心理的ハードルが下がる話でした。 そもそもRails 6.1からActiveRecordに導入された機能で、まだ実際に稼働中のプロダクトでの実例もあまり聞いたことが無かったので具体例と共にその特徴を知ることができ、有益な時間になりました。実際に導入する際にはまたこの発表を参考にさせていただこうと思います。 (@edy2xx) 生きた Rails アプリケーションへの delegated types の導入 by mozamimy - Kaigi on Rails 2023 やさしいActiveRecordのDB接続のしくみ speakerdeck.com MySQLへの接続確立するまでのActiveRecord内部の動きを、findメソッドをデバッグ実行した時のスタックトレースを参考に、接続に関する主要なクラスの役割を紹介しながら、内部処理を学ぶセッションでした。 ActiveRecordを用いるとdatabase.ymlに必要な情報を載せるだけでDBにアクセスしデータ取得や操作を行うことができるためあまり意識をすることがありませんでした。しかし、このセッションを通して改めてどのようにDBへの接続を確立・管理するかや、クラスごとの責務を理解できた気がしました。 後半の知見でも触れられていた事ですが、この内部処理を理解することで、接続プールはDBの接続情報を保持したインスタンスを持つため(端折っています)、フェイルオーバーなどでDBホストの変更を行っても、変更以前の接続情報を用いてしまう可能性があります。そのような可能性があることを知っておく事は運用において大事ですし、そのような事態になった時や、そうならないための打ち手を常に持っておく事も重要です。 最後に軽く触れられていたバリデーションクエリの他にも、Railsの再起動等を通して再接続を試みたり、一時的にコネクションプールをやめて都度接続にしたり、クエリ実行時にエラーが発生した場合にエラー内容によってリトライをかけるなど色々あるので改めて理解を深めようと思いました。 (@pokohide) やさしいActiveRecordのDB接続のしくみ by kubo - Kaigi on Rails 2023 Simplicity on Rails - RDB, REST and Ruby speakerdeck.com 偶発的な複雑性が少ない状態をシンプルと定義した上でテーブル設計・API設計・クラス設計の観点でどんなことを意識すべきかを紹介するセッションでした。 テーブル設計:リソースエンティティだけでなくイベントエンティティを見出してhas_many through 関連を作ること。 API設計:テーブルとリソースは似ていることもあるが同じではないので、テーブルに対応しない Controller を作ることは問題ないので気にしなくていいこと。 クラス設計:Controller の create アクションは INSERT INTO 以上のことをしても良いこと。Controller が複雑になってきたと感じたら FormObject の利用を検討すること。 事業としてはイベントエンティティがコンバージョンポイント(お金に変換できるデータという理解をしました)であるという話は確かになと思いました。タイミーのコンバージョンポイントは働くワーカーさんが求人に申し込むマッチングなのですが、ここに対応するモデルは UserOffering という User モデルと Offering モデルの多対多を管理するリソースエンティティとしての命名になっています。様々なものに紐づいているモデルなので変更を加えるのは大変ですが、長期的には手を加えていきたいと思いました。 イベントエンティティをモデルとして表現するという話は texta.fm でも耳にしていて、今回の発表を聞いて更に理解を深めたいと思ったので texta.fm での話の元になっていたパーフェクト Ruby on Rails を読んでみようと思います。 (@euglena1215) Simplicity on Rails - RDB, REST and Ruby by MOROHASHI Kyosuke - Kaigi on Rails 2023 Railsの型ファイル自動生成における課題と解決 speakerdeck.com Railsアプリに型を導入するときに起きるいくつかの課題と、それらの課題に対する解決策を紹介するセッションでした。 課題: Railsアプリに手軽に型を導入したい -> 解決: orthoses-rails 課題: アプリケーションコードの型が無い -> 解決: rb prototype 課題: YARDを活用したい -> 解決: orthoses-yard 課題: YARDのシンタックスをチェックしたい -> 解決: rubocop-yard 弊社でも型(RBS)を活用しているため、課題に共感しながら聞いていました。 Orthoses は使用していませんが、いくつかの解決策は弊社の開発環境でも活用できそうなので、Orthoses への乗り換えも含めて検討・参考にしたいと思いました。 また、発表の後に3Fの休憩部屋(ROOM 0)で今後の型に関する話をすることができて、個人的に型に対するモチベーションが上がる1日になりました。 今後は型(RBS)に関するOSSにも少しずつコントリビュートしていきたいと思います。 (@sinsoku) Railsの型ファイル自動生成における課題と解決 by Yuki Kurihara - Kaigi on Rails 2023 管理機能アーキテクチャパターンの考察と実践 speakerdeck.com 後追いで作ることになりがちな管理機能を Frontend, Backend のソースコードを置き場を元にいくつかのアーキテクチャパターンに分類しそれぞれのpros/consをまとめたのちに、B/43ではどのアーキテクチャを選択したのかと選択した理由・振り返りを共有するセッションでした。 タイミーは ActiveAdmin を用いてモノリシックコードベースに管理機能を密結合させています。ActiveAdmin を使う上で色々と大変な面はあるのですが、なんだかんだ ActiveAdmin は便利ということで使い続けています。とはいえ、過去・現在に最適なアーキテクチャが未来も最適である保証はないので、今後の管理機能の在り方を考える上で非常に参考になりました。 また、管理機能のバックエンドとエンドユーザー向け機能のバックエンドを同じにするのも同意です。管理機能の中には本来は機能として提供すべきなものが提供されていないことから運用に負がたまって社内管理機能に機能追加の力学が働き、結果として管理機能が肥大化していくというのはあるあるだと思っています。バックエンドを分けると管理機能に閉じた変更の方が容易になり、管理機能の肥大化を加速させる要因になりうると感じました。 (@euglena1215) 管理機能アーキテクチャパターンの考察と実践 by ohbarye - Kaigi on Rails 2023 オフライン参加楽しかったですね。2日間で多くの企業のエンジニアとも交流が出来ました。 今年は抽選に外れたのでブース出展できなかったのですが、来年はブース出展したいと思います。来年はブースでまた会いましょう!
アバター
はじめに こんにちはokodoonです タイミーのデータ基盤に対してデータモデリングを始めてしばらく経ったので、現状の全体構成を紹介したいと思います 全体構成 弊社のBigQueryは以下の4層にレイヤリングされています それぞれの役割は以下のような切り分けになっています レイヤー名 役割 データレイク層 複数ソースシステムのデータを未加工の状態でBigQueryにロードする宛先 dbt snapshotによるソースの履歴化 ステージング層 複数ソースシステムのデータを共通した処理でクレンジングする層 DWH層 ソースシステムのデータ形式を分析に適した形に変換する層 ディメンショナルモデリング/ログテーブルをイベント単位に分割/その他便利テーブル作成 データマート層 特定用途に対して1:1で作成されたテーブル群を格納する層 ダッシュボード用テーブル/Looker用テーブル/GoogleSheets用テーブル/外部サービス連携用テーブル 図で表すとこんな感じです DWH層, データマート層の切り分けについて 「DWHとデータマートとは?どう切り分けるか?」と100人に聞いたら100通りの答えが返ってきそうなこちらの定義 弊社内ではデータ基盤チーム内で議論した結果、以下のような切り分けをしています - DWH: ステージングデータを分析ニーズに合わせて加工したもの - データマート: 特定ニーズに対して1:1で作成されたテーブル なので ディメンショナルモデリングなどの分析用データへ変換したテーブル クエリを発行する際に、結果の出力が重くなってしまうテーブルを軽量化したもの などがDWH層に格納されているテーブル群で 特定ダッシュボード用に作られたテーブル Looker探索環境用に作られたwide_table 外部サービス連携用に成形されたテーブル などが現在データマート層に格納されているテーブル群になります ※「ダッシュボードに1:1で対応するマートを作る方針なのかあ」と思われるかもですが、こちらは現在推進中のdbt exposureを用いたアウトプット管理を見据えているためです(またこの辺りは別の機会にアウトプットします!) 各層の説明を少しだけ深掘り データレイク層 この層でdbt snapshotを活用した履歴化を実行しています 下流のステージング層/DWH層などで履歴化をしていない理由としては、何かしらの加工がされたあとのデータを履歴化する場合、加工処理に何かしらのバグや想定漏れがあった場合のロールバックが大変なためです 分析に使用する場合は以降の層でクレンジング/モデリングされたデータのみでいいため、基本的に分析ユーザーはデータレイク層へのアクセス権限を保持していません ステージング層 以下のような処理を加えて、生データをクレンジングしています データ型の変更と統一 パーティショニング/クラスタリング データマスキング 品質担保のテスト uniqueテスト Nullテスト 外部キーテスト ユーザーが3NF形式のデータに対してクエリを書く場合は、品質が保証されたステージング層のテーブルに対してクエリを実行してもらうような運用にしています DWH層 DWH層内は以下のような区分が作られています DWH層内区分 役割 dbtのフォルダパス events ログテーブルを分析要件にあるイベントごとに分割整理して軽くしたテーブル群 models/dwh/events component ディメンショナルモデリングのデザインパターンに当てはまらないが便利な部品のようなテーブル群 models/dwh/component pre_dimension 同一属性単位にドメイン情報を結合 ドメイン要件に従ったフラグ生成などの複雑な事前処理 models/dwh/dimension_modeling/pre_dimension dimension SCD Type2カラムの生成( https://en.wikipedia.org/wiki/Slowly_changing_dimension#Type_2:_add_new_row ) サロゲートキーの生成 models/dwh/dimension_modeling/dimension fact ビジネス指標の作成 関連Dimensionのサロゲートキーの反映 models/dwh/dimension_modeling/fact conformed_fact 複数プロセスを跨いだよく使われるfactの組み合わせをconformed dimensionで結合 複数プロセスを跨いだビジネス指標の作成 models/dwh/dimension_modeling/conformed_fact 弊社Dimensional Modelingについて 弊社Dimensional Modelingについてざっくり解説します SCD Type 6の採用 dimensionテーブルは「履歴化されたテーブル(Type2)」と「最新情報を持つテーブル(Type1)」に分けることなく、SCD Type1~3の全てを一つのテーブルで管理する方針としています こうすることでFactに登録するサロゲートキーが少なくなり、分析時のクエリと思考がスリムになると考えています 採用しているFactテーブルのパターン Transaction Fact + Factless Factを接合したFactテーブル Transaction FactテーブルとFactless Factテーブルを別々に作るのではなく、ビジネスプロセス単位でまとめてくっつけています 例えば「買い物プロセス」を仮定した場合に、Transaction Factにあたる 合計売上 とFactless Factにあたる 決済回数 が同一の買い物Factテーブルにあるイメージです Snapshot Factテーブル Transaction FactやFactless Factではうまく表現できないような指標(etc. ユーザー数全体の推移, 銀行残高の推移, )を日,月単位の粒度でスナップショットして保持します 例えば日毎のユーザーdimensionのサロゲートキーを保持するようなSnapshot Factテーブルを作成することで、日単位のユーザー数の推移をDimensionテーブルで絞り込み可能な状態で追えるようになります FatなFactテーブル Factテーブルは分析ニーズのあるビジネスプロセス単位で作られると思いますが、そのビジネスプロセスで発生する指標を全て一つのFactテーブルで完結して出力できるような方針をとっています 買い物の決済プロセスのFactテーブルを仮定すると、 決済Factの決済金額 * 決済Dimensionの消費税率 = 消費税金額 みたいにFactとDimensionを組み合わせて新しい指標を出力するのではなく、決済Factテーブルに 決済金額 , 消費税金額 を二つとも持っておくような形です こうすることで「Factテーブルに定義されている指標をDimensionテーブルで絞り込むだけでいい」「Factテーブルにない指標があればFactテーブルに足せばいい」とクエリ作成時の思考が軽くなると考えています Factテーブル上に社内の指標を全て整理して保持している状態です データマート層 データマート層内区分 役割 dbtのフォルダパス wide_table ディメンショナルモデリング済みテーブルをJOIN済みの状態にしたテーブル群 Lookerの探索環境に1:1で対応している models/marts/wide_table dashboard LookerStudioダッシュボード, Spreadsheetのコネクテッドシートに1:1で対応しているテーブル群 models/marts/dashboard その他 他サービスに連携していたり、特定プロジェクトのために一時的に作られるテーブル群 models/marts/{{その他}} Lookerとの接続について LookerのUIを見てみると、dimensionとmeasureが選択できるような、ディメンショナルモデリングを意識したものになっています そこで弊社ではこの画像のようにLookerのデザインに乗っかる形で、Lookerの接続先をディメンショナルモデリング済みのwide_tableとしており、スター内のFactテーブルの指標をmeasureとして、Factに接続するDimensionテーブル内の要素をDimensionテーブルごとに分割して表示しています こうすることで 商品の生産国が中国の決済金額を決済方法ごとに出力したい というユーザーの思考があった時に 商品の生産国だから => 商品Dimensionの生産国を選択 決済方法だから => 決済Dimensionの決済方法を選択 出力したい指標は決済金額だから => Factの決済金額を選択 とLooker上のユーザーの行動に繋がると思っており、 ユーザーの思考 , 実際に走るクエリ , Lookerの探索環境のUI ができるだけ一致するようなデザインになっています こうすることでLookerへのフィードバックがモデリングをより便利にすることに繋がり モデリングへのフィードバックがLookerをより便利にすることに繋がります ダッシュボード用 / スプレッドシート用のデータマートについて 基本的にマートはView形式で作成します ( 補足:Viewテーブルとは ) Viewとして作成することで無駄なビルドやコスト増加を避ける方針です マート化する判断基準は「正確性と安全性を保守すべき対象かどうか」であり、チーム内で定めた基準に従ってマート化を実施してアウトプットをコード管理します ダッシュボード用 / スプレッドシート用のデータマートをdbtで作成するのでdbt上でリネージュ管理が行えるようになり、破壊的な変更が上流で発生した場合に保守対象のダッシュボードへの影響を抑えることができます 作成したマートに対応するdbt exposureを宣言することでデータオーナー管理やアウトプット先管理も行えるようにする予定です 将来構想 数ヶ月以内に実現したい将来 dbt exposureによるオーナーを含めたアウトプットの管理 主要ビジネスプロセスの全モデル化 1年以内とかにはやりたい将来 サンドボックス層の作成 ポリシータグを用いた、メンバーのアクセス可能情報レベルに応じたクエリ範囲の制限 まとめ・今後の課題 弊社は組織拡大のスピードがとても早く、データ活用者数もとても多いので クエリ作成者間で出力される指標値がブレる。ダッシュボードやLookerの値とアドホッククエリの値がブレる。そもそも定義がブレている などの問題を防ぎつつ 簡便にあらゆる指標値がクエリできる ように、ディメンショナルモデリングを主軸としたデータモデリングを推進してきました 現在は作成したモデル数はかなりのものになり、それに従ってLooker環境もリッチになってきたのですが、アナリストが作るアドホッククエリやダッシュボード作成クエリにまだまだ採用してもらえていない課題感があります モデリング済みテーブルを使ってクエリを書くのはDWH開発に参加してくれているアナリストメンバーに集中していて、他アナリストメンバーにモデリングなどの技術的内容とDWHの開発内容などをうまく同期できていない状態です モデリング済みテーブルを使用することで 指標が全社定義と常に一致する Dimensionの履歴を考慮した指標出力が可能になる クエリ文量が1/5とかになる クエリパフォーマンスが向上するケースもある などメリットも大きいと思うので、モデリング手法の理解を助けるアウトプットやワークショップなど様々なHowを通して、アドホックなクエリに採用してもらってもっとフィードバックのサイクルが高速化するようにしていきたいです! We’re Hiring タイミーのデータ統括部では、ともに働くメンバーを募集しています!! product-recruit.timee.co.jp
アバター
イベント概要 2023年8月2日に「What’s “Next” JS Meetup」と題してバベル社のuhyoさんをお招きしてNext.jsに関する勉強会を開催しました。 その中でタイミーフロントエンドエンジニアのいーふとさん(@redshoga)の講演をイベントレポートにまとめてお届けします。 今回のスピーカー Server Actionsの概要 ※ 2023年8月2日のイベント開催時点での情報であり、将来的に変わる、変わっている可能性があります。 Server Actionsはざっくり説明するとサーバーで動作するコードをあたかもクライアント上に記述できる機能です。コード下部のLikeボタンがクリックされると、propsに渡されているincreateが呼び出され、その中でPrismaのclientが動作します。 クライアントで動作 レンダリング結果 サーバーで動作 送信データ 一つのファイルだけでなく別ファイルで書くことも可能で左側がクライアントで動作、右側がサーバーで動作します。それぞれに”use client” “use server”というディレクティブを書く必要があります。 レンダリング結果を見ると普通のフォームとして動作することがわかります。右側の”createPost” というのが普通のTypeScriptをインポートしているにも関わらず通常のformとして動いてくれる不思議な機能です。 送信データとしては普通のフォームデータです。内部ではACTION_IDがよしなに付与され、良い感じに動作します。 既存のフルスタックTSのアプローチ ここまでがServer Actionsを踏まえた未来のTypeScriptをイメージした内容だったのですが、現状のフルスタックTypeScriptにはどんなアプローチがあるのかを見ていきます。 よくあるのがスキーマの活用です。概念としてはOpen APIやGraphQLとも一緒だと思います。スキーマAPIの定義ファイルからTypeScriptの型生成を行う。よく見る代表例としてはopenapi-typescriptなどですね。タイミーではopenapi2aspidaというライブラリを使っています。aspidaというライブラリがあってそれに変換してくれるものですね。当然、定義ファイルが必要になりますが、自動生成される仕組みにしてしまえば楽なのかなと思います。 他の例としてはtRPCが今、流行っているのかなと思っています。TypeScriptに特化したRPCでインターフェースを提供するライブラリという解釈です。Next.js、Express、FastifyにインターフェースとしてつけてあげるとあたかもRPCのような感じで書くことが可能になります。フロント側の対応もしっかりしてReactのhookを勝手に提供してくれる優秀なライブラリだったりします。 シリアライズもsuperjsonが用いられており、MapやSetといったオブジェクトをそのまま送ることが出来ます。 クライアントサイド レンダリング結果 サーバーサイド 送信データ こちらが実際に書いた例です。 クライアントサイド側にapi.example.hello.useMutationというのがありますが、これがサーバーサイド側のコードを書くと勝手に生成されているような感覚で書くことができる開発者体験のよいライブラリになっています。 シリアライザとしてはsuperjsonが用いられており、内部ではよしなにwrapされているので使用感としてRPCっぽいといった感じのライブラリになります。 ここまでServer Actionsについてと今までのフルスタックTypeScriptをどうやって書いていたのか、という話をしてきましたが、比較するとServer Actionsは圧倒的に手軽です。”use server”と書くだけで動くので手軽です。 シリアライズの方式も微妙に違っていて、レスポンスで返ってくるやつを色々実験しているのですが、JSXとかも実は送れたりします。 Next.js特化で共通のインターフェースみたいな概念ではないので今のところはServer Actionsを書いて、それをNativeAppから呼ぶみたいなことは想定されていないと思います。revalidatePathの様な所謂、Next.jsのキャッシュサービスと併用して使ってねみたいなサンプルコードとかも散見されるので、インターフェースを提供するというよりはやはりNext.jsに特化したRPCといった印象です。 Server Actionsの活用例 Server Actionsを知っている人は似たイメージを持っていると思いますが、Vercel盛り盛りのフルスタック構成ですね。 サーバーのデータベースを触る際はServer ActionsとかAPI Routesとかで書いて、クライアントコードは普通にReactで書くと。Vercelは最近様々なサーバーレスストレージサービスを出しているのでそれらを活用して永続化していきます。 あとはKey-Valueストアとかもあるのでそれらでキャッシングをしてあげるといった感じになります。 もう一つはBFF as Server Actionsのようなイメージですね。BFF as Server Actionsという言葉自体は私が作った造語なのですが、先程の構成ではネイティブアプリケーションからAPIを叩けないですよね。 それをAPIだけVercelから外に出して、VercelのServer Actionsから叩いてあげる、といったことも可能ではないかなと思っています。あとは単純にServer ActionsにすることでJavaScriptが動かない環境でもフォームとタグが動くのでそういったプログレッシブエンハンスメントを主とした活用もあるのではないかなと思います。 まとめ 今回はSever Actionsこんな感じですよ、ということをお伝えしました。とはいえまだアルファ版なのでServer ActionsちゃんとGAされてほしいなと期待を持っています。便利なので使っていきたいという気持ちですね。最近のNext.jsはフルスタック寄りに様々な機能を提供してくれているんですけど、Railsのような規約(レール)がある訳ではないのでその議論は活発になっていくのでないかなというのを個人的に思っています。 ご清聴ありがとうございました。 uhyoさんの発表や弊社のもう一人の西浦さんの発表が気になる方は以下の動画を是非ご覧ください。 www.youtube.com 少しでも興味を持っていただいた方は是非こちらからカジュアルにお話しましょう! product-recruit.timee.co.jp
アバター
内容 こんにちは! スクラム マスターのShinoP ( @marupopu )です! この記事は、 スクラムフェス仙台2023 で登壇した事によるふりかえり記事になります。 実際の発表内容はこちら speakerdeck.com ※後で動画も公開されると思いますので、ぜひ見てください! なぜ登壇したか? 初めに、なぜ登壇する事となったか?ですが、時は今年の1月に行われた RSGT でコンフォートゾーンから抜け出す体験をしたのがきっかけでした。 具体的なコンフォートゾーンから抜け出す体験というのは、RSGT初参加で、初 OST のホストを務めた事です。 今までは外部の勉強会に参加しても、話を聞くだけで自分の中で解釈をして納得をして、わかった気になって終わっていました。 また、自分は参加する人で、登壇している人たちはすごい人というような印象を持っていました。 そんな中、RSGTならではの空気感や、 OST のホストをやる人も多く存在し、遠方から参加していることもあり、参加するからには効果を最大限にしたいという思いがあり、勢いのまま OST のホストに申し込んだ結果、多くの方が集まって頂き5チーム程度に分裂して話し合う規模になりました。 その時に、自分が思っているような疑問でも共感や気になってくれる人は案外多いのかもしれない…と認識できました。 その後、 タイミー主催の勉強会 での登壇経験をして、共同で発表してくださった Coincheck の方々や、参加者の方とScrumについて語り合う体験が楽しいと感じました。 www.youtube.com 自社主催の勉強会での登壇が楽しいと感じたので、さらに課外活動などにも興味が湧きました。 なぜ、 スクラム フェス仙台か? さて、なぜ スクラム フェス仙台を選択したのか?というと、私自身が 岩手県 盛岡在住ということもあり、仙台も割と近いので現地参加しやすかったのと、4年前に東京から岩手に移住してくるほど東北が好きで、東北という括りで盛り上げたかったのが理由になります! あと、去年も参加したかったのですが、現地チケット取れずに断念した記憶があるのでリベンジも込めて。 雰囲気はこんな感じ スクフェス 仙台間もなく始まるサメー🦈 pic.twitter.com/nVx7xLKmtK — スクラム フェス仙台 (@scrumsendai) 2023年8月25日 初プロポーザルと文豪制度 もちろん、初プロポーザルでした! Scrum Fest Sendai 2023 - SMって兼任?専任?結局どうなの? 〜持続可能なチームになる為の鍵〜 | ConfEngine - Conference Platform どのように書いたら良いかわからなかったので、他の方の書き方などを参考に、自分の伝えたいことを書いてみました。 また、採択頂けたタイミングで資料作成などを行うにあたり、弊社のTDE10の中の人つの”文豪制度”を利用しました。 productpr.timee.co.jp 壁打ちをしてもらいながら資料を作成し、本当に自分が伝えたいことを 言語化 するのを手伝って頂き、本当に助かりました。 また、壁打ちを行なっていく過程で、最初に出したプロポーザルのアウトラインがずれていたりもしたのに気が付いたので、何を伝えたいのか?から正しく伝えられるような 言語化 、資料作成までお世話になりました。 登壇者での参加は何がよかったか? RSGTなどもそうでしたが、登壇者でない時の参加と登壇者としての参加の違いを感じたので書いていこうと思います。 まず、気持ちの問題かもしれないのですが、”圧倒的喋りかけやすさ”があるような気がしました。 何人かに、タイミーのしのぴーさんですよね?って話しかけられて嬉しかったです😆 めちゃめちゃ目立つ登壇者用TシャツもGOOD! また、認定 スクラム マスター研修やクローズドなイベントでの交流のあった方々にも声をかけて頂いたりして、登壇者 = 参加することが確定しているなどのイメージなので、話しかけて頂いたのかな?と推測しています。 あとは、同じ登壇者同士で話せたことも良かったと思います。 初めての登壇者同士で話したりして、緊張しますね…とかその時の心境をシェアできたのはとても良かったです。 その他よかった点 こちらは登壇者でなくとも スクラム フェス仙台2023の体験がよかった点を書いていきます。 まず、なんと言っても” クラフトビール ”が美味しかった🍻🍻 (ビールを飲みながらの OST 最高でした!!!) Day1で少し飲みすぎて、Day2に酒焼けで声が少し枯れてしまったのは反省😂 ノベルティ のシャークレくんグラス 来週8/25,26は スクフェス 仙台! ノベルティ を製サメしてます🦈オンサイトの参加の方はぜひ現地で乾杯🍻しましょ〜 #scrumsendai pic.twitter.com/s7BZjrHJBZ — スクラム フェス仙台 (@scrumsendai) 2023年8月15日 また、その場でしか体験できないワークショップ体験や飲み会、昼ご飯時に話されるディープな悩みの相談会なども現地参加ならではの楽しみ方だなぁと思いました。 あとは、会場が複数の階に分かれていたこともあって、ギャザリング感がめっちゃ味わえました。 1階では、メイントラックと品川トラックとオンライントラックがモニターで映されていたのですが、その空間で話している人もいればモニターを見て観戦している人もいたり自由な感じでとても雰囲気が良かったです。 この場作りという観点で、 スクラム フェス仙台2023の運営メンバーが考え抜いたレイアウトだと思うと、すごいと感じておりました。 さらに、スタッフの方々も楽しんでいる&参加者も全員協力的だったと思うので、やはり場を作り上げていくのは全員でやるのだなぁと再認識できました。 反省点 めちゃめちゃ楽しかったのですが、ふりかえっていて、いくつか反省点が見えてきました。 それは、登壇を意識するあまり、他のセッションが頭に入ってこなかった…という点です。 Day1でお酒をたくさん飲んだのも緊張を紛らわすためかもしれません…(酒焼けの言い訳) そして、お気付きでしょうか…現地でのオリジナル写真がほとんどない事に… 精神的に一杯一杯で写真を撮るのも忘れていました… また、Day1ではしゃぎ過ぎて、登壇が終わった後の無気力感と 疲労 が半端なかったです。 (まさに スクラム フェス仙台2022のシャークレくんのツイートみたいなイメージ🤣) それだけ、この登壇にかけてきたという事にしておきましょう😅 初めての社外カンファレンスの登壇終わって、めっちゃ脱力してる 燃え尽きた… — ShinoP/しのぴー@ スクラム マスター (@marupopu) 2023年8月26日 #scrumsendai スクラム フェス仙台のすべての日程を終了しました。 2日間本当にありがとうございました! pic.twitter.com/RPuVsa3NRo — スクラム フェス仙台 (@scrumsendai) 2022年8月27日 今後について 今回、 スクラム フェス仙台に登壇者として参加して、色々な気付きが得られました。 その中で、 カイゼン したい点としては、登壇経験を積み重ねてさらに楽しみたい!ということです。 今回は初参加、初プロポーザル、外部イベントでの初登壇という事でめちゃめちゃ緊張したのですが、楽しみの方が上回る体験となりました。 しかし、もっと楽しむためには登壇に慣れる事により心に余裕を持ってさらに楽しめるのでは?と感じました! (あと、全力で楽しむために体力もつけたい!) 他には、今回の スクラム フェス2023の運営側の方々が光る場面がいくつも目撃したので、自分でもこのような場作りができるようになりたいと強く思いました。 元々、なぜ スクラム フェス仙台か?にも書いた通り、東北を盛り上げたいという気持ちがあります。現在、北東北(青森・岩手・秋田)に スクラム や アジャイル の勉強会はほとんど開催されていないですし、コミュニティも私の観測範囲では見当たらないように見えます。 (コンパスで見ると「 アジャイル ・ スクラム 」のワードで勉強会が一つもない) ですので、個人的なNextActionとして岩手で アジャイル / スクラム コミュニティを生み出す活動なども今後やっていきたいと考えています!! 最後に、こんな素晴らしい場作りをして頂いた運営の方々を初め、関わってくださった全員に改めて感謝をお伝えして、ふりかえりの記事とさせて頂きます!! ありがとうございました!そして、おつサメでした!! スクフェス 仙台2023!おつサメ〜🦈最高に楽しい2日を皆さんと作れて最高嬉しいシャー  #scrumsendai pic.twitter.com/jtNhSak5sz — スクラム フェス仙台 (@scrumsendai) 2023年8月27日
アバター
イベント概要 2023年8月24日にGaudiy社との共催で「 不確実性を乗りこなす強いプロダクトマネジメント組織のデザイン 」と題してプロダクトマネジメント組織に関する勉強会を開催しました。 不確実性の高いマーケットに対して、高い成果を出せるチームにしていきたいPdMやPOの方、組織設計からプロダクトのアウトカムを高めたいと考えているEMの方などに特にお勧めの勉強会でしたのでイベントの中から高石さんによる「『わからない』に立ち向かい続け成果を生み出す、実験志向なチームの育み方」の講演レポートをお送りします。 今回のスピーカー 「『わからない』に立ち向かい続け成果を生み出す、実験志向なチームの育み方」 不確実性の高いゴールに付き物の「わからない」 プロダクト開発ではゴールを設定します。タイミーではスクラムを採用しているチームが多いのでプロダクトゴールと表現されます。 RetentionRateの10%向上といった定量的なゴールもあれば、ユーザーストーリーの形式で特定属性のユーザーが○○を実現できるようになるといったゴールもあると思います。近年はこの様に何かしらのゴールに向かってイテレーティブな開発をすることが一般的になってきました。 ここで付き物なのが「わからない」ということです。ゴールが定まってもそこにどうやれば辿り着けるのかは「わからない」に満ちています。PdMやPOの方で「わからない」のフィードバックを貰ったことがない人はあまりいないのではないでしょうか? でもこの「わからない」ことってネガティブなことなのでしょうか? 人間にとって「わからない」ものは本能的に身構えてしまったり、気持ち悪さを感じることが多いと思います。一方でこの「わからない」気持ちは「わかりたい」という行動への原動力でもあります。「わからない」を放置しておくとモチベーションの低下に繋がってしまいますが、適切に解消が出来ればプロダクトの成果に繋がります。 「わからない」を「わかる」にする力がプロダクトの成果を生む 普段、仕事で強く意識することは少ないですが日常業務の中でも「わからない」ことが沢山あります。 こういった様々な疑問がある中で皆さんは無意識に解消する行動を取っていると思います。インタビューをしてみたり、A/Bテストを実施したり、言語化こそされていなくてもわかる様にするアクションを取っているのではないでしょうか。 その様な行動で「わからない」を一つずつ「わかる」に変えていくことで不確実性の高いゴールに対しても道筋が見えてきます。ゴールの達成方法がわかるようになってくるのでプロダクトの成功にまた一歩近づくことが出来ます。 逆に「わからない」を解決出来ないとBIGBANGリリースに繋がったり、出したは良いけどその後の計測が上手く出来ずに成功したかも失敗したかも判断出来ないといったことが発生します。それだけならまだしも「わからない」を解決出来ず、不確実性を許容出来ないチームになってしまうと簡単なゴールしか設定できなくなってしまうリスクもあります。 基本的にどのプロダクトも新規性が高くまだ世の中に無いものを生み出しているわけですから、文化として「わからない」を許容し、それを解消しつづける組織力をいかに創れるのかが企業の競争力に直結すると思います。 タイミーのケースから見る、チームの実験を支える環境づくり ユーザビリティテストやA/Bテストなどについてはよくまとまった書籍がたくさんありますし、皆さんも勉強されていることと思います。しかし、座学として知識を習得しても、それらを実際にチームとして行動に移すまでには辿り着けないことの方が多いのではないでしょうか? そこでタイミーでは「チームが実験を行ったり情報収集する、心理・物理のハードルを極限まで取り除く」ということを行っています。 チーム専属のアナリスト チーム専属になる前までは分析チームに依頼をして、回答を待つ形式でした。この場合、分析チームが忙しい場合だと答えが分かるまで1〜2週間掛かってきます。このようなことになると既にスプリントの1週間を超えてしまいます。依頼側としてもそんなに時間がかかるなら別のやり方を模索しよう、となり依頼することそのものを諦めてしまいがちです。 しかし、スクラムチームに1人専任でプロダクトアナリストが所属する様になったことで物事の大小を問わず、迅速な解決ができる様になり、定量面の仮説検証速度が向上しました。 ユーザーインタビューのハードルを極限まで落とす「 Interview as a Service 」 タイミーではユーザーと話したいと思ったらとにかくすぐに話せるようになっています。 Googleカレンダーにパブリックな仕組みがあってユーザーインタビューの予定を作ると自動で人がアサインされ、議事録が用意されて、インタビューアーのリクルーティングまで完結する仕組みがプロダクトマーケティング部によって運用されています。 このプロセスでは最短で明日、中央値でも2、3日後には望んだセグメントの人とお話が出来ます。 これによりスプリントの中に収まってくるので事前にリサーチして準備をしよう、ではなくスプリントの中で解くといったケースまで出てきたのも面白かったポイントです。 ここまで環境を整えていくと勝手に実験が増えていきます。実験をしよう、しようと思っているうちは中々実験が進みませんが、阻害する要因を全て取り除いていくと皆も実験したい気持ちはそもそもあるので自然と実験が増えます。 実験に特化したフレームワークの採用 『LeanUX』の中で紹介されているLeanUXCanvasを採用しています。 1〜8までのボックスがあるのですが、特に7が面白く「一番はじめに学習しないといけないこと」を明示しています。今回の言葉でいうと「わからないものの中でもっとも早くわかる様にしないといけないこと」ですね。「わからない」ことの存在を事前にフレームワークで宣言をしてその解消の重要性も説いていることがユニークに感じています。 取り組みのとある共通点 どのケースも全く違う取り組みに見えて、共通点があります。 ① ゴール達成の過程に生まれる「わからない」を「わかる」にする力をつけること ② チームを取り巻く、組織や環境面からアプローチしていること 「わからない」を爆速で楽にわかるようにするといった観点でチームや組織を組成すれば高い不確実性も許容して実験を重ねて解決できる強いチームが生まれます。 また「わからない」を解決するだけであれば手段はさまざまありますが、組織や環境面からのアプローチはレバレッジが効きます。例えばインタビューの話であれば開発チームに限らず、営業やマーケティングチームが使ったりしていて、全社に波及しています。 まとめ まとめると今日は不確実性という言葉は広いので「わからない」と置き換えましたが、この「わからない」ことをネガティブに受け取って欲しくないと思っています。「わからない」ことも当然ですが、すぐ「わかる」ことよりも「わからない」に対して挑戦して考えていく方が生産的でプロダクトの組織としてもより成果をあげれるのかなと思います。 実験や仮説検証でも個人だけの頑張りではなくチームとして取り組めることが多くあります。プロダクトのチームは元より実験をしたいので妨げる要因を取り除いていくことで自然と実験する文化が育まれ、不確実性に強いチームになっていきます。タイトルに繋がっていきますが皆で「わからない」に立ち向かい続け成果を生み出す、実験志向なチームを育てていきましょう。 充実のパネルディスカッションは本編動画から! www.youtube.com 実験文化のある組織をいいなと思った方は是非お話しましょう product-recruit.timee.co.jp
アバター
こんにちは! タイミーのデータアナリストの@takahideです。 今回は、メンバーが行っている「勉強会」を紹介させていただきます。 ご紹介する内容は以下になります。 ・勉強会ではどんなことをやっているの? ・勉強会を通じた業務の広がりとは? 勉強会について 勉強会の具体例 背景 勉強会で用いた本 勉強会の進め方 勉強会を通して 効果検証の講習会 まとめ We’re Hiring! 勉強会について まずは、勉強会に関して簡単に紹介させてください。 タイミーでは、有志のメンバーが勉強会を立ち上げる文化が根付いています。 その中でも、データアナリストの勉強会は大きく2種類あります。 一つは、「会計」「プロジェクトマネジメント」などテーマをもとにした「一話完結型」のもの。 もう一つは、課題本を決めて輪読する「読書会型」で、今回はこちらをご紹介します。 勉強会の例 「読書会型」の勉強会の一番の目的は「分析力の向上」です。 勉強会を通じて、分析手法に関する共通言語が浸透することで、分析スキルの底上げだけでなく、 分析プロセスや結果に関するコミュニケーションが円滑になりました。 また、他部署の方と勉強会を行うことで、複数部署間のコミュニケーションもスムーズになりました。 プロダクトチームと行なった勉強会の感想 今回は「効果検証」に関する勉強会を一つの事例として紹介できたらと思います。 勉強会の具体例 背景 効果検証の勉強会は、データアナリストの検証手法の習得を目的に始まりました。 タイミーの組織が大きくなるにつれて、検証が必要な施策の件数も増加傾向にあります。 また、マーケティング施策のように、ABテストでの検証が難しいものも増えており、手法の共通認識を作るニーズが高まっていました。 勉強会で用いた本 勉強会では、『効果検証入門〜正しい比較のための因果推論/計量経済学の基礎』(安井 翔太)を課題図書として用いました。 傾向スコア、差の差分法(DID)など効果検証の手法が網羅的に説明されているのと、コードが豊富なのが良かったです。 勉強会の進め方 勉強会は、週に一度、担当者がgoogle collabを用いて発表を行い、他メンバーから質疑応答を行いながら進めています。 コードを確認しながら進めることで理解が深まりやすくなっています。 勉強会を通して 大きく3つの活動に繋がりました。 一つ目は、当初の目的だった「効果検証手法に関する共通言語の促進」です。 特に、検証手法を用いる際の注意点を共通認識として持つことができました。 例えば、回帰不連続デザイン(RDD)を利用する際には、「分析対象が介入に関する状態を操作できない」ことが求められますが、こうした条件が満たされない場合は、逆に、集積分析(bunching analysis)のような操作を前提にデザインされた手法を試すなどです。 二つ目は、勉強会をきっかけに、データアナリスト内でpythonを推進する活動に繋がったことです。 勉強会はpythonで実行しましたが、勉強会の内容を踏まえてタイミーの実データを用いた検証を行う際に、pythonへの慣れの有無がボトルネックになっていることが分かりました。 そこで、現在は、pythonを通常の分析業務で自然に扱えるための学習パッケージの構築を進めています。 三つ目は、全社的な講習会に繋がったことです。 こちらに関して次に詳細をお伝えできたらと思います。 効果検証の講習会 冒頭でもお伝えしましたが、現在タイミーは組織拡大に伴い、効果検証のニーズが増加しています。 そこで、今回の勉強会の内容と、過去の分析ナレッジを踏まえて、全社的な効果検証の講習会を実施しました。 効果検証の目的から始まり、交絡因子など、効果検証で注意するべき内容をお伝えしました。 また、効果検証を進める際に、データアナリストと事業部がどのように連携すると良いかをお話しました。 まとめ データアナリストの勉強会のご紹介いかがだったでしょうか。 改めてにはなりますが、データアナリストが勉強会を行うメリットに「分析手法の習得」と「共通言語の促進」があると思います。 特に後者は、勉強会のナレッジを他部署の方に展開するなど、コミュニケーションの円滑化に繋がっていると考えます。 タイミーは事業、組織ともに拡大傾向にありまして、データアナリストが活躍する機会が多く存在しています。 これからもデータアナリスト発信で意思決定に役立つ情報を展開することで事業の成長に貢献したいと考えています。 We’re Hiring! タイミーのデータ統括部では、ともに働くメンバーを募集しています!! カジュアル面談 も行っていますので、少しでも興味がありましたら、気軽にご連絡ください。
アバター
こんにちは☀️ タイミーでアナリストとアナリティクスエンジニアしてますokodoonです 今回の記事はdbt CloudでPull Requestを作るときに、レビュー負荷が高くなってしまっていた問題を解消できるように、コンパイル済みのSQLをPR上にコメントするような仕組みを作成したことについての紹介です。 もし同じような課題感を抱えている方がいらっしゃれば、参考にしていただければ幸いです 課題感 今回選択した解決策 背景/前提 実装概要 各ステップの説明 PRの情報をもとにprofiles.ymlの動的生成 コンパイル処理の実施 PR上にコメント どんなふうに動くかみてみる 結果 We’re Hiring! 課題感 弊社のデータ基盤ではDWH層DataMart層は「分析用に加工されたデータを扱う層」として定義しています。 各種ドメインに依存した集計や変換のロジックが含まれるため、この層のモデリングに関しては基盤開発側のレビューのみでなくアナリスト観点でのレビューも必須となります。 ですが、分析ドメイン観点でのレビューが必要な場合に、純粋なSQLではないdbtモデルがアナリストレビューの障壁になることが多いです。 またアナリスト以外であっても「このmacroがどのようにコンパイルされるか」を把握するのは少し面倒だったりします 今回選択した解決策 そこでdbtモデルをコンパイルしたSQLファイルをPullRequest上にコメントするような仕組みを考えました。 この実装によって先ほど挙げた課題感が以下図のように解決されることを期待して開発しました 背景/前提 1. 開発環境について 弊社ではdbtを活用したデータ基盤の開発を行っており、 dbtモデル開発をdbt Cloud上の統合環境にて実施するような流れになっています。 2. CIジョブについて CI用のdbt CloudのJobは RUN ON PULLREQUEST で実行されて、以下のようなコマンドが実行されています。 dbt build --select state:modified+ mainブランチとの差分があるdbtモデルがBigQuery上にビルドされている状態です(本来CIで走るdbtコマンドをレイヤーごとに分割していますが、ここでは簡略化しています) 3. CI環境のスキーマ名とcustom_schemas.yml戦略 dbt Cloudで設定できるCI Jobではtarget schema名の命名が dbt_cloud_pr_<job_id>_<pr_id> となっており、連携レポジトリのPR番号に対して動的に作成されます。 (DBT_JOB_SCHEMA_OVERRIDEでこの命名規則の上書きもできますが、PR単位でCIテーブルが作成されて欲しいので、デフォルトの命名規則にしたがっています。) 参考: https://docs.getdbt.com/docs/deploy/continuous-integration#how-ci-works また、弊社では開発環境とCI環境のスキーマ名が {{ターゲットスキーマ名}}_{{カスタムスキーマ名}} になるように custom_schemas.sql で定義してあります。 そのため、CI環境のテーブルは dbt_cloud_pr_<job_id>_<pr_id>_{{カスタムスキーマ名}} の命名規則で作成されたデータセット名以下に作成されている状態です。 実装概要 作成したYAMLはこちら(クリックで展開) name : DBT Compile and Comment on PR on : pull_request : types : [ opened, synchronize, reopened ] jobs : dbt_compile : runs-on : ubuntu-latest permissions : contents : read pull-requests : write id-token : write env : DBT_ENVIRONMENT : dev steps : - name : Check out repository code uses : actions/checkout@v3 with : fetch-depth : 0 ref : ${{ github.event.pull_request.head.ref }} - name : Fetch base ref run : git fetch origin ${{ github.event.pull_request.base.ref }} - name : Set Up Auth uses : "google-github-actions/auth@v1" with : token_format : "access_token" workload_identity_provider : "hogehogehoge" service_account : "hogehogehoge@hogehoge.iam.gserviceaccount.com" - name : Set up Cloud SDK uses : google-github-actions/setup-gcloud@v1 - name : Set up Python uses : actions/setup-python@v3 with : python-version : 3.11 - name : Install dependencies run : | python -m pip install --upgrade pip pip install dbt-core dbt-bigquery - name : Generate profiles.yml run : | chmod +x ci/compile_sql_comment/generate_profile.sh ci/compile_sql_comment/generate_profile.sh ${{ github.event.pull_request.number }} - name : Compile DBT id : compile run : | dbt deps dbt compile --profiles-dir . --target dev --profile timee-dwh compiled_sqls="" files=$(git diff --name-only origin/${{ github.event.pull_request.base.ref }} ${{ github.event.pull_request.head.ref }} | grep '\.sql$' || true ) if [ -n "$files" ] ; then for file in $files; do compiled_file_path=$(find target/compiled -name $(basename $file)) echo "Compiled file path: $compiled_file_path" if [ -n "$compiled_file_path" ] ; then compiled_sql=$(cat "$compiled_file_path" ) compiled_sqls="${compiled_sqls}<details><summary>${file}</summary>\n\n\`\`\`\n${compiled_sql}\n\`\`\`\n\n</details>" fi done fi printf "%b" "$compiled_sqls" > compiled_sqls.txt - name : Comment on PR uses : actions/github-script@v6 with : script : | const fs = require('fs'); const output = fs.readFileSync('compiled_sqls.txt', 'utf8' ); const issue_number = context.payload.pull_request.number; const owner = context.repo.owner; const repo = context.repo.repo; async function processComments() { const comments = await github.rest.issues.listComments( { owner : owner, repo : repo, issue_number : issue_number, } ); const dbtComment = comments.data.find(comment => comment.body.startsWith('DBT Compile Result')); const body = ` DBT Compile Result : \n$ { output } `; if (dbtComment) { await github.rest.issues.updateComment( { owner : owner, repo : repo, comment_id : dbtComment.id, body : body } ); } else { await github.rest.issues.createComment( { owner : owner, repo : repo, issue_number : issue_number, body : body } ); } } processComments(); actionsの流れを説明するとこうなります。 PRブランチにチェックアウトして、PRブランチとmainの差分を確認するためにfetch workload_identity_providerを用いたBigQueryへの認証 必要パッケージのインストールと使用するパッケージの宣言 PRの情報をもとにprofiles.ymlの動的生成 コンパイル処理の実施 dbt compileの実行 mainとの差分があるファイルだけを抽出 差分ファイルのcompile結果を文字列化 PR上にcompile結果がなければdbt compile結果を新規コメント。既にcompile結果がコメントされていたらコメントをupdate 各ステップの説明 説明が必要そうなステップの説明をしていきます PRの情報をもとにprofiles.ymlの動的生成 上で述べた通り、CI環境のテーブルは dbt_cloud_pr_<job_id>_<pr_id>_{{カスタムスキーマ名}} の命名規則で作成されたデータセットに配置されています。 dbt compileの出力結果のデータセット名が dbt_cloud_pr_<job_id>_<pr_id>_{{カスタムスキーマ名}} になるように、デフォルトスキーマ名を動的に宣言するためのprofiles.ymlを作成するシャルスクリプトを作成しております #!/bin/bash set -e cat << EOF > profiles.yml timee-ci-dwh: target: ci outputs: dev: type: bigquery method: oauth project: ci_env_project schema: dbt_cloud_pr_39703_ $1 execution_project: ci_env_project threads: 1 EOF 参考: https://docs.getdbt.com/docs/core/connect-data-platform/bigquery-setup#oauth-via-gcloud これによりdbt compileの出力結果が、このターゲットスキーマ名を参照したスキーマになります。 コンパイル処理の実施 run : | dbt deps dbt compile --profiles-dir . --target ci --profile timee-ci-dwh compiled_sqls="" files=$(git diff --name-only origin/${{ github.event.pull_request.base.ref }} ${{ github.event.pull_request.head.ref }} | grep '\.sql$' || true ) if [ -n "$files" ] ; then for file in $files; do compiled_file_path=$(find target/compiled -name $(basename $file)) echo "Compiled file path: $compiled_file_path" if [ -n "$compiled_file_path" ] ; then compiled_sql=$(cat "$compiled_file_path" ) compiled_sqls="${compiled_sqls}<details><summary>${file}</summary>\n\n\`\`\`\n${compiled_sql}\n\`\`\`\n\n</details>" fi done fi printf "%b" "$compiled_sqls" > compiled_sqls.txt dbt compileをPRブランチで実行 git diffで差分があった.sqlファイル名をdbt compileの実行結果であるcompile済みsqlファイルが格納される target/compiled/ 以下でfindを実行してfileパスを取得 fileパスのcat結果を折りたたみタグ内に格納して文字列に追加 って流れの処理にしています。 このような処理にすることでSQLコンパイル結果のPRコメントを必要分だけコメントできる形としました。また、折りたたみタグに格納することでPullRequestの可視性を損なわないようにしました。 PR上にコメント - name : Comment on PR uses : actions/github-script@v6 with : script : | const fs = require('fs'); const output = fs.readFileSync('compiled_sqls.txt', 'utf8' ); const issue_number = context.payload.pull_request.number; const owner = context.repo.owner; const repo = context.repo.repo; async function processComments() { const comments = await github.rest.issues.listComments( { owner : owner, repo : repo, issue_number : issue_number, } ); const dbtComment = comments.data.find(comment => comment.body.startsWith('DBT Compile Result')); const body = `DBT Compile Result:\n${output}`; if (dbtComment) { await github.rest.issues.updateComment( { owner : owner, repo : repo, comment_id : dbtComment.id, body : body } ); } else { await github.rest.issues.createComment( { owner : owner, repo : repo, issue_number : issue_number, body : body } ); } } PR上のコメントの一覧を取得して、既に DBT Compile Result: で始まるコメントがPR上に存在するのであれば、そのコメントのアップデート、存在しないのであれば新しくコメントをする。 という処理をしています。 条件分岐が発生する処理を簡便に記載したかったのでinlineでjavascriptを記載できる github-script を使用して記述しています これによってactionが走るたびにコメントが新規でされるのではなく、一つのコメントが上書きされ続けるような処理となり、PullRequestの可視性を損なわないようにしました。 どんなふうに動くかみてみる こんなモデルをテストで作ってみました {{ config( schema = ' sample_schema ' )}} SELECT ' hogehoge ' AS name , 30 AS amount UNION ALL SELECT ' fugafuga ' AS name , 50 AS amount {{ config( schema = ' sample_schema ' )}} SELECT SUM (amount) AS sum_amount FROM {{ ref( ' sample_model1 ' ) }} AS sample_model1 以下のようにPullRequest上に変更内容が折り畳まれた状態でコメントされて、ref関数のコンパイル結果がCI環境のデータセットになっていることを確認できます 次にsample_model1.sqlを以下のように修正して再度pushしてみます {{ config( schema = ' sample_schema ' )}} SELECT ' hogehoge ' AS name , 30 AS amount UNION ALL SELECT ' fugafuga ' AS name , 50 AS amount UNION ALL SELECT ' blabla ' AS name , 100 AS amount 以下のように既存のcommentがeditされて、修正後の内容でコンパイルした結果で上書きされていることが確認できます 結果 弊社のデータ基盤は four keys計測による開発ヘルススコアの計測 を行っており、今回の仕組みのリリース前後で開発リードタイムを計測してみましたが、目立った影響は出ていませんでした😢 レビューを円滑にできる環境を整っていないことが課題ではなく、他業務との兼ね合いでDWH開発のレビューに充てることができる時間がそもそも少なそうだったり、レビューに必要なドメインのインプットが足りていないことがボトルネックになっていそうだなという発見にも繋がったので、そこはプラスに捉えています メンバーの声を聞いていると便利なことは間違いないらしいので、レビューコストの低減による持続的なトイル削減に将来的には繋がっていけばいいなと思ってます🙏 使えそうだな。試してみようかなと思っていただけたら幸いです! We’re Hiring! タイミーのデータ統括部では、ともに働くメンバーを募集しています!! product-recruit.timee.co.jp
アバター
こんにちは、タイミーのデータ統括部データサイエンス(以下DS)グループ所属の菊地です。 今回は、タイミーがBIツールとして導入しているLookerでの、 H3 を使用した可視化をするための取り組みを紹介したいと思います! H3とは H3 とは、Uber社が開発しているグリッドシステムで、オープンソースとして提供されています。 H3 では、位置情報に紐づいたイベントを階層的な六角形の領域にバケット化することができ、バケット化された単位でデータの集計が可能になります。 タイミーでは、サービスを提供する各都市の需給を測定するために,六角形単位で集計したデータを可視化するなど、様々な場面での分析に活用しており、例えば以下のような可視化を行なっております(数値はランダム値です)。 H3 についての詳細は、以下のページが参考になるかと思います。 https://h3geo.org/ https://github.com/uber/h3 https://www.uber.com/en-JP/blog/h3/ 前提条件 LookerのデータソースはGoogle Cloud BigQueryとします。 可視化に必要な各種ファイルの生成にはPythonを用いており、使用しているPythonバージョンと、依存ライブラリのバージョンについては、以下で動作確認を行っています。 Pythonバージョン: 3.11.4 依存ライブラリ geojson==3.0.1 h3==3.7.6 numpy==1.25.2 pandas==2.0.3 shapely==2.0.1 topojson==1.5 また、大変簡略化した例ですが、データソースのBigQueryプロジェクト・データセット・テーブルは、以下の想定とします。 プロジェクト名: sample-project データセット名: sample-dataset テーブル sales : 売上データを保持しているテーブル sales_at : 売上の日時 amount : 売上 place_id : places テーブルの外部キー places : 位置情報(緯度・経度)を保持しているテーブル places テーブルと sales テーブルには1:nのリレーションが存在 erDiagram places ||--|{ sales: "1:n" places { INTEGER id FLOAT latitude FLOAT longitude } sales { INTEGER id DATETIME sales_at INTEGER amount     INTEGER place_id } Lookerでの可視化を行うための手順 今回は以下の手順に従って、上記saleテーブルの売上をH3六角形にバケット化し、Looker上で可視化します。 緯度経度情報を保持しているBigQueryテーブルにH3六角形IDを付与し、別テーブルとして保存 TopoJsonファイルの作成 作成したTopoJsonファイルをLookerに追加 Lookerのmodelファイルにmap_viewフィールドを追加 Lookerのviewファイルにdimensionを追加 Lookerのmodelファイルにexploreを追加 Lookerでの可視化 1. 緯度経度情報を保持しているBigQueryテーブルにH3六角形IDを付与し、別テーブルとして保存 集計の際に使用する「緯度経度情報を保持しているBigQueryテーブル」(ここでは places テーブル)に対して、H3六角形ID(以下H3 hex idと記載)を付与し、別テーブルとして保存しておきます。ここでは h3_places テーブルとして保存しています。 下記は、 places テーブルをpandas.DataFrameとして読み込み、H3六角形解像度0~15までのH3 hex idを付与し、テーブルとして書き出すコードの例です。 H3六角形解像度は値が大きくなるにつれて、小さな六角形(=解像度が上げる)になり、詳細については下記ドキュメントが参考になるかと思います。 https://h3geo.org/docs/core-library/restable/ import h3 import pandas as pd class BigQueryClient : def __init__ (self): ... def read_table_as_dataframe (self, table_id: str ) -> pd.DataFrame: """BigQueryテーブルをpandas.DataFrameとして読み込む処理""" ... def write_table_from_dataframe (self, df: pd.DataFrame, table_id: str ) -> None : """pandas.DataFrameをBigQueryテーブルを書き込む処理""" ... def make_h3_hex_ids (df: pd.DataFrame) -> pd.DataFrame: _df = df.copy() for resolution in range ( 16 ): # 緯度・経度情報を元に、H3 hex idを付与 _df[f 'h3_hex_id_res_{resolution}' ] = df.apply( lambda x: h3.geo_to_h3(x[ 'latitude' ], x[ 'longitude' ], resolution), axis= 1 ) return _df if __name__ == '__main__' : ... bq_client = BigQueryClient() # 緯度(latitude)、経度(longitude)を保持しているBigQueryテーブルをDataFrameとして読み込む df = bq_client.read_table_as_dataframe( 'sample-project.sample-dataset.places' ) # H3 hex idを付与する h3_df = make_h3_hex_ids(df) h3_df.rename(columns= dict ( id = 'place_id' ), inplace= True ) # H3 hex idを付与したDataframeをBigQueryテーブルとして書き込み bq_client.write_table_from_dataframe(df=h3_df, 'sample-project.sample-dataset.h3_places' ) 例として、以下のような緯度経度を保持しているサンプルデータに、H3 hex idを付与した場合、以下のような結果になります。 import numpy as np np.random.seed( 42 ) tokyo_latitude = 35.6762 tokyo_longitude = 139.6503 df = pd.DataFrame( [[i, np.random.normal(tokyo_latitude, 0.3 ), np.random.normal(tokyo_longitude, 0.3 )] for i in range ( 1 , 11 )], columns=[ 'place_id' , 'latitude' , 'longitude' ] ) h3_df = make_h3_hex_ids(df) h3_df.head( 10 ) 2. TopoJsonファイルの作成 「1. 緯度経度情報を保持しているBigQueryテーブルにH3 hex idを付与する」でH3 hex idを付与したDataFrameを元に、TopoJsonファイルを作成します。 TopoJsonの詳細についてはこちらの「 topojson 」GitHubリポジトリを参照してください。 下記は、TopoJsonファイルを作成するコード例です。 処理の内容としては、GeoJson形式を経由して、TopoJsonに変換し、ファイルとして出力をしています。 TopoJsonファイルは、 H3解像度 別に作成しています。 from pathlib import Path import geojson import h3 import pandas as pd from shapely import geometry import topojson class H3ToGeojson : @ staticmethod def get_h3_geojson_features (h3_hex_ids: list [ str ]) -> list [geojson.Feature]: polygons = h3.h3_set_to_multi_polygon(h3_hex_ids, geo_json= True ) features = [geojson.Feature(geometry=geometry.Polygon(polygon[ 0 ]), properties= dict (h3_hex_id=h3_hex_id)) for polygon, h3_hex_id in zip (polygons, h3_hex_ids)] return features def get_h3_geojson_feature_collection_from_dataframe (self, df: pd.DataFrame, h3_hex_id_column: str ) -> geojson.FeatureCollection: assert df.columns.isin([h3_hex_id_column]).any(), f 'column `{h3_hex_id_column}` is not exists.' unique_h3_hex_ids = df[h3_hex_id_column].unique().tolist() geojson_features = self.get_h3_geojson_features(unique_h3_hex_ids) feature_collection = geojson.FeatureCollection(geojson_features) return feature_collection class H3ToTopojson : def __init__ (self): self.h3_to_geojson = H3ToGeojson() def get_h3_topojson_topology_from_dataframe (self, df: pd.DataFrame, h3_hex_id_column: str ) -> topojson.Topology: feature_collection = self.h3_to_geojson.get_h3_geojson_feature_collection_from_dataframe( df, h3_hex_id_column=h3_hex_id_column ) return topojson.Topology(feature_collection, prequantize= False ) def make_h3_topojson_file_from_dataframe (self, df: pd.DataFrame, h3_hex_id_column: str , save_file_path: Path) -> None : topojson_topology = self.get_h3_topojson_topology_from_dataframe(df=df, h3_hex_id_column=h3_hex_id_column) topojson_topology.to_json(save_file_path) if __name__ == '__main__' : ... h3_to_topojson = H3ToTopojson() save_dir = Path( 'topojson' ) save_dir.mkdir(exist_ok= True ) for resolution in range ( 0 , 16 ): h3_hex_id_column = f 'h3_hex_id_res_{resolution}' h3_to_topojson.make_h3_topojson_file_from_dataframe(df=h3_df, h3_hex_id_column=h3_hex_id_column, save_file_path=save_dir / f '{h3_hex_id_column}.json' ) 例として、先ほど作成したサンプルデータに対して、 resolution=4 を指定してTopoJsonファイルとして書き出す処理は以下のようになります。 h3_to_topojson = H3ToTopojson() h3_to_topojson.make_h3_topojson_file_from_dataframe(h3_df, resolution= 4 ) TopoJsonファイルの中身は以下のようになります。 { " type ":" Topology "," objects ": { " data ": { " geometries ": [{ " properties ": { " h3_hex_id ":" 842f5a3ffffffff " } ," type ":" Polygon "," arcs ": [[ -5 , -2 , 0 ]] ," id ":" feature_0 " } , { " properties ": { " h3_hex_id ":" 842f5bdffffffff " } ," type ":" Polygon "," arcs ": [[ 1 , -4 , 2 ]] ," id ":" feature_1 " } , { " properties ": { " h3_hex_id ":" 842f5abffffffff " } ," type ":" Polygon "," arcs ": [[ 3 , 4 , 5 ]] ," id ":" feature_2 " }] ," type ":" GeometryCollection " }} ," bbox ": [ 139.198358 , 35.267135 , 140.126313 , 36.103519 ] ," arcs ": [[[ 139.44526 , 35.765969 ] , [ 139.458427 , 36.000295 ] , [ 139.695196 , 36.103519 ] , [ 139.918545 , 35.971536 ] , [ 139.903854 , 35.7366 ]] , [[ 139.44526 , 35.765969 ] , [ 139.667342 , 35.634256 ]] , [[ 139.653549 , 35.399825 ] , [ 139.419179 , 35.297723 ] , [ 139.198358 , 35.429167 ] , [ 139.21065 , 35.662982 ] , [ 139.44526 , 35.765969 ]] , [[ 139.653549 , 35.399825 ] , [ 139.667342 , 35.634256 ]] , [[ 139.667342 , 35.634256 ] , [ 139.903854 , 35.7366 ]] , [[ 139.903854 , 35.7366 ] , [ 140.126313 , 35.603627 ] , [ 140.111006 , 35.368594 ] , [ 139.874758 , 35.267135 ] , [ 139.653549 , 35.399825 ]]]} 3. 作成したTopoJsonファイルをLookerに追加 LookerのFileBrowserを開いて、先ほど作成したTopoJsonファイルを追加します。 追加後、適切なフォルダにファイルを移動します。ここでは maps/h3 フォルダにTopoJsonファイルを移動します。 ├── maps    └── h3    ├── h3_hex_id_res_0.topojson    ├── h3_hex_id_res_1.topojson    ├── h3_hex_id_res_2.topojson ...    └── h3_hex_id_res_15.topojson 4. Lookerのmodelファイルにmap_viewフィールドを追加 下記のようにmap_layerを設定します。map_layerは H3解像度 別に設定しています。 property_key は「2. TopoJsonファイルの作成」で使用している H3ToGeojson.get_h3_geojson_features メソッド内の geojson.Feature の引数で設定している properties のkey名である h3_hex_id を指定しています。 map_layer: h3_hex_id_res_0 { file : "/ maps / h3 /h3_hex_id_res_0. topojson " format : topojson property_key: " h3_hex_id " } map_layer: h3_hex_id_res_1 { file : "/ maps / h3 /h3_hex_id_res_1. topojson " format : topojson property_key: " h3_hex_id " } map_layer: h3_hex_id_res_2 { file : "/ maps / h3 /h3_hex_id_res_2. topojson " format : topojson property_key: " h3_hex_id " } ... map_layer: h3_hex_id_res_15 { file : "/ maps / h3 /h3_hex_id_res_15. topojson " format : topojson property_key: " h3_hex_id " } 5 . Lookerのviewファイルにdimensionを追加 map_layer_nameは、「4. Lookerのmodelファイルにmap_viewフィールドを追記」で作成した、map_layer名を指定します。 dimensionは H3解像度 別に設定しています。 view : h3_places { sql_table_name: ` sample - project . sample - dataset .h3_places` ;; dimension : h3_hex_id_res_0 { group_label: " H3 " group_item_label: " H3 解像度0の六角形 ID " type : string sql : $ { TABLE } .h3_hex_id_res_0 ;; map_layer_name: h3_hex_id_res_0 } dimension : h3_hex_id_res_1 { group_label: " H3 " group_item_label: " H3 解像度1の六角形 ID " type : string sql : $ { TABLE } .h3_hex_id_res_1 ;; map_layer_name: h3_hex_id_res_1 } dimension : h3_hex_id_res_2 { group_label: " H3 " group_item_label: " H3 解像度2の六角形 ID " type : string sql : $ { TABLE } .h3_hex_id_res_2 ;; map_layer_name: h3_hex_id_res_2 } ... dimension : h3_hex_id_res_15 { group_label: " H3 " group_item_label: " H3 解像度15の六角形 ID " type : string sql : $ { TABLE } .h3_hex_id_res_15 ;; map_layer_name: h3_hex_id_res_15 } } 6 Lookerのmodelファイルにexploreを追加 下記のようにexploreを追加します。 explore : sales { label : " sales " ... join : h3_places { view_label: " place " type : inner sql_on: $ { sales .place_id } = $ { h3_places.place_id } ;; relationship : many_to_one } } 7. Lookerでの可視化 作成したexploreでマップでの可視化を行うと、地図上にH3六角形メッシュが表示され、メッシュ毎にバケット化された集計値を色で表現することができます。 下記は東京近郊のデータを H3解像度 7のdimensionを使用して可視化した例です(数値はランダム値です)。 今回作成した H3解像度 dimensionを変更することで、目的に合わせて六角形メッシュの大きさを変更して可視化を行うことが可能です。 まとめ 今回は、Uber社がオープンソースとして提供している H3 を使用して、Looker上で可視化を行う方法について解説しました。 タイミーでは今回紹介したLookerでの可視化以外にも、機械学習の特徴量作成時に使用するなど、様々な場面で H3 を活用しています。 今後も地理情報を活かした分析をする際に活用していきたいと考えています。 We’re Hiring! タイミーのデータ統括部では、ともに働くメンバーを募集しています!! 現在募集中のポジションは こちら です! 「話を聞きたい」と思われた方は、是非一度カジュアル面談でお話ししましょう! mermaid.initialize({startOnLoad: true});
アバター
はじめに マッチング領域、ワーキングリレーションチームの @Juju_62q です。 タイミーでは有志のメンバーが集まって1年半ほど前から輪読会を行っています。 現在5冊の書籍を読み終わっています。 ブログのタイトルにもありますが、今回 エンタープライズ アプリケーション アーキテクチャパターン (略称PoEAA)の輪読を実施しました。 書籍選定には以下のような狙いを置いていました。 複雑な ドメイン をソフトウェアで扱うための術を身につける アクティブレコードパターンにより詳しくなり、 Rails を上手く扱えるようになる 書籍に出てくる デザインパターン をきっかけとしてタイミーの アーキテクチャ について議論し、実験のきっかけを作る。 輪読会の方法 週1でmiroを利用して実施しています。 輪読会は参加者の「わからなかったこと」、「話してみたいこと」を掘り下げるような形で実施しています。 また、特別取り上げることは少ないですが、個人の感想や勉強になった場所を書けるようになっています。 「わからなかったこと」では参加者が書籍を読んでわからなかったことについて話します。 これは少し理解が曖昧な部分について、みんなで解説を行い理解を深めている様子です。 「話してみたいこと」ではタイミーの話や、ソフトウェアエンジニアリング一般で用途や使い分けについて話しています。 下記は「重ロック」の概念をタイミーのマッチングロジックに適用できないか話している様子です。 感想はそれぞれが自由に書いています。 輪読会では「参加者が少しでも参加前より理解できること」を目的に以下のような心構えを置いています。 本輪読会は読んだ人の疑問や議論したいことに基づいて意見交換をする形をとっています。したがって、参加者の協力がなければ良いものになることはありません。チームの学びを最大化するためにもあなたの思ったことを教えてください。 発言の正しさや凄さに関わらず、疑問、自分の意見を発言することに対して常にポジティブです。 ファシリテーター 及び参加者の皆さんはどんな意見も歓迎し、発言したことにまず感謝をするように努めてください。 特に今回は解釈が難しい書籍だったと思うので、「わからない」が主張しやすいことは大事だったと思っています。今後も参加者の「わからない」を大事にして運用していきたいと考えています。 学びや感想 岡野 Web Application Frameworkがよくできているというのを強く感じることができる書籍でした。 書籍の中に出てくる デザインパターン や考え方はタイミーでも実際に活かせることが多く、具体のテーブルやモデルについて議論ができるのはとても良い体験だったと思っています。 ActiveRecord と ドメイン モデルの関係とかの話が2002年の段階で整理されていたのは本当にすごいです。 難波 タイミーのバックエンドは Ruby on Rails を使っているので、この本を読むことでActive Recordの複雑さや大変さについて認識を新たにすることができたのはとても良かったです。書籍が発行された時代を感じるような課題意識や制約も多々ありますが抽象的に捉えることで現代でも参考になる部分はたくさんあったので、こういった過去の名著を一通り読んでみるのはオススメです。それにしてもLockは考えることが多くて本当に大変。 須貝 いつかは読みたいと思いながらなかなか読めずにいたPoEAAにチャレンジできて本当に良かったです。機会を作ってくれた輪読会主催メンバーに感謝しています。 書籍の中で紹介されているパターンが開発者間での共通言語となり「これは重オフラ インロック では?」といった会話がSlack上で発生することもありました。自分としてはまだまだ理解できていない部分もたくさんあるので今後も読み返していきたいと思います。 texta.fmの最初のほうのエピソードを聴くと Ruby on Rails との関係性も含めてさらに理解が深まるので本当におすすめです。自分はかなり助けられました。 終わりに PoEAAは2002年とそれなりに古い書籍ではありますが、考え方は現代にも通じる良い本です。 ここに書かれている設計パターンは Rails でもたくさん実装されています。 複雑なビジネスを扱う上で強力な武器になると思うので、みなさんぜひ一度読んでみてください。
アバター
こんにちは、Androidエンジニアのはる( @ haru2036 )とシャム( @arus4869 )です! 私たちは2023年6月8-9日にアメリカ合衆国サンフランシスコで開催された droidcon San Francisco 2023 に参加してきました。 前回のイベント報告編に引き続き、実際のセッションを紹介していきたいと思います。 tech.timee.co.jp 特に気になったセッション(haru編) Mobile Feature Flags and Experiments at Uber はじめに取り上げるのはUberのMahesh HadaさんとArun Babu A S PさんによるUberでのFeature Flag運用に関するセッションです。 Uberでは大量のFeature FlagやExperimentalなFeatureを管理するために独自のFeature Flag自動生成などの取り組みを行っているそうで、そういったFlagのFetchをするタイミングもサービスの性質上「大きく位置情報が変化したとき」など特殊なものがあるということでした(日本から渡航してきたばかりの私の端末上でもそれをトリガーとしたFlagのFetchが走ってたんだなあ、と謎の実感を持ちながら聞いていました)。 また、そういったFlagによって問題が起きた時にできる限り早くロールバックするためにFCMを用いて緊急ロールバック用メッセージを送信しているという話を聞いた時はなかなか衝撃を受けました。 タイミーでもFirebase Remote Configなどを使ったFeature Flag管理を行っているのですが、それとは桁外れに大規模で即時性の高い管理が行われていてさすがだなと思いました。 上記の緊急ロールバックの話など、世界で展開している大きなサービスならではの手法を知ることができたセッションでした。 Unlocking the Power of Shaders in Android with AGSL and Jetpack Compose 次に取り上げるのはRikin MarfatiaさんのAGSLをJetpack Composeで使うセッションです。 AGSLはAndroid上で利用できるシェーダ言語で、GPUを使用して画面を描画することができます。 私は趣味でUnityなどでシェーダを扱っていたのですが、それとよく似た記法のAGSLを用いることで視覚的にリッチなUIを実現することができるそうでした。 実際にデモとして紹介されていたUIはボタンが電球のように光ったり、写真ギャラリーの切り替え時に色収差を発生させるなどのとても派手な表現でしたが、それをCompose上で簡単に実現できることに感動しました。 AGSLについてはこちら↓ developer.android.com Reimagining text fields in Compose 最後に取り上げるのはGoogleのZach KlippensteinさんによるJetpack ComposeにおけるTextFieldの成り立ちとこれからについてのセッションです。 多くのプロダクトと同様に、MVPから始めてユーザースタディを繰り返しながら開発していったそうなのですが、これまた多くのプロダクトと同様に現在では技術的負債も貯まってきてしまっているそうです。 そこで、それを解消すべく大きくAPIを変更したBasicTextField v2を開発しているそうです。ユーザースタディ参加者も募集中だよ!とのことです。参加してみたいなあ。 GoogleでのCompose開発の舞台裏と、TextFieldの未来を同時に知ることができる一粒で二度美味しいセッションでした。 speakerdeck.com 特に気になったセッション(syam編) 次にsyamが特に気になったセッションを紹介していきます! Navigating the Unknown: Tips for Efficiently Learning a New Codebase ADAM GREENBERGさんによるセッションを紹介します。 新しいコードベースを理解し、その知識を活用するための具体的な手法と戦略について深く掘り下げたセッションでした。 ADAM GREENBERGさん は、自身の経験を基に、コードベースの理解、ドキュメンテーションの作成、そしてその知識の共有という3つの主要なステップを中心にお話ししていました。 以下にセッションで触れた3つの重要なステップについて触れます。 1. コードベースの理解 新しいプロジェクトや既存のコードベースに取り組む開発者にとって、コードベースの理解は非常に重要なステップである。コードベースの理解を促進させるためにはアーキテクチャ図の作成やデバッグツールの使用などが重要であると話していました。 2. ドキュメンテーションの作成 ドキュメンテーションをすることは、学んだこと振り返ったりを他の人々と共有するための重要なステップであると説いています。アーキテクチャ図の作成、重要な機能の説明、特定のコードスニペットの説明など必要に応じてドキュメンテーションする必要があると話していました。 3. 知識の共有 他の開発者が自分の知識を利用し、コードベースをより効果的に利用するための重要なステップです。このステップがチーム全体の生産性と効率を向上させ、個々の開発者が自身の理解を深めるのに役立つと述べました。 このセッションは、新しいコードベースを効果的に理解し、その知識を活用・共有するために大事なポイントをお話ししていました。 ちょうど僕たちのチームもオンボーディングや共通認知をとるためのREADMEを作成していることもあり、当たり前のことかもしれませんが、改めて聞くことができたのでこのセッションは有用でした。 Find your way with GoogleMap() {} 次は、 BRIAN GARDNERさんによるセッションを紹介します。 このセッションでは、その可能性を具体的に示すために、マップの表示、マーカーの追加、そしてクラスタリングといった基本的な機能がどのように簡単に実装できるのかを学びました。 特に印象的だったのは、大量のマーカーを効率的に表示するためのクラスタリング機能です。マーカーの数が増えると、地図が混雑し、ユーザーが特定のマーカーを見つけるのが難しくなります。しかし、クラスタリングを使用することで、近接するマーカーを一つのクラスタとして表示でき、地図の見やすさとパフォーマンスを向上させることができるとのことです。 また、このセッションでは、具体的な実装方法を確認するためのソースコードも提供されました。 JetpackComposeのGoogleMapを使用する際にいくつか注意点も述べていました。 クラスタリングについての注意点として、MarkerをClustering内で使用しないようにとの警告がありこれは、 IllegalStateException を引き起こす可能性があるそうです。 また、パフォーマンスを向上のために MapsInitializer.initialize(context, MapsInitializer.Renderer.LATEST) を使用して、最新のレンダラーを利用することを推奨していました。 さらに、Paparazziテストで、特にプレビューモードでの早期リターンを利用して、レイアウトを保持するためのBoxを返すと良いとのことでした。 ANR問題についても触れられていましたが、具体的な解決策は聞き取れなかったので、調べてみようかなと思います。 ちょうどGoogleMap周りをコンポーズ化している時だったので、とても有り難かったです。 セッションスライド speakerdeck.com ソースコード github.com Animating content changes with Jetpack Compose 最後に KINNERA PRIYA PUTTIさんによるセッションを紹介します! Jetpack Compose for Desktopを使ってスライドを作っており、随所にアニメーションが動くすごいプレゼンテーションでした! KINNERA PRIYA PUTTIさん は自身の実践を踏まえ、UIアニメーションをより魅力的にするための各種アニメーションテクニックについて語ってくれました。 以下にセッションで紹介された注目すべきテクニックについて触れていきます。 1. CrossFadeによるコンテンツ間の切り替え 新旧の画面や要素間のトランジションを滑らかにするCrossFadeは、ユーザーがアプリの使用中に感じる違和感を軽減するための非常に重要な要素です。彼は、これがいかに自然なユーザー体験を生むかについて語っていました。 2. animateContentSizeによるコンテンツサイズのアニメーション化 コンテンツのサイズを変更する必要がある場合、animateContentSizeを用いるとその変更がなめらかになります。これは、ユーザーが自由にコンテンツのサイズを調整できるようにするための重要なステップであると彼は指摘していました。 3. 各種UI要素に対するアニメーションの追加 リスト、詳細画面、ナビゲーションドロワー、ボトムシート、ダイアログなどのUI要素にアニメーションを追加することで、ユーザーフレンドリーなUIを実現する方法について具体的に語られました。 このプレゼンテーションは、Jetpack Composeを活用して、ユーザーにとって使いやすく、魅力的なUIアニメーションを創出するための重要なポイントを提供していました。 我々のチームもUI開発におけるアニメーションの活用に取り組んでおり、このような新たな視点やアイデアを得られることは非常に有意義でした。 まとめ 今回はセッションの内容をいくつか抜粋して紹介させて頂きました。 もしより詳しい内容や海外カンファレンスの様子に興味を持っていただけたら、カジュアル面談でお話しすることもできますので是非一度お話ししましょう! product-recruit.timee.co.jp
アバター
はじめに こんにちは、Androidエンジニアのはる( @ haru2036 )とシャム( @arus4869 )です! 私たちは2023年6月8~9日にアメリカ合衆国サンフランシスコで開催された droidcon San Francisco 2023 に参加してきました。 タイミーでは KaigiPass というカンファレンス参加補助制度があり、その海外参加第一号として私たちが参加してきた形になります。 私たちが開発・運営している タイミー は、ワーカーさんが利用するためにモバイルアプリが必要不可欠なサービスとなっており、その開発に関する知見を広く得るために参加してきました。 テック企業が集まるサンフランシスコでのカンファレンスは内容もそうですが情報の新鮮度といういう意味でも良い刺激になり、とても有意義な経験をすることができました。 会場のようす 会場となったカリフォルニア大学サンフランシスコ校は、医学分野が主になっているそうで、カンファレンスセンターのすぐ隣にリハビリセンターのようなものがあったりしました。そう言った意味ではソフトウェアのカンファレンス会場としては結構異色なのではないでしょうか。 また、国内のカンファレンスと比べてスポンサーの層が結構違うという感想を抱きました。 国内カンファレンスでは多くの場合Androidプラットフォーム上でサービスを展開している事業会社がスポンサーをしているケースが多いのですが、それと比較するとSentry, Bitrise, DataDogなどほとんどの会社が事業会社に対して開発をサポートするサービスを提供している会社だったのが興味深かったです。 https://twitter.com/arus4869/status/1666885385597898752?ref_src=twsrc%5Etfw%7Ctwcamp%5Etweetembed%7Ctwterm%5E1666885385597898752%7Ctwgr%5Eb44440b405611dde3ca583c63950f1096c7d8b21%7Ctwcon%5Es1_&ref_url=https%3A%2F%2Fwww.notion.so%2Ftimee%2Fd5096f7c9c0647b2a036b7393917e7b7 My first time abroad was #dcsf23 and it was really good. Thanks to the very helpful sessions and kind people! pic.twitter.com/MhtTpYF2ms — はる (@haru2036) 2023年6月10日 実際にブース出展している様々な会社のノベルティをいただきました。 droidcon sanfransiscoTシャツは入場者特典でみんなもらえるみたいです! すごく靴下が多い印象でした。 どこの会社のステッカーも良かったのですが、droidconステッカーが中でも嬉しかったです また、会場では一日を通して食事や飲み物が提供されていたのですが、全て屋外で提供されており参加者の憩いの場となっていました。私(はる)も朝は顔くらいのサイズがあるクロワッサンをかじりながらコーヒーを飲んでいました(笑) ランチの時間の屋外では見知らぬ人でも活発に話しかけて盛り上がっているところも多く、私も初めて会う方とお話しすることが多かったです。 実際のセッションの内容については後日投稿するセッション内容編をご覧ください! tech.timee.co.jp 初めての海外カンファレンスに参加してみて 今回初めて海外カンファレンスに参加してみた私たちでしたが、印象的だったのは先述のスポンサーの違いだけではありません。セッション自体の内容も、全世界でサービスを提供しているUberのような企業による実際の実装の解説や、Androidというプラットフォーム自体を提供しているGoogle自身によるフレームワーク自体についての解説、今後の予定の発表など、一次情報に直接触れている感覚がとても強いイベントでした。 また、日本でも「この中で⚪︎⚪︎使ってる方どのくらいいらっしゃいますかね?」と質問する発表者の方は多いですが、今回のイベントではさらに多くのスピーカーがそう言った質問を投げかけたりしていました。 また、結構な確率でジョークを挟んでくるスピーカーが多く、この辺りの話術は自分が発表する際にも見習いたいな、と感じました。 また、今回のトレンドとして多くセッションがあったのはJetpack Composeに関する話題で、やはり世界中のAndroid開発者が興味を持っているトピックなんだなと感じました。 おまけ せっかくサンフランシスコに行ったので、近くにあるいくつかのとても行きたかった場所に行ってきました! Computer History Museum (マウンテンビュー) 興奮のあまり建物の写真を撮り忘れました…… コンピュータに関わる人なら人生で一度はぜひ行ってもらいたいComputer History Museum。念願かなっていくことができました。そろばんや計算尺などの道具から始まり、コンピュータ科学の父と呼ばれているアラン・チューリングが解読したエニグマ暗号機の実機や、GUIを初めて搭載したコンピュータであるXerox Alto、iPhoneのご先祖様と言えなくもないNewton MessagePad, そして2021年に発売されたばかりのAI用超巨大プロセッサ、Cerebras Wafer Scale Engine 2などコンピュータの始まりから現在に至るまでのエポックメイキングなコンピュータたちが多数収蔵されていました。 周りを少し散策するだけでGoogleのロゴが入った建物だらけのエリアに迷い込んでしまうので、これぞシリコンバレー!という感じの場所でした。 Twitter X本社 当時は、この看板が外れるとは思いませんでしたが、Twitterの看板が外れる前に見ることができてよかったです! まとめ 今回の海外カンファレンスへの参加では、帰国してすぐにコードベースの改善に活かせる情報から、これからのAndroidを取り巻く情報までの幅広い知見たちだけではなく、実際にその場にいることによって得られる肌感覚のようなものや海外の開発者との新しい出会いなど、絶対にその場にいないと得られないものをたくさん持ち帰ってくることができたと思います。 また、タイミーでは一緒にサービスを成長させていく方を募集しています。もし少しでも興味を持っていただけたら、カジュアル面談受け付けておりますので是非一度お話ししましょう! product-recruit.timee.co.jp
アバター
こんにちは、マッチング領域でバックエンドエンジニアをしているぽこひで ( @pokohide ) です。 前回はRails edgeでCIを回し始めた話を紹介しました。 tech.timee.co.jp 今回は、実際に弊社でCIをRails edgeで回し始めた事で見つけたエラーの例を紹介していきます。記事公開時点(2023年7月)のバージョンは下記の通りです。 $ ruby -v ruby 3.2.2 (2023-03-30 revision e51014f9c0) +YJIT [aarch64-linux] $ rails -v Rails 7.0.6 ActiveRecord::DangerousAttributeError object_id is defined by Active Record このエラーに関する参考記事はこちらです。 euglena1215.hatenablog.jp 一部のモデルで object_id というカラム名を定義していたため以下のようなエラーが発生し、そのレコードを生成しているテストが軒並み落ちました。 ActiveRecord::DangerousAttributeError: object_id is defined by Active Record. Check to make sure that you don't have an attribute or method with the same name. # /usr/local/bundle/gems/factory_bot-6.2.1/lib/factory_bot/decorator/new_constructor.rb:9:in `new' # /usr/local/bundle/gems/factory_bot-6.2.1/lib/factory_bot/decorator.rb:16:in `send' ... Objectクラスで、オーバーライドされると予期せぬ影響を与えうるメソッド名と同じ名前を利用できなくなったからでした。 object_id に限らず dup , freeze , hash , class , clone なども利用できなくなるのでそういったカラム名を使わないようにする必要があります。 このエラーは object_id を object_identifier とリネームする事で対応しました。 Rails7.0.5.1からの create_association の挙動変化によるエラー 巷で話題のcreate_associationの挙動変更によるエラーです。既存レコードが存在する場合にユニーク制約によりバリデーションエラーが発生するといってテストケースがありましたが、そちらのテストが落ちました。Rails edgeを回し始めた頃はRails v7.0.4だったため、このエラーに遭遇しました。 Rails v7.0.5.1から create_association の挙動が「別々のトランザクションでinsertしてからdeleteする」から「同一トランザクションでdeleteしてからinsertする」に変わりました。この影響により、DBのレコードに依存するバリデーション(例: validates_uniquness_of )が効かなくなりました。この挙動変化に関する内容はこちらをご参考ください。 blog.willnet.in parser gemを利用してcreate_associationの呼び出し箇所を調査し、影響範囲を一つずつ確認、必要な箇所に条件文を追加するなどして対応しました。 既にリリースされている変更ではありますが、Rails edgeでCIを回し始めたことで早期に問題を発見でき、create_association削除する前に検証を挟む新しいオプションの提案をrails/railsへのPR *1 を通して行うなどしました。 NoMethodError: undefined method `reading_role' for ActiveRecord::Base:Class Rails7.1から ActiveRecord::Base.reading_role がなくなるため、この記述を行っていた箇所のテストが落ちました。 irb(main): 002 : 0 > ActiveRecord :: Base .reading_role { " severity " : " WARN " , " message " : " DEPRECATION WARNING: ActiveRecord::Base.reading_role is deprecated and will be removed in Rails 7.1. \n Use `ActiveRecord.reading_role` instead. \n (called from xxxxx) " } => :reading irb(main): 003 : 0 > ActiveRecord :: Base .writing_role { " severity " : " WARN " , " message " : " DEPRECATION WARNING: ActiveRecord::Base.writing_role is deprecated and will be removed in Rails 7.1. \n Use `ActiveRecord.writing_role` instead. \n (called from xxxxx) " } => :writing この問題に関しては元々出ていたWarningの内容に従い、 ActiveRecord.reading_role を代わりに使う事で対応しました。 partialsにインスタンス変数をlocalsとして渡す挙動が削除されたことによるエラー 弊社では請求書(PDF)を生成するために ActionController::Base#render_to_string を用いてHTML文字列を取得しています。その処理の中でpartialsにインスタンス変数をlocalsとして渡していましたが、そこでエラーが発生しました。 今までWarningが出ていた内容ではありますが、Rails7.1からインスタンス変数をlocalsで渡せなくなったためです *2 。 インスタンス変数ではなくローカル変数として渡すことで対応を予定しています。 before_type_castの返り値の型が変わったことによるエラー # create_table :posts, force: true do |t| # t.integer :foo, default: 1, null: false # end class Post < ActiveRecord :: Base enum foo : { x : 1 , y : 2 } end Integer型でEnumを定義しているカラム foo を持つモデルのレコードに対して record.foo_before_type_cast で参照すると、元々の環境ではInteger型で返っていたものが、Rails edge環境下ではString型で返るようになったため落ちているテストケースを見つけました。 record = Post .new( foo : :x ) # 検証当時の環境(Rails v7.0.6) record.foo_before_type_cast => 1 # Rails edge record.foo_before_type_cast => " 1 " 今回のエラーを再現するコード *3 を用意し、 git bisect *4 を利用して二分探索で挙動が変わったをコミットを調査しました。その後のコミットやPRを追ったところ、rails v7.1からDBの型で値を取得する *_for_database が追加されていることに気づいたため、その実装をバックポートして使うように修正することで対応を行いました。 # frozen_string_literal: true case Rails :: VERSION :: STRING when /^ 7 \. 0 / # 以下の定義を読み込むために何もしない。 when /^ 7 \. 1 / # v7.1 には含まれているため読み込まない。 return else # v7.2 以降で削除し忘れないように例外を投げる。 raise ( ' Consider removing this patch ' ) end module ActiveRecord module AttributeMethods module BeforeTypeCast # refs: https://github.com/rails/rails/pull/46283 def read_attribute_for_database (attr_name) name = attr_name.to_s name = self .class.attribute_aliases[name] || name attribute_for_database(name) end end end end 最後に 今回は、Rails edgeでCIを回すことによって見つけた将来動かなくなるコードの早期発見やその対応、原因についての簡単な解説を行いました。また紹介しきれていないですが、エラーだけでなくRails 7.2からの廃止予定を知らせるWarningもいくつか確認できました。 Rails edge導入当初は112個のテストケースが落ちましたが、徐々に対応を行なっていき落ちるテストケースは22件にまで減りました。こういった活動を継続することでRubyやRailsのコミュニティの進化に追随できるので引き続き頑張っていこうと思います。 タイミーでは一緒にサービスを成長させていく方を募集しています。もし少しでも興味を持っていただけたら、カジュアル面談受け付けておりますので是非お話ししましょう! product-recruit.timee.co.jp *1 : https://github.com/rails/rails/pull/48643 *2 : https://github.com/rails/rails/commit/8241178723d02123734a1efd01c12b9fda2f4fea *3 : https://gist.github.com/pokohide/b310ea180e7de0467360c96debbb8363 *4 : https://git-scm.com/docs/git-bisect
アバター
こんにちは、タイミーのデータ統括部データサイエンス(以下DS)グループ所属の小関です。 今回はDSグループがMLパイプライン構築時に活用しているVertex AI Pipelinesを効率的に開発するための取り組みを紹介したいと思います! Vertex AI Pipelinesとは Vertex AI Pipelines とは、Google Cloudが提供しているMLパイプラインをサーバーレスに構築・実行できるサービスです。 Vertex AI Pipelinesを活用することで、下記のようなデータをBigQeuryから取得し、特徴量の作成・データセットの分割後、モデルを学習するようなML パイプラインが比較的容易に構築できます。 Vertex AI Pipelinesで構築したMLパイプラインのサンプル Vertex AI Pipelinesの活用事例と挙げられた改善点 タイミーのDSグループでは、下記のようなML パイプラインをVetex AI Pipelinesで開発・運用しています。 ワーカーに対して、おすすめの募集を出力するパイプライン クライアントの離脱を予測するパイプライン 各種KPIを予測するパイプライン ML パイプラインを構築していく上で、以下のような改善点が挙げられていました。 パイプラインのリポジトリのディレクトリ構成や、CI/CDを共通化したい Google Cloud上での処理を共通化し、より使いやすい形で処理を呼び出せるようにしたい 各パイプラインに必ず入れ込む必要があるKubeflow Pipelines *1 の記述を共通化したい Vertex AI Pipelinesを効率的に開発するための取り組み 挙げられた課題に対して、DSグループでは以下の3つの取り組みを行なっています。 1. Vertex AI Pipelines開発用のテンプレートリポジトリの構築 cookiecutter を使用して、パイプライン開発に関わるディレクトリや、CI/CDに用いるyaml, shell scriptを生成してくれるテンプレートを作成しました。パイプラインの開発開始時にこのテンプレートを利用しています。 下記のようにcookiecutterコマンドでプロジェクトを生成し、プロジェクト名・プロジェクトの説明・Pythonのバージョン・作成者を入力することで、開発用のテンプレートが生成されます。 $ cookiecutter [開発用のテンプレートリポジトリのパス] > project_name [project_name]: > project_description []: > python_version [3.10.1]: > author [timee-dev]: # Vertex AI Pipelines開発用のテンプレート . ├── .github │   ├── PULL_REQUEST_TEMPLATE.md │   └── workflows │   ├── CI/CDのyamlファイル ├── .gitignore ├── Makefile ├── README.md ├── pyproject.toml ├── src │   └── pipeline_template │   ├── components │   │   └── component │   │   ├── パイプラインを構成するコンポーネントのソースコードとDockerfile │   ├── pipelines │   │   ├── パイプラインをコンパイル、実行するためのソースコード │   ├── pyproject.toml └── tests ├── テストコード 2. Google Cloudの処理を集約した社内ライブラリの構築 Google CloudのPythonライブラリ をラップして、BigQueryでのクエリ実行・クエリ結果のDataFrame化・テーブルの書き込みや、Cloud StorageにおけるファイルのI/O処理などを行える社内ライブラリを構築しています。こちらの社内ライブラリは、DSグループ全体で保守・運用を行なっており、バージョン管理とデプロイの自動化をした上で、Artifact Registryにプライベートパッケージとして置いて利用しています *2 。この社内ライブラリに関しては、Vertex AI Pipelinesに限らずVertex AI WorkbenchやCloud Runなど、他のGoogle Cloudのサービスでの実装でも活用されています。 簡単な利用例として、SQLファイルのクエリを実行して、 pd.DataFrame として取得する処理を紹介します。 # 社内ライブラリからBigQuery関連の処理をまとめているクラスをimport from [社内ライブラリ名].bigquery import BigQueryClient # project_idにGoogle Cloudのプロジェクト名、sql_dirにSQL fileを格納しているディレクトリ名を指定 bq_client = BigQueryClient(project_id=PROJECT_ID, sql_dir=SQL_DIR) # test.sql内でJinjaテンプレートで定義されているパラメーターをquery_paramsで受け取り、クエリの実行結果をpd.DataFrameとして受け取る test_df = bq_client.read_gbq_by_file( file_name= 'test.sql' , query_params= dict (loading_start_date=LOADING_START_DATE, loading_end_date=LOADING_END_DATE), ) -- SQL_DIR/test.sql DECLARE LOADING_START_DATE DEFAULT ' {{ loading_start_date }} ' ; DECLARE LOADING_END_DATE DEFAULT ' {{ loading_end_date }} ' ; SELECT * FROM `project_id.dataset_id.table_id` WHERE event_data BETWEEN LOADING_START_DATE AND LOADING_END_DATE 3. Kubeflow Pipelinesにおいて共通化できる処理を集約した社内ライブラリの構築 コンポーネント間のアーティファクトの受け渡し・Cloud StargeへのI/O処理や、yamlで定義されたコンポーネントの情報を取得してくる処理 *3 などを行える社内ライブラリを構築しています。こちらもDSグループ全体で保守・運用を行なっており、バージョン管理とデプロイの自動化をした上で、Artifact Registryにプライベートパッケージとして置いて利用しています。 利用例として、学習データを受け取り、それを特徴量とターゲットに分割するコンポーネントにおけるアーティファクトの受け渡し・Cloud StargeへのI/O処理の実装を紹介します。 # pipeline_name/components/component_name/src/main.py from dataclasses import dataclass import pandas as pd from [社内ライブラリ名].artifacts import Artifacts from [社内ライブラリ名].io import df_to_pickle @ dataclass class ComponentArguments : train_dataset_path: str @ dataclass class OutputDestinations : x_train_path: str y_train_path: str def main (args: ComponentArguments) -> pd.DataFrame: train_dataset = pd.read_pickle(args.train_dataset_path) x_cols = [ 'x1' , 'x2' ] y_col = [ 'y' ] X_train, y_train = train_dataset[x_cols], train_dataset[y_col] return X_train, y_train if __name__ == '__main__' : # アーティファクトのパスを取得 artifacts = Artifacts.from_args(ComponentArguments, OutputDestinations) # インプットとなるアーティファクトのパスを受け取り、main関数を実行 X_train, y_train = main(artifacts.component_arguments) # パイプラインのアーティファクトを管理するCloud Storageのバケットへpickle形式でX_train, y_trainを書き込む df_to_pickle(artifacts.output_destinations.x_train_path, X_train) df_to_pickle(artifacts.output_destinations.y_train_path, y_train) 取り組みから感じたメリット 上で挙げた取り組みを通じて、グループ全体で感じている主なメリットを紹介していきます。 開発のスピードが上がる。特にパイプラインのテンプレートが用意されている事で、開発の初動が大幅に速くなりました。 テンプレートやライブラリを通して、ファイル構成や処理に共通知があるので、メンバー間でのレビューがしやすくなっている。 個別に開発した処理を社内ライブラリに追加していくことで、グループ全体の資産として蓄積している。 属人化されているコードが減っていくので、新規メンバーのキャッチアップがし易くなる。 今回紹介した取り組み以外にも、MLパイプラインのソースコードのモノリポ化などDSグループでは常に社内のMLOps基盤を強固にしていく活動を続けています! We’re Hiring! タイミーのデータ統括部では、ともに働くメンバーを募集しています!! product-recruit.timee.co.jp *1 : Kubeflow Pipelines SDK で実装したパイプラインをVertex AI Pipelinesで動作させています *2 : Artifact RegistryのプライベートパッケージをPoetryで扱う方法は こちら が参考になります *3 : Kubeflow Pipelinesにおけるパイプラインの定義ファイルで使用します
アバター
DREグループの 石井 です。 先日といってももうしばらく前ですが、Techmeeというイベントで生産性についてトイルの計測をしてそれを一定に抑える取り組みをしているという話をさせて頂きました 。 https://timeedev.connpass.com/event/275750/ www.youtube.com これはDREグループ内の生産性を維持する=一定以上のアウトプットが出せる状態を作り出すための取り組みでした。 しかし、生産性といえば、古くはコード量で計測されていたような(最も、コード量は良い指標ではありませんが)ものや今で言えばfourkeysといったようなもので、どれだけアウトプットに繋がっているかが大切になります。 私の所属するDREグループでは、主に色々なデータソースからデータを抽出し、便利に分析しやすい状態にすることに責任を持つと同時に、データ基盤の運用も担当しています。 そのため、私達のグループはもとより、他の分析者の生産性を上げることに責任を持っていかないといけません。 今回はDREグループで始まったfourkeysによる生産性の計測と今後の展望について書かせていただこうと思います。 前提としてfourkeys 今回の計測に当たってはfourkeysをベースとして考えることにしました。 これについては書籍や色々な発表資料などを漁り、グループ内で勉強会などを通してある程度共通理解を得て我々の計りたいものに対してまずはこれでいってみよう、という合意が得られたためです。 我々が考える生産性については後述しますが、前提としてのfourkeysは以下の本や資料などを読んでいただけると理解が進むかと思います。 State of DevOps 2021 https://services.google.com/fh/files/misc/state-of-devops-2021.pdf (2022もありますが、フォーム入力が必要なためパブリックに公開されているこちらを貼っています)  エリート DevOps チームであることを Four Keys プロジェクトで確認する https://cloud.google.com/blog/ja/products/gcp/using-the-four-keys-to-measure-your-devops-performance 継続的デリバリーのソフトウェア工学 https://bookplus.nikkei.com/atcl/catalog/22/12/01/00531/ DREグループの考える生産性の定義 我々DREグループは社内向けのデータ基盤を提供するグループです。 つまり、生産性計測の対象が1つのチームに閉じるとは限らず、何なら複数グループの活動が全て終わって1つの機能提供が完了するというケースも多くあります。 例えば、新規データをつなぎ込みモデリングした上でダッシュボードを提供しようとするケースでは、DREグループだけでなく、モデリングを担当するBIグループが絡んできます。このとき、DREグループが担当するステージング処理までが完了したとしても価値が提供できていると言えるでしょうか?そうではなく、データの取り込み、ステージング処理、モデリング、ダッシュボード化のすべてが完了して価値を提供できていると言えると考えています。 そのため、開発はもちろんですが、その2グループの連携がスムーズに流れることも重要な要素であり、その点も改善の対象となってくるはずです。例えば、もしドキュメントが足りなくてBIグループがモデリングに入れないのであればそれをスムーズに提供するべきだし、依頼内容が曖昧でヒアリングに時間がかかりすぎるならそこも改善対象かもしれません。 つまり、スループットとしては絵にすると以下の全体で計測・改善を行う必要があると考えています。 また、単に速度だけを追求してしまうと障害を起こしまくる仕組みになってしまいかねません。 そのため、同時に安定性の指標として障害も計測する必要があるのですが、データ基盤の場合シンプルにデプロイによる失敗を計測すればよいかというとそういうことでもなく、どうしてもデータソース側の変更などにより「何もしてないのに壊れた」ということが発生することも多くあります。 そのあたりを考慮して実装を考えていく必要があります。 実装 実装は以下の様になりました。 基本的にDORAが公開してくれている実装を使い、Github上の営みをBigQuery上に収集しています。 デプロイ周りについては、 ほとんどのリポジトリはmain merge時にデプロイ自動化されているので、マージをデプロイの代替指標として採用する 障害は現在Notion上のポストモーテムDBで管理できているのでそこから引っ張ってくる ということにしています。 現時点では通常のfourkeysを取得できるようにしていますが、今後は上述した通りのリードタイムをNotion上にあるバックログなどと連携させて取得していく予定です。 考察と今後について 現時点ではほぼ素のfourkeysが取得できた状態です。 最初に計りたかったものからするとやや乖離がある状態ですが、それでもいくつかの示唆を得ることができました。 例えば、dbtのリポジトリでDREグループのリリース速度は十分早いのですが、データのモデリングを行うBIグループはかなりの時間がかかっておりなにか課題がありそうなことが特定できました。 これについてはヒアリングを行っており、実際にメンバー間でモデリングについての習熟度や前提条件の理解などの差があるために、レビューで大きな時間がかかっていることがわかりました。 また、当たり前ですがDREグループだとしても意図して大きいPRを出すとマージまでの時間が圧倒的に伸びていることがわかります。 どちらも担当ベースでは理解のあることと思いますが、具体的な数値として見えてくると議論の俎上にも上がりやすく、解決に向けて動きやすくなるのでシンプルなfour keysでも意味のある可視化なのではないかと思います。 ただ、やはりこれでは最初に書いたような課題は見えてこない部分はあるので、少しずつアップデートしていき、エンドツーエンドのfourkeysを測れるようにしてより生産性の高いデータ基盤を実現する礎にしていければと思っており、そのあたりはまたブログ等で発表できればと思っています。 おわりに 我々DREグループはデータ基盤を提供する裏方の部門ではありますが、データを使った業務の生産性を最大化するために業務に取り組んでいます。 こういった話に興味がある、少しでも気になった方はお気軽に カジュアル面談 に申し込みいただけると嬉しいです。
アバター