TECH PLAY

dely株式会社

dely株式会社 の技術ブログ

236

こんにちは、クラシルリワードのiOSエンジニア uetyo です! 先日行われた potatotips という、日々の開発の Tips を共有するイベントにて未熟ながら登壇したので、今回は登壇に至った背景なども交えながらレポートします! ※ 前回は気合いだけで頑張った話を多く載せすぎてしまったので、今回はスマートにいきます tech.dely.jp 登壇の経緯 ある日、上司と1on1をしているとこのような話になりました 私:今年の目標はスキルの向上もそうですが、社外のイベントとかコミュニティとの繋がりを増やしていきたいです 上司:うえちょさんって社外のiOSのコミュニティとかと関わりありますか? 私:iOSに関しては全く無いですね…コロナのタイミングで上京したのでイベントが全然なくて、、 上司:ならこのイベントとかどうですか?potatotipsというイベントで、LTで登壇とかもできるみたい。とりあえず今日あるみたいだから見てみたらどうですか? 私:お、気になります!見ます! 数週間後の1on1にて… 上司:うえちょさん、potatotipsの次の開催が決まったみたいだけど、登壇とかしてみるのはどうですか? 私:はい、やります 🔥🔥 (最近HealthKitとCoreMotionの開発していて躓いていたのでその話をしようかな) という流れであっさり決まってしまったイベント登壇でした。 私自身iOSDCなど大きめのカンファレンスやイベントは見るようにしていたのですが、小規模な(とはいえ参加者が100人近くいる、東京凄い)イベントは全然知らなかったこともあり、ワクワクしながらイベントを見ました。 発表内容 - HealthKit と CoreMotion の権限に四苦八苦した話 クラシルリワードのiOSアプリでは歩数に応じて歩数ゲージが蓄積され、蓄積された歩数ゲージをチケットに交換することができる機能があります。この機能を実現するために利用しているのが HealthKit と CoreMotion です。 この HealthKit と CoreMotion の取得したデータを利用するには、OS側の制限によりユーザーからの許可が必要です。この権限の許可率は、歩数機能をどのくらいのユーザーが利用してくれるかを示す重要な指標であるため、できる限り正確なデータの取得が求められていました。 私がクラシルからクラシルリワードへ移動したタイミングは、この許諾率を向上させるための施策が実施されていたため、これまで経験のなかった HealthKit と CoreMotion に初めて取り組むことになりました。 まずは大枠を掴もうということで、それぞれの権限状態とミニマムな取得方法ついてまとめました。 CoreMotionの権限状態と取得方法 CoreMotion は iOS4.0 から利用できる、かなり古くからあるフレームワークです。加速度やジャイロスコープなどアプリが入っている端末のセンサーが取得したデータを利用する際や、ユーザの活動タイプ(歩行、自転車など)を特定したりできます。データを取得するにはユーザの許可が必要です。 CoreMotionでは以下の4つの状態が存在します。これらは CMAuthorizationStatus として定義されています。 CMAuthorizationStatusの4つの許可状態 notDetermined:ユーザーがアプリにモーションデータへのアクセスを許可・否定どちらもしていない状態。 authorized:ユーザーがアプリにモーションデータへのアクセスを許可している状態。 denied:ユーザーがアプリにモーションデータへのアクセスを拒否している状態。 restricted:ペアレンタルコントロールなど、何らかの制限によりアプリがモーションデータへのアクセスを要求できない状態。 CoreMotion の許可状態は比較的簡単に、素直に取得することができます。 // CoreMotionの許可状態の取得方法 import CoreMotion let status = CMMotionActivityManager.authorizationStatus() switch status { case .notDetermined : print ( "🐶< ユーザは許可も拒否もしていないわん" ) case .authorized : print ( "🐶< ユーザはアクセス許可しているわん" ) case .restricted : print ( "🐶< アクセスが制限されているわん" ) case .denied : print ( "🐶< ユーザによってアクセスが拒否されたわん" ) } HealthKit の権限状態と取得方法 HealthKit は iOS8.0 から利用できる、それなりに古いフレームワークです。ヘルスケアアプリに保存されるデータ(健康関連データ)を利用することができます。データを取得するにはユーザの許可が必要です。 HealthKit の大きな特徴として、iPhoneだけでなく、任意のデバイス(AppleWatch等)が取得したデータも一元管理しているので、アプリは特に意識することなく任意デバイスが収集したデータも利用できます。 HealthKit では以下の3つの状態が存在します。これらは HKAuthorizationStatus として定義されています。 HKAuthorizationStatus の3つの権限状態 notDetermined:ユーザがアプリに特定の健康データへのアクセスを許可・拒否どちらもしてない状態(アクセス依頼を受けていない状態) sharingAuthorized:アクセスが許可されている sharingDenied:アクセスが拒否されている HealthKit の許可状態も同様に取得してみるため以下のようにコードを書きましたが、notDetermined以外の状態が正しく取得できません。 // HealthKit の許可状態の取得方法 import HealthKit let store = HKHealthStore() let status = store.authorizationStatus( for : HKQuantityType (.stepCount)) switch status { case .notDetermined :     print( "🐶< ユーザは許可も拒否もしてないわん" ) case .sharingDenied :     print( "🐶< ???" ) case .sharingAuthorized :     print( "🐶< ???" ) } Appleは HealthKit の許可状態に関してもプライバシーとして教えてくれないとのことです。 To help maintain the privacy of sensitive health data, HealthKit does not tell you when the user denies your app permission to query data. developer.apple.com しかしこれだと施策をするうえで非常に困ってしまうので解決方法を模索することにしました。 HealthKit の許可状態を擬似的に取得する 色々と試した結果、実際に数値を取得してみて、取得できる→許可されている、取得できない→許可されていない、と判断することで権限状態を擬似的に取得できることが判明しました。 notDetermined の状態は取得できるので、そもそも権限付与依頼のモーダルを出していない場合は出すようにします。notDetermined以外の場合は、実際に数値を取得します。この際、 HKError が発生した場合は拒否されている可能性が高く、それ以外のエラータイプ場合は許可されていると判断できます。 // HealthKit の許可状態を擬似的に取得する import HealthKit let store = HKHealthStore() let status = store.authorizationStatus( for : HKQuantityType (.stepCount)) if status == .notDetermined {     print( "🐶< まずはユーザに許可をもとめるわん" ) } else {     do {         _ = try await 今日の歩数を取得する関数() // 実際に取得しようとする         print( "🐶< 今日の歩数が取得できたのでアクセスは許可されているわん" )     } catch let error as HKError {         switch error.code {         case .errorAuthorizationDenied, .errorAuthorizationDenied, .errorRequiredAuthorizationDenied :             print( "🐶< アクセスは拒否されているわん" )         default :             print( "🐶< データは取得できないけど許可されているわん" )         }     } catch {         print( "🐶< データは取得できないけど許可されているわん" )     } } この際、歩数が0歩の場合もエラーになるので、細かくエラーハンドリングする必要があります。 まとめ CoreMotionとHealthKitの許可状態の取得まとめ CoreMotion は何ら問題なく権限状態が取得できますが、HealthKitでは少しひねって取得する必要がありました。 参考 HealthKitの開発時にはこちらの記事が参考になりました: https://qiita.com/dotrikun/items/f34420cb7f3c0fb2ac09
アバター
  【はじめに】 【開発体制とやっていることについて】 【6つのマイルール】 『① 依頼の解像度をその日に上げる』 『② キリの悪いところで終わりにする』 『③ 20分で終わるなら真っ先に対応』 『④ 行き詰まったらとりあえず生成』 『⑤ 自分のことをオープンに』 『⑥ 穏やかに、だけど自分を持つ』 【最後に】 【はじめに】 はじまして、クラシルリワードの23卒プロダクトデザイナーの haruto です🐒 この記事では 「仮説検証を早く回して、スピード感のある開発組織に」 という開発方針を掲げる、爆速の開発スピードを誇るクラシルリワードのデザイナーとして豆腐メンタルの自分がマイペースにデザインができるように心がけている6つのルールをご紹介しようと思います📮 クラシルリワードの開発方針 また、今回の記事では【デザインを作る上で】ではなく 【デザイナーとして働く上で】 意識していることについてまとめてみました、お読みいただいた方に何か一つでも発見があると嬉しいです☘️ 【開発体制とやっていることについて】 早速、前提として自分が所属する開発チームについてご紹介しようと思います🥕 メインでリテンション改善スクラムに所属しスクラムイベント等に参加しており、その他の機能開発チームやクリエイティブチームで必要があればデザインを作成するという複数チームに参加するという働き方でデザインをしています。 クラシルリワードの開発体制 ▼開発体制の詳細については以下の記事をご覧ください。 tech.dely.jp 担当しているデザインの業務としては、仮説検証やキャンペーンのために必要になるUIやグラフィック、LP作成などを作成しています🎨 こんな感じのものを作っています 【6つのマイルール】 働き方に関する前提をご理解いただけたと思うので、さっそく時間や脳内に余白を生み出しマイペースにデザインを進めるために意識していることをご紹介していこうと思います🏃‍♀️💨 6つのマイルール 『① 依頼の解像度をその日に上げる』 私たちのスクラムでは1週間分のプランニングを行いますが、冒頭でご紹介したように他のチームからの急な依頼が日常的にあるため、正確な見積もりが難しく、自ら優先順位や期限を調整する必要があります。このような働き方が始まった当初、各チームに必要なデザインの種類、期限、工数が不明で、頭が真っ白になり、「なにすればいいの…🥺」という状態に陥りかけていました。 ぴえん状態 その頃から、依頼をいただいた際に以下の点を意識的に行うように心がけています。(全てのタスクでしているわけではありませんが、抽象度が高いものや対応範囲が広いものについては必ずやるようにしています。) 「依頼をいただいた当日にやること」・期日を確認する・ちょっとだけ作ってみる・軽く作った上で気になる仕様について質問   依頼当日にやること 当日に素早く作成して質問することで以下の3つのメリットを感じています💭 事前に認識の齟齬を防ぐことができる 依頼の全容を把握し、工数を見積もれることで脳内がクリアになる 新鮮なタイミングで質問して文字として残すことで、作業時にスムーズに取り掛かることができる 自分に対するメリット以外にも、早い段階でデザインのイメージを相手にも持ってもらうことで、 事前情報が厚くなりスムーズに施策が回せる という点で大きなメリットがあるかなと思います。 元気100% やるべきことをクリアにすることで、最近は「バッチこい!!🔥🔥」状態でご機嫌にデザインをできています💪 『② キリの悪いところで終わりにする』 冒頭で触れたように、クラシルリワードは毎日のリリースを目指すほどの爆速開発を行っており、日々様々なチームから多くの依頼を受けています。ほとんどの依頼は1〜2日以内に完成させる必要があるため、必然的に割ける時間限られてしまいます。このような状況の中でもクオリティを維持するため、以下の2点を特に意識しています。 ① 新しいデザインは夜に始める  ② 最終仕上げを2段階で行う(急ぎでない場合) ① 新しいデザインは夜に始める ①の内容と似ていますが、翌日に新しいデザインを始められそうな場合は、 終業時間の1時間〜30分前にラフデザイン を作るようにしています。ラフを作りスコープをクリアにした状態で終業することで、 フリーの時間をアイデアを練るや軽いリサーチに充てることができ、翌朝からすぐにスムーズにデザイン作業ができ るため意識的にするようにしています。 ② 最終仕上げを2段階で行う(急ぎでない場合) デザインの最終仕上げを行う際、その瞬間は良さそうと感じても、木を見て森をみず状態に陥ることが見失うことが度々ありました。そのため、急ぎでないものについては フラットな視点で評価するために最終チェックは翌日の朝に改めてセルフフィードバックを行う ようにしています。 『③ 20分で終わるなら真っ先に対応』 自分は記憶力がザルで多くのタスクを抱えると整理が追いつかなくなり対応漏れが発生しエラー状態に陥る傾向があります。 エラー中 そのため、依頼が入った際には、まず簡単に見積もり、20分以内で完了できるものを小タスクとし、小タスクを最優先で対応するようにしています🚗💨 20分以内なら対応ルールを実施してから、脳に余白が生まれることでマイペースに大きなデザインタスクに集中できるようになっただけでなく、それぞれの施策で デザインに待ちの時間が短くなりスムーズに施策や検証ができるようになった 感覚があるのでおすすめのマイルールです✨ 『④ 行き詰まったらとりあえず生成』 クラシルリワードには、「クラシうさぎ」などかわいいキャラクターがいます。キャラクターたちに色々なポーズや着せ替えをする中で、特定のテーマに沿ってデザインすることがよくあります。しかし、テーマは定まっていても、なかなかピッタリとくるアイデアが集まらないことや、特定のポーズをどのように表現すれば良いかが掴みにくい時があります。 わからない状態 そんな時に、便利なダリさん(DALL-E3)に「2頭身で2足歩行のアニメ風のウサギのキャラクターを生成してください」といった適当なプロンプトから徐々にいい感じにイメージに近いイラストを生成してもらっています。他にもテイストの近いイラストを言語化してもらった上でパターンを出してもらうなどゴニョゴニョして、解像度を上げるようにしています💭 冬っぽいキャンペーンうさぎを作った時 まだ全然使いこなせていないですが、息詰まって頭が真っ白になりそうな時にChatGPTや生成AIを使って壁打ちをすることで、デザイン作りきれるかな不安期から脱することができるのでおすすめです💡 社内でオススメされていたChatGPTの本でデザイナーも使えるTipsがちらほらあったのでシェアハピです📕 bookclub.kodansha.co.jp 『⑤ 自分のことをオープンに』 毎週末、週報を作成し新卒チャンネルや日報チャンネルにシェアするようにしています。(毎週欠かさず書いて今週で#46になりました)始めた当初は、自分がやっていることを公開するという縛りをつけることで気を引き締めるために書き始めました。 ただ、続けてみるとマイペースにご機嫌でデザインするという点で以下の2つが良かったなと感じています 💭 ① 自分がやったことをまとめることで成長を振り返れる ② ステイクホルダーに自分のことを知ってもらえる   ① 自分がやったことをまとめることで成長を振り返れる 週報として毎週のやったことを記録することで、抜け漏れなく自分が何をやっていたのか、 何ができるようになったか振り返ることができ、ニンマリご機嫌になれます。 また、評価面談等でちゃんと自分がやっている・やってきたことを伝えるための資料として活用でき、漏れなく評価をしてもらうことができるようになると思うのでおすすめです 💭 今年のやったことをまとめたシート   ② ステイクホルダーに自分のことを知ってもらえる おそらく複数チームに所属するデザイナーは、なんかいろいろやってる人だけどイマイチ何ができるかわからないなぁと思われがちだと思います。ただ、 週報を通して自分の状態をオープンにすることで、自分のことを知ってもらえるし、自分は知ってもらえる安心感がある ので心に余裕ができ、次週もマイペースにデザインに臨むことができています。 毎週欠かさず書くために↓のようなことを意識して書いています👀もし、よかったら皆さんもチャレンジしてみてください🔥   [ 週報で意識していること ] 振り返り < やっていることをみてもらう 時間がないなら画像をペタペタするだけでもいいから継続する 1分もしないで見切れるくらいのボリュームにする フリーコーナーで仕事以外のことをシェアする weekly haruto🐒   最近は、「あいつは〇〇ができそうだから〇〇にアサインしてみよう」と週報きっかけで新たなバッターボックスに立てる機会を獲得するという密かな野望を抱いています😎 『⑥ 穏やかに、だけど自分を持つ』 チームとして、デザイン・プロダクトを作るためにはフィードバックは必要不可欠なものであると思います。好きな言葉に 「Feedback is Gift 🎁」 というものがあるのですが、自分は良いフィードバックをもらうために以下の2点を意識しています。 ① フィードバックや意見を伝えてもいい雰囲気を醸し出す ② 自分を持った質問をする   ① フィードバックや意見を伝えてもいい雰囲気を醸し出す まずそもそもフィードバックをもらうためにはあの人には伝えても大丈夫と思ってもらうために感謝の気持ちを常に伝えるよう心がけたり、スタンプを使って積極的に反応し、会話しやすいようにということを心がけています。 ② 自分を持った質問をする ただ単にフィードバックを求める雰囲気を作るだけでは、自分が求める方向性の良いフィードバックを受け取ることは難しいと思います。そこで 「何をみて欲しいのか」や「何で迷っていて、自分は何がいいと思うのか」 のように具体的に投げかけるようにし、相手にコメントする箇所のスコープを示すようにしています。 上記の2点を心がけることで、お互いに嫌な気持ちにならない良いフィードバックをいただくことができ、良いデザイン・プロダクトに向けた建設的なやりとりができるのでおすすめです💡 今回は【デザイナーとして働く上で意識していること】がテーマなので実際にやっていることはふわっとしか書かないですが、「みんなではじめるデザイン批評」という本をご紹介します📕(自分はまだnoteにあるサマリに目を通したレベルですが学びがたくさんあったのでおすすめです!💡) www.kinokuniya.co.jp 【最後に】 ここまでお読みいただきありがとうございました! まだまだまだまだ未熟ですが、なんとかデザイナー1年目をマイペース乗り切ることができそうです 💭2年目も引き続きマイルールを大切に、更新しながらメンバーといいプロダクトを作り、ユーザーの皆様により良い価値提供ができるデザイナーになれるように精進して参りたいと思います💪 クラシルリワードでは、一緒にアプリを作ってくださるデザイナーの方を募集しています! ちょっとでも面白そうだな思っていただけた方、ぜひご応募お待ちしています!✨ www.wantedly.com      
アバター
こんにちは。Androidエンジニアのkenzoです。 今回は普段チームでプロダクトを開発を行う際に、プロダクト・チーム・そして自分自身の成長のために心がけていること、またそうありたいと思っていることを少しだけご紹介します。 これらは内容としては当たり前のことかもしれませんが、改めて意識し実践することで、少しずつそれぞれの成長に繋げていくことができると考えています。 失敗から学ぶ 開発を進める中で、大小様々な事故やミスが発生することがあります。 リリース後のアプリがクラッシュするような大きな事故や、実装時に見つけてハッとして直すような小さなミスなど、失敗の種類は多岐にわたります。 できれば全て事前に防ぎたいところですが、起きてしまったものは仕方ないので、それらについては事後にきちんと振り返り、そこからきちんと学んで繰り返さないようにします。 ・失敗は成長の種 大抵の事故は、複数の要因が組み合わさることで発生します。たとえば、変更に弱い実装だった、コードレビューで見逃された、テストケースにそのパターンが含まれていなかった、仕様を勘違いしていた、特定の条件でしか起きないものだった、他の施策との兼ね合いで本番環境でのテストが難しいタイミングだった、などなど、様々な要因が影響します。どれか1つでも防げていれば回避できたかもしれませんが、実際にはどれもすり抜けた結果、事故は起きてしまいます。 適切な対応をした後にきちんとチームで振り返れば、いくつもの改善点を発見できると思います。これらに対処することがプロダクト・チーム・自身の成長に繋がるかもしれません。 それぞれをタスクに起こして一つずつ解決していけば(難しいものもあるでしょうが)、全く同じ要因による再発は防げます。 小さいかもしれませんが、成長ができたと言えるのではないでしょうか。 チームで振り返りをしたときの議事録 ・失敗について話しやすい環境 チームでの失敗には様々な物がありますが、元を辿れば個人の失敗に起因することがあります。それをチームで振り返る際には、誰かのミスを指摘することになりがちですが、する側もされる側も嫌だと思います。 そのため、普段から全員が失敗は共有してみんなで振り返るのが当たり前という認識を持てるような環境をつくることや、失敗を取り上げる際の方法にも配慮することが必要です。 ・個人の場合 個人の場合も、事故には至らなくとも、ちょっとしたミスや「あの時こうしていれば」と後悔することはよくあると思います。 こういう日々の伸びしろを逃すことなく、それぞれに積極的に対応することで少しずつ成長していきたいですね。 未来の読み手を意識したコード 今度はコードの話です。 長く続くプロダクトの仕様や技術が古くなるにつれて、コードの理解が難しくなっていきます。開発が速かったり、変化の激しい環境においてはなおさらで、数ヶ月後にはもはや別のプロダクトのようになっていることもあります。 現時点でさえ理解しにくいコードは将来さらに読みにくくなります。 過去のコードに苦しんだ経験のある開発者も少なくないと思います。自分の書くコードを読む未来の開発者にそんな思いをさせないためにも、未来の読み手を意識した親切なコードとその周辺環境を作っていきたいところです。 背景を知らずドメイン知識のない開発者が一人でそのリポジトリを見たとして、どれくらい理解できるのだろうと考えたりしています。 ・ベストプラクティスに従う 基本的には公式のドキュメントで紹介されている書き方や一般的に良いとされている書き方に従うことを推奨したいです。更新頻度の高い新しいフレームワークを使ってる部分でなければAIに教えてもらうのも良さそうです。 世の多くの開発者に良いと認識されているものがベストプラクティスなので、そのベストプラクティスに則ってコードが書かれていれば、新たにそのコードを触る開発者に「こう書いてね。」と伝えるコストも低く抑えられます。 また、より良いものが新たに生まれた場合には、誰かが紹介してくれるマイグレーションのプロセスにそのまま乗ることもできるかもしれません。 独自のより良い書き方を生み出して使うのも良いですが、その場合にかかる諸々のコストも考慮し、覚悟の上で導入する必要がありそうです。 ・書き手は最強 ある処理の実装を書いたとします。書いた直後はその処理についての全てを把握しているので、そのコードの把握も非常に容易です。 そのため、もしそのコードが複雑で読みにくいものとなっていたとしても、それに気付くのは難しいでしょう。 簡単にはいかないかもしれないですが、他の人や未来の自分が見たらどう思うか、◯◯を見て☓☓だとわかるか、というように一旦意識的に客観的な視点から再度コードを読んでみてもいいかもしれません。 これはちょっと前に自分の書いたコードがクソコードに見える現象の一因にもなっている気がします。 ・なぜ必要?なぜここに?なぜこう書く? 自分の書いたコード全てについて、これらの質問に明確に答えられるようにしておきたいです。 回答が明確でないコードは、他の人が理解するのに時間がかかったり、誤解によるバグを生む可能性もあります。 逆に明確なコードは、他の人がその意図に沿って開発できたり、意図の共有による技術力の向上にまで貢献するかもしれません。 できることならこの質問を何段階か深堀りすると良さそうです。 ・手がかりを正しく残す プロダクトを開発していると、ドメインの仕様に依る箇所や複雑な仕様の処理など、コードからきちんと仕様を把握するのが難しかったり時間がかかるものがあります。 それらを正しく理解するためにコメントやドキュメント、仕様書等で手がかりを残すことになりますが、それを有用なまま維持しておくことが意外と難しいこともあります。 変更に追従できていないドキュメントや、作ったものが何らかの移動やツールの変更のタイミングで失われるなど、環境によって様々ではありますが、時間の経過でアクセスが難しくなったり害悪になってしまうものもあります。 残す場合にはそれが将来必要な時が来るまで維持できる仕組みまで考えたいところです。 おわりに 今回ご紹介した内容は、多くの開発者が無意識に行っていることかもしれませんが、改めて意識的に実施することで効果を発揮する部分もあると思います。 私自身としても徹底できていないところもあるので、これらのポイントを意識し、より良いチーム開発を行っていけるよう精進していきたいと思います。
アバター
はじめに こんにちは!クラシルリワードでサーバーサイドエンジニア兼 PM をしている宇野です。 自分は去年の3月からクラシルリワードに JOIN して、おみくじや歩数、お得タブなど新機能の実装を担当してきました。 この記事では、学習サイクルを素早く回すために自分が意識していることを紹介します。 左: おみくじ機能 右: お得タブ dely にきて初めて記事を書くので軽く自己紹介をさせてください。こんな人です dely にきてそろそろ2年が経過します。 ポケモンとご飯が好き。土日は大体ポケモン対戦か美味しいお店を巡っています。 最近食べて美味しかったのは「だしいなり海木 日本橋店」です tabelog.com それでは本題にいきます!! 想定読者 クラシルリワードは約5人1チームの少人数で開発をしています。そのため、同じぐらいの人数で開発をしているエンジニアを想定しています。 詳しい開発体制については開発責任者の funzin の記事を見てもらえると嬉しいです。 tech.dely.jp なぜ素早く学習したいのか そもそもなぜ素早く学習サイクルを回したいのかというと、開発した機能をユーザーさんが使ってくれるかは分からないからです。どんなに仕様を綺麗にしてバグがない状態でリリースできても、ユーザーさんが使ってくれないとビジネス価値にはつながりません。特に自分が担当している新機能開発ではユーザーさんが動くか分からないので、リッチな仕様にするのではなく価値検証ができるミニマムの仕様でリリースすることを心がけています。そして、分析・学習をして次に活かすというサイクルが大事になってきます。大きくなる前にリスクを潰せて前に進むことができ、小さいチームでも大きな仕事ができます。 目標を可視化して、チームで進捗の認識を合わせる 自分たちはスクラム開発を採用しており、毎週スプリントプランニングを開催してスプリントゴールを設定しています。ただ、目標を設定するだけでは日々の仕事に追われて意識することは難しく、週の終わりに思い出すという事はよくあると思います。 この解決のために開発するときによく利用する場所にスプリントゴールを書くことにしています。Slack チャンネルのトピックやデイリースクラムで毎日見る Notion に貼り自然に目につく仕組みにしています。さらに、スプリント中盤に目標の進捗を確認することで「順調に進んでいるか」「順調ではない場合、ボトルネックは何か」を議論できるようにしています。 こうすることで、チーム全体で目標の認識が揃い最短で目標達成するための行動が考えられるので、開発仕様が洗練されてリリースまでの期間が短くなります。 左: Slack 右: Notion 専門領域を越境する (※) ここでいう越境は、自分の専門領域外の仕事をするだけではなく関心を持つ・学ぶことも含めます。 これはリリースまでの期間を短くすることに直接は関係ないのですが、専門領域だけではなく他の領域に越境することで学習サイクルが速くなると考えています。エンジニアなら「CS でどういうお問い合わせが来ているのか」「マーケティングチームはどういう施策を考えているのか」などを知ることを指します。これをすることで、相手の話している背景や目的を理解することができるので、「それを実現したいなら、この方法の方が最速で出せる」という話ができたりします。また、優先して改善すべき機能が分かったり、将来的にやりたいことの解像度が上がり設計にも役立ちます。 当時、自分は広告周りのことが全く分からないまま PM にアサインされたので事業責任者にお願いして広告勉強会を開いてもらったりしました。これは人数が少ないからそうせざるを得ない部分もありますが、越境をすることで自分が関わっているサービスを開発以外の観点からも見ることができ開発がより自分ごと化します。 注意点としては、インプットする情報を少なくして専門領域に集中して成果を出すことが求められることもあります。越境する時・しない時のタイミングは意識して考えるのが良いと思います。 初期は手動運用でスタートする エンジニアの三大美徳に「怠惰」があり、これは「楽をするために努力を惜しまない」と説明されることが多いです。例えば、繰り返し作業をするのが面倒なので、自動化して効率を上げるために開発をするなどです。これとは対立するのですが初期の学習サイクルを1秒でも早く回すために運用効率化の改善をしないのはありだと考えています。 「簡単に実装できるし手動運用めんどくさいから管理画面を作ってからリリースする」と考えたくなりますが、「設計 → 実装 → テスト」というフローを考えると最短でも 1 - 2日はかかると思います。せっかく管理画面を作っても機能を使って貰えなかったら意味がありません。 ユーザーさんに機能を触ってもらい価値検証ができるまでは手動運用で頑張り、検証ができてから自動化をします。 例えば、以下のようなパターンが考えられます。 管理画面は作らず、CSV とスクリプトでデータベースの更新をする ハードコードをして、変更する場合は都度デプロイする エンドポイントを用意せず、Firebase Remote Config から情報を取得する 「たかが1日、されど1日」ということで、少しでも開発工数を削減してリリースすることを心がけています。もちろん、運用の効率化も大事なことなので機能開発と運用改善はメリハリが大事になります。 最後に 今回は「学習サイクルを素早く回すために意識していること」を紹介してきました。 1つ1つはすごく簡単なことですが、凡事徹底してこれからもクラシルリワードを改善していきます。 ここまで読んでいただき、ありがとうございました!
アバター
プロダクトの開発方針 開発体制 1. スモールチーム開発 2. 毎日リリース可能な体制 3. CRMツールを活用した施策検証 4. CopilotやChatGPTなどの生成系AIを活用 5. M3 Maxの導入 まとめ こんにちは!クラシルリワードで開発責任者をしている funzin です。 この記事ではクラシルリワードの開発体制についてお話ししていきます。 カジュアル面談や面接でどのような開発体制かを聞かれることが増えてきたため、こちらに記事としてまとめていきます。 プロダクトの開発方針 クラシルリワードのプロダクトの開発方針として、「 仮説検証を早く回して、スピード感のある開発組織に 」を掲げています。 プロダクト開発をする上で、下記のような状況に一度は遭遇したことがある方も多いかと思います。 e.g. 数ヶ月かけて開発をした施策が、結局ユーザーに使われなかった DALL-E 3で生成した画像 クラシルリワードはまだリリースして1年半ほどのサービスで成長途中なこともあり、まだまだ機能が足りていない箇所がたくさんあります。 そのため施策を早くリリース、検証、改善していく流れがとても重要となります。 次の章でどのような開発体制でスピード感のある開発組織を実現しているかを紹介していきます。 開発体制 1. スモールチーム開発 2024/1現在、下記のような開発体制で行っています。 2024/01現在のチーム体制 大きく機能開発チームと横断チームに分かれています。 機能開発チームでは職能別で5名ほどのチームを作り、機能開発を行っています。 チームトポロジーでいう、ストリームアラインドチームに近しいです。 原則機能開発チーム内で施策や開発プロセスを全て完結するようにしています。 機能開発チーム このようなチーム体制にしている意図としては下記です。 目標KPIに対して、機能開発チームで責任を持つ 少数精鋭でやり切る チーム内に人数が多くてもタスクのお見合いやコミュニケーションパスの複雑性が発生する 各ポジション1名を基本として、足りない領域はチームでカバーする どのように機能開発チームが運用されているかは、こちらの記事をご覧ください。 クラシルリワードのプロダクトマネージャーの1週間はどんな感じ? 2. 毎日リリース可能な体制 施策を早く検証する上で、リリース頻度はとても重要になります。 そのため各領域で毎日リリースができる体制を整えています。モバイルとサーバーサイドでのリリース頻度は下記のようになっています。 モバイル(iOS, Android) 各機能開発チームがリリースしたい施策がある場合、いつでも審査提出が可能な状態 週によっては 5回リリース している日もある(平均週2ペース) iOSのリリースログ サーバーサイド GitHub ActionsとAWS CodeBuildを活用し、mainブランチにマージしたタイミングでリリースフローが実行される 高頻度なリリースを実現する上で、下記のようなことに取り組んでいます。 PRの粒度を小さくして、早くマージする(レビューの負担を減らす) チーム全体が共通の認識を持つことでPRサイズを小さく保ち、レビューが早くなる ヘルスチェックとして OffersMGR を利用して、FourKeysのチェックも行っています とあるチームのFourKeys 職能のLDRが隔週でFourKeysのレポートをしています FeatureFlagを利用したトランクベース開発を活用 開発環境では常に新機能が確認できる状態(mainブランチに含まれるため) 開発中の施策をTestFlight・Firebase App Distributionで配布することで動作確認を行い、すぐにリリースできるようにしている 細かくリリースし続けることでQAスコープの対象も小さくする そのぶんQAの頻度は上がってきますが、全体機能のデグレを検知できるようにMagicPodを活用 クラシルリワードにおける自動テストツール MagicPodの導入事例 3. CRMツールを活用した施策検証 施策をする上で第一に機能開発をすることを考えますが、新しい機能を開発する前にCRMツール( KARTE , Repro )を活用できるかを考えます。 e.g. RemoteConfig, プッシュ通知、ポップアップなど CRMツールを利用して開発工数を抑えて検証が行えるのであれば、それらを利用するがベストです。 これによって、実際に開発してみたけど使われなかったといった問題も防ぐことができます。 実際にどのようなユースケースがあるかを紹介します。 ユースケース: おすすめ運用枠の検証 運用枠などをRemoteConfigで表示(CTR/CVRの検証) 上記で効果が見込めるかつ、運用し続ける場合は、API化や管理画面の入稿できる対応を検討する 運用枠の効果が見込めない場合は、1の検証のみで終了し2の開発工数を抑えることができます。 施策をする上で自分たちで全てを実装する以外にCRMツールを活用することで開発工数を抑えた検証が行えます。 4. CopilotやChatGPTなどの生成系AIを活用 生成系AIを活用することで、開発の効率化を図っています。 エンジニアチームには GitHub Copilot を導入し、コーディングの開発の負担を減らしています。 また施策を分析する上で、クエリを書く機会が多いですがこちらも補完が効くため重宝しています。 (Copilotはコードだけでなく文章の補完もできるので、このブログの下書きにも利用しています。) 今までは社内の利用規則に基づいて個々人がChatGPTを使っていましたが、1月に ChatGPT Team が出たため、さっそく導入し活用できなかったビジネスデータをChatGPT上で利用し始めています。 下記のような社内専用のGPTsを作って、各チームで利用できる形にしていきたいと考えています。 e.g. リリース文言、施策案、クエリ補助 まだTeamプランは導入したてなこともあり、実際導入してみてどうだったかは別の機会に紹介できればと思います。 5. M3 Maxの導入 最後にPCのスペックについても触れさせてください。 今までエンジニアはM1 Maxを利用していましたが、検証端末でアプリのビルド時間の計測を行い、投資値効果があうと判断したためM3 Maxを2024/4から導入予定です。 検証機としてM3 Maxを2台購入し、iOSアプリでのクリーンビルド時間がM1 Maxに比べて半分( 2min -> 1min )になったため上記のような意思決定を行いました。 M3 Maxは下記2つのサイズで選択できるようにしています。 1. 14インチ - 16コアCPU、40コアGPU、16コアNeural Engine - 64GBユニファイドメモリ - 1TB SSDストレージ 2. 16インチ - 16コアCPU、40コアGPU、16コアNeural Engine - 64GBユニファイドメモリ - 1TB SSDストレージ 純粋にマシンスペックを上げることも、開発生産性において重要な意思決定の一つです。 まとめ クラシルリワードの開発体制について紹介してきました。 クラシルリワードでどのようにプロダクト開発を行っているかのイメージがもし伝われば幸いです。
アバター
はじめに こんにちは!クラシルリワードでプロダクトマネージャーをしているerinaです! 今回のブログでは、クラシルリワードチームでプロダクトマネージャーとしてどんな1週間を過ごしているのを書きたいと思います。 本題に入る前に、簡単に私の背景を紹介したいと思います。dely株式会社では、2022年2月に入社し、最初はTRILLアプリに所属して、2023年にクラシルリワードに異動し、もうすぐ3年目を迎えます。プロダクトマネージャー歴は前職も含めて約5年になります。 1週間はどんな感じ? いろんな人から「プロダクトマネージャーはどんなことしている?」「プロダクトマネージャーってコードを書くの?」よく聞かれますので、非技術系出身のプロダクトマネージャーはどのような一週間を過ごすかが具体的にイメージできるようになると思います! 私が所属しているユーザーリテンション改善スクラムは、部分的にアジャイル開発を導入しているので、基本スクラムイベント(1週間単位)と合わせて調整しています。大きく分けると以下の通りの1週間となります。 <毎日> KPIモニタリング 出勤後にまず前日のKPIを確認する 数値の変化があれば調査したり、デイリースクラムでメンバーに共有したりする デイリースクラム Jiraを使って、チームメンバーと進捗、ブロッカーや相談事項があるかを確認する 状況によって急遽対応しないといけないタスクがあれば優先順位を相談する 検証中の施策があれば、目標KPIを一緒に確認する <月曜日> スプリントバックログを整理 今のスプリントの達成度を確認する 次のスプリントのバックログと優先順位を整理する 火曜日プラニング会の資料を更新する → 検証中の施策のインサイトをまとめ、次のスプリント項目の共有など <火曜日> プラニング会 w/ PO & 他のスクラムPM 今進行中と予定のバックログの方向性のズレがないかをすり合わせする 他のステークホルダーに共有・相談する <水曜日> レトロスペクティブ KPIと今回のスプリントの達成度の振り返り KPTのフレームワークでチームメンバーと「Keep(成果が出ていて継続すること)」「Problem(解決すべき課題)」を洗い出し、「Try(次に取り組むこと)」を検討する スプリントプラニング 事前にスプリントバックログとチケットをJiraに追加する 検討・分析タスクの場合、相談・調査したい要件をまとめる バックログとゴールをメンバーに共有し、リソースや優先順位を調整する <木曜日と金曜日> 仕様書の更新とバックログの整理 バックログの詳細をエンジニアとデザイナーとすり合わせし、仕様書を更新する ロードマップを調整しながら、次のバックログを整理する そんな中で業務で心がけていること クラシルリワードチームのプロダクト開発の考え方 スピード感を大事にする 特にクラシルリワードチームで大事にしているのは、データ分析を基に具体的な仮説を構築し、スピーディに検証を行い、改善効果を評価して、そのサイクルを繰り返すことです。 クラシルリワードチームに異動してからは、チームの迅速なアプローチに驚かされましたが、意識的にアプローチを進めることで、今も多いときは週1回の検証を行っています。これにより、より効果的な意思決定が可能となり、目標達成への方向を見つけることができたと考えています。 おわりに クラシルリワードでプロダクトマネジャーの一週間のスケジュールはこんな感じになります。いかがだったでしょうか? これからチームが意識していることを念頭に置いて、今後もユーザーの基礎体験の改善を推進していきます。クラシルリワードを引き続き楽しみにしていただけると嬉しいです🐰🥕
アバター
こんにちは、クラシルリワードのサーバーサイドエンジニアのhaindです。 この記事では、クラシルリワードのdatabase負荷を分散するために、既存のRails 7アプリケーションにdatabaseのread/writeを分ける仕組みを導入した事例についてお話ししたいと思います。 現状と課題 クラシルリワードのサーバーサイドではRails 7を使っており、MySQLをdatabaseとして採用しています。初期段階から、replica(reader)とprimary(writer)のインスタンスが存在していましたが、アプリケーションはprimaryにのみ向けられていました。replicaインスタンスは障害発生時のフェールオーバー用に設けられています。 クラシルリワードアプリの速い成長に伴い、databaseへのトラフィックも早く増えています。ただ、databaseのprimaryインスタンスだけがクエリを処理するため、負荷が高いです。 primaryインスタンスのCPU使用率が特定の閾値を超えると、インスタンスをスケールアップする必要がありますが、この作業にはダウンタイムが伴うため、深夜にメンテナンスを行うことにしています。 それに加えて、databaseのreplicaが存在するにもかかわらず、それをクエリに活用できないのはもったいないです。クエリの増加に対して、replicaに負荷の半分を分散することで、本番のスケールアップをスキップし、コストを削減できます。負荷分散によって処理速度も向上できます。 技術選定 上記課題を解決するためにRails 7アプリケーションにread/writeを分ける仕組みの導入が検討されました。 調査した方法は以下の2つです。 Gemを利用する Rails 7の複数database機能を利用する 有名なgemとして octopus と makara があります。この2つのgemの詳細な比較についてこの 記事 が参考できます。 特にgemのreader/writerの自動切り替え機能に注目しています。 要するに、これらのgemは発行されたSQLクエリをもとに、適切なインスタンスにクエリを送信してくれます。 User.last # 裏側で以下のquery文が発行されます # SELECT `users`.* FROM `users` ORDER BY `users`.`id` DESC LIMIT 1 select クエリの場合はreplicaに送信され、それ以外( create 、 update など)はprimaryに送信されます。 makara What goes where? In general: Any SELECT statements will execute against your replica(s), anything else will go to the primary. octopus Replication When using replication, all writes queries will be sent to master, and read queries to slaves. (replica遅延問題に対して、テーブルに書き込んだ直後に、SELECTクエリを実行する場合は、それをprimaryで行うように指定できます) この機能は便利ですが、残念ながらこれらのgemは直近数年間メンテナンスされていないため、Rails 7での動作がうまくいきませんでした。発行されたqueryを解析できるように、gemはRailsの内部処理に介入しているようです。つまりRailsのソースコードに依存しています。 Railsの新しいバージョンでは、ソースコードの変更によって正しく機能しません。 次に、Rails 7の複数database機能の特徴を調べてみましょう。(詳細は こちら ) この機能ではreader/writerの自動切り替えと手動切り替えが可能です。 自動切り替えの仕組みは、到着したリクエストのHTTPメソッド(GET、POST、PATCH、DELETEなど)を基に、接続を切り替えます。 アプリケーションがPOST、PUT、DELETE、PATCHのいずれかのリクエストを受け取ると、自動的にwriterデータベースに書き込みます。リクエストがそれ以外のメソッドであっても、直近の書き込みがあった場合にはやはりwriterデータベースが利用されます。それ以外のリクエストではreplicaデータベースを使います。 手動で切り替えたい場合は特定の処理のブロックを以下で囲みます。 ActiveRecord::Base.connected_to(role: :reading) do # このブロック内のコードはすべてreadingロールで接続される end 選定の要件 メンバーの手が空いている時間に実施できる(数週間にわたる集中的な対応は難しいため) 変更箇所を素早くテストし、品質を確保できる それを踏まえて、Railsの複数database機能の手動切り替えを選択しました(readerに送信したいGET APIを手動でreaderを指定します、それ以外はデフォルトでwriterに向けます)。自動切り替えが望ましいですが、クラシルリワードのGET APIの一部はdatabaseを更新しています。APIが多いため、修正が必要なAPIを見つけ出すには時間がかかります。また、動作を確認する段階も時間がかかる見込みです。そのため、自動切り替えは適していないと判断しました。 実際に導入 以下の手順で導入を進めました Railsアプリケーションでreader/writerの設定 Rails guide のようにdatabase.ymlを変更しました。クラシルリワードの場合、primary、replicaのendpointが違うため、それぞれのhostも指定しました。 次にmigrationコマンドを実行すると自動的に db/primary_replica_schema.rb が生成されます(内容はschema.rbと同じです)。 この変更をlocalと開発用サーバーで動作確認し、問題がないことを確認した後、本番環境にリリースしました。 リクエスト数が多いGET APIを洗い出す Railsの複数database機能の手動切り替えの場合、クエリはデフォルトでprimaryに送信されます。replicaに送信したいGET APIを洗い出して、それらを優先して対応します。 対象APIのクエリをreplicaに送信する対応 対象APIを1つずつ実装、動作確認、リリースします。 実装は単にこれを追加するだけでした。 ActiveRecord::Base.connected_to(role: :reading, prevent_writes: true) do # このブロック内のコードはすべてreadingロールで接続される end prevent_writes: true はreplicaへの書き込みを防ぐことを意味します。ブロック内に書き込みを行った場合、実行時にエラーが発生します。GET APIをreplicaに向けるようにしていますが、後で他のメンバーがAPIを修正する際、更新処理を追加したらエラーが発生して、replicaに書き込まないようにしています。replicaへの書き込みはprimaryと同期されないため、ブロック内で書き込みを行う場合は明示的にprimaryを指定する必要があります。 注意:ActiveRecord::Base.connected_toブロックを抜けると、クエリが実行される点に留意してください。同じ処理は同一のブロック内にまとめると良いと思います。(詳細は こちら ) 導入の効果 修正対象のAPIの一部を修正したところ、primaryのCPU使用率が最大20%減少しました。今後はさらにreplicaを効果的に活用して、パフォーマンスとコストを改善していきたいと考えています。 まとめ クラシルリワードでRails 7の既存のアプリケーションにread/writeを分ける仕組みを導入する方法を紹介しました。皆さんの参考になれば幸いです!
アバター
はじめに 導入背景 サービス概要 リリース頻度 QA事情 自動テストツールの要件 トライアル時の検証 実際の運用事例 実数値 テストケース 運用体制 実際のテスト運用フロー MagicPodを運用してみてどうだったか 良かったところ 運用してみないとわからなかったところ まとめ はじめに こんにちは!クラシルリワードで開発責任者をしている funzin です。 この記事ではクラシルリワードに自動テストツールとして MagicPod を導入したことについて紹介してきます。 導入背景 サービス概要 はじめにクラシルリワードについて紹介します。クラシルリワードは「日常のお買い物体験をお得に変える」アプリです。日常行動をアプリ内で行うことでポイントが貯まり、貯まったポイントを商品券などに交換できるサービスです。 現在、 iOS ・ Android ・ Web の3つのプラットフォームで展開しています。 リリース頻度 クラシルリワードは1年前にリリースしたこともあり現在も機能開発が盛んに行われています。FeatureFlagを利用したトランクベース開発を行い、アプリは平均週2リリースを行うなど高頻度でリリースが行われています。 QA事情 リリースが高頻度のため開発速度に影響がないように下記のようにQAを行っていました。 機能追加・修正時に実装者がQA観点をまとめて、関係者に触ってもらい違和感がないかを確認 SlackでのQA依頼例 大きい機能開発の場合、デグレをしていないかを確認するために一括テストを実行 テストケースをスプレッドシートで管理 リリース当初から上記のようなQAを行っていましたが、サービス規模も大きくなっていく中でポイントやチラシを提供している小売の情報を扱ってるため、不具合が発生すると大きな影響が出てしまいます。 またリリース当初と比較すると機能が増えてきて、手動で既存機能のテストすることの難易度が上がってきました。 このような状態の解決をするために、自動テストツールを導入を検討しました。 (※各プラットフォームでUnitTestは書いていますが、今回はUIテストの自動化に着目しているため割愛します) 自動テストツールの要件 まずは自動テストツールを導入することで何を実現したいかを整理しました。 人が手動で行っていたテストケースを自動化 テストケースのメンテナンスコストを低く保つ 引き継ぎを想定して、非エンジニアでもテストケースの対応が可能な状態 上記の要件を実現するために、自動テストツールの候補として下記を洗い出しました。 各プラットフォームでのUITest(iOS: XCTest , Android: Espresso ) Maestro を利用したymlベースでのテスト実行 MagicPodのようなGUI上での自動テストツール 事前に定義した自動テスト要件と自動テストツールの候補を照らし合わして整理していきました。 1のUnitTestに関しては、それぞれのプラットフォームで開発工数がかかる、プラットフォームが別れているためどのようなテストケースを担保するかなどのコミュニケーションなどで工数を使うため見送り 2のMaestroに関しては、yml管理できるのは魅力的なものの、非エンジニアの対応コストが高いため見送り 3のMagicPodに関しては、GUI上でテストケースを作成できるので非エンジニアでも対応可能。 共有ステップ を利用すればメンテナンスコストも低く保てそう そのため、要件と最もマッチしそうであるMagicPodを第一候補として検討しました。 トライアル時の検証 MagicPodを検討したとはいえ実際に触ってみないと判断がつかないため、まずは 無料トライアル から始めました。 トライアル期間中に検証したことは下記です。 位置情報計測、ヘルスケアなどのサードパーティ製の仕組みを使った機能を含むためそれらのテストケースの作成が可能か テスト実行の自動化フローが組めるかどうか 1に関しては、事前にMagicPod側に懸念点の質問、実際にMagicPodでテストケースを書いて動作確認をしました。 2に関しては、本来であればCIを構築して検証するべきですがトライアル期間のため撤退する可能性もあります。そのため手元のPCで crontab を利用して擬似的にローカルPCをCIと見立てて検証しました。 下記がcrontab, script, Makefileの例となります。 $ crontab -l 0 10 * * 1-5 TZ =Asia/Tokyo make build-and-upload # 平日朝10時に実行する # Makefile .PHONY: build-and-upload build-and-upload: sh ./android.sh sh ./ios.sh # android.sh current_dir = $( pwd ) export JAVA_HOME =/Applications/Android\ Studio.app/Contents/jbr/Contents/Home export PATH = $PATH : $JAVA_HOME /bin # Build cd ../android && git co develop && git pull && ./gradlew assembleDebug # Renamed FILENAME =Rewards-Dev- $( date +%Y%m%d%H%M%S ) .apk cd app/build/outputs/apk/debug && mv app-debug.apk $FILENAME export MAGICPOD_ORGANIZATION =org export MAGICPOD_PROJECT =android cd " $current_dir " # Upload ./magicpod-api-client upload-app -a ../android/app/build/outputs/apk/debug/ $FILENAME # ios.sh current_dir = $( pwd ) # Build cd ../ios && git co master && git pull && \ xcodebuild \ -workspace App.xcworkspace \ -scheme App-Debug \ -sdk iphonesimulator \ -destination ' platform=iOS Simulator,name=iPhone 14 ' \ -configuration Debug \ -derivedDataPath DerivedData \ clean build # Renamed FILENAME =Rewards-Dev- $( date +%Y%m%d%H%M%S ) .app cd DerivedData/Build/Products/Debug-iphonesimulator && mv App-Dev.app $FILENAME export MAGICPOD_ORGANIZATION =org export MAGICPOD_PROJECT =ios cd " $current_dir " # Upload ./magicpod-api-client upload-app -a ../ios/DerivedData/Build/Products/Debug-iphonesimulator/ $FILENAME MagicPodの実行は magicpod-api-client を利用しました。 crontabで出社時間の毎朝10:00に実行し、MagicPodのテストスケジューラーは10:30に実行するようにしていたため、iOS・Androidの最新ビルドがMagicPodで実行されるようになり自動化フローを検証を進めることができました。 crontab経由でslack通知 上記の検証をトライアル期間中に行い、ある程度利用できることが確認できたため、本格導入に踏み切りました。 (トライアル期間でも多くの質問に答えていただいたMagicPodのCS担当者の皆様には感謝です。) 実際の運用事例 実際にMagicPodを導入してみてどうだったかを次のセクションからお話しします。 実数値 2023/12時点での実数値です。 テストケース数 iOS: 23テストケース Android: 23テストケース 平均テスト実行時間: 30分~1時間 MagicPodの運用者: 1名 それぞれ抜粋して補足説明していきます。 テストケース 基本的にはユーザー体験として優先度が高いものからMagicPodのテストケースとして作成しています。クラシルリワードではオンボーディング突破や、チラシ閲覧からのコイン獲得などがあげられます。 スプレッドシートで管理していたテストケースも考慮しつつ導入初期は一気にテストケースを作成するのではなく、Highのテストケースを一つずつ作成していきました。 テストケースをNotionで一覧化 MagicPodでは開発中のテストケースと一括テスト用のケースが存在するため「Morning Build」, 「Development」という2つのタグを使って管理しています。「Development」タグでは一括テスト対象から除外することで、一括テストに影響がないようにしています。 MagicPodのテストケース一覧 運用体制 MagicPodの運用体制としてQAチームを作る or アプリエンジニアに運用を任せるかで悩みましたが、現状は自分1人で運用しています。 理由としては下記です。 両OSの仕様を把握してる人間がどちらもメンテナンスすることで、OS間の差分を検知しやすい QA専任がいない中での複数人運用は、コミュニケーションコストが大きい 最終的にテストケースがメンテナンスされなくなって形骸化する可能性が大きい 一度テストケースを作って慣れたらそこまで時間はかからない まずは1人での運用を安定的に行い、将来的には引き継ぐことを想定して複数人で運用できる体制にできればと考えています。 実際のテスト運用フロー 次に実際のテスト運用フローについて説明します。 以下の図のように2つのタイミングで実行しています。 朝7:00の定期実行 リリースタグ付与時に実行 MagicPodでは主に機能開発によるデグレの検知を行っています。 リリース前のブロック用途としても利用可能ですが、テストがまだ不安定にこけることがあり開発者が都度確認するのが現状の開発速度を阻害してしまうためこのような運用にしています。 何回か再実行しても同様の失敗をする場合は開発者に確認するフローをとっています。 開発者への確認例 MagicPodを運用してみてどうだったか 実際に運用してみて、良かったところと運用してみないとわからなかったところがあるので紹介していきます。 良かったところ 手動で一括テストをしていた箇所がほぼMagicPodに置き換えができたこと もちろん全てのテストを置き換えることは難しいですが、手動でやっていたテストケースの大半を置き換えることができました。 実際にMagicPodを導入して、肌感は下記のような感覚値になりました。 開発者の心理安全性が高まったという声が増えた 毎朝定期実行やリリース前の実行によってデグレ検知ができる認知が開発チームに芽生え、既存機能のデグレを恐れずに安心して開発を行うことができるようになりました。 自動修復機能が便利 MagicPodの 自動修復機能 を利用すると、文言系などの細かい修正はMagicPodが自動で直してくれるのは、メンテナンス観点でもとても助かっています。 自動修正例 運用してみないとわからなかったところ 一度通ったテストが思ったよりもよくこける 個人的には一度テストが通っても、その精度は70%程度と考えています(感覚値)。 複数回実行しないと不安定な箇所は特定ができないため、それを逐一修正していく必要があります。 毎回修正していくことで精度を100%に近づけていくイメージです。 2023/12時点だとテストケースも精度が上がってきてだいぶ落ち着いてきましたが、導入当初は毎朝テストケースを直すところから始めていました。 機能開発が活発な画面だとメンテナンスコストが大きすぎる。 冒頭でも述べたように、リリース数が多いため下手にテストケースを追加すると毎日メンテナンスすることになります。 その場合、開発者とテストケース作成者のコミュニケーションコストが発生するためにお互いに幸せになりません。 このようなケースでは一定の開発期間は落ち着くまで、テストケースを書かない方が良いと判断しました。 まとめ この記事では自動テストツールとしてMagicPodを導入した経緯と実際の運用事例についてまとめました。 初めはリリースサイクルが早いプロダクトにおいて導入できるか懸念がありましたが、現状はワークしており導入して良かったと感じています。 MagicPodの導入検討している方はトライアルでお試ししてみて、自社サービスや組織にマッチするかを検討してみるのがおすすめです。
アバター
🐰はじめに クラシルリワードのAndroidアプリエンジニアをしているnozakingです、こんにちは! 先日、クラシルリワードのAndroid版でも歩数機能が遂にリリースされました(2023年12月現在はまだ一部のユーザーにのみ提供中です)。機能実現のためにGoogleのFitness APIを利用しているのですが、API利用申請の過程で CASAセキュリティ評価 を受ける必要がありました。 今回の記事では、CASAセキュリティ評価を通過し、検証文書(LOV)が発行されるまでの流れを紹介したいと思います。 play.google.com 歩数機能の画面。歩数に応じてゲージが溜まってインセンティブがもらえます。 🐰CASAセキュリティ評価って? CASA(Cloud App Security Assessment)はGoogleが提供するアプリのセキュリティ評価プログラムで、アプリの信頼性を徹底的に確保するものです。 🔗 App Defense Alliance https://appdefensealliance.dev/casa GoogleのFitness APIを利用するためには、 CASAのTier2セキュリティ評価 を完了する必要がありました。 🐰CASA Tier2セキュリティ評価を完了するためにやったこと CASA Tier2セキュリティ評価を通過するために私たちがやったことを紹介します。 基本的に公式で案内されている通りの流れを行いました。 🔗 CASA Tier 2 Process | App Defense Alliance https://appdefensealliance.dev/casa/tier-2/tier2-overview 1. 通知 API利用申請のメールの中でCASA Tier2セキュリティ評価を実施するように指示を受けます。いくつかの選択肢が提示されましたが、私たちは オープンソースツールを活用したTier2セルフスキャン を選択しました。 2. アプリのスキャン ガイダンスに従い、アプリケーションのスキャンを実施します。スキャンをおこなうと結果としてCWEリストが出力されます。CWEは、ソフトウェアおよびハードウェアの弱点タイプのリストで、コミュニティによって開発されたものです。 cwe.mitre.org この工程はプロセスに記載されているものの、結果のCWEリストを提出する機会はありませんでした。 とはいえ、何かCWE結果が何かしらあれば、後々対応することになるはずなのでここで対応しておいた方が後でスムーズだと思います。 3. 結果の送信 初回提出と修正対応 CASAポータルにアカウントを作成し、アプリに関する情報とアプリのソースコードを提出します。 rc.products.pwc.com アプリのソースコード全体を提出可能な状態(zip)にまとめるのですが、詳しい手順は申請フォームの中に記載されているのでそれに従います。提出後、静的解析の結果が通知され、指摘事項があれば修正対応を行います。(たしか30〜1時間くらいで結果が来ました) クラシルリワードのケースでは軽微な修正を数個対応するだけで済みました。 例えばセキュリティプロバイダにパッチを適用できるように対応するなどをおこないました。 🔗 Update your security provider to protect against SSL exploits https://developer.android.com/training/articles/security-gms-provider アンケート回答 静的解析を通過する連絡を受け取ったら、次はアンケートに回答を記入して提出します。こちらは十数ページに渡るほど項目数が多く、内容が難しく、すべて英語で記載する必要があったので時間が掛かりました。 アンケートの回答を提出すると、CASAの担当者から必要に応じて追加の質問が来ます。やりとりはCASAポータル内にあるメッセージ画面で行います。 質問に対して該当しない場合は N/A を記入し、その根拠を説明する必要があるのですが、ここが一番時間がかかったポイントです。 例えば、クラシルリワードにおけるアカウント管理はFirebase Authenticationを用いたGoogle認証だけなので、認証に関する質問内容について N/A と回答していました。これについて、Google認証APIの仕様(セキュリティ水準を満たすものかどうか)について詳細に説明を求められ、何度もやり取りを行いました。 4. ファイナライズ アンケート回答後の問答を通過したら検証文書(LOV)が発行されます。このLOVをGoogleからのセキュリティ評価要求メールに返信し、Fitness APIの利用が承認されました。 🐰スムーズな完了のために 実は歩数機能の実装よりもGoogleからの承認を得るためのやり取りの方が多くの時間が掛かりました。スムーズに終えるために気をつけたいポイント、工夫を紹介したいと思います。 納得させるために必要なことは全て記載する CASA担当者とのやり取りで、「これくらい説明すれば分かるだろう」と思っていてもなかなか伝わらなかった印象でした。何度もメッセージを送って返信を待つを繰り返すくらいなら、最初から具体的に詳細すぎるくらいに説明した方がスムーズに進んだなと思いました。 メッセージの作成はAIの助けを借りる GoogleやCASA担当者からの返信は待つしかないですが、こちらボールになったらなるべく早く返すことが大事だと思います。今回のメッセージのやり取りは英語で行っていたのですが、私は英語が得意ではなかったため余計に時間を掛けないようにChatGPTを活用しました。 伝えたい内容を箇条書きにする ChatGPTで、このメールに箇条書きした内容を伝える返信メッセージを作成してほしいと依頼する ChatGPTがいい感じの英語の文章を作成してくれる 適切な形に修正して返信メッセージを送る ※質問の回答内容すべてを丸投げするのはNGです。伝えたい内容は自分でちゃんと考えましょう。 という感じで、返信する内容を考える以外は素早く行えました。ありがたい技術ですね。 🐰おわりに 時間はかかりましたが、CASAセキュリティ評価を受けることでセキュリティについて一定のレベルをクリアし、大きな達成感を得ました。 Android版のクラシルリワードでは歩数機能を一部のユーザーに公開中ですが、ユーザー体験や収益性をブラッシュアップさせ、全Androidユーザーにも使ってもらえるように改善を重ねているところです。歩数機能に対するユーザーからの期待も高まっているため、全Androidユーザーに最高の形で提供できるよう、これからも頑張ります。どうぞご期待ください!
アバター
こんにちは! クラシルリワードでグラフィックデザイナーをしているmakosunです🐍 クラシルリワードには2023年7月に「クラシうさぎ」というキャラクターが新しく登場しました🐰🥕 私はキャラクターに関連するデザイン・グラフィック面を主に担当しています。 キャラクターを使ったクリエイティブのデザイン、ポーズや表情の追加、シーズンに合わせたイラストの制作、アニメーションの制作などを行なっています。 今回のブログでは、「 キャラクター運用する上で工夫していること 」を書いていきます。 一番左のうさぎ キャラクターのポーズは立体感を意識する🕺 イラストを新しく描き起こすときは、基本的に 斜めから見たポーズ にしています。 正面からのイラストは立体感がなく、のっぺりとした印象になってしまうので、 斜めのポーズや、正面でも奥行きのあるイラストにしています。 共感性の高いポーズで親近感を沸かす🤝 ポーズのイラストを新しく追加するときは共感性の高いものにしています。 スマホを見る、晩御飯を考える、寝る、本を読む、など人が普段生活しているのと同じようにクラシうさぎも過ごしていることで、親近感が湧くようにしています。一緒に生活しているような気分になって、愛着を持ってほしいという期待も込めています🐰 キャラクターの表情🐰 バナーやPOPUPなどのクリエイティブに使用する際は、キャラクターのビジュアルに飽きが来ないようにするために、同じイラストを使いすぎないことを気をつけています。 「喜ぶ」という感情だけでも違う表現がいくつかあるので、それを使い分けています。同じポーズでも目を笑わせたり、口を開かせたりなど変化があるようにしています。 アプリアイコン📱 アプリのアイコンはノーマルポーズのクラシうさぎを使用していますが、季節のイベントに合わせて、うさぎにコスチュームを着せたり、背景を変更したりしています。 その時は通常用のアイコンからの変化を少なくするために、 クラシうさぎの位置・大きさは変えない、顔の見える範囲を広くする ことを意識しています。 変化がありすぎると、クラシルリワードのアプリだと気づかず開くことができない、または間違えてアプリを削除してしまう可能性があるので、ユーザーの皆さんが気づく範囲で変えるということに気をつけています。 特に顔の横についているもふもふは特徴的な形 をしているので、モチーフで隠れないようにしています。 終わりに 今回は「キャラクター運用で工夫していること」をご紹介しました! キャラクター運用をやりたい!という方の参考になれば幸いです。 またクラシうさぎもどんどん活躍の場を広げていくので、楽しみにしていただけると嬉しいです🐰🥕 おまけ クラシうさぎのぬいぐるみとCGです。 私はぬいぐるみ作りが趣味なので、個人的に作ってみました。 CGはBlenderの勉強をするために作ってみました。
アバター
こんにちは、クラシルリワードのSRE担当のjoooee0000です。 私はクラシルリワードのサービスローンチの約3ヶ月後にサーバー兼インフラエンジニアとしてjoinし、サービスの成長と共に、開発速度とシステムの信頼性の向上を目指してシステムの改善を行ってきました。 その中で、特に開発速度と信頼性向上に寄与したと思う3つの改善を紹介したいと思います。 改善を行う際に「コスト(金額、導入共に)と効果のバランス」「運用しやすいシンプルな設計」に特に気をつけたので、参考なると幸いです。 (話すこと: 取り組んだ施策の概要紹介、 話さないこと: 細かいhow to) やってよかったこと3選 ブランチ戦略の見直しとシンプルな検証環境の維持 ログの構造化とNewRelicログUIの導入 インシデント管理ツールの導入 それぞれ、改善前にどのような課題があったか、改善後のメリットなどを紹介していきます。 1. ブランチ戦略の見直しとシンプルな検証環境の維持 ここ1年間、チームの成長に合わせてブランチ戦略や検証環境が今の開発スタイルに合っているかを都度検討しながら、速度を落とさずに開発を進めてきました。そこで、初期から現在までのブランチ戦略の改善や検証環境をどのように運用しているかを紹介します。 チームが始まった当初のサーバーサイドエンジニアが1 ~ 3人くらいだった頃の話になりますが、下記の図のように、main / develop / stagingとfeatureブランチの4種類のブランチで運用されていました。 当時のブランチ戦略 また、main / develop / stagingそれぞれのブランチをベースにしたPullRequestをマージすると各環境にデプロイされる、というデプロイ戦略でした。 当時のブランチ戦略は、GitFlowなどのブランチ戦略と違い、developブランチを経由してmainブランチにリリースされるといった開発フローではなく、開発フロー中にdevelopブランチとmainブランチが合流するポイントがありませんでした。 それゆえ、mainブランチからdevelopブランチを一回切ったあとはdevelopブランチとmainブランチが徐々に乖離していくという課題があり、featureブランチをマージする際に複雑なコンフリクトやforce pushが必要になることが頻繁に発生し、開発速度が低下していました。stagingに関しては、利用頻度が活発ではなかったためデプロイの内容が徐々に古くなっていくという状況でした。 そこでブランチ戦略をmainとfeatureブランチだけのGithubFlowのブランチ戦略に変更し、developブランチの廃止に伴い、検証環境には個々のfeatureブランチをGitHub Actionsのworkflow dispatchをhookにブランチを選んでデプロイできるようにしました。ステージング環境にはfeatureブランチからmainブランチへのマージをhookに本番と一緒にデプロイされるようにしました。 デプロイフロー また、そのタイミングで検証環境を開発者個々人に用意するかどうかが議論に上がりました。しかし、個々人用に検証環境を作ることで検証環境独自の運用が発生することを回避するために、本番環境と全く同じ構成の1台で運用することで検証環境の運用コストを下げることを選択しました。そして、検証環境は最終的な確認のみに使い、local環境での開発をメインにするようにしました。ブランチ戦略とデプロイを改善したことで、検証環境が1台でも問題なく開発ができるようになりました。また、少なくともここ1年間はほとんど検証環境の運用作業が発生しておらず、シンプルな構成の恩恵を受けています。 開発者のフィードバック 現在、サーバーサイドエンジニアが6名+web開発の1名になり、ブランチ戦略を整理した当時の倍以上になったことで複数人が同時に検証環境を利用したいシーンが増えてきました。個人のfeatureブランチをデプロイする方法のみだと、検証に待ちが発生する状態になりました。 そこで、検証環境を複数台に増やす検討の前に、まずはブランチ戦略やデプロイの改善をしました。 デイリーでmainからその日限りのdevelopブランチを自動作成し、そのブランチに複数人がGitHub Actionsを通して自動マージ&デプロイをして検証を行う設計に変更しました。別の開発者がマージしたコードとコンフリクトした場合は自動マージできないので開発者が解消する必要がありますが、デイリーでmainからdevelopブランチを作成しているため、mainとの乖離による無駄なコンフリクトは発生することはありません。また、developブランチへのマージやブランチ作成をGitHub Actionsで自動化することで開発者の負担を下げています。 最新ブランチ戦略 このように、チームの規模や開発スタイルに合わせたブランチ戦略の見直しと、検証環境をシンプルに保つことで、開発速度の向上や運用工数の削減をすることができています。 2. ログの構造化とNewRelicログUIの導入 サービスをリリースしてから数カ月間は、CloudWatch Logsを利用しており、かつ構造化していないログで検索性がとても低い状況でした。そのため、ほとんどアプリケーションログが活用されておらず、不具合調査などの業務効率が大幅に下がっていました。 そこで、NewRelicの導入とアプリケーションログをjson形式に構造化をすることにしました。 まず、NewRelicを選定した理由として、下記があります。 NewRelicのデータ転送コストがCloudWatch Logsの1/3程度で済むこと ログ基盤のElasticsearchの運用はSaaSのNewRelicに任せることで運用コストが削減できること ログ管理UIの利便性 ログ管理をしようと思ったときに一番料金がかさむポイントとしてログ基盤へのデータ転送の料金があります。実際に、CloudWatch Logsでもデータ転送料がコストの大半を締めていました。 NewRelicのデータ転送コストはSaaSの中でもトップクラスで安く、CloudWatch Logsの1/3程度で済んだためコスト削減にも繋がりました。(実際にはつなぎ込みのためのKinesis Data FirehoseとBackup用のS3の料金があり1/3コスト減とまでは行きませんが、それでもCloudWatch Logs単体利用より安価)NewRelicはAPMのSaaSとしてよく知られていますが、2022年からはフルオブザーバビリティプラットフォームとして様々な機能が提供されています。より高度な機能の利用を検討する場合はユーザーアカウントごとの課金もありますが、ログ管理UIの利用はBasicUserという無料ユーザーでも利用可能です。 また、自社基盤でログ用のElasticsearchを運用することも考えましたが、Elasticsearchの運用はNewRelicに任せることで運用コストを削減しました。 NewRelicのログ管理UIはポチポチするだけである程度の絞り込みが行えたり、下記のようにグラフも自動で出してくれるのでCloudWatch Logsを利用していたときと比べて圧倒的に利便性が上がりました。 実際のNewRelicログUI また、無料で使えるダッシュボード機能を使い、よく検索するログ(5xxエラーが出ている上位10エンドポイントの検索、など)をダッシュボード化して可視化することでトラブルシュートの速度が上がりました。 NewRelicDashboard NewRelicは前に述べたように、他の機能も充実しているため拡張性もあります。実際に、SLI / SLOの可視化や外形監視など、他でも利用できるところが多々あり、サービスが大きくなってきた現在も便利に使えています。 また、アプリケーションログをjson形式に構造化することでログ検索の利便性が上がりました。 アプリケーションのサーバーサイドはRailsを利用していますが、Railsのログは、エンドポイント、リクエストパラメータ、レスポンスタイム、ヘッダーなどの情報が複数行に分かれているため、各エンドポイントにかかった時間やパラメータなどを一行で検索することができませんでした。また、例えば、CloudWatch Logsのクエリでリクエストごとのレスポンスタイムを検索しようとすると下記のような複雑なクエリを毎回書く必要があり不便でした。 fields @timestamp, @message | filter @message like /Completed 200/ | parse @message "Completed 200 OK in *ms (Views: *| ActiveRecord: *ms" as response_time_rails, response_time_view, response_time_rds | filter response_time_rails > 500 | sort @timestamp desc | limit 20 そこで、 GitHub - roidrage/lograge: An attempt to tame Rails' default policy to log everything. を利用してエンドポイントやレスポンスタイムの情報、ヘッダーの情報の一部を一行で出力できるようにし、コード上で指定している ::Rails.logger で出力されるRailsのアプリケーションログも同様にjson形式に構造化しました。 また、::Rails.loggerのタグ機能でrequest_idを1行ごとに付与し、1リクエストのログを紐付けられるようにすることでリクエストごとのログが追いやすくなりました。 request_idによるリクエストごとのログ検索 Railsのログの構造化に関しては、意外と参考記事が見つからなかったので後日追ってブログにしようと思います。 NewRelicを導入したことで、開発速度・信頼性共に恩恵を受けることが多々ありましたし、わたしたちのサービスのトラフィックやログ量だと、CloudWatch LogsからNewRelicに移行することでコスト削減にも繋がりました。また、ログの構造化をすることで不具合調査や障害対応時にスムーズに原因を特定でき、役立っています。 3. インシデント管理ツールの導入 3つめは、インシデント管理ツールを導入し、MTTR(平均復旧時間)の短縮をした話です。 私達のサービスは、サービス利用ピーク時間帯が朝方です。サービス利用のピーク時間は、他の時間帯と比較して障害発生確率が高くなる傾向にあります。しかし、ピーク時間帯が朝方のため、開発メンバーが起きておらず、障害に気が付かないことでMTTR(平均復旧時間)が伸びてしまったことがありました。 アラートはSlackに流すようにしていましたが、それだけだと 「常にSlackを見ていないと障害に気づけない」、「アラート通知が他のSlack通知と一緒の音で紛れがち」という課題がありました。 そこで、まずは朝方や休日のインシデントに確実に気づけるよう、インシデント管理ツールのOpsgenieというインシデント管理ツールを導入しました。インシデント管理ツールの機能の中でも、アラート通知機能によってMTTRを短くする対策することにしました。 CloudwatchAlarmでアラートが上がったらSNS経由でOpsgenieにアラートを送信するようにします。また、自分のスマートフォンにOpsgenieのアプリをインストールすることで、アラートをスマホで受け取れるようになります。 そうすることで、障害が起きるとスマホから目覚ましのアラームのような音が鳴り(心臓に悪いという点では不評)、障害に迅速に気づくことができます。Opsgenieの導入以降は障害に気づくまでの時間が短縮され、MTTRの短縮に寄与しました。 加えて、サービスに関する重要な意思決定ができるメンバー(現EM)も一緒にアラートを受け取れるようにし、障害時の対応に迷った際、迅速に意思決定ができるようにしています。 Opsgenieは、アラート通知機能を使いたいだけであれば、利用人数5人まではフリープランで足ります。 最近チームの人数が増えたこと、過去のインシデントとアラートの紐づけのニーズが上がってきたことにより有料プランへの切り替えを予定していますが、この1年間はフリープランで十分なメリットを享受できていました。その他にも、フリープランで オンコールローテーションの自動スケジューリング (メンバーとローテーション期間を指定すれば自動でスケジュールを組んでくれる) エスカレーション機能 (スマホで受け取ったアラートのボタンを押せば自動でエスカレーションできる) オンコールスケジュール画面 などが利用でき、複数人の開発者のオンコールローテーションを簡単に仕組み化することができます。 インシデント管理ツールとしてはPagerDutyが有名ですが、Opsgenieは同じような機能をPagerDutyの半額で利用できるのでコスト面も優れています。また、 Atlassian の製品なので品質も特に問題なくUIも使いやすいです。 Opsgenieには様々な機能がありますが、まずはシンプルにアラート通知の機能を使うだけでもメリットがあるので、おすすめです。 また、OpsgenieはTerraformでproviderが提供されており、設定内容をTerraformで管理することができます。Terraformで管理することで、属人化や設定のブラックボックス化を防いでいます。 まとめ 開発速度・信頼性向上のための取り組みを3つ紹介しました。 運用面やコスト面を考慮して改善を行っているので、小さいチームにも参考になれば幸いです!
アバター
NHK関連の話ではないです こんにちは harry( @gappy50 )です〜。 これまでクラシルでデータエンジニアをしておりましたが、最近クラシルリワードという別プロダクトでデータエンジニアをしております。 クラシルリワードのデータ基盤は以下に詳細がありますので、ご興味あればどうぞ! tech.dely.jp 本記事のタイトルは私がTwitter改めXにポストした投稿から抜粋しました(恥 おい、誰も騒いでないから騒ぐけどExternal Network AccessっていうSnowflakeから外部へアクセスできる機能、データサイロ完全にぶっ壊せるぞ。 Federated Queryとして他のPFのDWHだけじゃなく独自のデータもAPIとかから取ってSnowflakeでクエリぶん回せるんだぜ。 https://t.co/qj18hEkMAh — harry (@gappy50) 2023年10月20日 今日は、クラシルリワードとクラシルのデータをSnowflakeのExternal Network Accessを使って越境させて分析できるようにしたよ〜というお話をします。 まずはその前に少し小話を。 どうやるかだけ気になる人は後半をご覧ください。 クラシルとクラシルリワードのデータ基盤とデータ基盤よもやま話 クラシルリワードではGoogle Analytics for Firebaseの行動ログを主に分析するためデータ基盤はBigQueryをベースに据えて日々の分析業務を行っています。 一方、これまでわたしがいたクラシルではAWS上に構築されたSnowflakeに行動ログや各種データを格納して分析をしております。 各プロダクトがアジリティをもって意思決定をしていくには、余計なことはせずに大事なデータがあるところ、重心があるところに軸(DWH)を置いていくのがまずは正攻法ですよね。 クラシルの場合は、AWS環境上にのみデータがあるのでSnowflakeを使うのは自然な流れですし、クラシルリワードの場合はGCPにデータの重心があるのでそこを軸に考えるのが自然です。 ただし、それぞれのデータの利活用がどんどん進んでいくとこんな声も聞こえてきます。 (´-`).。oO(クラシルの○○のデータとクラシルリワードの△△のデータを串刺しでみることで◎◎の結果を知りたい) (´-`).。oO(クラシルとリワードのデータをうまく突合できないかね?) そういった場合、データエンジニアがこれまで考えるべきことは 😤 < (必要なデータunloadしてどっちかに寄せておくか〜)とか 😤😤 < (Embulkを使って全部データかき集めるか〜)とか 😤😤😤 < (データサイロをぶっ壊すための天下一武道会を開催してデータ基盤を統一するぞ!)とか。 データエンジニアの腕がなるところですね。後半になるにつれて鼻息が聞こえてきます😤 全社のデータ基盤をAにしました!とかBに移行しました!とかいろんな事例も世の中にはわんさかあるし、 長期目線で考えたらデータサイロはないほうがよいに決まってます。 ちなみにですが、データサイロは以下のようなものを指します。 データサイロとは、互いに分離され、社内の他部門、他部署からアクセスできないデータストレージ/管理システムのことです。これは、互いに通信したり情報を共有したりすることができない個別のシステムやデータベースにデータが保存されている場合に発生します。データサイロは、非効率、ミス、遅延の原因となるだけでなく、企業がデータを活用して有益な情報を取得し、より適切な意思決定をするうえで妨げとなります。その結果、社内の複数の部署や事業部門でデータが重複したり、一貫性なく使用されることが多くなります。 データサイロとは | 用語集 | HPE 日本 まさに今クラシルとクラシルリワードはデータサイロが起きそうな(起きてる)状況ですね。 我々の場合は、それぞれのデータの重心がAWSとGCPに分かれてしまっていることがより難しくしている原因ですね。 ただし、鼻息荒くするだけでこのデータサイロをぶっ壊すことはできません。 これらを解消するための価値やコストを説明できる状況にならなければそんなものは夢物語になってしまいます。 そもそも本当に組織のアジリティを落としてまでやるべきことなのかみたいなことまで考え出すとそんなに気軽に意思決定なんてできません。 特にAWSやGCPなどのプラットフォームを横断せざるを得ない場合、そのデータ量が多いものを重心がないほうへ寄せるのはストレージコストだけでなく、転送コスト、それらを実現するためのコンピューティングリソースのコストなどの運用コストを寄せ始めてからずっと払い続けることを意味しています。 そしてそれらを乗り越えてデータサイロをぶっ壊した!となっても、事業インパクトもなく、むしろ利便性を下げてしまったら本末転倒ですよね。 なので、各事業の状況や会社としての状況、それらに紐づく制約や条件によっては、データサイロをぶっ壊すとことがいいかといえばそうでないことも大いにありますし、逆にいえば最初から長期目線でデータサイロを起こさない意思決定をすることで利活用が進むことでかかってしまう莫大な移行コストを抑えるようなことも考えられると思います。 後者ができれば一番幸せではありますが、前者も後者もやり直しがほぼできない一発勝負の世界だということも肝に銘じないといけません。 そうやって、データエンジニアは鼻息ではなく溜息をつきながら都度都度必要なデータの出し入れするコストを払い続けるのです。 このような状況において、データサイロはぶっ壊すことは不可能なのでしょうか? …もしかしたら、SnowflakeのExternal Network Accessがあればぶっ壊すことができるかもしれません。 External Network Accessのサンプルは以下に詳細があります。 この例ではSnowflakeのSQLからGoogle Translateを呼び出すサンプルを例示しています。 docs.snowflake.com 話長くなりました。本編です。 External Network AccessでBigQueryのデータをSnowflakeから呼び出す 元ネタはこれです。 medium.com 上記簡単に説明すると SnowflakeでExternal Network AccessというPuPrの機能を使うことで外部APIへセキュアにアクセスできる BigQueryやGoogle スプレッドシート等にあるデータセットもAPIから呼べるのでデータサイロ打破できる ただし、大量なデータを頻度高く取るとBigQueryからの転送コストやBigQueryとSnowflakeのどちらにもコンピューティングリソースのコストを払わないといけないから注意してね みたいな記事です。 ただし上記記事のままだと、大量のデータを取得できてもSnowflake側で展開する際のコストがかかりすぎたり、そもそもデータをSnowflake上に展開できないことがあります。 そのため、UDTFsに変更をした上でSnowflakeからBigQueryのデータを取得してみたいと思います。 また、OAuthではなくGoogle Cloudのサービスアカウントを利用してアクセスしてみます。 今回は ACCOUNTADMIN で実装をしておりますが、External Network Accessは外部へのデータ出し入れが可能になる機能であるため、Snowflake上での適切な権限設定が必要であることは理解していただければと思います。 また、現時点(2023-10-25時点)ではPuPrの機能であることを念頭においておく必要があります。 まずBigQueryへのアクセスのためのルールをスキーマ内に作成します。 use hoge_dev.external; create or replace network rule bq_rule mode = egress type = host_port value_list = ( ' oauth2.googleapis.com ' , ' bigquery.googleapis.com ' ); 次にサービスアカウントのアカウントキーを GENERIC_STRING としてシークレットを作成します。 create or replace secret bq_service_account type = GENERIC_STRING SECRET_STRING = ' {ここに払い出されたアカウントキーの情報を入れる。必要に応じてエスケープしたりする。} ' ; そして、上記のネットワークルールとシークレットを使ってexternal access integrationを作成します create or replace external access integration bq_access allowed_network_rules = (bq_rule) allowed_authentication_secrets = (bq_service_account) enabled = true ; 最後に上記のexternal access integrationを使ってUDTFsを作成します。 CREATE OR REPLACE FUNCTION run_bq_query_table(projid string, sql string) RETURNS table(value variant) LANGUAGE PYTHON RUNTIME_VERSION = 3.8 HANDLER = 'BigQueryDataFetcher' EXTERNAL_ACCESS_INTEGRATIONS = (bq_access) PACKAGES = ( 'snowflake-snowpark-python' , 'requests' , 'google-auth' ) SECRETS = ( 'cred' = bq_service_account) AS $$ import _snowflake import requests import json from google.oauth2 import service_account from google.auth.transport.requests import Request class BigQueryDataFetcher : def __init__ (self): self.headers = self._get_headers() @ staticmethod def _get_headers (): # Snowflakeで作成したsecretから認証情報を取得 credentials_info = _snowflake.get_generic_secret_string( "cred" ) credentials_dict = json.loads(credentials_info) credentials = service_account.Credentials.from_service_account_info( credentials_dict, scopes=[ "https://www.googleapis.com/auth/bigquery" , "https://www.googleapis.com/auth/cloud-platform" ] ) # 認証情報をもとにGCPのサービスアカウントのトークンを取得 credentials.refresh(Request()) # ヘッダー情報を返却 return { "Authorization" : f "Bearer {credentials.token}" , "Content-Type" : "application/json" } def process (self, project_id, sql_query): # BigQueryにクエリを投げるエンドポイント query_url = f "https://bigquery.googleapis.com/bigquery/v2/projects/{project_id}/queries" # クエリを実行して初回のレスポンスデータを取得 response_data = self._execute_query(query_url, sql_query) job_id = response_data[ "jobReference" ][ "jobId" ] location = response_data[ "jobReference" ][ "location" ] schema = response_data[ 'schema' ][ 'fields' ] columns = [field[ 'name' ] for field in schema] # 取得した行データを{"カラム名": "値"}のjson formatに変換しtupleを返却 # UDTFではreturnよりもyieldにするほうが遅延評価の恩恵を受けて効率がよい # https://docs.snowflake.com/ja/developer-guide/udf/python/udf-python-tabular-functions#implementing-a-handler for row in response_data.get( 'rows' , []): yield ({col: item[ 'v' ] for col, item in zip (columns, row[ 'f' ])}, ) # ページトークンが存在する場合は次ページのデータを取得する page_token = response_data.get( "pageToken" ) result_url = f "{query_url}/{job_id}" while page_token: payload = { "timeoutMs" : 180000 , "location" : location, "pageToken" : page_token } response_data = self._get_query_results(result_url, payload) page_token = response_data.get( "pageToken" ) for row in response_data.get( 'rows' , []): yield ({col: item[ 'v' ] for col, item in zip (columns, row[ 'f' ])}, ) def _execute_query (self, url, sql_query): # BigQueryにクエリをpostする payload = { "query" : sql_query, "useLegacySql" : False , "timeoutMs" : 180000 } response = requests.post(url, headers=self.headers, data=json.dumps(payload)) response.raise_for_status() return response.json() def _get_query_results (self, url, payload): # クエリの結果をgetする response = requests.get(url, headers=self.headers, params=payload) response.raise_for_status() return response.json() $$; あとは、SnowflakeからBigQueryに投げるクエリとSnowflake側での変換処理を書いておしまいです。 select value:event_date::string as event_date, value:event_name::string as event_name, value:record_count:: number as record_count, value from table (run_bq_query_table( ' <GCPのProject ID> ' , ' select event_date, event_name, count(*) record_count from `events_*` where _TABLE_SUFFIX = '' 20231001 '' group by 1, 2 ' )); どやぁ ある程度Snowflakeで扱いやすいようにBigQueryからのレスポンスを整形してvalueというvariantの1列にデータを格納するようにしています。 また、レスポンスが大きいとAPI側でページングがされるので、そこらへんもよしなに取れるようにしてます。 手元では100万行超、2列のデータをBigQueryから取得して表示されるまでXSで動かして1分程度で返ってきました。 ただし、VARIANT列にすべてのレコード情報をkey-valueの形で格納しているので、1行あたりのデータ量が最大長の16 MBを超えると何かしらの問題があるかもしれません。 また、BigQueryのSTRUCT型のレスポンスが結構カオスなので事前にBigQuery側でunnestする等の処理はしておいたほうが幸せになれそうです。 他にも、こうしたほうがもっとよいよ〜みたいなのあればぜひ教えてください💪 さいごに いかがでしたでしょうか? 煩雑なデータエンジニアリングを日々行ったり、データサイロの解消のための天下一武道会を開催をせずに、SnowflakeからExternal Network Accessを使えば最速でデータサイロを解消することもできそうですね。 今回はBigQueryからのデータ取得をサンプルとして例示しましたが、流行りのOpenAIをSnowflakeにちょちょっと記述するだけでSQLから呼び出すこともできそうですし、他にも既存の外部データの連携やデータパイプラインに対しても色々な選択肢を取れるようになるので、色々と夢が広がりますね。 そういうわけで、External Network Accessはデータサイロをぶっ壊せる可能性がありますよというお話でした。
アバター
Hello. My name is K, and I am currently working as an android engineer in Kurashiru. 🚀 Preface Brief Intro to Relay Creating UI Packages with Relay Using the component in Android Studio Map to Compose Theme Map to Existing Components Review of Relay: thoughts on it’s practical usage Preface At Kurashiru, we're on a journey of transitioning to Jetpack Compose. This has led me to experiment with Relay plugin in Kurashiru-next. As you may be aware, Relay is currently in alpha and is a design-to-code transformation tool. Relay makes it possible for designers to create UI components in Figma and export/import them into Android Studio to generate pixel-perfect Compose code. The ability to seamlessly generate pixel-perfect Compose code from Figma UI components, makes me wonder a bunch of questions, “Does this tool suitable for practical use?”, “How will it look like with Kurashiru?” and “Who is this for?”. So, I did a little experiment to transform a few Kurashiru’s components in Figma to Compose code using Relay and I would like to share that experience in this article. Brief Intro to Relay Relay provides instant handoff of Android UI components between designers and developers. Designers use the Relay for Figma plugin to annotate and package UI components for developer use, including information about layout, styling, dynamic content and interaction behavior. These UI Packages provide a shared model for UI components, and can be exchanged and updated in a collaboration between designers and developers. Relay consists of three plugins: the Relay for Figma plugin, the Relay for Android Studio plugin, and the Relay Gradle plugin for developers. Once setup, the process of converting the design into code can be done in a few steps: Package up a component in Figma by specifying parameters (information about the layout, styling, arbitrary content, and interaction behavior of the design). Import UI packages into Android Studio. Build the project and the codes will be generated. I will skip the setup parts and jump straight to using it. Here is documentation on how to set up . Creating UI Packages with Relay Let’s work on simple Button Component as an example. Button Component in Design system These are different variants of button used in Kurashiru app. I reckon a button is a good exercise to study basic capabilities of Relay. Continuing to the packaging of component, define the parameters that the developers need to be able to control. In this scenario, design (size, primary, alternative, filled, outlined and so on), onTap and Text are added to be able to pass dynamically. Packaging the Component Using the component in Android Studio Importing to android studio is pretty simple to sort out by following the steps from here . After building the project, the codes are auto-generated. Details of what kind of codes are generated can be read here . To see the results, I dropped the generated Button into a @Preview composable like this. @Preview @Composable fun ThemeButtonPreview() { Button( text = "HELLO" , type = Type.Filled, size = Size.Large, state = State.Theme, onTap = { /* do nothing*/ } ) } @Preview @Composable fun PrimaryButtonPreview() { Button( text = "HELLO" , type = Type.Filled, size = Size.Large, state = State.Primary, onTap = { /* do nothing*/ } ) } Preview Composable Map to Compose Theme For colors and typography, Relay generates literal values by default. This ensures translation accuracy, but prevents components from using the Compose theming system. To resolve the theme issues, Relay offers an experimental feature,   Mapping Styles to a Compose Theme . This feature allows to map named styles in Figma, to tokens, and then from tokens to Compose theme properties which allows us to point Figma styles directly to their Compose equivalents. Mappings are defined in  json but I will not go further into those and continue with the next point which is about Mapping components to existing code. Map to Existing Components Developers can customize the code generation process by providing a mapping between a UI Package and an existing code component instead of the generated code. This is beneficial when the existing implementation has features that cannot be achieved by the generated code such as animation or complex behavior (such as a drop down menu). Another experimental feature, allows us to map our own created components to the generated components. In addition to the button, I also conducted an experiment with the SearchBar. The generated search bar is not user interactive, which means cannot type inputs or detect input changes, and other complex logics are needed to be handled as well. To solve these problems, Map Components to existing code feature can be used. This feature basically allows to use the developer coded composable which can do the functionalities that are hard to describe in Figma. The mapping file name must match with that of the UI Package folder for the component it replaces, <<component_package.json>>, mappings/search_bar.json in this case. This file will map ui-packages/search_bar to the hand-rolled composable. { " target ": " KurashiruSearchBar ", // name of existing composable " package ": " com.kurashiru.ui.textfield ", // package directory of existing composable " generateImplementation ": true , " generatePreviews ": true } This way, the generated code can be customized and composables that operate the required functions can be created. 🎉 Review of Relay: thoughts on it’s practical usage Still in alpha, Relay is a tool that is stable and functional. It does as it promises, seamlessly produce Compose code from Figma designs. Using the versioned design system components in Figma as the source of truth is a valuable approach from a design system standpoint, necessitating a rigorous process for designers who collaborate with the system. It requires more communication between designers and developers to settle on things like naming of parameters and to be able to deliberate with interaction & accessibility handling, which may require modification of the existing design system. Kurashiru has a stable design system which is being used for both Android and iOS. Since Relay doesn't support iOS, adapting the design system exclusively for Android would be an extra effort. The generated component is tightly coupled with Figma design system and allowing for effortless one-click updates to the UI packages. However, this tight coupling may pose challenges when conducting AB tests. It definitely is faster than building of simple components by hand but as there are limitations to it, coding the complex components by hand and mapping to those generated components will become a necessity. To wrap up my thoughts on Relay, it is a pretty dandy tool. However, its limitations, mostly stemming from its early stages, mean that its benefits depend on the size and composition of the mobile team that inhabits it. For now, Relay feels like more effort than it’s worth to Kurashiru. But hey! Since it is introduced as part of Modern Android, I would like to wait to see its growth and expecting Relay to grow into a tool that bridge design-to-code without the need of extra works. Of course these are just my thoughts and words on trying it out, so, do not take these words and try it out! See you in the next articles 👋🏼 References 📚 developer.android.com codelabs.developers.google.com
アバター
iOSDCについて iOSDCは 公式ページ によると、 iOSDC Japan 2023はiOS関連技術をコアのテーマとしたソフトウェア技術者のためのカンファレンスです。 と紹介されており、日本最大級のiOS関連のカンファレンスと知られています。 iOSDC 2023では1,409枚のチケットが発行され、非常に多くの方々が参加されました。 このカンファレンスは9/1から9/3の3日間、オフラインとオンラインのハイブリッド形式で開催されました。 発表した内容 当日は私もLT枠で" ShazamKitの魔法を解き明かす: 音楽認識技術「オーディオフィンガープリント」の探検! "というタイトルで発表を行いました。 当日の資料は以下からご覧いただけます。 speakerdeck.com このテーマは業務とは直接関係ありませんが、社内勉強会用にまとめた内容をもとに応募しました。 iOSの技術とは直接関連しない音楽認識技術に関する話だったため、反応が気になりましたが、Twitterやニコ生のコメントを見ると多くの方に楽しんでいただけたようです。 iOSDCでは、他にも音楽関連の発表があり、自動作曲や空間オーディオなどの興味深いトピックについて学ぶことができました。 社内での共有会 カンファレンス終了後、dely社のiOSエンジニアたちが集まり、印象的だった発表についての共有会を開催しました。 聴講した発表をもとに自社サービスに使えそうなもの・単純に面白かった内容について話し合いました。 特に盛り上がりましたスライドを以下に紹介します。 speakerdeck.com speakerdeck.com speakerdeck.com speakerdeck.com 他にも興味深い発表が多く、上に挙げた以外の発表も含め社内のiOSエンジニアで盛り上がりました。 終わりに iOSDCでの登壇と、社内での共有会について紹介しました。 年に一度の大きなカンファレンスなので、社内でも多くの興味と議論が湧き上がりました。 WWDCの後にも同様の共有会を設けましたが、今後もこのような機会を増やしていきたいと思います。 最後に、カンファレンスの運営を支えてくださった皆さま、スポンサーの皆さまに心からの感謝を申し上げます。 次回もこのような機会があれば、積極的に参加したいと思っております。
アバター
Hello, I'm Allan, a Server-Side Engineer at Kurashiru 🚀 While Kurashiru predominantly relies on MySQL, it's intriguing to explore the broader landscape of database management. Enter PostgreSQL, a robust contender, known for its powerful techniques of Sharding and Partitioning. Even if you're steeped in a different database system, understanding these strategies can be invaluable. This guide delves into the world of PostgreSQL, elucidating how to implement and monitor Sharding and Partitioning within a Dockerized Rails framework. It's a journey of ensuring agility and scalability, irrespective of your primary database preference. Let's dive in! 📘 Table of Contents Understanding the Strategies Sharding Why Use Sharding? Challenges When to use Sharding Partitioning Types of Partitioning in PostgreSQL Advantages Summary of key concepts Practical Implementation with Docker & Rails Sharding Partitioning Monitoring & Performance Insights The Power of pg_stat_statements Disk Usage Metrics Monitoring Connections Third-Party Monitoring Tools Always Test in Staging Parting Thoughts 🌐 Understanding the Strategies 1. Sharding : Sharding is essentially the practice of splitting your database into several smaller, more manageable pieces, and distributing them across a range of storage resources. Why Use Sharding? Distributed Data : With data distributed across servers, you can leverage the power of multiple machines. This ensures that as your data grows, you can keep adding servers to handle the load. Isolation : By isolating data, you ensure that issues affecting one shard (like an overload or crash) won't cripple your entire system. Challenges : Operational Complexity : Maintaining multiple shards, ensuring data consistency, and handling backups can be challenging. Cross-Shard Queries : Aggregating data or joining tables across shards can be complex and slower. When to use Sharding : When data is too large to fit efficiently on a single server. When you have high transaction rates that require distributed processing. Geographic distribution requirements. 2. Partitioning : Partitioning divides a table into smaller, more manageable pieces, yet the partitioned table itself is still treated as a single entity. Types of Partitioning in PostgreSQL : Range Partitioning : Rows are partitioned based on a range of values. For instance, orders from different months can be stored in separate partitions. List Partitioning : Rows are partitioned in specific predefined categories. E.g., sales data can be partitioned by country. Hash Partitioning : Rows are partitioned using a hash function on the partition key, ensuring even data distribution. Advantages : Improved Query Performance : Data retrieval can be faster since scans are limited to specific partitions. Maintenance : Older data can be purged without affecting the rest of the table by simply dropping a partition. 3. Summary of key concepts : The table below summarizes the significant differences between sharding and partitioning for your reference. We will explain these terms in detail further on. Dimension Sharding Partitioning Data Storage On separate machines On the same machine Scalability High Limited Availability High Similar to unpartitioned database Number of Parallel Queries Depends on the number of machines Depends on the number of cores in the single machine Query Time Low Medium to Low Vertical partitioning Vs Horizontal partitioning (sharding) 🛠 Practical Implementation with Docker & Rails 1. Sharding : Setup : Create multiple PostgreSQL instances using Docker Compose, each representing a shard. docker-compose.yml : version : '3' services : primary_db : image : postgres environment : POSTGRES_USER : user POSTGRES_PASSWORD : password POSTGRES_DB : app_development shard_one_db : # ... similar to above ... shard_two_db : # ... and so on ... database.yml : development : primary : adapter : postgresql database : app_development # other settings... shard_one : adapter : postgresql database : app_shard_one # other settings... # ... and so on ... Model Connection : By default, models connect to the primary. For specific models: class SomeModel < ApplicationRecord connects_to database : { writing : :shard_one , reading : :shard_one } end 2. Partitioning : Setup : Partition tables based on chosen criteria. Migration Example (Partitioning by Date): class CreatePosts < ActiveRecord :: Migration [ 6.1 ] def up create_table :posts , partition_key : :created_at do |t| t.string :title t.text :content t.timestamps end # Define a partition for 2023 execute <<- SQL CREATE TABLE posts_2023 PARTITION OF posts FOR VALUES FROM ('2023-01-01') TO ('2024-01-01'); SQL end def down drop_table :posts execute " DROP TABLE posts_2023; " end end 📊 Monitoring & Performance Insights The real worth of any database scaling strategy is determined by how it performs in the wild. To ensure your sharding or partitioning strategy is delivering its promise, you need robust monitoring and performance insights. 1. The Power of pg_stat_statements : This module provides a means to track execution statistics of all SQL statements executed by a server. Activation : Uncomment or add the following line in postgresql.conf : shared_preload_libraries = ' pg_stat_statements ' Usage : This view will contain rows of normalized query strings and associated statistics: results = ActiveRecord :: Base .connection.execute( " SELECT query, calls, total_time FROM pg_stat_statements ORDER BY total_time DESC LIMIT 10; " ) results.each do |row| puts " Query: #{ row[ ' query ' ] } , Calls: #{ row[ ' calls ' ] } , Total Time: #{ row[ ' total_time ' ] }" end This code fetches the top 10 queries with the highest total execution time, which helps in identifying slow-performing queries. 2. Disk Usage Metrics : As data grows, monitoring the disk space becomes crucial, especially when using partitioning, as each partition can grow at different rates. Checking Table and Partition Sizes : sizes = ActiveRecord :: Base .connection.execute(<<~ SQL SELECT nspname || '.' || relname AS "relation", pg_size_pretty(pg_total_relation_size(C.oid)) AS "size" FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) WHERE nspname NOT IN ('pg_catalog', 'information_schema') ORDER BY pg_total_relation_size(C.oid) DESC LIMIT 10; SQL ) sizes.each { |row| puts "#{ row[ ' relation ' ] } - #{ row[ ' size ' ] }" } This snippet fetches the top 10 relations (tables/partitions) based on their size. 3. Monitoring Connections : With sharding, connections can exponentially grow as you connect to multiple databases. Use the following to monitor active connections: active_connections = ActiveRecord :: Base .connection.execute( " SELECT datname, count(*) FROM pg_stat_activity GROUP BY datname; " ) active_connections.each do |row| puts " Database: #{ row[ ' datname ' ] } , Active Connections: #{ row[ ' count ' ] }" end 4. Third-Party Monitoring Tools : There are a myriad of third-party tools that offer extensive PostgreSQL monitoring capabilities: pgBadger : An advanced PostgreSQL log analyzer. Datadog : Offers PostgreSQL integration to monitor performance and health. New Relic : Their PostgreSQL monitoring integration provides insights into slow queries, busy times, and more. 5. Always Test in Staging : Before pushing any changes, especially related to database scaling, always test in a staging environment. This environment should mirror your production setup. Tools like pgbench can help simulate load on your database, allowing you to gather performance insights. 🌟 Parting Thoughts Whether sharding or partitioning, the choice hinges on your application’s needs. If you anticipate vast datasets with geographical dispersion, sharding can be a boon. However, if your dataset is massive but localized, partitioning might suffice. In either case, a well-architected Rails app, coupled with PostgreSQL's robustness and Docker's encapsulation, can stand tall against scaling challenges and hope this article find it helpful for those who are considering sharding or partitioning. 📚 References planetscale.com edgeguides.rubyonrails.org blog.bytebytego.com Sharding vs. Partitioning Demystified: Scaling Your Database
アバター
はじめに クラシルリワードについて データ基盤の全体構成 BigQueryの選定理由 データ基盤における重要な役割 アプリのイベントログのscan量削減 DynamoDBやRDSをBigQueryにSync Snowflakeの活用 BIツール(Redash, Looker) Redash Redash repositoryのフォルダ・ファイル構成 git管理下のSQLクエリをRedashに反映 Lookerについて さいごに はじめに こんにちは!クラシルリワードで開発責任者をしているfunzinです。 この記事ではクラシルリワードのデータ基盤の構成について紹介していきます。 クラシルリワードについて はじめにクラシルリワードのサービス概要について紹介させてください。 クラシルリワードは「日常のお買い物体験をお得に変える」アプリです。 買い物のためにお店に行く(移動する)、チラシを見る、商品を買う、レシートを受け取る......。これら日常の行動がポイントに変わり、そのポイントを使って様々な特典と交換することができます。 気になる方は サービスサイト をぜひご確認ください。 それではさっそく本題のデータ基盤の構成について説明していきます。 データ基盤の全体構成 データ基盤の全体構成 クラシルリワードのデータ基盤の技術スタックは下記になります。 DWH: BigQuery(メイン), Snowflake(サブ) ETL: Embulk, Digdag BI Tool: Redash, Looker 全体像のみではそれぞれの役割が把握しにくいため、次の章より詳細を説明していきます。 BigQueryの選定理由 クラシルリワードではメインのデータ基盤としてBigQueryを利用しています。 BigQueryを選定した理由としては下記となります。 データエンジニアが不在のため、フルマネージドなサービスを利用したい サービスの立ち上げ当初、新規事業ということもあり最小人数で開発をしていたためデータエンジニアが不在でした。 しかし、施策結果を振り返るためにイベントログ実装は必須であり、データ基盤を構築する必要がありました。 また自社データ基盤の場合、イベントログのスキーマ設計、エラー時の対応、データパイプラインの修正対応などに時間がかかることが懸念としてありました。 そのためフルマネージドなBigQueryを採用することで、データ基盤の構築にかかる時間を短縮しサービス開発にリソースを割くことができると判断しました。 2. Google Analytics for Firebaseとの相性が良い クラシルリワードは 現在 iOS / Android アプリでサービスを提供しています。 モバイルアプリでは Firebase を利用することが一般的であり、クラシルリワードでもFirebaseを積極的に利用しています。 Google Analytics for Firebase を利用することで下記のメリットがあります。 BigQuery Export を利用することで、GAに送信されたイベントログが1日一回定期実行でイベントログのテーブルとしてBigQueryに自動でエクスポートされるため自前でイベントログのデータパイプラインを作成する必要がない ログのスキーマ が事前に決まっているため、開発者は event_name , event_params など最低限のものだけを意識すれば良い アプリ側ではFirebase Analyticsのライブラリが提供されているため、下記のように送信処理を書くのみで完結する iOSでのイベント送信例 // event_name: test_event // event_params: ["index": 0] Analytics.logEvent( "test_event" , parameters : [ "index": 0 ] ) インフラはAWSで構築しているためAWS Athena、クラシルで利用しているSnowflakeなども検討しましたが、Google Analytics for Firebaseを利用するメリットが大きいためBigQueryを採用しました。 データ基盤における重要な役割 この章ではクラシルリワードのデータ基盤において、重要な役割を抜粋して説明していきます。 アプリのイベントログのscan量削減 BigQueryの選定理由でも説明しましたが、 アプリからイベントログを送信するためにGoogle Analytics for Firebaseを利用しています。 日付別テーブルがBigQueryにエクスポートされるため、これだけでもイベントログ分析は可能になります。 しかし、そのまま日付別テーブルを利用する場合、DAUが増えるにつれて、BigQueryのscan量が増加しコストに影響してきます。 例えば起動イベントのみを抽出する場合、日付別テーブルを利用するとwhere句で条件を指定してもその範囲の日付全てのイベントが対象となり、パーティション分割テーブルと比較してscan量が多くなってしまい、結果的にコストに影響します。 日々分析をする上でのscan量の課題を解決するために、下記のような工夫をしています。 パーティション分割テーブルを利用 BigQueryにエクスポートされた日付別テーブルを直接利用するのではなく、パーティション分割テーブルとして変換して分析ではこちらを利用するようにしています。 下記がパーティション分割テーブルを作成するサンプルクエリです。 CREATE OR REPLACE TABLE `product_firebase_analytics.events` PARTITION BY DATE (event_timestamp_micros) CLUSTER BY event_name, new_event_name, platform AS SELECT event_date, TIMESTAMP_MICROS(event_timestamp) AS event_timestamp_micros, ARRAY_TO_STRING( [event_name, ( SELECT value.string_value FROM UNNEST(event_params) WHERE KEY = ' id ' )], " _ " ) AS new_event_name, platform, ... -- カラムが続く FROM `project.events_*` パーティション: Date クラスタ: event_name, new_event_name(後述), platform パーティション分割テーブル作成後は クエリスケジューリング を利用し、毎日定期実行で指定範囲の日数を上書きするような形でイベントログをアップデートしています。 -- 直近3日以内のイベントログを上書きする DECLARE start_date_interval INT64 DEFAULT 3 ; DECLARE end_date_interval INT64 DEFAULT 0 ; DELETE FROM `product_firebase_analytics.events` WHERE DATE (event_time, ' Asia/Tokyo ' ) BETWEEN DATE_SUB( CURRENT_DATE ( ' Asia/Tokyo ' ), INTERVAL start_date_interval DAY ) AND DATE_SUB( CURRENT_DATE ( ' Asia/Tokyo ' ), INTERVAL end_date_interval DAY ); INSERT INTO `product_firebase_analytics.events` SELECT event_date, TIMESTAMP_MICROS(event_timestamp) AS event_timestamp_micros, ARRAY_TO_STRING( [event_name, ( SELECT value.string_value FROM UNNEST(event_params) WHERE KEY = ' id ' )], " _ " ) AS new_event_name, platform, ... -- カラムが続く FROM `project.events_*` WHERE REGEXP_EXTRACT(_TABLE_SUFFIX, r ' [0-9]+ ' ) BETWEEN FORMAT_DATE( " %Y%m%d " , DATE_SUB( CURRENT_DATE ( ' Asia/Tokyo ' ), INTERVAL start_date_interval DAY ) ) AND FORMAT_DATE( " %Y%m%d " , DATE_SUB( CURRENT_DATE ( ' Asia/Tokyo ' ), INTERVAL end_date_interval DAY ) ) 2. new_event_nameの導入 Google Analytics for Firebase の仕様上、イベント名は最大500までしか定義できません。 カジュアルに新規イベントを定義しすぎると上限の500に到達してしまい、新規イベントの計測ができなくなります。 イベント数の上限が500を超えないために、イベント名を一意にするのではなく、 event_params にイベントを識別できる id(STRING) を含めて、パーティション分割テーブル作成時に new_event_name として定義しています。 例えば下記のようなイベント名とIDの場合、 new_event_name はtest_hogeになります event_name: test event_params.id: hoge new_event_name: test_hoge new_event_name はパーティション分割テーブル作成時に event_name と id を結合することで、新しいイベント名として定義することで実現しています。 ARRAY_TO_STRING( [event_name, ( SELECT value.string_value FROM UNNEST(event_params) WHERE KEY = ' id ' )], " _ " ) AS new_event_name new_event_name によって、下記のようなメリットが得られます。 event_name はtest, imp, tapなど汎用的なイベントのみを定義して使い回すため、イベント名の定義数で500を超えることがなくなる クラスタに new_event_name を指定しているため、分析する側は new_event_name を条件に指定することでscan量を削減することが可能 DynamoDBやRDSをBigQueryにSync クラシルリワードではインフラ環境をAWSで構築しているため、ユーザーのデータはRDSやDynamoDBに保存されています。 これらのデータをアプリから送信したイベントログと結合してBigQueryで分析をしたいニーズがでてきます。 現在は Digdag と Embulk を利用して、1日1回定期実行でBigQueryにSyncをしています。 これらはAWS ECSの タスクスケジューリング で実現しています。 多くのテーブルは全体更新をしていますが、一部テーブルはレコード数が多くなっているため差分更新をしています。 差分更新の対応については説明が長くなってしまうため、また別のテックブログで話せればと思います。 運用面に関しては、現状大きな課題はないもののバッチ処理が失敗した時の調査やリトライなどの運用工数もかかり始めてはきているので、自前のETLフローではなく trocco や Fivetran などのSaaS ETLツールも検討しています。 Snowflakeの活用 メインのデータ基盤はBigQueryですが、一部で Snowflake を利用しています。 Snowflakeを利用することになった背景は下記2つです。 クラシルのデータをクラシルリワード開発部で分析可能にするため 現状dely社では各事業部ごとに技術選定をしています。 クラシルではAWS Athena, Snowflakeを利用してデータ分析をしています。クラシルリワードでは今まで説明してきましたがBigQueryをメインで利用しています。 しかし、事業部が分かれているため、クラシル側のデータを分析するときに何か問題が発生するとクラシルのデータエンジニアに都度依頼する運用コストが発生していました。 この課題を解消するために、クラシルリワード側でSnowflakeをアカウントを作成し、Snowflakeの データシェアリング を利用することで、クラシルリワード開発部でクラシルのデータを自由に分析することができるようにしています。 2. 一部のクラシルのテーブルをBigQueryにエクスポートしたいため クラシルリワードでは クラシルチラシ にまつわる情報もユーザーに提供していますが、チラシ情報のテーブルがクラシル側にあるため、BigQueryで分析を完結することができません。 例えば、クラシルリワード側でどの店舗のチラシを見られたかを分析したい場合、どうしてもBigQueryに店舗のマスターデータが必要になってきます。 そのためクラシルチラシのデータ(e.g. 店舗情報)をクラシルリワード側のBigQueryに格納するために下記のようなフローを構築することで実現しています。 Snowflake(クラシル) -> Snowflake(クラシルリワード) -> Google Storage(クラシルリワード) -> BQ(クラシルリワード) クラシルでのデータをBigQueryにエクスポートすることで、クラシルリワード側ではBigQueryのみで完結してチラシ情報を分析することが可能となっています。 BIツール(Redash, Looker) 最後にBIツールについて説明します。 Redash 現状はセルフホスティングで Redash 用のEC2インスタンスを立てて、SQLクエリを書いて分析をすることが多いです。 しかしSQLクエリは人によって書き方が異なる、数値出しの判断が間違える可能性があるため、SQLクエリをGitHubのRepositoryで管理してレビューをする体制をとっています。 こちらはバイセルさんの Redashのクエリ管理方法 を参考にしています。 Redash repositoryのフォルダ・ファイル構成 フォルダ構造 ├── README.md ├── csv // クエリ名などをcsv管理 ├── queries // クエリ管理 ├── requirements.txt └── scripts // Redashに反映用のスクリプト csv name,data_source_id,query_id query_test_name,1,29 CSVではクエリ名、データソースID, クエリIDのみを最低限管理しています。 git管理下のSQLクエリをRedashに反映 PRがマージされたタイミングでGitHub Actionsを実行し差分があるクエリのみをRedashに反映させるようにしています。 GitHub Actions ステップ実行順 変更があったクエリの差分抽出( write_diff_query_only.sh ) Redashに反映( main.py ) write_diff_query_only.sh #!/bin/bash # write_diff_query_only.sh # PR差分のみをcsv_query_list.csvに反映するスクリプト header = `head -n 1 csv/query_list.csv` current_commit = $GITHUB_SHA previous_commit = $( git rev-parse " ${current_commit} " \^ 1 ) query_ids = `git diff --name-only " $current_commit " " $previous_commit " | grep queries | grep -o ' [0-9]\+ ' | tr ' \n ' ' | ' ` query_ids = ${query_ids % | } echo " $header " > csv/diff_query_list.csv if [ ! -z " $query_ids " ]; then grep -E " ( $query_ids )$ " csv/query_list.csv >> csv/diff_query_list.csv fi # main.pyで差分のみを上書きして実行するため cat csv/diff_query_list.csv > csv/query_list.csv main.py # main.py import os.path from redash_client.client import RedashClient import csv import os class Query : def __init__ (self, base_path, name, data_source_id, query_id, schedule= None , description= 'generated by retail-redash-query' ): self.query_id = query_id self.name = name self.data_source_id = data_source_id self.schedule = schedule self.description = description path = 'queries/{0}.sql' .format(query_id) self.load_query(base_path, path) def load_query (self, base_path, path): with open ( '{0}/{1}' .format(base_path, path), 'r' ) as f: self.query = f.read() # Read query list def get_queries (): reader = csv.reader( open ( 'csv/query_list.csv' , 'r' )) queries = [] # Skip header header = next (reader) for row in reader: queries.append(Query( "./" , row[ 0 ], row[ 1 ], row[ 2 ])) return queries RedashClient.BASE_URL = os.environ[ 'REDASH_HOST' ] RedashClient.API_BASE_URL = os.environ[ 'REDASH_HOST' ] + "/api/" api_key = os.environ[ 'REDASH_API_KEY' ] redash_client = RedashClient(api_key) # Update queries for query in get_queries(): print ( 'QueryID: ' + query.query_id) try : redash_client.update_query( query.query_id, query.name, query.query, query.data_source_id, query.description ) except RedashClient.RedashClientException: print ( 'Error: ' + query.query_id) 現状は差分があるクエリのみをRedashに反映するように影響範囲をとどめています。 Lookerについて RedashではSQLクエリを書くというハードルが残り続けるため、SQLクエリをかけない非エンジニアでも分析が可能な体制にするため、現在は並行して Looker にも移行中です。 さいごに クラシルリワードのデータ基盤について紹介しました。 データエンジニアが不在の中でも、分析基盤を構築しサービス改善につなげることができました。 引き続きデータを分析することでクラシルリワードを改善していきたいと思いますので、よろしくお願いします。
アバター
Hi, I am Akane, a UI/UX designer at Kurashiru’s search team. This time I would like to talk about how our search team uses the UX mapping methods to organise, identify and prioritise our tasks from the product’s perspective by using “User story mapping”. 1. To identify the users’ goals. At this step, we will decide what’s our user’s goals. 2. Map users’ activities. List out key Activities, Steps and Details that are involved in the search function. By viewing the mapping, our team members could have a clear vision of each key activity, and follow up with steps users take during each activity and details. 3. Organise tasks for future release We have summarised all tasks that are possible to get improved for feature releases from left to right. Before discussing with PdM, I listed each potential task along with current screenshots as well as some detailed notes that could help us to visualise tasks. After discussing this mapping with PdM, we “finalised” tasks by each phase and priority. Because we are using Figjam’s sticker function, it is very easy for us to adjust the priority of each task, remove un-needed ones and add new ones as well. Here is an example of how we bring an “easy-to-use” search experience to our users. Before discussing with PdM, I will write down which area I would like to improve (WHAT), the reason (WHY), and how I plan to do it (HOW.) Here is the summary of why we decided to add the search bar to the home screen. Kurashiru currently has two types of users: Type 1: users who already know what they are looking for (search for ingredients that they already have in the fridge or at home, shopping before) User action: open app → input query from the home search bar → view result Most of Kurashiru's loyalty users are in this category. Type 2: users who don’t know what they are looking for, just want to browse some ideas (based on the ideas, they will go buy these essential ingredients, shopping after) User action: open app → Access search top feed → browse recommended keywords or content list → input query or click recommended keywords → view result These 2 types of users' needs and requirements are different, so we want to create the best user experience that would cover both our user's needs (create 2 different entry points for different user groups). For those Kurashiru’s loyal users, they need to take 2 steps in order to reach the search bar. After the UX adjustment, they could access the search bar once they open our app, imagining you try to find a recipe after a busy day of work or taking care of kids at home every day, this small UX improvement could save our user half of the time every day. By adding the “search bar” to the home feed, after the release, the data proves that most of our users are choosing to use the “search bar” on the home feed. After a certain period, the number of users who use the “home search bar” and “search tab” are showing a stable trend. So, here is how our team prioritise and manages our tasks by using the UX mapping method. In the end, these methods are just theories, we need to adjust how we will use them to match our team's needs. Thank you for reading, see you next time. :)
アバター
Hello, my name is Niko, and I am currently working in Kurashiru's data enabling team as a newly joined data engineer. While I'm enthusiastic about learning Japanese, my proficiency with Japanese particles is still a work in progress (笑). For this reason, I have decided to write this blog in English. Preface At Kurashiru, we use dbt (data build tools) as our platform to handle data transformations from the data lake to our data warehouse. dbt is a helpful set of tools and frameworks that let us create transformation queries in line with software engineering principles. Gone are the days of dealing with complicated queries on our data platform. With dbt, we can manage and "engineer" our data just like regular software development. Since we started using dbt, our productivity in data modeling has significantly improved, which we find very beneficial. dbt also provides us with useful tools like "slim CI" for Continuous Integration. In this blog, I'll explain what SLIM CI is and why it's essential for our workflow. If you want to know more about dbt, you can explore the official documentation here: docs.getdbt.com dbt CI Job In Continuous Integration (CI), the process involves automatically building and testing new code whenever a developer tries to merge it into the main repository. This principle applies to dbt too. When a developer makes a Pull Request (PR) in a dbt repository, it's essential to follow CI practices. Doing so helps us avoid broken queries in our Production database and maintain good code quality. Now, let's see the flow of CI job that dbt performs using an illustration: dbt's CI Job Let's imagine the Master branch codes are linked to the Production environment. When a Pull Request (PR) is made, the CI JOB will duplicate the existing data model from Production to a temporary environment. Then, it will build and test the copied model along with the changes from the PR. So far, this process works fine for application code because it usually requires only a small amount of data for building and testing new codes. However, the problem arises when dealing with data models in data modeling works. It demands a significant amount of data and incurs high data processing costs for just one PR. Therefore, we need to find a way to improve this process and reduce the overall expenses. Slim CI Luckily, dbt offers us SLIM CI, which plays a crucial role in this situation. When we switch from the default CI Job to SLIM CI, our CI workflow transforms into the following: dbt's Slim CI Job In the new workflow, the CI Job doesn't have to copy all existing models from the Production environment for building and testing. Instead, it only creates the data model that has changed in the PR and refers to other required dependencies (like source tables or views for JOIN, UNION) already present in the production environment. This way, the process becomes much faster and more cost-effective since we only create what is necessary. dbt's SLIM CI achieves this by using a built-in powerful feature called "defer." With "defer," a single model can be built without the need to rebuild all its dependencies in the CI temporary environment. This efficient approach saves time and resources, making the whole data modeling process smoother and more efficient. To Defer or Not Defer So, what is "defer"? To grasp the concept of "defer," let's first understand how dbt functions. To build our data models using dbt, we typically run this command # first run dbt build Under the hood, dbt will do something like this: dbt command under the hood When we examine the final step of the dbt process, we notice that dbt always produces artifacts. These artifacts represent the most recent state of the current data models in the target database or data warehouse. Now, how does "defer" come into play? Well, "defer" uses these artifacts to check if there are any differences between the dbt runs and the existing state of the target database or data warehouse, kind of like a DIFF logic. To enable "defer" in dbt, we can include some flags to the previously run first run command, which looks something like this: # second run # --select state:modified # means only select changed states compared to the previous state dbt build --select state:modified --defer --state path_to_first_run_state With this approach, dbt constantly checks the previous state and only builds models that have been changed from the successful previous run of dbt. When we include this command in a Job that triggers when a Pull Request (PR) is requested, dbt refers to it as "SLIM CI". Example Imagine we work at a beef bowl restaurant, and our responsibility is managing the restaurant's data. In the data warehouse's production environment (DBT_PRD), we have two models named meat and rice : Model in our production datawarehouse We want to create a new model called "meat_rice_menu" by combining the data from the two existing models, meat and rice . This new model will be represented in a chart like this: meat_rice_menu model default CI Job Let's proceed with the PR and use this model plan. First, we'll run the regular dbt CI Job without defer/SLIM CI. During this process, dbt will create a new temporary schema for each PR that is being tested. Fortunately, dbt will always log the activities of the run. Here's the completion message from dbt's CI logs: Completion status dbt CI job There, we can see that dbt made 2 table models and 1 view model. To make sure, let's look at our current temporary PR schema. When we check the temporary PR schema, we'll see that it did create all three models, both the parent and child models: CI without defer When we take a look at the definition, we can see that the newly created model was referencing the temporary schema: Newly created model referencing temporary models. SLIM CI Job Now, let's try the PR again, but this time, we'll use defer/SLIM CI. We already have artifacts that show the current state from the production's dbt Job runs. When we run the PR job with defer/SLIM CI, let's look at the same finished log message that dbt always creates for every run: Completion status dbt SLIM CI job We notice that it only created one view model. When we check the PR's temporary schema, we see that only the new model is created, which is meat_rice_menu and it didn't duplicate the existing tables: CI Job with Defer (Slim CI) When we see the definition again, we can see that now it referencing the production environment (DBT_PRD): Newly created model referencing production's models. By referring directly to the production models instead of copying them, we can greatly reduce the costs of our PR. This approach avoids unnecessary duplication, and we only focus on building and testing the changes needed for the new model. As a result, the process becomes more efficient and cost-effective. Summary Slim CI is a powerful dbt feature that can save time and reduce costs during the build and testing of models in the CI workflow. By using defer, Slim CI can identify differences between the current data warehouse state and the changes in PR requests. This boosts our productivity since we don't have to wait long for PRs to be built and tested before merging them to the master branch. Today, I introduced one of the powerful features of dbt in this blog. However, we don't want to stop there. In line with our commitment to continuous improvement (KAIZEN) and one of our value which is good to great , we aim to enhance our data infrastructure further. Thank you for reading this blog, and stay tuned for more data engineering adventures ahead! References Continuous integration in dbt Cloud | dbt Developer Hub Defer | dbt Developer Hub Using dbt Deferral to Simplify Development | Infinite Lambda http://careers.dely.jp careers.dely.jp
アバター
もうすぐ8月で猛暑も超えて酷暑の季節になりましたね🥵  毎日エアコンで涼みながら最近はEMとして仕事している「みうら」です。お久しぶりです。 前回私の書いたブログ記事では、採用活動に関わっていたと記載していました。 転職活動や選考フローへの参加は自身でも行ったことはあるのですが、実は採用活動を業務の中心として活動した経験はなく、初めての経験でした。 そんな採用ビギナーな私でしたが、前期では私が関わった採用活動で数名エンジニアがありがたいことにジョインしてくれました🙌 ということでその中途採用活動をする際に行ったことを一部紹介しようと思います。 エンジニア採用の方法 これを見ている方はエンジニアだと思うので大体イメージついている方は多いと思いますが、少しエンジニア採用の方法を紹介します。 弊社では主に大きく分けると3つの方法で行っています。 ダイレクトリクルーティング Findy/Wantedly/Green/forkwell…といった転職マッチングサービス。 サービス上で企業が候補者を探してメールのやり取りをするサービス。 会社と個人を直接繋いでやり取りできるのが特徴。 転職エージェント レバテック/Geekly/リクルート…といった転職エージェントサービス/企業。 会社と個人の間に仲介で入ってくれることで、会社と個人間のマッチングコストを担ってくれるのが特徴。 自社サイト採用/リファラル採用 自社のHPからの応募や、社員紹介から会社と個人を直接繋いでやり取りする、他社を挟まない形の採用。 コストが安かったり、リファラルだと入社後の期待値が合いやすいなどが特徴。 エンジニア採用に参加した時に感じた課題 採用に参加した時は右も左も分からず、まずは内定を取るために候補者の方々をリストアップし、いわゆる母集団形成から始めたのですが、進めていくうちに弊社の採用業務には以下のような課題があることに気づき始めました。 採用担当の残業が多い 求人票が整備されておらず記載内容がバラバラ 二次選考突破率が低い 採用の進め方が定型化されていない リクルーターからすると残業が多かったり業務フローが整っていないことはあるあるなのかもしれませんが、採用業務全体として工数観点での改善点があったり、フォーマットが揃っていないことで余計に工数が掛かっていたり、工数や業務フローを改善しなければより上の採用業務は行えない感触をその時は感じていました。 採用戦略の策定と改善 課題は多く見つかったので、あとはどう解決していくかです。 上記課題を踏まえて、以下を戦略として進めていました。 工数と時間削減 募集(母集団)を増やす 選考体験の改善 特に、エンジニア採用は工数がかなり掛かることがわかったので、 まずは工数と時間削減から改善することにしました。 業務としては内定を取らないといけないのですが、まずは既存の業務を改善しないと内定を得ることが難しいと強く感じていました。 上記の戦略を元に、前期の採用活動では多くの改善を行いましたが、紹介しきれないので、今回は戦略の内、「工数と時間削減」で行ったことについて紹介します。 工数と時間削減 採用には候補者1名に対してもかなり人と工数が掛かっています。 弊社では最終選考までに大体以下の工数が候補者1名辺りに掛かっています。 スカウター: 15分 リクルーター: 4時間 エンジニア3名: 4時間 マネージャー: 1時間 CTO: 1時間 約10時間!今回改めてまとめてみましたが、かなりの工数が掛かっていますね…!😰 内定までとなるともう3〜4時間ほどは工数が掛かることでしょう。 これを減らしたり、スムーズにするといった改善をすることは、とても重要で大きい話になってくるわけです。 候補者とのスケジュール調整簡略化 今までのフローだとメールのやり取りの途中で、eeasyという日程調整ツールをスカウターから採用チームに発行してもらわないといけなかったのですが、実はこれに問題がありました。 スカウト送信時に日程調整URLを送れず、候補者から面談参加希望をもらった後に発行するため、メールやり取りに時間が掛かる 日程調整URLを発行するためだけで採用チームに依頼が必要となり、時間が掛かる 日程調整に採用チームを挟むことで無駄な工数と時間がかかっていたということです。ここはスカウト送信と同時に日程調整URLを送ることで改善できないかと考えていました。 改善のためにeeasyのプランを変えたり他のツールも検討したのですが、Googleワークスペースを契約していたこともあり、追加費用無しで使えることもあって、Googleカレンダーの予約スケジュールで対応しました。 support.google.com 細かい設定部分で融通が効かず、使いづらい部分もありますが、今の所問題なく使えており、スカウターが採用チームをまたずに素早く日程調整ができるようになっています🙌 エンジニアが送っていたスカウトをやめ、スカウターからスカウトを実施 ダイレクトスカウト媒体では、エンジニアが直接スカウトを送るほうがよいだろう、という判断の元、エンジニアがスカウトを送信するルールが有りました。しかし、ここにも問題がありました。 というのも、エンジニアは本来メールを書くのに慣れていないんですね。一筆メールを書くのにも時間が掛かりますし、スカウトが本来の業務でもない…スカウトメールを書くより開発したい思いがありスカウトメール作成も上達しない…ということで、廃止しました。 代わりにエンジニアは候補者のチェックに専念し、専門のスカウターがスカウトメッセージを送るように変更しました。 このおかげでスカウト通数を増やすことができ、エンジニア側の負担も減らせてwin-winで良かったと思います🙌 採用チームにコーディネーター職を定義 社内には採用担当としてリクルーター職が存在するのですが、そのリクルーター職がコーディネーター業務として、候補者との調整業務も行っていました。これも課題のポイントでした。 これが原因でリクルーター職の残業時間がとても多い状況となっていたり、本来行いたい改善業務に集中出来ない問題がありました。 これを改善するために、採用のアシスタントの方にコーディネーター業務の教育を実施し、リクルーターの調整業務をコーディネーター職に移管しました。 朝会15分の認識合わせをするだけで、リクルーター職の負担を減らし、リクルーター職が行うよりも候補者との調整業務をよりスピーディにするように変えました🙌 現在は体制が少し変わってしまったため、当時のコーディネーターの運用はそのままとはいかないのですが、現在もアシスタントの方はコーディネーター業務を活かしてもらっています。 まとめ 今回はエンジニアがエンジニア中途採用業務を担当するにあたって改善したことの一部を紹介しました。 エンジニア採用は難しく厳しい時代と言われていますが、逆に言うと社内も社外も正解がわかっておらず、そのわからない状況を正解に持っていく面白さがあると思います😄 私も採用活動は業務として行うのは初めてでしたが、数名エンジニア採用に繋がったなど、無事成果が出せてよかったと思います。 採用活動は関わる人数が多く、工数削減や業務フロー改善が効きやすい業務だと思います。これを読んだ方も参考に改善してみたり、ノウハウをシェアいただけると嬉しいです。 最後に 最後に、弊社ではSRE職を積極的に募集しています。 クラウドインフラ経験者や、AWSを経験したいサーバサイドエンジニアの方、良ければお話伺ってみませんか?高トラフィックサービスである、クラシルのインフラに興味あれば良ければお話しましょう!以下リンクから応募いただけると嬉しいです。 herp.careers 採用情報はこちらです careers.dely.jp
アバター
はじめに こんにちは〜。 クラシルでデータエンジニアをしておりますharry( @gappy50 )です。 これまで、クラシルではデータ基盤からのデータをアプリケーションや推薦システムへ活用するためにDWHにSnowflakeを導入し、様々な活用をしてきました。 tech.dely.jp その一方で、データ「分析」基盤としての利用を推し進めていくために現在はdbtでのデータモデリングを中心として、一貫性のあるデータ分析が行えるような土台作りに注力しています。 今回は、Snowflakeのコスト監視やdbtのモデリングのコストの監視・可視化ができるdbt-snowflake-monitoringについて簡単にご紹介したいと思います。 select.dev 現在のクラシルの分析における課題 クラシルでは高速で開発・検証をするためにSQLをみんなで書ける文化が根付いている反面、BIツールであるRedashに様々なKPIや指標が散在するためデータカオスが生まれておりました。 そのため2021年からdbtを導入し、必要なデータマートやマスタ関連のデータ整備をしていました。 tech.dely.jp それから2年後、高速で開発・検証を進めた結果、dbtでもデータカオスが生まれはじめそうな気配が漂ってきました!(なんてこった 分析で使われているのかわからないテーブル、定期実行しているクエリの実行コストがかさんできた、dbtのbuildに時間がかかりはじめているけど消していいのか、など… データエンジニアでよくある課題だと思うのですが、テーブルやパイプラインの棚卸はコスト死を防ぐためには死活問題です。 日々の運用クエリはここ1〜2年間でどうにか最低限のお掃除はできるものはあるものの、不要になったリソースを特定し利用者と合意を取るためにはそれなりの時間と根性と思い切りが必要です。 また、そのような監視は分析ワークロードだけでなくdbtから実行されるELTワークロードに対しての監視までを行う必要があるため、 監視対象はdbtのモデルが増えれば増えるほどELTと分析の両側面からの棚卸が必要になります。 dbt-snowflake-monitoringの導入 そのような複雑なワークロードの監視を簡単にしてくれる最高のツール、あります。 Snowflakeのコスト監視SaaSを提供しているSELECT社のOSSであるdbt-snowflake-monitoringです。 SaaS版のSELECTはWeb上からLooker統合やSlackへの通知機能等もあるのでかなり魅力的ですね。 OSS版のdbt-snowflake-monitoringはdbtとSnowflakeに特化しているものの、我々のワークロードのほぼほぼを監視できるので導入をするだけで即時メリットがあります。 導入は以下のQuick Startの手順に従うだけなのでそれほど難しくないです。 select.dev 導入が完了したら dbt build --select dbt_snowflake_monitoring を実行するとdbt-snowflake-monitoringのモデルがデプロイされます。dbt Cloudの場合はdbt jobに定義をしておいて定期的に更新できるようにしておくのもよいでしょう。 dbt-snowflake-monitoringのプロジェクトはSnowflakeのaccount_usageなどの情報からコスト監視用のモデルを作成してくれる デプロイされたモデルは以下のような使用方法ができます! select.dev 毎月の請求額をサービスごとに算出したり 請求額の推移を可視化できる 毎月のウェアハウスごとのコストを可視化できるのはもちろんのこと 直近クエリが実行されていないテーブルをテーブルサイズ順に確認できるので不要になってるテーブルの棚卸もコストパフォーマンスがよいものから棚卸できますし 直近コストがかかっているクエリの特定もすぐにできます。 さらには、これが強いのですがdbtのモデルごとのコストの監視もできます。 query_base_table_access のクエリ実行回数とあわせて監視すると不要なデータモデリングのお掃除も楽になりそうですね! さいごに いかがでしたか? dbtとSnowflakeを利用しているのであればまず入れてみるのはいかがでしょうか? 我々クラシルのデータチームは、今年度からは分析の利便性を向上させるためにパワーが出せる状態になっているので、これまでの負債を解消しながら現在はこのようなコスト監視や、立ち向かえていなかったデータモデリングやデータカタログの充足化、Lookerの利活用など新たな分析基盤のための土台作りに注力をしてます。 全員がSSOTのデータやロジックで意思決定をしはじめて、正しい意味で高速で開発・検証ができる状態を目指して日々奮闘していきたいと思います。yatteiki 🦵 :takeshi_arigato: https://careers.dely.jp/ careers.dely.jp
アバター