TECH PLAY

エス・エム・エス

エス・エム・エス の技術ブログ

256

エス・エム・エスのエンジニアの宮坂です。2022年1月に入社し、以来、介護事業者向け経営支援サービス「カイポケ」の リニューアル開発 に携わっています。 今回は私が開発を担当している「介護報酬の算定ロジック開発」についてお話したいと思います。 介護報酬とは何か そもそも介護報酬とは何でしょう? 高齢になったり病気になったりして、周りのサポートがないと生活が難しい状態になってしまった場合、介護サービスを受けることになります。介護サービスは、提供したサービス内容や介護の必要度合いなどに応じて金額が定められていて、これを介護保険(一部、利用者負担)から介護サービスを提供した事業者に支払われます。これが介護報酬と呼ばれるものです。 介護保険は、法律によって40歳以上になると誰もが加入することになり、その保険料は毎月の給与などから引かれているので、介護を受けていない人も含めて介護サービスを支えていることになります。 難解な介護報酬の算定 この介護報酬、ちゃんと計算して請求しないと介護サービスを提供している事業者は収入を得られないので事業を続けられず困ってしまうのですが、この算定がすごく難しいのです。 提供される介護サービスにはそれぞれ「単位数」というものが定義されていて、提供した条件によってはさらに単位数が加算されます。その計算のルールが算定構造として国から提供されており、それをもとにロジックを組み立てていきます。こうして、算定した単位数に、地域ごとに定められた「地域単価」を掛けることで、介護報酬が決まります。(介護報酬 = 単位数 × 地域単価) 例えば、ある介護サービスの利用者に 「身体介護」という訪問介護のサービスを20分提供した 訪問は2人で行った その提供時間は「早朝」だった とします。このときの地域単価が10円の場合の介護報酬は、 身体介護 20分以上30分未満 = 250 単位 … ① 2人の訪問介護員等による場合 = ① × (200/100) = 500 単位 … ② 夜間もしくは早朝の場合 = ② + ② × (25/100) = 625 単位 … ③ 介護報酬 = ③ × 10 = 6,250 円 となります。このうちの利用者の負担額が1割とすると、 介護保険への請求額 = 6,250 × (1 - 0.1) = 5,625 円 利用者への請求額 = 6,250 - 5,625 = 625 円 となり、介護サービス事業者は、介護保険から 5,626 円、介護サービスの利用者から 625 円を受け取る計算になります。 (介護保険の自己負担は所得に応じて1割から3割までのいずれかとなります) 図1:訪問介護費の算定構造の一部 https://www.wam.go.jp/gyoseiShiryou-files/documents/2022/0322211552821/202203a.pdf (赤囲み線は引用者) (2023年3月3日 閲覧) こういった単純なパターンであればそこまで難しくありませんが、介護報酬を算定する際には様々な条件を考慮しなくてはなりません。 単位数が加算される条件の考慮 図1 だと「緊急時訪問介護加算」は「身体介護」提供時にしか加算されない 利用者の介護度(どの程度の介護が必要なのかのレベル) 介護度に応じた介護保険から支払うことができる金額の限度額の考慮 生活保護などによる公費負担 利用者が引っ越しをした、介護度が変わったなど介護サービス提供中に発生した変更の考慮 などなど、挙げ出したらキリがないですが、介護報酬をキチンと計算するには、すべてのパターンを考慮した算定ロジックを実装する必要があります。このパターンとこのパターンを組み合わせた場合はどう算定したら良いのだろう…というような算定構造だけではわからない不明瞭なパターンも多くあり、様々な資料を読み漁りつつ、正確な算定を実装していく必要があり、ここが介護報酬の算定を難解にしている理由の一つでもあります。 3年に一度の介護制度の改正 もう一つ、介護報酬の算定にとって高いハードルがあります。それは3年に一度行われる報酬改定です。刻々と変わる社会の状況に対応するため、定期的に介護保険制度の見直しが行われています。これによって、今までの制度が更新されることもあれば、新しい制度が追加されることもあるので、これに対応して行かなければいけません。 報酬改定については以前にも記事にしているので、興味がある方はそちらもご覧ください。 tech.bm-sms.co.jp この改正された制度は大半が4月から(一部は10月から)適用されるのですが、適用開始のギリギリまで正式決定されません。例えば令和3年の報酬改定では、令和3年3月31日に確定された旨が通知されました。お金に関わる計算なので、正確な実装を求められるのですが、制度の内容が不明瞭な中での短期間での開発が必要になり、開発の難易度が高くなっています。 もちろん、過去分の請求をしていない人たちも存在するため、制度改正前の介護報酬の算定もある程度の期間は維持しておかないといけません。これもロジックの実装の難易度を上げる要因となっています。 難解な算定ロジックと闘うために だからといって、この部分の実装を諦めるわけにはいきません。介護報酬の算定とその請求処理はカイポケのコア機能です。これが正しく実装されなければ、プロダクトが成り立たないのです。 そこで、難易度の高い計算をより容易により正確に実装するため、様々なトライをしています。 新たな介護報酬算定ロジックの開発 度重なる報酬改定の短期間での開発で、継ぎ足し継ぎ足しの実装をしていった結果、既存の介護報酬の算定ロジックは非常に複雑度の高いものになっています。そこで、介護報酬の算定順序や算定に影響のある条件を整理し直して、介護報酬算定ロジックの開発を一から行っています。 先程ご紹介した算定構造はある程度のパターンを持った構造で表現できるので、これを元に算定ロジックの実装を進めています。一度算定構造の理解が誤っていて、実装を一からやり直しをすることがありましたが、理解度が上がった段階で整理をすることができたので、シンプルな設計に再構成することができました。 まだまだ完成には程遠いですが、短期間での報酬改定に対応できる算定ロジックを目指して開発を進めて行きたいと思っています。 専門家によるテストケースの作成 紹介したとおり、介護報酬の算定には、様々なパターンが考えられ、すべてのパターンで正確に算定できる必要があります。算定ロジックの実装と並行して、ドメインエキスパートやQA、プロダクトオーナーなどエンジニア以外のロールの方々の知識を集結して、考えうるパターンを網羅したテストケースを作成してテスト実行を自動化しました。 これによって、実装の変更による不具合をすぐに検出し、品質の担保をしていきたいと思っています。 まとめ 介護報酬の算定の難解さと、その難解な算定ロジックとどのように闘っているかを紹介しました。正直、私も入社して1年ほどなので、まだまだわからないことだらけです…。 ですが、いろいろな知識を持ったチームメンバーと、難解なロジックを一つのアプリケーションに落としていく過程にやりがいも面白さを感じています。チームの雰囲気も良く、協力的なので、様々なトライをしながらの開発ができ、そういった環境もロジック開発の面白さを感じさせてくれる部分なのかなと思ったりしています。 まだまだ開発の半ばなので、一緒にチャレンジしてくれる仲間が必要です!少しでも興味を持ってもらえたら幸いです!
アバター
こんにちは。2022年10月にエス・エム・エスに入社した塩井です。 現在はプロダクト開発部介護キャリア開発グループにて、介護職向け求人情報サイト「カイゴジョブ」の開発を行なっています。 この記事では、私が前職からの転職先にエス・エム・エスを選んだ理由、そして実際にエス・エム・エスで働いてみて感じていることなどをご紹介したいと思います。 誰? 改めましてしおいと申します。Twitterでは @coe401_ 、GitHubでは @shioimm と名乗っています。 前職では東京と福岡に教室を展開する小中高校生向け英語塾の社内開発チームで学習管理システムの開発を行なっていました。 プログラミング言語Rubyとそのコミュニティが大好きで、好きが高じた結果RubyKaigi 2021とRubyKaigi 2022にて登壇の機会をいただきました。よく地域Rubyコミュニティのミートアップに出入りしています。 前職までのあらすじ 前職が英語塾の会社であったことを前述しましたが、更に遡るとそれ以前はプログラマではなく、コールセンター業務や事務職で数社を転々としていました。そうするうち、「せっかく働くのなら自分が働いた分、少しでも世の中が良くなるような仕事をしたい」と考えるようになりました。 世の中には、解決すればもっと世の中が良くなるような課題がたくさん存在しており、それらの課題に取り組んでいる企業・団体・人々もまたたくさん存在しています。その中でも特に、テクノロジーの力で社会課題の解決を目指すようなプロダクトを作る仕事をしたい、と考えてプログラマになることを志し、自分の中での大きな関心ごとであった教育事業に関わるため前職で働き始めました。 とはいえ特にコンピュータサイエンスについての素養はなく、付け焼き刃で少々Rubyをかじった程度の状態で職に就いたため、最初は右も左もわからず…。社内では周りの上司や先輩方にお世話になり、社外ではRubyコミュニティの人々と交流しながら七転八倒しているうちに何とか業務でも趣味でもたのしくプログラミングができるようになっていきました。 前職は小さなチームで小さなプロダクトをじっくり開発するという特徴があったため、自分たちで考えて実現できることも多く、次第に開発をスムーズに進めるための自分たちらしいやり方も整っていきました。 2021年から2022年にかけては事業部を含めたチームメンバーたちとのn人n+1脚で、将来のサービス開発に向けた土壌を整えるための一連の大きな改修を行い、それらがひと段落つきそうな気配が見えた時、このサービスはこれから先も続いていくけれど、自分の仕事はここまでだな、という感覚が自分の中に芽生えました。プログラマとして初めての転職のタイミングがやってきたのです。 3つの転職条件とエス・エム・エス さて転職を考えるにあたり、こんな会社で働きたい、というふんわりとした条件を3つ考えました。それは、「やっぱり世の中が良くなるような仕事がしたい」「今よりももっと技術力を高めたい」「Rubyが好きな人々と一緒に働きたい」というものです。 そして、いろいろな会社の方からお話を伺った中で、これらの条件を全て満たしていたのがエス・エム・エスでした。 ①「世の中が良くなるような仕事がしたい」 転職先が自分自身の問題として力を尽くしたい社会課題に取り組んでいる会社であってほしい、というのは自分がプログラマになることを決めた動機づけであり、どうしても外すことのできない条件でした。 これに対し、エス・エム・エスは高齢社会が直面する社会課題の解決を目指す会社です。 ご存じのとおり少子高齢化・人口減少は、この社会における喫緊の課題であり、これから時間の経過と共に人口動態がどのように変化していくか、それによって世の中がどのように変化していくか、はある程度予測可能である一方、それをどのようにすれば解決できるのかについては今なお手探り状態です。エス・エム・エスでは40以上ものサービスを展開しており、介護・医療・ヘルスケア・シニアライフという4つの領域でそれぞれに情報インフラを構築することでこれらの課題に向き合おうとしています。 また医療・介護は社会にとっての大事な関心ごとであると同時に、自分の家族や自分自身の人生に直接関わる大事な関心ごとでもあります。老いと無縁な人はいません。 カジュアル面談の際に「介護にまつわるサービスは介護を受けるその人のためのサービスでもあるけれど、その家族のためのサービスでもある」と技術責任者である田辺さんが仰っていたことが印象に残っています。 ②「今よりももっと技術力を高めたい」 前職ではプロダクト開発が売上に直結するビジネスモデルではなかったため、腰を据えて機能を考える、開発に取り組むという経験を積むことができました。 一方で自分自身の経験不足による引き出しの少なさを痛感しており、転職先では前職とはまた異なる開発経験を積むためにより規模が大きく・成長がはやく・そして長く使われるプロダクトの開発に携わりたいと思っていました。 これに対し、前述の通りエス・エム・エスは4つの事業領域で40以上ものサービスを提供しており、それらの中には歴史があり規模の大きいものから比較的新しいものまで様々なものが含まれています。それらの共通点は、いずれも10年20年と成長し続けるサービスを目指して提供されているという点です(高齢社会の課題に向き合うということはそれほどに長い時間を見据えてのサービス提供が必要であるためです)。 こうした特徴を踏まえて、エス・エム・エスで開発者として働くということは、長い時間をかけて成長し続けるサービスに関わることができるチャンスであると感じました。 同時に、キャリアを積んでいく中で場合によっては特徴の異なる複数のサービスに関わるなど選択肢の幅が広がる可能性もあるのではないかと考えました。 ③「Rubyが好きな人々と一緒に働きたい」 冒頭でも自己紹介しましたが、私は職業プログラマであると同時にRubyistでもあります。そのため、同じRubyが好きな人々が働いているような、そして業務外での自分のRubyistとしての個人的な活動を見守ってもらえるような環境で働きたいと思いました。 一方、エス・エム・エスでは多くのサービスを多くのチームで開発しており、その全てのプロダクトがRubyで開発されているわけではありません。 しかし技術責任者の田辺さんはもちろん、入社後私の所属先となった介護キャリア開発グループにもRubyコミュニティでおなじみの人々が所属しており、そして会社としてはRubyKaigiやKaigi on Railsなどのカンファレンスにもスポンサーとして協賛しており、Rubyコミュニティと地続きの環境であることに間違いありません。 (余談: 入社直前に参加したRubyKaigi 2022では現地会場のホワイエにて、入社後の私の上長である諸橋さん、私、そして私にとってのRubyコミュニティの憧れの人々という面々でお喋りするというとてもRubyKaigiっぽい一幕があり、地続き感溢れていました) こうしたことを踏まえて、改めて「こんな会社で働きたい」を考えてみた時、自分にとってエス・エム・エス以上の選択肢はない、と判断しました。そして実際の転職活動ではエス・エム・エス一社のみに選考を申し込み、ありがたいことに入社が決まったという次第です。 エス・エム・エス入社後の日々 ということで入社して数ヶ月が経ちました。 予想はしていたものの、前職とは会社の規模・事業ドメイン・技術スタック(一部)・開発の進め方…と何もかもが違う環境に身を置いているためまだまだ学ぶことの多い目の回るような毎日を過ごしています。 と同時に、ここまでの数ヶ月は自分にとってエス・エム・エスは最良の選択だった、という点について確信を持つことのできた数ヶ月でもありました。 私の所属先である介護キャリア開発グループで開発している「カイゴジョブ」は、介護従事者の方々が新しいキャリアを歩むお手伝いをするための求人広告サービスです。 エス・エム・エス社内にはカイゴジョブに隣接する他のサービスもあるのですが、私の入社当時はそれらのサービス間を横断するような形でより手厚くユーザーの方の支援ができるようにするための機能開発の真っ最中でした。どんな機能を開発すると、それがどんな形で介護従事者の方々のもとに届き、それによってどんな風に世の中が少し良くなるか、を入社直後から早速体感することになりました。 技術面では予想の通り、前職との主に事業ドメインの特性や事業規模やインフラ構成の違いなどから自分にとって初めて出会うような課題が数多くあり、その分学びの機会に恵まれています。 それに加えて頼りになるチームメンバーの先輩たちに囲まれながら、困り事を気軽に相談できるSlackチャンネルや過去の開発の知見が詰まった膨大なドキュメントを活用しつつ日々の開発を行なっています。 さらには開発メンバーの間でごく自然にペアプロ・モブプロが行う文化があり、私の好きな『達人プログラマー』の一節にある「一人ぼっちでコーディングに取り組んではいけない」というTipsが体現されている環境で働いています。 (最近は開発タスクを管理しているTrelloにてペアプロ相手を自動アサインする「モブりたい」ラベルが追加されるなどどんどん運用が進化しています) そして何より、開発に責任感を持って真摯に向き合い、ごく自然にお互いに助け合うチームメンバーの姿勢からはいつも刺激をもらっています。 チームメンバーである児玉さんによる先日の記事『アジャイルでビッグバンリリースの不確実性を低減する試み』の結びに 今回、私達は開発とテストの工程を並行するプロセスを採用し、デザインリニューアルプロジェクトを進めてきましたが、はじめからこの開発手法を確立できていたわけではありません。日々のプロセスの中で上手くいったことや課題に感じたことをチームでふりかえりながらプロセスを最適化してきました。 とあることからも、より良いやり方を皆で模索し続けるチームの真摯さが伺えます。 カジュアル面談の際に田辺さんが「エス・エム・エスのメンバーには誠実な人が多い」と仰っていたのを日々実感しており、自分もその一員として事業、プロダクト、チームに対して誠実なメンバーでありたいと思っています。 tech.bm-sms.co.jp おわりに 以前、チームメンバーである真下さんの記事『エンジニアはプロダクトの事業領域に関心を持ってなければいけないのか?』が投稿されました。 tech.bm-sms.co.jp エス・エム・エスで開発に携わる各部署メンバーへ入社時点でエス・エム・エスの事業領域(医療・介護・シニアライフ・ヘルスケア)についてどの程度興味を持っていたかについて調査した結果についてまとめたもので、なかなか興味深い結果となっています。 私自身も医療・介護・シニアライフ・ヘルスケアという事業領域に明確な興味を持っていたわけではありませんが、ふんわりとした「世の中が良くなる仕事がしたい」「もっと技術力を高めたい」「Rubyが好きな人々と一緒に働きたい」という気持ちで入社したエス・エム・エスで、現在は事業への手応えを感じつつ、技術的なコンフォートゾーンから出つつ、信頼できる人々と一緒に働くことができています。 この記事では執筆者である私にとってのエス・エム・エスが転職先としてどんな会社だったのか、について書き連ねてきました。この記事でエス・エム・エスに少しでも興味を持ってくださる方がいれば幸いです。
アバター
はじめまして、エス・エム・エスでSREとして介護事業者向け経営支援サービス「カイポケ」の開発・運用に携わっている小笠原です。 2022年10月に入社してしばらく働くなかで組織やプロダクト開発の状況がある程度見えてきたので、入社エントリを兼ねて自身の所属するチームや仕事について紹介します。 内容は現在のスナップショットにはなりますが、中で働いている人や組織の雰囲気、向き合っている課題について今後入社を検討される方の参考になると嬉しいです。 入社までにやってきたこと 先に自身の経歴について紹介しますと、情報系の大学院を出て2017年からIT業界で働き始めて今年で6年目です。ポジション的にはいわゆる中堅どころのエンジニアです。新卒でSIerに入社してSEとして顧客の比較的規模の大きなWeb基盤の開発・運用を1年ほど行い、そこからWebベンチャーに転職して4年半ほど中規模なWebシステムの開発・運用に幅広く携わりました。 前職ではReact/Railsを用いたアプリの機能開発を経験し、その後は基盤開発に軸足を置いて基盤の構築・運用やアプリと基盤の間に落ちるような諸々の課題解決に取り組んでいました。 個人的に興味のあった課題(フロントエンド/バックエンドの機能開発、アプリのパフォーマンス改善、基盤の刷新、CI/CD構築、セキュリティ強化、モニタリングの導入・運用、コンテナオーケストレーションツールを用いたアプリ開発など)を一通り経験でき満足したことと、コンフォートゾーンに入っていたこともあり一度環境を変えようと考えたのが転職のきっかけです。 Web開発に関して一通りの経験は積めたものの技術的にはまだまだ未熟と感じていたため、これまでの経験を活かしつつさらに経験を深められる会社を探していました。 エス・エム・エスを知ったきっかけ そんな中、LinkedInでEMの古萱からメッセージをもらったのがエス・エム・エスを知るきっかけでした。それまではエス・エム・エスについては会社の名前も知らなかったのですが、メッセージに添えられていた 技術責任者田辺のブログ を読んで興味を持ちました。この記事では「継続性アーキテクト」という概念で技術を深める方向のキャリアを説明しており、技術志向のエンジニアに寄り添った考え方に感銘を受けました。そのような考え方を持った人が運営する組織であるなら一度話を聞いてみたいと考えました。 そして、実際に転職活動を行う中で採用要件や向き合っている課題について話を聞く中で私がこれまで行ってきた経験が多く活かせそうだと気づき応募するに至りました。 入社の決め手 複数社を受けて総合的に判断した結果入社を決めたので「これで決めた!」というものはないのですが、エス・エム・エスが他と比べて良いと思ったのは以下の点です。 ビジネスモデルが強い 医療介護業界は今後も需要が伸びるためそこをターゲットとしているエス・エム・エスは継続的に事業が発展する可能性が高く、エンジニアとして長期でコミットできそうと考えました。 事業に興味を持てた 私は自分が携わったシステムやサービスが最終的にどんな価値を社会に提供できているかが仕事のモチベーションになっています。エス・エム・エスが取り組んでいる医療・介護領域の事業はその点提供価値がわかりやすいため取り組みがいがあると考えました。 課題が多くて挑戦の余地がある サービスおよび機能の数が多く歴史もあるため、課題が多く取り組む仕事の範囲も広くて飽きないと考えました。よい設計で整理が進んだプロダクトの場合、かえって面白い仕事がない場合もあるので課題が多いことは自分は肯定的に受け取りました。 個人の裁量や動きやすさがありそうに見えた 大企業ではあるものの開発組織は人数的にも体制的にもまだまだ発展段階にあり、開発者個人の裁量が大きく動きやすさがあると考えました。また、組織の技術責任者やリードする立場の方がいわゆるWeb系の企業出身者が多く、エンジニア個人の動きやすさに配慮している印象を受けていました。 実際に入社してからは「カイポケ」というプロダクト(後述)およびそのリニューアルプロジェクトにSREメンバーとして関わって働いています。2つのチームで色はそれぞれ異なるのですが、自分が入社前に抱いていた組織やチームへの印象は入社後も大きく変わることはなかったように感じています。 関わっているサービス「カイポケ」について 「カイポケ」は介護事業者向けのSaaSです。 介護事業者が国の保険制度を利用する際の請求業務を行ったり、事業所職員がタブレットから記録業務を行ったり、それ以外にも介護事業に関わる様々な業務をこのサービス上で行うことができます。 カイポケの現行システムは開発開始から10年以上が経過しており、当初は主に外部のリソースを利用して開発していたところ、その後 内製化に舵を切って取り組んできた歴史があります 。現在4万を超える事業所で導入されており、40個以上の機能を備えています。 機能・ドメインごとにサーバが分割されているためサーバ台数は多いものの、主となるシステムの基盤構成はシンプルでALB、EC2、RDSをベースとした一般的なWebアプリを想像してもらえると良いかと思います。アプリケーションサーバのレベルでは分割されているものの、DBなどのリソースのアプリケーション間で共有されているため、システム全体としてはモノリシックな側面も残っています。 ところで、先日以下の記事でもご紹介したように、現在、この現行のシステムをリニューアルするプロジェクトが進んでいます。 tech.bm-sms.co.jp リニューアルをすることにした理由はいくつかあるのですが、先述のようにモノリシックな部分があるがゆえに変更の影響範囲が広くなりやすいこと、また、共有されているリソースの存在が組織全体の開発スピード向上のボトルネックになりうること、などがSREチームに関わる部分として挙げられます。 そのような背景のなかで私は現行「カイポケ」(以下、現カイポケ)のSREとリニューアルプロジェクトのSRE、2つのチームにメンバーとして関わっています。それぞれ向き合っている課題と求められる仕事に違いがあるため、雰囲気がわかるよう簡単に紹介していければと思います。 現カイポケのSREについて まず現カイポケのSREの責務ですが、開発・運用含め基盤関連の仕事全般と言うような形でふわっと与えられています。開発チームからの依頼対応や各種基盤リソースの障害対応、各種コンポーネントのバージョン更新、脆弱性対応などインフラエンジニアという括りで担当が分かれるような運用寄りの仕事もあれば、リリース改善やトイルの削減、プラットフォームのモダン化と言った一般的にSREが担当するような仕事まで様々です。 その中で何を優先してどこまで手を出すかはチームの体制・余力に応じてチーム内で調整するようになっており、チームの裁量は大きいです。チームが少数体制だったときには現状維持を主な目的とした「守り」の施策に集中していたときもありましたが、現在では人員が増えたこともありチームの業務範囲を拡大して現状改善を主な目的とした「攻め」の施策の割合を増やしているところです。 例えば直近で進んでいる施策には「バッチ実行基盤のマネージドサービスへの移行」、「デプロイ自動化」、「オブザーバビリティ改善」などがあり運用負荷削減や開発効率化、システム安定性向上といった現状からの改善を図っています。 現カイポケはシステムが少し古い構成で動いていることもあり、仕事の傾向としては現代的な構成や運用に持っていくことで価値が出ることが多いです。アプリは介護ドメインの複雑さを反映してビジネスロジックは複雑である反面、非機能要件はシビアではないため基盤側は世の中的によく使われているWebアプリケーション運用のベスト・プラクティスを正しく導入できれば着実に改善を進めることができるように見えています。 ただ、プロダクトの主たる部分がモノリシックな作りということもあり変更の影響範囲が広くなる傾向があります。新しい技術スタックの導入を試みると、検証中に思わぬ箇所で導入を妨げるような課題にぶつかります。課題にはいろいろな種類があるのですが、経緯を紐解いて地道に解決の道筋を作る必要が出ることが多く、歴史の長いプロダクト特有の難しさを感じます。 個人的にはそのような課題がある中でうまく差し込めるような解決策を考えるのがパズルゲームのようで面白いと考えています。技術責任者やEM、アーキテクトの方々も「過去のしがらみは気にせずどんどん改善を進めてくれたら良いよ」と肩を押してくれるので、今後もSRE主導での改善活動に積極的に取り組んでいきたいと考えています。 リニューアルプロジェクトのSREについて 一方でリニューアルプロジェクトのSREの責務はプロダクトが初期構築のフェーズということで柔軟性を持って動くことを期待されており、明確な定義はありません。ただしチームの大きな目標としては「システムが安定して動いている状態」と「システムを早く世の中にリリースできる状態」を維持することを目指して動いています。 SREの立ち位置がわかりやすくなるので先に開発体制について説明しておくと、リニューアルプロジェクトではアジリティの高い開発体制を目指しており、これを実現するため開発チームには基盤リソースのアクセス権限が与えられています。開発チームがオーナーシップを持つサブシステムごとの基盤リソースについては開発チームが管理するため、SREチームはシステムの全体設計や共通コンポーネントの管理を始めとするシステム横断の技術課題の解決に集中できるようになっています。 例えば直近の仕事で言うと以下の記事で紹介しているように「OpenTelemetryの検証」や「本運用を見据えた監視体制の整備」などに取り組んでいます。 tech.bm-sms.co.jp リニューアルプロジェクトではシステムを0ベースに近い形で設計しているため、制約が少ない中で技術選定や開発が行える環境です。もちろん技術の導入には妥当な選定理由は必要ですが、検証で該当技術の成熟度を測って導入判断を行ったり新しいバージョンのライブラリの使い勝手を試したりを気軽にできるのは新規構築ならではの楽しい部分だと考えています。 現在リニューアルプロジェクトでは初期バージョンのリリースに向けて動いており、運用基盤もそれに合わせて整備を進めていく段階です。SREチームでも開発状況に合わせて様々な施策を担っていくことになるので、自分でもどんどん手を動かしてプロジェクトの成功に貢献していきたいと考えています。 まとめ 個人的な転職の経緯から、現在関わっているチームや仕事について簡単に紹介させていただきました。これを読んで「カイポケ」のSREチームの雰囲気や普段どのような課題と向き合って仕事をしているかについて理解が深まると嬉しいです。 どちらのSREチームも開発状況が日々少しずつ更新されているため、本文では敢えて具体的に利用している技術の話やシステム構成の話、組織内の詳細な体制の話などは省略しました。より具体的な話や今後の見通しなどが気になった方はぜひ一度カジュアル面談にいらしてください。
アバター
2022年10月、介護事業者向け経営支援サービス「カイポケ」のエンジニアリングマネージャー (以下、EM)としてエス・エム・エスに入社した酒井( @_atsushisakai )です。先日、転職活動については 個人のブログ の方に書いてみましたが、入社エントリとしてエス・エム・エスの入社までの経緯と、EMとして実際にどのような仕事をしているかも簡単にご紹介します。これから「EMとして」転職を控えている方で、不安を持たれている方にとっての指針になったりすると嬉しいです。 私のバックグラウンド 前職では、株式会社 MIXI で「家族アルバム みてね」というプロダクトを、創業メンバーとして立ち上げからグロースまで8年以上に渡り開発を続けていました。ソフトウェアエンジニアとしては、iOS/Android のネイティブアプリ開発、Ruby on Rails でのバックエンド開発や AWS でインフラを構築・運用するなど、少人数チームならではの領域を越えまくるスリリングな開発を行なっていました。 また、後半数年間は EM として、ピープルマネジメントとプロダクト開発チームのスケールのための組織作り・プロセス作り・採用活動などに取り組んでいました。 エス・エム・エスとの出会い そんな中、エス・エム・エスに出会ったのは転職のおよそ1年前、YOUTRUST でのスカウトメッセージでした。メッセージを受け取った後、コーポレートサイトを見てミッションや事業内容が非常に特徴的に感じ、その時は転職を全く考えていなかったのですが「面白そうだし一回話を聞かせてもらおう。自分の組織のために学びも得たいし!」くらいの軽い気持ちでカジュアル面談を申し込み、技術責任者の @sunaot と話をしました。 カジュアル面談では、エス・エム・エスの ミッション を中心に、展開しているサービスの課題、開発組織の課題などを広く聞かせてもらいつつ、自身の組織マネジメントの悩みなどを話して共感してもらったりなど、とても楽しく会話したのを思い出します。 なぜエス・エム・エスに入社を決めたのか その後、何度か @sunaot と話をさせてもらう中で私は転職する決意をし、最終的にエス・エム・エスを選びました。転職先としてエス・エム・エスを選んだ理由はいくつかあります。 エンジニアリング組織をより大きくスケールさせていくフェーズであること 私はこれまで、比較的小〜中規模 (50名くらい) の規模でプロダクト開発を中心とする組織のEMとして活動していました。自身の中には、EMとしてより成長していくためには、さらに大きな規模の組織で発生する課題に向き合い、そこから得られる経験が必要であると感じており、その規模感とエス・エム・エスのプロダクト開発組織がこれからスケールしていくであろう規模感のイメージが合致していたことが、この会社を選んだ一つの理由です。 戦略を特に重要視していること カジュアル面談で何度も話を聞いていく中で感じたのは、エス・エム・エスが一貫して「戦略」をとても重要視していることでした。社会課題に対してどのような視点からペインを解決しなければいけないかという Why の部分と、それをいつまでに解決すべきかという When の部分が徹底的に言語化されており、それに沿った形で非常に多くの事業やプロダクトに紐づいています。自分自身、事業における戦略が明確かつ相応な分量で言語化されていることは、迷った時に立ち返ることができる安心材料になるのでマネジメントの観点からも非常に業務が進めやすそうな魅力的な会社だと感じました。 子ども世代への影響度が大きいこと 最後は当然、解決しようとしている課題とそのための事業についてです。エス・エム・エスが展開している非常に多くの事業は、2040年問題と呼ばれる高齢者人口がピークに達する時代を見据えた課題解決の方向性を示そうとしています。2040年といえば、現在3歳の自分の子どもがちょうど社会に出る時代です。それを考えた時に、ひとつのプロダクトをじっくり作っていくことも重要ですが、多くの観点から社会の根本的な課題を解決するために自分の能力を使い、少しでも生きやすい社会に変化させていくことが、今の自分にとっては納得度も高く、何より我が子の未来のためになるのではないかと考え、最終的にこの会社に入社することを決めました。 入社してどうだったか? 入社後、最初の一週間は非常に戸惑いました。それは、「EMとして会社にジョインする」ということが初めてだったことが一番大きな理由だと今振り返って思います。膨大な情報量と日々目まぐるしく動くプロジェクトの状況など、プロダクトの立ち上げからほとんど全てを把握していた前職の環境からは一変し、自分自身がどの情報を選択し、深くインプットした上でチームやメンバーに影響を与えていくことができるかを判断するのがとても苦労しました(これは正直、今でも苦労しています)。 ただ、チームメンバーは非常にオープンマインドの方が多いので、積極的に1on1を申し込んだりなどを重ねて、コミュニケーションを取る機会を増やしていったことで思ったよりも早く溶け込むことができました。そうすることで、EMとしてコミットできる小さな課題を徐々に見つけることができるようになっていったのを思い出します。エス・エム・エスの開発カルチャーとして、モブプログラミングや意識的なオンライン雑談の時間などが充実しており、新しい人を受け入れやすいマインドが備わっているのも影響が大きいと感じています。 入社後にやった仕事 現在は介護事業者向け経営支援サービス「カイポケ」の開発プロジェクトにおいて、フロントエンドチームの EM としてアサインしてもらっています。また、エンジニアやデザイナーのプロダクト開発に必要な人材の採用活動も幅広く扱わせてもらったり、プロジェクト全体のエンジニアリング部門におけるマネージャーとして、課題の集約や情報共有の結節点となるなど、現場に近いところでプロジェクト推進のためにできることを徐々に増やしていっています。 この中でもいくつか、入社後にフロントエンドチームの EM として取り組んだ仕事をご紹介します。私が EM を務めるフロントエンドチームは、私が入社する1ヶ月前に新たなチームとして発足しており、様々な整備やチームビルディングを行っていかなければいけない状況でした。以下は、そういう状況で私が EM として取り組んだ課題解決のいくつかの事例となります。 チームの価値観や暗黙知を言語化する 私がジョインしたタイミングでは、徐々に技術選定は進んでおり、チームがコードを書き始められるような土台は出来上がってきていました。ただ、立ち上がったばかりのチームなので、複数人でコードを書いていく上でそれぞれのメンバーが持つ開発の価値観を揃えたり、暗黙知となっている事柄を言語化することなどがまだそれほど出来ていない状態であったことに気づきました。これらを言語化することが長期的なパフォーマンス向上を実現する土台となるという経験はこれまでもあったので、まずはここに取り組ませてもらいました。そのひとつの成果として「コードレビューガイドライン」を GitHub リポジトリ上に立ち上げたり、同様に「コーディングガイドライン」も叩きとして作り、今後コードを書いていく中で何か取り決めがあった方が良いだろうというものをガイドラインとして言語化し、明確に共有していく流れを作りました。 コードレビューガイドライン スクラムの導入 コードベースが徐々に整理されていくのと同時に、ユーザー価値の実現プロセスとしては、スクラムを取り入れていくことが初期に決まっていました。スクラムのフレームワークを使い、どのようにプロダクトバックログアイテムを管理・見積もりを行うか、チーム内でスクラム関連のイベントをどのタイミングで、どのぐらいのタイムスケールで実施するかなどを PdM とじっくり話しながらプロセスを構築していきました。プロセス全体においては自身がスクラムマスターとして、チームがスクラムにおけるメリットをしっかりと享受できるよう、まずは教科書的に必要な要素をしっかり実践していくことを徹底しています。このあたりは、前職で長年スクラムを実践してきた経験がとても活かされていると感じています。 フロントエンドチームのMission/Vison策定 開発が安定的に進み始め、新たにメンバーがジョインした頃に、より細かくお互いの価値観を揃え、チームとして目指したい姿を共有するために、今後数年を見据えた Mission/Vision をメンバーみんなで作ることにしました。プロジェクト全体のミッションを達成するためにチームとしてどのような貢献をするべきか (Mission)、また「組織・技術・採用・プロダクト」の様々な観点で、チームがどのような状態になっていたいか (Vision) を複数回ディスカッションを重ね、言語化し、将来像をメンバー間で共有しました。 フロントエンドチームのMission/Vision フロントエンドチームの分割 チームが立ち上がり数ヶ月経つと、様々な方面からチームにメンバーがアサインされるようになり、人員のボリューム感も出てきました。今後開発対象となるアプリケーションも複数あるため、チームを分割していく流れを作っていくことが自然な状況になってきました。チームをどういう単位で分割するか、また分割していく上で事前に考える必要がある様々な事柄を整理し、ガイドライン化しながら、チーム内外で認識合わせをしたりなどをしてきました。主に実施したことは以下のようなものです。 誰をどのチームにアサインするかの決定 メンバー間での役割分担 (マネジメント、プロジェクトリードなど) 複数チームの情報流通のためのドキュメントや会議体の整備 複数チームで行うコードレビューのためのガイドラインのアップデート 事前のコードベースのリファクタリング 共通コンポーネントの整理 現在は、ふたつのフロントエンドチームがお互いに連携しながら、アプリケーションを開発しています。 おわりに 以上が、入社の経緯や入社して以降の実際の仕事の内容でした。まだ入社して数ヶ月ではありますが、EMとして大きな裁量を持ち、自分自身が一番レバレッジを効かせられる場所を見つけ、自由に動いて良いということを技術責任者の @sunaot から言ってもらえています。そういう中で、徐々に転職した理由であるEMとしての「マネジメントの規模感」を大きくしていけるような視点も増えてきており、これから EM としてエス・エム・エスでプロダクト開発と事業に全力投球できることを非常にワクワクしながら過ごしています。 EMとしてこれから転職を考えている方は「どのように既存の開発組織に入っていくのが良いだろうか?」ということを悩まれると思いますが、今回の私のポストの内容が少しでも役に立つと嬉しいです。 最後になりますが、エス・エム・エスでは社会の課題解決を一緒にやっていただける様々なプロダクト開発の人材を募集しておりますので、気になった方は、ぜひ一緒によりプロダクト開発に取り組んでいきましょう!
アバター
こんにちは、SREをやっている @okazu_dm です。 経歴としては、サーバサイドエンジニアからセキュリティエンジニアを経て、エス・エム・エスではサービス横断で技術的な課題を解決しています。 基本的には組織に必要なことと自分ができることや、やりたいことが交わるポイントで仕事をしており、現在はSREとして働いています。 今回は、 過去の記事 とは違い、既存SREチームとは別に、介護事業者向け経営支援サービス「カイポケ」のリニューアルプロジェクトにスコープを絞って新しく立ち上げたSREチームとしての活動を紹介します。 リリース前のプロダクトのSREなので、一般的なSREの定義とは乖離する内容もある点ご留意ください。 リニューアルプロジェクトの概要 昨年末に出したフロントエンドの技術選定に関する記事 での説明と内容的な差分はありませんが、SREの業務の説明の前にこちらでも改めて触れておきます。 インターネットサービスとしては比較的長く稼働しているカイポケですが、ソフトウェアとして見ると、10年以上の時間の中で発生した様々な需要に応じる形で機能を継ぎ足されてきた大きなモノリシックアプリケーションです。 現在進行しているリニューアルプロジェクトは、カイポケのサービスの安定性やプロダクトの優位性を高めるべく、アーキテクチャレベルで見直しをかけ、部分ごとに移行する形でリリースしていく開発プロジェクトです。詳しくは以下のサイトをご覧ください。 careers.bm-sms.co.jp リニューアルプロジェクトのSREチームの担当範囲 開発途中という性質上、プロダクト全体の仕様やシステム構造のボラティリティも高く、SREの役割もプロダクトの状況に応じて変化しています。 その上でチームの大きな目標として「システムが安定して動いている状態」と「システムを早く世の中にリリースできる状態」を維持することを目指しています。 実務的には現在は以下のような役割を担っています。 インフラの設計&構築 内部的にサービスを分割する設計となるため、インフラについても各開発チームと分割統治する形をとっています。SREチームは全てのサービスが乗る基盤となるインフラを担当しています。 開発プロセスの大まかな整備 同じAWSアカウント上で複数チームが同時並列で開発を進めるために、アカウント全体の利用ルールやコスト管理などはSREチームの担当としています。 モニタリング基盤の整備 モニタリングに利用する外部SaaSの選定やシステム全体のアーキテクチャにどのようにモニタリングを組み込むかについてはSREチーム主導で検討し、構築を進めています 今後の開発の中でプロダクトの全体像が明確になっていくにつれて役割が増えることが予想されますが、現状は以上が主要な業務です。 直近の技術的トピック SREチームでは前述のように役割が流動的なため、日々の業務に伴い広くツールや外部サービスの技術検証や導入を行っているのですが、その中でもこの場に書きやすいものをピックアップしてご紹介します。 認証基盤の選定 リニューアルするに伴い認証部分についても検討が必要になりました。これについてはSREチームを含む一部のメンバーで最初にいくつかの候補を出し、最終的にAuth0を採用することに決めました。 大まかな方針として、「ユーザの認証情報を社内に持たない」点と「各開発チームが簡単に扱える(独自仕様が少なく直感的である)」点を重視して選定しました。 候補としたものと、調査した結果の短い所感をそれぞれ以下に書いていきます。 Google Identity Platform: 特に不足はなさそうだが、Auth0のほうが便利そうだった。 Amazon Cognito: 他のソリューションと比べて安上がりだが、仕様がやや独特で開発者全員がスムーズに理解できるかは不安があった。 Azure AD B2C: 安くて安定していそうだが、Azure自体を触ったことがなく使いたい機能が限定的であるのに対して未知の部分が多いため今回は見送り。 汎用的なモニタリングのパイプラインの実現 こちらでは、具体的にはOpenTelemetry関連ツール導入の実現可能性について検証し、実際に導入して分散システムのモニタリングの構築を進めている現況の紹介をします。 OpenTelemetry まず、OpenTelemetry(以下で一部otelと略していることもあります)について簡単に紹介します。 プロジェクトの公式サイト( https://opentelemetry.io/ )から引用したものを日本語訳して貼りますが、以下のように表現されています。 OpenTelemetryは、ツール、API、および SDK の集合です。テレメトリデータ(メトリクス、ログ、およびトレース) を計測、生成、収集、およびエクスポートし、ソフトウェアのパフォーマンスと動作の分析に役立てます。 これだけだと理解が難しいかと思うので、公式の図を踏まえつつもう少し具体的な紹介をします。 図は https://opentelemetry.io/docs/ より引用 図の中心にあるOTel Collector(以下単にCollectorと称する)が重要な役割を果たすツールです。これは、アプリケーションやインフラ(図の左側のコンポーネント群)から能動的または受動的にテレメトリデータを吸い上げ、Collectorがサポートするいくつかの形でエクスポートします。エクスポート先の例としては以下のようなものが存在します。( https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter より) Jaeger Zipkin Prometheus Datadog AWS X-Ray また、アプリケーション側が自動でリクエストや関数呼び出しなどの単位(上記の公式の文中にあるテレメトリデータの中で言うところのトレース)で処理の実行記録を残すために、アプリケーションに組み込む計測用ライブラリも開発されています。 これは使い方の一例ですが、例えばDatadogの従来の導入方法は、公式のライブラリをアプリケーションに組み込み、Datadog Agentを立ててDatadog Agent経由でDatadogにログやトレースを送る、という形でした。これのもう一つの選択肢として、ライブラリをDatadogのものからOpenTelemetry用のものに変更し、Dataodg Agentの代わりにCollectorを導入することで同様のことが実現できるようになりました。 このように、OpenTelemetryは従来のベンダが個々に作っていたモニタリングのパイプラインの一部を統一的な形で実現しています。 導入の背景 OpenTelemetryを導入すると何ができるのか、という点については前述したように、少なくとも現状の我々の利用範囲だと何か新しいことができるようなものではありません。 その上でOpenTelemetry導入に至った経緯について説明します。 まず、今回のプロダクトでは初期のリリースの規模などから見てもモニタリング基盤は外部のソリューションに任せた方が良いだろうという方針でDatadogを採用することにしました。 そして既に社内でOpenTelemetryを採用しているチームがあった点と、プロダクトが拡大した場合はDatadog以外のソリューションを検討することもあるだろうという将来的な可能性を見据えて、サービス間の乗り換えが比較的容易そうなOpenTelemetryを採用しました。 Datadog Agent vs OTel Collector Datadog AgentはOTLP(OpenTelemetryProtocol)のメッセージを受けることが可能なので、OpenTelemetryを採用する場合でも以下の図のようにCollectorを使うか、Datadog Agentを使うかの選択肢が発生します。 図は https://docs.datadoghq.com/opentelemetry/ より引用 それぞれ単純なアプリケーション1つの環境でsidecarとして動作させて、デプロイ時の落とし穴がないか、という点やコンピューティングリソースの消費量などをざっくり確認してみました。検証の結果、どちらも単体で使う分には大きな差はないように見受けられたため、中長期的にCollectorの柔軟性が活きることを期待して、Collectorを標準のツールとして採用しました。 また、計測用のライブラリについては未検証ですが、OpenTelemetryのものを使ったコンポーネントとDatadog製のものを使ったコンポーネントを同時に使った場合、分散トレーシングに問題が出るのではないかと考えています。 具体的には以下のように、DatadogとOpenTelemetryの間でトレースのIDのデータフォーマットが違う点が問題となることが予想されます。 https://docs.datadoghq.com/ja/tracing/other_telemetry/connect_logs_and_traces/opentelemetry/ なお、仕組みの話を補足するとトレースのID自体は計測用ライブラリ側で生成しており、OpenTelemetryのライブラリを利用した場合Datadog AgentやCollectorのDatadog ExporterがDatadogに送る前にフォーマットを変換しているようです。 例: Datadog Exporterでフォーマット変換していると思われる箇所 github.com 現状の懸念 ここまで、利便性や今後の可能性について様々な点から紹介してきましたが、一方で現状で本番投入するにあたっては以下のような懸念もあります。 Stableでない部分も多く、各種データの吸い上げからエクスポートの部分までをすべて公式が実装していた状態よりは今後の不安がある(どちらか片方の破壊的変更が今後起こった場合など) 現在、sidecarとしてAWS FireLens(fluentbit) *1 とCollectorを立てているが、関係する要素が増えると事故が起こる可能性もそれだけ増えるため可能であればもっと単純な形に置き換えたい(単純計算だとシステム内部に直列で繋がる要素が増えると事故率は上がる) まとめ この記事では、現在進行中のカイポケリニューアルプロジェクトのSREチームが担っている役割についての紹介をしました。 また、SREチーム内の直近の業務紹介の中で、OpenTelemetryの紹介とDatadog連携についての調査内容の一部分を要約して説明しました。 興味を持っていただけた方は、直近での転職意思の有無にかかわらず、ぜひカジュアル面談にお越しいただければと思います。 *1 : ECSタスクのログをS3に転送するために使用しています https://aws.amazon.com/jp/blogs/news/under-the-hood-firelens-for-amazon-ecs-tasks/
アバター
介護事業者向け経営支援サービス「カイポケ」のエンジニアリングマネージャー、酒井( @_atsushisakai )です。現在、私たちは「カイポケ」のフルリニューアルプロジェクトに着手しています。今日は、このプロジェクトをご紹介します。また、プロジェクトで扱っている課題や私たちが日々取り組んでいる技術的なチャレンジについて、これからさまざまな観点の記事を deep dive してお届けしていきます。そのあたりについても簡単にご紹介します。 「カイポケ」フルリニューアルプロジェクトとは? 私たちは、介護事業所の運営に不可欠な「保険請求」の機能をはじめ、事業所経営を総合的に支援するための多くの機能を提供する業界特化型 SaaS である「カイポケ」を開発・運用しています。 「カイポケ」はサービスローンチから17年が経過し、システムの安定性・開発効率の観点で多くの課題を抱えているのが現状です。同時に、高齢化社会が進む背景もあり介護業界からのニーズは年々高まっています。そこで、今後の更なる継続的な事業成長を見越して、アーキテクチャから全てを見直すような開発プロジェクトを2021年から始動してきました。 今回、このプロジェクトについて、扱っている技術的な課題や将来の展望など、詳しい取り組み内容についてまとめたサイトを新しく立ち上げました。情報量はとても多いのですが、非常にチャレンジングで難易度が高いが故のプロジェクトの面白さが伝わる内容になっていると思います。是非一度ご覧になっていただき、興味を持っていただけると嬉しいです。 careers.bm-sms.co.jp プロジェクトの裏側を順次公開中 今後、多くの難易度の高い技術的な課題をどう解決していこうとしているかであったり、多くの人が関わる複雑で規模の大きなプロジェクトをどのように進めているかというプロセスの工夫など、さまざまな観点でフルリニューアルプロジェクトの裏側をご紹介していく予定です。 既にフロントエンドの技術選定については以下の記事でご紹介しています。 tech.bm-sms.co.jp 来週には @okazu_dm さんによる SREチームの取り組みについての記事が公開されます。さらに、今後、以下のようなテーマでコンテンツを順次公開していく予定です。(内容は変更の可能性もあるのでご了承ください。) 極めて複雑な介護費用請求金額の計算について 大規模なプロジェクトを運営するための開発プロセスについて カイポケの新しいアーキテクチャについて プロジェクトを支えるメンバーを積極採用中 最後になりますが、このフルリニューアルプロジェクトでは、まだまだ多くのエンジニアの方の協力を必要としています。先ほどご紹介したプロジェクト専用サイト内に 募集中のポジション を記載していますので、興味のある方はぜひご覧ください!また、エンジニアだけでなく、 プロダクトマネージャー ・ UI/UXデザイナー などプロジェクトに携わるポジションを全方位的に募集しています。 カジュアル面談もやっておりますので「選考に進む気持ちはまだないけどプロジェクトについては面白そうなので聞いてみたい」という方でも構いません。ご興味があれば是非お気軽にお声がけください!
アバター
はじめに エス・エム・エス BPR推進部カスタマーデータGrで、「 ナース人材バンク 」等のキャリア事業を中心に、社内のデータ活用の推進、データ基盤の開発を担当しています、橘と申します。 私達カスタマーデータGrでは、Google CloudのBigQueryを中心としたデータ基盤を構築しており、社員がデータを利用して意思決定をする業務をサポートする役割を担っています。本記事ではそのデータ基盤についてご紹介したいと思います。 カスタマーデータGrのデータ基盤について 私達が運用するデータ基盤のデータ連携アーキテクチャについて、簡単にまとめると以下の図のようになります。 エス・エム・エスのキャリア事業で活用したいデータは、AWS上に構築されたシステム、Salesforce、GoogleドライブやGoogleスプレッドシート、その他SaaS等、様々な場所に散らばっております。これらをGoogle Cloud上のストレージに収集し、利用目的に応じたデータストアへ連携し、業務への活用をする、といった構成となっています。 データ基盤のワークフローエンジン これらのデータを連携する基盤として、GCP上でApache Airflowをマネージドで提供するサービス、Cloud Composerを利用しています。Airflowでは次の図のようなワークフローをPythonで定義して運用することができます。各処理を動かすタスクを整理し、それを有向非巡回グラフで依存関係を定義し、前のデータのロードが終わったら、そのデータを加工するタスクを動かす、といったことが実現できます。 Airflowには、BigQueryやCloud Storage等のGoogle Cloud上の各サービスへ接続する機能が元々あります。その他の外部サービスへ接続する共通処理であったり、プログラムでビジネスロジックを記述する必要がある処理に関しては、Pythonを書いてこのAirflow上で動かすことができます。 3年ほど前にこのAirflowを本番環境で導入し、今では100件以上のワークフローを動かす基盤として運用しています。 BigQueryを中心としたETL基盤 BigQueryは、とにかく大量のデータを蓄積することが可能なので、Google Cloudに連携したデータのほとんどをここにロードしています。BigQueryに蓄積したデータを加工して、集計・分析用のデータマートを作成する、ELT(Extract, Load, Transform)の構成を取っています。データの加工には、SQLやJavaScript(BigQuery SQLのUDFとして利用)を主な手段として採用しています。BigQuery上のSQLであれば当然リソースはBigQueryのリソースを利用できるので、大量データを効率良く処理ができます。導入前はオンプレミス環境に構築したETLツールを用いて、サーバのリソースを割いてデータ加工の処理をしていましたが、BigQueryの導入によって、数億件のデータでも安定して高速で処理ができるようになりました。 BigQuery上のデータは、マーケターやエンジニア等のデータ利用者がSQLを実行して参照したり、Looker Studio、Googleスプレッドシート等を用いたレポートを作成して閲覧できるようにしています。 また、BigQueryのデータは集計データを蓄積するのみにとどまりません。後述するGoogle Cloud上に構築した業務Webアプリケーションが利用するCloud SQLやFirestoreに連携したり、BigQueryのリソースを用いて大量データを集計した結果を、AWSやSalesforce等の社内の別システムへの連携をしたりと、大量データを処理する業務システムとしての役割も担っています。 Looker StudioやGoogleスプレッドシートを用いたデータの活用 弊社ではエンジニア組織のみならず、全社的にグループウェアとしてGoogle Workspaceを利用しています。そのため、GoogleスプレッドシートやLooker StudioといったGoogleのサービスを用いることで、Googleアカウントとデータの閲覧権限があれば、URL一つで誰もが必要なデータにアクセスできる環境を目指しています。 Looker Studioでは、次の図のような図表やフィルタを簡単に作成して共有ができます。日々の業務で観測したいデータをまとめたダッシュボードを作成して、事業の意思決定に役立てています。参照するデータは前述のBigQuery上のデータを用いています。BigQueryやSQLの知識がなくても、GUI上で見たい指標を選んでグラフを作成したり、簡単な数式も組んだりすることができるので、データエンジニアに依存することなく、データの利用者がダッシュボードを編集できるようになりました。 Googleスプレッドシートにもまた、BigQueryに接続できるコネクタがあります。Looker Studioのようなダッシュボードではなく、慣れ親しんだ表計算を用いて集計業務をしたいケースに応えるために利用しています。従来はExcel上でマクロを組んで、複数のデータソースをまとめて処理していた業務を、BigQueryとスプレッドシートに置き換えることで、ある程度の業務の自動化が可能となりました。 また、集計や分析に必要なデータは、データ基盤に蓄積したデータのみではありません。集計したい指標の軸となる独自のマスタデータを管理して、それをBigQueryに連携するインターフェースとしてもGoogleスプレッドシートを利用しています。Googleスプレッドシートはデータ利用の入出力のインターフェースとして欠かせないものとなっています。 Cloud SQLやFirestoreを用いた業務アプリケーション BigQueryは大量データを処理したり、集計したデータを一括で閲覧したりするのに向いている一方で、RDBMSのように業務アプリケーションで用いるデータベースとしては、パフォーマンスやコストの観点で非常に不向きです。Google Cloud上にためたデータをWebアプリケーション上で活用したいといったケースにも対応する場合は、Cloud SQLやFirestoreといった別のデータストアサービスも利用しています。 Cloud SQLは、RDBMSを利用できるマネージドサービスで、蓄積したデータをWebアプリケーション上で閲覧したり編集したりするために用いています。Webアプリケーションは同じくGoogle Cloud上のApp Engineで構築しています。BigQueryは、Cloud SQLへの接続設定をすることで、BigQuery上のデータとCloud SQL上のデータをSQLで結合して利用することもできるので非常に使い勝手が良いです。 Firestoreは、構成が複雑な構造化データをシステム化するために用いたNoSQLのデータストアです。RDBMSで表現がしにくい構造のデータ、項目数が多かったりばらつきがあったりして定義が難しいデータ等を用いたアプリケーション用に利用しています。 今後の課題 さて、今回ご紹介したデータ基盤ですが、今はデータの活用を推進していくフェーズとなっております。ビジネス上の課題を解決するためにデータ基盤を利用したり、○○のデータを集計したいといった話は多数いただきます。一方でデータの利用用途が部署ごとにバラバラで、結局は集計したいデータを一元で管理できていなかったり、あるいは既存のETLツールで組んだデータ連携処理+Excel上での集計処理から抜け出すことができなかったりと課題は多数あります。 データを活用する基盤がある今、更なる活用に向けて、私達データエンジニアも事業の理解と、データ活用の推進により力を入れていく必要があると感じています。 おわりに 本記事ではエス・エム・エスのキャリア事業におけるデータ基盤についての事例をご紹介しました。 私達のデータ基盤は、ご紹介した通りGoogle Cloudの技術をフルに活用しています。パブリッククラウドの知識やデータサイエンスの知識も勿論求められますが、ある程度の基盤が開発され、実際のデータの活用を推進していくフェーズの今、データを利用する人たちの課題を理解する力、そしてどういったデータを用いれば課題解決に繋がるのかを提案できる力も必要となっています。 エス・エム・エスは新しいメンバーを募集しています。 私達のチームは、筆者のようなデータエンジニアから、元は基幹アプリケーションの開発に携わっていたエンジニア、データやドメイン知識が豊富なプロダクトオーナーなど、様々なスキルセットを持ったメンバーで構成されています。データ活用を推進するBPR推進部として一丸となって日々の業務の課題の理解と解決に取り組んでいます。 弊社の事業に携わってみたい方、興味のある方は、ぜひこちらのページものぞいてみてください。 エス・エム・エス - エンジニア採用情報
アバター
「エス・エム・エス社員に訊いてみた」第三弾として、介護事業者向け経営支援サービス「カイポケ」の事業責任者である園田さんへのインタビュー記事をお届けします。 事業責任者の役割 本日はよろしくお願いします。はじめに、園田さんの担っている「事業責任者」という役割がどんな役割なのかを教えてください。 カイポケに関わる組織のうち、プロダクトマネジメントとエンジニアリングの部署は田辺さん( @sunaot )が担当で、それ以外のセールス、マーケティング、カスタマーサクセス、事業開発といった部署を僕が担当しています。事業責任者が担っているのは、 「顧客に提供する価値を最大化するために必要なことすべて」 です。事業の方針を立てて、必要になるリソースを調達して、プロダクトを市場に投下・フィットさせていくというのが一つの流れです。代表的には、調査をして事業計画を立てたり、人を採用したり、予算を取ってきたり、ステークホルダーと対話したりといった事柄となります。 取り組む事柄には、連続的なものと非連続的なものがあります。事業上のボトルネックを特定して解消し、また次にボトルネックになったところを特定して解消し……というのを繰り返すのは連続的な部分です。また、個々の担当者がそれぞれの担当分野を見ているのに対して、横断的に見ることのできる自分が事業全体を統合するというのも役割になります。一方で、非連続なことを考える場面というのもあります。ミッションやビジョンの定義といったタイムラインの長いものを考えたり、事業を再定義するといったことも、僕が担うことの多い仕事です。 カイポケはSaaSのソフトウェアだけでなく、金融事業やICT機器のレンタル事業、M&A仲介事業といった、ソフトウェアと非ソフトウェアの事業を統合的に提供しているので、サービス全体のポートフォリオマネジメントも重要です。新規のサービスを作ることよりも、「何かの機能を無くす、事業を撤退する」という意思決定をする方が、担当者としては難しい場合がありますが、プロダクト全体の観点をもって、一定のユーザーがいる中で「削ぎ落す」という意思決定をする事は自身にしかできないので、とても大事だと思っています。 これまでのキャリア ありがとうございます。事業責任者になるまではどのようにキャリアを歩んできたのですか? 一貫して、「必要なことは全部やる」という仕事の仕方をしています。新卒で小さなベンチャー企業に就職して、2年目くらいには一つの事業の責任者という立場になったので、色々やりました。電子書籍サービスの会社だったので、配信サイトのUI/UXの改善をやったり、コンテンツを増やすために版権を取りにいく動きをしたり、市場を広げるために営業に行ったりと、その時々にボトルネックになっている部分を解消しにいく動きをしていました。その後の転職先でも同じような動きをしていましたし、エス・エム・エスのグループ会社であるエムスリーキャリアやMIMSに在籍していたときもそうです。その後いちど起業をしていた時期もありました。 カイポケに関わるようになってから なるほど、エス・エム・エスには起業を経験されたあとで戻ってきたのですよね。戻ってきてからはどのようなことをして今に至っているのですか? カイポケには10程度のプロダクトラインが有るのですが、それぞれのLTV(ライフタイムバリュー)を把握している人はいなくて、リソース投下の優先順位も曖昧な状態でした。そこで、まず各部署からデータを取得して、各プロダクトのLTVを分析しました。 各部署の長にヒアリングをしていく中で、サイロ化していて、事業全体を統合して把握している人が存在しないことを課題に感じました。そのせいなのか、データが各グループ内に閉じていてデータの形態もそれぞれ独自(Excelがあったり、CSVファイルがあったり、あるいは時系列データで持っているところがある一方でスナップショット的なデータしかないところもあったり)で、横串で分析をする事が極めて困難になっていたのです。 こういった部分の改善と並行して、ボトルネックを探索的にみていって特定するという活動をしました。ちなみに、最近ではデータの民主化がだいぶ進んできています。データの民主化については以下の記事も見てみてください。 tech.bm-sms.co.jp その後、分析対象を広げていって、事業をこうしたら良いのでは?という提案を行い、最終的には、事業の責任者になりました。 PythonやSQLを使ったデータ分析の話 データの話が出ましたが、園田さんはPythonやSQLを使ってデータ分析などをバリバリできる人だと聞きました。どういうきっかけで身に付けたんですか? 僕は、別にデータ分析を専門としているわけではないのですが、事業のために必要なことをやるというスタンスで仕事をしていく中で、やはりデータ分析という所作は必要で、以前からよく行っていました。昔はExcelとVBAでやっていたのですが、大量データを扱うことが多かったので、SQLで分析するようになりました。Pythonで前処理を行うようになったのも似たような理由です。 カイポケの事業運営の特徴 カイポケの事業運営についてお話を伺います。データの話も出ていましたが、カイポケの事業運営の特徴を教えてください。 可能な限り「勘」で動くことを避けて、調査/分析→仮説構築→仮説検証→施策実行という流れにそって運営しています。そして、分析のアプローチの一つとして、現場に身を投じて感覚的に理解することと、データ分析の2つを重視しています。 前提として、カイポケが対象とする医療介護業界は、エンタメやBtoCのようなトレンドに左右される事業という性質は薄く、一定程度はStable(安定的)なマーケットです。プロダクトを作る側が何か新機軸を打ち出すということよりも、顧客のニーズを捉えてそこにソリューションで応えていくことの方が重要だと考えています。さらに、カイポケは既に多くの顧客に利用していただいていて、データも社内に相当程度蓄積されています。そのため、意思決定をするときにデータを活用するということがフィットしていると思います。また、マーケットの性格上、中小の法人の顧客が大多数を占めていて、特定の顧客に売上の多くを依存しているわけではないということも、データ分析の重みが増す要因です。 なるほど、プロダクト開発においてデータ分析を用いるというのは近年では当たり前になってきている印象もありますが、カイポケは特にそれがフィットしているという側面があるのですね。ここまでの話では机上の分析を重んじている印象がありますが、一方で事業所訪問などもよくしていますよね。 はい、プロダクトの使われる現場に行くことから得られる肌感覚も大切にしています。介護事業所などを訪問することはやはり大事で、僕自身も率先して行きますし、メンバーにも推奨しています。カイポケに関わるメンバーは若い人が多いので、介護事業所に縁のある人が少なく、実際にプロダクトが使われる場面のイメージを持ちづらいんです。いくら分析をしても、イメージが湧かないところに対して考えることというのは的を射ていないものになりがちです。たとえば、障害児通所支援事業所を訪れてみると、カイポケを使ってもらう端末としてタブレットはあまりフィットしないということがわかります。介護事業所と違ってそこにいるのは子どもなので、職員のタブレットで遊びたがってしまうんですよね。こういったことは現場に行くとわかるので、現場に行くことは問題解決のためには非常に有益なんです。また、現場に行くとやっぱりがんばろうという気持ちにもなれて、仕事にも熱がこもってきます。営業の方であれば当然現場には行くんですが、プロダクトマネージャーやエンジニアの方というのは普段の仕事の中では現場に行く機会は意図して行わないと少ないので、例えば「事業所に訪問したい!」とチャットで投げれば営業側ですぐに設定するようにするなど、各人が現場を訪問するハードルを極力下げるようにして、どんどんメンバーには訪問してもらえるようにしています。 カイポケのプロダクト開発の特徴 プロダクト開発という観点ではカイポケにはどんな特徴があるでしょうか? 先ほど言ったことと関連するのですが、SaaSらしいプロダクト開発をしているということは言えます。「SaaSらしい」というのは、Slerのように、特定の顧客のためにカスタマイズを頑張るのではなく、顧客全体にとってのValueを考え、最大公約数的にプロダクトを作っていくということです。そのようにして作ったプロダクトの仕様から外れる部分については、顧客の業務を可能な限りシステムに寄せてもらうという思想ですね。これが有効なのは、カイポケが対象としているマーケットが、中小の顧客が圧倒的多数を占めるマーケットだからです。もちろん顧客の声というのは重要なインプットではあるのですが、営業が大口の顧客と約束してきてしまったから特殊な機能を作らないといけない、みたいなことは避けています。特定の顧客のための例外処理的なものを実装するとシステムとしても複雑になってしまいますし、多数の顧客にとって使わない機能が存在していると、プロダクトの完成度はむしろ落ちると思っています。 また、カイポケで一番大事にしているのは顧客への提供価値を最大化することで、システム開発ですべて解決しようとはしていません。顧客の課題を解決するのにシステムでの対応が一番的確な場合はもちろんシステム開発をしますが、たとえばサポートでの支援で解決するというのも選択肢のひとつとして扱っています。システムという狭義のプロダクトに限らず、セールスやカスタマーサクセス、サポートや外部の協力会社といったエコシステム全体を「プロダクト」として捉えています。これはテクノロジーで全てをスマートに解決してやろうという志向の人からすると「え〜」と思うような側面かもしれませんが、介護や医療の現場のリアルで複雑な課題に対して、「現実解で解く」という面白さもあります。システム開発ではなく問題の解決が私たちの商売というわけです。 プロダクト開発部門との関係 エンジニアとしても、言われたものをただ作るというのは避けたいし、作るなら本当に顧客にとって必要なものを作りたいので、事業全体としてそういう方針だとやりやすいですね。園田さんと、プロダクトマネージャーやエンジニアの部門との関係はどういうふうになっているのですか? プロダクトマネージャーはプロダクトに責任を負います。何かを決めるという時に、エンジニアリングリーダーシップ、プロダクトリーダーシップ、ビジネスリーダーシップという3つに分けて考えるというのを田辺さんが社内で提唱しているのですが、僕はこのうちのビジネスリーダーシップのみを権限として持つようにしています。ですから、最終的にプロダクトのHOWの部分(どうするのか、どう作るのか)については、事業責任者である僕にも「こうしろ」という権限はありません。ビジネスの観点から、こういう背景があるとか、こういう未来予測になるとか、収益上こういう形にしたいとか、「こうするのがいいと思う」という意見はもちろん言います。しかし、最終的にそれをプロダクトとしてどのように表現するかは、プロダクトリーダーシップの担い手であるプロダクトマネージャーが責任を負っていますし、技術的にどのように実現するかはエンジニアリングリーダーシップを担うエンジニアが責任を負います。ここは完全に権限が委譲されている状態です。たとえば、プロダクトマネージャーの決めたロードマップに対して僕が何かを差し込むというのはできないようになっています。 なぜそうしているかというと、何かを最終的に決める人というのを1人に決めておくことによって色々なことがスムーズにいくと考えているからです。これはプロダクト開発部門に対してだけでなく、セールスやマーケティングといった事業部門のメンバーに対してもそうで、僕は「ここはあなたに100%委譲したので僕は決められません、あなたが決めてください」とよく言っています。これは元々の僕のマネジメントスタイルで、そこと先ほどの田辺さんの言っている3つのリーダーシップの話はマッチしていたので今の組織運営はそういう形になっています。 エンジニアへのメッセージ 最後に、エンジニアに対して伝えたいことはありますか? 理想としては、エンジニアの方にも、顧客に対して価値をどう提供するかについてよりよい方法を提案してもらえると嬉しいです。もちろん最終的には実装する、ものづくりをするというのがエンジニアの職責だとは思うのですが、プロダクトマネージャーがプロダクトマネジメントトライアングルの全てを担うことはかなり難しいので、「いやそこはこう作った方が拡張性があるよ」や「そこは実装しなくていいんじゃないか」といった議論をエンジニアからもしてもらえるといいなと思います。そのためにはやはり、できれば顧客を理解してほしいなと思っています。 もちろん、エンジニアとしては、一番興味がある、やりがいを感じるのはそういう部分じゃなくて技術の部分だ、という人もいると思うので、みんながみんなそうなるとは思っていないのですが、もし顧客を理解してものづくりをするという部分にも興味を持っているエンジニアがいれば、とてもハッピーですし、そういう人にとってやりやすい環境を作る努力は惜しみません。社内でのコミュニケーションの場も用意しますし、顧客と話をする機会もアレンジします。事業所訪問のハードルを下げるというのもこういう意図でやっていることです。ですから、こういう動き方に興味がある人がいたら一緒に働けると嬉しいです。 ありがとうございました! (終)
アバター
介護事業者向け経営支援サービス「カイポケ」の開発をしている伊藤です。2019年4月に入社し、一貫してプロダクト開発部の介護レセチームでエンジニアとして活動しています。 本稿では私の所属する介護レセチームで実施している、システムの改善活動について気をつけていることや気がついたことをまとめます。 介護レセチームとは 介護レセチームは、介護事業者向け経営支援サービス「カイポケ」の介護領域の開発を担当するチームです。「カイポケを使った介護業務をノンストレスで行えるようにすることで利用者に向きあうゆとりを増やすこと」をチームのミッションに掲げ、日々カイポケの機能開発などを実施しています。 カイポケでは介護事業所の経営を支援するための様々な機能を提供していますが、コア機能の1つとして、介護事業所が収入を得るために必要となる請求業務の支援機能があります (請求業務はレセプト業務とも呼ばれ、介護レセチームの名前の由来にもなっています)。この請求業務の支援機能は、2000年から施行されている介護保険制度のルールに依存する機能です。この介護保険制度は数年ごとに「介護報酬改定」と呼ばれる見直しが実施され、様々な改正が行われます。介護報酬改定についての詳細はこちらの過去のブログをご参照ください。 tech.bm-sms.co.jp 介護報酬改定の改定内容をカイポケへ素早く反映し、ユーザへ価値を届ける必要があるため、介護レセチームでは普段からリファクタリングなどのシステム改善活動に取り組んでいます。 介護レセチームでのシステムの改善活動について システムの改善を行うにあたって、まずは一般的によいとされている知識をインプットすることから始めるのがよいと考えています。システム改善の理想的なゴールを思い描きゴールに向かって効率的に進むために、まずは知識や方法論の理解が必要で、これは大前提になると思います。余談ですが、介護レセチームは技術知識のインプットに意欲的なメンバーが多く、毎週実施しているふりかえりの中で、だいたい誰かしらが読み終えた技術書の紹介をしてくれます。 介護レセチームでは、新しい機能開発を実施する場合でもすぐに改修を実装するのではなく、リファクタリングから始めます。修正対象のモジュールにテストがない場合はまずテストコードを書き、リファクタリング対象の振る舞いを壊さないよう保護したうえでリファクタリングを実施しています。 リファクタリングを実施する上での方法論の詳細については、本稿での説明は控えますが以下の書籍が参考になると思います。 www.seshop.com このように特別なことをしているわけではないのですが、介護レセチームはこれらの改善活動を継続的に実施しています。 システムの継続的な改善活動 システムの改善は長丁場の作業になりがちです。秘孔のような改善ポイントを見つけて、そこを修正するだけで一発で理想に到達できれば理想的なのですが、なかなかそうはいきません。特にカイポケでは、前述のとおり介護報酬改定に起因する開発を実施することがあり、スケジュールの締め切りが事実上決まっていることが度々あるので、毎回改善活動を理想的なゴールまで持っていけるわけではありません。 しかし、一発で理想にたどり着けないからといって何も改善しなければ、文字通り何も改善することはできません。一発で理想にたどり着けないのならば、今回の対応で最低限どこまで改善するのかチームで認識を合わせ、少しずつ理想に近づける作業を継続的に続ける必要があります。 繰り返しになりますが、システムの改善を実施する上では効率的に改善を実施するための知識や方法論を知っておくことは大前提です (もしそれらが足りていないようなら、知識や方法論をインプットすることから始めるのがよいと思います)。しかし、継続的に改善のサイクルを回していくためには知識や方法論を知っているだけでは不十分です。システムの改善活動は、ユーザからは目に見える改善につながらない場合も多くあります。そのため、継続的に取り組むためには改善の必要性について関係者(開発チームのメンバーやプロダクトマネージャー、QAチームのメンバーなど)の間で共通認識を作り、皆の目線をあわせた上で、粘り強く継続的に取り組むことが大切です。 無力感に気づき、無力感と向きあう ある程度の規模のサービスに関わっていると、大量の課題に優先順位をつけて順番に対応していくことが多いと思いますが、介護レセチームでも各課題を評価した上で優先度順に対応を行っています。 しかし、ある時一歩下がって課題全体の消化状況を見てみると、消化できているのは緊急性が高い課題ばかりになっていることに気がつきました。反面、やればすぐに対応できる課題や重要だが緊急性の高くない課題が積み上がっている状態になっていました。 振り返ってみると、前述の通りカイポケではスケジュールの締め切りが事実上決まっていることが度々あるため、緊急性の高い課題を締め切りに間に合わせることにフォーカスすることが多く、やればすぐに対応できる課題や重要だが緊急性の高くない課題に手をつけにくい雰囲気になってしまっていたように思います。 結果として、「自分たちはこんな簡単なことにも着手できないのか……」、「サービスの将来にとって重要な課題に着手できない……」といった、もやもやとした無力感がチームに溜まった状態になっていました。 介護レセチームでは、開発メンバーとプロダクトマネージャーで課題の扱い方について対話を重ねる中で、自分たちが無力感を感じていることに気づきました。無力感に気づいてから、開発メンバーとプロダクトマネージャーの間で、「無力感を放っておくとメンバーの日々の活動に対するモチベーション維持やキャリア形成(技術/業務スキルの取得や、技術/業務スキルを活かした業務経験を得ることなど)にネガティブな影響があり、各メンバーに介護レセチームで長く生き生きと活躍してもらうことが難しくなるのでは?」という懸念について認識をすり合わせることができました。 現在では以下のように課題を扱っています。 課題の優先度を決める際には以下のように評価を行う 緊急性の高い課題の緊急度合いにも濃淡があるので、対応しなかった場合、サービスにどの程度影響があるのか個別に評価する 課題の緊急性だけでなくサービスにとっての将来的な重要性や、対応した際にチームが得られる学びや経験の多さなども評価する やればすぐに対応できる課題については、新しいメンバーが加入した時の最初のタスクにしたり、手が空いたときに着手する課題としてチームで認識を共有しておくことで消化しやすくする 無力感を認識することで緊急性以外の観点から課題を扱えるようになり、少しずつこれまで積極的に実施できていなかったサービスのデリバリー方法の改善などの改善施策にも取り組めるようになってきています。 活動をチームで粘り強く継続的に続けるには、チームメンバーが消耗せず、生き生きと活躍できる状態に近づけることも必要だと思います。そのために、無力感のような自分たちの負の感情と向きあい、どのようにそれを扱っていくのか明確にすることも重要だと感じています。 小さい歩みを続けるために ここまでシステムの改善活動は継続性が大切だと繰り返してきましたが、現実問題として、スケジュールの期限がある中である課題の対応中に思わぬ問題が見つかったり、他の緊急性の高い対応依頼が飛び込んできたりすることもあるわけで、地道な活動を日々継続的に続けるのが苦しいときもあります。 私は苦しい状況になればなるほど、関係者の間で活動の必要性についての共通認識ができていて目線が揃っているかが試されると考えています。目線の揃ったチームは苦しい状況でも皆で励ましあいながら前に進むことができ、改善がうまくいったときには皆で互いに感謝しあい、喜びあうことができます。そしてまた次の改善活動に取り組むことができるのです。 介護レセチームの改善活動はまだまだ道半ばです。私達は一緒に喜びを分かちあい、励ましあいながら改善を進めてくれる仲間を探しています。関心を持ってくださった方は、ぜひ末尾のカジュアル面談のリンクから話を聞きにきてください。 最後に、私が尊敬する元プロ野球選手のイチローさんの言葉で本稿を終わろうと思います。 小さなことでも満足感、満足することっていうのはすごく大事なことだと思うんですよね。だから、僕は今日のこの瞬間とても満足ですし、それは味わうとまた次へのやる気、モチベーションが生まれてくると僕はこれまでの経験上信じているので。これからもそうでありたいと思っています。 (出典: 国際情勢研究会『イチロー 会見全文』 /「 第1章 メジャー通算3000本安打達成会見(全文)」p.19)
アバター
こんにちは、エス・エム・エスのフロントエンドエンジニアの城内です。 前職では、ヘルスケア系のスタートアップでソフトウェアエンジニアをしていましたが、2022年8月にエス・エム・エスへ入社し、介護事業者向けの経営支援サービス「カイポケ」の改善をするチームに所属しています。 カイポケの改善を進める開発チームでは、この度フロントエンド専任チームを立ち上げました。この記事ではフロントエンドチーム立ち上げの背景や、チームの立ち上げから進めてきた技術選定について書きたいと思います。 カイポケ改善プロジェクト 介護事業者向けの経営支援サービス「カイポケ」は、4万を超える事業所で導入されている SaaS 型のサービスです。介護事業には様々なサービスの種類(ex. 居宅介護支援、通所介護支援、訪問介護支援、etc…)があり、カイポケはそれぞれのサービス種類に対応した約40のサービス・機能を提供しています。カイポケは15年以上の歴史の中で、各サービス種類への対応や法改正に合わせてシステムを拡張してきました。カイポケはモノリシックなアプリケーションとして構築されてきたので、拡張に次ぐ拡張で様々な機能が複雑に絡み合う大きなプロダクトになっています。 高齢化が進む日本社会において、介護領域では制度改正や改正に伴う現場対応など、目まぐるしい変化が起こり続けています。システムとしても、こういった介護業界の変化や数年に一度行われる法改正へ対応していくことが求められます。今のカイポケは現在の介護事業を支えるプロダクトになっていますが、長期的なタイムスパンでこれらの法改正などの要望に応えていくことを考えた場合に、より良いシステムのアーキテクチャにできないかと改善プロジェクトのチームで検討を重ねてきました。そういった背景から、システムを改修する際の修正範囲の局所化と新しいサービス種類への対応などといった拡張性の担保、生産性向上のための並列性の確保を目的にマイクロサービスアーキテクチャへ移行を進める改善プロジェクトが始まりました。 カイポケの規模では一度に全てのサービスをマイクロサービスに移行するのは現実的でないため、今回の改善プロジェクトでは特定のサービス種類に対象を絞って少しずつリリースをして、小さく早くユーザーからのフィードバックを回して確実に価値を積み上げていく方針を取ることにしました。 フロントエンドチームの発足 当初想定していた改善プロジェクトの開発チームの体制は、ドメインごとに適切な単位でマイクロサービスに切り出し、各サービスごとに同じエンジニアがバックエンド・フロントエンドを実装する形でした。しかし、カイポケのサイト全体の情報設計を再検討しユーザビリティテストを行った結果、カイポケはドメイン単位ではなく様々なユースケース単位(ex. 経営者、ケアマネージャ、サービス事業者、etc…)で利用されていることが見えてきました。さらに、ユースケース起点でシステムを分割することで情報設計の複雑さが改善されナビゲーションがシンプルになるということがわかり、ユーザーインターフェイスとなるフロントエンドについてはユースケース単位で分割する方針になりました。 この変更によってドメイン単位で分割したバックエンドとユースケース単位で分割したフロントエンドでシステムの境界が異なることになり、従来通り一つのチームでバックエンドとフロントエンドを実装するチーム構成が合わなくなってきたため、バックエンドとフロントエンドのチームを分割し、フロントエンド専任のチームが発足することになりました。 全体的なシステムの構成としては、バックエンドとフロントエンドの間には (GraphQL)を配置して疎結合にし、フロントエンドからは Gateway を通して横断的にバックエンドの API を呼び出せる構成を取っています。 フロントエンドチームはまだ発足して数ヶ月で、今はチームビルディングやフロントエンドで採用する技術選定を進めているフェーズになります。 ここからはカイポケの改善活動でフロントエンドに求められる開発の要件と技術選定について紹介します。 改善プロジェクトのフロントエンドにおける要件 現在のカイポケの UI は1000ページ以上ある大規模なプロダクトです。 昨今のフロントエンド開発では、コンポーネントを組み合わせて画面を構築する手法が一般化しましたが、カイポケの改善プロジェクトも例外ではありません。複数の画面で利用するコンポーネントは適切な単位で共通化することはもちろんですが、コンポーネント数が増えても破綻しないような一貫性と拡張性を持った設計が求められます。 また、現在のカイポケは長年の機能追加によって改修の影響範囲が見えにくくなり部分的な改修が続いた結果、全体的に統一感のない UI や少しずつ機能の違う同じようなページが増えていきました。今回の改善プロジェクトでは、ユースケースを元にフロントエンドを分解し、それぞれのユースケースに適切な UX を目指すとともに、カイポケとして一貫したユーザー体験を提供するために統一感のある UI の開発も求められています。 技術選定 言語 技術選定する上で最初に考えたのは、型を中心に据えることです。 カイポケは巨大なプロダクトであり、改善プロジェクトも長期にわたることが想定されています。小さく早くユーザーからのフィードバックを回して確実に価値を積み上げていく方針のため、継続的な改善やリファクタリング等の活動も必要になります。こういったプロジェクトでは、静的型チェックによる整合性の検査、補完やリファクタリングを中心としたエディタの支援など、TypeScript を導入することによる生産性の向上は非常に大きいものがあります。 また、バックエンドとの通信には GraphQL を採用しました。 後述する GraphQL Code Generator を利用することで API のレスポンスに型がつくのはもちろん、フロントエンドとバックエンドでスキーマ定義の合意を取ることで、フロントエンドとしてはモックサーバーを利用して開発をし、バックエンドはスキーマを返すロジックを実装するという、スキーマ駆動で独立性高く並行して開発を進められるのも今回のプロジェクトのチーム体制にマッチしていました。 UI ライブラリ・フレームワーク UI ライブラリには、コンポーネント指向で画面を構築できること、TypeScript との親和性の高さやシェアの高さから React を採用しました。 React と合わせて、規約を持ち込むことによる生産性やパフォーマンスの向上を目的にフレームワークに Next.js を採用しました。pages 以下にファイルを配置すればルーティングされる仕組みが欲しかったのと、webpack の設定を隠蔽してくれたり、zero-configuration で TypeScript に対応できたり、v11.0.0 からは ESLint の設定がデフォルトで用意されたりと、プロジェクトの足回りを整えてくれる機能が備わっているので、ユーザーへの価値提供に繋がるアプリケーションの開発に集中することができています。 また、レンダリング方法として SSR / CSR / SSG を選べるのも Next.js の大きな特徴です。今回の改善プロジェクトでは CSR 中心で実装を進めていますが、将来的に SSR に対応したいページが出てきた場合にも対応できるように技術的な選択肢を残しておきたかったのも理由の1つです。 GraphQL Gateway との通信で利用する GraphQL クライアントは、 Apollo を採用しました。 バックエンドの各マイクロサービスの GraphQL スキーマをまとめるために Apollo Federation を採用しているのでそちらとの相性もありますが、GraphQL Code Generator で Apollo の Hooks を生成できる点も大きいです。フロントエンドは .graphql を書き、GraphQL Code Generator で各マイクロサービスの GraphQL スキーマから Hooks を含んだ生成ファイルを React Component が利用する形かつ型が一貫した状態で生成する仕組みを構築することができます。 UIフレームワーク 一般的な WEB アプリケーションで共通して使用する UI パーツについては車輪の再発明を避けたかったので、UI フレームワークを導入しました。MUI や Tailwind CSS などと比較した結果、以下の理由で Chakra UI を採用しました。 デザイナーが作成していたデザインとテイストが近い 標準でモーダルやフォーム、タブなどのコンポーネントが揃っている Color や Typography などのデザイントークンのルールが整備されている アクセシビリティが考慮されている Figma が提供されている Chakra UI は Color や Typography、Spacing などデザイントークンが JavaScript のオブジェクト形式で定義されており、これらを上書きすることでアプリケーション独自のスタイルを定義できる Theme という機構を持っています。今回のプロジェクトでは、統一感のある UI を作っていくためにデザイナーと連携してデザインシステムの構築を進めていて、デザイントークンの定義にこの機構を利用しています。 Chakra UI の Theme の機構とカイポケで作りたいデザインシステムのルールがマッチしないのではないかという懸念もありましたが、プリミティブトークンとセマンティックトークンを分けて定義できたり、プリセットで定義している Color や Typography、Spacing などのバリエーションが今回のプロジェクトのデザインシステムにおいては必要十分と判断して採用を決めました。 Figma も提供されているので、Chakra UI のコンポーネントをデザイナーが作成した Figma に取り込んでもらい、Chakra UI の Props と Figma の Component Properties を一致させた形でデザインシステムを整えていただいています。こういった形でデザイナーとエンジニアでデザイントークンやコンポーネントの粒度についての認識を揃えながら、デザインシステムを育てていっています。 その他の主要ライブラリ state management: Recoil form: React Hook Form schema validator: Zod component catalog: Storybook test: Jest test library: Testing Library 今後はE2E テストや Figma で更新されたデザイントークンをフロントエンド側に自動更新する仕組みなども整備していこうと考えています。 技術顧問 ここまで書いてきた技術選定は、私を含めたフロントエンドチームのメンバーと相談して決めていますが、チームメンバーがナレッジを持っていない領域のこともあります。そういった際に相談できる場として、Japan Node.js Association 代表理事の古川陽介さん( @yosuke_furukawa )に技術顧問として参画いただいています。中長期的な視野に沿った技術選定や、プロジェクトの進め方に関するアドバイスなどをいただけるので、チームやプロジェクトにあったちょうど良い技術選定ができていると感じています。こういった技術的なサポートがある環境なので、チームメンバーも疑問点を解消しながら安心感を持ってプロジェクトを進められています。 おわりに この記事では、介護事業者向け経営支援サービス「カイポケ」を改善するフロントエンドチームの発足の背景と技術選定の一部を紹介しました。 エス・エム・エスでは開発メンバーを募集しています。カイポケの開発に興味を持ったり、チャレンジしてみたいという方がいれば、ぜひこちらも覗いてみてください。またカジュアルに話だけ聞いてみたい、といった方も大歓迎です。こちらのページよりお気軽にご連絡ください!
アバター
この記事は、「MySQL Advent Calendar 2022」の13日目の記事です。 qiita.com 株式会社エス・エム・エスでエンジニアをしている @koma_koma_d です。今回はMySQLにおけるセミジョイン最適化について調べた内容を書きます。 ※記載内容に誤りなどがある場合は筆者のTwitter宛に連絡をいただけると幸いです。 前置き この記事で書くこと この記事では、MySQLにおけるセミジョイン最適化について、サンプルテーブルを用いた実行例を示しながら、 セミジョイン最適化とは何か セミジョイン最適化はなぜ有用か セミジョイン最適化にはどのような種類があるのか どのようにクエリやテーブル定義を変えると戦略が変化するか どのような実行計画になるか どのようなオプティマイザトレースになるか などを紹介します。 執筆にあたって、Web上のリソースなどをある程度調べましたが、自分が気になった上記のような内容を網羅しているものが見当たらなかったので、誰かの役に立つかもと思って書いています。 この記事で書かないこと MySQLの公式ドキュメントを読むだけでわかること 公式ドキュメントを読むだけでわかることについては、この記事では書きません(必要に応じて公式ドキュメントから引用をする場合はあります)。 MySQL :: MySQL 8.0 リファレンスマニュアル :: 8.2.2 サブクエリー、導出テーブル、ビュー参照および共通テーブル式の最適化 MySQL :: MySQL 8.0 Reference Manual :: 8.2.2.1 Optimizing IN and EXISTS Subquery Predicates with Semijoin Transformations コードリーディングに基づいた知見 MySQLのコードはGitHubで公開されているので、コードを読むことで動作を読み解くことも可能ではありますが、筆者にはその技量まではないので今回は対象外です。 GitHub - mysql/mysql-server: MySQL Server, the world's most popular open source database, and MySQL Cluster, a real-time, open source transactional database. MySQLのバージョン間の比較 MySQLはセミジョイン最適化が導入された後も進歩を続けており、その過程でセミジョイン最適化に関連するバージョンアップも行われているようですが、それらについて細かく言及することは今回の記事ではしません。 他のRDBMSとの比較 Oracleなどの他のRDBMSにも類似した最適化があるようですが、それらとの比較は今回は対象外とします。 セミジョイン最適化概説 前置きが長くなりましたが、本題に入っていきます。今回のテーマであるセミジョイン最適化は、MySQL 5.6 からサブクエリの実行に関する最適化として導入されたものです。まず、なぜセミジョイン最適化が「最適化」たりうるのか、パフォーマンス的に嬉しいのかを説明します。 なぜセミジョイン最適化がパフォーマンス的に嬉しいのか? 本来、サブクエリはメインクエリに従属しており、サブクエリ側からはメインクエリ側のカラムを参照できます(相関サブクエリはこれを利用したもの)。サブクエリ側からメインクエリ側のカラムを参照できるということは、メインクエリ側のテーブルが先にアクセスされるということです。 しかし、セミジョイン最適化が行われると、結合(JOIN)として処理することになるので、①先にサブクエリ側のテーブルにアクセスすることが可能になります。このため、サブクエリ側のテーブルに先にアクセスした方が効率が良い場合には、パフォーマンス上のメリットを享受できる可能性があります。 『詳解MySQL 5.7』 では以下のように記載されています。 MySQL 5.6 では、 IN サブクエリを SEMIJOIN という特殊な JOIN へと変換することで、より効率的な実行計画が選択されるようになった。SEMIJOINとは、駆動表の1行に対して内部表からマッチする行が1行だけになるという特殊な結果を産むJOINである。 SEMIJOINがなぜunique_subqueryやindex_subqueryより優れているかというと、テーブルをJOINする順序を入れ替えられるからである。サブクエリ内でアクセスされるテーブルを先にアクセスするような実行計画のほうが効率的なものになるケースは少なくない。 (『詳解MySQL 5.7』p.110) www.shoeisha.co.jp 更に、セミジョイン最適化を適用したクエリは、通常の結合では必ずしも満たされていない条件である、 最終的な結果にサブクエリ側のカラムが含まれない メインクエリ側の1行に対してサブクエリ側の複数行をマッチさせない(マッチすることを考慮しなくてよい) という条件を満たすため、②通常の結合では取ることのできない処理の効率化を行うことができます。(MySQLに焦点を当てた記述ではありませんが) 『SQL実践入門』 では以下のように記載されています。 「Semi-Join」は日本語では「準結合」または「半結合」と呼ばれています。これは通常の結合の際には現れない、EXISTS述語(とIN述語)を使ったときに特有のアルゴリズムです。 このアルゴリズムの特徴は次の2つです。 機能的には、結果には駆動表となるテーブルのデータしか含まれず、しかも1行につき必ず1行しか結果が生成されない(通常の結合の場合、1対Nの結合の場合は行数が増えることがある) 内部表にマッチする行を1行でも発見した時点で残りの行の検索を打ち切れるため、通常の結合よりもパフォーマンスが良い (『SQL実践入門』p.341) gihyo.jp 以上で記載した、 ①先にサブクエリ側のテーブルにアクセスすることが可能 ②通常の結合では取ることのできない処理の効率化を行うことができる という2つの特性をどのように活かすかが、次に紹介するそれぞれのセミジョインの戦略で違ってきます。 セミジョインにはどのような「戦略」があるのか? セミジョインには、いくつかの種類があります。MySQLではそれらを「戦略(Strategy)」と表現しています。各戦略の特徴は、以下のように整理できます。 なお、表中の「重複の除去」は、先述の「メインクエリ側の1行に対してサブクエリ側の複数行をマッチさせない(マッチすることを考慮しなくてよい)」という側面に関するもので、メインクエリ側の1行がサブクエリ結果との結合によって最終的な結果の中で重複しない理由、重複させない方法を記載しています。 戦略の名称 サブクエリ側の結合キー列のINDEXの必要性 駆動表と内部表 重複の除去 Table Pullout UNIQUE制約必要 可変 UNIQUE制約により保証 LooseScan 必要 サブクエリ側が駆動表 インデックスを活用しながら結合時に実施 Materialization 不要(一時表で自動作成) 可変 サブクエリ実体化時に除去 Duplicate Weedout 不要 可変 結果を返す前に除去 FirstMatch 不要 メインクエリ側が駆動表 結合時に除去 以下、個別に補足説明を加えます。記載している内容は参考資料に依拠しているほか、サンプルテーブルを使った実行例から分かる内容を記載しています。 主な参考資料は以下の2つです。 MySQL道普請便り 第43回 MySQLの準結合(セミジョイン)について セミジョインについての親切な紹介。駆動表と内部表がどうなるかはこちらに主に依拠した。 MariaDB 10.6 [日本語] 最適化とチューニング セミジョイン副問い合わせの最適化 (MySQLではなくMariaDBですが)それぞれの戦略が図付きで解説されていてわかりやすい。 各戦略の特徴 ここからは、上で記載した表の内容を戦略ごとに補足していきます。各戦略について細かく論じていくにあたって、サンプルテーブルを用いて実際に実行計画やオプティマイザトレースを取得した結果を随時示します。実行計画やオプティマイザトレースについては、実物を示すのが最も参考になると思いましたので、厚め(実行計画は全部、オプティマイザトレースは一部抜粋)に載せましたが、記事が長くなってしまうので折りたたんでいます。展開したい場合は「▶︎詳細」となっているところをクリックしてください。 サンプルテーブルの前提 サンプルテーブルの前提条件を記載しておきます。 使用したMySQLのバージョン MySQL 8.0.21 ※現時点での最新は 8.0.31 です。たまたま調べようと思ったタイミングでローカルに入っていたのがこのバージョン( 以前個人ブログの方に書いた記事 の検証で使ったバージョンだった)だったために過ぎません。 サンプルテーブル CREATE TABLE `employee` ( `emp_id` int unsigned NOT NULL AUTO_INCREMENT, `main_floor` int unsigned DEFAULT NULL , `gender` int unsigned DEFAULT NULL , PRIMARY KEY (`emp_id`), ) ENGINE=InnoDB; CREATE TABLE `department` ( `dept_id` int unsigned NOT NULL AUTO_INCREMENT, `main_floor` int unsigned DEFAULT NULL , `dept_name` varchar ( 100 ) DEFAULT NULL , PRIMARY KEY (`dept_id`) ) ENGINE=InnoDB; データ内容(クリックで展開) employee テーブル emp_id main_floor gender 1 1 1 2 2 2 3 3 3 4 4 1 5 5 2 6 6 3 7 7 1 8 8 2 9 9 3 10 10 1 11 11 2 12 12 3 13 13 1 14 14 2 15 15 3 16 16 1 17 17 2 18 18 3 19 1 1 20 2 2 21 3 3 22 4 1 23 5 2 24 6 3 25 7 1 26 8 2 27 9 3 28 10 1 29 11 2 30 12 3 31 13 1 32 14 2 33 15 3 34 16 1 35 17 2 36 18 3 department テーブル dept_id main_floor dept_name 1 1 Finance 2 2 Legal 3 3 Human Resorces 4 4 Corporate Planning 5 5 Sales 1 6 6 Sales 2 7 7 Accounting 8 8 Development 1 9 9 Development 2 Table Pullout Table Pullout 戦略は、以下のような特徴を持ちます。 通常のJOINとして処理する 結合キーにUNIQUE制約(PRIMARY KEY含む)がある場合に利用可能 メインクエリの結果1行に対してサブクエリから0or1行しか返らないことが保証されているので、通常のJOINにして結合順序を入れ替えてもメインクエリ側の行が最終結果の中で重複することがない サンプルテーブルを用いた実行例から分かることは以下の通りです。 department 表の main_floor 列にUNIQUE制約を付与したところ選択された オプティマイザトレースの pulled_out_semijoin_tables という項目に department 表が表れている EXPLAIN ANALYZE をみると、 Remove duplicates from ... という重複除去を表す情報がない ここが後述のLooseScanとの違い SELECT * FROM employee e WHERE e.main_floor IN ( SELECT main_floor FROM department d ) 実行計画 mysql> explain select * from employee e where e.main_floor in ( select main_floor from department d ); +----+-------------+-------+------------+-------+---------------------------+---------------------------+---------+----------------------+------+----------+--------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+-------+---------------------------+---------------------------+---------+----------------------+------+----------+--------------------------+ | 1 | SIMPLE | d | NULL | index | department_main_floor_IDX | department_main_floor_IDX | 5 | NULL | 9 | 100.00 | Using where ; Using index | | 1 | SIMPLE | e | NULL | ref | employee_main_floor_IDX | employee_main_floor_IDX | 5 | sandbox.d.main_floor | 2 | 100.00 | NULL | +----+-------------+-------+------------+-------+---------------------------+---------------------------+---------+----------------------+------+----------+--------------------------+ 2 rows in set, 1 warning ( 0.00 sec) Note (Code 1003 ): /* select#1 */ select `sandbox`.`e`.`emp_id` AS `emp_id`,`sandbox`.`e`.`main_floor` AS `main_floor`,`sandbox`.`e`.`gender` AS `gender` from `sandbox`.`department` `d` join `sandbox`.`employee` `e` where (`sandbox`.`e`.`main_floor` = `sandbox`.`d`.`main_floor`) mysql> explain analyze select * from employee e where e.main_floor in ( select main_floor from department d )\G *************************** 1 . row *************************** EXPLAIN : -> Nested loop inner join (cost= 7.45 rows = 18 ) (actual time = 0.040 .. 0.078 rows = 18 loops= 1 ) -> Filter: (d.main_floor is not null ) (cost= 1.15 rows = 9 ) (actual time = 0.023 .. 0.026 rows = 9 loops= 1 ) -> Index scan on d using department_main_floor_IDX (cost= 1.15 rows = 9 ) (actual time = 0.022 .. 0.024 rows = 9 loops= 1 ) -> Index lookup on e using employee_main_floor_IDX (main_floor=d.main_floor) (cost= 0.52 rows = 2 ) (actual time = 0.005 .. 0.005 rows = 2 loops= 9 ) オプティマイザトレース(一部抜粋) { "steps" : [ { "join_preparation" : { "select#" : 1, "steps" : [ // 中略 { "transformations_to_nested_joins" : { "transformations" : [ "semijoin" ] , "expanded_query" : "/* select#1 */ select `e`.`emp_id` AS `emp_id`,`e`.`main_floor` AS `main_floor`,`e`.`gender` AS `gender` from `employee` `e` semi join (`department` `d`) where ((`e`.`main_floor` = `d`.`main_floor`))" } } ] } } , { "join_optimization" : { "select#" : 1, "steps" : [ // 中略 { "pulled_out_semijoin_tables" : [ { "table" : "`department` `d`" , "functionally_dependent" : true } ] } , // 中略 ] } } , { "join_explain" : { "select#" : 1, "steps" : [ ] } } ] } LooseScan LooseScan 戦略は以下のような特徴を持ちます。 サブクエリ側のテーブルの結合キーのカラムにインデックスがある場合に利用可能 ※UNIQUE制約がついていれば Table Pullout も使えることになる サブクエリ側のインデックスを重複を避けながらスキャンしていく サブクエリ側が必ず駆動表になる 重複の除去を、サブクエリのインデックスを使って実現するため サンプルテーブルを用いた実行例から分かることは以下の通りです。 department 表の main_floor 列にインデックスを追加したところ、LooseScan が候補に上がるようになった UNIQUE制約をつけると Table Pullout も利用可能になる オプティマイザトレースの final_semijoin_strategy が LooseScan 実行計画の Extra 列に LooseScan と表示 EXPLAIN ANALYZE をみると、 Remove duplicates from ... という重複除去を表す情報がある ここがTable Pullout との違いになる SELECT * FROM employee e WHERE e.main_floor IN ( SELECT main_floor FROM department d ) ※ Table Pullout と同じクエリ 実行計画 mysql> explain select * from employee e where e.main_floor in ( select main_floor from department d ); +----+-------------+-------+------------+-------+---------------------------+---------------------------+---------+----------------------+------+----------+-------------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+-------+---------------------------+---------------------------+---------+----------------------+------+----------+-------------------------------------+ | 1 | SIMPLE | d | NULL | index | department_main_floor_IDX | department_main_floor_IDX | 5 | NULL | 9 | 100.00 | Using where ; Using index ; LooseScan | | 1 | SIMPLE | e | NULL | ref | employee_main_floor_IDX | employee_main_floor_IDX | 5 | sandbox.d.main_floor | 2 | 100.00 | NULL | +----+-------------+-------+------------+-------+---------------------------+---------------------------+---------+----------------------+------+----------+-------------------------------------+ 2 rows in set, 1 warning ( 0.00 sec) Note (Code 1003 ): /* select#1 */ select `sandbox`.`e`.`emp_id` AS `emp_id`,`sandbox`.`e`.`main_floor` AS `main_floor`,`sandbox`.`e`.`gender` AS `gender` from `sandbox`.`employee` `e` semi join (`sandbox`.`department` `d`) where (`sandbox`.`e`.`main_floor` = `sandbox`.`d`.`main_floor`) mysql> explain analyze select * from employee e where e.main_floor in ( select main_floor from department d )\G *************************** 1 . row *************************** EXPLAIN : -> Nested loop inner join (actual time = 0.109 .. 0.141 rows = 18 loops= 1 ) -> Remove duplicates from input sorted on department_main_floor_IDX (actual time = 0.091 .. 0.095 rows = 9 loops= 1 ) -> Filter: (d.main_floor is not null ) (cost= 1.15 rows = 9 ) (actual time = 0.090 .. 0.093 rows = 9 loops= 1 ) -> Index scan on d using department_main_floor_IDX (cost= 1.15 rows = 9 ) (actual time = 0.089 .. 0.091 rows = 9 loops= 1 ) -> Index lookup on e using employee_main_floor_IDX (main_floor=d.main_floor) (cost= 4.70 rows = 2 ) (actual time = 0.004 .. 0.005 rows = 2 loops= 9 ) オプティマイザトレース(一部抜粋) { "steps" : [ { "join_preparation" : { "select#" : 1, "steps" : [ // 中略 { "transformations_to_nested_joins" : { "transformations" : [ "semijoin" ] , "expanded_query" : "/* select#1 */ select `e`.`emp_id` AS `emp_id`,`e`.`main_floor` AS `main_floor`,`e`.`gender` AS `gender` from `employee` `e` semi join (`department` `d`) where ((`e`.`main_floor` = `d`.`main_floor`))" } } ] } } , { "join_optimization" : { "select#" : 1, "steps" : [ // 中略 { "considered_execution_plans" : [ { "plan_prefix" : [ ] , "table" : "`department` `d`" , "best_access_path" : { "considered_access_paths" : [ { "access_type" : "ref" , "index" : "department_main_floor_IDX" , "usable" : false , "chosen" : false } , { "rows_to_scan" : 9, "filtering_effect" : [ ] , "final_filtering_effect" : 1, "access_type" : "scan" , "resulting_rows" : 9, "cost" : 1.15, "chosen" : true } ] } , "condition_filtering_pct" : 100, "rows_for_plan" : 9, "cost_for_plan" : 1.15, "semijoin_strategy_choice" : [ { "strategy" : "MaterializeScan" , "choice" : "deferred" } ] , "rest_of_plan" : [ { "plan_prefix" : [ "`department` `d`" ] , "table" : "`employee` `e`" , "best_access_path" : { "considered_access_paths" : [ { "access_type" : "ref" , "index" : "employee_main_floor_IDX" , "rows" : 2, "cost" : 6.3, "chosen" : true } , { "rows_to_scan" : 36, "filtering_effect" : [ ] , "final_filtering_effect" : 1, "access_type" : "scan" , "using_join_cache" : true , "buffers_needed" : 1, "resulting_rows" : 36, "cost" : 32.65, "chosen" : false } ] } , "condition_filtering_pct" : 100, "rows_for_plan" : 18, "cost_for_plan" : 7.45, "semijoin_strategy_choice" : [ { "strategy" : "LooseScan" , "recalculate_access_paths_and_cost" : { "tables" : [ { "table" : "`department` `d`" , "best_access_path" : { "considered_access_paths" : [ { "access_type" : "ref" , "index" : "department_main_floor_IDX" , "usable" : false , "chosen" : false } , { "rows_to_scan" : 9, "filtering_effect" : [ ] , "final_filtering_effect" : 1, "access_type" : "scan" , "resulting_rows" : 9, "cost" : 1.15, "chosen" : true } ] } , "unknown_key_1" : { "searching_loose_scan_index" : { "indexes" : [ { "index" : "department_main_floor_IDX" , "covering_scan" : { "cost" : 0.2522, "chosen" : true } } ] } } } ] } , "cost" : 7.4522, "rows" : 2, "chosen" : true } , { "strategy" : "MaterializeScan" , "recalculate_access_paths_and_cost" : { "tables" : [ { "table" : "`employee` `e`" , "best_access_path" : { "considered_access_paths" : [ { "access_type" : "ref" , "index" : "employee_main_floor_IDX" , "rows" : 2, "cost" : 6.3, "chosen" : true } , { "rows_to_scan" : 36, "filtering_effect" : [ ] , "final_filtering_effect" : 1, "access_type" : "scan" , "using_join_cache" : true , "buffers_needed" : 1, "resulting_rows" : 36, "cost" : 32.65, "chosen" : false } ] } } ] } , "cost" : 10.25, "rows" : 2, "duplicate_tables_left" : false , "chosen" : false } , { "strategy" : "DuplicatesWeedout" , "cost" : 12.05, "rows" : 18, "duplicate_tables_left" : false , "chosen" : false } ] , "chosen" : true } ] } , { "plan_prefix" : [ ] , "table" : "`employee` `e`" , "best_access_path" : { "considered_access_paths" : [ { "access_type" : "ref" , "index" : "employee_main_floor_IDX" , "usable" : false , "chosen" : false } , { "rows_to_scan" : 36, "filtering_effect" : [ ] , "final_filtering_effect" : 1, "access_type" : "scan" , "resulting_rows" : 36, "cost" : 3.85, "chosen" : true } ] } , "condition_filtering_pct" : 100, "rows_for_plan" : 36, "cost_for_plan" : 3.85, "semijoin_strategy_choice" : [ ] , "rest_of_plan" : [ { "plan_prefix" : [ "`employee` `e`" ] , "table" : "`department` `d`" , "best_access_path" : { "considered_access_paths" : [ { "access_type" : "ref" , "index" : "department_main_floor_IDX" , "rows" : 1, "cost" : 12.6, "chosen" : true } , { "access_type" : "scan" , "chosen" : false , "cause" : "covering_index_better_than_full_scan" } ] } , "condition_filtering_pct" : 100, "rows_for_plan" : 36, "cost_for_plan" : 16.45, "semijoin_strategy_choice" : [ { "strategy" : "FirstMatch" , "recalculate_access_paths_and_cost" : { "tables" : [ ] } , "cost" : 16.45, "rows" : 36, "chosen" : true } , { "strategy" : "MaterializeLookup" , "cost" : 10.5, "rows" : 36, "duplicate_tables_left" : false , "chosen" : true } , { "strategy" : "DuplicatesWeedout" , "cost" : 24.65, "rows" : 36, "duplicate_tables_left" : false , "chosen" : false } ] , "pruned_by_cost" : true } ] } , { "final_semijoin_strategy" : "LooseScan" , // 中略 } } ] } , //中略 ] } } , { "join_explain" : { "select#" : 1, "steps" : [ ] } } ] } Materialization Materialization 戦略は以下の特徴を持ちます。 サブクエリの結果を実体化して、結合キーにインデックスを作成して重複を取り除いてからメインクエリとJOINする ※結合キーのカラムにインデックスがあれば LooseScan が使えるし、UNIQUE制約がついていれば Table Pullout が使える ただし、コスト次第で他の戦略ではなくこちらが選択されることはある たとえば、サブクエリでWhere句での絞り込みをしていて、絞り込み後に実体化する Materialization の方が、LooseScan(JOIN時に絞り込みを行う)よりもコストが低くなるケースなど 作成されたインデックスのキーが <auto_key> という形で実行計画に現れることがある メインクエリ側とサブクエリ側のどちらが駆動表、内部表となるかはコストで決まる 実体化されたサブクエリ側が内部表になる場合が MaterializeLookup 実体化されたサブクエリ側が駆動表になる場合が MaterializeScan 参考: MySQL: Query Optimizer 4.MaterializeLookup (Materialize inner tables, then setup a scan over outer correlated tables, lookup in materialized table) 5.MaterializeScan (Materialize inner tables, then setup a scan over materialized tables, perform lookup in outer tables) サンプルテーブルを用いた実行例から分かることは以下の通りです。 これまでのクエリのサブクエリにWHERE句を追加したところ選択された オプティマイザトレースの final_semijoin_strategy が MaterializeScan 今回はサブクエリ側の方が駆動表になったパターン 実行計画の select_type に MATERIALIZED と表示 SELECT * FROM employee e WHERE e.main_floor IN ( SELECT main_floor FROM department d WHERE d.dept_name LIKE ' Sales% ' ) 実行計画 mysql> Explain select * from employee e where e.main_floor in ( select main_floor from department d where d.dept_name like 'Sales%' ); +----+--------------+-------------+------------+------+-------------------------+-------------------------+---------+------------------------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+--------------+-------------+------------+------+-------------------------+-------------------------+---------+------------------------+------+----------+-------------+ | 1 | SIMPLE | <subquery2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | 100.00 | Using where | | 1 | SIMPLE | e | NULL | ref | employee_main_floor_IDX | employee_main_floor_IDX | 5 | <subquery2>.main_floor | 2 | 100.00 | NULL | | 2 | MATERIALIZED | d | NULL | ALL | NULL | NULL | NULL | NULL | 9 | 11.11 | Using where | +----+--------------+-------------+------------+------+-------------------------+-------------------------+---------+------------------------+------+----------+-------------+ 3 rows in set, 1 warning ( 0.00 sec) Note (Code 1003 ): /* select#1 */ select `sandbox`.`e`.`emp_id` AS `emp_id`,`sandbox`.`e`.`main_floor` AS `main_floor`,`sandbox`.`e`.`gender` AS `gender` from `sandbox`.`employee` `e` semi join (`sandbox`.`department` `d`) where ((`sandbox`.`e`.`main_floor` = `<subquery2>`.`main_floor`) and (`sandbox`.`d`.`dept_name` like 'Sales%' )) オプティマイザトレース(一部抜粋) { "steps" : [ { "join_preparation" : { "select#" : 1, "steps" : [ // 中略 { "transformations_to_nested_joins" : { "transformations" : [ "semijoin" ] , "expanded_query" : "/* select#1 */ select `e`.`emp_id` AS `emp_id`,`e`.`main_floor` AS `main_floor`,`e`.`gender` AS `gender` from `employee` `e` semi join (`department` `d`) where ((`d`.`dept_name` like 'Sales%') and (`e`.`main_floor` = `d`.`main_floor`))" } } ] } } , { "join_optimization" : { "select#" : 1, "steps" : [ // 中略 { "considered_execution_plans" : [ { "plan_prefix" : [ ] , "table" : "`department` `d`" , "best_access_path" : { "considered_access_paths" : [ { "rows_to_scan" : 9, "filtering_effect" : [ ] , "final_filtering_effect" : 0.1111, "access_type" : "scan" , "resulting_rows" : 1, "cost" : 1.15, "chosen" : true } ] } , "condition_filtering_pct" : 100, "rows_for_plan" : 1, "cost_for_plan" : 1.15, "semijoin_strategy_choice" : [ { "strategy" : "MaterializeScan" , "choice" : "deferred" } ] , "rest_of_plan" : [ { "plan_prefix" : [ "`department` `d`" ] , "table" : "`employee` `e`" , "best_access_path" : { "considered_access_paths" : [ { "access_type" : "ref" , "index" : "employee_main_floor_IDX" , "rows" : 2, "cost" : 0.7, "chosen" : true } , { "rows_to_scan" : 36, "filtering_effect" : [ ] , "final_filtering_effect" : 1, "access_type" : "scan" , "using_join_cache" : true , "buffers_needed" : 1, "resulting_rows" : 36, "cost" : 3.8503, "chosen" : false } ] } , "condition_filtering_pct" : 100, "rows_for_plan" : 2, "cost_for_plan" : 1.85, "semijoin_strategy_choice" : [ { "strategy" : "MaterializeScan" , "recalculate_access_paths_and_cost" : { "tables" : [ { "table" : "`employee` `e`" , "best_access_path" : { "considered_access_paths" : [ { "access_type" : "ref" , "index" : "employee_main_floor_IDX" , "rows" : 2, "cost" : 0.7, "chosen" : true } , { "rows_to_scan" : 36, "filtering_effect" : [ ] , "final_filtering_effect" : 1, "access_type" : "scan" , "using_join_cache" : true , "buffers_needed" : 1, "resulting_rows" : 36, "cost" : 3.8503, "chosen" : false } ] } } ] } , "cost" : 3.05, "rows" : 2, "duplicate_tables_left" : true , "chosen" : true } , { "strategy" : "DuplicatesWeedout" , "cost" : 3.25, "rows" : 2, "duplicate_tables_left" : false , "chosen" : false } ] , "chosen" : true } ] } , { "plan_prefix" : [ ] , "table" : "`employee` `e`" , "best_access_path" : { "considered_access_paths" : [ { "access_type" : "ref" , "index" : "employee_main_floor_IDX" , "usable" : false , "chosen" : false } , { "rows_to_scan" : 36, "filtering_effect" : [ ] , "final_filtering_effect" : 1, "access_type" : "scan" , "resulting_rows" : 36, "cost" : 3.85, "chosen" : true } ] } , "condition_filtering_pct" : 100, "rows_for_plan" : 36, "cost_for_plan" : 3.85, "semijoin_strategy_choice" : [ ] , "pruned_by_cost" : true } , { "final_semijoin_strategy" : "MaterializeScan" , // 中略 } ] } , // 中略 ] } } , { "join_explain" : { "select#" : 1, "steps" : [ ] } } ] } Duplicate Weedout Duplicate Weedout には以下の特徴があります。 JOINしてから、一時テーブルを作成して重複を取り除く メインクエリの1行に対してサブクエリの結果が複数行マッチする場合には、JOINで処理することによって重複が発生するので、それを取り除くという戦略 JOIN時にメインクエリとサブクエリのどちらが駆動表・内部表となるかはコストによって決まる(どちらともありうる) 重複の除去を最後にするので、JOINはどちらを駆動表として行ってもよいため サンプルテーブルを用いた実行例から分かることは以下の通りです。 Materialization 戦略が選択されたときのクエリをベースとして、メインクエリ側に AND で条件を足している final_semijoin_strategy が DuplicateWeedout 実行計画の Extra 列に Start temporary と End temporary と表示 EXPLAIN ANALYZE では一番上に Remove duplicate e rows using temporary table (weedout) と表示 重複の除去を一時テーブルを用いて実施している SELECT * FROM employee e WHERE e.main_floor IN ( SELECT main_floor FROM department d WHERE d.dept_name LIKE ' Sales% ' ) AND gender = 2 ; 実行計画 mysql> explain select * from employee e where e.main_floor in ( select main_floor from department d where d.dept_name like 'Sales%' ) and gender = 2 ; +----+-------------+-------+------------+------+-------------------------+-------------------------+---------+----------------------+------+----------+------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+-------------------------+-------------------------+---------+----------------------+------+----------+------------------------------+ | 1 | SIMPLE | d | NULL | ALL | NULL | NULL | NULL | NULL | 9 | 11.11 | Using where ; Start temporary | | 1 | SIMPLE | e | NULL | ref | employee_main_floor_IDX | employee_main_floor_IDX | 5 | sandbox.d.main_floor | 2 | 10.00 | Using where ; End temporary | +----+-------------+-------+------------+------+-------------------------+-------------------------+---------+----------------------+------+----------+------------------------------+ 2 rows in set, 1 warning ( 0.00 sec) Note (Code 1003 ): /* select#1 */ select `sandbox`.`e`.`emp_id` AS `emp_id`,`sandbox`.`e`.`main_floor` AS `main_floor`,`sandbox`.`e`.`gender` AS `gender` from `sandbox`.`employee` `e` semi join (`sandbox`.`department` `d`) where ((`sandbox`.`e`.`main_floor` = `sandbox`.`d`.`main_floor`) and (`sandbox`.`e`.`gender` = 2 ) and (`sandbox`.`d`.`dept_name` like 'Sales%' )) mysql> explain analyze select * from employee e where e.main_floor in ( select main_floor from department d where d.dept_name like 'Sales%' ) and gender = 2 \G *************************** 1 . row *************************** EXPLAIN : -> Remove duplicate e rows using temporary table (weedout) (cost= 1.85 rows = 0 ) (actual time = 0.051 .. 0.062 rows = 2 loops= 1 ) -> Nested loop inner join (cost= 1.85 rows = 0 ) (actual time = 0.047 .. 0.057 rows = 2 loops= 1 ) -> Filter: ((d.dept_name like 'Sales%' ) and (d.main_floor is not null )) (cost= 1.15 rows = 1 ) (actual time = 0.027 .. 0.030 rows = 2 loops= 1 ) -> Table scan on d (cost= 1.15 rows = 9 ) (actual time = 0.022 .. 0.025 rows = 9 loops= 1 ) -> Filter: (e.gender = 2 ) (cost= 0.52 rows = 0 ) (actual time = 0.011 .. 0.012 rows = 1 loops= 2 ) -> Index lookup on e using employee_main_floor_IDX (main_floor=d.main_floor) (cost= 0.52 rows = 2 ) (actual time = 0.011 .. 0.012 rows = 2 loops= 2 ) オプティマイザトレース(一部抜粋) { "steps" : [ { "join_preparation" : { "select#" : 1, "steps" : [ // 中略 { "transformations_to_nested_joins" : { "transformations" : [ "semijoin" ] , "expanded_query" : "/* select#1 */ select `e`.`emp_id` AS `emp_id`,`e`.`main_floor` AS `main_floor`,`e`.`gender` AS `gender` from `employee` `e` semi join (`department` `d`) where ((`e`.`gender` = 2) and (`d`.`dept_name` like 'Sales%') and (`e`.`main_floor` = `d`.`main_floor`))" } } ] } } , { "join_optimization" : { "select#" : 1, "steps" : [ // 中略 { "considered_execution_plans" : [ { "plan_prefix" : [ ] , "table" : "`department` `d`" , "best_access_path" : { "considered_access_paths" : [ { "rows_to_scan" : 9, "filtering_effect" : [ ] , "final_filtering_effect" : 0.1111, "access_type" : "scan" , "resulting_rows" : 1, "cost" : 1.15, "chosen" : true } ] } , "condition_filtering_pct" : 100, "rows_for_plan" : 1, "cost_for_plan" : 1.15, "semijoin_strategy_choice" : [ { "strategy" : "MaterializeScan" , "choice" : "deferred" } ] , "rest_of_plan" : [ { "plan_prefix" : [ "`department` `d`" ] , "table" : "`employee` `e`" , "best_access_path" : { "considered_access_paths" : [ { "access_type" : "ref" , "index" : "employee_main_floor_IDX" , "rows" : 2, "cost" : 0.7, "chosen" : true } , { "rows_to_scan" : 36, "filtering_effect" : [ ] , "final_filtering_effect" : 0.1, "access_type" : "scan" , "using_join_cache" : true , "buffers_needed" : 1, "resulting_rows" : 3.6, "cost" : 3.8541, "chosen" : false } ] } , "condition_filtering_pct" : 10, "rows_for_plan" : 0.2, "cost_for_plan" : 1.85, "semijoin_strategy_choice" : [ { "strategy" : "MaterializeScan" , "recalculate_access_paths_and_cost" : { "tables" : [ { "table" : "`employee` `e`" , "best_access_path" : { "considered_access_paths" : [ { "access_type" : "ref" , "index" : "employee_main_floor_IDX" , "rows" : 2, "cost" : 0.7, "chosen" : true } , { "rows_to_scan" : 36, "filtering_effect" : [ ] , "final_filtering_effect" : 0.1, "access_type" : "scan" , "using_join_cache" : true , "buffers_needed" : 1, "resulting_rows" : 3.6, "cost" : 3.8541, "chosen" : false } ] } } ] } , "cost" : 3.05, "rows" : 0.2, "duplicate_tables_left" : true , "chosen" : true } , { "strategy" : "DuplicatesWeedout" , "cost" : 2.89, "rows" : 0.2, "duplicate_tables_left" : false , "chosen" : true } ] , "chosen" : true } ] } , { "plan_prefix" : [ ] , "table" : "`employee` `e`" , "best_access_path" : { "considered_access_paths" : [ { "access_type" : "ref" , "index" : "employee_main_floor_IDX" , "usable" : false , "chosen" : false } , { "rows_to_scan" : 36, "filtering_effect" : [ ] , "final_filtering_effect" : 0.1, "access_type" : "scan" , "resulting_rows" : 3.6, "cost" : 3.85, "chosen" : true } ] } , "condition_filtering_pct" : 100, "rows_for_plan" : 3.6, "cost_for_plan" : 3.85, "semijoin_strategy_choice" : [ ] , "pruned_by_cost" : true } , { "final_semijoin_strategy" : "DuplicateWeedout" } ] } , // 中略 ] } } , { "join_execution" : { "select#" : 1, "steps" : [ ] } } ] } FirstMatch FirstMatch 戦略には以下の特徴があります。 通常のNLJ(Nested Loop Join)に似ているが、内側の(サブクエリ側の)ループを回しているときに1行でも見つかったら即座に内側のループを打ち切って外側のループの次の周回に進むことができるため効率が良い メインクエリ側が必ず駆動表になる 重複の除去を、内側のループを途中で打ち切ることによって実現しているため サンプルテーブルを用いた実行例から分かることは以下の通りです。 これまでのクエリとは違い、department表へのクエリをメインクエリとしている これまでは、サブクエリのテーブルの方が小さかったが、今回のクエリはメインクエリのテーブルの方が小さい オプティマイザトレースの final_semijoin_strategy が FirstMatch 実行計画の Extra 列に FirstMatch(department) の表示がある SELECT * FROM department WHERE department.main_floor IN ( SELECT main_floor FROM employee ); 実行計画 mysql> explain select * from department where department.main_floor in ( select main_floor from employee ); +----+-------------+------------+------------+------+-------------------------+-------------------------+---------+-------------------------------+------+----------+-------------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+------------+------------+------+-------------------------+-------------------------+---------+-------------------------------+------+----------+-------------------------------------+ | 1 | SIMPLE | department | NULL | ALL | NULL | NULL | NULL | NULL | 9 | 100.00 | Using where | | 1 | SIMPLE | employee | NULL | ref | employee_main_floor_IDX | employee_main_floor_IDX | 5 | sandbox.department.main_floor | 2 | 100.00 | Using index ; FirstMatch(department) | +----+-------------+------------+------------+------+-------------------------+-------------------------+---------+-------------------------------+------+----------+-------------------------------------+ 2 rows in set, 1 warning ( 0.00 sec) Note (Code 1003 ): /* select#1 */ select `sandbox`.`department`.`dept_id` AS `dept_id`,`sandbox`.`department`.`main_floor` AS `main_floor`,`sandbox`.`department`.`dept_name` AS `dept_name` from `sandbox`.`department` semi join (`sandbox`.`employee`) where (`sandbox`.`employee`.`main_floor` = `sandbox`.`department`.`main_floor`) mysql> explain analyze select * from department where department.main_floor in ( select main_floor from employee )\G *************************** 1 . row *************************** EXPLAIN : -> Nested loop semijoin (cost= 5.20 rows = 18 ) (actual time = 0.034 .. 0.050 rows = 9 loops= 1 ) -> Filter: (department.main_floor is not null ) (cost= 1.15 rows = 9 ) (actual time = 0.022 .. 0.026 rows = 9 loops= 1 ) -> Table scan on department (cost= 1.15 rows = 9 ) (actual time = 0.022 .. 0.025 rows = 9 loops= 1 ) -> Index lookup on employee using employee_main_floor_IDX (main_floor=department.main_floor) (cost= 0.54 rows = 2 ) (actual time = 0.002 .. 0.002 rows = 1 loops= 9 ) オプティマイザトレース(一部抜粋) { "steps" : [ { "join_preparation" : { "select#" : 1, "steps" : [ // 中略 { "transformations_to_nested_joins" : { "transformations" : [ "semijoin" ] , "expanded_query" : "/* select#1 */ select `department`.`dept_id` AS `dept_id`,`department`.`main_floor` AS `main_floor`,`department`.`dept_name` AS `dept_name` from `department` semi join (`employee`) where ((`department`.`main_floor` = `employee`.`main_floor`))" } } ] } } , { "join_optimization" : { "select#" : 1, "steps" : [ // 中略 { "considered_execution_plans" : [ { "plan_prefix" : [ ] , "table" : "`department`" , "best_access_path" : { "considered_access_paths" : [ { "rows_to_scan" : 9, "filtering_effect" : [ ] , "final_filtering_effect" : 1, "access_type" : "scan" , "resulting_rows" : 9, "cost" : 1.15, "chosen" : true } ] } , "condition_filtering_pct" : 100, "rows_for_plan" : 9, "cost_for_plan" : 1.15, "semijoin_strategy_choice" : [ ] , "rest_of_plan" : [ { "plan_prefix" : [ "`department`" ] , "table" : "`employee`" , "best_access_path" : { "considered_access_paths" : [ { "access_type" : "ref" , "index" : "employee_main_floor_IDX" , "rows" : 2, "cost" : 4.0525, "chosen" : true } , { "access_type" : "scan" , "chosen" : false , "cause" : "covering_index_better_than_full_scan" } ] } , "condition_filtering_pct" : 100, "rows_for_plan" : 18, "cost_for_plan" : 5.2025, "semijoin_strategy_choice" : [ { "strategy" : "FirstMatch" , "recalculate_access_paths_and_cost" : { "tables" : [ ] } , "cost" : 5.2025, "rows" : 9, "chosen" : true } , { "strategy" : "MaterializeLookup" , "cost" : 10.5, "rows" : 9, "duplicate_tables_left" : false , "chosen" : false } , { "strategy" : "DuplicatesWeedout" , "cost" : 8.9025, "rows" : 9, "duplicate_tables_left" : false , "chosen" : false } ] , "chosen" : true } ] } , { "plan_prefix" : [ ] , "table" : "`employee`" , "best_access_path" : { "considered_access_paths" : [ { "access_type" : "ref" , "index" : "employee_main_floor_IDX" , "usable" : false , "chosen" : false } , { "rows_to_scan" : 36, "filtering_effect" : [ ] , "final_filtering_effect" : 1, "access_type" : "scan" , "resulting_rows" : 36, "cost" : 3.85, "chosen" : true } ] } , "condition_filtering_pct" : 100, "rows_for_plan" : 36, "cost_for_plan" : 3.85, "semijoin_strategy_choice" : [ { "strategy" : "MaterializeScan" , "choice" : "deferred" } ] , "pruned_by_heuristic" : true } , { "final_semijoin_strategy" : "FirstMatch" , "recalculate_access_paths_and_cost" : { "tables" : [ ] } } ] } , // 中略 ] } } , { "join_execution" : { "select#" : 1, "steps" : [ ] } } ] } 終わりに 以上、セミジョイン最適化について、個々の戦略の内容を含めて書いてきました。サンプルテーブルを用いて取得した実行計画やオプティマイザトレースはあくまで一例に過ぎず、少し条件を変えるとまた違った結果が得られるかもしれません。冒頭にも書いたように、記載している内容に誤りなどを見つけた場合は 筆者のTwitter までご一報いただけると幸いです。 参考資料 公式 5.6 8.2.1.18 サブクエリーの最適化 8.2.2.1 Optimizing Subqueries with Semijoin Transformations 8.2.2.2 Optimizing Subqueries with Materialization 8.8.5.2 切り替え可能な最適化の制御 8.0 8.2.2.1 Optimizing IN and EXISTS Subquery Predicates with Semijoin Transformations 8.9.3 オプティマイザヒント MySQL 8.0.30 Source Code Documentation その他 MySQL道普請便り 第43回 MySQLの準結合(セミジョイン)について MariaDB 10.6 [日本語] 最適化とチューニング セミジョイン副問い合わせの最適化 INとEXISTSはどちらが速いのか? MySQL のサブクエリって、ほんとに遅いの? MySQL 8.0.21 では Multi-Table Trick が必要なくなったらしい MySQL道普請便り第103回MySQL 8.0のセミジョインの変更点
アバター
介護職向け求人情報サイト「カイゴジョブ *1 」の開発をしている児玉卓也です。 弊社の プレスリリース でお知らせしているように、カイゴジョブは2022年5月にデザインリニューアルを行いました。 デザインリニューアルにはサイトのコンセプトなどはそのままで、UIなどの見た目を部分だけを変更するものから、コンセプトやユーザーシナリオというサービスの基盤となる部分から再設計し直すことがあるかと思いますが、カイゴジョブが行ったデザインリニューアルは後者の方で、カイゴジョブにおける求職者の体験をより良くするためにコンセプト・ユーザーシナリオをアップデートした上でデザインを全面的に変更しました。 全面リニューアルとなるとその影響範囲は大きく、特にプロダクトの品質を保ちつつ大規模リリースをすることは、経験された方にはわかると思いますが大変な作業となります。今回のデザインリニューアルでは40,000行、900を超えるファイル数の変更となり、我々が日々行っているリリースの数百倍くらい大きなリリースとなりました。 デザインリニューアル時のPRの変更行数 本記事では私達が大規模リリースをしなければならなかった理由を説明した後に、どのような開発手法を用いて大規模なリリースを安全に行えたかについて説明したいと思います。 デザインリニューアルの背景 環境の変化が激しく不確実性が高まる時代ではありますが、介護業界も例に漏れず変化が激しい業界です。カイゴジョブも市場の変化やユーザーニーズの変化に合わせて新機能を追加するなど求職者への価値提供を追求してきました。そのような中でビジネスモデルの変革を進めていくこととなりました。 ビジネスモデルが変わったことで求職者に期待したい行動やサイト上でのゴールも変わりました。しかし、サイト全体の体験は2019年に設計したものであったため、機能の開発をする際に既存の導線との接続であったり、体験の整合性をサイト全体と合わせるのが困難と感じることが増えてきました。 その問題を解決するためにデザインリニューアルを行い新しいビジネスモデルにそった体験を提供することとなりました。 アジャイルなビックバンリリース 前述したように今回のデザインリニューアルではサービスの基盤となるコンセプトから作り直していることもあって、画面単位、コンポーネント単位に分けてリリースをしてしまうと全体の動線の中で体験の整合性が合わなくなってしまうので、全画面の開発完了後に一斉にリリースする必要がありました。 このような大規模なリリースをしようとする際に、開発工程が完全に完了してからQAテストを行うとなると、バグが見つかったときの手戻りのコストが大きく、また、バグの度合いによってはリリース時期の延期をせざるを得ない状況となってしまいます。そのような事態になることは避けたかったので、開発工程と並行してテストを行えるようなプロセスで進めていきました。 このプロセスで開発をすすめる上で、タスクの作成方法と優先順位付けがポイントとなります。 テスト可能な単位でタスクを作成 まず、タスクの作成方法ですが、QAエンジニアによるテストの実行が可能となる単位でタスクを作成していきます。 私達の開発チームはエンジニア、デザイナー、QAエンジニアが1つのチームとなって活動しており、日々の活動の共有に加えて、チームの活動のふりかえり、さらに実装前のフェーズである要件定義なども一緒に行い、密にコミュニケーションをとるチームであることが特徴です。 デザインリニューアルのプロジェクトにおいても、普段の開発と同様に要件定義やユーザーストーリー作成のフェーズからQAエンジニアに参加してもらい、何を作らなければならないかを対話しながら具体化し、共通理解を深めながらタスクを作成し管理します。QAエンジニアは受け入れ条件を開発前から把握できるため、開発と並行してテストシナリオの作成などテスト実行に向けた準備が可能となります。 また、QAエンジニアが要件定義の場にいることで、仕様に抜け漏れがないか、不具合が入り込む余地がないかなど、QAエンジニアの観点で提案をもらうことができ、開発着手する前に不具合に気づくこともできました。 テストの優先度の高いものは早めに着手する デザインリニューアルのプロジェクトでは事業の影響度、開発コストなどの基準に加え、テスト優先度を含めて優先順位を決定し、開発・テストの取捨選択をしていきました。 基本的に事業への影響度が高いものはテストの優先度も高くなりますが、テストを効率よく進めるために以下の観点も重要視しています。 テストボリュームが多くなりそうな改修であるか 仕様が複雑そうな改修であるか テストの優先度が高いものは不確実性が高く、早期に解消しないとリリースブロックとなる可能性もあるので、優先順位付けをする中でも重要な基準となります。 副次的効果 開発・テストのサイクルを繰り返していくプロセスの副次的効果として、バグの修正が容易であることが挙げられます。というのも、開発完了からテストのフィードバックまでのリードタイムが短いため、バグ修正の際もコンテキストや実装内容の詳細の記憶を思い出す労力を多くかけずに対応できるからです。 おわりに 今回、私達は開発とテストの工程を並行するプロセスを採用し、デザインリニューアルプロジェクトを進めてきましたが、はじめからこの開発手法を確立できていたわけではありません。日々のプロセスの中で上手くいったことや課題に感じたことをチームでふりかえりながらプロセスを最適化してきました。 デザインリニューアルを終えた後もプロジェクトのふりかえりを行い、QAエンジニアが実施したシナリオテストを自動テストに落とし込んでいきたいなど課題が見つかり、今後も継続的に改善していきたいと思っています。 最後になりますが、デザインリニューアルによってカイゴジョブは求職者にとってより使いやすいサービスとなりました。しかし、求職者に提供できる価値はまだまだたくさんあると感じています。今後も求職者の圧倒的な不足という社会的な課題を解決するために、プロダクトの価値を高めていきたいと思っていますので、興味のある方はぜひお話を聞きに来てください! *1 : カイゴジョブは介護業界に特化した求人情報サイトで、2004年より展開しているサービスです。利用者はカイゴジョブのサイト上で求職者の希望に近い求人を検索し応募できることに加え、電話での求人検索や、応募手続きができます。 介護特有のこだわり条件で求人を探せることなど、介護に特化した求人情報サイトであることが特徴の1つでもあります。
アバター
はじめまして、カイゴジョブの開発をしている真下です。 皆さんは今自分が所属している会社が事業を行なっている業界に対して、興味を持っていますでしょうか。また、入社時にはどの程度興味を持っていましたか。 エス・エム・エスが事業を行う業界に対する印象 私は前職では、所謂エンターテイメントの業界でWebサービスの開発を行なっていました。当時は、この業界への強い興味があったわけではありませんが、業界全体の雰囲気が何となく面白そうだと感じて入社し、結果的には、サービスの開発自体に対して面白さを見出して働いていた思い出があります。 一方で、エス・エム・エスは「医療・介護・シニアライフ・ヘルスケア」の業界で事業を行なっておりますが、私はエス・エム・エスに入社するまでこの4つの業界での業務経験は全くありませんでした。入社する前は、これらの業界に対して元々そこまで強い興味や理解がなく、「業界に対する高度な知識や理解が必要である」という印象がありました。このような理由から、エス・エム・エスでしっかりとやっていけるのだろうかという多少の不安がありました。しかしながら、入社して約1年が経ち、これまでを振り返ると日々やりがいを感じながら楽しく業務を行ってこられたと感じております。 アンケートの実施 弊社には、例えば「クラウドベンダー」「金融」「ゲーム」といった様々な業界経験をバックグラウンドに持つエンジニアが在籍しております。 そこで、弊社のエンジニアは「医療・介護・シニアライフ・ヘルスケア」の業界に対して、どの程度興味を持ってエス・エム・エスに入社したのか気になり、アンケートを取ってみました。 アンケートでは以下の項目について回答してもらいました。 入社時、エス・エム・エスが事業を行なっている業界(医療・介護・シニアライフ・ヘルスケア)への興味はありましたか?以下の3つから回答してください。 1: とても興味があった 2: まあまあ興味があった 3: 全く興味がなかった アンケートの集計結果は以下のようになりました。 回答者のうち、入社時にはエス・エム・エスが事業を行なっている業界に対する興味を全く持っていなかった人が約3分の1を占めるという結果になりました。このアンケート結果から、弊社のエンジニアはエス・エム・エスが事業を行なっている業界以外の、別のどのようなところに興味があったのか、そして今はどのようなことに魅力ややりがいを感じているのか気になり、何人かのメンバーにインタビューしてみました。 エス・エム・エスで働く魅力 インタビューでは、以下のように様々な観点からの回答が得られました。 介護・医療などの業界に対しては元々興味がありませんでしたが、入社前に技術責任者の田辺さんから聞いた事業説明で、エス・エム・エスが事業を行なっている「医療・介護・シニアライフ・ヘルスケア」の業界そのものというよりは、その業界の中で行なっているエス・エム・エスの事業が面白そうだと感じて、入社しました。事業の面白さは入社後にも感じながら仕事をしていますが、入社後に発見した面白さとして、業務ルールの複雑さがあります。現在担当しているプロダクトには、介護保険制度が関わっていて、その制度に由来する複雑な計算ルールをシステムに落とし込む必要があります。これは難しいと感じる一方で、やりがいを感じている点でもあります。 (エンジニア) 前々職は、医療業界で事業を行う会社に所属していましたが、業界に対しては特に強い興味を持っていませんでした。そして、エス・エム・エスに対しては、なんとなく社会貢献できるイメージの会社であるという印象を持っておりました。一方で入社後には、エス・エム・エスが行なっているビジネスモデルの幅広さが面白いと感じるようになりました。例えば、現在私が担当しているカイポケはフィンテック領域に関わるような事業があったり、シニアライフ領域では食事に関する事業があったりと、「医療・介護・シニアライフ・ヘルスケア」の枠を超えて事業を行なっている印象があります。(エンジニアリングマネージャー) 前職は営業代行系の事業会社に所属しており、全く異なる業界にいました。入社前には、医療や介護の業界に対する興味ではなく、これらの業界自体の市場の拡大に興味がありました。そして、入社後に自分が担当しているサービスは、医療介護に直接関わっているというよりは、その業界で、人材紹介のビジネスモデルを行なっている構造になっています。この業界における求職者のニーズを考えるのが面白いと感じています。また、転職というライフイベントを支えるサービスのサポート戦略を考えることにやりがいを感じています。 (エンジニア) 私が現在担当している「カイゴジョブ」というサービスについても、名前から想像されるような介護サービスというよりは、むしろ求人広告サービスや業務システム開発のような要素が強く、ユーザーが使いやすい検索システムデザインを考えたり、使いやすい管理機能の開発をするといったようなことに対して面白さを感じています。 業界への関心をハードルに感じる必要はない エンジニアとして働くにあたって、自分が作るソフトウェアが使われる領域への興味を最初から持っているのはもちろん素晴らしいことです。しかし実際には、別のところに面白さややりがいを見出しているケースも多いと思いますし、今回の社内でのアンケートとインタビューでも、入社前はそこまで強く業界への関心をもっていなかった人が多かったことがわかりました。 「介護・医療・シニアライフ・ヘルスケア」と言われると「あまり身近でないな……」などと感じる人も多いかと思いますが、あまりそこをハードルに感じずに、話を聴きにきていただけるとうれしいです。
アバター
はじめに プロダクト開発部でキャリア事業のEM(エンジニアリングマネージャー)をやっている大野です。前回は、私がエンジニアリングマネジメントをするに当たってどのようなアプローチをとっているのかを紹介しました。 tech.bm-sms.co.jp 今回は、他のEMと会話している際に、「EMが取り組む施策は失敗する可能性が高いよね」という話があがり、それに対して共感できる部分もありつつ、じゃあどうすればいいのか?を考える機会になったので、まとめてみました。 EMが取り組む施策は失敗する可能性が高い? EMやそれに近しい役割を経験したことのある方なら、思い当たることがある人も多いのかなと思います。 自身の成功体験を元に組織改善しようとするが、中々変えることができない 他社のよい事例を参考に、自社での取り組みを開始したがうまく軌道に乗らない などです。 私自身も、過去こういった経験がそれなりにあり、現在でもそういった場面に出くわすことはあると感じます。 なぜそういったことが起こるのか 一概にこうだ!という理由はないと考えていて、たとえば以下のような阻害要因が経験としてはあります。 現場とEMとの温度差 現場は常に忙しく、余力がない EMとメンバーで見えているものが違い、課題として認識していない 導入時はよかったが、除々に形骸化したり廃れてしまった どうすればいいか? こういった課題を解決するために、どうすれば組織・チームが動くのかをEMは日々トライ&エラーを繰り返していく形になります。こういった答えが明確にない課題解決を繰り返していくことで、EMとしてのスキルが磨かれていくと感じています。 私自身、何かに取り組む際、最低限意識するよう心がけていることがあります。以下でそれらをご紹介します。 1. まずは情報収集 前述した阻害要因の多くは、自分の持っている情報が不足していることに起因しています。そのため、今でも1on1やエンジニア以外の職種の方と接する機会を活用し、課題感を把握するよう努めています。 2. 期待値のすり合わせ もう1つ大事なこととして、相手との期待値を合わせていくことです。 EMがやりたいことを理解してもらい、そこにメンバーも同じような期待値を持ってもらうことで、取り組みの進み方が変わってきます。 3. セカンドペンギンを意識する ビジネス用語のセカンドペンギンとは少しニュアンスが異なるのですが、勝手にそう名付けています(笑)何か新しい取り組みをする際、1人目が動いた後、2人目、3人目が続くか、同じような熱量で取り組んでくれるか、がその後に大きく影響します。 私自身、推進していく内容に合わせてファーストペンギンになることもあれば、セカンドペンギンになることもありますし、すべてメンバーに取り組んでもらうこともあります。 4. 失敗を恐れないチームづくり これは言葉のままですが、何かチャレンジしようとした際のフットワークの軽さに影響します。失敗しても問題がないことや、そもそもチャレンジを推奨するようなチームにしていくべきです。 たとえば、リファクタリングと影響範囲に伴う不具合の懸念等を天秤にかけているメンバーがいた場合には、 万一、不具合が発生してもリカバリーすればよい 合わせて自動テストを書くチャンス など、チャレンジを後押しするよう日々接することで、チーム文化として根付いていきます。このような文化を作っていくことで、EMのやろうとしていることについても「すぐにはうまくいかないかもしれないけど、チャレンジしてみよう」とフォロワーシップを発揮してくれるメンバーが増えてくるのではないかと思います。 EMの職能 上記で記載した内容は、とても重要とは思いつつも、「どういうスキルか?」と問われると一言で表すことのできない難しさがあります。 情報収集する際に、専門的な知識が必要になる場合もありますし、事業理解が必要な場面もあります。期待値を合わせていくのに、信頼関係が構築できているようなコミュニケーション能力も必要でしょう。 そういった正解が決まってないことに対して自他の強みを活かし、成果を出すのがEMで、その再現性を高めるのがEMの職能となるため、EMの職能がはっきりと定義されたものを見ないのはそういう面があるのかなと思います。 経験上、EMとしてトライ&エラーを多く経験している人ほど、上記のような自分なりのやり方の引き出しが多い印象です。 おわりに 今回は他のEMとの話をきっかけに、EMの失敗からの学びと、そこからどういった行動に結びつけて解決していけばよいかを考える機会になりました。
アバター
介護事業者向けの経営支援プラットフォーム「カイポケ」で、プロダクトマネージャー兼アーキテクトを務めている三浦です。 11月2日にオンラインで開催された、プロダクトマネージャーカンファレンス 2022(pmconf 2022)で、「日々の意思決定で使うB2Bプロダクトマネジメントサイクル」のタイトルで発表しました。 発表要旨は以下のとおりです。 プロダクトマネージャーの重要な仕事の一つに意思決定があります。ユーザー価値を生み出すために、プロダクトマネージャーはプロダクトマネジメントトライアングルそれぞれの観点から次に作るものは何か?要件は何か?作っているものはユーザー価値があるのか?など様々なイシューに対して開発チームとして日々、意志決定をしなければなりません。さらにB2Bプロダクトの場合は法令や行政、マーケット、プロダクト導入決定者の観点が加わり、プロダクトマネージャーにとって意思決定は難しいものになります。 このセッションでは、B2Bプロダクトマネジメントにおいて必要な観点が何かを整理し、どうやってプロダクトマネジメントトライアングルを使って意思決定をしていくのか実例を交えて紹介します。 (セッション内容紹介より) 2022.pmconf.jp この記事では、発表資料とともに、発表時に寄せられた質問の一部への応答・補足説明を公開します。 発表内容やカイポケの開発についてより詳しく聞いてみたいという方は、 ぜひカジュアル面談にお越しください! open.talentio.com 資料 質問への回答・補足説明 Q:複数の介護形態でペインの発生場所も異なる中で、どのように優先度をつけてきたか、これまでとこれからの話も踏まえてお聞きしたいです。 前提として、介護業界は非常に広大な市場ですので、基本的には介護業態によって市場が細分化できます。その上で、優先順位は、マーケットの大きさ、エス・エム・エスの強みがフィットするかなどの要素を掛け合わせて決めています。まずは細分化した市場を選んで、特に注意すべきペインに取り組んでいくという形です。 Q:歴史の長いプロダクトだとは思うのですが、それならではのプロダクトマネジメントの難しさはありますか? 歴史が長いので、「どうしてこういう仕様になっているのか」という仕様がわからない箇所がどうしても出てきます。カイポケは18年以上の歴史がありますので、当初のメンバーは当然ほとんど在籍していません。そうすると、仕様の「なぜ」はもはやリバースエンジニアリングで解き明かしていく感覚になります。 Q:アクセスコントロールの例で、外側のトライアングルから具体化していくのが分かりやすくていいと思ったのですが、トライアングルのバランスはどんな感じで取っていくのでしょうか まずはトライアングルの登場人物のペルソナを考えています。 開発者の市場...エンジニア 顧客(ユーザー)...介護事業、意思決定者(経営者) 行政(GTM: Go To Market)...厚労省、財務省 三者が何を考えていて、今後何をしたいのか?を意識的に考えています。その上で、例えばAという要件に対して三者が「いいよね」って言ってくれるかどうかを考えて判断しています。 Q:トリプルトライアングルを用いて意思決定するときにバーティカル SaaS ならではのアプローチってありますでしょうか?よく使うパターンがあるのかどうか気になります! 前提として、個人的にトリプルトライアングルを使うのはtoB向けがメインだと思っています。 そしてVertical SaaSになると、「ペインの深さ」だとか、「業界にいかに深く入り込めるか?」がポイントになってきますので、トリプルトライアングルの三つの角をさらに分解する、例えば顧客(ユーザー)でも「経営者、管理職、従業員」とさらにトライアングルを作って考える、といったアプローチを行っています。 Q:法令対応はドメインエキスパートのような方がいるのでしょうか?難しそうです。。 もちろんいます。介護業界からドメインエキスパートとしてお越しいただいた方もいらっしゃいますし、おなじく介護業界出身の方がPdMをしているケースもあります。 業務上の細かなやり取りや、無数に種類がある帳票などについて、どういう目的で作っていて、次の工程は何か、などを把握して開発していくには、やはりドメインエキスパートの存在は不可欠です。 Q:トリプルトライアングルの導入について、どう使えばいいかわからない?という事がありそう…つまずいたポイントなどあれば教えて下さい 外側のトライアングルから考えることは大事だと考えています。 最初は内側のトライアングルから整理していました。しかしそうすると、例えば、アクセスコントロールについて足りない観点が出てきます。カイポケの場合であれば、行政の出しているガイドラインで遵守すべき項目が定められており、これは最初から要件として取り込んでおく必要があります。 外側のトライアングル(法令)から要件を抽出していかないと「蓋を開けたら法令遵守できてないかも?」みたいなことが起こるとわかってきたので、外側からトライアングルを作り始めるようになりました。
アバター
はじめに プロダクト開発部でキャリア事業のEM(エンジニアリングマネージャー)をやっている大野です。今回は自分が見ているチームに関して、普段どういったアプローチをしているのか?という話を元に、自身のマネジメント術に関して深堀りしていきたいと思います。 一般的なEMとは? 企業におけるフェーズや組織設計で、EMが持つ役割は変わるものの、大きくはピープルマネジメントを行いつつ、プロジェクトマネジメントやテックリード、プロダクトマネジメント等組織に必要な役割を多岐に担っている形が一般的なEM像かと思います。 大野の担当範囲 私自身、前職を含めると約9年EMに近い役割を担っています。 最近だとピープルマネジメント、テックリードが主で、状況次第でプロジェクトマネジメントやプロダクトマネジメントに近い役割で動くこともあります。 マネジメントボリュームの話をすると、エンジニア30人弱をEMとしてマネジメントしており、事業としても様々な方と仕事を進めるため、ステークホルダーとしては100名超の方とお仕事させてもらっています。 よく聞く「マネジメントは多くて10名まで」という一般的な数からするとかなり多い部類になるのかなと思います。 そのため、どうやって回しているのか?と聞かれることもよくあります。 こういった背景から、今回自分の仕事術を深堀りしていくことで参考になる人も多いのでは?と思った次第です。 ピープルマネジメント編 まとめてみた結果、極力、「マネジメントコスト = 人数*時間」にならないようにしているなと自分でも理解しました。 まずは任せる メンバーと仕事を進める際、可能な限り裁量を渡すようにしています。 事業側とのコミュニケーションをどう取るか 開発におけるリファクタをどう進めるか などの裁量を渡すことで、メンバー自身が考える成長機会となり、チームの成長に結びつきます。 任せる上で大事なこととしては 狙いを伝える(裁量を譲渡すること・自分で思考する等) 失敗も経験として恐れないこと チャレンジした上でキャパオーバーするものは手伝う あたりに気をつけて伝えています。ここが認識合わせできていないと、ただの丸投げとなってしまうので気をつけないといけない部分です。 1on1よりも普段の接点を増やす これは前職で30人マネジメントすることになった際に定着したのですが、メンバーとのコミュニケーションは普段の接点を多く持つことに重きを置いています。 1on1でしか得られない高揚はもちろんあるのですが、1on1で改まって話をするより、普段の業務中に会話をしたほうが圧倒的に鮮度・頻度が増えるため、プラスになることが多いです。結果、1on1の頻度としては多いメンバーで隔週、大半のメンバーはQ単位で1回程度と一般的な1on1の頻度よりはかなり少ない形で運用しています。 リモート業務中心となり、偶発的な会話の機会が減ってしまったこともあり、業務上のコミュニケーションはSlack中心、雑談等はMTGの間のGoogle Meetでの通話等で行うことが増え、より普段の接点自体に意識を向けるようになりました。 自身もバリューを発揮する 上記のようなコミュニケーションをメンバーと取る上で重要なのは、自分が業務やエンジニアリングにおける示唆をするに値する人間であることをメンバーに知ってもらうことです。 もちろん、色々やり方はあるとは思うのですが、私自身は「背中で語る」が最も手っ取り早く効果も高いと思っています。 といっても、すべての領域で無双するような必要はなく、自身の強みをベースにチームメンバーとの関係構築を行っていくことで、エンジニアならではのスキルからくる信頼関係が構築されていきます。 テックリード編 自身の技術力 私自身、技術面に関してはそこまで尖っているわけではありません。元々、マネジメントにキャリアを歩むきっかけとしても、技術を極めていく自分が想像できなかった面もあったりします。 ただ、実際にマネジメントをやってきた結果、やはり技術力は必要だなと認識する場面は多々あります。 結果、自分としては「自チームにおいて、最も技術力が高い人と同じレベルで会話ができる」をモットーに技術面のキャッチアップを行うようにしています。 これが満たせない場合、チームでの技術的な意思決定に自分が関われず、それがビジネスに本当に必要なものなのか?の判断を間違える可能性があるからです。 メンバーの興味関心に合わせる 見る範囲が広い為、自分だけで技術面のチャレンジを行ったところで、チームには浸透しません。そのため、メンバーが今どういう部分に興味関心を持っているか、業務上どういう課題を持っているか等、メンバー個々が動機づきやすいような内容を中心に開発改善を進めることが多いです。 こういった流れ自体が定着していくことで、新たな提案にも前向きに取り組めるチーム文化ができていると感じます。チームに感謝! プロダクトマネジメント編 事業を広い視点から理解する 事業に貢献するために、事業理解の中でも「色々な視点」から事業を見ることを意識しています。 それぞれの深い部分は各担当の人に勝てないので、実際に事業面で把握するようにしているのは以下のような情報です。 大枠の事業戦略 各担当が日々追いかけているKPIとその相関 サービスを利用するペルソナ 隣のチームのHOTな話題 これらの情報をインプットとして、「事業の色々+エンジニアリング」を理解したユニークな人になるようにしています。 こうして、事業での意思決定の場で自分ならではのバリューを出すようにしています。 問題解決を進める 業務を進める上で、開発に限らず日々たくさんの問題が発生します。 私自身、どんな問題があっても必ず前に進めるというのを意識しており、これを体現していくことで周りからの信頼も勝ち取れ、更に自分に情報が集まりやすくなる好循環が生まれます。 ここで前に進めると表現したのは、必ずしも自分で解決する必要はないからです。重要なのは「大野に聞けば何かしら前に進む」と思ってもらうことなので、自分で解決してもよいですし、解決に適任な人に繋ぐこと自体にバリューがあります。 今は、メンバーにどんどん裁量を渡しているので、大野ではなくメンバーに真っ先に相談・質問が来るように関係構築を進めるよう推進しています。 また同時に、メンバーにとって困難な問題が発生した際はいつでもヘルプできるような距離感は保ちつつ、自身は比較的難易度の高い問題の解決に時間を充てられるようにしています。 おわりに 今回は、広い範囲の組織をみる上で、自分なりに行っている仕事術のような形で色々と書かせていただきました。 こういった言語化や、この記事から生まれる会話でよりEMとしても、組織としても成長していければと思います。
アバター
技術組織のマネジメント @sunaot です。エス・エム・エスで技術組織のマネージャーをしています。入社時点から技術組織全体のマネジメントを担う役割でスタートし、今年で7年が過ぎました。 「エンジニアリングマネージャー (以下、EM) の仕事とはなんですか?」と聞かれたときにその定義を答えられるでしょうか? 「1on1をすること」「メンバーの育成をすること」など、これは EM の仕事だという要素は挙げられても、全体像を言える人は中々いないのではないかと思います。そうしたときに頼りになるのは書籍ですが、EM に特化して仕事の全体像を語った書籍というのは日本語ではなかなかありませんでした *1 。 EM の仕事単体でその全体像を説明するのが難しいのにはそれなりの理由があります。この記事では、全体像を語りにくい EM の仕事というものについて、技術組織のマネジメントという視点から全体を説明し、その中で EM の果たす役割を定義するという流れで説明をしてみたいと思います。 対象となる読者の人は、 ある程度 EM としての経験があり、これからどのようによりよい EM となっていけばいいか悩んでいる人 現在 EM をしていて、より広い範囲の役割で仕事をすることが求められそうな人 です。自分で自分の仕事をつくっていく必要が出てきた人の考える土台になることを狙って書いています。 一方で、あまり対象ではないであろう人は、 これから初めての EM をするという人 です。経験をしていない人にとっては、もう少し具体的にどういう活動をすればいいのかというのをステップ・バイ・ステップで解説してくれるようなもののほうが合っていると思います *2 。 技術組織のマネジメントの要素とレイヤー それでは、技術組織のマネジメントから説明をしていきます。最初に、技術組織のマネジメントと EM の仕事の区別を説明します。 技術組織のマネジメントは、会社の技術組織全体の成功へ責任を持ち、成功のために必要な活動を設計し、実行をすることです。スコープとして技術組織という組織を対象にしています。一方、EM の仕事はスコープとしてチームを対象としています。人数としては多くて8名、最大でも15名くらいをイメージしています。 技術組織という組織が成功するためには、次のような要素が必要になります。 実現したいことと目標 体制とアプローチ アライメントと実行 組織構造 情報流通 コラボレーション 価値観と文化、モラル さらに、技術組織のマネジメントのレイヤーとしては次の3つがあります。 個人 チーム 組織 組織が、技術組織のマネジメントの要素を満たして成功をするために、各レイヤーのそれぞれで必要な活動をしていくのが、技術組織の経営です。 ここからは、それぞれの要素やレイヤーについて説明をしていきます。 技術組織のマネジメントの7要素 実現したいことと目標 これは文字通りの意味ですが、マネジメントの根幹になります。他のすべての要素のゴールでもあり、制約でもあります。その組織にとって成功とは成果とはというものを定義します。表現形式としてミッション・ビジョンや短期的にOKRのような形態をとります。 会社組織であれば、上位概念になる組織から求められることもありますし、一方でそれを鵜呑みにしないことも組織の価値になります (鵜呑みにするならサブセットになって組織固有の価値が低いため)。あとは、周辺の他の組織から求められる要求というのもあります。より広い役割の場合は社会や市場、競合他社という視点から考えることもあるでしょう。 いずれにせよ、その組織固有の価値とはなにかを考え続け、状況の変化に合わせた更新を加え続けていく仕事になります。 体制とアプローチ 実現したいことと目標、それに向けて得たい成果からスタートします。そこへ向けて必要な体制やアプローチというのは多くの選択肢の中からマネージャーと組織の能力のキャパシティの中で変わってきます。これがマネージャーを優秀な人に担ってもらうべき理由でもあります。 現在の組織能力と実現性というのは制約になりますが、基本的にはあくまで成果からの逆算で考えます。 無限の選択肢と組織能力を持っているのであれば、最善の解を選べばいいわけですが、通常マネージャーが置かれている状況はそうではありません。制約条件があり、ビジネスや開発/技術におけるセオリーがあり、その中で数少ない成功の可能性のある選択肢を選ぶ必要があります。 ビジネスや開発/技術の状況・コンテキストをどう読むのか。組織の能力や体力、マネージャー自身の得意不得意も加味して、どの選択肢に勝算を求めるのか。これはマネージャーの仕事の思考の自由度における最大の楽しみになります。 先行するのはアプローチであり、それを実現するために必要な体制を整えていくというのが基本です。体制は、採用と育成、組織づくりが支えているので実際はこれだけで一大トピックになります。 アライメントと実行 アプローチと体制から進むべき道が決まったら、今度はそれを組織的に実現していく必要があります。ここまでの内容は主に戦略と呼ばれますが、実現されない戦略は絵に描いた餅です。戦略が優れていたと評価されるためにはここから先の実現の過程が重要になります。実現の過程は地味ですが、現実の仕事で重要なのは実現とそれを支える実行です。このパートが組織が動くかの肝だと言ってもいいでしょう。ここから、それぞれについて説明します。 アライメント ここで言うアライメントは組織におけるアライメント (organizational alignment) です。実現したいことと目標に対して、組織の一人ひとりが同じゴールへ向かうように理解と意思と行動の方向をそろえ続ける活動です。アライメントという活動があるというよりは、様々な活動を通じてアライメントのとれた状態を実現していきます。たとえば、組織での方針説明会のようなものは活動の一例ですし、インセプションデッキのような活動もアライメントのための活動の一種になります。いわば、組織の照準を合わせる活動です。 実行 実行は、アライメントでそろった方向に対して進めていく過程です。現実には様々な理由で目指した方向に対して歩みを止める力が働きます。実行の過程は、個人や人と人の間に生まれる歩みを止める障害を取り除き、素早く目的地までたどり着くための活動です。戦略や方針が間違っている以前に実行をできずに物事が進まず停滞する組織というのが多く、マネージャーが新しい組織へいった場合に最初にテコ入れするのが実行の文化をつくることであるケースは多いです (一般に実現したいことやアプローチを見直すことは時間がかかるが、実行の文化をつくることは短期で成果を出しやすいため)。 実行の過程についてはマネージャーによって得意なやり方が分かれるところですが、共通する基本的な形式としては「現状把握」と「進行 (進めること)」と「進路補正 (問題の解消)」で成り立っています。たとえば、 『HIGH OUTPUT MANAGEMENT』 ではインディケーターや1on1 *3 によって現状把握をして、教育 (訓練) とモチベーションの向上やナッジングによって進行や進路補正をする手法が解説されています。実行とは、論理的には成り立っている方針に対して、実現をする過程で現れる現実とのギャップを解消しながら前へ進めていく活動です。 組織構造 組織構造は、他のマネジメント要素に対しての枠組みになります。組織の成功に向けて適した構造をとるといった形で導出されるものです。導出されるものでありながら、一度決めた枠組みは制約として働くので、継続的に狙いにあった形に変化させていく必要があります。組織上密結合にするほど、同じ目標を見ることの容易さが上がったり、日常の中での情報流通の量が増えるなどの効果が出やすいです。ただ、組織の構造によらず、狙って特定の要素を強化することは可能なので、もっとも優先したいことへ寄せて組織構造を設計して、課題が出る部分にサポートをしていったりします。特定の優れた組織構造を目指すよりも、その組織構造で過ごす中での組織のコンディションへの影響を追いかけて、主要なテーマが変わってきたら構造を見直すことが重要です。 情報流通 組織の構造ができあがったら、その中で働く人たちが仕事を主体的に進められるように必要な情報を手に入れられる状態をつくる必要があります。プッシュであれプルであれ、どこでどのような情報が手に入れられるのかという土台になる場を設計して、その場の提供をします。場は会議や定期的なイベントという実装をとることもありますし、イシューやチケット、ドキュメントのような実装をとることもあります。土台で埋まらない部分の情報について、ハブとして日常的に埋めていく活動をしたり、他の人が埋める手伝いをします。とくに組織と組織の間で情報流通が途切れることが多いので、必要な情報が相互にやりとりされるように場の設計をします。 コラボレーション コラボレーションは実行の過程で組織やチーム、個人のレベルでの相互の関係が機能するようにしていく動的な活動です。チームのレベルではチームビルディングによってコラボレーションの質と量を上げたり、個人と個人の間では協力して物事を進めるための考え方や行動に働きかけたりします。直接的にコラボレーションの機会や場をつくることもありますし、コラボレーションが活発になるような環境づくりをすることもあります。 価値観と文化、モラル 価値観や文化といったものは、会社の単位で設定されていることも多いと思います。当然それを一つの前提とするわけですが、ただ技術組織固有の価値観や文化があることが多いのと組織の状況やフェーズによって今テーマとして取り組むべきものというのが移り変わるので、意識的に扱ったほうがよいと思います。実現していくための活動としては、採用や育成、評価といったものもあれば、直接的な価値観や文化浸透のための施策、日常の一つ一つの振舞いや行動習慣に至るまであらゆるポイントで「どのような組織でありたいか」を表現していくことになります。仮に他の目的で行う組織の施策だとしても、その思考様式や表現方法は価値観や文化の表明を含むものになるため、いろいろな場面で意識をする必要があります。 技術組織のマネジメントの3レイヤー 個人 スタートは個人のレイヤーです。そもそも個人は組織のために居るわけではないので、個人が個人としてパフォーマンスを発揮できるように支援をしていくというのが一つの役割です。そして、個人は組織のために居るわけではない一方で、技術組織のマネジメントの仕事は組織の成功を実現することです。個人のレイヤーではこの個人の働きを組織の成果へつなげるための働きかけをしていきます。技術組織のマネジメントの7要素が個人のレイヤーではどのように評価できるのかを考え、個人への後押しをしたりコーチをしたりフィードバックをしたりします。 現実に個人に向き合っているときには、必ずしも組織の成果のことだけを考えるわけではありません。というよりも、目の前のマネージャーが組織の立場からしか会話をしてくれないのであれば信頼関係は築けません。個人としてのその人の立場を尊重して考えることへ100%の意識を使いながら、同時にチームや組織の視点も提供できるのがマネージャーの付加価値になります。 チーム チームは現代のサービス開発の基本単位です。そのため、技術組織のマネジメントの7要素はどれもチームのレイヤーで機能をしている必要がありますし、考えるときにもチームを主体として考えたときにどのようにそれぞれの要素を機能させるかという視点で考えることが多いです。また、一見すると個人のものに見える課題を解決するときにも、あえて「チームのレイヤーでその課題を解決するとしたら」という視点の置換をすることも多いです。個人に課題が出ているときは、その一つ上のチームのレイヤーでなにかの課題を抱えている結果であることが多いからです。 組織 組織のレイヤーで考える場合も、基本はチームを中心に考えます。物事が成し遂げられる基本単位がチームだからです。一方、チームはそれぞれに状況や成熟度合いが違います。複数のチームに対して一定の土台となる仕組みや質、成熟度合いをつくっていくには組織というレイヤーでのサポートが必要になります。チームが気持ちよく働き成果を出すことへ集中できるようにするには組織がどうあればよいかを考えていくのが組織のレイヤーです。 ここまでで、技術組織のマネジメントに必要な7つの要素と3つのレイヤーについて説明をしてきました。次回は、この要素とレイヤーに対してエンジニアリングマネージャーはどういった仕事を担っているのかを説明します。 *1 : 『エンジニアリングマネージャーのしごと』 という翻訳書が8月に出版されました。 *2 : この点、『エンジニアリングマネージャーのしごと』は期待に応えてくれる場合が多そうです。 *3 : 書籍では「ワン・オン・ワン」として紹介されています。
アバター
はじめに はじめまして。エス・エム・エスで介護事業者向け経営支援サービス「カイポケ」の開発をしている古川です。今回はエス・エム・エス内で行っていた『プロダクト・レッド・オーガニゼーション』読書会の取り組みを紹介します。 エス・エム・エスでの読書会 エス・エム・エスでは読書会が数多く行われています。チーム内で実施しているものもあれば、チームを跨いだ有志での会もあります。カイポケの開発チームは現在リモート中心で業務を行っているので、読書会もリモートで行っています。本記事でご紹介する読書会もリモートワークが主体になってから生まれた読書会です。 『プロダクト・レッド・オーガニゼーション』とは 読書会の話に入る前に、『プロダクト・レッド・オーガニゼーション』(以下、本書)について簡単に内容を紹介します。 pub.jmam.co.jp 本書はPendoの共同創業者兼CEOであるトッド・オルソン氏の著書で、原著は2020年、日本語の翻訳書は2021年10月に発売されました。本書ではプロダクトを通して顧客の獲得と維持、拡大を行う組織をプロダクト主導型組織と呼び、そのような組織がどのようにプロダクトからデータを収集し、意思決定を行っているかについて解説をしています。 私の所属するカイポケの開発チーム内で、ロードマップの作成や優先順位づけにあたって、利用データをこれまで以上に重視しようという意識が高まっていたこともあり、この書籍に興味を持ちました。 『プロダクト・レッド・オーガニゼーション』を読書会で読むことになった経緯 本記事でご紹介する読書会は、2021年4月頃から現在の形式で活動をしています。エンジニアだけでなく、プロダクトマネージャーなども参加して一緒に本を読んでいるのが特徴です。 読書会で読む本はそれぞれが候補をいくつか挙げ、最終的には参加者全員の話し合いによって決めています。明確な選定基準はないのですが、技術一辺倒の本よりも、エンジニアとプロダクトマネージャーがそれぞれの立場で読むことができ、みんなで色んな話をするきっかけになるような本を選んでいます。 そのような経緯で、これまでにこの読書会で読んできた本は以下のとおりです。 『プロダクトマネジメント』 『エリック・エヴァンスのドメイン駆動設計』 (一部) 『プロダクト・レッド・オーガニゼーション』 『プロダクトリサーチ・ルールズ』 (現在読書中) プロダクト系の読書会と銘打っているわけではないのですが、並べてみるとプロダクトに関する書籍が多いことに気づきます。 読書会の進め方 Slack上で通話をしながら行っています。進め方は以下の通りで、これを1時間ひたすら繰り返します。 読む範囲(数ページ程度)を決める それぞれが黙々と読む 読んでいる間に生まれた疑問点や感想をesaに書き出す それぞれが読み終わったところで、出てきた疑問点や感想について議論する すべて話したら次の読む範囲を決める 実際に行われた議論の例 以上のような進め方で本書を読んでいる中で、実際に行われた議論の例を紹介します。 『プロダクト・レッド・オーガニゼーション』は、さまざまな視点から幅広いプロセスについて、非常に具体的に記述しているため、「自分たちのプロダクトで考えるとどうなるだろう?」という問いが生まれやすかったです。 例えば無料トライアルの効果的な事例として、ガイド付きのセルフサービス体験が紹介されています。トライアルの中でプロダクトの価値を最大限に感じてもらうため、そこに至る準備や設定を省略し、架空のテストデータとウォークスルーのプログラムを用意することで、より短い時間で効果を感じてもらえるようになったという事例が紹介されていました。 この事例紹介を読んだあと、自分たちのプロダクトであるカイポケのトライアルについての議論が生まれました。介護サービスの提供は1ヶ月単位で行われます。そのため給付管理業務がカイポケでどこまで効率的になるのかを実感していただくためには、最低でも1ヶ月という期間が必要になります。カイポケではこの1ヶ月単位で行われる業務が円滑に行えるように設計をしています。 しかし、前述のウォークスルーの例を読んで、カイポケ内で有効性を十分に伝えられていないのではないかという疑問が生まれました。そこで、カイポケであればどのようなウォークスルーが考えられるかについて議論しました。 1ヶ月の流れを掴めるようなウォークスルーはどのようなものが考えられるのか。 そのために準備しておくデータは何か。 使い始めのユーザーがつまずきやすいポイントはどこなのか。 これらを実現するためにはどのような環境が必要か。 現状の仕組みで実現するためにはどのような設計が考えられるか。 このような論点に加えて、実際の利用状況のデータ等も参照しながら、エンジニアとプロダクトマネージャーが立場を跨いで議論することができました。 読書会を終えての感想 読書会を通じて、本書で出てくるアイデアを自分たちのプロダクトだったらどのように適用できるだろうかと考える機会が度々ありました。 また、データ分析やメトリクスに基づく意思決定の重要性が繰り返し説明されていたことも印象に残りました。業務の中でもメトリクスの整備や振り返りを進めていたところだったので背中を押してもらえたような気がします。 以下では、私の感想だけでなく、読書会の他の参加者の感想も紹介します。 プロダクトマネージャーの感想 「正しいもの」を作るためにデータを活用すること、プロダクトチームとカスタマーサクセスチームの連携、価値の無い機能は捨てるなど、全体的に取り組み始めたことや今後やりたいと思っていることに関連した内容が多く刺さる本だった。 顧客の獲得からプロダクトの改善、戦略の立案からプロダクトオプスの部分まで幅広い観点で書かれていたので、今後のガイド的に使っていけると思った。 エンジニアの感想 顧客満足度やNPSといったよく目にする指標について、それを利用する文脈・目的を含めて説明されていて、役に立った。 プロダクトを改善していくための仕組みをプロダクト自体に色々と組み込んでいくのがよいという話が多々あり、そのようなことを行おうとしていくと、システム開発の仕方にも変化が出てくるのではないかと感じた。 終わりに 現在読書会では『プロダクトリサーチ・ルールズ』を読んでいます。読書会のesaやSlackから興味を持ってもらい、新しいメンバーも加わりました。単純に参加者が増えただけでなく、カイポケではないプロダクトを担当してるエンジニアや別チームのプロダクトマネージャーが参加してくれるようになりました。 このような読書会だけでなく、エス・エム・エスでは普段からプロダクトマネージャーとエンジニアが近い距離で働いています。役割によって壁を作ることなく、共に取り組みながらより良いプロダクトを提供できるよう努力していきます。
アバター
医療・介護・ヘルスケア・シニアライフの4つの領域で高齢社会に適した情報インフラを構築している株式会社エス・エム・エスのAnalytics&Innovation推進部( 以下、A&I推進部)でデータ分析基盤開発を担当している長谷川です。 A&I推進部はエス・エム・エス社内のデータを横断的に収集し、データの分析や加工から、データに基づく施策までを行う部門で、現在は介護事業者向け経営支援サービスである「カイポケ」や、介護職向け求人情報サービスである「カイゴジョブ」のデータ分析やレコメンドシステムの開発を行っています。 エス・エム・エスは多くのサービスでAWSを採用しており、A&I推進部においてもAWSのマネージドな機能を活用してデータ分析やサービス開発を行っています。 A&I推進部とは エス・エム・エスは主に医療・介護領域を事業のドメインとしていますが、それらのうち特に介護領域は労働集約型の事業が多く、産業データ活用があまり進んでいないのが現状です。 ここ数年で介護給付費は10兆円を超え、国家予算のおよそ1/10を占めるほどにまで膨らんでいますが、日本の少子高齢化は今後も進んでゆく可能性が非常に高く、 2065年には65歳以上の高齢者が全人口の4割近くになるとも予想されています。 高齢化に伴い医療や介護の需要が増大する一方で、生産年齢人口の減少により、これらのサービスを支える医療・介護従事者の不足が深刻な課題となっています。これにより、今後質の高い医療・介護サービスの提供が難しくなると予想されます。 エス・エム・エスは、解決すべき重要な社会課題の1つとして「質の高い医療・介護サービスの提供が困難になる」という問題を捉えており、A&I推進部はその中でデータ分析を利用した介護領域への貢献を推し進めるために立ち上げられた事業部です。 横断データ分析するための環境づくり エス・エム・エスには介護・医療領域の従業者と事業所とのマッチングを業務領域とするキャリア事業部や、SaaS型の「カイポケ」など、介護・医療領域のデータが多く蓄積されていますが、個々のサービスごとにデータが分断されており、蓄積されたデータが最大限に活用されているとは言い難い状態でした。 A&I推進部はエス・エム・エスでのデータ活用を押し進めるべく、各サービスのデータを横断的に収集、安全にアクセスできる仕組みと、個人情報を匿名化したうえで配布できるデータ分析環境をAWS上に構築しました。 この仕組みはA&Iが主としてデータ分析を行いつつ、エス・エム・エスの社員が希望すれば分析環境にアクセスできる仕組みも同時に提供してきましたが、なかなか活用が進みませんでした。 「データの民主化」の誤解 データ活用が進まなかった理由として、「データ民主化」という言葉への誤解があったと思います。 我々は「事業横断でデータを集めて一元管理すること」ができ「希望すれば誰もが分析環境にアクセスできる仕組み」を提供できれば、データエンジニアやアナリスト以外の社員が自主的にデータにアクセスして分析するようになると考え、それを称して「データの民主化」と捉えていました。 しかしながらこれは完全に誤った解釈で、データの管理や環境を用意するのはあくまで手段であり、本来は「誰もが容易に集めたデータにアクセスし、分析や意思決定を行える文化を醸成すること」だったのですが、この視点が完全に抜け漏れていたのです。 データ活用が捗らなかった理由 私はエス・エム・エスに入社するまではWEBエンジニアとしてビッグデータソリューションの開発・運用に携わってきました。 そのためマーケターやアナリスト業務について深い理解を持っておらず、逆にSQLやビッグデータ基盤などの知識・経験を有していました。これが誤解を生む要因の一つでした。 SQLはWEBエンジニアのほぼ共通言語のようなものなので、欲しいデータの入ってるテーブルをいくつかJOINしてデータを集計することが当たり前でしたが、開発以外の業務ではSQLを使わないことのほうが多く、SQLクライアントに接続することの障壁が高く、さらにそのうえでSQLを書くということはかなり難しいことでした。 例えば毎日所定のフォルダにバッチ処理でDBのデータを集計したデイリーサマリを出力しているとします。 このサマリを時間別に集計したいとなった場合、バッチ処理で利用しているテーブルを特定し、集計軸を変えて正しく動くクエリを書く、ということを1からやれと言われたらエンジニアでも難しいですが、それをマーケターやアナリストが自分でやるというのはかなり難易度が高い業務になります。 また、分析そのものの難しさもデータ活用が進まなかった理由です。 分析を依頼する場合、依頼元も何をどう可視化したいのか、どこのデータを使えばやりたい分析ができるのかがわからないことのほうが大半で「時系列でマーケットシェアを出したいんです」のような依頼が来た場合、直近12か月に絞ったシェア率なのか、月次・週次は平均値なのか、その場合は移動平均なのか、などのイメージを予め固めたうえでないと可視化することは難しいです。 しかしながら、大抵の場合は「おそらくこういったデータがあれば何かが見えてくるはず」という主観的なイメージが起点となるので、手さぐり状態で可視化や分析を始め、「データを探す→抽出する→分析して可視化する」を繰り返す必要があり、A&Iの分析環境ではこのサイクルを繰り返すことに非常に多くのコストがかかっていました。 課題解決に向けて 課題が見えてきたので、分析したいことはあるが解像度を上げることが難しい事業側に対し、より高速にデータを可視化し、そのうえでそのうえで多種多様な軸で分析を繰り返すにはどうすればよいかを考えました。 A&I推進部はDWHにRedshiftを採用しているのですが、これまではRedshiftで集計したデータを一度S3に出力し、S3から各事業部のBIツールごとに異なる出力先へ出力、必要に応じてTableauなどのダッシュボード更新を手動で行っていました。 これでは最新のデータに更新するにも手動で再取り込みが必要ですし、BIツールのビジュアル更新にも時間がかかり、最新の状態でKPIを見直すことができません。 そこで、各事業部で利用しているBIツールについてはCSVデータを提供するに留め、新しい分析依頼については可視化をすべてQuickSightに集約することに方針を変更しました。 QuickSightはAWS上のマネージドサービス(S3、Athena、Redshift、RDSなど)をデータソースとしてWEBブラウザベースで可視化できるBIツールで、他のBIツールに比べても閲覧者が最大$5/月、作成者も$24/月(年間契約なら$18/月)という リーズナブルなコストで利用できるBIツール です。 QuickSightはデータソースという単位で可視化するデータを管理しており、 ほとんどのAWSのマネージドなリレーショナルデータベースに接続可能 です。 A&I推進部では事業データを集計してサマリデータを作る部分を共通バッチ化しているため、Redshiftには加工されたサマリデータの最新版が常に格納されており、QuickSightから最新のサマリデータに直接接続できるため、この仕組みを使えば可視化におけるアウトプット速度を最大化できることがわかりました。 しかしながらプロダクトとして採用するためには、A&I分析環境のルールに沿ったセキュリティ要件をクリアする必要がありました。 A&Iの分析環境はセキュリティの観点から特定のIPからのみ接続を許可しており、一般ユーザーにはAWSコンソールも開放していません。一方でQuickSightはグローバルなサービスのためAWSコンソールからログインする必要があり、アクセス元IPによる接続制限をかけることができませんでした。 これを解決するためにまずAWS SSOによるIP認証を試みたのですが、AWS SSOを通すと接続元IPがAWSのIPとなってしまい、正しい接続元IPを取得することができずこの方法も利用できませんでした。 そこでフロントにIP制限を設けたKeycloakを配置し、認証をkeycloak経由とすることで接続元IPに絞ったうえでQuickSightにアクセスする手法を採用しました。 QuickSight導入により、Redshift上の最新のデータソースに即座にアクセスしブラウザで最新のKPIを確認出来るようになりました。 またダッシュボードを見ながら「この軸を深掘りしたい」「別の軸で集計したい」という希望に対してその場で更新しその結果を共有できるようになり、一番の課題であった「多様な軸をすぐに可視化して分析」することができるようになりました。 QuickSight導入後 QuickSight導入後、可視化案件の質が大きく向上しました。 例えばコールセンターの可視化案件では、これまではダッシュボード更新の煩雑さからダッシュボードの元データをExcelで直接集計し、ダッシュボード更新が行われない問題がありましたが、今ではQuickSight上で日々のKPIを追うようになっています。 またQuickSightはSPICEというインメモリDBを利用しているためデータ集計速度が向上し、Excel集計では限界だったデータの可視化なども可能になりました。 すべてがAWSのマネージドな環境に収まった結果、今まではデータエンジニアがデータ集計し、データアナリストが可視化を行うというように、暗黙的に業務が分担されていましたが、データエンジニアがデータ集計からボード作成まで行うようになり、メンバーの業務スキルの幅が広がるという、当初想定していなかった良いフィードバックも生まれました。 昨年9月に本格導入した後、これまでに60以上の可視化を行い、うち40以上のダッシュボードが今でも活用されています。 今後の課題 QuickSight導入により分析・可視化の速度を格段に改善でき、A&I事業部と事業担当者双方が、データの解像度を揃えながら分析を繰り返すことができるようになったことは「データ民主化」の大きな一歩だといえます。 ただしこれらのダッシュボード作成はデータエンジニア、データアナリストが行うことがまだまだ多く、本来のデータ民主化である「社員誰もが独自に分析を行い、意思決定できる文化の醸成」まで及んでいません。 しかしながら今回QuickSightを導入したことで「便利そうだからやってみたい」という土壌ができ、このムードを維持しながらデータ分析に参加してもらうことが今後の課題と考えています。 また技術的には、QuickSightには異常検知や未来予測などのインサイト機能であったり、SageMakerで作成した独自モデルと統合できるなどML機能が豊富に備わっていますが、これらの機能についても十分に活用できていないため、ゆくゆくはAI機能を活用したダッシュボード開発も進めていきたいと思います。
アバター
エス・エム・エス テックブログ運営の小林と樽本です。今回の記事は 技術推進グループ長に聞いてみた・前編 「全社横断の技術アドバイザー」 の後編となります。 光宗朋宏 Tomohiro Mitsumune -技術推進グループ グループ長 Ex. DeNA 面接においてどういうことを意識されているのでしょうか? 四つ目の質問です。光宗さんはエンジニアの方の技術面接を担当されることが多いと思いますが、面接の際にどんなことを意識しているのか教えてください。 自分も面接時に、光宗さんに技術面接を担当いただいたのですが、今までの仕事について突っ込んで聞かれた覚えがあります...(笑)。 もちろん先方の経歴は事前に拝見しているのですが、「話を組み立てる能力」は特に重視しており、そこをチェックできるような面接を心がけています。 具体的には、ご自身の経歴を自由作文形式で喋っていただくようにしています。 僕がいっぱい喋ってしまうとなんだか誘導尋問のようになってしまうので、例えば「今までキャリアの中で一番大変だった仕事はなんですか?」と投げかけてみて、聞かれたことに対してどうやって話を組み立てていくのかな?という点を見ています。 エンジニアって「難しい課題に対してロジックを組んで解決するスキル」と同じくらい「組み立てたロジックを他人に説明するスキル」が大事だと僕は思っています。 なので、自分は面接中になるべく喋らないことを目標にしたりして、その辺りのスキルを見るようにしていますね。 となると、光宗さんが面接でいっぱい喋ってしまう時は候補者さんがうまく話を組み立てられなかったりで、お見送りとなってしまうパターンが多いのでしょうか? エンジニア3〜4年目のいわゆるポテンシャル枠の方に対しては、話している中でヒントを出したりすることもありますが、エンジニア15年目のベテランの方にはそれ相応の期待値があるので、そういったヒントを出すことはあまりしないですね。なので、僕がいっぱい喋ったことがお見送りの基準になるかどうかは、先方に対する期待値によって変わってきますね。 なるほど、こちらの問いかけに対するアウトプットの質でレベル感だったりを測っているのですね。 はい。これまでの面接官の経験として、一時間という時間の中で相手のことをより深く知るには「相手に多く喋ってもらう」方針は間違っていないと思っています。 面接時に意識していることとしては、「相手に多く喋ってもらうこと」ということになるんですね。 エンジニアとしてステップアップしてきた方法は? 最後の質問です。ベタですが、今までエンジニアとして働かれてきた中で、どうやって様々な壁や修羅場を乗り越えてステップアップしてきたのかというところをお聞きしたいです。 どうやってステップアップして来たかと言われると、自分で狙ってキャリアを築いてきたわけではないので偶然なところがあります。 過去に3回転職してきた中では、結局今まで自分がやってきたことの延長戦だと面白くないので「自分の手に負えないことをやりたい」とか、「明らかにレベルの高そうなところに行きたい」「ハードルがあるところに行きたい」などのマインドを持つようにしていました。そうすると壁というかチャレンジみたいなことが自然といっぱい出てくるので、それをその時々で乗り越えられるように努力してきましたね。 転職歴的なところでいうと、一社目がメーカー系で二社目が大手toCのWeb企業でした。その時の社内の風潮として、新規事業を立てたり別の会社を子会社化したりするものがありまして、その流れで新規事業として立ち上げるWebサイトの開発を希望して入ったんですね。 チームにはエンジニアが僕一人、デザイナーが一人、そしてPMとして起案者が一人いまして、起案者のやりたいこと、実現したいことをヒアリングした上で当時の競合サービスを片っ端から調べました。 人気だった他社サービスのデザインをベースにしようということになり、デザイン面はデザイナーと協力して考え、それ以外のサービスの立ち上げからは僕一人でやっていました。サーバーの知識は大学〜大学院時代に研究室のLinuxサーバーの管理者を好きでやっていたり、当時は自宅サーバーが流行っていた時代でその流行に乗っかっていたりしたので、わりかしそこら辺は得意だったので進められました。 その時エンジニア1人でやり遂げられたのは、自分が持っていた趣味嗜好と仕事がたまたまフィットしてうまくいっただけなので、狙ってやったかと言われると全く狙っていなかったです。 「手に負えないことをやりたい」と思って進んでみたら、良い方に進んだようです。なのでステップアップというか、自分を成長させるためにあえて「手に負えない」現場や環境に飛び込んできたと思います。 かなりストイックですね...。自分のできること/できないことの線引きをした上で、できないことをできるようになりたいという姿勢ですよね。 できるようになりたい、はちょっとかっこよく言い過ぎですね(笑)。 単に飽き性なんだと思います。同じことをやり続けると飽きるだけです。 大体何年くらいで飽きが来るのですか? 大体3-5年ですね。一般的なエンジニアの転職サイクルと一緒です。 なるほど...(笑)。 新規事業立ち上げのWebサイト開発を希望し入社され、そこからどのようなキャリアを進んで行ったのでしょうか? 新規事業のサービスは無事にローンチされて、社内の評判もよかったです。社内的な知名度と評価はこれがきっかけで生まれたんじゃないかなと思います。 ローンチ後の二年半くらいはそのサービスの開発と運用を諸々、サーバー監視以外は基本一人でやっていました。 その二年くらいの実績を評価してくれる人から、子会社の開発部長やらない?みたいな話をいただいて、二年以上やっていて流石にやることがなくなってきていたので、引き継ぎ資料を作って子会社に開発部長として移籍しました。 その時が初めてのマネージャー職だったのでしょうか? そうですね、初めてです。 なるほど。初めてマネージャーをご経験される中で、多くのエンジニアが悩みがちな満足のいくエンジニアリングとマネジメントの両立はできていたのでしょうか? できていたと思います。一応部長として、営業だったりデザインだったりの他部署の偉い人が集まる会議に出ていたんですが、そこで僕が喋ることは「こういうことやりたいです」でした。 結局マネジメント役でそういう場所に出ると、ある程度の裁量があるのでやりたいことを自分で決められるんですね。もちろんやりたくないこともあるんですが。 自分のやりたいことができるような流れを作っていたので、満足にエンジニアリングができていました。 ただ、忙しいっちゃ忙しかったですね...。 確かにお話を聞いているとエンジニアの数が足りていなさそうな感じはしていました。 マネジメントするエンジニアの数は4-5人と多くなかったので、マネジメントで忙しい訳ではなかったです。 タイミングとか巡り合わせもあると思うのですが、元々マネジメント志向はあったのでしょうか? 1ミリもなかったです。 ただ、その会社で働いていくうちに「この会社ではマネジメントスキルがないと、給料も上がらないしやりたいことができないな」ということはわかってきていたので、マネジメントをやりたいかやりたくないかは置いておいて、やった方が得になるなと考えました。 長い目で見ても、コードも書けてマネジメントもできる方が人として強いんじゃないかと考えてました。実際楽しくないしあまりやりたくはなかったですが、やっておいた方がいいと思ってやってましたね。 なるほど...。お話を聞く限りエンジニアリングとマネジメントの両立もできていたように思えるのですが、そのままそのマネージャ職を続けるという選択はしなかったんですか? その会社に5年いたんですけど、開発部長としてマネジメントをやるのに飽きたので転職したのが前職の大手ソーシャルゲーム企業なんです。 飽きるっていうと言葉が悪いですが、向こう3、5年同じ環境に居続けた場合と、違う環境に移動した場合で伸び幅がどれだけ違うかとか、モチベーションを保てるのか、とかが大きな理由ですね。 あとは当時の大手ソーシャルゲーム企業は、どこも有名他社と採用合戦をやり合ってて、できるエンジニアはそこら辺のどこかに行くみたいなのが風潮としてあったんですよ。なので、できるエンジニアに囲まれることで自分のレベルがどのくらいなのか客観的にわかるかな、と期待していたのも大きいですね。 やっぱりお話を聞いているとストイックなところが垣間見えるのですが、そんなことないんですかね...。 うーん、自分の実力って他人に評価してもらわないとわからない部分もあるじゃないですか。そして、同じ環境に居続けると、「どう立ち振る舞えば高い評価を得られるのか」がわかってきてしまいますし、それは評価を受ける上でフェアじゃないと思っています。それがストイックなのかはわかりません(笑)。 感覚的には、お金を稼いでる会社がネームバリューある人をいっぱい採用する環境ってメジャーリーグっぽくて、そういう憧れみたいなのもありました。 大手ソーシャルゲーム企業の時に田辺さんと一緒にお仕事されてたんでしたっけ? そうです。3年半ぐらいいて、最初の一年ぐらい田辺さんと同じチームで働いてて、仕事内容としてはエス・エム・エスにおける技術推進グループみたいなところで、僕と田辺さんは横串で開発効率改善のようなことをしていました。 前職から既に横断的にご活躍されてきたんですね。エス・エム・エスに入ってこられたのは田辺さんがエス・エム・エスに入られてからなんですか? 田辺さんがエス・エム・エスに転職してから、1年半後ぐらいに入社してます。ずっと誘われてはいましたが、少し時間が空いてますね。その時は、エス・エム・エスも一つの選択肢として考えていて、他にもいろんな会社の話を聞きつつ転職活動をしていて、最終的にエス・エム・エスに決めました。 その時はエス・エム・エス一本ではなくて、別の会社の話も聞いていたんですね。 ですね、いろんな会社の話を聞いてました。 そろそろお時間になりました。本日はありがとうございました。インタビューという名目でしたが、色々とスケールの大きい話が聞けて為になる時間を過ごさせてもらいました! キャリアの話は意図してこのキャリアに進んだわけではないので、あんまり参考にならないかもしれません。ただ、迷ったら面白そうな道を選んできたのでそれが吉と出ているかも。選択をミスっても死なないですし、売り手市場なのでなんとでもなるでしょう、という気持ちでキャリアを選択してみて良いと思います。 (完) tech.bm-sms.co.jp
アバター