エムスリーテックブログ エムスリー(m3)のエンジニア・開発メンバーによる技術ブログです 2025-02-28T11:00:00+09:00 m3tech Hatena::Blog hatenablog://blog/8599973812294943206 Rust で作る Step Functions が何でこけたかわかる君 hatenablog://entry/6802418398331821598 2025-02-28T11:00:00+09:00 2025-02-28T11:00:01+09:00 こんにちは、AI・機械学習チームの苅野です。エムスリーに転職してそろそろ一ヶ月、今日も楽しくコードを書いています。 この記事では仕事の合間に作った Rust 製の CLI を紹介します。AWS Step Functions のステートマシンが途中で実行に失敗した時に何が原因で何回失敗したかを数えることができます。 <p>こんにちは、AI・機械学習チームの<a href="https://bsky.app/profile/hkford.bsky.social">苅野</a>です。エムスリーに転職してそろそろ一ヶ月、今日も楽しくコードを書いています。</p> <p>この記事では仕事の合間に作った Rust 製の CLI を紹介します。AWS Step Functions のステートマシンが途中で実行に失敗した時に何が原因で何回失敗したかを数えることができます。</p> <ul class="table-of-contents"> <li><a href="#とあるプロダクトでの-Step-Functions">とあるプロダクトでの Step Functions</a><ul> <li><a href="#Run-Task-失敗に対応する">Run Task 失敗に対応する</a></li> </ul> </li> <li><a href="#Step-Functions-が何でこけたか一目でわかる-CLI-を作る">Step Functions が何でこけたか一目でわかる CLI を作る</a><ul> <li><a href="#その前に-Rust-で書いてどうだったか">その前に: Rust で書いてどうだったか</a></li> <li><a href="#dialoguer-でターミナルからステートマシンを選ぶ">dialoguer でターミナルからステートマシンを選ぶ</a></li> <li><a href="#AWS-SDK-for-Rust-でステートマシンの一覧や実行履歴を取得する">AWS SDK for Rust でステートマシンの一覧や実行履歴を取得する</a></li> <li><a href="#AWS-SDK-for-Rust-をテストする">AWS SDK for Rust をテストする</a></li> <li><a href="#AWS-SDK-for-Rust-の-tips">AWS SDK for Rust の tips</a></li> </ul> </li> <li><a href="#We-are-hiring">We are hiring!</a></li> </ul> <h2 id="とあるプロダクトでの-Step-Functions">とあるプロダクトでの Step Functions</h2> <p>私たちのチームでは定期的に ECS Task on Fargate Spot で処理を実行し、そのオーケストレーションに Step Functions を利用しているプロダクトを運用しています。オーケストレーションといってもただ Step Functions から ECS の Run Task を実行しているだけですが、Step Functions を用いることでリトライ処理を Step Functions に任せて ECS Task は処理の内容に集中することで責務を分けています。</p> <p><figure class="figure-image figure-image-fotolife" title="Step Functions ステートマシン"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20250228/20250228110009.png" width="631" height="211" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Step Functions ステートマシン</figcaption></figure></p> <p>入社後のオンボーディング中に追加された Slack チャンネルの 1 つに AWS や Google Cloud で動いているワークロードで発生したエラーが通知されるチャンネルがあります。このチャンネルに入ってしばらくすると Step Functions の実行に失敗した通知がいくつか送られてきました。</p> <p><figure class="figure-image figure-image-fotolife" title="slack 通知"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20250228/20250228110005.png" width="800" height="401" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>slack 通知</figcaption></figure></p> <p>コンソールにログインして詳細を確認したところ、Task の起動に失敗したことや Task が途中で中断されたことが原因のようでした。</p> <h3 id="Run-Task-失敗に対応する">Run Task 失敗に対応する</h3> <p>Run Task が失敗する場合は<a href="https://repost.aws/ja/knowledge-center/ecs-fargate-runtask-capacity">ベストプラクティス</a>にあるように以下が推奨されます。</p> <ul> <li><p>複数 AZ を指定する</p></li> <li><p>複数の CPU とメモリの組み合わせを指定する</p></li> <li><p>間隔をおいてリトライする</p></li> </ul> <p>ステートマシンを定義している Terraform コードを確認したところリトライの間隔が短く回数も少なかったので、間隔を長く取り回数も増やすように修正しました。</p> <p>ただこれで無事解決というわけではありません。複数 AZ を指定して何度もリトライすれば処理を完了できる可能性が高まるというだけで、仮に 5 回リトライしても 5 回とも Task 起動に失敗すれば処理は失敗です。リトライ回数をもっと大きな値、例えば 100 回まで増やせば大抵の処理は完了しそうですが、処理が途中まで完了していて中断されることが何度も起きていると最初から Fargate (not Fargate Spot) で起動しておけばよかったとなりかねません。あるいは定常的に数回リトライしているならもしかしたら前払い RI で確保した EC2 上で Task を起動してしまった方が単純なコスト面だけでいえば安いとなるかもしれません。</p> <h2 id="Step-Functions-が何でこけたか一目でわかる-CLI-を作る">Step Functions が何でこけたか一目でわかる CLI を作る</h2> <p>このように考え始めると Step Functions の実行履歴を調べてステートマシンがどんな理由で何回失敗したか調べたくなります。しかしコンソールを開いて該当のステートマシンを開き、実行履歴を見て、どの状態で失敗したかとその原因を確認する、ということを繰り返しているとあっという間に時間が過ぎていきました。色々な画面を開く必要があって面倒です。</p> <p>そこで AWS のコンソールの色々な画面を開く手間を省くためにステートマシンの実行履歴を調べて失敗した実行について原因が何で何回 Task が失敗したかを表示する CLI を作りました。個人的に興味がある Rust で実装しています<a href="#f-d438c71c" id="fn-d438c71c" name="fn-d438c71c" title="Python でもいいかなと思いましたが boto3 をモックするライブラリの moto で Step Functions の API がいくつか対応していないので見送りました。">*1</a>。</p> <p>まずこの CLI の使い方を紹介します。最初に CLI を実行するとまず実行履歴を調べるステートマシンを選びます。</p> <pre class="code term" data-lang="term" data-unlink>State Machine を選択: &gt; calculate-scores-v2 calculate-scores-workday-v2 calculate-scores-not-workday-v2</pre> <p>そして選択したステートマシンの直近の実行履歴を見た上で失敗した実行について Spot 中断によって失敗した回数と Task 起動に失敗した回数を教えてくれます。</p> <pre class="code term" data-lang="term" data-unlink>State Machine を選択: calculate-scores-workday-v2 選択した State Machine の成功は 98 回、失敗は 2 回 失敗した State Machie Execution: arn:aws:states:ap-northeast-1:012345678901:execution:calculate-scores-workday-v2:5fc789df-1a97-3819-448a-b15da23ad9a5_f483b343-f640-b2e9-2d63-80dd10c4b15c 実行失敗した Task は 6 回でそのうち Spot 中断は 5 回、Task 起動失敗は 1 回 失敗した State Machie Execution: arn:aws:states:ap-northeast-1:012345678901:execution:calculate-scores-workday-v2:7a5907fc-f7e0-c206-4eb4-e0f3d2a0f385_e90e49c0-ae1d-298d-caea-17664ea6a3fc 実行失敗した Task は 6 回でそのうち Spot 中断は 2 回、Task 起動失敗は 4 回 </pre> <p>ここからはこの CLI を作る上で使ったクレートと AWS SDK for Rust の使い方(ユニットテストの仕方を含む)を紹介していきます。</p> <h3 id="その前に-Rust-で書いてどうだったか">その前に: Rust で書いてどうだったか</h3> <p>この CLI は外部の API を呼んでその結果をちょこっと整形するだけなので Rust 以外の言語で実装していいと思います<a href="#f-82d9f951" id="fn-82d9f951" name="fn-82d9f951" title="処理時間は外部 API 呼び出しの結果まちが支配的なので速度面の優位性はあまりないですし、メモリ安全が活きる場面もあまりない">*2</a>。後述するように AWS SDK のモック定義が Builder pattern で書くため結構大変だったので、たとえば <a href="https://docs.aws.amazon.com/sdk-for-go/v2/developer-guide/unit-testing.html">Go SDK でモック</a>した方が楽かもしれません。</p> <p>ただいざ作り始めてみると Rust で CLI を作るのは意外とスムーズにできました。難しいことをしていないからというのはありますが 1 時間くらいで動くものが作れました。</p> <p>ターミナル上で選択するには <code>dialoguer</code> クレートを、AWS API を呼ぶには AWS SDK for Rust を使いましたがどちらも example を少し変えればやりたいことができました。Rust には Result 型や Option 型があるのでかっちり作れてよかったです。興味があるからという理由で Rust で実装しましたがそんなに悪くないかもと思いました。</p> <h3 id="dialoguer-でターミナルからステートマシンを選ぶ">dialoguer でターミナルからステートマシンを選ぶ</h3> <p>実行履歴を調べる対象のステートマシンをターミナル上で選ぶために <a href="https://github.com/console-rs/dialoguer"><code>dialoguer</code> クレート</a> を使いました。<a href="https://docs.rs/dialoguer/latest/dialoguer/struct.Select.html#example">ドキュメント</a>にあるようにスライスを渡すとターミナル上で選んだ要素のインデックスが返ってきます。次のようにステートマシンの一覧を渡しています。</p> <pre class="code lang-rust" data-lang="rust" data-unlink><span class="synStatement">let</span> state_machine_names <span class="synStatement">=</span> <span class="synPreProc">vec!</span>[<span class="synConstant">&quot;state_machine_1&quot;</span>, <span class="synConstant">&quot;state_machine_2&quot;</span>] <span class="synStatement">let</span> selection <span class="synStatement">=</span> <span class="synPreProc">Select</span><span class="synSpecial">::</span><span class="synIdentifier">new</span>() .<span class="synIdentifier">with_prompt</span>(<span class="synConstant">&quot;State Machine を選択&quot;</span>) .<span class="synIdentifier">items</span>(<span class="synType">&amp;</span>state_machine_names) .<span class="synIdentifier">interact</span>() .<span class="synIdentifier">unwrap</span>(); </pre> <h3 id="AWS-SDK-for-Rust-でステートマシンの一覧や実行履歴を取得する">AWS SDK for Rust でステートマシンの一覧や実行履歴を取得する</h3> <p>AWS SDK for Rust はサービスごとにクレートが分かれていて、今回扱う Step Functions は <a href="https://docs.rs/aws-sdk-sfn/latest/aws_sdk_sfn/"><code>aws_sdk_sfn</code> クレート</a>が公開されています。例えばステートマシンの一覧を取得するならこんな感じになります(次のサンプルでは簡略化のためエラーは <code>unwrap()</code> で潰していますが実際に作った CLI では <a href="https://docs.rs/thiserror/latest/thiserror/"><code>thiserror</code></a> を使ってエラー処理しています)。</p> <pre class="code lang-rust" data-lang="rust" data-unlink><span class="synStatement">use</span> aws_sdk_sfn <span class="synStatement">as</span> sfn; <span class="synPreProc">#[::tokio::main]</span> async <span class="synStatement">fn</span> <span class="synIdentifier">main</span>() <span class="synStatement">-&gt;</span> <span class="synType">Result</span><span class="synStatement">&lt;</span>(), <span class="synPreProc">sfn</span><span class="synSpecial">::</span>Error<span class="synStatement">&gt;</span> { <span class="synStatement">let</span> config <span class="synStatement">=</span> <span class="synPreProc">aws_config</span><span class="synSpecial">::</span><span class="synIdentifier">load_from_env</span>().await; <span class="synStatement">let</span> client <span class="synStatement">=</span> <span class="synPreProc">aws_sdk_sfn</span><span class="synSpecial">::</span><span class="synPreProc">Client</span><span class="synSpecial">::</span><span class="synIdentifier">new</span>(<span class="synType">&amp;</span>config); <span class="synStatement">let</span> state_machines <span class="synStatement">=</span> client.<span class="synIdentifier">list_state_machines</span>().<span class="synIdentifier">send</span>().await.<span class="synIdentifier">unwrap</span>(); <span class="synConstant">Ok</span>(()) } </pre> <p><code>list_state_machines()</code> でステートマシン一覧を、<code>list_executions()</code> であるステートマシンの実行一覧を、<code>get_execution_history()</code> でステートマシンのある実行のイベント履歴を取得して加工すれば失敗した実行の原因を数えることができます。</p> <h3 id="AWS-SDK-for-Rust-をテストする">AWS SDK for Rust をテストする</h3> <p><a href="https://docs.aws.amazon.com/sdk-for-rust/latest/dg/testing.html">公式ドキュメント</a>に Rust SDK でユニットテストを実装する方法が書かれています。今回は <code>automock</code> を使うことにしました。また公式ドキュメントだと <code>#[cfg(test)]</code> や <code>#[cfg(not(test))]</code> をつけて挙動を制御していますが、この方法だと VSCode が混乱したので次のように trait を定義するようにしました。</p> <pre class="code lang-rust" data-lang="rust" data-unlink><span class="synPreProc">#[cfg_attr(test, automock)]</span> <span class="synStatement">pub</span> <span class="synStatement">trait</span> <span class="synIdentifier">SfnClient</span> { async <span class="synStatement">fn</span> <span class="synIdentifier">list_state_machines</span>( <span class="synType">&amp;</span><span class="synConstant">self</span>, ) <span class="synStatement">-&gt;</span> <span class="synType">Result</span><span class="synStatement">&lt;</span>ListStateMachinesOutput, <span class="synPreProc">sfn</span><span class="synSpecial">::</span><span class="synPreProc">error</span><span class="synSpecial">::</span>SdkError<span class="synStatement">&lt;</span>ListStateMachinesError<span class="synStatement">&gt;&gt;</span>; async <span class="synStatement">fn</span> <span class="synIdentifier">list_executions</span>( <span class="synType">&amp;</span><span class="synConstant">self</span>, state_machine_arn: <span class="synType">Option</span><span class="synStatement">&lt;</span><span class="synType">String</span><span class="synStatement">&gt;</span>, max_results: <span class="synType">Option</span><span class="synStatement">&lt;</span><span class="synType">i32</span><span class="synStatement">&gt;</span>, ) <span class="synStatement">-&gt;</span> <span class="synType">Result</span><span class="synStatement">&lt;</span>ListExecutionsOutput, <span class="synPreProc">sfn</span><span class="synSpecial">::</span><span class="synPreProc">error</span><span class="synSpecial">::</span>SdkError<span class="synStatement">&lt;</span>ListExecutionsError<span class="synStatement">&gt;&gt;</span>; async <span class="synStatement">fn</span> <span class="synIdentifier">get_execution_history</span>( <span class="synType">&amp;</span><span class="synConstant">self</span>, execution_arn: <span class="synType">Option</span><span class="synStatement">&lt;</span><span class="synType">String</span><span class="synStatement">&gt;</span>, ) <span class="synStatement">-&gt;</span> <span class="synType">Result</span><span class="synStatement">&lt;</span>GetExecutionHistoryOutput, <span class="synPreProc">sfn</span><span class="synSpecial">::</span><span class="synPreProc">error</span><span class="synSpecial">::</span>SdkError<span class="synStatement">&lt;</span>GetExecutionHistoryError<span class="synStatement">&gt;&gt;</span>; } </pre> <p>こうしておくとテストコードでは次のように Mock のクライアントを使うことができます。</p> <pre class="code lang-rust" data-lang="rust" data-unlink><span class="synPreProc">#[cfg(test)]</span> <span class="synStatement">mod</span> <span class="synIdentifier">test</span> { <span class="synStatement">use</span> <span class="synPreProc">super</span><span class="synSpecial">::</span><span class="synType">*</span>; <span class="synStatement">use</span> <span class="synPreProc">mockall</span><span class="synSpecial">::</span><span class="synPreProc">predicate</span><span class="synSpecial">::</span>eq; <span class="synStatement">use</span> <span class="synPreProc">sfn</span><span class="synSpecial">::</span><span class="synPreProc">primitives</span><span class="synSpecial">::</span>DateTime; <span class="synStatement">use</span> <span class="synPreProc">sfn</span><span class="synSpecial">::</span><span class="synPreProc">types</span><span class="synSpecial">::</span>{ ExecutionListItem, ExecutionStatus, HistoryEvent, HistoryEventType, StateMachineListItem, StateMachineType, TaskFailedEventDetails, }; <span class="synStatement">fn</span> <span class="synIdentifier">setup_mock_client</span>() <span class="synStatement">-&gt;</span> MockSfnClient { <span class="synStatement">let</span> <span class="synType">mut</span> mock <span class="synStatement">=</span> <span class="synPreProc">MockSfnClient</span><span class="synSpecial">::</span><span class="synIdentifier">default</span>(); <span class="synComment">// There are two state machines: stateMachine:1 and stateMachine:2</span> mock.<span class="synIdentifier">expect_list_state_machines</span>().<span class="synIdentifier">return_once</span>(<span class="synStatement">||</span> { <span class="synConstant">Ok</span>(<span class="synPreProc">ListStateMachinesOutput</span><span class="synSpecial">::</span><span class="synIdentifier">builder</span>() .<span class="synIdentifier">set_state_machines</span>(<span class="synConstant">Some</span>(<span class="synPreProc">vec!</span>[ <span class="synPreProc">StateMachineListItem</span><span class="synSpecial">::</span><span class="synIdentifier">builder</span>() .<span class="synIdentifier">set_state_machine_arn</span>(<span class="synConstant">Some</span>( <span class="synConstant">&quot;arn:aws:states:ap-northeast-1:012345678901:stateMachine:1&quot;</span>.<span class="synIdentifier">to_string</span>(), )) .<span class="synIdentifier">set_name</span>(<span class="synConstant">Some</span>(<span class="synConstant">&quot;1&quot;</span>.<span class="synIdentifier">to_string</span>())) .<span class="synIdentifier">set_creation_date</span>(<span class="synConstant">Some</span>(<span class="synPreProc">DateTime</span><span class="synSpecial">::</span><span class="synIdentifier">from_secs</span>(<span class="synConstant">946_713_600</span>))) .<span class="synIdentifier">set_type</span>(<span class="synConstant">Some</span>(<span class="synPreProc">StateMachineType</span><span class="synSpecial">::</span>Standard)) .<span class="synIdentifier">build</span>() .<span class="synIdentifier">unwrap</span>(), <span class="synPreProc">StateMachineListItem</span><span class="synSpecial">::</span><span class="synIdentifier">builder</span>() .<span class="synIdentifier">set_state_machine_arn</span>(<span class="synConstant">Some</span>( <span class="synConstant">&quot;arn:aws:states:ap-northeast-1:012345678901:stateMachine:2&quot;</span>.<span class="synIdentifier">to_string</span>(), )) .<span class="synIdentifier">set_name</span>(<span class="synConstant">Some</span>(<span class="synConstant">&quot;2&quot;</span>.<span class="synIdentifier">to_string</span>())) .<span class="synIdentifier">set_creation_date</span>(<span class="synConstant">Some</span>(<span class="synPreProc">DateTime</span><span class="synSpecial">::</span><span class="synIdentifier">from_secs</span>(<span class="synConstant">946_713_600</span>))) .<span class="synIdentifier">set_type</span>(<span class="synConstant">Some</span>(<span class="synPreProc">StateMachineType</span><span class="synSpecial">::</span>Standard)) .<span class="synIdentifier">build</span>() .<span class="synIdentifier">unwrap</span>(), ])) .<span class="synIdentifier">build</span>() .<span class="synIdentifier">unwrap</span>()) }); } </pre> <p>このように Mock のクライアントを定義して AWS API 呼び出しをテストしつつステートマシンの実行一覧から情報を抽出するコードを書くことで最終的に冒頭で紹介したような CLI を作りました。</p> <h3 id="AWS-SDK-for-Rust-の-tips">AWS SDK for Rust の tips</h3> <ul> <li>ログレベルに注意する</li> </ul> <p>AWS SDK for Rust は <a href="https://docs.rs/tracing/latest/tracing/"><code>tracing</code></a> クレートを使っていて<a href="https://docs.aws.amazon.com/sdk-for-rust/latest/dg/logging.html">ドキュメント</a>にあるように環境変数 <code>RUST_LOG</code> で出力するログのレベルを調整できます。DEBUG だと多くのログが出てしまうので INFO くらいで出すか <a href="https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives"><code>directive</code></a>を使って DEBUG でログを出す target を絞り込んでおくといいです。</p> <pre class="code term" data-lang="term" data-unlink># DEBUG で出てくるログ(これでもほんの一部) 2025-02-26T01:39:33.794696Z DEBUG aws_runtime::fs_util: loaded home directory src=&#34;HOME&#34; 2025-02-26T01:39:33.795016Z DEBUG load_config_file{file=Default(Config)}: aws_runtime::env_config::source: performing home directory substitution home=&#34;/Users/hidekazu.karino&#34; path=&#34;~/.aws/config&#34; 2025-02-26T01:39:33.795073Z DEBUG load_config_file{file=Default(Config)}: aws_runtime::env_config::source: home directory expanded before=&#34;~/.aws/config&#34; after=&#34;/Users/hidekazu.karino/.aws/config&#34; 2025-02-26T01:39:33.795350Z DEBUG load_config_file{file=Default(Config)}: aws_runtime::env_config::source: config file loaded path=Some(&#34;/Users/hidekazu.karino/.aws/config&#34;) size=34 2025-02-26T01:39:33.795482Z DEBUG load_config_file{file=Default(Credentials)}: aws_runtime::env_config::source: performing home directory substitution home=&#34;/Users/hidekazu.karino&#34; path=&#34;~/.aws/credentials&#34; 2025-02-26T01:39:33.795499Z DEBUG load_config_file{file=Default(Credentials)}: aws_runtime::env_config::source: home directory expanded before=&#34;~/.aws/credentials&#34; after=&#34;/Users/hidekazu.karino/.aws/credentials&#34; 2025-02-26T01:39:33.795534Z DEBUG load_config_file{file=Default(Credentials)}: aws_runtime::env_config::source: config file not found path=~/.aws/credentials 2025-02-26T01:39:33.795563Z DEBUG load_config_file{file=Default(Credentials)}: aws_runtime::env_config::source: config file loaded path=Some(&#34;/Users/hidekazu.karino/.aws/credentials&#34;) size=0 2025-02-26T01:39:33.798279Z DEBUG aws_sdk_sts::endpoint_lib: loading default partitions 2025-02-26T01:39:33.962491Z DEBUG hyper_rustls::config: with_native_roots processed 161 valid and 0 invalid certs 2025-02-26T01:39:33.963139Z DEBUG aws_sdk_sfn::endpoint_lib: loading default partitions 2025-02-26T01:39:33.963565Z DEBUG invoke{service=sfn operation=ListStateMachines sdk_invocation_id=4509290}:apply_configuration: aws_smithy_runtime::client::orchestrator: timeout settings for this operation: TimeoutConfig { connect_timeout: Set(3.1s), read_timeout: Disabled, operation_timeout: Disabled, operation_attempt_timeout: Disabled } 2025-02-26T01:39:33.963653Z DEBUG invoke{service=sfn operation=ListStateMachines sdk_invocation_id=4509290}:try_op: aws_smithy_runtime_api::client::interceptors::context: entering &#39;serialization&#39; phase 2025-02-26T01:39:33.963885Z DEBUG invoke{service=sfn operation=ListStateMachines sdk_invocation_id=4509290}:try_op: aws_smithy_runtime_api::client::interceptors::context: entering &#39;before transmit&#39; phase 2025-02-26T01:39:33.963950Z DEBUG invoke{service=sfn operation=ListStateMachines sdk_invocation_id=4509290}:try_op: aws_smithy_runtime::client::retries::strategy::standard: no client rate limiter configured, so no token is required for the initial request. 2025-02-26T01:39:33.963960Z DEBUG invoke{service=sfn operation=ListStateMachines sdk_invocation_id=4509290}:try_op: aws_smithy_runtime::client::orchestrator: retry strategy has OKed initial request 2025-02-26T01:39:33.963983Z DEBUG invoke{service=sfn operation=ListStateMachines sdk_invocation_id=4509290}:try_op: aws_smithy_runtime::client::orchestrator: beginning attempt #1 2025-02-26T01:39:33.964026Z DEBUG invoke{service=sfn operation=ListStateMachines sdk_invocation_id=4509290}:try_op:try_attempt: aws_smithy_runtime::client::orchestrator::endpoints: resolving endpoint endpoint_params=EndpointResolverParams(TypeErasedBox[!Clone]:Params { region: Some(&#34;ap-northeast-1&#34;), use_dual_stack: false, use_fips: false, endpoint: None }) endpoint_prefix=None 2025-02-26T01:39:33.964051Z DEBUG invoke{service=sfn operation=ListStateMachines sdk_invocation_id=4509290}:try_op:try_attempt: aws_smithy_runtime::client::orchestrator::endpoints: will use endpoint Endpoint { url: &#34;https://states.ap-northeast-1.amazonaws.com&#34;, headers: {}, properties: {} } 2025-02-26T01:39:33.964221Z DEBUG invoke{service=sfn operation=ListStateMachines sdk_invocation_id=4509290}:try_op:try_attempt:lazy_load_identity:provide_credentials{provider=default_chain}: aws_config::meta::credentials::chain: loaded credentials provider=Environment 2025-02-26T01:39:33.964250Z DEBUG invoke{service=sfn operation=ListStateMachines sdk_invocation_id=4509290}:try_op:try_attempt:lazy_load_identity: aws_smithy_runtime::client::identity::cache::lazy: identity cache miss occurred; added new identity (took 53µs) new_expiration=2025-02-26T01:54:33.964184Z valid_for=899.999935s partition=IdentityCachePartition(7) 2025-02-26T01:39:33.964297Z DEBUG invoke{service=sfn operation=ListStateMachines sdk_invocation_id=4509290}:try_op:try_attempt: aws_smithy_runtime::client::identity::cache::lazy: loaded identity 2025-02-26T01:39:33.964598Z DEBUG invoke{service=sfn operation=ListStateMachines sdk_invocation_id=4509290}:try_op:try_attempt: aws_smithy_runtime_api::client::interceptors::context: entering &#39;transmit&#39; phase 2025-02-26T01:39:33.964622Z DEBUG invoke{service=sfn operation=ListStateMachines sdk_invocation_id=4509290}:try_op:try_attempt: aws_smithy_runtime::client::http::hyper_014: new TCP connector created in 0ns 2025-02-26T01:39:33.964729Z DEBUG invoke{service=sfn operation=ListStateMachines sdk_invocation_id=4509290}:try_op:try_attempt: aws_smithy_runtime::client::http::body::minimum_throughput: applying minimum upload throughput check future options=MinimumThroughputBodyOptions { minimum_throughput: Throughput { bytes_read: 1, per_time_elapsed: 1s }, grace_period: 5s, check_window: 1s }</pre> <ul> <li>Builder pattern に慣れる</li> </ul> <p>Rust SDK でクライアントをモックする場合はます <code>MockSfnClient</code> を作成します。そしてモッククライアントに対して <code>expect_list_state_machines()</code> などを定義し、 <code>MockSfnClient.list_state_machines()</code> が呼ばれた時に返すものを定義する流れになります。例えば <code>list_state_machines()</code> は <a href="https://docs.rs/aws-sdk-sfn/latest/aws_sdk_sfn/operation/list_state_machines/struct.ListStateMachinesOutput.html"><code>ListStateMachinesOutput</code></a>構造体を返すのですがこの構造体は Builder pattern で定義していくことになります。</p> <p>Builder pattern では構造体の field を埋めるのに <code>set_&lt;field 名&gt;</code> メソッドを呼んでいき最後に <code>build()</code> して構造体を作成します。次の例だと <code>ListStateMachinesOutput</code> 構造体が <code>next_token</code>, <code>state_machines</code> field を持ってます。そのためこの値を埋めるのにそれぞれ <code>builder.set_next_token()</code> と <code>builder.set_state_machines()</code> を呼んでいます。</p> <pre class="code lang-rust" data-lang="rust" data-unlink><span class="synStatement">struct</span> <span class="synIdentifier">ListStateMachinesOutput</span> { <span class="synStatement">pub</span> next_token: <span class="synSpecial">::</span><span class="synPreProc">std</span><span class="synSpecial">::</span><span class="synPreProc">option</span><span class="synSpecial">::</span><span class="synType">Option</span><span class="synStatement">&lt;</span><span class="synSpecial">::</span><span class="synPreProc">std</span><span class="synSpecial">::</span><span class="synPreProc">string</span><span class="synSpecial">::</span><span class="synType">String</span><span class="synStatement">&gt;</span>, <span class="synStatement">pub</span> state_machines: <span class="synSpecial">::</span><span class="synPreProc">std</span><span class="synSpecial">::</span><span class="synPreProc">vec</span><span class="synSpecial">::</span><span class="synType">Vec</span><span class="synStatement">&lt;</span><span class="synError">crate</span><span class="synSpecial">::</span><span class="synPreProc">types</span><span class="synSpecial">::</span>StateMachineListItem<span class="synStatement">&gt;</span>, _request_id: <span class="synType">Option</span><span class="synStatement">&lt;</span><span class="synType">String</span><span class="synStatement">&gt;</span>, } <span class="synStatement">let</span> builder <span class="synStatement">=</span> <span class="synPreProc">ListStateMachinesOutput</span><span class="synSpecial">::</span><span class="synIdentifier">builder</span>(); <span class="synStatement">let</span> output <span class="synStatement">=</span> builder.<span class="synIdentifier">set_next_token</span>(<span class="synConstant">Some</span>(<span class="synConstant">&quot;token&quot;</span>)).<span class="synIdentifier">set_state_machines</span>(<span class="synConstant">Some</span>(<span class="synPreProc">vec!</span>[state_machine_list_item_1,state_machine_list_item_2])).<span class="synIdentifier">build</span>().<span class="synIdentifier">unwrap</span>(); </pre> <p>先ほどの例だと省略しましたが <code>set_state_machines()</code> の引数の <code>StateMachineListItem</code> も Builder pattern で作成する必要があります。今回だとモックを定義するだけで 150 行ほどかかったので繰り返し定義する構造体はヘルパー関数を作って作成した方がいいかもしれません。</p> <h2 id="We-are-hiring">We are hiring!</h2> <p>AI・機械学習チームでは、手段は Rust に限らずともちょっとしたツールの自作も楽しめるようなエンジニアを募集しています。 次のリンクからご応募お待ちしています!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/">jobs.m3.com</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-d438c71c" id="f-d438c71c" name="f-d438c71c" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">Python でもいいかなと思いましたが boto3 をモックするライブラリの <a href="https://docs.getmoto.org/en/latest/docs/services/stepfunctions.html">moto で Step Functions の API がいくつか対応していない</a>ので見送りました。</span></p> <p class="footnote"><a href="#fn-82d9f951" id="f-82d9f951" name="f-82d9f951" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">処理時間は外部 API 呼び出しの結果まちが支配的なので速度面の優位性はあまりないですし、メモリ安全が活きる場面もあまりない</span></p> </div> hidekazu3 【ソフトウェアエンジニアインターン参戦記】クラウド型電子カルテのリクエスト数を13%削減した話 hatenablog://entry/6802418398328125728 2025-02-18T11:00:00+09:00 2025-03-04T12:16:49+09:00 はじめまして!エムスリーのデジカルチームでソフトウェアエンジニアとしてインターンシップに参加した藤原です。 この記事ではインターン中に行なったタスク内容とエムスリーでのインターンシップの感想や学びをお伝えします! エムスリーでの働き方などをお伝えして、インターンや就職を考えている他の学生の参考になることを願っています! <p>はじめまして!エムスリーのデジカルチームでソフトウェアエンジニアとしてインターンシップに参加した藤原です。</p> <p>この記事ではインターン中に行なったタスク内容とエムスリーでのインターンシップの感想や学びをお伝えします!</p> <p>エムスリーでの働き方などをお伝えして、インターンや就職を考えている他の学生の参考になることを願っています!</p> <h2 id="自己紹介">自己紹介</h2> <p>私は普段は東京理科大学の修士1年でデジタル通信に関する研究を行なっています。 UWB(Ultra Wide Band)という高精度な測距測位精度を誇る通信方式の研究をしています。この技術は位置測位システムなどに活用されています。具体的にはAirTagやiPhoneなどにも活用されており「探す」にも使われている技術です。</p> <h2 id="業務内容">業務内容</h2> <p>インターンシップの業務内容は実際のチームにアサインされ、2週間で環境構築・実装・テストを行い、チケットを消化していくスタイルです。</p> <p>また、タスクの内容もメンターの方と相談しながら、選択できるフルオーダーメイド型という部分も魅力です。私がアサインされたチームのプロダクトである、<a href="https://digikar.m3.com/">デジカル</a>はクラウド型の電子カルテサービスで7000件を超える導入件数の実績がある大規模なプロダクトです。</p> <p>このような大規模なサービスならではのタスクをいただき、技術的にも成長したいと思い、その旨を伝えたところ、パフォーマンス向上に関するタスクをいただきました。</p> <p>具体的なタスク内容としては、「ブラウザで2本のチャンネルでロングポーリングしている部分を1本化することによってリクエスト数を削減する」というタスクです。ここからタスクの背景や詳細をお伝えしていきます。</p> <h3 id="前提">前提</h3> <p>まず、前提として、規模が大きくなるにつれて増えつつあるデジカルのリクエスト数を削減したいという思いがあります。</p> <h3 id="背景">背景</h3> <p>タスクの背景としては、現在、デジカルにおいてコネクション数が増え、リクエスト数が増加しています。その中でロングポーリングしている2つのチャンネルがリクエスト数の約26%を消費しています。この部分を最適化することによってリクエスト数の削減につながります。具体的にはロングポーリングしているチャンネルを1本化できれば、期待値としては約13%ほどのリクエスト数削減が期待できます。</p> <p>そのため、ビジネス面や今後の実装面でも非常にインパクトが大きいタスクになります。</p> <h3 id="技術スタック">技術スタック</h3> <p>ロングポーリングの部分は<a href="https://github.com/saiya/dsps">DSPS</a>というGo製のPub/Subサーバーのライブラリを用いて実装しています。</p> <p><figure class="figure-image figure-image-fotolife" title="DSPS構成図"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20250218/20250218110005.png" width="800" height="370" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>DSPS構成図</figcaption></figure></p> <p>DSPSを用いるメリットとしては、Redisとの連携、JWTの設定が容易、ライブラリ化されているため、実装を簡潔に行えるという点があります。</p> <p>その他、バックエンドはRuby(Ruby on Rails)、フロントエンドはTypeScript(React)を用いています。</p> <h2 id="取り組んだタスク">取り組んだタスク</h2> <p>やったこととしては大きく3点あります</p> <ul> <li>DSPSにおいてロングポーリングを1本化するための新しいチャンネルを定義</li> <li>バックエンド側で新しいチャンネルにリクエストを送信できるような実装</li> <li>フロントエンドで処理が重複しないように共通化</li> </ul> <p>ロングポーリングの処理はDSPSサーバーとPub/Subサーバー両方で動いている部分もあったため、DSPSサーバーで通信を行えるように一部移行しました。DSPSサーバーへの移行部分や、フロントエンド処理の共通化は今後の移行の際の助けになれば嬉しいです。</p> <p>また、DSPSサーバーへの切り替え、チャンネルの1本化により、既存のテストに通らなくなる部分も多かったため、テストコードの修正も行うことができました。</p> <h2 id="インターンシップを通しての感想">インターンシップを通しての感想</h2> <p>エムスリーのインターンシップでは様々な貴重な経験ができ、間違いなく今後の私にとって、糧となると思いました。具体的な要素をこれからお伝えします。</p> <h3 id="やっていきの精神">「やっていき」の精神</h3> <p>エムスリーのソフトウェアエンジニアの最大の特徴としては、バックエンドやフロントエンドなどといった役割が分かれておらず、タスクベースでフロントからインフラまで一貫して実装するという部分です。私自身、フロントエンドの経験が少なかったため、実装のキャッチアップや技術を身につける部分に手こずってしまう部分は多かったですが、一気に集中して取り組んだため、この短期間でもかなり理解が進み、実装をやり切ることができました。また、メンターの方とペアプロのような形でサポートをしていただく機会もあり、細かな作法や開発手法についても学ぶ部分が非常に多かったです。</p> <p>年次に関わらずできる人に任せていく文化であり、その前提には技術力の高い人々が集まっている環境であるからこそだと感じました。</p> <p>また、技術が目的ではなく、プロダクトをより良くしていくために高い技術力を活かしていきます。プロダクトをより良くしていきたい、という思いが開発へのモチベーションにつながるという部分は自社開発ならではのメリットだと思いました。私もプロダクトに情熱を注いで開発に取り組みたいと思っているので、その環境の中で開発できたのが良かったです。</p> <h3 id="社内の雰囲気を肌で感じることができた">社内の雰囲気を肌で感じることができた</h3> <p>インターン期間中は社員と同じように定例ミーティングやイベントに参加させていただき、社内の雰囲気を肌で感じることができました。</p> <p>朝にはDaily Scrumという前日の進捗確認と当日のやることを擦り合わせる会があるため、リモートでもアラートを上げやすい環境にあります。また、zoomではチャットが常に盛り上がっているため、伝えたいことを伝えやすい環境にあり、コミュニケーションミスが起こりにくい環境になっている点が非常に良いと思いました。</p> <p>Tech Talkという社内のLT会や、社内のエンジニアが一同に集まってナレッジを共有し合うミーティングにも参加させていただきました。インターンシップに参加する前にエムスリーの印象として、ギークな人々の集団、というイメージがありましたが、まさにそれを体感するような会でした。そのような環境で過ごすことで常に刺激を受け続けることができる環境にあると思います。また、自分の基準を常に高い位置に置ける環境にあると思いました。</p> <p><a href="https://www.youtube.com/@m3techchannel160">エムスリーの公式テックチャンネル</a>でもその雰囲気を感じることができると思うので興味のある方はぜひ見てみて下さい!</p> <h3 id="大規模サービスに触れることができた">大規模サービスに触れることができた</h3> <p>大規模なプロダクトのリクエスト数を削減し、パフォーマンスを向上させる、という経験は学生では中々経験する機会はないと思います。そのようなタスクを任せていただき、実装できたのは非常に稀有な経験になり、今回の経験が自分の頭の中のリソースにストックされることにより、今後のエンジニア人生において助けになる時が来るのではないかと感じました。</p> <h3 id="おまけ">おまけ</h3> <p>2週間のインターンシップの内、初日と最終日に出社するのですが、出社日にはランチをご馳走になったり、業務後には飲み会にも誘っていただきました! 業務関係なく、就活の話や今後の人生の話、その他色々な話をすることができ、密度の濃い、忘れられない2週間になりました! <figure class="figure-image figure-image-fotolife" title="出社時のランチ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20250218/20250218110008.jpg" width="800" height="600" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>出社時のランチ</figcaption></figure></p> <h2 id="最後に">最後に</h2> <p>一般的なインターンシップは学生何人かに対して、開催されるところが多いと思います。エムスリーのインターンシップは基本的には期間中は学生1人がチームにアサインされ、メンターがついて実際のタスクを遂行していくスタイルです。このためフルオーダーメイドで非常に密度の高く、成長できる環境が整っています。技術的に難しい部分、苦労する部分も多かったですが、それを乗り越えるたびに成長を実感しました。</p> <p>興味がある方はぜひエントリーしてみてください!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fopen.talentio.com%2Fr%2F1%2Fc%2Fm3%2Fhomes%2F4354" title="【新卒】エンジニアリンググループ 募集一覧 / エムスリー株式会社" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://open.talentio.com/r/1/c/m3/homes/4354">open.talentio.com</a></cite></p> tsunufu プロダクトマネージャーが売ったことのないプロダクトは(爆発的には)売れない hatenablog://entry/6802418398325726328 2025-02-06T11:00:00+09:00 2025-02-06T11:00:00+09:00 こんにちは、エンジニアリンググループプロダクトマネージャーの阪口です。 私は医師向けのアンケート調査やそのデータ分析を手掛けるリサーチプロダクトチームに所属し、チームのビジョン「データとテクノロジーを活用し、医療に関する意思決定とアクションを、無駄なく、正しく、最速にする」を実現するため、日々奮闘しています。 この記事では、あるプロダクトのリニューアルにて自らがセールス活動に奔走することで得ることができた学びを共有したいと思います。 プロダクトリニューアルの背景 クライアントからの評価と課題 俺は最強のセールスになる! 1.顧客の解像度が上がり、売れるためのメッセージが見つかった 2.プロダク… <p>こんにちは、エンジニアリンググループプロダクトマネージャーの阪口です。<br/> 私は医師向けのアンケート調査やそのデータ分析を手掛けるリサーチプロダクトチームに所属し、チームのビジョン「データとテクノロジーを活用し、医療に関する意思決定とアクションを、無駄なく、正しく、最速にする」を実現するため、日々奮闘しています。<br/> この記事では、あるプロダクトのリニューアルにて自らがセールス活動に奔走することで得ることができた学びを共有したいと思います。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20250205/20250205205330.png" width="800" height="496" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <ul class="table-of-contents"> <li><a href="#プロダクトリニューアルの背景">プロダクトリニューアルの背景</a></li> <li><a href="#クライアントからの評価と課題">クライアントからの評価と課題</a></li> <li><a href="#俺は最強のセールスになる">俺は最強のセールスになる!</a><ul> <li><a href="#1顧客の解像度が上がり売れるためのメッセージが見つかった">1.顧客の解像度が上がり、売れるためのメッセージが見つかった</a></li> <li><a href="#2プロダクトチームへ希望を持ち込めた">2.プロダクトチームへ希望を持ち込めた</a></li> <li><a href="#3営業メンバーの本プロダクトに対するプライオリティがあがった">3.営業メンバーの本プロダクトに対するプライオリティがあがった</a></li> </ul> </li> <li><a href="#おわりに">おわりに</a></li> <li><a href="#We-are-hiring">We are hiring!!</a></li> </ul> <h2 id="プロダクトリニューアルの背景">プロダクトリニューアルの背景</h2> <p>今回手掛けたプロダクトのリニューアルはフレームワークと言語バージョンのアップデートとしてスタートしたものでしたが、ご利用いただいているクライアントにインタビューしたところ、「アンケート調査がとても早く実施できる」、「リードタイムが短い場合は、このプロダクト一択」とスピードについて、高い評価を得ていることが分かりました。<br/> そこで、私たちは「このプロダクトはまだまだ伸びしろがあるはずだ!」と考え、単なるシステム更改で終わらせるのではなく、「クライアントの調査を最速にするための新たな開発も行う」といった意思決定をしました。</p> <h2 id="クライアントからの評価と課題">クライアントからの評価と課題</h2> <p>サービスをローンチした後、すぐに利用いただいたクライアントにお話を伺いにいきました。クライアントからは新たに開発を行った機能に関して期待通りの高評価をいただくことができました。これなら、ビジネスの成果にもすぐに繋がるだろうとそう思っていました。</p> <p>しかし…そう簡単にはいきませんでした。</p> <p>前年と比較して売上の変化がなかなか現れなかったのです。 プロダクトは評価されているのに、なぜ売上が伸びないのか。<br/> 悩み苦しむ中で、次の2つの記事に出会い、大きな衝撃を受けました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fchikathreesix.com%2Fpost%2Fburning-needs" title="顧客のBurning needsを解決する | chikathreesix" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://chikathreesix.com/post/burning-needs">chikathreesix.com</a></cite></p> <blockquote><p>Founderが売れない製品は「売れない」<br/> 良いプロダクトを作れば勝手に売れる、いい曲作れば売れると言っている売れないバンドマンと同じだと思いませんか?早期にこの誤解を解くことから始めるべきです。こんなのあったら便利だなと作ったものが瞬く間に売れて成功する、ソーシャルネットワークの映画のような世界があなたに訪れることはまずあり得ません。</p></blockquote> <p><iframe id="talk_frame_698021" class="speakerdeck-iframe" src="//speakerdeck.com/player/d0d0705ccc44445096e2dd4b0fd8f044" width="710" height="399" style="aspect-ratio:710/399; border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen"></iframe> <cite class="hatena-citation"><a href="https://speakerdeck.com/tumada/serusuanimaruninarou-v2-sutatoatupufalseying-ye-huo-dong-1">speakerdeck.com</a></cite></p> <blockquote><p>セールスとは<br/> 商品を売って売上を上げること+顧客と話して学びを得ること<br/> セールスを誰かに任せてはいけない、折角のあなたの学びが減ってしまうから</p></blockquote> <p>私は、良いプロダクトは自然に売れるという幻想を抱いてしまっていたのかもしれません。プロダクトマネージャーとして、このプロダクトが売れるために何ができるか。セールスが突破口になると思い、覚悟を決めました。<br/> VPoPの山崎からも「プロダクトが実際に売れることが示せれば、周りがついてくる。<strong>プロダクトマネージャーが売ったことのないプロダクトは爆発的には売れない</strong>のではないか」と背中を押していただきました。</p> <h2 id="俺は最強のセールスになる">俺は最強のセールスになる!</h2> <p>顧客リストの整理から始め、担当営業と連携してアポイントを取得しました。またプロダクトデモだけでなく、営業の先輩方に商談の進め方や受け答えについてアドバイスをいただきました。<br/> 2件/日のペースで商談を行い、1日の少なくとも半分をセールス活動に費やしました。<br/> その結果、売上を実際に上げることに成功し、次の3つの成果を得ることができました。</p> <h5 id="1顧客の解像度が上がり売れるためのメッセージが見つかった">1.顧客の解像度が上がり、売れるためのメッセージが見つかった</h5> <p>具体的にどのようなメッセージが刺さるのか、見つけることができました。そのメッセージを提案資料やプロダクトにフィードバックすることで、よりサービスが磨かれていきました。</p> <h5 id="2プロダクトチームへ希望を持ち込めた">2.プロダクトチームへ希望を持ち込めた</h5> <p>作っているものが売れないとプロダクトマネージャーだけでなく、プロダクトチーム皆が不安になります。<br/> 実際にプロダクトが売れることでプロダクトの方向性が正しいこと、また着実にゴールに向かって進捗していることを実感できました。</p> <h5 id="3営業メンバーの本プロダクトに対するプライオリティがあがった">3.営業メンバーの本プロダクトに対するプライオリティがあがった</h5> <p>営業メンバーと成功体験を共有することで、協力関係が深まりました。営業メンバーからのプロダクトに対する質問や商談への同席の依頼が増えていきました。</p> <h2 id="おわりに">おわりに</h2> <p>いかがでしたでしょうか。 いままでインタビューを目的に顧客に会うことはやってきましたが、顧客に売り込むことに集中したのははじめてでした。実際に顧客がお金を払うまでの全てのコミュニケーションを体験することで、「クライアントの調査を最速にするための新たな開発も行う」とした意思決定が良い方向に向かっていることも強く実感できました。 <br><br> そしてその後、このプロダクトがどうなったかというと… <br/> <br> 前年比の売上を2倍にすることができました!<br/> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20250205/20250205205334.jpg" width="800" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="We-are-hiring">We are hiring!!</h2> <p>エムスリーエンジニアリンググループでは、一緒に働く仲間を募集しています! まずはカジュアル面談から、以下URLよりご応募をお待ちしています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/">jobs.m3.com</a></cite></p> <p>また、リサーチプロダクトチームに関する詳細は以下をご覧ください。</p> <p><iframe id="talk_frame_1125741" class="speakerdeck-iframe" src="//speakerdeck.com/player/77c248784c9c49b49ad993e931ff8c24" width="710" height="399" style="aspect-ratio:710/399; border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen"></iframe> <cite class="hatena-citation"><a href="https://speakerdeck.com/m3_engineering/introduction-of-m3-research-product-team">speakerdeck.com</a></cite></p> sho-sakaguchi 簡易生命表を読んでみる hatenablog://entry/6802418398323612034 2025-02-04T18:00:00+09:00 2025-02-04T18:00:03+09:00 AI・機械学習チームの髙橋です。今年の健康診断結果をみてからランニングを始めました。健康大事。 さて、今回は簡易生命表という厚生労働省が毎年発表している統計資料を読んでいきたいと思います。 簡易生命表は、「ある1年において各性別/年齢の人がその年に死亡する確率」を集計して統計処理をしたものです。 表を読むと例えば、「2023年において30歳男性がその1年に死ぬ確率は0.055%」といったことが分かります。 生命表は保険数理などでの基礎統計となっていて、みなさんにとって身近なところでいうと、平均寿命はこの生命表から計算される統計量になります。 <p>AI・機械学習チームの髙橋です。今年の健康診断結果をみてからランニングを始めました。健康大事。</p> <p>さて、今回は簡易生命表という厚生労働省が毎年発表している統計資料を読んでいきたいと思います。 簡易生命表は、「ある1年において各性別/年齢の人がその年に死亡する確率」を集計して統計処理をしたものです。 表を読むと例えば、「2023年において30歳男性がその1年に死ぬ確率は0.055%」といったことが分かります。</p> <p>生命表は保険数理などでの基礎統計となっていて、みなさんにとって身近なところでいうと、平均寿命はこの生命表から計算される統計量になります。</p> <h3 id="平均寿命の定義">平均寿命の定義</h3> <p><a href="https://www.mhlw.go.jp/toukei/saikin/hw/life/life23/dl/life23-02.pdf">厚生労働省の発表</a>によれば2023年時点での平均寿命は男性が81.09歳、女性が87.14歳です。 言葉を聞いた印象では、今生きている人たちの想定される寿命の予測を平均したもの、と思われるかもしれませんがこれは異なります。</p> <p>平均寿命は「(今年生まれた)0歳児の平均余命」を定義されています。</p> <p>言い換えれば平均余命とは、2023年の死亡状況が今後もずっと続くと仮定した場合に今年生まれた0歳児が何歳まで生きられるかの期待値を表しています。</p> <h3 id="生命表から平均寿命を求める">生命表から平均寿命を求める</h3> <p>では実際に現時点で手に入る最新2023年の簡易生命表から平均寿命求めてみましょう。 平均寿命を求めるには今年0歳で生まれた人が 「0 * 0歳で死亡する確率+ 1 * 1歳で死亡する確率...」と積分すればよさそうです。</p> <p>生命表は<a href="https://www.mhlw.go.jp/toukei/saikin/hw/life/life23/index.html">&#x4EE4;&#x548C;&#xFF15;&#x5E74;&#x7C21;&#x6613;&#x751F;&#x547D;&#x8868;&#x306E;&#x6982;&#x6CC1;&#xFF5C;&#x539A;&#x751F;&#x52B4;&#x50CD;&#x7701;</a>の「令和5年簡易生命表のダウンロード」からExcelファイルで取得できます。 綺麗なテーブルではなくそのままだと扱いにくいので今回は整形してcsvファイルに変換しました。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd life_table = pd.read_csv(<span class="synConstant">'jp_male_2023_life_table.csv'</span>) POPULATION = <span class="synConstant">100000</span> MAX_AGE = <span class="synConstant">105</span> average_lifespan = <span class="synConstant">0.0</span> <span class="synStatement">for</span> age <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">0</span>, MAX_AGE+<span class="synConstant">1</span>): death_count = life_table.query(<span class="synConstant">'Age == @age'</span>)[<span class="synConstant">'Death'</span>].iloc[<span class="synConstant">0</span>] average_lifespan += (age + <span class="synConstant">0.5</span>) * death_count <span class="synComment"># 連続性補正のため0.5を加算</span> <span class="synIdentifier">print</span>(f<span class="synConstant">&quot;average lifespan: {average_lifespan / POPULATION}&quot;</span>) <span class="synComment"># average lifespan: 81.09207</span> </pre> <p>結果は81.09歳となり、厚労省が発表している平均寿命と一致しました。</p> <p>一点注意としては積分する際に年齢に0.5を足す「連続値補正」を行っている点で、これは0歳で亡くなる、というのは生まれた日から1歳の誕生日前日までの期間なので0.5歳として計算したほうが正確になるためです。</p> <h3 id="簡易生命表から分かる少し意外なこと">簡易生命表から分かる少し意外なこと</h3> <h4 id="現在の平均寿命より長生きする人が多い">現在の平均寿命より長生きする人が多い</h4> <p>所得や資産の場合だと少数の金持ちが大きく平均を引き上げて算術平均と中央値が大きくズレることが知られていますが、似たようなことが寿命でも起きます。 少数の人が不幸にして若い年齢で亡くなってしまうことで算術平均が下がりますが、多数の人は高齢まで生きるため、寿命中位数(生命表から求められる今年生まれた人の寿命の中央値)は平均寿命より長くなります。</p> <p>実際2023年の寿命中位数は男性が83.99歳、女性が90.02歳と平均寿命より3年近く長いです。つまり、日本人の半数以上の人は<strong>平均寿命+3年</strong>程度は生きることになります。</p> <h4 id="期待寿命は年月が経つと伸び続ける">期待寿命は年月が経つと伸び続ける</h4> <p>私(1993年生の男性)を例に、これまでにどのように期待寿命(現在の年齢+平均余命)がどのように変わってきたのかみてみると、次のようになりました。</p> <p><figure class="figure-image figure-image-fotolife" title="生命表から計算される筆者の期待寿命(その時の年齢+平均余命)"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20250204/20250204180008.png" width="800" height="629" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>生命表から計算される筆者の期待寿命(その時の年齢+平均余命)</figcaption></figure></p> <p>0~25歳の間では5年経過するごとに大体1歳ぐらい私の期待寿命は伸びてきています。<a href="#f-4f22fad0" id="fn-4f22fad0" name="fn-4f22fad0" title="30歳のときは2023年で、新型コロナの影響で全体の平均寿命が1歳近く短くなったため伸びていない。">*1</a></p> <p>これは次の2つの効果によって生じています。</p> <ol> <li><p>すでにX年生きてきた分(それまでに死ななかった分だけ)その年の平均寿命:=0歳児の余命よりは期待寿命が長くなるため</p></li> <li><p>平均余命は集計年の死亡確率をもとに計算するが、実際には年月が経過するにつれ死亡確率が低下し平均余命が伸びるため</p></li> </ol> <p>1つ目については具体的に見てみると、0歳での死亡率が若年のなかでは高いため、0歳を乗り越える1歳になるとその時点で男性の期待寿命は81.09歳から81.24歳に+0.15歳上がります。 40歳まで生存していると82.06歳、年金受給開始年齢の65歳だと84.97歳まで上昇します。</p> <p>2つ目については、日本の平均寿命は戦後、新型コロナの流行を除けば一貫して伸びています。体感的にはそこまで違いがない最近の2010年→2020年でも男性は2年近く、女性も1.4年ほど伸びています。</p> <p><figure class="figure-image figure-image-fotolife" title="平均寿命の推移"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20250204/20250204180011.png" width="800" height="622" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>平均寿命の推移</figcaption></figure></p> <h2 id="まとめ">まとめ</h2> <p>今回は簡易生命表を読み解いてみました。統計資料を少し深堀りすると直感とは違う実相が見えてくる、統計やデータサイエンスの面白さの一端が表現できていれば幸いです。</p> <h2 id="we-are-hiring">we are hiring!</h2> <p>AI・機械学習チームは統計学やデータサイエンスに関心ある方を求めています! 次のリンクからご応募お待ちしています!</p> <h3 id="エンジニア採用ページはこちら">エンジニア採用ページはこちら</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/">jobs.m3.com</a></cite></p> <h3 id="カジュアル面談もお気軽にどうぞ">カジュアル面談もお気軽にどうぞ</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2Fcasual-session%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/casual-session/">jobs.m3.com</a></cite></p> <h3 id="インターンも常時募集しています">インターンも常時募集しています</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fopen.talentio.com%2Fr%2F1%2Fc%2Fm3%2Fhomes%2F4354" title="【新卒】エンジニアリンググループ 募集一覧 / エムスリー株式会社" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://open.talentio.com/r/1/c/m3/homes/4354">open.talentio.com</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-4f22fad0" id="f-4f22fad0" name="f-4f22fad0" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">30歳のときは2023年で、新型コロナの影響で全体の平均寿命が1歳近く短くなったため伸びていない。</span></p> </div> eufo プログラミングクイズ この演算結果が得られる元のコードは? hatenablog://entry/6802418398324751429 2025-02-03T11:00:00+09:00 2025-02-03T11:00:03+09:00 こんにちは。エムスリーエンジニアリンググループの藤原です。 今回の記事ではパズルのようなプログラミングクイズを出題してみます。 自分で考えたい方は問題部分から記事を読み進めずに考えてみてください。問題の後にヒント、答えと続いています。 問題 では、問題です。 巳年(Python) '25年 なクイズということで作成してみました。 (元日あたりに作ったので [1:-1] 部分も実は"かかって"います!) 難読クイズとは少し違った趣向のクイズで、エンジニアリングとしては何の役にも立たないかもしれないですが、たまにはこういうのも面白いのではないかと思います。 <p>こんにちは。エムスリーエンジニアリンググループの藤原です。</p> <p>今回の記事ではパズルのようなプログラミングクイズを出題してみます。<br/> 自分で考えたい方は問題部分から記事を読み進めずに考えてみてください。問題の後にヒント、答えと続いています。</p> <h2 id="問題">問題</h2> <p>では、問題です。</p> <table> <thead> <tr> </tr> </thead> <tbody> <tr> <td><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20250203/20250203110004.png" width="800" height="601" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></td> </tr> </tbody> </table> <p>巳年(Python) '25年 なクイズということで作成してみました。 (元日あたりに作ったので <code>[1:-1]</code> 部分も実は"かかって"います!)<br/> 難読クイズとは少し違った趣向のクイズで、エンジニアリングとしては何の役にも立たないかもしれないですが、たまにはこういうのも面白いのではないかと思います。</p> <h2 id="ヒント">ヒント</h2> <p><details><summary>自力で解きたい人のためのヒント</summary> <code>"*"</code> で文字列となっている箇所以外の伏字部分は全て<a href="https://docs.python.org/ja/3.13/library/functions.html">組み込み関数</a>です。 </details></p> <h2 id="解答">解答</h2> <p><details><summary>ここから解答です。</summary>想定解のコードはこちら。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synIdentifier">sorted</span>(<span class="synIdentifier">oct</span>(<span class="synIdentifier">ord</span>(<span class="synConstant">&quot;*&quot;</span>)))[<span class="synConstant">1</span>:-<span class="synConstant">1</span>] </pre> <p>そして、「 <code>_</code> 」部分をつなげてできる言葉は「 <code>doctor</code> 」でした!</p> <h3 id="考え方">考え方</h3> <p>解答に至るための考え方としては、Pythonの組み込み関数を「型」や「文字数」で絞って考えるのが一番論理的な解き方かと思われます。</p> <p>以下では考え方の一例を示します。<br/> 問題に示されている長さ1の文字列から始まる状態の遷移を次の4つに分けて推理を進めることとしましょう。</p> <ol> <li>3文字の関数①を適用して状態①に</li> <li>3文字の関数②を適用して状態②に</li> <li>6文字の関数③を適用して状態③に</li> <li>スライス <code>[1:-1]</code> を適用して <code>['2', '5']</code> に</li> </ol> <p>まず、状態③をスライスすることで <code>['2', '5']</code> が得られることから、状態③は長さ4の <code>List</code> であることがわかります。<br/> また、関数③は6文字の組み込み関数です。 候補は以下の通り6つ。</p> <pre class="code" data-lang="" data-unlink>&gt;&gt;&gt; import builtins &gt;&gt;&gt; [name for name in dir(builtins) if callable(getattr(builtins, name)) and len(name) == 6] [&#39;divmod&#39;, &#39;filter&#39;, &#39;format&#39;, &#39;locals&#39;, &#39;object&#39;, &#39;sorted&#39;]</pre> <p>これらのうち <code>List</code> を返すのは <code>sorted</code> のみのため、関数③は <code>sorted</code> に決まり、状態②は長さ4の <code>Iterable[str]</code> であることもわかります。</p> <p>ここで3文字の組み込み関数も列挙しておきましょう。</p> <pre class="code" data-lang="" data-unlink>&gt;&gt;&gt; [name for name in dir(builtins) if callable(getattr(builtins, name)) and len(name) == 3] [&#39;abs&#39;, &#39;all&#39;, &#39;any&#39;, &#39;bin&#39;, &#39;chr&#39;, &#39;dir&#39;, &#39;hex&#39;, &#39;int&#39;, &#39;len&#39;, &#39;map&#39;, &#39;max&#39;, &#39;min&#39;, &#39;oct&#39;, &#39;ord&#39;, &#39;pow&#39;, &#39;set&#39;, &#39;str&#39;, &#39;sum&#39;, &#39;zip&#39;]</pre> <p>関数②の候補は、長さ4の <code>Iterble[str]</code> を返し得るものなので <code>bin</code>, <code>chr</code>, <code>hex</code>, <code>oct</code>, <code>set</code>, <code>str</code> です。<br/> また、関数①の候補は、長さ1の文字列を引数に取り得るものなので <code>all</code>, <code>any</code>, <code>dir</code>, <code>int</code>, <code>len</code>, <code>ord</code>, <code>set</code>, <code>str</code> です。</p> <p>ここからは関数①と関数②の候補を組み合わせて考えていきます。<br/> 関数①の候補が返す型は <code>bool</code>, <code>List[str]</code>, <code>int</code>, <code>Set[str]</code>, <code>str</code> のいずれかですが、関数②の候補いずれかを適用して <code>'2'</code> と <code>'5'</code> が要素に含まれる長さ4の <code>Iterable[str]</code> を得るためには <code>int</code> を返すものしか条件を満たせません。<br/> <code>int</code> を返す関数①の候補は3つありますが、長さ1の文字列を引数とした場合に <code>int</code> と <code>len</code> が返す値は高々一桁の数値となり、これらも関数②を適用して <code>'2'</code> と <code>'5'</code> が要素に含まれる長さ4の <code>Iterable[str]</code> を得るという条件を満たせないため、関数①は <code>ord</code> であることがわかります。</p> <p>関数②の決定には、問題の「 <code>_</code> 」部分を繋ぐと言葉になるという前提を使うことにしましょう。候補の <code>bin</code>, <code>chr</code>, <code>hex</code>, <code>oct</code> のうち、<code>d***or</code> で言葉ができるもを探すと <code>oct</code> の <code>doctor</code> が見つかります。関数②は <code>oct</code> で良さそうです。</p> <p>最後に、出発点となっている長さ1の文字列を探します。 <code>ord</code> と <code>oct</code> を適用して <code>"0o25"</code> または <code>"0o52"</code> になるものが条件を満たしますが、長さ1の文字列となるのは <code>"0o52"</code> の場合で、その文字列は伏字の <code>"*"</code> そのままであることがわかります。</p> <pre class="code" data-lang="" data-unlink>&gt;&gt;&gt; chr(int(&#34;25&#34;, 8)) &#39;\x15&#39; &gt;&gt;&gt; chr(int(&#34;52&#34;, 8)) &#39;*&#39;</pre> <p>全ての謎が解けました。 <code>"*"</code> から始まる状態の遷移を順番に追ってみましょう。</p> <ol> <li>3文字の関数①を適用<br/> <code>ord('*')</code> -> <code>42</code></li> <li>3文字の関数②を適用<br/> <code>oct(42)</code> -> <code>'0o52'</code></li> <li>6文字の関数③を適用<br/> <code>sorted('0o52')</code> -> <code>['0', '2', '5', 'o']</code></li> <li>スライスを適用<br/> <code>['0', '2', '5', 'o'][1:-1]</code> -> <code>['2', '5']</code></li> </ol> <p>解説は以上です。<br/> ここでは論理的な考え方の例を示してみましたが、もしかするとひらめきで答えに辿り着いてしまった人もいるかもしれません。</p> <p></details></p> <h2 id="別解">別解</h2> <p><details><summary>実はこの問題には別解があります。 </summary> 社内Slackでも出題してみたところ、なんと3パターンの別解が発見されました。 「 <code>_</code> 」部分を繋いでも言葉にならないものではありますが、演算結果が <code>['2', '5']</code> に合致するものたちです。</p> <h3 id="別解1-oct-を-str-に">別解1. <code>oct</code> を <code>str</code> に</h3> <pre class="code lang-python" data-lang="python" data-unlink><span class="synIdentifier">sorted</span>(<span class="synIdentifier">str</span>(<span class="synIdentifier">ord</span>(<span class="synConstant">&quot;┰&quot;</span>)))[<span class="synConstant">1</span>:-<span class="synConstant">1</span>] </pre> <p><code>oct</code> を <code>str</code> にしても型的には問題なく、演算結果が条件を満たす値を探すと <code>ord("┰") = 9520</code> が見つかるということのようです。</p> <h3 id="別解2-oct-を-hex-に">別解2. <code>oct</code> を <code>hex</code> に</h3> <pre class="code lang-python" data-lang="python" data-unlink><span class="synIdentifier">sorted</span>(<span class="synIdentifier">hex</span>(<span class="synIdentifier">ord</span>(<span class="synConstant">&quot;%&quot;</span>)))[<span class="synConstant">1</span>:-<span class="synConstant">1</span>] </pre> <p>別解1と同様、 <code>oct</code> を <code>hex</code> にした場合に条件を満たす <code>ord("%") = 37</code> (37は16進数で25)が見つかります。</p> <h3 id="別解3--を文字列に含ませる">別解3. <code>(</code> を文字列に含ませる</h3> <pre class="code lang-python" data-lang="python" data-unlink><span class="synIdentifier">sorted</span>(<span class="synConstant">&quot;25(&quot;</span>+((<span class="synConstant">&quot;a&quot;</span>)))[<span class="synConstant">1</span>:-<span class="synConstant">1</span>] </pre> <p>コード中の <code>(</code> を文字列に含めてしまうパターンも発見されました。このパターンだと <code>25a</code> の3文字は位置の入れ替え可能という自由度もあります。</p> <p></details></p> <h2 id="We-are-hiring-">We are hiring !!</h2> <p>いかがだったでしょうか。ここまで読んでいただいたあなたは、問題を解く喜びを知っている方でしょう。 エムスリーでは医療業界の複雑な問題に共に挑戦するエンジニアを募集しています。<br/> 下記のリンクからご応募お待ちしています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/">jobs.m3.com</a></cite></p> m-fujiwara-m3 poetryのバージョンを2.0.0に上げたらinstallできなくなった hatenablog://entry/6802418398318475750 2025-01-09T11:00:00+09:00 2025-01-09T11:00:00+09:00 AI・機械学習チームの池嶋(@mski_iksm)です。AI・機械学習チームではPythonで開発しているプロダクトのパッケージ管理にpoetryを使用していますが、年明け早々こんなメッセージが出てpoetry installができなくなるトラブルが頻発しました。 Warning: The current project could not be installed: No file/folder found for package XXXXXXXXXX 2025年1月5日にリリースされたpoetry 2.0.0では、自パッケージのインストールに関する挙動が変更されました。その影響で、poet… <p>AI・機械学習チームの池嶋(<a href="https://x.com/mski_iksm">@mski_iksm</a>)です。AI・機械学習チームではPythonで開発しているプロダクトのパッケージ管理にpoetryを使用していますが、年明け早々こんなメッセージが出て<code>poetry install</code>ができなくなるトラブルが頻発しました。</p> <blockquote><p>Warning: The current project could not be installed: No file/folder found for package XXXXXXXXXX</p></blockquote> <p>2025年1月5日にリリースされた<a href="https://github.com/python-poetry/poetry/releases/tag/2.0.0">poetry 2.0.0</a>では、自パッケージのインストールに関する挙動が変更されました。その影響で、poetry installが失敗するケースが発生しています。 この記事では、この問題に対する対策を解説します。</p> <h2 id="tldr">tl;dr</h2> <p><code>pyproject.toml</code>の<code>[tool.poetry]</code>セクションの<code>package-mode</code>を<code>false</code>にする</p> <pre class="code" data-lang="" data-unlink># pyproject.toml [tool.poetry] package-mode = false</pre> <h2 id="詳細">詳細</h2> <p>poetryではpackage modeとnon-package modeの2種類があります。 package modeは自分自身をパッケージとして環境にインストールしてから使うモードで、主にパッケージ開発に使われます。 non-package modeは逆に自分自身をパッケージとして環境にインストールせずに使うモードで、依存するサードパーティのパッケージ管理にpoetryを使用する場合に選択されます<a href="#f-f2de64b1" id="fn-f2de64b1" name="fn-f2de64b1" title="non-package modeが導入されたのは2024年2月の1.8.0からと、意外と新しめの機能です。https://github.com/python-poetry/poetry/releases/tag/1.8.0">*1</a>。 デフォルトではpackage modeが選択されています。</p> <pre class="code bash" data-lang="bash" data-unlink>. ├── hoge │ ├── __init__.py │ └── code.py ├── poetry.lock └── pyproject.toml</pre> <p>例えば上記のようなディレクトリ構造の場合、<code>hoge</code>自身を環境にインストールするかどうかをモードで切り替えます。</p> <p>package modeの時、poetryは<code>pyproject.toml</code>に記述する<code>[project]</code>セクションの<code>name</code>で自分自身のパッケージ名を判断しています。<a href="#f-be02239b" id="fn-be02239b" name="fn-be02239b" title="[project]セクションのnameがない場合、[tool.poetry]セクションのnameを参照しますが、後者の設定は非推奨になりました。">*2</a></p> <p>ここで指定した名前のディレクトリが見当たらない場合の挙動がバージョン2.0.0から変更されました。</p> <h3 id="200より前">2.0.0より前</h3> <p>ディレクトリに自パッケージがない場合、warningで警告しつつもスルー。自パッケージは環境へのインストールは実施されません。</p> <h3 id="200以降">2.0.0以降</h3> <p>ディレクトリに自分パッケージがない場合、<code>poetry install</code>自体が失敗する。(警告メッセージはwarning)</p> <p>自分自身をインストールするpackage modeを指定しながらそのパッケージが見当たらない、という現象は何か間違っている可能性が高いので、ここではインストールが失敗するのは自然に思われます。</p> <h2 id="自パッケージを環境にインストールしなくても使える場合ってどういうこと">自パッケージを環境にインストールしなくても使える場合ってどういうこと?</h2> <h3 id="1-単一スクリプトなどそもそもimportをしない場合">1. 単一スクリプトなど、そもそもimportをしない場合</h3> <p>poetryを依存パッケージ管理にだけ使っている場合です。こういう場合はpackageを作っていないので、no-package modeを選択するのが良さそうです。</p> <h3 id="2-自パッケージがモジュール検索PATHにある場合">2. 自パッケージがモジュール検索PATHにある場合</h3> <p>Pythonでは、モジュール検索PATHが通るディレクトリのコードをimportできます。 モジュール検索PATHは<code>sys.path</code>で確認ができる値で、デフォルトでは依存パッケージがインストールされる<code>site-packages</code>ディレクトリや、実行するスクリプトのディレクトリが含まれています。 これまで環境に自パッケージをインストールしなくても参照できていたのは、自パッケージがモジュール検索PATHの通った場所に配置されているためです。</p> <pre class="code bash" data-lang="bash" data-unlink>. ├── hoge │ ├── __init__.py │ └── code.py ├── main.py ├── poetry.lock ├── pyproject.toml └── script └── main.py</pre> <p>例えば、上記のようなディレクトリ構造のときに、<code>main.py</code>から<code>import hoge</code>は可能です。 これは<code>main.py</code>が<code>hoge</code>と同じディレクトリにあるためです。 モジュール検索PATHには、実行するスクリプトのディレクトリが含まれているためimport可能になっています。</p> <p>一方、<code>script/main.py</code>から<code>import hoge</code>はできません。 <code>python script/main.py</code>を実行すると、モジュール検索PATHには<code>.</code>ではなく<code>script</code>が追加され、<code>hoge</code>が見つからないためです。</p> <p>なお、「自パッケージがモジュール検索PATHにある」場合、<code>pyproject.toml</code>の<code>[project]</code>セクションの<code>name</code>に自パッケージの名前を正しく指定してあげることで、今回の問題は回避できます。</p> <h2 id="対策">対策</h2> <blockquote><p>Warning: The current project could not be installed: No file/folder found for package XXXXXXXXXX</p></blockquote> <p>この問題の対策の方針は大きく2種類あります。</p> <p>1つは、これまで通り自パッケージはインストールしないようにする方針です。<code>pyproject.toml</code>の<code>[tool.poetry]</code>セクションの<code>package-mode</code>を<code>false</code>にすることで、no-package modeを明示的に使用するようにします。</p> <pre class="code" data-lang="" data-unlink># pyproject.toml [tool.poetry] package-mode = false</pre> <p>なお、<code>poetry install --no-root</code>をする方法もありますが、これは「package modeのまま<a href="#f-32429f9c" id="fn-32429f9c" name="fn-32429f9c" title="package modeではあるので、pyproject.tomlにおいて、[project.name] or [tool.poetry.name]と[project.version] or [tool.poetry.version]の指定を求められます。このうち[tool.poetry.name]は非推奨になっているので、将来的に[project.name]への移行が必要になるかもしれません。">*3</a>自パッケージのインストールだけをスキップする」というもので、modeの指定とやっていることが捻れたままのため、no-package modeを明示的に選択する前者がいいかと考えています。</p> <p>もう1つは、自パッケージをインストールするようにする方針です。 これは、<code>[project]</code>セクションの<code>name</code>に自パッケージ名を指定することで実現可能です。</p> <p>急ぎの場合は、これまで通りの挙動をするためにno-package modeに変更するので良さそうですが、将来的にscriptディレクトリなどから自パッケージをimportしたいなど柔軟に対応したい場合は、後者のようにpackageとして環境にインストールを検討してもいいかもしれません。</p> <h2 id="他のpoetry-200での変更点">他のpoetry 2.0.0での変更点</h2> <h3 id="poetry-shellがpluginになった"><code>poetry shell</code>がpluginになった</h3> <p>poetryの環境内に入るためのコマンドであった<code>poetry shell</code>が、plugin経由で入れる機能になりました。今後は<code>eval $(poetry env activate)</code>を使うことが推奨されています。</p> <h3 id="poetry-lockのデフォルトが--no-updateになった"><code>poetry lock</code>のデフォルトが<code>--no-update</code>になった</h3> <p><code>poetry lock</code>の際に、アップデートが必須ではないパッケージは現状のままにする<code>--no-update</code>オプションがデフォルトになりました。</p> <h3 id="pyprojecttomlのprojectセクションのサポート"><code>pyproject.toml</code>の<code>[project]</code>セクションのサポート</h3> <p>本記事でも度々設定している、<code>pyproject.toml</code>の<code>[project]</code>セクションがサポートされたのは2.0.0からです。 これまで<code>[tool.poetry]</code>セクションに記述していた設定もいくつかは非推奨になり、<code>[project]</code>セクションへの記述に移行しているので注意が必要です。</p> <p>メジャーアップデートらしく、他にもたくさんの変更がありますので、<a href="https://github.com/python-poetry/poetry/releases/tag/2.0.0">公式解説</a>もご参照ください。</p> <h2 id="we-are-hiring">we are hiring!</h2> <p>AI・機械学習チームでは、ライブラリのメジャーアップデートにも怯まずに突っ込んでwhyを調査するエンジニアを募集しています。 下記のリンクからご応募お待ちしています!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/">jobs.m3.com</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-f2de64b1" id="f-f2de64b1" name="f-f2de64b1" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">non-package modeが導入されたのは2024年2月の1.8.0からと、意外と新しめの機能です。<a href="https://github.com/python-poetry/poetry/releases/tag/1.8.0">https://github.com/python-poetry/poetry/releases/tag/1.8.0</a></span></p> <p class="footnote"><a href="#fn-be02239b" id="f-be02239b" name="f-be02239b" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">[project]セクションのnameがない場合、[tool.poetry]セクションのnameを参照しますが、後者の設定は非推奨になりました。</span></p> <p class="footnote"><a href="#fn-32429f9c" id="f-32429f9c" name="f-32429f9c" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">package modeではあるので、pyproject.tomlにおいて、[project.name] or [tool.poetry.name]と[project.version] or [tool.poetry.version]の指定を求められます。このうち[tool.poetry.name]は非推奨になっているので、将来的に[project.name]への移行が必要になるかもしれません。</span></p> </div> mski_iksm 2024年の技術動画からオススメをまとめるぜ hatenablog://entry/6802418398316201658 2024-12-31T15:31:38+09:00 2024-12-31T15:31:38+09:00 年末にこんにちは、こんばんは。VPoEの河合(@vaaaaanquish)です。 声優の小林裕介さん、内山夕実さんが、ご結婚されたとの事で、最近リゼロ最新話まで追いついた私も喜びを隠せません。嬉しい! さて、本記事では2024年も終わるこの日に年末年始を楽しく過ごせる技術LT動画をいくつか紹介して、皆様によい年末を提供出来ればと思い筆を取っています。 年の暮まで技術技術でやっていきましょう! エムスリーテックトークとは TEX嫌い大学生の闘い Firebase De Akka Actor版人狼アプリリメイク フラクタル図形をうろ覚えの式から描いてみた PlaywrightでVRT環境を整備… <p>年末にこんにちは、こんばんは。VPoEの河合(@vaaaaanquish)です。 声優の小林裕介さん、内山夕実さんが、ご結婚されたとの事で、最近リゼロ最新話まで追いついた私も喜びを隠せません。嬉しい!</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241231/20241231153140.png" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>さて、本記事では2024年も終わるこの日に年末年始を楽しく過ごせる技術LT動画をいくつか紹介して、皆様によい年末を提供出来ればと思い筆を取っています。</p> <p>年の暮まで技術技術でやっていきましょう!</p> <ul class="table-of-contents"> <li><a href="#エムスリーテックトークとは">エムスリーテックトークとは</a></li> <li><a href="#TEX嫌い大学生の闘い">TEX嫌い大学生の闘い</a></li> <li><a href="#Firebase-De-Akka-Actor版人狼アプリリメイク">Firebase De Akka Actor版人狼アプリリメイク</a></li> <li><a href="#フラクタル図形をうろ覚えの式から描いてみた">フラクタル図形をうろ覚えの式から描いてみた</a></li> <li><a href="#PlaywrightでVRT環境を整備してハッピーになったその先の世界">PlaywrightでVRT環境を整備してハッピーになった、その先の世界</a></li> <li><a href="#Water-Sort-Puzzleを解いてみる">Water Sort Puzzleを解いてみる</a></li> <li><a href="#おわりに">おわりに</a><ul> <li><a href="#We-are-hiring-">We are hiring !!</a><ul> <li><a href="#エンジニア採用ページはこちら">エンジニア採用ページはこちら</a></li> <li><a href="#カジュアル面談もお気軽にどうぞ">カジュアル面談もお気軽にどうぞ</a></li> <li><a href="#インターンも常時募集しています">インターンも常時募集しています</a></li> </ul> </li> </ul> </li> </ul> <p>  </p> <h1 id="エムスリーテックトークとは">エムスリーテックトークとは</h1> <p>M3 Tech Talkは、隔週で金曜日に開催している社内の技術勉強会です。</p> <p>2013年から11年継続、260回以上開催。毎回3~4名の方に15分ほどの話をしてもらっていて、エンジニア100名のうち60~70人が毎回ZoomやSlackでワイワイ実況している、エムスリーエンジニアリンググループの恒例イベントです。 年に数回のオフライン開催も実施しており、12月時点で2月末まで登壇希望者が埋まっている超人気コンテンツにもなっています。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.m3tech.blog%2Fentry%2F2024%2F12%2F21%2F120000" title="「ギーク」が集う学び舎 - 11年以上続くM3 Tech Talkを支える情熱に迫る - エムスリーテックブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.m3tech.blog/entry/2024/12/21/120000">www.m3tech.blog</a></cite></p> <p>盛り上がりの秘密は、技術の深さや広さ、ニッチさ、そして面白い使い方、プロダクトへの応用と幅広く聞ける事にあります。</p> <p>カジュアル面談等でもよく「チーム間の技術共有はどうしてるんですか?」「コミュニケーションはどんな感じですか?」などの質問を受けるのですが、テックトークで自身のチーム活動や個人活動をアピールすることで「技術で繋がっている」のがエムスリーの面白いカルチャーの1つだと思って以下の記事を紹介しています。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.m3tech.blog%2Fentry%2Ftechtalk-offline-2023" title="230回続く社内LT大会の忘年会が盛り上がった件について - エムスリーテックブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.m3tech.blog/entry/techtalk-offline-2023">www.m3tech.blog</a></cite></p> <p>テックトークの登壇内容は、<a href="https://www.youtube.com/@m3techchannel160">エムスリー公式テックチャンネル</a>で公開しています。 もちろん、社内でしか共有できないようなプロダクトに関連した情報やエッジが効き過ぎた登壇については公開していないのですが、エムスリーの技術カルチャーを知って頂くために一部だけオープンしています。</p> <p>ここでは、さらにその一部、2024年に公開された動画の中から、私がオススメするおもしろ技術動画を紹介していこうと思います。</p> <p>  </p> <h1 id="TEX嫌い大学生の闘い">TEX嫌い大学生の闘い</h1> <p>皆様、学生時代にTeXを教わったのは記憶にありますでしょうか。</p> <p>学生時代のTeXのbuild環境を作る作業を思い出しただけで、私も頭を抱えてしまいます(近年はOverleafやDocker Imageが充実していたりRe:VIEW等もあるので良い時代になっていますね)。</p> <p><iframe width="560" height="315" src="https://www.youtube.com/embed/JJ8AoVND6AM?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen title="TEX嫌い大学生の闘い 【M3 Tech Talk 第233回】"></iframe><cite class="hatena-citation"><a href="https://youtu.be/JJ8AoVND6AM?si=uN1FD0qttB1BouwV">youtu.be</a></cite></p> <p>こちらの動画では、Markdown PreviewやPugを使って、いかにTeXを避けるか大健闘した学生時代の思い出が語られています。 正気に戻るオチまで最高の動画です。</p> <p>  </p> <h1 id="Firebase-De-Akka-Actor版人狼アプリリメイク">Firebase De Akka Actor版人狼アプリリメイク</h1> <p>皆様、Web上で人狼をプレイできるサービス『<a href="https://www.docodemo-jinrou.com">どこでも人狼</a>』はご存知でしょうか。</p> <p>検索するとVTuberが使っている様子が出てくるようなサービスですが、実はエムスリーのエンジニアが趣味で開発しています。</p> <p><iframe width="560" height="315" src="https://www.youtube.com/embed/2PWvfpcueIw?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen title="2024 01 26 Firebase De Akka Actor版人狼アプリリメイク 【M3 Tech Talk 第237回】 v01"></iframe><cite class="hatena-citation"><a href="https://youtu.be/2PWvfpcueIw?si=69tbFz3p1Bfn4EQw">youtu.be</a></cite></p> <p>こちらの動画は、アクセス増加と円安でGKE運用に苦しめられたエンジニアによるFirebase移行の赤裸々トークになっています。 最後に出てくる心に優しいグラフまで含めてオススメの動画です。</p> <p>  </p> <h1 id="フラクタル図形をうろ覚えの式から描いてみた">フラクタル図形をうろ覚えの式から描いてみた</h1> <p>皆様、フラクタル図形を書きたいなと思ったあの頃の気持ち、覚えていますでしょうか。</p> <p>フラクタル図形とは、フランスの数学者ブノワ・マンデルブロが提唱した幾何学の概念で、図形の一部を拡大しても全体と似た形になる「自己相似性」を示す図形です。</p> <p><iframe width="560" height="315" src="https://www.youtube.com/embed/ejMyrqkBzcs?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen title="フラクタル図形をうろ覚えの式から描いてみた 【M3 Tech Talk 第237回】"></iframe><cite class="hatena-citation"><a href="https://youtu.be/ejMyrqkBzcs?si=f_xNC9bCDTsT1wSC">youtu.be</a></cite></p> <p>こちらの動画では、SPG(サイトプロモーショングループ)で働く青山さんが、フラクタル図形を構築する式の記憶を辿りながら開発が進みます。青山さんの職種はエンジニアではないのですが、JavaScriptでデモが動く様子まであるという、かなり面白いコンテンツになっています。</p> <p><iframe width="560" height="315" src="https://www.youtube.com/embed/sK7YhoXhbGg?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen title="フラクタル図形をうねうね動かしたらケラリーノ・サンドロヴィッチ氏を思い出した話 【M3 Tech Talk 第238回】"></iframe><cite class="hatena-citation"><a href="https://youtu.be/sK7YhoXhbGg?si=yadj1F1OvYiDuZbv">youtu.be</a></cite></p> <p>2つ目のうねうね動かす動画には、1つ目の動画で撒いた伏線が回収されるオチがあり、続けて見る価値がある技術動画になっています。</p> <p>  </p> <h1 id="PlaywrightでVRT環境を整備してハッピーになったその先の世界">PlaywrightでVRT環境を整備してハッピーになった、その先の世界</h1> <p>皆様も自動テスト、やっていますかね?</p> <p>エムスリーでもPlaywrightやSelenium、TestRail、mabl…と幅広いツールでVRTが行われています。QAチームメンバーだけでなく、エンジニア全体で品質を担保すべく、様々な取り組みを行っている最中です。</p> <p><iframe width="560" height="315" src="https://www.youtube.com/embed/JtLWa8iT7jE?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen title="PlaywrightでVRT環境を整備してハッピーになった、その先の世界 【M3 Tech Talk 第239回】"></iframe><cite class="hatena-citation"><a href="https://youtu.be/JtLWa8iT7jE?si=FHKR7K4YOXiXtOFJ">youtu.be</a></cite></p> <p>こちらの動画では、そんなVRT構築中に起こった謎のブラウザ挙動に迫り、解決に至った背景と詳しい調査を具体的に紹介しています。 フロントエンドの不思議な挙動が好きなエンジニアにオススメの一本です。</p> <p>  </p> <h1 id="Water-Sort-Puzzleを解いてみる">Water Sort Puzzleを解いてみる</h1> <p>皆様、スマホ操作中に出てくるゲーム広告はお好きでしょうか。</p> <p>私はお宝が溶岩に燃えるゲーム(?)の広告をよく見ます。</p> <p><iframe width="560" height="315" src="https://www.youtube.com/embed/xMIdMFvAzPM?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen title="Water Sort Puzzleを解いてみる【M3 Tech Talk 第246回】"></iframe><cite class="hatena-citation"><a href="https://youtu.be/xMIdMFvAzPM?si=TFGZCH_eH9iZLiSN">youtu.be</a></cite></p> <p>こちらの動画は、そんなゲーム広告をきっかけにパズルSolverを開発する、いかにもギークなエンジニアが考えそうな内容になっています。 Solver開発と実際にプレイするので、どちらの方が時間が溶けるのかよくわからない面白動画になっていてオススメです。</p> <p>  </p> <h1 id="おわりに">おわりに</h1> <p>いかがでしょうか。</p> <p>本記事では、エムスリーのギークな動画の中から一部を紹介、見どころポイントをお伝えしました。 年末は、エムスリーテックトークの動画を見ながら、一緒に除夜の鐘を鳴らしましょう!</p> <p>私は、来年に向け、動画レビューを進めておきますね(笑)</p> <h2 id="We-are-hiring-">We are hiring !!</h2> <p>エムスリーでは、年末のそばも餅もおせち料理も技術も大好きな意欲のあるエンジニアを募集しています。 エムスリーテックトークでギークなトークを隔週で楽しめるのもエムスリーだけ!</p> <p>新卒・中途それぞれの採用だけでなく、カジュアル面談やインターンも常時募集していますのでよろしくお願いします!</p> <h3 id="エンジニア採用ページはこちら">エンジニア採用ページはこちら</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/">jobs.m3.com</a></cite></p> <h3 id="カジュアル面談もお気軽にどうぞ">カジュアル面談もお気軽にどうぞ</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2Fcasual-session%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/casual-session/">jobs.m3.com</a></cite></p> <h3 id="インターンも常時募集しています">インターンも常時募集しています</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fopen.talentio.com%2Fr%2F1%2Fc%2Fm3%2Fhomes%2F4354" title="【新卒】エンジニアリンググループ 募集一覧 / エムスリー株式会社" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://open.talentio.com/r/1/c/m3/homes/4354">open.talentio.com</a></cite></p> vaaaaaanquish エムスリーのAI・機械学習チームって何やってるの?2024年1年間で作ってきた28個のプロダクトを大公開 hatenablog://entry/6802418398312664579 2024-12-31T10:00:00+09:00 2024-12-31T13:28:18+09:00 こんにちは。エンジニアリンググループゼネラルマネジャー & 機械学習エンジニアの大垣です。 さて、私が機械学習エンジニアとして仕事をしているAI・機械学習チームでは、今年一年で28個のプロダクトをリリースしました。月に2つくらいは新規プロダクトが出てる計算ですね。なかなか高速にリリースできているのではないでしょうか。 なお、この1年で5名のメンバーが新規に加わり、チームが12人から17人になったので、来年は更に加速していきたいです!*1 これらのプロダクトを簡単にお見せしつつ、エムスリーという医療xWebの企業でMLのチームはどういう仕事をしているのか、というのをお届けできればと思います! 多… <p>こんにちは。エンジニアリンググループゼネラルマネジャー &amp; 機械学習エンジニアの大垣です。</p> <p>さて、私が機械学習エンジニアとして仕事をしているAI・機械学習チームでは、今年一年で28個のプロダクトをリリースしました。月に2つくらいは新規プロダクトが出てる計算ですね。なかなか高速にリリースできているのではないでしょうか。 なお、この1年で5名のメンバーが新規に加わり、チームが12人から17人になったので、来年は更に加速していきたいです!<a href="#f-f1f8d85f" id="fn-f1f8d85f" name="fn-f1f8d85f" title=" プロダクトカルタのブログにあるように、今年はじめ時点では年間15くらいだったので、今年も開発速度が1.5倍以上になってます">*1</a></p> <p>これらのプロダクトを簡単にお見せしつつ、エムスリーという医療xWebの企業でMLのチームはどういう仕事をしているのか、というのをお届けできればと思います!</p> <p><figure class="figure-image figure-image-fotolife" title="多いっちゃ多いので、新メンバーはカルタでプロダクトを覚えています。"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241231/20241231100006.png" width="800" height="518" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>多いっちゃ多いので、新メンバーはカルタでプロダクトを覚えています。このカルタ自体もLazzaroniというプロダクトです<br/><a href="https://www.m3tech.blog/entry/2024/08/19/163000">&#x5E74;&#x9593;15&#x500B;&#x4EE5;&#x4E0A;&#x306E;&#x30D7;&#x30ED;&#x30C0;&#x30AF;&#x30C8;&#x3092;&#x30EA;&#x30EA;&#x30FC;&#x30B9;&#x3059;&#x308B;AI&#x30C1;&#x30FC;&#x30E0;&#x3092;&#x5165;&#x793E;&#x3057;&#x305F;&#x3066;&#x306E;&#x30D5;&#x30EC;&#x30C3;&#x30B7;&#x30E5;&#x306A;&#x76EE;&#x7DDA;&#x3067;&#x7D39;&#x4ECB;&#x3059;&#x308B; - &#x30A8;&#x30E0;&#x30B9;&#x30EA;&#x30FC;&#x30C6;&#x30C3;&#x30AF;&#x30D6;&#x30ED;&#x30B0;</a></figcaption></figure></p> <ul class="table-of-contents"> <li><a href="#2024年に立ち上げられたAI機械学習プロジェクト一覧">2024年に立ち上げられたAI・機械学習プロジェクト一覧</a></li> <li><a href="#レコメンド">レコメンド</a><ul> <li><a href="#コンテンツの回遊">コンテンツの回遊</a></li> <li><a href="#求人の最適化">求人の最適化</a></li> <li><a href="#クロスサービス">クロスサービス</a></li> <li><a href="#内部広告枠">内部広告枠</a></li> </ul> </li> <li><a href="#ユーザー向けアプリケーション">ユーザー向けアプリケーション</a><ul> <li><a href="#EBHS">EBHS</a></li> <li><a href="#製薬業界の方向けPWAアプリケーション">製薬業界の方向けPWAアプリケーション</a></li> </ul> </li> <li><a href="#情報提供のオートメーション">情報提供のオートメーション</a><ul> <li><a href="#時系列での興味モデリング">時系列での興味モデリング</a></li> <li><a href="#自動配信">自動配信</a></li> <li><a href="#外部広告">外部広告</a></li> <li><a href="#ダッシュボード">ダッシュボード</a></li> </ul> </li> <li><a href="#臨床AI">臨床AI</a><ul> <li><a href="#線維化ILDの早期診断のためのBMAXがリリースされました">線維化ILDの早期診断のためのBMAXがリリースされました</a></li> <li><a href="#PAHの診断の研究がリリースれました">PAHの診断の研究がリリースれました</a></li> <li><a href="#その他まだ開発中のもの">その他まだ開発中のもの</a></li> </ul> </li> <li><a href="#他のMLプロダクトに提供するための内部エンジンプロダクト">他のMLプロダクトに提供するための内部エンジンプロダクト</a><ul> <li><a href="#LLMによる記事の構造化">LLMによる記事の構造化</a></li> <li><a href="#動画データの文字起こし構造化">動画データの文字起こし・構造化</a></li> <li><a href="#文章をいい感じの場所で区切る">文章をいい感じの場所で区切る</a></li> </ul> </li> <li><a href="#MLインフラ">MLインフラ</a><ul> <li><a href="#内製機械学習パイプラインgokartに強力な型サポートを導入する">内製機械学習パイプラインgokartに強力な型サポートを導入する</a></li> <li><a href="#チーム内linter">チーム内linter</a></li> </ul> </li> <li><a href="#We-are-hiring-">We are hiring !!</a><ul> <li><a href="#エンジニア採用ページはこちら">エンジニア採用ページはこちら</a></li> <li><a href="#カジュアル面談もお気軽にどうぞ">カジュアル面談もお気軽にどうぞ</a></li> <li><a href="#インターンも常時募集しています">インターンも常時募集しています</a></li> </ul> </li> </ul> <h2 id="2024年に立ち上げられたAI機械学習プロジェクト一覧">2024年に立ち上げられたAI・機械学習プロジェクト一覧</h2> <p>AIチームでは、テーマに沿ったA~Zの名前をプロダクトにつけており、2024年は、2024年1月に作られた川の名前シリーズ後半のV(Volga、コンテンツにLLMで識別的タグ付け)から始まり、酒の名前シリーズもほぼ一周周り、V(Vermouth、動画の書き起こしとチャプター生成)が2024年12月に最後にリリースされました。 酒の名前シリーズでA-Zが4週目なので、AI・機械学習チーム発足以来で言うと、ざっくりいまは100個程度のプロダクトがあることになります。</p> <p>数だけ話してもどうしようもないので、AI・機械学習チームで2024年に立ち上げられたプロダクトの名前と一言まとめを大公開します!</p> <div><style> table.ogaki{ width: 100%; border-collapse: separate; border-spacing: 5px; } table.ogaki th,table td{ padding: 10px 0; text-align: center; border-radius: 5px; } table.ogaki tr:nth-child(even){ background-color: #eee } table.ogaki tr:nth-child(odd){ background-color: rgb(13,39,102); color: #eee; } </style></div> <table class="ogaki" style="font-size: 100%;font-weight: bold"> <tbody> <tr> <td>Volga<br/>タグ付け</td> <td>Wulik<br/>画像変換</td> <td>Xingu<br/>協調フィルタリング</td> <td>Yodogawa<br/>アンケート項目の構造化 </td> </tr> <tr> <td> Zambezi<br/>画像生成 </td> <td> Asti<br/>外部広告支援 </td> <td> Brandy<br/>ユーザー活動の可視化 </td> <td> Chita<br/>時系列成長テーブルデータ</td> </tr> <tr> <td> Dunkel<br/>疾患時系列予測モデル </td> <td> Eiswein<br/>カルテの構造化 </td> <td> Fizz<br/>マーケティングデータ連携 </td> <td> Gin<br/>前日行動によるレコメンド </td> </tr> <tr> <td> Hoegaarden<br/>データ中心PWAスマホアプリ </td> <td> Iichiko<br/>オンラインレコメンド </td> <td> Jura<br/>名寄せ </td> <td> Kir<br/>クロスサービスレコメンド1 </td> </tr> <tr> <td> Lazzaroni<br/>プロダクト名カルタ </td> <td> Mojito<br/>ステップ配信 </td> <td> No.6<br/>皮膚疾患の診断AI </td> <td> Orion<br/>PVデータのモデリングエンジン </td> </tr> <tr> <td> Pisco<br/>モデル出力提供ミドルウェア </td> <td> Queen<br/>プッシュ通知レコメンド </td> <td> Rum<br/>求人レコメンド </td> <td> Sauvignon<br/>クロスサービスレコメンド2 </td> </tr> <tr> <td> Tokaji<br/>求人レコメンド2 </td> <td> Underberg<br/>クロスサービスレコメンド3 </td> <td> Vermouth<br/>動画の書き起こし・構造化 </td> <td> Whiskey<br/>記事中の病院のタグ付け </td> </tr> </tbody> </table> <p>具体的なサービスはあかせないのでレコメンド1,2...みたいになってしまっているものもありますが、雰囲気掴んでいただけたのではないでしょうか。</p> <p>この後の章では、更に分野ごとにざっくりどういうことやってるかの紹介をします。</p> <h2 id="レコメンド">レコメンド</h2> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241231/20241231100017.png" width="800" height="334" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>2024年の開発リストを見てもわかると思いますが、やはりwebサービスの会社でMLチームが最も力を発揮する分野の一つはレコメンデーションです。</p> <h4 id="コンテンツの回遊">コンテンツの回遊</h4> <p>例えば医師コミュニティ内のコンテンツだったりで、次から次へといろんな投稿を読みたい、みたいなサービスがあります。それらのサービスでは記事ごとに、次はこの記事がオススメなので読んでみて下さい、という形で、サービス内の回遊を促すレコメンドがユーザー体験に協力に効きます。論文のようなストック型のコンテンツでは協調フィルタリングベースだったり、ニュースのようなフロー型のコンテンツではオンライン学習ベースだったり、一口に回遊レコメンドと言ってもコンテンツの質ごとにモデルは多岐にわたります。</p> <h4 id="求人の最適化">求人の最適化</h4> <p>エムスリーのサイトの中には、医師、薬剤師、医療従事者の皆様のための求人情報を提供しているサービスがあります。 現在の居住地から無理なく通える範囲で自分にあった求人があるか、というのは非常に重要なパーソナライズですので、もちろんエムスリーではこの求人パーソナライズを開発しています。</p> <h4 id="クロスサービス">クロスサービス</h4> <p>エムスリーのような複数サービスを持つ会社にとって、あるサービスは使ってるけど、他のサービスはそこまでではない、というユーザーの方は、まだ気づいてもらっていない良さがある、という意味でもったいないわけです。このサービスでこういう行動をしている人にはこっちのサービスも刺さるんじゃないか?などの観測を通して、ユーザーの皆様が気づいていないサービスへの動線を提供するのもレコメンドの一つです。</p> <h4 id="内部広告枠">内部広告枠</h4> <p>内部で広告ってどういうこと?と感じるかもしれませんが、これは前項のクロスサービスをより抽象化した枠となります。 例えば、論文を読みに来た先生に薬剤の情報を提供したり、という形です。 ここを広告枠と考えている理由は、広告の仕組みと同様に、各サービスが枠を提供して、そこにユーザーごとに最適なコンテンツを、具体的にはCTR/CVRを予測して抽選し、出す仕組みにしているからです。</p> <p>その特性上エムスリーのレコメンド領域でもかなり自由度が高い、かつ、コンテクストを活かしたレコメンドが期待される領域で、正直まだまだやりきれてないんですが、特に今年は、深いコンバージョン(クリックだけじゃなくその後コンテンツをしっかり見る)、レコメンド自体のパフォーマンスが落ちないように候補を早めに足切りする、等のサブアルゴリズムを追加しました。</p> <h2 id="ユーザー向けアプリケーション">ユーザー向けアプリケーション</h2> <p>AI・機械学習チームでは、実はフロントエンドも含めたアプリケーションも作成しています。データ・機械学習をもとにエムスリーのすべてを向上させるのがミッションなので、ないサービスは自分で作る、というのもミッションに含まれるわけです。</p> <h4 id="EBHS">EBHS</h4> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fm3comlp.m3.com%2Flp%2Febhs-life%2Febhs-life%2F" title="EBHS Life | エムスリー株式会社" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://m3comlp.m3.com/lp/ebhs-life/ebhs-life/">m3comlp.m3.com</a></cite></p> <p>健康診断の結果って漫然と観てしまうことありますよね。エムスリーでは健康診断の結果をもとに、健康寿命にどれくらい影響を及ぼすのか、どう改善したらいいのか、というのを個人レベル・企業レベルで提供するEBHSというサービスを運営しています。このサービスの中の予測モデルはもちろん、データ連携・認証・フロントエンドもふくめてまるっとAI・機械学習チームのエンジニアが開発しています。</p> <h4 id="製薬業界の方向けPWAアプリケーション">製薬業界の方向けPWAアプリケーション</h4> <p>疾患のデータや、多様なコンテンツのクロスレコメンドを扱うAI・機械学習チームとして、実はそれらのデータを活かした、スマホでアクセスするためのPWAアプリケーションも開発したりしています。 いま最も重要な疾患やニュースの情報をプッシュ通知で届けたりなど、スマホの体験を最大に活かして業界内の活動を支援しています。 もちろんデザイングループや他チームのプロダクトマネージャーと協力してUIの磨き込みも行います。</p> <h2 id="情報提供のオートメーション">情報提供のオートメーション</h2> <p>レコメンドのようなブラックボックス寄りな最適化も良いのですが、"こういう人にこういうタイミングでこんな情報届けられれば喜んでもらえるのにな"という具体的な課題を、施策開発者と協力して実現していくのもデータ・MLの重要な役割です。</p> <h4 id="時系列での興味モデリング">時系列での興味モデリング</h4> <p>ユーザーの皆様にとって、最も興味が高いタイミングで面白い記事が配信されてほしいわけです。この、興味というのは連続的に変化するものなので、誰が・何に興味があるだけではなく、いつ、興味があるかのモデリングも非常に重要な分野です。</p> <h4 id="自動配信">自動配信</h4> <p>モデリングできるだけでは不足なのでさらにそこから、適切な人に適切に情報を届ける、というシステム化もMLチームで取り組んでいます。</p> <h4 id="外部広告">外部広告</h4> <p>エムスリーでも、他媒体の外部広告は利用することがあります。その際に、うるさい広告にならないように、刺さりそうな人に刺さりそうな内容だけを出したい。 そのためには、社内でターゲティングのレコメンドエンジンを動かしたり、最適な文言を生成したり、動かしたレコメンドエンジンを元に入稿したりと割と様々な最適化をする必要があります。</p> <h4 id="ダッシュボード">ダッシュボード</h4> <p>上記プロダクトたち、例えば自動配信を試作開発メンバーに提供した後に、実際それがユーザーにとって良かったものになってるか。もっと伸ばせるところはないか、という可視化があってこそ施策を磨けるわけです。AIによる出力と施策が噛み合っているか、などをダッシュボードを通じて把握できるようにするのももちろん仕事の一環になります。</p> <p>一方で、ダッシュボードの乱造は、それはそれで仕事のための仕事になるので気をつけています。シンプルな仕組みでダッシュボードを作れるようにしたり、slack通知で完結する部分はslackに閉じる、インタフェースがスプレッドシートで十分なものはスプレッドシートからのBigquery連携とする、など、ダッシュボードを目的としない仕事の仕方をしています。</p> <h2 id="臨床AI">臨床AI</h2> <p>医療の分野でMLというと、画像診断などはイメージ湧きやすいかもしれません。エムスリーのMLチームでも臨床現場で使われるAIの開発に取り組んでいます。</p> <h4 id="線維化ILDの早期診断のためのBMAXがリリースされました">線維化ILDの早期診断のためのBMAXがリリースされました</h4> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fm3comlp.m3.com%2Flp%2Fm3com%2FBMAX" title="BMAX | 線維化ILDの検出を支援するAI" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://m3comlp.m3.com/lp/m3com/BMAX">m3comlp.m3.com</a></cite></p> <h4 id="PAHの診断の研究がリリースれました">PAHの診断の研究がリリースれました</h4> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fbmcpulmmed.biomedcentral.com%2Farticles%2F10.1186%2Fs12890-024-02891-4" title="Artificial intelligence-based model for predicting pulmonary arterial hypertension on chest x-ray images - BMC Pulmonary Medicine" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://bmcpulmmed.biomedcentral.com/articles/10.1186/s12890-024-02891-4">bmcpulmmed.biomedcentral.com</a></cite></p> <h4 id="その他まだ開発中のもの">その他まだ開発中のもの</h4> <p>ウェアラブルデバイス、病理画像、もちろん胸部その他X線・CTなど様々なモダリティのモデルを作成しています</p> <h2 id="他のMLプロダクトに提供するための内部エンジンプロダクト">他のMLプロダクトに提供するための内部エンジンプロダクト</h2> <h4 id="LLMによる記事の構造化">LLMによる記事の構造化</h4> <p>AI・機械学習チームでのLLMのもっとも重要な利用は、既存の資産を(オンデマンドで)構造化する部分となります。 例えば、血液のがんについて情報が必要なユーザーが居るときに、そもそもどの記事が血液のがんに関連するものであるかがわかってないとレコメンドの精度も落ちるわけですよね。</p> <p>もちろん基本的にはこういう構造化も専用モデルを作ればいいわけですが、LLMで構造化することによって、より特化した施策を短期間で立ち上げられることが非常に便利です。</p> <h4 id="動画データの文字起こし構造化">動画データの文字起こし・構造化</h4> <p>もちろん構造化はテキストの記事だけではありません。動画・画像・医用画像・健康診断データ・その他医療データなど、記事以外にも構造化することで利用価値が開けるデータは非常に多くあります。直近のプロダクトでは、動画を元に、文字起こししつつ構造化する、というのをインターンの2週間でリリースしました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241231/20241231100014.png" width="800" height="359" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><a href="https://www.m3tech.blog/entry/2024/12/19/170000">&#x30A4;&#x30F3;&#x30BF;&#x30FC;&#x30F3;&#x3067;&#x306F;&#x3058;&#x3081;&#x3066;&#x306E;&#x30D7;&#x30ED;&#x30C0;&#x30AF;&#x30C8;&#x958B;&#x767A;&#x3092;&#x7D4C;&#x9A13;&#x3057;&#x305F;&#x8A71;&#x3010;&#x30BD;&#x30D5;&#x30C8;&#x30A6;&#x30A7;&#x30A2;&#x30A8;&#x30F3;&#x30B8;&#x30CB;&#x30A2;&#x30A4;&#x30F3;&#x30BF;&#x30FC;&#x30F3;&#x53C2;&#x6226;&#x8A18;&#x3011; - &#x30A8;&#x30E0;&#x30B9;&#x30EA;&#x30FC;&#x30C6;&#x30C3;&#x30AF;&#x30D6;&#x30ED;&#x30B0;</a></p> <h4 id="文章をいい感じの場所で区切る">文章をいい感じの場所で区切る</h4> <p>ここまで紹介したわかりやすいもの以外に、ちょっと面白いプロダクトを紹介すると、ある長い文章を、適切な改行をして、例えば三行でまとめる。 というものも1プロダクトとして動いていたりします。 なんでこれがほしいかと言うと、外部・内部の広告だったり、メールマガジンだったりでコンテンツを紹介するときに、どうしても紹介文が複数行に鳴ってしまうわけですが、その時の見栄えはユーザーの理解に非常に影響が強いからです。 このように、汎用的であるがゆえに突き詰めると非常に重要な問題になるプロダクト、というのも内部連携ではよくあります。</p> <h3 id="MLインフラ">MLインフラ</h3> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241231/20241231100010.png" width="800" height="425" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>27のプロダクト名にはなってないけど、チームの開発速度が向上するための施策もめちゃめちゃやってます。</p> <p>特に面白いところは、インフラ担当がいるとかではなく、MLエンジニア・ソフトウェアエンジニア・MLOpsエンジニアが各自自分の気になるところをガンガン快適にしていった結果インフラが整っているところです。</p> <h4 id="内製機械学習パイプラインgokartに強力な型サポートを導入する">内製機械学習パイプラインgokartに強力な型サポートを導入する</h4> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.m3tech.blog%2Fentry%2Fai%2Fgokart-type" title="Genericやらoverloadやらを使って、MLパイプラインツールgokartを型安全にしてみた - エムスリーテックブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.m3tech.blog%2Fentry%2Fgokart-mypy-plugin" title="mypy plugin に入門して社内 OSS である gokart を型安全にしてみた - エムスリーテックブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p> <h4 id="チーム内linter">チーム内linter</h4> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.m3tech.blog%2Fentry%2F2024%2F10%2F02%2F110000" title="チームで培われたベストプラクティスをlintとして周知する - エムスリーテックブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p> <h2 id="We-are-hiring-">We are hiring !!</h2> <p><figure class="figure-image figure-image-fotolife" title="AI・機械学習チーム紹介資料より"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241231/20241231100021.png" width="800" height="445" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>AI・機械学習チーム紹介資料より<br/><a href="https://speakerdeck.com/m3_engineering/introduction-of-m3-ai-team">&#x30A8;&#x30E0;&#x30B9;&#x30EA;&#x30FC; AI&#x30FB;&#x6A5F;&#x68B0;&#x5B66;&#x7FD2;&#x30C1;&#x30FC;&#x30E0;&#x7D39;&#x4ECB;&#x8CC7;&#x6599; / Introduction of M3 AI Team - Speaker Deck</a> </figcaption></figure></p> <p>エムスリーでは、MLを専門としているチームでありつつも、"なんでもやって成果を出す"を楽しめるエンジニアを募集しています。</p> <p>当該チームでのMLエンジニア・ソフトウェアエンジニア・MLOpsエンジニアを始めとし、その他多様なチームでの働き方に興味がありましたらぜひカジュアル面談ご応募下さい!</p> <h3 id="エンジニア採用ページはこちら">エンジニア採用ページはこちら</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/">jobs.m3.com</a></cite></p> <h3 id="カジュアル面談もお気軽にどうぞ">カジュアル面談もお気軽にどうぞ</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2Fcasual-session%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/casual-session/">jobs.m3.com</a></cite></p> <h3 id="インターンも常時募集しています">インターンも常時募集しています</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fopen.talentio.com%2Fr%2F1%2Fc%2Fm3%2Fhomes%2F4354" title="【新卒】エンジニアリンググループ 募集一覧 / エムスリー株式会社" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://open.talentio.com/r/1/c/m3/homes/4354">open.talentio.com</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-f1f8d85f" id="f-f1f8d85f" name="f-f1f8d85f" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"> <a href="https://www.m3tech.blog/entry/2024/08/19/163000">プロダクトカルタのブログ</a>にあるように、今年はじめ時点では年間15くらいだったので、今年も開発速度が1.5倍以上になってます</span></p> </div> Hi_king AIチームGoogle Kubernetes Engineオンボーディングチュートリアル hatenablog://entry/6802418398315065216 2024-12-30T17:00:00+09:00 2024-12-30T17:00:01+09:00 こんにちは。AI・機械学習チーム(以下AIチーム)チームリーダー、兼、エンジニアリンググループゼネラルマネージャーの横本(@yokomotod)です。 AIチームでは開発したMLプロダクトの実行基盤としてGoogle Kubernetes Engine(以下GKE)を採用しています。デプロイ関連コードのテンプレートも整備され、新しいプロダクトをスタートさせるときはGKE初心者でも簡単にデプロイまで持っていけるようになっています。 しかし自動生成は一方で、なにがどうなってデプロイされているのかブラックボックスのままになってしまいがちという問題もあります。 そこで今回は、チームメンバーへのオンボー… <p>こんにちは。AI・機械学習チーム(以下AIチーム)チームリーダー、兼、エンジニアリンググループゼネラルマネージャーの横本(<a href="https://x.com/yokomotod">@yokomotod</a>)です。</p> <p>AIチームでは開発したMLプロダクトの実行基盤としてGoogle Kubernetes Engine(以下GKE)を採用しています。デプロイ関連コードのテンプレートも整備され、新しいプロダクトをスタートさせるときはGKE初心者でも簡単にデプロイまで持っていけるようになっています。</p> <p>しかし自動生成は一方で、なにがどうなってデプロイされているのかブラックボックスのままになってしまいがちという問題もあります。</p> <p>そこで今回は、チームメンバーへのオンボーディング、そしてKubernetesを触ってみたい人の参考になればと期待して、 ゼロからGKEにデプロイするハンズオン形式のチュートリアルを作ってみました。</p> <ul class="table-of-contents"> <li><a href="#Whats-this">What's this</a><ul> <li><a href="#前提条件">前提条件</a></li> </ul> </li> <li><a href="#GKEクラスタの準備">GKEクラスタの準備</a><ul> <li><a href="#プロジェクトの作成">プロジェクトの作成</a></li> <li><a href="#Billing-Account-の設定">Billing Account の設定</a></li> <li><a href="#GKEサービスのAPIを有効化">GKEサービスのAPIを有効化</a></li> <li><a href="#GKEクラスタの作成">GKEクラスタの作成</a></li> <li><a href="#クラスタへの接続">クラスタへの接続</a></li> </ul> </li> <li><a href="#デプロイするDockerイメージの準備">デプロイするDockerイメージの準備</a><ul> <li><a href="#サンプルアプリケーションをビルドする">サンプルアプリケーションをビルドする</a></li> <li><a href="#Artifact-Registryにプッシュする">Artifact Registryにプッシュする</a></li> </ul> </li> <li><a href="#いざデプロイ">いざデプロイ</a><ul> <li><a href="#プレーンなYAMLファイルでのデプロイ">プレーンなYAMLファイルでのデプロイ</a></li> <li><a href="#Namespaceの作成">Namespaceの作成</a></li> <li><a href="#Deployment-の作成">Deployment の作成</a></li> <li><a href="#ServiceIngress-の作成">Service、Ingress の作成</a></li> </ul> </li> <li><a href="#改善">改善</a><ul> <li><a href="#Kustomize-の導入">Kustomize の導入</a></li> <li><a href="#Skaffold-の導入">Skaffold の導入</a></li> </ul> </li> <li><a href="#お片付け">お片付け</a></li> <li><a href="#おわり">おわり</a><ul> <li><a href="#We-are-hiring-">We are hiring !!</a><ul> <li><a href="#エンジニア採用ページはこちら">エンジニア採用ページはこちら</a></li> <li><a href="#カジュアル面談もお気軽にどうぞ">カジュアル面談もお気軽にどうぞ</a></li> <li><a href="#インターンも常時募集しています">インターンも常時募集しています</a></li> </ul> </li> </ul> </li> </ul> <h2 id="Whats-this">What's this</h2> <p>このチュートリアルでは</p> <ol> <li>GKEでクラスタを作成</li> <li>サンプルアプリケーションのDockerイメージをbuild、push</li> <li>サンプルアプリケーションのクラスタへのデプロイ</li> <li>dev/prodの2環境にデプロイするための改良</li> </ol> <p>を行います。</p> <p>チュートリアル中で使うファイルなどは以下のgithubレポジトリでも公開しています</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fyokomotod%2Fgke-tutorial" title="GitHub - yokomotod/gke-tutorial" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/yokomotod/gke-tutorial">github.com</a></cite></p> <h4 id="前提条件">前提条件</h4> <p>Google Cloudのアカウントの作成や <code>gcloud</code> CLIのインストールまでは完了している前提としています。</p> <h2 id="GKEクラスタの準備">GKEクラスタの準備</h2> <p>まずはチュートリアルを行うためのGKEクラスタから準備します。</p> <p>公式ドキュメント: <a href="https://cloud.google.com/kubernetes-engine/docs/how-to/creating-an-autopilot-cluster">https://cloud.google.com/kubernetes-engine/docs/how-to/creating-an-autopilot-cluster</a></p> <h4 id="プロジェクトの作成">プロジェクトの作成</h4> <p>クラスタを配置するプロジェクトですが、最後にまとめて掃除できるように、また他のリソースと干渉しないように、今回のチュートリアル専用のプロジェクトを作成しましょう。</p> <p>例えば <code>m3-ai-team-k8s-tutorial-(ランダムな数字)</code> という名前でプロジェクトを作成します。</p> <pre class="code console" data-lang="console" data-unlink>PROJECT_ID=m3-ai-team-k8s-tutorial-$RANDOM gcloud projects create $PROJECT_ID</pre> <h4 id="Billing-Account-の設定">Billing Account の設定</h4> <p>GKEを使用するためには、プロジェクトにBilling Accountが設定されている必要があります。</p> <p>有効になっているか確認</p> <pre class="code console" data-lang="console" data-unlink>gcloud billing projects describe $PROJECT_ID</pre> <p>出力</p> <pre class="code" data-lang="" data-unlink>billingAccountName: &#39;&#39; billingEnabled: false name: projects/m3-ai-team-k8s-tutorial-xxxxx/billingInfo projectId: m3-ai-team-k8s-tutorial-xxxxx</pre> <p>作成直後であれば <code>billingEnabled: false</code> になっているはずなので、Billing Account を設定します。</p> <p>Billing Account の一覧を取得して、設定したいACCOUNT_IDを確認。</p> <pre class="code console" data-lang="console" data-unlink>gcloud billing accounts list</pre> <p>プロジェクトに紐づけます。</p> <pre class="code console" data-lang="console" data-unlink>ACCOUNT_ID=xxxxxx-xxxxxx-xxxxxx gcloud billing projects link $PROJECT_ID --billing-account=$ACCOUNT_ID</pre> <p>これで <code>billingEnabled: true</code> になっているはずです。</p> <pre class="code console" data-lang="console" data-unlink>gcloud billing projects describe $PROJECT_ID</pre> <p>出力</p> <pre class="code" data-lang="" data-unlink>billingAccountName: billingAccounts/xxxxxx-xxxxxx-xxxxxx billingEnabled: true name: projects/m3-ai-team-k8s-tutorial-xxxxx/billingInfo projectId: m3-ai-team-k8s-tutorial-xxxxx</pre> <h4 id="GKEサービスのAPIを有効化">GKEサービスのAPIを有効化</h4> <p>GKEを利用するためにAPIを有効化する必要があります。まずはAPIが有効になっているか確認します。</p> <pre class="code console" data-lang="console" data-unlink>gcloud services list --project=$PROJECT_ID \ | grep container.googleapis.com</pre> <p>プロジェクト作成直後では <code>container.googleapis.com</code> が表示されない = API無効状態のはずなので、次のようにして有効化します。</p> <pre class="code console" data-lang="console" data-unlink>gcloud services enable container.googleapis.com --project=$PROJECT_ID</pre> <p>有効になったか、再度確認</p> <pre class="code console" data-lang="console" data-unlink>gcloud services list --project=$PROJECT_ID \ | grep container.googleapis.com</pre> <p>出力</p> <pre class="code" data-lang="" data-unlink>container.googleapis.com Kubernetes Engine API</pre> <h4 id="GKEクラスタの作成">GKEクラスタの作成</h4> <p>いよいよGKEクラスタを作成します。今回はAutopilotモードで作成します。</p> <p>クラスタ名など</p> <pre class="code console" data-lang="console" data-unlink>export CLUSTER_NAME=tutorial-cluster export LOCATION=asia-northeast1</pre> <p>いざ作成!</p> <pre class="code console" data-lang="console" data-unlink>gcloud container clusters create-auto $CLUSTER_NAME \ --location=$LOCATION \ --project=$PROJECT_ID</pre> <p>しばらく時間がかかりますが、以下のように出力されれば構築完了です。</p> <pre class="code" data-lang="" data-unlink>NAME LOCATION MASTER_VERSION MASTER_IP MACHINE_TYPE NODE_VERSION NUM_NODES STATUS tutorial-cluster asia-northeast1 1.30.6-gke.1125000 34.146.50.13 e2-small 1.30.6-gke.1125000 3 RUNNING</pre> <p>※ 次のような警告が出ますが、後ほどインストールするので今は気にしなくて大丈夫です。</p> <pre class="code" data-lang="" data-unlink>CRITICAL: ACTION REQUIRED: gke-gcloud-auth-plugin, which is needed for continued use of kubectl, was not found or is not executable. Install gke-gcloud-auth-plugin for use with kubectl by following https://cloud.google.com/kubernetes-engine/docs/how-to/cluster-access-for-kubectl#install_plugin</pre> <p>Webコンソール</p> <p><a href="https://console.cloud.google.com/kubernetes/list/overview">https://console.cloud.google.com/kubernetes/list/overview</a></p> <p><figure class="figure-image figure-image-fotolife" title="作成されたクラスタ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241230/20241230170011.png" width="800" height="143" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>作成されたクラスタ</figcaption></figure></p> <h4 id="クラスタへの接続">クラスタへの接続</h4> <p>デプロイなどの操作するためのCLI <code>kubectl</code> と、警告に出ていた認証用のプラグインも必要なのでインストールします。</p> <pre class="code console" data-lang="console" data-unlink>gcloud components install kubectl gke-gcloud-auth-plugin</pre> <p>作成したクラスタに対して認証を通します。</p> <pre class="code console" data-lang="console" data-unlink>gcloud container clusters get-credentials $CLUSTER_NAME \ --location=$LOCATION \ --project=$PROJECT_ID</pre> <p>これでクラスタにアクセスできるようになりました。試しにNamespace 一覧を表示してみましょう。</p> <pre class="code console" data-lang="console" data-unlink>kubectl get namespaces</pre> <p>出力</p> <pre class="code" data-lang="" data-unlink>NAME STATUS AGE default Active 9m47s gke-gmp-system Active 8m36s gke-managed-cim Active 9m19s gke-managed-filestorecsi Active 9m9s gke-managed-system Active 8m54s gmp-public Active 8m36s kube-node-lease Active 9m48s kube-public Active 9m48s kube-system Active 9m48s</pre> <p>Namespaceが表示されていれば成功です!</p> <h2 id="デプロイするDockerイメージの準備">デプロイするDockerイメージの準備</h2> <p>クラスタの準備が出来たので、次はデプロイするサンプルアプリケーションのDockerイメージを準備します。</p> <p>公式ドキュメント: <a href="https://cloud.google.com/kubernetes-engine/docs/deploy-app-cluster">https://cloud.google.com/kubernetes-engine/docs/deploy-app-cluster</a></p> <h4 id="サンプルアプリケーションをビルドする">サンプルアプリケーションをビルドする</h4> <p>Google Cloudが提供するサンプルアプリケーションをビルドします。</p> <p>ソースコード: <a href="https://github.com/GoogleCloudPlatform/kubernetes-engine-samples/blob/main/quickstarts/hello-app/main.go">https://github.com/GoogleCloudPlatform/kubernetes-engine-samples/blob/main/quickstarts/hello-app/main.go</a></p> <p>実はこのサンプルのビルド済みイメージも公開されていて、公式ドキュメントではそちらを使っています。が、ここではビルドしてプッシュする部分もやってみましょう。</p> <p>ソースコードをclone。</p> <pre class="code console" data-lang="console" data-unlink>git clone https://github.com/GoogleCloudPlatform/kubernetes-engine-samples.git cd kubernetes-engine-samples/quickstarts/hello-app/</pre> <p>ビルド。イメージタグは後ほど作成するArtifact Registryの設定に合わせて指定します。</p> <pre class="code console" data-lang="console" data-unlink>docker build -t $LOCATION-docker.pkg.dev/$PROJECT_ID/tutorial/hello-app:latest .</pre> <p>ローカルで動作確認してみましょう</p> <pre class="code console" data-lang="console" data-unlink>docker run --rm -p 8080:8080 $LOCATION-docker.pkg.dev/$PROJECT_ID/tutorial/hello-app</pre> <p>起動ログ</p> <pre class="code" data-lang="" data-unlink>2024/12/26 18:52:16 Server listening on port 8080</pre> <p>curlやブラウザでアクセスできればビルド成功です。</p> <pre class="code console" data-lang="console" data-unlink>curl localhost:8080</pre> <p>出力</p> <pre class="code" data-lang="" data-unlink>Hello, world! Version: 1.0.0 Hostname: 748b9d88aa6e</pre> <h4 id="Artifact-Registryにプッシュする">Artifact Registryにプッシュする</h4> <p>無事ビルドできたので、Artifact Registryにプッシュします。Artifact Registry は、Dockerイメージ(や各種パッケージも)を保存・配信するGoogle Cloudのサービスです。</p> <p>プッシュ先のリポジトリが必要なので作成しましょう。</p> <pre class="code console" data-lang="console" data-unlink>gcloud artifacts repositories create tutorial --repository-format=docker \ --location=$LOCATION \ --project=$PROJECT_ID</pre> <p>プッシュできるように認証を設定します。</p> <pre class="code console" data-lang="console" data-unlink>gcloud auth configure-docker $LOCATION-docker.pkg.dev</pre> <p>push!</p> <pre class="code console" data-lang="console" data-unlink>docker push $LOCATION-docker.pkg.dev/$PROJECT_ID/tutorial/hello-app:latest</pre> <h2 id="いざデプロイ">いざデプロイ</h2> <p>お疲れ様でした。クラスタとアプリケーションのイメージが出来たので、いよいよデプロイしていきます!</p> <h4 id="プレーンなYAMLファイルでのデプロイ">プレーンなYAMLファイルでのデプロイ</h4> <p>Kubernetesにデプロイする構成は基本的にYAMLファイルで記述します。</p> <p>まずは普通にYAMLファイルを書いてデプロイしてみましょう。</p> <h4 id="Namespaceの作成">Namespaceの作成</h4> <p>Namespaceはデプロイするリソースを分けるためのものです。</p> <p>今回は <code>tutorial-dev</code> という名前で作成します。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">apiVersion</span><span class="synSpecial">:</span> v1 <span class="synIdentifier">kind</span><span class="synSpecial">:</span> Namespace <span class="synIdentifier">metadata</span><span class="synSpecial">:</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> tutorial-dev </pre> <p><code>kubectl apply</code> コマンドでファイルの内容を適用します。</p> <pre class="code console" data-lang="console" data-unlink>kubectl apply -f namespace.yaml</pre> <h4 id="Deployment-の作成">Deployment の作成</h4> <p>DeploymentはPodのデプロイを管理するリソースです。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">apiVersion</span><span class="synSpecial">:</span> apps/v1 <span class="synIdentifier">kind</span><span class="synSpecial">:</span> Deployment <span class="synIdentifier">metadata</span><span class="synSpecial">:</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> helloweb <span class="synIdentifier">namespace</span><span class="synSpecial">:</span> tutorial-dev <span class="synIdentifier">labels</span><span class="synSpecial">:</span> <span class="synIdentifier">app</span><span class="synSpecial">:</span> hello <span class="synIdentifier">spec</span><span class="synSpecial">:</span> <span class="synIdentifier">selector</span><span class="synSpecial">:</span> <span class="synIdentifier">matchLabels</span><span class="synSpecial">:</span> <span class="synIdentifier">app</span><span class="synSpecial">:</span> hello <span class="synIdentifier">replicas</span><span class="synSpecial">:</span> <span class="synConstant">3</span> <span class="synIdentifier">template</span><span class="synSpecial">:</span> <span class="synIdentifier">metadata</span><span class="synSpecial">:</span> <span class="synIdentifier">labels</span><span class="synSpecial">:</span> <span class="synIdentifier">app</span><span class="synSpecial">:</span> hello <span class="synIdentifier">spec</span><span class="synSpecial">:</span> <span class="synIdentifier">containers</span><span class="synSpecial">:</span> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> hello-app <span class="synIdentifier">image</span><span class="synSpecial">:</span> asia-northeast1-docker.pkg.dev/m3-ai-team-k8s-tutorial-xxxxx/tutorial/hello-app:latest <span class="synIdentifier">ports</span><span class="synSpecial">:</span> <span class="synStatement">- </span><span class="synIdentifier">containerPort</span><span class="synSpecial">:</span> <span class="synConstant">8080</span> <span class="synIdentifier">resources</span><span class="synSpecial">:</span> <span class="synIdentifier">requests</span><span class="synSpecial">:</span> <span class="synIdentifier">cpu</span><span class="synSpecial">:</span> 200m </pre> <p>少し長いですが、使用するDockerイメージやportなど <code>docker run -p 8080:8080 IMAGE</code> と同様の設定や、使用するCPU量などを書いています。</p> <pre class="code console" data-lang="console" data-unlink>kubectl apply -f deployment.yaml</pre> <p>デプロイすると <code>replicas: 3</code> に従ってPodが3つ作成されているはずです。</p> <pre class="code console" data-lang="console" data-unlink>kubectl -n tutorial-dev get po</pre> <pre class="code" data-lang="" data-unlink>NAME READY STATUS RESTARTS AGE helloweb-5b6b6cc799-wq6n6 1/1 Running 0 14s helloweb-5b6b6cc799-sl38s 1/1 Running 0 14s helloweb-5b6b6cc799-t6n8h 1/1 Running 0 14s</pre> <p>Webコンソール</p> <p><a href="https://console.cloud.google.com/kubernetes/workload/overview">https://console.cloud.google.com/kubernetes/workload/overview</a></p> <p><figure class="figure-image figure-image-fotolife" title="デプロイされたDeployment"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241230/20241230170013.png" width="685" height="178" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>デプロイされたDeployment</figcaption></figure></p> <p>ログも見てみましょう。</p> <pre class="code console" data-lang="console" data-unlink>kubectl -n tutorial-dev logs helloweb-5b6b6cc799-t6n8h</pre> <pre class="code" data-lang="" data-unlink>2024/12/26 19:02:32 Server listening on port 8080</pre> <p>無事コンテナが起動しました。が、まだ外部からアクセスできるようにはなっていません。</p> <h4 id="ServiceIngress-の作成">Service、Ingress の作成</h4> <p>Global Load Balancerを使って外部からアクセスできるようにします。</p> <p>Global Load BalancerはGoogle Cloudのリソースですが、GKEではIngressリソースを使うことでk8sから構築できます。</p> <p>Serviceの作成</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">apiVersion</span><span class="synSpecial">:</span> v1 <span class="synIdentifier">kind</span><span class="synSpecial">:</span> Service <span class="synIdentifier">metadata</span><span class="synSpecial">:</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> helloweb <span class="synIdentifier">namespace</span><span class="synSpecial">:</span> tutorial-dev <span class="synIdentifier">spec</span><span class="synSpecial">:</span> <span class="synIdentifier">selector</span><span class="synSpecial">:</span> <span class="synIdentifier">app</span><span class="synSpecial">:</span> hello <span class="synIdentifier">ports</span><span class="synSpecial">:</span> <span class="synStatement">- </span><span class="synIdentifier">port</span><span class="synSpecial">:</span> <span class="synConstant">80</span> <span class="synIdentifier">targetPort</span><span class="synSpecial">:</span> <span class="synConstant">8080</span> </pre> <p>Serviceのapply</p> <pre class="code console" data-lang="console" data-unlink>kubectl apply -f service.yaml</pre> <p>Ingreesの作成</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">apiVersion</span><span class="synSpecial">:</span> networking.k8s.io/v1 <span class="synIdentifier">kind</span><span class="synSpecial">:</span> Ingress <span class="synIdentifier">metadata</span><span class="synSpecial">:</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> helloweb <span class="synIdentifier">namespace</span><span class="synSpecial">:</span> tutorial-dev <span class="synIdentifier">spec</span><span class="synSpecial">:</span> <span class="synIdentifier">defaultBackend</span><span class="synSpecial">:</span> <span class="synIdentifier">service</span><span class="synSpecial">:</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> helloweb <span class="synIdentifier">port</span><span class="synSpecial">:</span> <span class="synIdentifier">number</span><span class="synSpecial">:</span> <span class="synConstant">80</span> </pre> <p>Ingressのapply</p> <pre class="code console" data-lang="console" data-unlink>kubectl apply -f ingress.yaml</pre> <p>applyしてしばらくすると <code>ADDRESS</code> が割り当てられ、さらにまたしばらくするとアクセスできるようになります。</p> <p><a href="https://console.cloud.google.com/net-services/loadbalancing/list/loadBalancers">Webコンソール</a>でも作成されたGlobal Load Balancerが確認できます。</p> <p>(見た目構築が完了したように見えてもアクセスが出来ない時間がしばらくあるので、なにか構築を間違えたのかと心配になるのですが、焦らずしばらく待ってみましょう)</p> <pre class="code console" data-lang="console" data-unlink>kubectl -n tutorial-dev get ingress</pre> <pre class="code" data-lang="" data-unlink>NAME CLASS HOSTS ADDRESS PORTS AGE helloweb &lt;none&gt; * 34.54.192.203 80 5m28s</pre> <pre class="code console" data-lang="console" data-unlink>curl 34.54.192.203</pre> <pre class="code" data-lang="" data-unlink>Hello, world! Version: 1.0.0 Hostname: helloweb-5b6b6cc799-t6n8h</pre> <p>おめでとうございます!デプロイしたアプリケーションにアクセスが出来るようになりました!!</p> <h2 id="改善">改善</h2> <p>プレーンなYAMLを使ってデプロイに成功しましたが、実際の運用ではベタ書きのYAMLでは管理が難しくなりがちです。</p> <p>例えばdev環境とprod環境のようにデプロイ先が複数あって設定が少しだけ異なる、ということはよくあります。</p> <p>ほとんど同じYAMLをコピペ状態で複数メンテナンスすることは変更漏れやミスが起こりやすく管理が大変なので、いい感じにYAMLを合成する方法が欲しくなります。</p> <p>ここからは、構成ファイルをもう少し管理しやすくするツールを導入して改善してみましょう。</p> <h4 id="Kustomize-の導入">Kustomize の導入</h4> <p>Kustomizeを使うことでYAMLを合成することができます。</p> <p>例えば、以下のようにbaseとなるYAMLを作成し、そこに対してdevとprodの環境ごとに差分を適用するといったことが可能です<a href="#f-fb7581a8" id="fn-fb7581a8" name="fn-fb7581a8" title="今回は簡単のために同一クラスタ上でnamespaceだけで環境を分離しています。実際にはdev環境とprod環境はプロジェクトレベルで分離されて別クラスタにします">*1</a>。</p> <p><code>base/kustomization.yaml</code>:</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">namespace</span><span class="synSpecial">:</span> TO_BE_SPECIFIED <span class="synIdentifier">resources</span><span class="synSpecial">:</span> <span class="synStatement">- </span>deployment.yaml <span class="synStatement">- </span>service.yaml <span class="synStatement">- </span>ingress.yaml </pre> <p>(<code>- namespace.yaml</code> の行が無いことに気づいた鋭い方はしばしお待ち下さい。)</p> <p>今回はNamespace名だけを変更するような差分を適用してみます。</p> <p><code>dev/kustomization.yaml</code>:</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">namespace</span><span class="synSpecial">:</span> tutorial-dev <span class="synIdentifier">resources</span><span class="synSpecial">:</span> <span class="synStatement">- </span>../base </pre> <p><code>prod/kustomization.yaml</code>:</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">namespace</span><span class="synSpecial">:</span> tutorial-prod <span class="synIdentifier">resources</span><span class="synSpecial">:</span> <span class="synStatement">- </span>../base </pre> <p>デプロイ済みのdev環境は、Kustomize化しただけで合成結果のYAMLは変化していないはずなので、差分がないことを確認します。</p> <pre class="code console" data-lang="console" data-unlink>kubectl diff -k dev/</pre> <p>デプロイする場合は</p> <pre class="code console" data-lang="console" data-unlink>kubectl apply -k dev/</pre> <p>のようにデプロイできます。</p> <p>未作成のprod環境も同様にデプロイ出来ますが、その前にもう1つツールを導入しましょう。</p> <h4 id="Skaffold-の導入">Skaffold の導入</h4> <p>Kustomizeを導入したことでYAMLの管理が楽になりましたが、まだDockerイメージのビルドとデプロイを個別に行う必要があります。</p> <p>Skaffoldを使うことで、Dockerイメージのビルドとデプロイといったパイプラインを一括で行えるようにしてみましょう。</p> <p>まずskaffoldをインストールします。</p> <pre class="code console" data-lang="console" data-unlink>gcloud components install skaffold</pre> <p><code>skaffold.yaml</code> という構成ファイルを記述します。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">apiVersion</span><span class="synSpecial">:</span> skaffold/v4beta11 <span class="synIdentifier">kind</span><span class="synSpecial">:</span> Config <span class="synIdentifier">build</span><span class="synSpecial">:</span> <span class="synIdentifier">artifacts</span><span class="synSpecial">:</span> <span class="synStatement">- </span><span class="synIdentifier">image</span><span class="synSpecial">:</span> asia-northeast1-docker.pkg.dev/m3-ai-team-k8s-tutorial-xxxxx/tutorial/hello-app <span class="synIdentifier">context</span><span class="synSpecial">:</span> ../kubernetes-engine-samples/quickstarts/hello-app/ <span class="synIdentifier">docker</span><span class="synSpecial">:</span> <span class="synIdentifier">dockerfile</span><span class="synSpecial">:</span> Dockerfile <span class="synIdentifier">local</span><span class="synSpecial">:</span> <span class="synIdentifier">useBuildkit</span><span class="synSpecial">:</span> <span class="synConstant">true</span> <span class="synIdentifier">manifests</span><span class="synSpecial">:</span> <span class="synIdentifier">kustomize</span><span class="synSpecial">:</span> <span class="synIdentifier">paths</span><span class="synSpecial">:</span> <span class="synStatement">- </span>dev <span class="synIdentifier">profiles</span><span class="synSpecial">:</span> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> dev <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> prod <span class="synIdentifier">manifests</span><span class="synSpecial">:</span> <span class="synIdentifier">kustomize</span><span class="synSpecial">:</span> <span class="synIdentifier">paths</span><span class="synSpecial">:</span> <span class="synStatement">- </span>prod </pre> <p>これで、コマンド1つでDockerイメージのビルドとデプロイが一括で行えるようになります。</p> <pre class="code console" data-lang="console" data-unlink>skaffold run -p dev</pre> <p>prod環境も構築しましょう。</p> <p>ここで一つAIチーム流で、NamespaceはKustomizeに含めず、外部で作成しています<a href="#f-0b0c7c8b" id="fn-0b0c7c8b" name="fn-0b0c7c8b" title="実際にはterraformを使って他のk8s外のリソースと一緒に作成しています。おかげでterraform destroyでk8sリソースも含めてきれいに消すことが出来たりもします。">*2</a>。こうすることで、なんらかの間違いで接続先の環境が違っていた場合、namespaceが無いのでデプロイが安全に失敗するようにしています。</p> <p>というわけで、prod環境のNamespaceを作成</p> <pre class="code console" data-lang="console" data-unlink>kubectl create namespace tutorial-prod</pre> <p>skaffold runで一括デプロイ!</p> <pre class="code console" data-lang="console" data-unlink>skaffold run -p prod</pre> <p>これで、dev, prodの2環境の構成ファイルをDRYに保ち、簡単にデプロイ出来るようになりました。</p> <h2 id="お片付け">お片付け</h2> <p>チュートリアルは以上です!</p> <p>無駄な料金を発生させないために、namespaceごと削除しておきましょう。</p> <pre class="code console" data-lang="console" data-unlink>kubectl delete namespace tutorial-dev tutorial-prod</pre> <p> プロジェクトも削除。</p> <pre class="code console" data-lang="console" data-unlink>gcloud projects delete $PROJECT_ID</pre> <h2 id="おわり">おわり</h2> <p>如何でしたでしょうか。入門用チュートリアルはネット上にも多くありますが、GKEやKubernetesの世界は広く、ちょうど自分たちが欲しいスコープ、欲しい手順のハンズオンがあってもいいかなと思って書いてみました。</p> <p>お役に立てば幸いです。</p> <h3 id="We-are-hiring-">We are hiring !!</h3> <p>ここまで読み進めていただきありがとうございます。これでいつでもAIチームでロケットスタートできますね!</p> <p>もしご興味ありましたらこちらのページからどうぞ。 カジュアル面談・ご応募お待ちしております!</p> <h4 id="エンジニア採用ページはこちら">エンジニア採用ページはこちら</h4> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/">jobs.m3.com</a></cite></p> <h4 id="カジュアル面談もお気軽にどうぞ">カジュアル面談もお気軽にどうぞ</h4> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2Fcasual-session%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/casual-session/">jobs.m3.com</a></cite></p> <h4 id="インターンも常時募集しています">インターンも常時募集しています</h4> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fopen.talentio.com%2Fr%2F1%2Fc%2Fm3%2Fhomes%2F4354" title="【新卒】エンジニアリンググループ 募集一覧 / エムスリー株式会社" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://open.talentio.com/r/1/c/m3/homes/4354">open.talentio.com</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-fb7581a8" id="f-fb7581a8" name="f-fb7581a8" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">今回は簡単のために同一クラスタ上でnamespaceだけで環境を分離しています。実際にはdev環境とprod環境はプロジェクトレベルで分離されて別クラスタにします</span></p> <p class="footnote"><a href="#fn-0b0c7c8b" id="f-0b0c7c8b" name="f-0b0c7c8b" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">実際にはterraformを使って他のk8s外のリソースと一緒に作成しています。おかげでterraform destroyでk8sリソースも含めてきれいに消すことが出来たりもします。</span></p> </div> yokomotod 半年間の運用で学んだDatastream導入の勘所 hatenablog://entry/6802418398315033676 2024-12-29T12:00:00+09:00 2024-12-29T12:00:00+09:00 こんにちは、エンジニアリンググループデータ基盤チームの木田です。先日公開されたCTO兼VPoP山崎の記事にある通りゼネラルマネージャーを拝命しまして、データ活用の観点だけではなくそれ以外の側面でも組織全体を支える立場となりました。クリスマスが過ぎ、すっかり街は年末モードになりましたね。毎年この変わり身の速さに驚くとともに、新年の足音を感じる時期でもあります。 門松とクリスマス飾りが同居する年末らしい光景。エムスリー赤坂オフィスから徒歩15分の距離にある麻布台ヒルズマーケットの一角にて エムスリーのデータ基盤利用者は今年も順調に増えまして、システム (サービスアカウント)も含めると倍々で増加して… <p>こんにちは、エンジニアリンググループデータ基盤チームの木田です。先日公開されたCTO兼VPoP<a href="https://www.m3tech.blog/entry/cto-retrospective-2024">山崎の記事</a>にある通りゼネラルマネージャーを拝命しまして、データ活用の観点だけではなくそれ以外の側面でも組織全体を支える立場となりました。クリスマスが過ぎ、すっかり街は年末モードになりましたね。毎年この変わり身の速さに驚くとともに、新年の足音を感じる時期でもあります。 <figure class="figure-image figure-image-fotolife" title="門松とクリスマス飾りが同居する年末らしい光景。エムスリー赤坂オフィスから徒歩15分の距離にある麻布台ヒルズマーケットの一角にて"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241229/20241229120017.jpg" width="800" height="509" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>門松とクリスマス飾りが同居する年末らしい光景。エムスリー赤坂オフィスから徒歩15分の距離にある麻布台ヒルズマーケットの一角にて</figcaption></figure></p> <p>エムスリーのデータ基盤利用者は今年も順調に増えまして、システム (サービスアカウント)も含めると倍々で増加しております。それに呼応する形でBigQuery上のクエリやユースケースの多様性 (そして料金も) 増しております。データ基盤の開発・運用を手がけるデータ基盤チームでは、今年の活動としてdbtやdataformの利用拡大によるデータパイプラインの品質強化や、クライアント企業へのタイムリーなレポーティングニーズに対応するため<a href="https://cloud.google.com/datastream?hl=ja">Datastream</a> による基幹データベースからのニアリアルタイムデータ連携を始めました。Datastreamの導入・運用を夏頃から始めて約半年が経ち、本記事では導入時の検討事項やその後の運用について振り返ります。</p> <ul class="table-of-contents"> <li><a href="#CDCとは">CDCとは</a><ul> <li><a href="#CDCの使い所">CDCの使い所</a></li> </ul> </li> <li><a href="#CDC導入の事前準備と設計">CDC導入の事前準備と設計</a><ul> <li><a href="#ソースDBにロギングの設定を追加する">ソースDBにロギングの設定を追加する</a></li> <li><a href="#ネットワーク経路を確認する">ネットワーク経路を確認する</a></li> <li><a href="#BigQueryのデータセットを用意する">BigQueryのデータセットを用意する</a></li> </ul> </li> <li><a href="#運用開始後のトラブルシューティングあれこれ">運用開始後のトラブルシューティングあれこれ</a><ul> <li><a href="#BigQuery上で更新反映されない問題">BigQuery上で更新反映されない問題</a><ul> <li><a href="#データソース側のログアーカイブ待ちによるもの">データソース側のログアーカイブ待ちによるもの</a></li> <li><a href="#BigQuery側のstalenessの設定によるもの">BigQuery側のstalenessの設定によるもの</a></li> </ul> </li> <li><a href="#Backfillしたらレコードが重複">Backfillしたらレコードが重複</a></li> </ul> </li> <li><a href="#Datastream導入の振り返り">Datastream導入の振り返り</a><ul> <li><a href="#良かった点">良かった点</a></li> <li><a href="#難しかった点">難しかった点</a></li> </ul> </li> <li><a href="#まとめ">まとめ</a></li> <li><a href="#We-are-hiring-">We are hiring !!</a></li> </ul> <h2 id="CDCとは">CDCとは</h2> <p>CDC(Change Data Capture)はデータソース (主にOracleやPostgreSQLなどのリレーショナルデータベース)からの変更イベントを検知し、それらの変更をリアルタイムでキャプチャして他のシステムとのデータ連携を可能にする技術です。動作原理としてはRDBのトランザクションログを監視し、変更イベントを検知、宛先のデータベースに同期する方式が多いです。</p> <p>CDCを実現するクラウドサービスとしてGoogle CloudであればDatastream, AWSには <a href="https://aws.amazon.com/jp/dms/">DMS</a> があります。FivetranやAirbyteも同様の仕組みによるデータ連携の機能を有しています。</p> <h3 id="CDCの使い所">CDCの使い所</h3> <p> 弊社ではデータウェアハウスとして用いているBigQueryとの親和性なども踏まえDatastreamを選択してデータ連携を実装しました。Datastreamのようなサービスを利用する際はコスト面とのバランスも重要です。バッチ連携の方が安価かつ手軽に実装できるケースも多いため闇雲に全テーブルのニアリアルタイム連携を目指すのではなく、必要に迫られたテーブルから順次対応を進めるのが合理的だと考えます。それ以外に、物理削除があったり更新差分の特定が困難などの理由で差分連携が難しいテーブルを連携する場合、データサイズが大きく更新頻度の低いテーブルを連携する場合においても有効な手段です。</p> <p> 次節以降ではDatastreamを導入した際の検討事項や運用していて遭遇したトラブルについてご紹介します。Datastreamに限らずCDC全般で当てはまることも多いはずなので今後導入する際の参考になれば幸いです。</p> <h2 id="CDC導入の事前準備と設計">CDC導入の事前準備と設計</h2> <h3 id="ソースDBにロギングの設定を追加する">ソースDBにロギングの設定を追加する</h3> <p> DatastreamはデータソースのRDBMSのトランザクションログを監視して変更を検知するという仕組み上データソースに対する準備、例えばPostgreSQLであればWALのレベルをLogicalに設定したり、Oracleの場合はサプリメンタルロギングの有効化が事前に必要となります。データソース側の状況次第では負荷やストレージに影響することも考えられるので検証しつつ慎重に進めたいところです。また、DBごとに必要となる設定項目や制約が多数あるためデータソースごとの制約事項には必ず事前に目を通すことをお勧めします。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcloud.google.com%2Fdatastream%2Fdocs" title="Datastream documentation  |  Google Cloud" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://cloud.google.com/datastream/docs">cloud.google.com</a></cite></p> <h3 id="ネットワーク経路を確認する">ネットワーク経路を確認する</h3> <p>Datastreamには<a href="https://cloud.google.com/datastream/docs/private-connectivity?hl=ja">プライベート接続</a>の機能があり、Google CloudのVPCとPeeringすることで内部IPアドレスでの通信が可能です。しかし推移的なPeeringはできないので所定のVPCに配置されていないDB (例えばAWSやオンプレミスに配置されたDB) との間の同期のためにはリバースプロキシインスタンスの配置が必要です。図上で (中略)とされている箇所ですが、AWS, GCP, オンプレミスをVPNで相互接続<sup id="fnref:1"><a href="#fn:1" rel="footnote">1</a></sup>するような構造になっています。</p> <p><figure class="figure-image figure-image-fotolife" title="プライベート接続を用いた構成イメージ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241229/20241229120014.png" width="676" height="288" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>プライベート接続を用いた構成イメージ</figcaption></figure></p> <h3 id="BigQueryのデータセットを用意する">BigQueryのデータセットを用意する</h3> <p>CDCで連携されたデータが直接書き込まれるデータセットとデータ基盤利用者が実際にクエリするデータセットを分離しました。</p> <p><figure class="figure-image figure-image-fotolife" title="利用者向けのデータは承認済みビューをとして配置"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241229/20241229120007.png" width="707" height="292" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>利用者向けのデータは承認済みビューをとして配置</figcaption></figure></p> <p>連携されたテーブルによっては全社の利用者に公開したくないケースが考えられます (例えば、氏名やメールアドレスなどのような個人情報など)。ストリームの設定によりテーブル単位・カラム単位で除外設定も可能ですが、用途によっては適宜マスクした状態で利用する等のケースも考えられるため、CDCの過程では除外せずに一度BigQuery上の非公開データセットに連携し、承認済みビューの形で公開領域に配置するという設計にしました。</p> <p>最後に、連携元データベースのインフラ担当者やDBAとは密に連携を取る必要があるため早めに巻き込んでおくことが重要です。私たちは定期的にSREチーム所属の担当者と定例を持って双方のタスクを確認しながら準備しました<sup id="fnref:2"><a href="#fn:2" rel="footnote">2</a></sup>。</p> <h2 id="運用開始後のトラブルシューティングあれこれ">運用開始後のトラブルシューティングあれこれ</h2> <p>運用を開始してからいくつかデータ同期の問題に遭遇し、都度解決を図ってきました。事例集的に記載しますので何かの参考になれば幸いです。</p> <h3 id="BigQuery上で更新反映されない問題">BigQuery上で更新反映されない問題</h3> <p> Datastream上で正常にログ出力されているにもかかわらずBigQueryへのデータ同期がされていないように見える事象がありました。これには2つの要因がありました。データソース側・宛先テーブルの側双方の要因が考えられるのでCloud Logging上のDatastreamのログを参照して切り分けることになります。</p> <h4 id="データソース側のログアーカイブ待ちによるもの">データソース側のログアーカイブ待ちによるもの</h4> <p> この時のデータソースはOracleだったのですが、REDOログがアーカイブされるまでの間変更は流れてこないという制約によるものでした。Datastreamのログ上はOracleのSCNは追えているが変更イベントは流れてこないという状況でした<sup id="fnref:3"><a href="#fn:3" rel="footnote">3</a></sup>。</p> <p><figure class="figure-image figure-image-fotolife" title="最新SCNは追えているがREDOログがスイッチされておらず変更イベントは取得できていない様子"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241229/20241229120010.png" width="717" height="144" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>最新SCNは追えているがREDOログがスイッチされておらず変更イベントは取得できていない様子</figcaption></figure></p> <h4 id="BigQuery側のstalenessの設定によるもの">BigQuery側の<a href="https://cloud.google.com/bigquery/docs/change-data-capture?hl=ja">staleness</a>の設定によるもの</h4> <p>stalenessの詳細はリンク先のドキュメントに譲りますが、ざっくはCDCの更新イベントが到達してからBigQueryのクエリ結果として確認できるようになるまでの最大タイムラグです。<code>max_staleness</code>の値を小さくすることでよりリアルタイムに近いデータ同期が実現できますが、BigQueryへの書き込み時のクエリ料金が増すというトレードオフがあるので注意が必要です。データ鮮度要求に応じて無理ない値を設定すべきでしょう。</p> <h3 id="Backfillしたらレコードが重複">Backfillしたらレコードが重複</h3> <p>データソース側で移行作業があり、テスト環境で接続設定変更を行ないデータの再同期 (Backfill) を実行したところレコードの重複が発生しました。これはデータソース側のテーブルに主キーが設定されていなかったことで発生しました (ユニーク索引は定義されていたがDDL上でPrimary Keyと明示されていなかった)。</p> <p>Oracleの場合主キー指定のないテーブルに対してDatastreamはRowIDをキーと見なして同期を行うため、export/importなどでデータの物理配置が変わってRowIDが変更されると別レコードとして扱われてしまっていました。なお、PostgreSQLの場合はPrimaryKeyや<a href="https://www.postgresql.org/docs/current/sql-altertable.html#SQL-ALTERTABLE-REPLICA-IDENTITY">REPLICA IDENTITY</a> を指定しておく必要があります。この辺りもDBMSごとに挙動の違いがあるので把握しておく必要があります。</p> <h2 id="Datastream導入の振り返り">Datastream導入の振り返り</h2> <p>導入・運用をしてみて改めて良いと感じた点や難しいと感じた点がありました。</p> <h3 id="良かった点">良かった点</h3> <ul> <li><p>比較的簡単な設定と現実的な費用でニアリアルタイムのデータ同期が実現できました。サーバーレスのサービスであるため、実行環境などのインフラを整える必要もなく、少人数のチームでも運用できています。</p></li> <li><p>データソース側のスキーマ変更や物理削除に対する追随も可能。 特にデータソース側でカラム追加などがあった際にも特に運用対応をせずともBigQueryのテーブルに変更が反映されました。スキーマドリフトへの対応がスムーズにできたのは良いサプライズでした。</p></li> </ul> <h3 id="難しかった点">難しかった点</h3> <ul> <li><p>データソースと密結合にならざるを得ない。データソースとなるDBごとに細かい制約<sup id="fnref:4"><a href="#fn:4" rel="footnote">4</a></sup>があったり、ソースDBのメンテナンス時はCDCも足並みを揃える必要があり、データソースのDBに対する依存が発生します。この点はアーキテクチャ上の制約として受容する必要があります。</p></li> <li><p>事前の費用見積りの難しさ。連携対象のテーブルのサイズや更新頻度の情報からおおまかなCDC費用を試算してスタートしたものの、全体としてはデータ転送・プロキシインスタンスの費用・CDCの料金・BigQuery料金と複数の費目を想定する必要があり一筋縄ではいきませんでした。実際に一定規模、かつ無償枠には収まらない程度に小規模なワークロードを動かしてみて想定外の見落としがないかチェックするのが良いと感じました。</p></li> </ul> <h2 id="まとめ">まとめ</h2> <p>CDC (Change Data Capture)はデータソースとなるDBごとの制約や内部挙動の複雑さがある一方、簡易な設定でデータ同期ができる強力なソリューションです。弊社でもDatastreamを実際に導入、運用をしてみて良い点・課題点ありますが今のところ総じて安定した運用ができています。今後は利用範囲の拡大だけではなく費用の最適化やデータ欠損の検知なども強化していきたいと考えています。</p> <p>また、来たる2025年に向けてはニアリアルタイムデータだけではなく、セキュリティ上考慮が必要なデータソースやそれらを扱うユースケースも増えそうなのでデータ保護の枠組みも一段とバージョンアップしてきたいと思っております。それではお体に気をつけて良いお年をお迎えください!</p> <h2 id="We-are-hiring-">We are hiring !!</h2> <p>エムスリーのデータ基盤活用範囲は年々広がっております。コストパフォーマンスの良いデータ基盤の構築・運用やデータを活用したアプリケーション開発で活躍の場がたくさんあります。もしご興味ありましたらこちらのページからどうぞ。 カジュアル面談・ご応募お待ちしております!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/">jobs.m3.com</a></cite> <iframe src="https://open.talentio.com/r/1/c/m3-inc/embed/pages/57609" width="100%" height="300" frameborder=0 title="%E3%82%A8%E3%83%A0%E3%82%B9%E3%83%AA%E3%83%BC+%7C+%E3%83%87%E3%83%BC%E3%82%BF%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2"></iframe><cite class="hatena-citation"><a href="https://open.talentio.com/r/1/c/m3-inc/pages/57609">open.talentio.com</a></cite></p> <div class="footnotes"> <hr/> <ol> <li id="fn:1"> 詳しくはぜひこちらをお読みください <a href="https://www.m3tech.blog/entry/gcp_aws_vpn_struggle">AWS&#x30FB;&#x30AA;&#x30F3;&#x30D7;&#x30EC;&#x3068; GCP &#x3092; VPN &#x3067;&#x76F8;&#x4E92;&#x63A5;&#x7D9A;&#x3059;&#x308B;&#x969B;&#x306E;&#x52D8;&#x6240; - &#x30A8;&#x30E0;&#x30B9;&#x30EA;&#x30FC;&#x30C6;&#x30C3;&#x30AF;&#x30D6;&#x30ED;&#x30B0;</a><a href="#fnref:1" rev="footnote">&#8617;</a></li> <li id="fn:2"> 実際のところは上述程周到な計画を立ててスタートしたわけではなく、試行錯誤しながら導入を進めました。ここではその内容を振り返って後知恵的にまとめています。<a href="#fnref:2" rev="footnote">&#8617;</a></li> <li id="fn:3"> テスト環境でトランザクション量が少なかったため発生していました。REDOログのサイズ次第ではレプリケーション遅延の要因になるので設定を確認して対応しました。<a href="#fnref:3" rev="footnote">&#8617;</a></li> <li id="fn:4"> 落とし穴が色々とあり、落ちながら学びました。公式のドキュメントもどんどん充実しているような気がします。<a href="#fnref:4" rev="footnote">&#8617;</a></li> </ol> </div> takumikid2 エムスリーの全員 QA 作戦 hatenablog://entry/6802418398312840707 2024-12-28T16:00:00+09:00 2024-12-28T16:00:00+09:00 皆さんこんにちは。エンジニアリンググループゼネラルマネージャー兼 QA チームリーダーの窪田です。 1年が経つのは非常に早く、やりたかったことをやれたのかと振り返る年末になりました。 本日は私が GM になる前から力を入れてきた活動「全員QA体制への移行*1」についてお話をします。 本内容は、12/24 に取締役CTO兼VPoPの山崎がブログで話題に挙げていた「組織的ステップチェンジ」の1つでもあり、組織的なインパクトも大きな活動です。 www.m3tech.blog はじめに 現状の開発フロー 全員 QA 体制への移行 決起集会(エンジニアリンググループミーティング) 方針・目標設定と周知 … <p>皆さんこんにちは。エンジニアリンググループゼネラルマネージャー兼 QA チームリーダーの窪田です。 1年が経つのは非常に早く、やりたかったことをやれたのかと振り返る年末になりました。 本日は私が GM になる前から力を入れてきた活動「全員QA体制への移行<a href="#f-0a6e7b5e" id="fn-0a6e7b5e" name="fn-0a6e7b5e" title="プロジェクトネームがほしいよねという話にもなり、チームメンバーやChat GPTなどに相談した結果 Quality boost tActics 略してQubotA という愛称を影でつけています。">*1</a>」についてお話をします。 本内容は、12/24 に取締役CTO兼VPoPの山崎がブログで話題に挙げていた「組織的ステップチェンジ」の1つでもあり、組織的なインパクトも大きな活動です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.m3tech.blog%2Fentry%2Fcto-retrospective-2024" title="2024年、エムスリーエンジニアリンググループで行った組織的ステップチェンジ5選。 - エムスリーテックブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.m3tech.blog/entry/cto-retrospective-2024">www.m3tech.blog</a></cite></p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#現状の開発フロー">現状の開発フロー</a></li> <li><a href="#全員-QA-体制への移行">全員 QA 体制への移行</a><ul> <li><a href="#決起集会エンジニアリンググループミーティング">決起集会(エンジニアリンググループミーティング)</a></li> <li><a href="#方針目標設定と周知">方針・目標設定と周知</a></li> <li><a href="#現在までの進捗">現在までの進捗</a></li> </ul> </li> <li><a href="#おわりに">おわりに</a></li> <li><a href="#おまけ">おまけ</a></li> <li><a href="#Were-hiring">We're hiring</a></li> </ul> <h2 id="はじめに">はじめに</h2> <p>山崎のブログでも言及されていた通り、優秀な QA エンジニア採用は競争が激しい状態にあり各社採用に苦労している状況です。一方でエンジニア採用は順調に推移しており、開発力は日々増強されています。開発スピードの向上に対応出来るようにQA チーム内でも様々な施策を打ってきたため、QA 作業待ちチケットの大量発生などの大きな支障はまだ出ていません。しかし、今後も採用が厳しい状態が続き、現在と同様な方法で QA 作業を進めていくと、開発スピードに対応できなくなるときが訪れ、組織スケールのボトルネックになってしまう可能性があります。 また、QA エンジニアが担当サービスのテストに大きな工数を割いているため、品質改善施策に取り組む時間も十分にとれていない状況となっています。テストを中心とした品質確保施策のみではさらなる品質改善は難しいためこちらについても改善が必要です。</p> <h2 id="現状の開発フロー">現状の開発フロー</h2> <p>現在、QA チームでは、各サービスの開発チームに入り込み、仕様の確認からシステムテストの実施までを担当しています。簡略化したフローで表すと下図のようになります(チームによって採用している開発手法が違うのであくまでも一例です)</p> <p><figure class="figure-image figure-image-fotolife" title="現在の開発フロー"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241228/20241228160002.png" width="800" height="377" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>現在の開発フロー</figcaption></figure></p> <p>現在の開発フローはチーム内で密にコミュニケーションを取りながら並行して作業ができるため作業効率もよく、それぞれのメンバーの得意分野に専念できていると感じており、これもまた1つの安定した形なのかなと思っています。ただし、プロダクトマネージャー(以降 PdM)、エンジニア(以降 Eng)の採用が順調に進んでいるため人数のバランスが将来的に崩れてしまい、システムテスト工程(図のテスト設計〜テスト実施)がリリースのボトルネックになると予想されます。</p> <h2 id="全員-QA-体制への移行">全員 QA 体制への移行</h2> <p>上記問題点をまずは解消し、将来の組織拡大、さらなる品質改善を達成するために「全員 QA 体制への移行」といったチャレンジをすることになりました。 簡単に言ってしまえば QA エンジニアが中心に行ってきたシステムテストを核とした品質保証活動を開発チーム全員でやってしまおうということです。言うは易し。</p> <p>全員 QA 体制への移行で達成したいことはたくさんありますが、大まかに次のポイントを達成したいと思っています。</p> <ul> <li>テスト活動をチーム全員で出来るようにする</li> <li>さらなる品質改善施策の実施</li> </ul> <p>1つ目はテスト活動をチーム全員で実施できるようになることで、リリースのボトルネックが将来的に発生するのを防ぎ、全員でプロダクトを触ることにより品質意識を更に醸成することが目標になります。</p> <p>2つ目は現在、システムテストを中心とした品質保証活動となっているため、次3点の活動によりさらなる品質改善をさせることが目標です。</p> <ul> <li>要件定義や 画面設計書 などのドキュメント品質向上</li> <li>リリース後指標の監視によるサービス品質向上</li> <li>品質指標の随時確認と分析の強化</li> </ul> <p>今回のブログではすでに様々な活動を実施している前者について詳しく説明します。</p> <h3 id="決起集会エンジニアリンググループミーティング">決起集会(エンジニアリンググループミーティング)</h3> <p>12/26 に VPoE の河合が公開した記事の通り、エンジニアリンググループではグループメンバー全員が参加するミーティングを月に1回実施しています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.m3tech.blog%2Fentry%2Fengineering-books-2024" title="2024年にエンジニアリンググループミーティングで紹介した書籍の紹介 - エムスリーテックブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.m3tech.blog/entry/engineering-books-2024">www.m3tech.blog</a></cite></p> <p>この中で、現在の QA エンジニア採用の現状を踏まえたうえで以下の点について説明をしました。</p> <ul> <li>QA エンジニアの増加に頼らない品質確保プロセスの確立が必要であること <ul> <li>半年後、1年後にどのような状態になりたいかの方針・目標も合わせて説明(後述)</li> </ul> </li> <li>テストを中心とした品質保証活動では限界があるため、全体で協力して品質を高める体制が必要であること</li> </ul> <p>説明の後ある程度の反対意見などがあるかなと思っていましたが、予想以上に協力的な声が届きました。改めてエムスリーエンジニアリンググループの互いにリスペクトし合う、助け合う文化に感動を覚えたものです。このような雰囲気もあったため次のステップであるチームへの個別説明もスムーズに進めることができました。</p> <h3 id="方針目標設定と周知">方針・目標設定と周知</h3> <p>Eng に従来 QA エンジニアが実施していたテストを実施してもらうことがとりあえずの基本方針です。実はこの方向性は今年から急に始まったというわけではなく、確認が容易な軽い修正や一部のバッチ処理などはすでに Eng にテストを実施してもらっていました。<a href="#f-5ecbf75f" id="fn-5ecbf75f" name="fn-5ecbf75f" title="これについての詳しい話は QA チームメンバーが色々と頑張ってくれたので別途ブログが書かれる予定です">*2</a>今回はこの活動の範囲を拡大したいというものです。</p> <p>何をするにも目標を設定することが大事です。まずは目標のための指標を作成しました。次の表のように評価基準は3段階になります。</p> <p><figure class="figure-image figure-image-fotolife" title="評価基準"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241228/20241228160009.png" width="800" height="269" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>評価基準</figcaption></figure></p> <p>次に、各チームが現在この評価基準のどのレベルにいるかをマッピングました。その上でチームの性質などを加味してどのタイミングでどのレベルになっていたいかの目標を設定しました。次の表が作成例です。すでに半分以上のテストを実施できているチームは半年後にほぼ全てのテストを実施するように設定しています。また、現在のテスト実施比率が低いチームかつ、高い品質を維持しなければいけないチームについては品質確保の仕組みを整える時間を加味する必要があるため、緩やかな目標を設定しています。</p> <p><figure class="figure-image figure-image-fotolife" title="目標設定例"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241228/20241228160005.png" width="710" height="188" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>目標設定例</figcaption></figure></p> <p>上記目標と方針を各チームに説明をしました。各チームに所属している QA チームメンバーに現実的な進め方について提案をしてもらい、それを揉んだ上で細かいルールなどを決定しました。</p> <p>ルールの一例を次に示します。今回は 表の C チームの例です。</p> <ul> <li>高品質を要求される機能についてはこれまで通り QA エンジニアが中心となってテストを実施する</li> <li>上記以外の改修でテスト工数が1日未満のものについては Eng がテストを実施する <ul> <li>Eng は確認したテスト観点をチケットに記載をし、QA エンジニアはそれをレビューする <ul> <li>必要に応じて 追加の観点を Eng にテストしてもらう</li> </ul> </li> </ul> </li> <li>テスト観点は QA チーム内で蓄積している資料があるため必要に応じて確認をしてもらう</li> </ul> <p>実施の最初の段階ではなるべく簡略なルールにしておき、問題が発生次第修正をしていく方針にしました。 このルールを Eng に説明をして、即実施していただきました。</p> <h3 id="現在までの進捗">現在までの進捗</h3> <p>C チームでの進捗を一例として紹介します。C チームでは本施策を実施する前から少しずつ Eng によるテストを実施していました。そのため、今回の施策に対しては比較的スムーズに進めることができています。設定したルールに従って動けているため、次のステップ(テスト実施率の向上)に向けての作戦を立てている段階です。 上記の活動による成果を簡単にまとめてみました。</p> <ul> <li>月間 130 件の改修のテストを Eng が実施できている</li> <li>今まで QA エンジニア3人で担当していた仕事を2人で回せる様になった <ul> <li>残りの1人は品質改善のための作業着手中</li> </ul> </li> </ul> <p> 目標としているテスト比率までの道のりはまだまだありますが、着実に良い方向に進んでいる実感があります。 成果の中に品質改善の話が少し出ていますが詳しい話については次回以降のブログで紹介しようと思います。</p> <h2 id="おわりに">おわりに</h2> <p>当初、この活動により品質が多少落ちてしまわないかという懸念もありましたが、QA エンジニアによるレビューをきちんと行ったり、今まで整備してきた自動テストを活用したりすることにより品質を一定に保てており、懸念は顕在化しておりません。地道な改善活動が実を結んだ結果とも言えます。</p> <p>うまく行っていることばかりでなく課題もあります。Eng によるテスト実施比率をこれ以上拡大するためには大規模プロジェクトのテストも実施して貰う必要があります。今まで品質計画〜テスト実施を QA エンジニアが実施してきたため、Eng にそのノウハウを伝搬しつつ、適切にレビューやテスト支援を実施して品質を落とさないように移行する必要があります。 こちらについてはまだ検討中で、良い成果が出次第また報告させていただきたいと思います。</p> <p>今後の展望としては、QA エンジニアが品質改善に随時着手ができるようになったり、より高い品質が求められるサービスに QA エンジニアを集中させることができるようになることが目標です。</p> <h2 id="おまけ">おまけ</h2> <p>これ、完全達成しちゃうと QA エンジニアのお仕事はどうなるの?という話も出てきます。 QA エンジニアのお仕事はあくまでも品質を確保する、高める事にあり、テストは手段の1つです。上流のレビューに飛び込んでいったり、現在テストなどで確認できていない課題を見つけに行ったり、リリース後の数値をきちんと確認できるように動いたり……とやることはたくさんあります。さらには開発が常に順調に進むとも限らずその時は QA エンジニアの品質知識を活かすチャンスです。 エムスリーの QA エンジニアはチームの品質のパートナーとしても何でもやるぞ! というスタンスで活動しているため、今後も様々なチャレンジが待っていると予想しています(直近でもチャレンジはたくさんある……やりたいことがありすぎる……)</p> <p><figure class="figure-image figure-image-fotolife" title="全員QA で生成された AI画像"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241228/20241228160012.png" width="800" height="535" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>全員QA で生成された AI画像</figcaption></figure></p> <h1 id="Were-hiring">We're hiring</h1> <p>エムスリーでは 全員 QA の方針で関係者全員で品質保証できる体制に向かって進んでいます。テストが得意な方、全体的な品質保証に興味がある方、すべてのプロセスの品質に興味のある方などなど様々な人に楽しいチャレンジが待っている状態です。ご興味のある方はぜひ一度お問い合わせください!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/">jobs.m3.com</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-0a6e7b5e" id="f-0a6e7b5e" name="f-0a6e7b5e" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">プロジェクトネームがほしいよねという話にもなり、チームメンバーやChat GPTなどに相談した結果 Quality boost tActics 略してQubotA という愛称を影でつけています。</span></p> <p class="footnote"><a href="#fn-5ecbf75f" id="f-5ecbf75f" name="f-5ecbf75f" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">これについての詳しい話は QA チームメンバーが色々と頑張ってくれたので別途ブログが書かれる予定です</span></p> </div> j-kubota エムスリー学生エンジニア向け情報まとめ hatenablog://entry/6802418398313534577 2024-12-27T15:56:59+09:00 2025-02-20T03:35:09+09:00 こんにちは、最近Re:ゼロを人生で初めて見ましたVPoEの河合(@vaaaaanquish)です。もっと早く知りたかったです。 さて、エムスリーエンジニアリンググループでは、長らく新卒エンジニア採用を小さい規模で進めておりました。 2024年からは、その規模を少し拡大し、様々なイベントに出たりインターンを受け入れるなどしています。 本記事は、学生エンジニアの皆様から聞かれる事も多い内容をまとめ、エムスリーのエンジニア組織を知って頂く事を目的とするものです。 エムスリーのエンジニアインターン Rust/Go/k8sを全て触ってBatchをリリースした事例 実プロダクトのKotlinバックエンドを… <div align="center"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241227/20241227155712.png" width="500" height="400" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></div> <p>こんにちは、最近Re:ゼロを人生で初めて見ましたVPoEの河合(<a href="https://x.com/vaaaaanquish">@vaaaaanquish</a>)です。もっと早く知りたかったです。</p> <p>さて、エムスリーエンジニアリンググループでは、長らく新卒エンジニア採用を小さい規模で進めておりました。 2024年からは、その規模を少し拡大し、様々なイベントに出たりインターンを受け入れるなどしています。</p> <p>本記事は、学生エンジニアの皆様から聞かれる事も多い内容をまとめ、エムスリーのエンジニア組織を知って頂く事を目的とするものです。</p> <ul class="table-of-contents"> <li><a href="#エムスリーのエンジニアインターン">エムスリーのエンジニアインターン</a><ul> <li><a href="#RustGok8sを全て触ってBatchをリリースした事例">Rust/Go/k8sを全て触ってBatchをリリースした事例</a></li> <li><a href="#実プロダクトのKotlinバックエンドを改修した事例">実プロダクトのKotlinバックエンドを改修した事例</a></li> <li><a href="#分散処理MLOpsのOSSを開発公開した事例">分散処理MLOpsのOSSを開発、公開した事例</a></li> <li><a href="#電子カルテのGoRubyTypeScriptパフォーマンス改善事例">電子カルテのGo/Ruby/TypeScriptパフォーマンス改善事例</a></li> <li><a href="#社内のBQテスト基盤を作った事例">社内のBQテスト基盤を作った事例</a></li> <li><a href="#動画をテキスト化してLLMで要約リリースした事例">動画をテキスト化してLLMで要約、リリースした事例</a></li> <li><a href="#医療画像を解析するMLモデルを構築した事例">医療画像を解析するMLモデルを構築した事例</a></li> </ul> </li> <li><a href="#エムスリー株式会社について">エムスリー株式会社について</a><ul> <li><a href="#エムスリーが創る事業とプロダクト">エムスリーが創る事業とプロダクト</a></li> <li><a href="#事業を支えるエンジニアリンググループ">事業を支えるエンジニアリンググループ</a></li> <li><a href="#エンジニアのギークカルチャー">エンジニアのギークカルチャー</a></li> </ul> </li> <li><a href="#活躍するエムスリー新卒エンジニア">活躍するエムスリー新卒エンジニア</a></li> <li><a href="#おわりに">おわりに</a><ul> <li><a href="#We-are-hiring-">We are hiring !!</a></li> </ul> </li> </ul> <p>  </p> <h1 id="エムスリーのエンジニアインターン">エムスリーのエンジニアインターン</h1> <p>エムスリーでは、基本的に<strong>エンジニアインターンを通年で募集</strong>しています。 <strong>学業優先</strong>の思想をもって実施していますので、時期や日程も調整可能です。</p> <ul> <li>開始時期:学業の都合に合わせて調整可能</li> <li>実施期間:2週間~</li> <li>就業方法:リモート可</li> <li>時給:2,500円(交通費含)※ 2週間 8H×10営業日就業した場合 200,000円</li> <li>業務内容:メンターと相談し志向に沿って設計します</li> </ul> <p>インターンの<strong>内容もフルカスタム</strong>で実施しています。 以下より、各職種の詳細が見られますので参考にしてください。</p> <ul> <li><a href="https://open.talentio.com/r/1/c/m3/pages/80781">ソフトウェアエンジニア</a></li> <li><a href="https://open.talentio.com/r/1/c/m3/pages/80782">アプリエンジニア</a></li> <li><a href="https://open.talentio.com/r/1/c/m3/pages/78949">機械学習エンジニア</a></li> <li><a href="https://open.talentio.com/r/1/c/m3/pages/78950">MLOpsエンジニア</a></li> <li><a href="https://open.talentio.com/r/1/c/m3/pages/80780">セキュリティエンジニア</a></li> </ul> <p>直近のインターンの事例も紹介しておきます。</p> <h4 id="RustGok8sを全て触ってBatchをリリースした事例">Rust/Go/k8sを全て触ってBatchをリリースした事例</h4> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.m3tech.blog%2Fentry%2F2024%2F09%2F02%2F174905" title="インターンの二週間で社内APIを新しく建て本番リリースまで何でもやった話【ソフトウェアエンジニアインターン参戦記】 - エムスリーテックブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p> <h4 id="実プロダクトのKotlinバックエンドを改修した事例">実プロダクトのKotlinバックエンドを改修した事例</h4> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.m3tech.blog%2Fentry%2Fintern%2F2019%2F09%2F24" title="インターンとして新サービスの開発に携わらせていただきました - エムスリーテックブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p> <h4 id="分散処理MLOpsのOSSを開発公開した事例">分散処理MLOpsのOSSを開発、公開した事例</h4> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.m3tech.blog%2Fentry%2F2023%2F03%2F14%2F180137" title="gokartのMLパイプラインをKubernetesで並列分散実行できるライブラリkannonを作った話 - エムスリーテックブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p> <h4 id="電子カルテのGoRubyTypeScriptパフォーマンス改善事例">電子カルテのGo/Ruby/TypeScriptパフォーマンス改善事例</h4> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.m3tech.blog%2Fentry%2F2025%2F02%2F18%2F110000" title="【ソフトウェアエンジニアインターン参戦記】クラウド型電子カルテのリクエスト数を13%削減した話 - エムスリーテックブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p> <h4 id="社内のBQテスト基盤を作った事例">社内のBQテスト基盤を作った事例</h4> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.m3tech.blog%2Fentry%2Fintern-bigquery-local-test" title="AI・機械学習チームでのインターンでBigQueryのローカルテスト基盤を作った話 - エムスリーテックブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p> <h4 id="動画をテキスト化してLLMで要約リリースした事例">動画をテキスト化してLLMで要約、リリースした事例</h4> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.m3tech.blog%2Fentry%2F2024%2F12%2F19%2F170000" title="インターンではじめてのプロダクト開発を経験した話【ソフトウェアエンジニアインターン参戦記】 - エムスリーテックブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p> <h4 id="医療画像を解析するMLモデルを構築した事例">医療画像を解析するMLモデルを構築した事例</h4> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.m3tech.blog%2Fentry%2F2019%2F09%2F02%2F153000" title="CycleGANで効果を発揮!画像の構造を抽出する特徴量&quot;MIND&quot;を実装してみる - エムスリーテックブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p> <p>  </p> <h1 id="エムスリー株式会社について">エムスリー株式会社について</h1> <p>エムスリーは、医療IT事業を主軸とする会社です。 学生向けのイベント等では、エムスリーという会社をシンプルな以下2点で説明しています。</p> <ul> <li><strong>大きな会社ですが少数精鋭です</strong></li> <li><strong>ギークなカルチャーを大切にしています</strong></li> </ul> <h2 id="エムスリーが創る事業とプロダクト">エムスリーが創る事業とプロダクト</h2> <p>2000年創業の東証プライム上場企業、国内外に140社以上のグループ会社がある非常に大きな会社です。 ビジネス職種は学生からの人気も高く、コンサルやGAFAに並んで就活の人気企業ランキング上位に入っています<a href="#f-ecffa5d5" id="fn-ecffa5d5" name="fn-ecffa5d5" title="「入社が難しい有名企業ランキング」トップ200社 東洋経済オンライン より">*1</a>。</p> <div align="center"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241227/20241227155701.jpg" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" style="width:400px" itemprop="image"></span></div> <p>30以上のWebプロダクトを開発、100以上の医療事業を展開しています。 その中でエンジニアは、国内の9割以上の医師が会員となるプラットフォーム『m3.com』、日本導入数No. 1の電子カルテ『デジカル』、病院の待ち時間を0にする医療DXアプリ『デジスマ』、会員数500万人超の医師Q&amp;Aサービス『AskDoctors』など、エンジニアリングが中心となるtoB/toCプロダクトを多く提供しています。</p> <h2 id="事業を支えるエンジニアリンググループ">事業を支えるエンジニアリンググループ</h2> <p>エンジニアリンググループには、現在110名程のエンジニアが在籍しています<a href="#f-e24b549d" id="fn-e24b549d" name="fn-e24b549d" title="2024/12/23時点">*2</a>。</p> <p>事業規模に対してエンジニアの人数が多くないため「知っている先輩が入社していて…」というパターンが少なく、学生からの認知度はそう高くはない認識です。 一方、技術関連の実績は毎年出せており、技術コミュニティ内では平均的に技術力が高い事が一定知られているかなとは思います。</p> <div align="center"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241227/20241227155705.jpg" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" style="width:400px" itemprop="image"></span></div> <p>マネジメントチームも含めて20のチームがあり、1チーム5~10名で構成しています。 小さいチームで大きな成果を出すため、フラットにプロダクトの構想、設計にも関わりながら高速な意思決定を回しています。</p> <p>実際にCEOとの開発定例を持つ開発チームもあるほどに、多くのギークが自ら意思決定し、提案し、手を動かして活躍するための制度や仕組みを持つ会社です。 人数に比例して卒業生も少ないながら、エンジニアリンググループからIT業界にCEO/CTO/VPoEを10名以上排出していたり、GAFAや各メガベンチャーから卒業生の活躍の声が聞こえてきており、<strong>ギークの登竜門</strong>のようなカルチャーでもあります。</p> <h2 id="エンジニアのギークカルチャー">エンジニアのギークカルチャー</h2> <p>エムスリーでは、事業で大きな成果を出すため、こだわりとリスペクトを持って活動するギークカルチャーを大切にしています。</p> <p>代表的な例が、技術選定です。 各チームに任せているため、言語やフレームワークは隣のチームに行けば別のものにいつでもチャレンジ出来る環境になっています。 意思決定にも関与したい新旧の技術に触れて学ぶのが好きな、ギーク向けの文化です。</p> <div align="center"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241227/20241227155708.jpg" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" style="width:400px" itemprop="image"></span></div> <p>技術好きが集まっているため、隔週で行っているLT大会は10年以上、200回を超えて続いています。今でも毎回エンジニア60~70人がZoomやSlackに集まり、毎回技術の話で盛り上がっています。新卒入社したエンジニアやインターンの学生も登壇しており、私も毎回刺激を受けています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.m3tech.blog%2Fentry%2Ftechtalk-offline-2023" title="230回続く社内LT大会の忘年会が盛り上がった件について - エムスリーテックブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p> <p>また、当テックブログはもちろんのこと、技術書典などでも新卒入社のエンジニアが執筆活動をしています。OSSコミッターや書籍執筆経験のあるエンジニアは複数在籍していますので、そういった人と一緒にワイワイ活動できます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechbookfest.org%2Forganization%2F38290003" title="エムスリーエンジニアリンググループ執筆部 | 技術書典" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p> <p>大学の研究室やサークル、技術コミュニティで、技術トークをワイワイやるのが好きで、その知識やカルチャーを世の中に活かしていきたい学生エンジニアの皆様は是非見てもらえると良いかなと思います。</p> <p>  </p> <h1 id="活躍するエムスリー新卒エンジニア">活躍するエムスリー新卒エンジニア</h1> <p>実際に、新卒で入社後活躍しているエンジニアの情報もあります。</p> <p>先に紹介した技術LTのうち、外部に公開できるものはYouTube上に公開しています。 その中で新卒入社したエンジニアが発表している動画を以下にまとめています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fyoutube.com%2Fplaylist%3Flist%3DPLaOiAFIVRZ5w-wW2d0G1Xh-jMusJEsVgU%26si%3D5EMM8pxRyANPvU5I" title="新卒エンジニア動画集" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p> <p>新卒採用サイトでは、新卒から入社して活躍している人のインタビューも掲載されています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ffresh.m3recruit.com" title="エムスリー株式会社 新卒採用" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p> <p>インターン以外の学生向けのイベントでは、サポーターズさんが進める技育プロジェクトや外資就活などのイベント、未踏プロジェクトや各技術カンファレンスでも新卒エンジニアが多く参加していますので、是非会った際は現場の声を聞いてもらえると良いと思います。</p> <p>  </p> <h1 id="おわりに">おわりに</h1> <p>本記事では、エムスリーの学生エンジニア向けの情報をまとめました。</p> <p>より詳しく知りたい方は、200ページを超える<a href="https://speakerdeck.com/m3_engineering/introduction-of-m3-all-teams">全チーム紹介資料</a>や<a href="https://speakerdeck.com/m3_engineering/geek-of-geeks">エンジニアのカルチャーをまとめた生成AIによる資料</a>もありますので是非どうぞ。</p> <p>また、Xアカウントでは毎週難読クイズなども配信しています。 インターン情報のアップデートなど、定期的に投稿していますので、是非フォローしてください。</p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr"><a href="https://twitter.com/hashtag/m3_morning?src=hash&amp;ref_src=twsrc%5Etfw">#m3_morning</a> 金曜恒例!難読クイズ!<br>明日から <a href="https://twitter.com/hashtag/%E6%8A%80%E8%82%B2%E7%A5%AD?src=hash&amp;ref_src=twsrc%5Etfw">#技育祭</a> が始まるという事で【geekな2日間】をテーマに技育イベントでよく使われるJSで問題を作りました。<br><br>geekなスクリプトは何を出力するのか!!<br>答えがわかったら是非引用リプライで! !<br>&gt; [_=&#39;geek&#39;.small()[2]]+[-~_._|2]; <a href="https://t.co/0kuLoeRDoF">pic.twitter.com/0kuLoeRDoF</a></p>&mdash; エムスリー エンジニア公式 (@m3_engineering) <a href="https://twitter.com/m3_engineering/status/1836940234669662703?ref_src=twsrc%5Etfw">2024年9月20日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p>  </p> <h2 id="We-are-hiring-">We are hiring !!</h2> <p>インターン以外にも通年で新卒エンジニアを募集しています。 「ちょっと話を聞いてみたいかも」といった方は是非ご連絡下さい。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/">jobs.m3.com</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-ecffa5d5" id="f-ecffa5d5" name="f-ecffa5d5" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://toyokeizai.net/articles/-/844492?page=5">「入社が難しい有名企業ランキング」トップ200社 東洋経済オンライン</a> より</span></p> <p class="footnote"><a href="#fn-e24b549d" id="f-e24b549d" name="f-e24b549d" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">2024/12/23時点</span></p> </div> vaaaaaanquish 2024年にエンジニアリンググループミーティングで紹介した書籍の紹介 hatenablog://entry/6802418398313806755 2024-12-26T12:00:00+09:00 2024-12-26T12:00:00+09:00 年末にこんにちは、こんばんは。12月に長女の音楽発表会があり、長女の成長に涙したVPoEの河合(@vaaaaanquish)です。 本文とは関係ない娘と私 本記事では、エンジニアリンググループで毎月行っている全体会、Group Meetingで実施している『おまけコンテンツ』を紹介します。 オススメの書籍紹介になりますので、年末のお供に是非。 Engineering Group Meetingとは 書籍紹介 仕事に必要なことはすべて映画で学べる 熟達論 - 人はいつまでも学び、成長できる RANGE(レンジ)知識の「幅」が最強の武器になる データセキュリティ法の迷走 テクノロジーの世界経済史 … <p>年末にこんにちは、こんばんは。12月に長女の音楽発表会があり、長女の成長に涙したVPoEの河合(<a href="https://x.com/vaaaaanquish">@vaaaaanquish</a>)です。</p> <p><figure class="figure-image figure-image-fotolife" title="本文とは関係ない娘と私"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241226/20241226120006.jpg" width="800" height="533" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>本文とは関係ない娘と私</figcaption></figure></p> <p>本記事では、エンジニアリンググループで毎月行っている全体会、Group Meetingで実施している『おまけコンテンツ』を紹介します。</p> <p>オススメの書籍紹介になりますので、年末のお供に是非。</p> <ul class="table-of-contents"> <li><a href="#Engineering-Group-Meetingとは">Engineering Group Meetingとは</a></li> <li><a href="#書籍紹介">書籍紹介</a><ul> <li><a href="#仕事に必要なことはすべて映画で学べる">仕事に必要なことはすべて映画で学べる</a></li> <li><a href="#熟達論---人はいつまでも学び成長できる">熟達論 - 人はいつまでも学び、成長できる</a></li> <li><a href="#RANGEレンジ知識の幅が最強の武器になる">RANGE(レンジ)知識の「幅」が最強の武器になる</a></li> <li><a href="#データセキュリティ法の迷走">データセキュリティ法の迷走</a></li> <li><a href="#テクノロジーの世界経済史-ビルゲイツのパラドックス">テクノロジーの世界経済史 ビル・ゲイツのパラドックス</a></li> <li><a href="#ビジュアルシンカーの脳-絵で考える人々の世界">ビジュアル・シンカーの脳: 「絵」で考える人々の世界</a></li> <li><a href="#テクノロジストの条件">テクノロジストの条件</a></li> <li><a href="#ネガティブケイパビリティ-答えの出ない事態に耐える力">ネガティブ・ケイパビリティ 答えの出ない事態に耐える力</a></li> <li><a href="#実力も運のうち-能力主義は正義か">実力も運のうち 能力主義は正義か?</a></li> <li><a href="#1兆ドルコーチ">1兆ドルコーチ</a></li> </ul> </li> <li><a href="#おわりに">おわりに</a><ul> <li><a href="#We-are-hiring-">We are hiring !!</a><ul> <li><a href="#エンジニア採用ページはこちら">エンジニア採用ページはこちら</a></li> <li><a href="#カジュアル面談もお気軽にどうぞ">カジュアル面談もお気軽にどうぞ</a></li> <li><a href="#インターンも常時募集しています">インターンも常時募集しています</a></li> </ul> </li> </ul> </li> </ul> <p>  </p> <h1 id="Engineering-Group-Meetingとは">Engineering Group Meetingとは</h1> <p>エムスリーでは、月一回全エンジニアが集まるGroup Meetingを開催しています。 Group Meetingの本編では、各チームで行っている施策、技術の共有を行っています。 また、新しいメンバーの自己紹介など、重要な情報の全体周知も行っており、エムスリーのカルチャーを支える大事な会になっています。</p> <p>Group Meetingには、5分ほどの『おまけコーナー』がありまして、歴代VPoEが担当してユニークなコンテンツを紹介しています。 過去には、歴代VPoEの山崎がプロダクトマネジメントを紹介したり、岩佐がエンジニアリング要素のあるゲームを紹介したりしていました。 個性のある発表も多い人気コンテンツです。</p> <p>私は、良書や気になったインターネットコンテンツを紹介したりしてます。 <figure class="figure-image figure-image-fotolife" title="おまけのインターネットコンテンツ紹介事例 - バイアス議論で使われる赤点戦闘機の図は実は元論文には存在しない二次創作でしかも沢山あるという小ネタ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241226/20241226120010.jpg" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" style="width:400px" itemprop="image"></span><figcaption>おまけのインターネットコンテンツ紹介事例 - バイアス議論で使われる赤点戦闘機の図は実は元論文には存在しない二次創作でしかも沢山あるという小ネタ</figcaption></figure> 直近では、AI技術だとか社会的な統計のお話が多かったりします。 個人的には最近音声合成分野が熱いので、VRC実装も含めて楽しんでいる話を次回はしたい所です。</p> <p>  </p> <h1 id="書籍紹介">書籍紹介</h1> <p>Group Meetingでは書籍紹介も伝統的に続いています。 毎回数冊紹介しているのですが、全ては紹介しきれない量です。 当然、エンジニアリングに関わる名著、<a href="https://www.amazon.co.jp/%E9%81%94%E4%BA%BA%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E%E3%83%BC-%E7%AC%AC2%E7%89%88-%E7%86%9F%E9%81%94%E3%81%AB%E5%90%91%E3%81%91%E3%81%9F%E3%81%82%E3%81%AA%E3%81%9F%E3%81%AE%E6%97%85-David-Thomas/dp/4274226298">達人プログラマー</a>や<a href="https://www.amazon.co.jp/%E9%97%98%E3%81%86%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E%E3%83%BC-%E6%96%B0%E8%A3%85%E7%89%88-G%E3%83%BB%E3%83%91%E3%82%B9%E3%82%AB%E3%83%AB%E3%83%BB%E3%82%B6%E3%82%AB%E3%83%AA%E3%83%BC/dp/4822247570">闘うプログラマー</a>、<a href="https://www.amazon.co.jp/%E8%84%B3%E3%81%AB%E5%8F%8E%E3%81%BE%E3%82%8B%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E6%9B%B8%E3%81%8D%E6%96%B9-%E2%80%95%E8%A4%87%E9%9B%91%E3%81%95%E3%82%92%E9%81%BF%E3%81%91%E6%8C%81%E7%B6%9A%E5%8F%AF%E8%83%BD%E3%81%AB%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AE%E7%B5%8C%E9%A8%93%E5%89%87%E3%81%A8%E3%83%86%E3%82%AF%E3%83%8B%E3%83%83%E3%82%AF-Mark-Seemann/dp/4814400799">脳に収まるコードの書き方</a>、<a href="https://www.amazon.co.jp/Team-Geek-%E2%80%95Google%E3%81%AE%E3%82%AE%E3%83%BC%E3%82%AF%E3%81%9F%E3%81%A1%E3%81%AF%E3%81%84%E3%81%8B%E3%81%AB%E3%81%97%E3%81%A6%E3%83%81%E3%83%BC%E3%83%A0%E3%82%92%E4%BD%9C%E3%82%8B%E3%81%AE%E3%81%8B-Brian-Fitzpatrick/dp/4873116309">Team Geek</a>なども紹介しています。</p> <p>本記事では、Group Meetingおまけコーナーで紹介した書籍から、なるべく多くの社会人に紹介したい本をテーマに考えピックアップした書籍10選ということで書いていこうと思います。</p> <p>  </p> <h4 id="仕事に必要なことはすべて映画で学べる">仕事に必要なことはすべて映画で学べる</h4> <p>クリエイターとして、代えがたいユニークな創作の能力を仕事という場で最大限活かす、という方法論が学べる本です。2013年の書籍ですが、技術と事業が複雑に絡まる現代に刺さる一冊だと思います。 <div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B00GAS3EKO?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="sponsored noopener"><img src="https://m.media-amazon.com/images/I/51TjDweScFL._SL500_.jpg" class="hatena-asin-detail-image" alt="仕事に必要なことはすべて映画で学べる" title="仕事に必要なことはすべて映画で学べる"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B00GAS3EKO?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="sponsored noopener">仕事に必要なことはすべて映画で学べる</a></p><ul class="hatena-asin-detail-meta"><li><span class="hatena-asin-detail-label">作者:</span><a href="https://d.hatena.ne.jp/keyword/%B2%A1%B0%E6%A1%A1%BC%E9" class="keyword">押井 守</a></li><li>日経BP</li></ul><a href="https://www.amazon.co.jp/dp/B00GAS3EKO?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="sponsored noopener">Amazon</a></div></div></p> <p>  </p> <h4 id="熟達論---人はいつまでも学び成長できる">熟達論 - 人はいつまでも学び、成長できる</h4> <p>為末大氏の書籍です。スポーツにおいて「真のトップランナー」へ成長する過程を考察しています。エンジニアのキャリアに通づる所も多いですし、何かを突き詰める全ての成長論に通づる良書です。 <div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B0C6M66CH9?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="sponsored noopener"><img src="https://m.media-amazon.com/images/I/31JbjAjKKJL._SL500_.jpg" class="hatena-asin-detail-image" alt="熟達論―人はいつまでも学び、成長できる―" title="熟達論―人はいつまでも学び、成長できる―"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B0C6M66CH9?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="sponsored noopener">熟達論―人はいつまでも学び、成長できる―</a></p><ul class="hatena-asin-detail-meta"><li><span class="hatena-asin-detail-label">作者:</span><a href="https://d.hatena.ne.jp/keyword/%B0%D9%CB%F6%C2%E7" class="keyword">為末大</a></li><li>新潮社</li></ul><a href="https://www.amazon.co.jp/dp/B0C6M66CH9?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="sponsored noopener">Amazon</a></div></div></p> <p>  </p> <h4 id="RANGEレンジ知識の幅が最強の武器になる">RANGE(レンジ)知識の「幅」が最強の武器になる</h4> <p>技術の幅を広げるべきか、一点突破で極めるべきか、そういった議論を深める一冊です。書籍の結論としては"幅"に依り過ぎている部分もありますが、タイガー・ウッズとフェデラーの対比など、各エピソードが面白いのでオススメです。 <div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B0868DR365?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="sponsored noopener"><img src="https://m.media-amazon.com/images/I/41X3WChUZFL._SL500_.jpg" class="hatena-asin-detail-image" alt="RANGE(レンジ) 知識の「幅」が最強の武器になる" title="RANGE(レンジ) 知識の「幅」が最強の武器になる"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B0868DR365?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="sponsored noopener">RANGE(レンジ) 知識の「幅」が最強の武器になる</a></p><ul class="hatena-asin-detail-meta"><li><span class="hatena-asin-detail-label">作者:</span><a href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A4%A5%D3%A5%C3%A5%C9%A1%A6%A5%A8%A5%D7%A5%B9%A5%BF%A5%A4%A5%F3" class="keyword">デイビッド・エプスタイン</a></li><li>日経BP</li></ul><a href="https://www.amazon.co.jp/dp/B0868DR365?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="sponsored noopener">Amazon</a></div></div></p> <p>  </p> <h4 id="データセキュリティ法の迷走">データセキュリティ法の迷走</h4> <p>法律家から見た時のセキュリティのお話が書かれた書籍です。技術的に全てが最新とはいかないのですが、一方で技術視点だけでは解消できないセキュリティの課題を詳しく書いてくれています。技術者の考え方が広がる良い書籍だと思います。 <div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B0CQ23S2PZ?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="sponsored noopener"><img src="https://m.media-amazon.com/images/I/413vBvXBiIL._SL500_.jpg" class="hatena-asin-detail-image" alt="データセキュリティ法の迷走" title="データセキュリティ法の迷走"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B0CQ23S2PZ?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="sponsored noopener">データセキュリティ法の迷走</a></p><ul class="hatena-asin-detail-meta"><li><span class="hatena-asin-detail-label">作者:</span><a href="https://d.hatena.ne.jp/keyword/%A5%C0%A5%CB%A5%A8%A5%EB%A1%A6J%A1%A6%A5%BD%A5%ED%A5%D6" class="keyword">ダニエル・J・ソロブ</a>,<a href="https://d.hatena.ne.jp/keyword/%A5%A6%A5%C3%A5%C9%A5%ED%A5%A6%A1%A6%A5%CF%A1%BC%A5%C4%A5%A9%A5%B0" class="keyword">ウッドロウ・ハーツォグ</a>,<a href="https://d.hatena.ne.jp/keyword/%BE%AE%B8%FE%C2%C0%CF%BA" class="keyword">小向太郎</a></li><li>勁草書房</li></ul><a href="https://www.amazon.co.jp/dp/B0CQ23S2PZ?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="sponsored noopener">Amazon</a></div></div></p> <p>  </p> <h4 id="テクノロジーの世界経済史-ビルゲイツのパラドックス">テクノロジーの世界経済史 ビル・ゲイツのパラドックス</h4> <p>『イノベーションがこれまでにないペースで次々に出現しているというのに‥アメリカ人は将来についてますます悲観的になっている』という序章から始まる本です。まさに現代って感じですよね。その中でエンジニアとしてどう生きるか、改めて考える一冊になると思います。 <div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B08JCGNK7J?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="sponsored noopener"><img src="https://m.media-amazon.com/images/I/41T4WtV5icL._SL500_.jpg" class="hatena-asin-detail-image" alt="テクノロジーの世界経済史 ビル・ゲイツのパラドックス" title="テクノロジーの世界経済史 ビル・ゲイツのパラドックス"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B08JCGNK7J?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="sponsored noopener">テクノロジーの世界経済史 ビル・ゲイツのパラドックス</a></p><ul class="hatena-asin-detail-meta"><li><span class="hatena-asin-detail-label">作者:</span><a href="https://d.hatena.ne.jp/keyword/%A5%AB%A1%BC%A5%EB%A1%A6B%A1%A6%A5%D5%A5%EC%A5%A4" class="keyword">カール・B・フレイ</a></li><li>日経BP</li></ul><a href="https://www.amazon.co.jp/dp/B08JCGNK7J?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="sponsored noopener">Amazon</a></div></div></p> <p>  </p> <h4 id="ビジュアルシンカーの脳-絵で考える人々の世界">ビジュアル・シンカーの脳: 「絵」で考える人々の世界</h4> <p>何事も論理的に整理して、というのが社会では求められやすい世の中ですが、ビジュアルで捉える人も居るよという本です。社内でも「こういう時に感覚で考えるかも」と、論理以外の思考法に対するリスペクト話が盛り上がりました。 <div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B0CCNY7ZSX?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="sponsored noopener"><img src="https://m.media-amazon.com/images/I/51+dV+ihSyL._SL500_.jpg" class="hatena-asin-detail-image" alt="ビジュアル・シンカーの脳 「絵」で考える人々の世界" title="ビジュアル・シンカーの脳 「絵」で考える人々の世界"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B0CCNY7ZSX?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="sponsored noopener">ビジュアル・シンカーの脳 「絵」で考える人々の世界</a></p><ul class="hatena-asin-detail-meta"><li><span class="hatena-asin-detail-label">作者:</span><a href="https://d.hatena.ne.jp/keyword/%A5%C6%A5%F3%A5%D7%A5%EB%A1%A6%A5%B0%A5%E9%A5%F3%A5%C7%A5%A3%A5%F3" class="keyword">テンプル・グランディン</a></li><li>NHK出版</li></ul><a href="https://www.amazon.co.jp/dp/B0CCNY7ZSX?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="sponsored noopener">Amazon</a></div></div></p> <p>  </p> <h4 id="テクノロジストの条件">テクノロジストの条件</h4> <p>古い書籍ですが、ドラッカーの名著です。技術と知識を伴い自ら手を動かすエンジニアとしての在り方と、未知なるものを体系化しイノベーションを起こす方法について説かれています。 <div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B0081M7XZ4?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="sponsored noopener"><img src="https://m.media-amazon.com/images/I/51WVtClaR5L._SL500_.jpg" class="hatena-asin-detail-image" alt="テクノロジストの条件 はじめて読むドラッカー (技術編)" title="テクノロジストの条件 はじめて読むドラッカー (技術編)"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B0081M7XZ4?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="sponsored noopener">テクノロジストの条件 はじめて読むドラッカー (技術編)</a></p><ul class="hatena-asin-detail-meta"><li><span class="hatena-asin-detail-label">作者:</span><a href="https://d.hatena.ne.jp/keyword/%A3%D0%20%A3%C6%20%A5%C9%A5%E9%A5%C3%A5%AB%A1%BC" class="keyword">P F ドラッカー</a></li><li>ダイヤモンド社</li></ul><a href="https://www.amazon.co.jp/dp/B0081M7XZ4?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="sponsored noopener">Amazon</a></div></div></p> <p>  </p> <h4 id="ネガティブケイパビリティ-答えの出ない事態に耐える力">ネガティブ・ケイパビリティ 答えの出ない事態に耐える力</h4> <p>「答えの出ない事態に耐える力」というサブタイトルの通り、即時的な答えを出したい人の多い現代に必要な一冊だと思います。働いていてもプライベートでも起こり得る、「あちらを立てればこちらが立たず」な状況を上手く捉えようというお話が書いてあります。 <div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B0772WGXFS?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="sponsored noopener"><img src="https://m.media-amazon.com/images/I/51qt4nzHzEL._SL500_.jpg" class="hatena-asin-detail-image" alt="ネガティブ・ケイパビリティ 答えの出ない事態に耐える力 (朝日選書)" title="ネガティブ・ケイパビリティ 答えの出ない事態に耐える力 (朝日選書)"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B0772WGXFS?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="sponsored noopener">ネガティブ・ケイパビリティ 答えの出ない事態に耐える力 (朝日選書)</a></p><ul class="hatena-asin-detail-meta"><li><span class="hatena-asin-detail-label">作者:</span><a href="https://d.hatena.ne.jp/keyword/%D6%E4%CC%DA%A1%A1%CB%A9%C0%B8" class="keyword">帚木 蓬生</a></li><li>朝日新聞出版</li></ul><a href="https://www.amazon.co.jp/dp/B0772WGXFS?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="sponsored noopener">Amazon</a></div></div></p> <p>  </p> <h4 id="実力も運のうち-能力主義は正義か">実力も運のうち 能力主義は正義か?</h4> <p>社会学寄りの書籍ですが、私達のものづくりの前提となっている社会の形について考えられる大切な一冊です。私達が愛してやまない技術を使う先の事を考える、そのきっかけになればなと思います。 <div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B0CGTVP2TT?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="sponsored noopener"><img src="https://m.media-amazon.com/images/I/41FabmBcraL._SL500_.jpg" class="hatena-asin-detail-image" alt="実力も運のうち 能力主義は正義か? (ハヤカワ文庫NF)" title="実力も運のうち 能力主義は正義か? (ハヤカワ文庫NF)"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B0CGTVP2TT?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="sponsored noopener">実力も運のうち 能力主義は正義か? (ハヤカワ文庫NF)</a></p><ul class="hatena-asin-detail-meta"><li><span class="hatena-asin-detail-label">作者:</span><a href="https://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%B1%A5%EB%20%A5%B5%A5%F3%A5%C7%A5%EB" class="keyword">マイケル サンデル</a></li><li>早川書房</li></ul><a href="https://www.amazon.co.jp/dp/B0CGTVP2TT?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="sponsored noopener">Amazon</a></div></div></p> <p>  </p> <h4 id="1兆ドルコーチ">1兆ドルコーチ</h4> <p>最後は、心にガツンと来る一冊です。あなたがやらなければ何も変わらない、ただそれだけの事を丁寧に伝えてくれます。私も年末読み返したい至極の一冊です。 <div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B07ZCY5BXF?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="sponsored noopener"><img src="https://m.media-amazon.com/images/I/51ONEF5axHL._SL500_.jpg" class="hatena-asin-detail-image" alt="1兆ドルコーチ――シリコンバレーのレジェンド ビル・キャンベルの成功の教え" title="1兆ドルコーチ――シリコンバレーのレジェンド ビル・キャンベルの成功の教え"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B07ZCY5BXF?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="sponsored noopener">1兆ドルコーチ――シリコンバレーのレジェンド ビル・キャンベルの成功の教え</a></p><ul class="hatena-asin-detail-meta"><li><span class="hatena-asin-detail-label">作者:</span><a href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%EA%A5%C3%A5%AF%A1%A6%A5%B7%A5%E5%A5%DF%A5%C3%A5%C8" class="keyword">エリック・シュミット</a>,<a href="https://d.hatena.ne.jp/keyword/%A5%B8%A5%E7%A5%CA%A5%B5%A5%F3%A1%A6%A5%ED%A1%BC%A5%BC%A5%F3%A5%D0%A1%BC%A5%B0" class="keyword">ジョナサン・ローゼンバーグ</a>,<a href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%E9%A5%F3%A1%A6%A5%A4%A1%BC%A5%B0%A5%EB" class="keyword">アラン・イーグル</a></li><li>ダイヤモンド社</li></ul><a href="https://www.amazon.co.jp/dp/B07ZCY5BXF?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="sponsored noopener">Amazon</a></div></div></p> <p>  </p> <h1 id="おわりに">おわりに</h1> <p>いかがでしたでしょうか。</p> <p>これで、今年もエンジニアの皆さんの積読を増やすことができました。 来年もEngineering Group Meetingを通して「おまけコンテンツ」で1記事書けるよう、私も年末年始に積読を消化していきたいと思っています。</p> <p>積読消化でよいお年を過ごしていきましょう。 やっていき!</p> <h2 id="We-are-hiring-">We are hiring !!</h2> <p>エムスリーでは、書籍も技術も大好きな意欲のあるエンジニアを募集しています。 Engineering Group Meetingのおまけを楽しめるのもエムスリーだけ!</p> <p>新卒・中途それぞれの採用だけでなく、カジュアル面談やインターンも常時募集していますのでよろしくお願いします!</p> <h3 id="エンジニア採用ページはこちら">エンジニア採用ページはこちら</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/">jobs.m3.com</a></cite></p> <h3 id="カジュアル面談もお気軽にどうぞ">カジュアル面談もお気軽にどうぞ</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2Fcasual-session%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/casual-session/">jobs.m3.com</a></cite></p> <h3 id="インターンも常時募集しています">インターンも常時募集しています</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fopen.talentio.com%2Fr%2F1%2Fc%2Fm3%2Fhomes%2F4354" title="【新卒】エンジニアリンググループ 募集一覧 / エムスリー株式会社" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://open.talentio.com/r/1/c/m3/homes/4354">open.talentio.com</a></cite></p> vaaaaaanquish DBテーブルのカラムを削除するときにやること・考えること hatenablog://entry/6802418398313995595 2024-12-26T00:00:00+09:00 2024-12-26T14:50:33+09:00 突然ですが皆様は、稼働済みサービスのDBからテーブルカラムを削除されたりすること、ありますでしょうか? 基本的に削除はまずやらないのではと思います。えっやらないの? と思われた方もいらっしゃるかもしれませんが、きっとこの記事を読めばなぜ多くの方がカラム削除を避けるのかわかることでしょう。 とはいえ、そうして使わないカラムがテーブルに溜まっていくとやがて新規加入メンバーがコードにキャッチアップする妨げとなるレベルにまで溜まってきたりします。いつかは大掃除のときがくるわけです。DBは寿命長いですからね。そうしたときに実際どのような手順でカラムを削除するのか見ていきましょう。エムスリーエンジニアリン… <p>突然ですが皆様は、稼働済みサービスのDBからテーブルカラムを削除されたりすること、ありますでしょうか? 基本的に削除はまずやらないのではと思います。えっやらないの? と思われた方もいらっしゃるかもしれませんが、きっとこの記事を読めばなぜ多くの方がカラム削除を避けるのかわかることでしょう。</p> <p>とはいえ、そうして使わないカラムがテーブルに溜まっていくとやがて新規加入メンバーがコードにキャッチアップする妨げとなるレベルにまで溜まってきたりします。いつかは大掃除のときがくるわけです。DBは寿命長いですからね。そうしたときに実際どのような手順でカラムを削除するのか見ていきましょう。エムスリーエンジニアリンググループUnit1(製薬プロモーション)・Unit9(治験臨床研究支援)チームエンジニアの三浦 [<a href="https://www.m3tech.blog/archive/author/Sampo" target="_blank">記事一覧 <span style="font-family:blogicon"></span></a>]が、最近実際にやった作業から知見をお届けします。</p> <ul class="table-of-contents"> <li><a href="#長いので3行で">長いので3行で</a></li> <li><a href="#考え方">考え方</a></li> <li><a href="#1-そのカラムへのアプリケーション外からのアクセスをなくす">1. そのカラムへのアプリケーション外からのアクセスをなくす</a></li> <li><a href="#2-カラムをNULL許容にする">2. カラムをNULL許容にする</a></li> <li><a href="#3-アプリケーション側でそのカラムへのアクセスをなくしリリース">3. アプリケーション側でそのカラムへのアクセスをなくし、リリース</a></li> <li><a href="#4-カラムをDROP">4. カラムをDROP</a></li> <li><a href="#他のケース不要なテーブルの削除">他のケース:不要なテーブルの削除</a></li> <li><a href="#他のケースカラムの改名">他のケース:カラムの改名</a></li> <li><a href="#まとめ">まとめ</a></li> <li><a href="#We-are-hiring">We are hiring</a></li> </ul> <h3 id="長いので3行で">長いので3行で</h3> <ol> <li>そのカラムへのアプリケーション外からのアクセスをなくす</li> <li>カラムをNULL許容にする</li> <li>アプリケーション側でそのカラムへのアクセスをなくし、リリース</li> <li>カラムをDROP</li> </ol> <p>すみません4行になってしまいました。でもこれ以上は減らせません。</p> <h3 id="考え方">考え方</h3> <p>最も大事な点は稼働中のサービスがリリース前後で無用にエラーを起こさないようにということになるのですが、それに加えて、開発・QA環境のことですね。別にフィーチャーブランチを切って開発やQAの作業をしている人々が詰まないことも重要な観点となります。そして忘れてはいけない、データ分析をしている人々への目配りが三つ目ですね。</p> <p>さてまず、カラム削除をアプリケーションとDBどちらから先にやるかという議論ができます。両者を厳密に「同時」にリリースはできませんからね。どちらを先にしましょう、もし先にDBからカラムを削除すると、アプリケーションがエラーを出し始めるのは容易に想像がつきます。では、アプリケーションが先か。</p> <p>先にアプリケーション側からそのカラムへの参照をなくしてリリースすればあとは落ち着いてDBからカラムを削除できる、はい、その発想でほぼ間違いありません。ただ、それだけの順番で行くとアプリケーション側リリースと同時に大量のアラートが飛び始める可能性があります。何がいけなかったでしょう。</p> <p> ︙</p> <p>INSERT文が落ちるようになったのです。アプリケーション側でカラムをまったく参照しなくなれば、INSERTするのに必要なカラムデータも足りず軒並みエラーというわけ。つまりアプリケーション側からカラム参照を削除するのに先立って、「それなしでもINSERTが通る」ようにしておかなくてはならなかったのです。</p> <p>この、INSERT文を通るように→アプリケーション側から削除→DBから削除の3段階をサービス本体での修正とすると、これにアプリケーション外からのアクセスへの考慮を入れて前述の4段階となります。</p> <h3 id="1-そのカラムへのアプリケーション外からのアクセスをなくす">1. そのカラムへのアプリケーション外からのアクセスをなくす</h3> <p>事業として提供しているサービスならば、多かれ少なかれその利用実績・実態をデータ分析したり集計したりするニーズがあるはずです。すなわち、アプリケーションDBを分析用DBに取り込んで加工しているチームがあるはずです。アプリケーション本体の開発に集中しているとついそうしたチームへの目配りを忘れてしまい、新機能リリースと同時にデータ分析チーム方面から悲鳴が上がってしまったなんて経験をお持ちの方も少なくないのでは。</p> <p>ですから定石としていつもステップを踏むようにしましょう、DBスキーマ変更の最初の一手はデータ分析チーム相談。特に何かを削除するような確実にエラーを起こす変更ならば絶対にはずせない事項となります。</p> <p>データ分析チームもその下流での影響がすぐに読み切れるとは限らず、データマートからも無邪気にそのカラムを消してしまえるとは限りません。まずは保守的に、次のように取り込みSQLで無意味データを取り込むように変更などすることになるでしょう。</p> <pre class="code lang-sql" data-lang="sql" data-unlink> <span class="synStatement">SELECT</span> id, tenant_id, name, - billing_start_date, + <span class="synType">DATE</span><span class="synSpecial">'</span><span class="synConstant">1970-01-01</span><span class="synSpecial">'</span> <span class="synSpecial">AS</span> billing_start_date, - billing_end_date + <span class="synType">DATE</span><span class="synSpecial">'</span><span class="synConstant">2200-12-31</span><span class="synSpecial">'</span> <span class="synSpecial">AS</span> billing_end_date <span class="synSpecial">FROM</span> users </pre> <p>NULL非許容だったbilling_start_date, billing_end_dateを削除するというストーリーですが、下流影響を防ぐためNULLではなく固定値を読み込むようにする変更をしている例です<a href="#f-d0a2aa94" id="fn-d0a2aa94" name="fn-d0a2aa94" title="遠い未来という意図で2200年というわりあい近い年月を採用しているのが気になる方もいらっしゃるかもしれません。しかしデータ分析分野だとよく使われるPythonライブラリのpandasが日付として最大2262-04-11までしか扱えないという問題があり、それに引っかからない切りの良いい年号というとこのあたりになるなどといった事情があります。175年後ですが、175年前はまだ江戸時代ですからこれでも相当遠い未来ですよね">*1</a>。</p> <h3 id="2-カラムをNULL許容にする">2. カラムをNULL許容にする</h3> <p>そのカラムを指定しなくてもINSERT文が通るようにするということです。ということはNULL許容にするのでなくDEFAULTを設定するというのも別解になります。ただこれから消すカラムに値を持たせる必要もそうありませんから、NULL許容にしておくので十分でしょう。本来NULL非許容でデータマートにも必ず値が取り込まれてきたのだとしても、1.の段階でちゃんとダミー値を取ってくれるようになりましたから心置きなくNULL許容にできますね。</p> <p>カラムがもともとNULL許容だったらこのステップはパスです。おめでとうございます。</p> <h3 id="3-アプリケーション側でそのカラムへのアクセスをなくしリリース">3. アプリケーション側でそのカラムへのアクセスをなくし、リリース</h3> <p>アプリケーションから先にカラム参照をなくしましょうというのは総論でお話しましたが、ここであえてさらに「いったんリリースしてしまいましょう」というのも付け加えます。つまり同日にDBからのカラム削除までやってしまわずに別の機会にしましょうということと。その理由が、他のメンバーの開発・QAを止めないようにという観点です。</p> <p>アプリケーションからもDBからもカラムを削除するという修正、これをリリースする前にQAするには、QA環境でDBからカラム削除することになりますよね。すると、他のフィーチャーブランチがQA環境でエラーを出してしまうようになります。これを防ごうとするとどのような手順を踏まないといけないかというと</p> <ol> <li>アプリケーションからカラム参照を削除する修正を書く→QA→リリース<br/> (この間、DBスキーマに変更は加わらないので他のフィーチャーブランチも問題なく動く)</li> <li>各フィーチャーブランチで本流の最新をマージなりリベースなりしてもらう<br/> (各ブランチとも、DBからカラムがいつ消えても動く状態になった!)</li> <li>DBからカラムを削除するマイグレーションをQA→リリース<br/> (QAする段階でまずQA用DBからカラムが消えるが、各フィーチャーブランチは問題ない)</li> </ol> <p>二人以上のエンジニアがいるなら、アプリケーションからとDBからカラムを削除するのは別のリリース機会に分けたほうがいいとなります。最初に書いた4手順のうちこの3番目で、文末の「リリース」というのにはチームの皆に本流マージをしてもらうというステップも含まれていたことになりますね。</p> <h3 id="4-カラムをDROP">4. カラムをDROP</h3> <p>もはやあなたを阻むものはありません。何年もかけて溜まったいらないカラム、すっきり削除しましょう。</p> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synStatement">ALTER</span> <span class="synSpecial">TABLE</span> users <span class="synStatement">DROP</span> <span class="synSpecial">COLUMN</span> billing_start_date; <span class="synStatement">ALTER</span> <span class="synSpecial">TABLE</span> users <span class="synStatement">DROP</span> <span class="synSpecial">COLUMN</span> billing_end_date; </pre> <p>おつかれさまでした。</p> <h3 id="他のケース不要なテーブルの削除">他のケース:不要なテーブルの削除</h3> <p>テーブル削除の場合には、INSERT文が通らなくなる問題がありません。よって少し手順は簡単、今度は本当に3行で書けます。</p> <ol> <li>そのカラムへのアプリケーション外からのアクセスをなくす</li> <li>アプリケーション側でそのテーブルへのアクセスをなくし、リリース</li> <li>テーブルをDROP</li> </ol> <h3 id="他のケースカラムの改名">他のケース:カラムの改名</h3> <p>名前が実態を表さなくなってしまったので改名したいというニーズがたまに生じるのはその通りです。しかし⋯⋯改名は難しいです! もしどうしてもやりたいとしても、新しいカラムを作って使うようにして古いカラムは使うのをやめる(そしていつか大掃除のときに⋯⋯)とするしかないでしょう。もちろんそれだって簡単ではなく、</p> <ol> <li>新しいカラムをNULL許容で作る</li> <li>アプリケーションの更新系で両方のカラムに書き込むようにし、リリース(上の例と同じく、リリースとは各フィーチャーブランチにも取り込むことまでを意味します)</li> <li>古いカラムから新しいカラムへデータをコピーするDMLをDBで流す</li> <li>この時点で、NULL非許容にする必要性があればそうできる</li> <li>アプリケーション外からのアクセスを新しいカラムへ変更</li> <li>アプリケーションの参照系を新しいカラムへ変更し、リリース</li> <li>アプリケーションの更新系で古いカラムへの書き込みはやめる(NULL非許容なら無意味データを書き込むようにする)</li> </ol> <p>かような一大作業となります。アプリケーションプログラム内で変数名やフィールド名を変えるリファクタリングとは桁違いの大変さです。</p> <h3 id="まとめ">まとめ</h3> <p>おまけも含めてこのようなやることリストをご覧に入れたのですが、スキーマ変更には限りなくパターンがありますから一記事では本来とても網羅できるものではありません。そこでそのときどきでこうした実施プランを立てられるよう、ここまででプランを立てるのに考えていたことは何だったのかを振り返ってみましょう。</p> <p>急に人間臭い話になってしまいますが、他者への目配りが出発点となっていたと思います。データ分析チームを、他に開発・QAしている人を、また参照系は更新系を、更新系は参照系をといったように他の人達、他のユースケースたちが詰まないかを各ステップで考えることで悲鳴の上がらないスキーマ変更を構成しようとしてきたのです。</p> <p>もちろん、人間の想像力には限りがありますから、想像できなかったところまでは目が配れません。そうして悲鳴が上がることになってしまったら振り返りをして、そうすればもっと広く目が配れるようになります。次のスキーマ変更はきっともっとうまくいきます。</p> <h3 id="We-are-hiring">We are hiring</h3> <p>影響範囲に目を配って、それでも悲鳴を上げさせてしまったら振り返ってもっと目を広く配れるようにする⋯⋯このサイクルはスキーマ変更作業に限らないこと、すべての開発分野でエンジニアを成長させてくれることです。私達エムスリーのエンジニアはすべきことを自分とチームで考えて開発していますので、それだけ影響範囲を考えたりも、ときには影響を読み落としてしまったりも日々しています。そういう成長サイクルの回っている環境、ご興味あったらこちらのページからどうぞ。応募を前提にしないカジュアル面談もやっています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/">jobs.m3.com</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-d0a2aa94" id="f-d0a2aa94" name="f-d0a2aa94" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">遠い未来という意図で2200年というわりあい近い年月を採用しているのが気になる方もいらっしゃるかもしれません。しかしデータ分析分野だとよく使われるPythonライブラリのpandasが日付として最大2262-04-11までしか扱えないという問題があり、それに引っかからない切りの良いい年号というとこのあたりになるなどといった事情があります。175年後ですが、175年前はまだ江戸時代ですからこれでも相当遠い未来ですよね</span></p> </div> Sampo 機械学習エンジニアやデータサイエンティストにもウケるノベルティが作りたい hatenablog://entry/6802418398311775894 2024-12-25T09:00:00+09:00 2024-12-25T09:00:12+09:00 ギークな技術者の皆様、こんにちは。メリークリスマス。 リモートワークで運動不足を感じているため、VTuberのリングフィットアドベンチャーのプレイ動画を見て気を紛らわしています、VPoEの河合(@vaaaaanquish)です。 本記事はエムスリーアドベントカレンダー2024の最終日の記事にあたります。 エムスリーアドベントカレンダーには今年もギークな記事が揃っていますので、年末年始の技術研鑽のお供に是非どうぞ。 qiita.com はじめに ノベルティの下準備 digits plot wordcloud おわりに We are hiring !! エンジニア採用ページはこちら カジュアル面談… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241225/20241225090101.png" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>ギークな技術者の皆様、こんにちは。メリークリスマス。<br/> リモートワークで運動不足を感じているため、VTuberのリングフィットアドベンチャーのプレイ動画を見て気を紛らわしています、VPoEの河合(<a href="https://x.com/vaaaaanquish">@vaaaaanquish</a>)です。</p> <p>本記事は<a href="https://qiita.com/advent-calendar/2024/m3">エムスリーアドベントカレンダー2024</a>の最終日の記事にあたります。<br/> エムスリーアドベントカレンダーには今年もギークな記事が揃っていますので、年末年始の技術研鑽のお供に是非どうぞ。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fadvent-calendar%2F2024%2Fm3" title="エムスリー - Qiita Advent Calendar 2024 - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://qiita.com/advent-calendar/2024/m3">qiita.com</a></cite></p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#ノベルティの下準備">ノベルティの下準備</a></li> <li><a href="#digits">digits</a></li> <li><a href="#plot">plot</a></li> <li><a href="#wordcloud">wordcloud</a></li> <li><a href="#おわりに">おわりに</a><ul> <li><a href="#We-are-hiring-">We are hiring !!</a><ul> <li><a href="#エンジニア採用ページはこちら">エンジニア採用ページはこちら</a></li> <li><a href="#カジュアル面談もお気軽にどうぞ">カジュアル面談もお気軽にどうぞ</a></li> <li><a href="#インターンも常時募集しています">インターンも常時募集しています</a></li> </ul> </li> </ul> </li> </ul> <p> </p> <h1 id="はじめに">はじめに</h1> <p>私事なのですが、今年はエンジニアの採用活動を頑張りました。</p> <p>ギークなファンを増やすという観点では、<a href="https://x.com/m3_engineering">@m3_engineering</a>のフォロワーがめでたく1万人を超えました。やったー!🎉</p> <p>その活動の一環として定期的に難読クイズを作成、発信していたのですが、結果として今や社内に100以上のプログラミングクイズが存在しているらしいです(笑)。 その一部は、技術カンファレンスや技術書典でも公開しており、目にした事がある方も少なくないかなと思います。</p> <p><figure class="figure-image figure-image-fotolife" title="サポーターズ主催の技育アカデミアでの発表資料より"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241225/20241225090015.jpg" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" style="width:500px" itemprop="image"></span><figcaption>サポーターズ主催の技育アカデミアでの発表資料より</figcaption></figure></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.m3tech.blog%2Fentry%2Ftechbook7" title="エムスリーが技術書典17で新刊を出します! - エムスリーテックブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.m3tech.blog/entry/techbook7">www.m3tech.blog</a></cite></p> <p>他にも、エムスリーを紹介するコンテンツとして、200ページ超えの全チーム紹介スライドを作成しました。 エムスリーの大きな企業感と、各チームのスタートアップ感の両方が伝わるよう作っているので、こちらも年末のお供に是非どうぞ。</p> <iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/353bdaef98bc4b45bc2c13e7b2866ebb" title="エムスリー全チーム紹介資料 / Introduction of M3 All Teams" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 315;" data-ratio="1.7777777777777777"></iframe> <p>実際の採用においては、GAFA出身者を筆頭に、メガベンチャーやスタートアップ、大手SIerから続々とギークなエンジニアが入ってきてくれています。 技術で医療に挑戦する仲間が増えることは、兎にも角にも嬉しい事です。</p> <p> </p> <p>そういった採用活動の中でも、今年一番の推しコンテンツが『<strong>Quineクリアファイル</strong>』です。 Quineと呼ばれる、実行すると実際に動作するコードが返ってくるコードをベースに、M3ロゴを表現したノベルティです。 かなり人気のノベルティで、多くの方から「ギークな心意気が伝わる!」「◯◯言語も作って!」と絶賛の声を頂いています。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241225/20241225090057.png" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" style="width:400px" itemprop="image"></span> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.m3tech.blog%2Fentry%2Fpython_quine" title="エムスリーが難読プログラミングオタクに送るノベルティ、Python Quineクリアファイルの作り方 - エムスリーテックブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.m3tech.blog/entry/python_quine">www.m3tech.blog</a></cite></p> <p>なんと、謎の力で動くgifバージョンも以下記事内にあったりします。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.m3tech.blog%2Fentry%2F2024%2F11%2F07%2F110000" title="エムスリー福岡Quineを作りました! - エムスリーテックブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.m3tech.blog/entry/2024/11/07/110000">www.m3tech.blog</a></cite></p> <p>もらった書類やチラシ等も挟んで使えますし、コードが書いてあるので普段使いの中でもハッカー気分を味わえます。 私も愛用していて、4歳の娘がたまにくれるお手紙や折り紙を大事に挟んでいます。</p> <p>そんな好調のQuineクリアファイルなのですが、先日技術関連のイベントでクリアファイルを配っていたところ、他社の機械学習系のリサーチエンジニアの方から「<strong>素晴らしい!良い発想だし真似したい!でもうちはリサーチ系の会社だからコードが書いてあっても興奮する人が少ないかもな…</strong>」という相談を受けました。</p> <p>「<strong>たしかに</strong>」</p> <p>そう感じた、全てのギークでスマートなエンジニアと働きたい私は、急いでスクリプトを書き始めたのでした。</p> <p> </p> <h1 id="ノベルティの下準備">ノベルティの下準備</h1> <p>まず、下準備として機械学習リサーチでは必須のツールである<code>matplotlib</code>を使って文字を描画していきます。 ノベルティには社名やロゴを表記したい訳ですが、やはり機械学習エンジニアやデータサイエンティストが使い慣れたツールで作ってこそだと思うからです。 それに、会社名の出てないノベルティを作っても誰も解釈できないですからね<a href="#f-e9a7c20e" id="fn-e9a7c20e" name="fn-e9a7c20e" title="かいしゃだけに">*1</a>。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> PIL <span class="synPreProc">import</span> Image, ImageFont, ImageDraw, ImageOps <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">from</span> matplotlib <span class="synPreProc">import</span> pyplot <span class="synStatement">as</span> plt <span class="synPreProc">from</span> matplotlib.ticker <span class="synPreProc">import</span> AutoMinorLocator <span class="synPreProc">import</span> matplotlib_fontja message = <span class="synConstant">'M3'</span> <span class="synComment"># 描画するテキスト</span> font_size = <span class="synConstant">2000</span> <span class="synComment"># フォントサイズ</span> margin = <span class="synConstant">200</span> <span class="synComment"># 文字周辺を囲うマージン</span> <span class="synComment"># 一旦大きく描画</span> font = ImageFont.truetype(matplotlib_fontja.get_font_ttf_path(), font_size) plot_size=<span class="synIdentifier">len</span>(message)*font_size img_array = np.zeros((plot_size, plot_size, <span class="synConstant">3</span>), np.uint8) img = Image.fromarray(img_array) draw = ImageDraw.Draw(img) <span class="synComment"># 文字描画後のサイズを取得してcut</span> text_bbox = draw.textbbox((<span class="synConstant">0</span>,<span class="synConstant">0</span>), message, font=font) img_array = img_array[<span class="synConstant">0</span>:text_bbox[<span class="synConstant">3</span>]+text_bbox[<span class="synConstant">1</span>],<span class="synConstant">0</span>:text_bbox[<span class="synConstant">2</span>]] <span class="synComment"># 文字描画</span> img = Image.fromarray(img_array) draw = ImageDraw.Draw(img) draw.text((<span class="synConstant">0</span>, <span class="synConstant">0</span>), message, font=font, fill=(<span class="synConstant">255</span>, <span class="synConstant">255</span>, <span class="synConstant">255</span>, <span class="synConstant">0</span>)) <span class="synComment"># marginを付与</span> img=ImageOps.expand(img, border=margin) <span class="synComment"># 画像をプロット</span> fig = plt.figure() ax1 = fig.add_subplot(<span class="synConstant">111</span>) ax1.imshow(np.array(img)) ax1.axis(<span class="synConstant">&quot;off&quot;</span>) plt.show() </pre> <p><figure class="figure-image figure-image-fotolife" title="描画された文字画像"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241225/20241225090018.png" width="515" height="371" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>描画された文字画像</figcaption></figure></p> <p>これでまずは下準備ができました。 <code>matplotlib_fontja</code>の前身である<code>japanize-matplotlib</code>にフォントのPathを取得するメソッドを生やしたのは実は私なので、なんとなく感慨深い画像です。</p> <p>以降は、ここで作った<code>PIL</code>の<code>img</code>を上手く使っていきます。  </p> <h1 id="digits">digits</h1> <p>機械学習エンジニアやデータサイエンティストが最初に扱うデータセットといえば、<code>sklearn.datasets</code>でしょう。 中でも画像系の機械学習モデルの処理を学ぶ上で必ず通る、定番中の定番データセットが<code>digits</code>です。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> sklearn <span class="synPreProc">import</span> datasets digits = datasets.load_digits() plt.imshow(digits.images[<span class="synConstant">0</span>]) <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">15</span>): plt.subplot(<span class="synConstant">3</span>,<span class="synConstant">5</span>,i+<span class="synConstant">1</span>) plt.axis(<span class="synConstant">&quot;off&quot;</span>) plt.title(<span class="synIdentifier">str</span>(digits.target[i])) plt.imshow(digits.images[i],cmap=<span class="synConstant">&quot;gray&quot;</span>) </pre> <p><figure class="figure-image figure-image-fotolife" title="親の顔より見たdigits"><div class="images-row mceNonEditable"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241225/20241225090021.png" width="408" height="412" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241225/20241225090023.png" width="516" height="387" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></div><figcaption>親の顔より見たdigits</figcaption></figure></p> <p>そこで、Image Recognitionの専門家が生き生きしだす、<code>digits</code>の描画を思い返すような画像を作ってみます。 先のロゴ画像生成スクリプトの<code>font_size</code>を極限まで小さくして、画像全体を荒くして生成しなおします。</p> <p><figure class="figure-image figure-image-fotolife" title="font_size=12,margin=0で生成したロゴ画像"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241225/20241225090026.png" width="515" height="378" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>font_size=12,margin=0で生成したロゴ画像</figcaption></figure></p> <p>良い感じですね。 もうほぼ<code>digits</code>と言って差し支えないでしょう。 ですが、より「あの頃見た<code>digits</code>」に近付けるため、生成した画像を<code>0.~16.</code>の値に変換します。</p> <pre class="code lang-python" data-lang="python" data-unlink>plt.imshow(np.array(img.convert(<span class="synConstant">'L'</span>))//<span class="synConstant">16</span>) plt.show() </pre> <p><figure class="figure-image figure-image-fotolife" title="お、お前はあの時の…!"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241225/20241225090029.png" width="543" height="400" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>お、お前はあの時の…!</figcaption></figure></p> <p>完璧に「あの頃見た<code>digits</code>」ですね。</p> <p>あとはこれをTシャツなどに印刷してノベルティとしていけば、「解像度が低すぎてぼやけているのか、懐かしの涙でぼやけているのか、もはやわからない」といった声が聞こえてくる事間違いなしでしょう。</p> <p> </p> <h1 id="plot">plot</h1> <p>機械学習エンジニアやデータサイエンティストは、画像認識を専門とする方々だけでない事は重々承知しています。 Kaggleなどテーブルデータから入った人にも刺さる、そんなノベルティが必要そうです。</p> <p>テーブルデータといえばExplanatory Data Analysis (EDA)。 データの可視化をするフリをした画像を生成してみます。 ロゴ画像生成スクリプトで生成した画像をマスクとして、散布図を可視化をしてみます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> numpy.random <span class="synPreProc">import</span> randint size = <span class="synConstant">5000</span> mask = np.array(img.convert(<span class="synConstant">'L'</span>))==<span class="synConstant">0</span> data = np.array([(size-x,y) <span class="synStatement">for</span> (x,y) <span class="synStatement">in</span> <span class="synIdentifier">zip</span>( randint(<span class="synConstant">0</span>,mask.shape[<span class="synConstant">0</span>],size=size), randint(<span class="synConstant">0</span>,mask.shape[<span class="synConstant">1</span>],size=size)) <span class="synStatement">if</span> mask[x,y]]) plt.scatter(data[:,<span class="synConstant">1</span>], data[:,<span class="synConstant">0</span>]) </pre> <p><figure class="figure-image figure-image-fotolife" title="どこからどう見ても散布図です、本当にありがとうございました。"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241225/20241225090032.png" width="560" height="412" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>どこからどう見ても散布図です、本当にありがとうございました。</figcaption></figure></p> <p>どう見ても<code>plt.scatter</code>です。 EDAをしすぎて疲れた時、コーヒーを飲むマグカップに<code>plt.scatter</code>が描かれているイケてる空間を演出する、そんなノベルティが作れそうな画像です。</p> <p> </p> <p>とはいえ、これだけでは散布図感はまだ薄いです。 私は子どもの頃から「デカいものと光るものと動くものは格好良い」という教えで生きてきているため、動かしてみたいと思います。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> matplotlib.animation <span class="synPreProc">import</span> FuncAnimation, PillowWriter size = <span class="synConstant">5000</span> frames = <span class="synConstant">100</span> min_data = <span class="synConstant">10</span> mask = np.array(img.convert(<span class="synConstant">'L'</span>))==<span class="synConstant">0</span> data = np.array([(size-x,y) <span class="synStatement">for</span> (x,y) <span class="synStatement">in</span> <span class="synIdentifier">zip</span>( randint(<span class="synConstant">0</span>,mask.shape[<span class="synConstant">0</span>],size=size), randint(<span class="synConstant">0</span>,mask.shape[<span class="synConstant">1</span>],size=size)) <span class="synStatement">if</span> mask[x,y]]) fig, ax = plt.subplots() sc = ax.scatter(data[:min_data][:,<span class="synConstant">1</span>], data[:min_data][:,<span class="synConstant">0</span>]) <span class="synStatement">def</span> <span class="synIdentifier">update</span>(frame): frame=frame+<span class="synConstant">1</span> sc.set_offsets(np.c_[data[:min_data+(size//frames)*frame][:,<span class="synConstant">1</span>], data[:min_data+(size//frames)*frame][:,<span class="synConstant">0</span>]]) <span class="synStatement">return</span> sc, ani = FuncAnimation(fig, update, frames=frames, interval=<span class="synConstant">50</span>, blit=<span class="synIdentifier">True</span>) ani.save(<span class="synConstant">&quot;m3plot.gif&quot;</span>, writer=PillowWriter(fps=<span class="synConstant">10</span>)) </pre> <p><figure class="figure-image figure-image-fotolife" title="コイツ、動くぞ…!.gif"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241225/20241225090035.gif" width="640" height="480" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>コイツ、動くぞ…!.gif</figcaption></figure></p> <p>動きました。 最高です。 技術イベントのブース等で流せば、目を引く事間違いなしのアニメーションができました。 今回は<code>randint</code>で生成しましたが、右肩上がりの分布を作って勢いを表現したりする事が出来るかもしれません。 seabornを使ったりして、オシャレにしても良いかもしれませんね。 採用で関わったギークなエンジニアにメールで送ったりすれば、きっと喜ばれるギフトになるでしょう<a href="#f-55b0bb42" id="fn-55b0bb42" name="fn-55b0bb42" title="gifだけに">*2</a>。</p> <p>   </p> <h1 id="wordcloud">wordcloud</h1> <p>機械学習エンジニアやデータサイエンティストには、自然言語処理も重要なスキルです。 自然言語処理の可視化といえば、<code>wordcloud</code>が筆頭に挙がります。 Pythonの<code>wordcloud</code>モジュールには、画像でmaskする機能がありますので、そちらを使っていきましょう。</p> <p>テキストは自然言語処理で頑張って作っても良いのですが、令和といえば大LLM時代。 ChatGPTに以下のようなpromptを投げて単語リストを作ってもらいました。</p> <pre class="code text" data-lang="text" data-unlink>・エムスリー株式会社のエンジニアリンググループを単語1000個で表現して。重複は許します。単語はCSVでちょうだい。 ・エムスリー株式会社を単語1000個で表現して。重複は許します。単語はCSVでちょうだい。 ・エムスリー株式会社のエンジニアが使う技術を単語1000個で表現して。重複は許します。単語はCSVでちょうだい。</pre> <p>上記3つのpromptで生成したCSVを空白区切りの文字列にして、いざ生成です。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> wordcloud <span class="synPreProc">import</span> WordCloud wc = WordCloud( <span class="synComment"># contour_width=1,</span> max_font_size=<span class="synConstant">400</span>, font_path=matplotlib_fontja.get_font_ttf_path(), background_color=<span class="synConstant">&quot;white&quot;</span>, mask=np.array(img)) wc.generate(text) _, ax = plt.subplots(figsize=(<span class="synConstant">14</span>,<span class="synConstant">14</span>)) ax.imshow(wc) ax.axis(<span class="synConstant">&quot;off&quot;</span>) plt.show() </pre> <p><figure class="figure-image figure-image-fotolife" title="countour_widthあり/なしのWordCloud画像"><div class="images-row mceNonEditable"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241225/20241225090046.png" width="800" height="561" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241225/20241225090050.png" width="800" height="561" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></div><figcaption>countour_widthあり/なしのWordCloud画像</figcaption></figure></p> <p>良い感じですね。 随分昔なのですが、私はWordCloudの日本語のテキスト配置の処理を書いていた時期があります。 その頃から、日本語WordCloudには「フォントの関係で英語に比べて隙間が出来やすい」という課題があります。 自社のデザイナーと相談しながら、フォントや<code>max_font_size</code>を変更するなどしてノベルティで使える画像を作っていくのが良いのではないかなと思います。</p> <p>本当に良いフォントが見つかるといいですね<a href="#f-1c3524ab" id="fn-1c3524ab" name="fn-1c3524ab" title="フォントだけに">*3</a>。</p> <h1 id="おわりに">おわりに</h1> <p>というわけで、ノベルティをお題にいろいろな画像を作ってみましたというお話でした。 これで2025年もAI分野のイベントが盛り上がる事間違いなしですね!</p> <p><figure class="figure-image figure-image-fotolife" title="よいお年を!"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241225/20241225090054.png" width="543" height="189" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>よいお年を!</figcaption></figure></p> <p>さて、クリスマスに向けて進んできた<a href="https://qiita.com/advent-calendar/2024/m3">エムスリーアドベントカレンダー2024</a>はいかがでしたでしょうか? エムスリーはエンジニアが若干100名ちょっとながら、ギークでスマートなカルチャーで来年も大きく医療事業にチャレンジしていきます。</p> <p>皆様の応援あってこそだと思っておりますので、来年も何卒よろしくお願いします!</p> <h2 id="We-are-hiring-">We are hiring !!</h2> <p>今回はAI分野の中でも画像やテーブルデータ、自然言語処理を意識して記事を書いてみました。 エムスリーでは、それら以外のAI技術の活用はもちろんのこと、医師の9割が会員のプラットフォーム「m3.com」や日本No.1の電子カルテ「デジカル」、病院の待ち時間を0にするDXアプリ「デジスマ」など、多くのプロダクトを開発しています。</p> <p>一緒にチャレンジしてくれるギークでスマートな仲間を随時募集しています。 新卒も中途もカジュアル面談、インターンで私と握手! 2025年はエムスリーへ!</p> <h3 id="エンジニア採用ページはこちら">エンジニア採用ページはこちら</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/">jobs.m3.com</a></cite></p> <h3 id="カジュアル面談もお気軽にどうぞ">カジュアル面談もお気軽にどうぞ</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2Fcasual-session%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/casual-session/">jobs.m3.com</a></cite></p> <h3 id="インターンも常時募集しています">インターンも常時募集しています</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fopen.talentio.com%2Fr%2F1%2Fc%2Fm3%2Fhomes%2F4354" title="【新卒】エンジニアリンググループ 募集一覧 / エムスリー株式会社" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://open.talentio.com/r/1/c/m3/homes/4354">open.talentio.com</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-e9a7c20e" id="f-e9a7c20e" name="f-e9a7c20e" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">かいしゃだけに</span></p> <p class="footnote"><a href="#fn-55b0bb42" id="f-55b0bb42" name="f-55b0bb42" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">gifだけに</span></p> <p class="footnote"><a href="#fn-1c3524ab" id="f-1c3524ab" name="f-1c3524ab" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">フォントだけに</span></p> </div> vaaaaaanquish M3におけるGitHub Copilot活用状況 hatenablog://entry/6802418398314106228 2024-12-24T16:00:00+09:00 2024-12-26T04:21:19+09:00 北海道ツーリングにて訪れた野付半島 はじめに あっあっあー こんぺこ!こんぺこ!こんぺこー! 時期感的になんとなくこんな挨拶をしてみた @bloody_snow です。 M3エンジニアリンググループの2代目VPoEをつとめ、 @vaaaaanquish に交代してからは、 M3 Technologies にてM3のグループ会社をメインで担当しています。M3に関しては個別のサービス開発というよりかはGitHub Copilot、ChatGPT Team、GitHub Enterprise、比較的高性能なMacBookProの導入、AWSの教育受講など全体の生産性への取り組みが多くなっています。 … <p><figure class="figure-image figure-image-fotolife" title="北海道ツーリングにて訪れた野付半島"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241224/20241224160004.jpg" width="800" height="600" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>北海道ツーリングにて訪れた野付半島</figcaption></figure></p> <h2 id="はじめに">はじめに</h2> <p>あっあっあー</p> <p>こんぺこ!こんぺこ!こんぺこー!</p> <p>時期感的になんとなくこんな挨拶をしてみた @bloody_snow です。 M3エンジニアリンググループの2代目VPoEをつとめ、 @vaaaaanquish に交代してからは、 <a href="https://www.m3t.co.jp/">M3 Technologies</a> にてM3のグループ会社をメインで担当しています。M3に関しては個別のサービス開発というよりかはGitHub Copilot、ChatGPT Team、GitHub Enterprise、比較的高性能なMacBookProの導入、AWSの教育受講など全体の生産性への取り組みが多くなっています。</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#利用状況の概要">利用状況の概要</a></li> <li><a href="#言語別">言語別</a></li> <li><a href="#エディタ別">エディタ別</a></li> <li><a href="#Copilot-Chat">Copilot Chat</a></li> <li><a href="#おわりに">おわりに</a></li> <li><a href="#We-are-hiring">We are hiring!</a></li> </ul> <h2 id="利用状況の概要">利用状況の概要</h2> <p>GitHub Copilotとはなんぞや?や世間一般的な利用状況などは割愛させていただきます。</p> <p>GitHubに用意されている</p> <p><a href="https://docs.github.com/ja/rest/copilot/copilot-usage">GitHub Copilot &#x306E;&#x4F7F;&#x7528;&#x72B6;&#x6CC1;&#x30E1;&#x30C8;&#x30EA;&#x30C3;&#x30AF;&#x306E; REST API &#x30A8;&#x30F3;&#x30C9;&#x30DD;&#x30A4;&#x30F3;&#x30C8; - GitHub Docs</a></p> <p>を用いると組織においてのGitHub Copilotの利用状況を確認できます。取得できるJSONを自分で可視化してもよいのですが、</p> <p><a href="https://github.com/github-copilot-resources/copilot-metrics-viewer">GitHub - github-copilot-resources/copilot-metrics-viewer: Tool to visualize the Copilot metrics provided via the Copilot Business Metrics API (current in public beta)</a></p> <p>viewerもコミュニティによって用意されているものがありましたので、こちらを使わせていただきました。データの取得期間は2024/10/21〜11/17の28日間のデータとなります。では、実際の概要を下記に示します。</p> <ul> <li>Accepted Rate: 29.64%</li> <li>Suggestions: 198,539</li> <li>Accepted Prompts: 58,846</li> <li>Accepted Code Lines: 77,480</li> <li>平日の利用者数: 90〜100</li> <li>利用言語: Python, Kotlin, Go, TS, Ruby, TSReact, SQL, Vue, Yaml, Terraformの順番</li> <li>利用エディタ: VSCode, IntelliJ, Emacs, neovim, Xcodeの順番</li> </ul> <p><figure class="figure-image figure-image-fotolife" title="Total Lines Suggested | Total Lines Accepted"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241224/20241224160007.png" width="800" height="435" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Total Lines Suggested | Total Lines Accepted</figcaption></figure></p> <h2 id="言語別">言語別</h2> <p>機械学習やバッチ処理でPythonが多く使われているため利用率が1位となりました。バックエンドではKotlinやgoが多く使われているため、2位3位と続きました。typescript/typescriptreactの区別は少しはっきりとはしませんが、フロントエンドやバックエンドでの利用もあり上位に入賞しています。その他SQLやYamlとTerraformなどの設定ファイルも入ってきていました。</p> <p><figure class="figure-image figure-image-fotolife" title="言語別"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241224/20241224160010.png" width="800" height="650" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>言語別</figcaption></figure></p> <h2 id="エディタ別">エディタ別</h2> <p>エディタに関してはそこまでの多様性はありませんでした。VSCodeが非常に強く、それにIntelliJが続くという結果でした。少し意外だったのはEmacsが3位とVimをおさえたことです。私自身もEmacsユーザーとしてプライベートでRustを書く時などにつかっていたりしますので嬉しい結果です。</p> <p><figure class="figure-image figure-image-fotolife" title="エディタ別"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241224/20241224160014.png" width="800" height="521" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>エディタ別</figcaption></figure></p> <h2 id="Copilot-Chat">Copilot Chat</h2> <p>Copilot Chatに関してもCopilot利用者の約半数ぐらいの40〜50名ほどに使われていました。初期はVSCodeのみ対応だったと記憶しており、あまり広く使われなかった印象でしたが、対応範囲も増え幅広く使われるようになってきています。</p> <p><figure class="figure-image figure-image-fotolife" title="Copilot Chat"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241224/20241224160017.png" width="800" height="529" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Copilot Chat</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="Total Active Copilot Chat Users"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241224/20241224160021.png" width="800" height="406" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Total Active Copilot Chat Users</figcaption></figure></p> <h2 id="おわりに">おわりに</h2> <p>M3におけるGitHub Copilotの活用状況を紹介させていただきました。M3は医療業界においてDXをすすめようとしている会社です。ITエンジニアとして自身の日々の活動も率先してDXをすすめていき、また、今後もより便利なプロダクト(ChatGPT, Dify, v0, etc)をどんどん採用して進歩し、ブログでも紹介していければと思っています。</p> <h2 id="We-are-hiring">We are hiring!</h2> <p>エムスリーおよびエムスリテクノロジーズでは、ギークでスマートなエンジニアを中心に、多数のチャレンジに果敢にアタックするプロダクト開発メンバーを絶賛募集中です。</p> <p>エンジニア、QA、デザイナー、プロダクトマネージャーどの職種でも打席数が多く、プロダクト開発の基礎知識を習得しながら、社会にインパクトのある収益性の高いプロダクトを生み出す経験、ホームランの打ち方を学べるを学べる良い環境だと思います。</p> <p>ご興味のある方は、下記のサイトから是非カジュアル面談等、お申し込みください!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/">jobs.m3.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.m3t.co.jp%2F" title="M3 Technologies" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.m3t.co.jp/">www.m3t.co.jp</a></cite></p> bloodysnowx 2024年、エムスリーエンジニアリンググループで行った組織的ステップチェンジ5選。 hatenablog://entry/6802418398313607477 2024-12-24T10:14:56+09:00 2024-12-24T10:18:18+09:00 皆さんこんにちは、こんばんは。スノーピークのノクターン*1をすでに持っているにもかかわらず、SOTOのヒノト*2を買ってしまった取締役CTO兼VPoPの山崎です。 せっかくなので違いをレビューしておくと、SOTOのヒノトはガスを充填して持ち歩ける専用タンクがあるのは便利なのですが、意外と容量が小さく、マニュアル通り連続燃焼時間は1〜2時間と意外と短いです。もちろん、ヒノトもスノーピークのノクターン同様、OD缶に直接接続すれば一晩以上連続燃焼できると思うので、ランプ側に問題はありません。そういえば、こういうときのためにキャンピングムーンのBKTC-28というOD缶互換ガスタンクを買っておいたな、… <p>皆さんこんにちは、こんばんは。スノーピークのノクターン<a href="#f-08390cf3" id="fn-08390cf3" name="fn-08390cf3" title="https://ec.snowpeak.co.jp/snowpeak/ja/%E3%82%AD%E3%83%A3%E3%83%B3%E3%83%97/%E3%83%A9%E3%83%B3%E3%82%BF%E3%83%B3%28%E3%82%AC%E3%82%B9%E3%83%BBLED%29%E3%83%BB%E3%83%A9%E3%82%A4%E3%83%88/%E3%83%AA%E3%83%88%E3%83%AB%E3%83%A9%E3%83%B3%E3%83%97-%E3%83%8E%E3%82%AF%E3%82%BF%E3%83%BC%E3%83%B3/p/18758">*1</a>をすでに持っているにもかかわらず、SOTOのヒノト<a href="#f-3060e5c5" id="fn-3060e5c5" name="fn-3060e5c5" title="https://soto.shinfuji.co.jp/products/sod-260/">*2</a>を買ってしまった取締役CTO兼VPoPの山崎です。</p> <p>せっかくなので違いをレビューしておくと、SOTOのヒノトはガスを充填して持ち歩ける専用タンクがあるのは便利なのですが、意外と容量が小さく、マニュアル通り連続燃焼時間は1〜2時間と意外と短いです。もちろん、ヒノトもスノーピークのノクターン同様、OD缶に直接接続すれば一晩以上連続燃焼できると思うので、ランプ側に問題はありません。そういえば、こういうときのためにキャンピングムーンのBKTC-28というOD缶互換ガスタンクを買っておいたな、と思って引っ張り出したらこれがベストフィット。最大燃焼時間も大きく延長できました。皆さん参考にしてください。</p> <p>本ブログは<a href="https://qiita.com/advent-calendar/2024/m3">エムスリー Advent Calendar 2024</a>の24日目の記事です。</p> <p>ちなみに昨日はAI・機械学習チームの中村伊吹(<a href="https://x.com/inakam00">@inakam00</a>)の素晴らしい記事でした!エムスリーエンジニアリンググループの文化がよくまとまっておりますので、まだ読んでいない人は是非ご一読ください^^。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.m3tech.blog%2Fentry%2Fbest-mr-2024" title="AI・機械学習チームベストMR(Merge Request)決定戦 2024 - エムスリーテックブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.m3tech.blog/entry/best-mr-2024">www.m3tech.blog</a></cite></p> <p><figure class="figure-image figure-image-fotolife" title="左がSOTOのヒノト+純正ガスタンク、右がスノーピークのノクターン+キャンピングムーンのBKTC-28ガスタンクです。圧倒的にBKTC-28のほうが連続燃焼時間が長いです。もちろん、ヒノトにBKTC-28を装着することも可能です。"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241224/20241224101458.jpg" width="800" height="600" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>左がSOTOのヒノト+純正ガスタンク、右がスノーピークのノクターン+キャンピングムーンのBKTC-28ガスタンクです。圧倒的にBKTC-28のほうが連続燃焼時間が長いです。もちろん、ヒノトにBKTC-28を装着することも可能です。</figcaption></figure></p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#ばんくしことVPoE河合に権限委譲">①ばんくしことVPoE河合に権限委譲</a></li> <li><a href="#ゼネラルマネージャー8人体制への移行">②ゼネラルマネージャー8人体制への移行</a></li> <li><a href="#主力事業とAI機械学習チームの融合">③主力事業とAI・機械学習チームの融合</a></li> <li><a href="#全員QA体制への移行">④全員QA体制への移行</a></li> <li><a href="#プロダクトマネジメント組織の強化">⑤プロダクトマネジメント組織の強化</a></li> <li><a href="#まとめ">まとめ</a></li> <li><a href="#We-are-hiring">We are hiring!</a></li> </ul> <h3 id="はじめに">はじめに</h3> <p>毎年、アドベントカレンダーではエムスリーエンジニアリンググループの組織的チャレンジをまとめています。</p> <p>2024年4月には、私が代表取締役を務めるエムスリーテクノロジーズ株式会社<a href="#f-1b5bde33" id="fn-1b5bde33" name="fn-1b5bde33" title="https://www.m3t.co.jp">*3</a>の立ち上げなど大きなチャレンジもあり、エムスリー本体の強化とグループ会社の支援をバランスよく進めてきた1年となりました。</p> <p>エムスリーテクノロジーズ株式会社とグループ会社の支援は2025年の大きく加速していくことになると思いますので、来年のアドベントカレンダーに取っておくことにして、本日はエムスリー本体の強化について「2024年、エムスリーエンジニアリンググループで行った組織的ステップチェンジ5選。」と題して説明しておきたいと思います。</p> <p>昨年の記事はこちら。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.m3tech.blog%2Fentry%2Fcto-retrospective-2023" title="2023年は3段階でシフトチェンジ!2024年はさらに加速してやっていきます! - エムスリーテックブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.m3tech.blog/entry/cto-retrospective-2023">www.m3tech.blog</a></cite></p> <p>エムスリーテクノロジーズ株式会社設立のプレスリリースはこちら。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fprtimes.jp%2Fmain%2Fhtml%2Frd%2Fp%2F000000009.000125568.html" title="グループ会社支援を目的とした開発支援専門会社|エムスリーテクノロジーズ株式会社を設立" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://prtimes.jp/main/html/rd/p/000000009.000125568.html">prtimes.jp</a></cite></p> <h3 id="ばんくしことVPoE河合に権限委譲">①ばんくしことVPoE河合に権限委譲</h3> <p>まずは、ばんくしことVPoE河合への権限委譲を進めました。 河合さんは2023年5月にエムスリーにVPoEとして戻ってきてくれたのですが、皆さんも御存知の通り、現在関連各所で大活躍頂いております。</p> <p>2024年は中途採用戦略、新卒採用戦略、各種イベント登壇、各種イベントスポンサー、テックブログレビュー、テックチャンネルレビュー、尋常ではない1on1の実施など、VPoE業のほぼ全てを自律的にこなして頂きました。</p> <p>彼のお陰で、エンジニアリンググループを次のステップに変革できたことは言うまでもありません。本当にいつもありがとうございます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.m3tech.blog%2Fentry%2Fthe-reason-why-vaaaaanquish-came-back-as-VPoE-in-M3" title="ばんくしこと河合俊典さんにエムスリー3代目VPoEとして戻ってきてもらった理由 - エムスリーテックブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.m3tech.blog/entry/the-reason-why-vaaaaanquish-came-back-as-VPoE-in-M3">www.m3tech.blog</a></cite></p> <h3 id="ゼネラルマネージャー8人体制への移行">②ゼネラルマネージャー8人体制への移行</h3> <p>2024年スタート時点では、エムスリーエンジニアリンググループのゼネラルマネージャー(旧グループリーダー)<a href="#f-a59f3487" id="fn-a59f3487" name="fn-a59f3487" title="2024年10月より、エムスリーのすべての部門で部門長の呼び名がグループリーダーからゼネラルマネージャーに変更となりました。">*4</a>は私、河合、岩佐、高島の4名でした。</p> <p>2024年4月の体制変更で大垣、木田、窪田の3名が抜擢、続いて10月に横本が抜擢となり、現在はゼネラルマネージャー8名体制へと進化しています。</p> <p>しかも、結果的に8名中6名がAI・機械学習チームの出身となり、今後のAI時代に向けて、準備が整った状況です。</p> <p><iframe id="talk_frame_1254733" class="speakerdeck-iframe" src="//speakerdeck.com/player/353bdaef98bc4b45bc2c13e7b2866ebb" width="710" height="399" style="aspect-ratio:710/399; border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen"></iframe> <cite class="hatena-citation"><a href="https://speakerdeck.com/m3_engineering/introduction-of-m3-all-teams?slide=17">speakerdeck.com</a></cite></p> <h3 id="主力事業とAI機械学習チームの融合">③主力事業とAI・機械学習チームの融合</h3> <p>となれば、主力事業へのさらなるテコ入れを始めなければなりません。</p> <p>2024年10月より、エムスリーの主力事業を営む担当ゼネラルマネージャー<a href="#f-5682d7e9" id="fn-5682d7e9" name="fn-5682d7e9" title="エンジニアリンググループのゼネラルマネージャーは全員で責任を持ってグループ全体を共同運営しつつ、それぞれ担当のチームを持っています。">*5</a>を木田、大垣体制にアップデートして、既存開発チームへのAI・機械学習導入を強化しています。</p> <p>2025年はこの取り組みをさらに加速させていく予定です。</p> <p><iframe id="talk_frame_1135339" class="speakerdeck-iframe" src="//speakerdeck.com/player/98d2854b494f474b88cdf2378ded88f0" width="710" height="399" style="aspect-ratio:710/399; border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen"></iframe> <cite class="hatena-citation"><a href="https://speakerdeck.com/m3_engineering/introduction-of-m3-ai-team">speakerdeck.com</a></cite></p> <h3 id="全員QA体制への移行">④全員QA体制への移行</h3> <p>また、窪田率いるQA体制も大きく進化を遂げようとしています。</p> <p>皆さんも御存知の通り、現在、優秀なQAエンジニアの採用は熾烈な競争環境で、各社がしのぎを削る戦乱の世の中です。 私達はこの環境で戦いつつも、すべてのエンジニアが自ら品質に責任を持つ、次の段階へステップチェンジしたいと考えています。</p> <p>幸い、エムスリーエンジニアリンググループは早くからユニットテストの導入や、CI/CD、IaC、コンテナ化などを整備していたおかげで、エンジニアの品質意識は平均レベル以上になっています。</p> <p>これを基盤に、2024年10月より、全員QAプロジェクトがスタートし、QAエンジニアのより効率的な配置転換とエンジニアへの品質管理の移譲が始まっています。</p> <p><iframe id="talk_frame_1182337" class="speakerdeck-iframe" src="//speakerdeck.com/player/684878d783c3415080c1e326f49635cc" width="710" height="399" style="aspect-ratio:710/399; border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen"></iframe> <cite class="hatena-citation"><a href="https://speakerdeck.com/m3_engineering/introduction-of-m3-qa-team">speakerdeck.com</a></cite></p> <h3 id="プロダクトマネジメント組織の強化">⑤プロダクトマネジメント組織の強化</h3> <p>プロダクトマネジメント組織の強化にも取り組みました。</p> <p>エムスリーエンジニアリンググループのプロダクト支援チームには現在10名以上のプロダクトマネージャーが在籍<a href="#f-7060f488" id="fn-7060f488" name="fn-7060f488" title="2024年も新たに2名のプロダクトマネージャーが入社してくれました!">*6</a>し、各事業チームへの落下傘を通じて、日夜、医療を変革するようなプロダクト開発に取り組んでいます。 これまで進めてきた「お互いの経験から学ぶ」という方針を大切にしながら、より効率よく組織を拡大していくために、2024年12月より、泉、阪口の2名のチームリーダーに抜擢し、私を含めたマネジメントチームを編成しています。</p> <p>2025年は採用強化も含めた、プロダクトマネジメント組織の強化に取り組んでいきたいと思います。</p> <p>ちなみにpmconf2024のDiscordイベントでお話した、プロダクトマネージャーにとってのホームランについては、<a href="https://adventar.org/calendars/10453">プロダクトマネージャー Advent Calendar 2024</a>の14日目の記事でも触れていますので、まだご覧になっていない方はぜひご一読ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.m3tech.blog%2Fentry%2Fpmconf2024-retrospective" title="【pmconf 2024】クライス&amp;カンパニーのDiscord企画振り返り:プロダクトマネージャーにとってのホームランとは? - エムスリーテックブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.m3tech.blog/entry/pmconf2024-retrospective">www.m3tech.blog</a></cite></p> <h3 id="まとめ">まとめ</h3> <p>いかがでしたでしょうか?</p> <p>本ブログでは、「2024年、エムスリーエンジニアリンググループで行った組織的ステップチェンジ5選。」と題して、2024年のエムスリーエンジニアリンググループの組織的な取り組みについて紹介させて頂きました。</p> <p>本当に優秀なリーダー陣、メンバーに支えられて、エムスリーエンジニアリンググループは、ますますエンジニアにとって楽しく、チャレンジングに働ける環境に進化していると実感しています。</p> <p>2025年も引き続き、エンジニア組織の強化を通じて、少しでも日本、世界の医療を前進させる手助けができればと思っています。</p> <p>今年も、エムスリーエンジニアリンググループに所属している素晴らしいエンジニアの皆さんに感謝を込めて、2025年もエムスリーグループやっていき!そしてメリークリスマス!<a href="#f-209a9c78" id="fn-209a9c78" name="fn-209a9c78" title="今年は珍しくギリギリにはならず、12/23に執筆していますw。これもばんくしことVPoE河合と優秀な仲間たちのおかげや〜。">*7</a></p> <p><figure class="figure-image figure-image-fotolife" title="写真は先日開催された第260回M3 Tech Talkでの集合写真。第1回開催は2013/5/24だったので11年以上続くエンジニアリンググループの伝統です。"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241224/20241224101501.jpg" width="800" height="420" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>写真は先日開催された第260回M3 Tech Talkでの集合写真。第1回開催は2013/5/24だったので11年以上続くエンジニアリンググループの伝統です。</figcaption></figure></p> <h3 id="We-are-hiring">We are hiring!</h3> <p>エムスリーでは、ギークでスマートなエンジニアを中心に、多数のチャレンジに果敢にアタックするプロダクト開発メンバーを絶賛募集中です。</p> <p>エンジニア、QA、デザイナー、プロダクトマネージャーどの職種でも打席数が多く、プロダクト開発の基礎知識を習得しながら、社会にインパクトのある収益性の高いプロダクトを生み出す経験、ホームランの打ち方を学べるを学べる良い環境だと思います。</p> <p>ご興味のある方は、下記のサイトから是非カジュアル面談等、お申し込みください!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/">jobs.m3.com</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-08390cf3" id="f-08390cf3" name="f-08390cf3" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://ec.snowpeak.co.jp/snowpeak/ja/%E3%82%AD%E3%83%A3%E3%83%B3%E3%83%97/%E3%83%A9%E3%83%B3%E3%82%BF%E3%83%B3%28%E3%82%AC%E3%82%B9%E3%83%BBLED%29%E3%83%BB%E3%83%A9%E3%82%A4%E3%83%88/%E3%83%AA%E3%83%88%E3%83%AB%E3%83%A9%E3%83%B3%E3%83%97-%E3%83%8E%E3%82%AF%E3%82%BF%E3%83%BC%E3%83%B3/p/18758">https://ec.snowpeak.co.jp/snowpeak/ja/%E3%82%AD%E3%83%A3%E3%83%B3%E3%83%97/%E3%83%A9%E3%83%B3%E3%82%BF%E3%83%B3%28%E3%82%AC%E3%82%B9%E3%83%BBLED%29%E3%83%BB%E3%83%A9%E3%82%A4%E3%83%88/%E3%83%AA%E3%83%88%E3%83%AB%E3%83%A9%E3%83%B3%E3%83%97-%E3%83%8E%E3%82%AF%E3%82%BF%E3%83%BC%E3%83%B3/p/18758</a></span></p> <p class="footnote"><a href="#fn-3060e5c5" id="f-3060e5c5" name="f-3060e5c5" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://soto.shinfuji.co.jp/products/sod-260/">https://soto.shinfuji.co.jp/products/sod-260/</a></span></p> <p class="footnote"><a href="#fn-1b5bde33" id="f-1b5bde33" name="f-1b5bde33" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://www.m3t.co.jp">https://www.m3t.co.jp</a></span></p> <p class="footnote"><a href="#fn-a59f3487" id="f-a59f3487" name="f-a59f3487" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text">2024年10月より、エムスリーのすべての部門で部門長の呼び名がグループリーダーからゼネラルマネージャーに変更となりました。</span></p> <p class="footnote"><a href="#fn-5682d7e9" id="f-5682d7e9" name="f-5682d7e9" class="footnote-number">*5</a><span class="footnote-delimiter">:</span><span class="footnote-text">エンジニアリンググループのゼネラルマネージャーは全員で責任を持ってグループ全体を共同運営しつつ、それぞれ担当のチームを持っています。</span></p> <p class="footnote"><a href="#fn-7060f488" id="f-7060f488" name="f-7060f488" class="footnote-number">*6</a><span class="footnote-delimiter">:</span><span class="footnote-text">2024年も新たに2名のプロダクトマネージャーが入社してくれました!</span></p> <p class="footnote"><a href="#fn-209a9c78" id="f-209a9c78" name="f-209a9c78" class="footnote-number">*7</a><span class="footnote-delimiter">:</span><span class="footnote-text">今年は珍しくギリギリにはならず、12/23に執筆していますw。これもばんくしことVPoE河合と優秀な仲間たちのおかげや〜。</span></p> </div> yamazaki-m3 AI・機械学習チームベストMR(Merge Request)決定戦 2024 hatenablog://entry/6802418398312573660 2024-12-23T17:00:00+09:00 2024-12-23T17:00:00+09:00 この記事はエムスリー Advent Calendar 2024 23日目の記事です。 AI・機械学習チームの中村伊吹(@inakam00)です。 ソフトウェア開発において、優れたコードの変更は時としてアート作品のような美しさや、抜群の機能改善をもたらすことがあります。GitHubでPull Request(PR)を使ったことがある人なら、感動するようなコード修正に出会った経験があるのではないでしょうか。 エムスリーAI・機械学習チームでは、この感動を共有し称え合うために、年に一度「ベストMerge Request(MR)決定戦」を開催しています。MRとはGitLabのMerge Request… <p>この記事は<a href="https://qiita.com/advent-calendar/2024/m3">エムスリー Advent Calendar 2024</a> 23日目の記事です。 AI・機械学習チームの中村伊吹(<a href="https://x.com/inakam00">@inakam00</a>)です。</p> <p>ソフトウェア開発において、優れたコードの変更は時としてアート作品のような美しさや、抜群の機能改善をもたらすことがあります。GitHubでPull Request(PR)を使ったことがある人なら、感動するようなコード修正に出会った経験があるのではないでしょうか。</p> <p>エムスリーAI・機械学習チームでは、この感動を共有し称え合うために、年に一度「ベストMerge Request(MR)決定戦」を開催しています。MRとはGitLabのMerge Requestの略称です。 GitHubでいうところのPR(Pull Request)にあたります。</p> <p><figure class="figure-image figure-image-fotolife" title="普段は全国の拠点でリモートワークで働いているAI・機械学習チームも、年に数回全員集合する機会があります。先週金曜日に社内ライブラリやOSSにコントリビュートをしようという企画を実施しました。"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241223/20241223170040.jpg" width="800" height="603" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>普段は全国各地でリモートで働いているAI・機械学習チームも年に数回全員集合する機会があります。先週金曜日に社内ライブラリやOSSにコントリビュートをしようという企画を実施しました。</figcaption></figure></p> <p>今年も、チームメンバー全員で「これはすごかった!」と感じたコード変更をノミネートし、投票の結果トップ10を選出しました。シンプルな改善から斬新なアイデアまで、いろんな「職人技」が詰まっています。</p> <p><figure class="figure-image figure-image-fotolife" title="AI・機械学習チームの全MRを1年前まで遡るにはページネーションの227ページ目までいく必要がある(約4500件)。RenovateなどのメンテナンスMRを除いてもおそらく1000件以上。"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241223/20241223170034.png" width="800" height="69" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>AI・機械学習チームの全MRを1年前まで遡るにはページネーションの227ページ目までいく必要がある(約4500件)。Renovateなどのメンテナンスを除いてもおそらく1000件以上。</figcaption></figure></p> <p>このブログでは、実際にコードを書いた人や、レビューを通じてその価値を高めることに関わったメンバーたちが、それぞれのMRの魅力について語っていきます。では、第10位から見ていきましょう!</p> <ul class="table-of-contents"> <li><a href="#第10位-機械学習モデルの性能向上施策">第10位 機械学習モデルの性能向上施策</a></li> <li><a href="#第9位-localディスクキャッシュの活用">第9位 localディスクキャッシュの活用</a></li> <li><a href="#第7位同率-gokartへのmypy-pluginの追加">第7位(同率) gokartへのmypy pluginの追加</a></li> <li><a href="#第7位同率-Kubernetesクラスター都合でのバッチの中断をリトライ回数にカウントしない">第7位(同率) Kubernetesクラスター都合でのバッチの中断をリトライ回数にカウントしない</a></li> <li><a href="#第6位-ある日突然BigQueryのdownloadが遅くなっていましたさてなぜでしょう">第6位 ある日突然BigQueryのdownloadが遅くなっていました。さてなぜでしょう?</a></li> <li><a href="#第5位-gokartのlinterを追加">第5位 gokartのlinterを追加</a></li> <li><a href="#第4位-コード検索基盤を構築するためのインデックス作成フロー">第4位 コード検索基盤を構築するためのインデックス作成フロー</a></li> <li><a href="#第3位-Google-Cloud-ArtifactRegistryのクリーンアップ">第3位 Google Cloud ArtifactRegistryのクリーンアップ</a></li> <li><a href="#第2位-TaskOnKartへ型を追加">第2位 TaskOnKartへ型を追加</a></li> <li><a href="#第1位-ロードバランサー一括監視">第1位 ロードバランサー一括監視</a></li> <li><a href="#まとめ">まとめ</a><ul> <li><a href="#We-are-hiring-">We are hiring !!</a><ul> <li><a href="#エンジニア採用ページはこちら">エンジニア採用ページはこちら</a></li> <li><a href="#カジュアル面談もお気軽にどうぞ">カジュアル面談もお気軽にどうぞ</a></li> <li><a href="#インターンも常時募集しています">インターンも常時募集しています</a></li> </ul> </li> </ul> </li> </ul> <h2 id="第10位-機械学習モデルの性能向上施策">第10位 機械学習モデルの性能向上施策</h2> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241223/20241223170006.png" width="800" height="140" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <ul> <li>紹介者: 鴨田</li> </ul> <p>AI・機械学習チームでは、様々な機械学習モデルの開発や運用をしています。その中のあるモデルの簡素化や取り回しの良さに着目したバージョンアップにおいて、v2モデルがv1モデルと比較して精度が悪化するという課題に直面しました。</p> <p>調査の結果、主原因の1つが特徴量に対して強制的にSVD(特異値分解)による圧縮を適用していたことだと判明しました。特徴圧縮の処理を見直すことでv2モデルの精度をv1と同等のレベルにまで回復させることに成功しました。</p> <p>調査から原因究明、対応までなんと2日というスピード感も相まって堂々の10位入賞となりました。</p> <h2 id="第9位-localディスクキャッシュの活用">第9位 localディスクキャッシュの活用</h2> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241223/20241223170010.png" width="800" height="349" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <ul> <li>紹介者: 池嶋</li> </ul> <p>AI・機械学習チームでは様々な分析用関数を共有リポジトリに実装していますが、一部処理に時間がかかるものがありました。例えばデータのダウンロードなどは何度やっても結果は同じなので結果をキャッシュすることで流用したいところです。普段、AI・機械学習チームではgokartを使って処理を書いているので、結果をキャッシュできているのですが、gokart化されていない関数でもキャッシュを使って高速化したくなっていました。</p> <p>このMRでは、キャッシュしても安全な関数を洗い出してlocalキャッシュを有効化することで全体の処理を3倍程度高速化できるようになり、分析の生産性向上に寄与しました。</p> <h2 id="第7位同率-gokartへのmypy-pluginの追加">第7位(同率) gokartへのmypy pluginの追加</h2> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241223/20241223170013.png" width="800" height="449" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fm3dev%2Fgokart%2Fpull%2F384" title="feat: add mypy plugin by hiro-o918 · Pull Request #384 · m3dev/gokart" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/m3dev/gokart/pull/384">github.com</a></cite></p> <ul> <li>紹介者: 山本</li> </ul> <p>エムスリーが公開しているOSSであるgokartにmypy pluginを追加したPRです。</p> <p>これまでコンストラクタに引数の型と異なったものを渡したとしても、mypyで検知できませんでした。 そこで、dataclassや<a href="https://github.com/pydantic/pydantic">pydantic</a>のことを思い出し、mypy pluginを実装することで上記の問題を解消しました。</p> <p>詳しくは次のブログを参考にしてください。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.m3tech.blog%2Fentry%2Fgokart-mypy-plugin" title="mypy plugin に入門して社内 OSS である gokart を型安全にしてみた - エムスリーテックブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.m3tech.blog/entry/gokart-mypy-plugin">www.m3tech.blog</a></cite></p> <h2 id="第7位同率-Kubernetesクラスター都合でのバッチの中断をリトライ回数にカウントしない">第7位(同率) Kubernetesクラスター都合でのバッチの中断をリトライ回数にカウントしない</h2> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241223/20241223170017.png" width="800" height="151" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <ul> <li>紹介者: 三浦</li> </ul> <p>AI・機械学習チームのGKE<a href="#f-ccb55186" id="fn-ccb55186" name="fn-ccb55186" title="Google Kubernetes Engine. Google Cloudが提供するマネージドKubernetesクラスター">*1</a>では、約300個のバッチが稼働しています。GKEでは、ノードへのバッチの配置最適化や、スポットインスタンス<a href="#f-8223f031" id="fn-8223f031" name="fn-8223f031" title="可用性が担保されない反面、格安で利用できるノード。Preemptible VM, Spot VMとも">*2</a>の停止により、バッチが中断・再実行されることがあります。このような中断もリトライにカウントされ、リトライ上限に達した結果、バッチがSLAを満たさないことがありました。</p> <p>これに対し、リトライのカウントを制御する機能を<a href="https://kubernetes.io/docs/tasks/job/pod-failure-policy/#using-pod-failure-policy-to-ignore-pod-disruptions">Kubernetesのドキュメント</a>を発見し、採用しました。本来着目したいリトライ要因のみ<a href="#f-b850bb85" id="fn-b850bb85" name="fn-b850bb85" title="例えば、社内外のAPIなど、連携するコンポーネントへの疎通">*3</a>にフォーカスできるようになり、コストを抑えつつ<a href="#f-1a67e341" id="fn-1a67e341" name="fn-1a67e341" title="単にリトライ上限を増やすでも解決できますが、例えばコードのバグや設定ミスなどリトライで解決しない場合に、不要なインスタンスの起動時間とそれに伴う費用が発生します">*4</a>監視・運用の質を向上できました。</p> <h2 id="第6位-ある日突然BigQueryのdownloadが遅くなっていましたさてなぜでしょう">第6位 ある日突然BigQueryのdownloadが遅くなっていました。さてなぜでしょう?</h2> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241223/20241223170020.png" width="800" height="149" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <ul> <li>紹介者: 中村(伊)</li> </ul> <p>正解は、BigQuery Storage APIを使わない設定になってしまっていたからです。</p> <p>AI・機械学習チームではBigQueryを操作するための社内ライブラリを整備することによって、100個近いプロダクトでも処理の共通化を実現しています。その際に、BigQuery Storage APIを利用することでBigQueryのダウンロードを高速化していたのですが、pandas-gbq v0.22からデフォルトでは利用できなくなっていたことを修正するMRです。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fgoogleapis%2Fpython-bigquery-pandas%2Fpull%2F735" title="feat: move bqstorage to extras and add debug capability by chalmerlowe · Pull Request #735 · googleapis/python-bigquery-pandas" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/googleapis/python-bigquery-pandas/pull/735">github.com</a></cite></p> <p>発見の経緯として、紹介者の中村(伊)が諸事情で数日間メンテナンスができない間に、代わりにプロダクトの保守を行なってくれたチームリーダーの北川が動作検証中に発見しました。AI・機械学習チームはプロダクト数が多いため、スピードを優先するためにある程度の属人化を許容するという体制をとっています。そんな中でも他人の仕事を気にかけ、さらに改良まで行ってしまうチームリーダーの動きはさすがでした。</p> <p>来年はベストMR決定戦〜タイトル部門〜をやりたいので、内容は伝わるが絶妙なタイトルのMRをこれからは作成していきたいですね。</p> <h2 id="第5位-gokartのlinterを追加">第5位 gokartのlinterを追加</h2> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241223/20241223170023.png" width="800" height="233" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <ul> <li>紹介者: 氏家</li> </ul> <p>エムスリーがOSSとして公開しているgokartに対してlinterを作成したMRになります。</p> <p>AI・機械学習チームではgokartが様々なプロダクトで利用されており、社内では知見が溜まっています。しかし、新メンバーなどが入ったときはレビューで「これ使わないほうが良いですよ」と言うなど、口頭での普及をしている状態が続いていました。人手のチェックでは見逃しなども発生するので完全に統一するにはなかなか難しいです。</p> <p>そこでこのMRでは、そのような知見をLinterとして機械的にチェックすることによって、レビューを挟まずにチェックできる仕組みを作りました。 詳しくはブログを出しているので、ぜひそちらも御覧ください。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.m3tech.blog%2Fentry%2F2024%2F10%2F02%2F110000" title="チームで培われたベストプラクティスをlintとして周知する - エムスリーテックブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.m3tech.blog/entry/2024/10/02/110000">www.m3tech.blog</a></cite></p> <h2 id="第4位-コード検索基盤を構築するためのインデックス作成フロー">第4位 コード検索基盤を構築するためのインデックス作成フロー</h2> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241223/20241223170027.png" width="800" height="480" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <ul> <li>紹介者: 中村(弘)</li> </ul> <p>コード検索基盤を構築するためのインデックス作成フローの開発のためのMRです。</p> <p>エムスリーでは今までCloud Source Repositoryを利用したコード検索を利用していましたが、サービスのEnd of Salesがアナウンスされてしまいました。移行先を調査していたところ、Sourcegraphの内部で利用されているZoektというコード検索エンジンを見つけました。Zoektを使ったコード検索基盤を社内向けに構築することで、他サービスに課金することなくコード検索基盤を実現でき、コストを大幅に節約することに成功しました。</p> <p>コード検索基盤の詳細についてはエムスリーテックブック7で公開しています。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechbookfest.org%2Fproduct%2Fg2gqKCQ96newVVkru0YgAm%3FproductVariantID%3DrVnrwrVH1qJWm0qxss1Vpd" title="エムスリーテックブック7:エムスリーエンジニアリンググループ執筆部" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techbookfest.org/product/g2gqKCQ96newVVkru0YgAm?productVariantID=rVnrwrVH1qJWm0qxss1Vpd">techbookfest.org</a></cite></p> <h2 id="第3位-Google-Cloud-ArtifactRegistryのクリーンアップ">第3位 Google Cloud ArtifactRegistryのクリーンアップ</h2> <p><figure class="figure-image figure-image-fotolife" title="数十個のリポジトリに一括適用されたため、ラベルの反映などが大量に行われた"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241223/20241223170037.png" width="800" height="330" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>数十個のリポジトリに一括適用されたため、ラベルの反映などが大量に行われた</figcaption></figure></p> <ul> <li>紹介者: 横本</li> </ul> <p>みなさん、古くなったDockerイメージはきちんと掃除出来ていますでしょうか。恥ずかしながらAI・機械学習チームではGoogle Cloud ArtifactRegistry(以下GARと省略)のCleanup設定が徹底出来ていない状態でした。言うまでもなく古過ぎるイメージはストレージ料金の浪費ですし、またGARは前身であるContainerRegistry(以下GCRと省略、非推奨・提供終了予定でチームでも移行)よりもストレージ料金が割高です。</p> <p>そこでちゃんと削除ポリシーを徹底しようということになったのですが、チームには多くのプロダクトがあり、対象のGARリポジトリも多数。メンバーみんなで分担して設定祭りか…と思われたところ「まとめてやっちゃいます」と数十個のリポジトリを一括で対応してくれた本MRに感謝が集まり第3位に輝きました!</p> <p>なおGARはシングルリージョンに閉じることで通信料金を回避できるため、(ちゃんとストレージを節約すれば)トータルではお得なケースが多いと思われます。GAR移行していきましょう。</p> <h2 id="第2位-TaskOnKartへ型を追加">第2位 TaskOnKartへ型を追加</h2> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241223/20241223170030.png" width="800" height="567" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fm3dev%2Fgokart%2Fpull%2F391" title="Add TaskOnKart load type by kitagry · Pull Request #391 · m3dev/gokart" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/m3dev/gokart/pull/391">github.com</a></cite></p> <ul> <li>紹介者: 北川</li> </ul> <p>エムスリーがOSSとして公開しているMLワークフローライブラリであるgokartへのPRになります。</p> <p>近年Pythonには型に関する機能が増えています。その型の機能をふんだんに使ってgokartの弱点であったタスク間の型の受け渡しをできるようにしたのがこのPRの内容になります。 これによって実行前にエラーに気づきやすくなっただけではなく、コードリーディングを容易にするなどの効果が得られました。</p> <p>詳しくは次のブログを参考にしてください。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.m3tech.blog%2Fentry%2Fai%2Fgokart-type" title="Genericやらoverloadやらを使って、MLパイプラインツールgokartを型安全にしてみた - エムスリーテックブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.m3tech.blog/entry/ai/gokart-type">www.m3tech.blog</a></cite></p> <h2 id="第1位-ロードバランサー一括監視">第1位 ロードバランサー一括監視</h2> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241223/20241223170044.png" width="651" height="275" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <ul> <li>紹介者: 須藤</li> </ul> <p>AI・機械学習チームでは、Google CloudのCloud Monitoringでロードバランサーを監視しています。以前はプロダクト毎にロードバランサーを監視しており、監視設定はプロダクト担当者がTerraformで設定していました。しかし、AI・機械学習チームはプロダクト数が多く、監視設定をプロダクト担当者が実施する運用だと設定漏れが発生していました。</p> <p>そこで、このMRでは、個別で監視を設定するのではなく、1つの設定で全ロードバランサーを監視するようにしました。これにより、各プロダクト担当者が監視の設定をする必要がなくなり、監視漏れを無くすことができました。</p> <h1 id="まとめ">まとめ</h1> <p>今年の第1位は須藤さんの一括ロードバランサー監視でした。このMRは大量にプロダクトを生み出すAI・機械学習チームにとって漏れの起きやすい「監視」を自動化するというスマートな変更でした。</p> <p>AI・機械学習チームではレビュー時に「ベストMR候補」とコメントしておくことで、ベストMR決定戦に備えていると同時に、より革新的なMRを生み出そうというモチベーションを高めています。2024年はチームの人数がこれまで以上に増え、MR数も多くなりました。来年はどういった改善が行われるのか期待ですね。</p> <p>それでは、またベストMR決定戦2025で会いましょう!</p> <h2 id="We-are-hiring-">We are hiring !!</h2> <p>エムスリーAI・機械学習チームでは、どんどん改修を進めていく意欲のあるエンジニアを募集しています。 新卒・中途それぞれの採用だけでなく、カジュアル面談やインターンも常時募集しています!</p> <h3 id="エンジニア採用ページはこちら">エンジニア採用ページはこちら</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/">jobs.m3.com</a></cite></p> <h3 id="カジュアル面談もお気軽にどうぞ">カジュアル面談もお気軽にどうぞ</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2Fcasual-session%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/casual-session/">jobs.m3.com</a></cite></p> <h3 id="インターンも常時募集しています">インターンも常時募集しています</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fopen.talentio.com%2Fr%2F1%2Fc%2Fm3%2Fhomes%2F4354" title="【新卒】エンジニアリンググループ 募集一覧 / エムスリー株式会社" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://open.talentio.com/r/1/c/m3/homes/4354">open.talentio.com</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-ccb55186" id="f-ccb55186" name="f-ccb55186" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">Google Kubernetes Engine. Google Cloudが提供するマネージドKubernetesクラスター</span></p> <p class="footnote"><a href="#fn-8223f031" id="f-8223f031" name="f-8223f031" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">可用性が担保されない反面、格安で利用できるノード。Preemptible VM, Spot VMとも</span></p> <p class="footnote"><a href="#fn-b850bb85" id="f-b850bb85" name="f-b850bb85" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">例えば、社内外のAPIなど、連携するコンポーネントへの疎通</span></p> <p class="footnote"><a href="#fn-1a67e341" id="f-1a67e341" name="f-1a67e341" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text">単にリトライ上限を増やすでも解決できますが、例えばコードのバグや設定ミスなどリトライで解決しない場合に、不要なインスタンスの起動時間とそれに伴う費用が発生します</span></p> </div> inakam00 はじめてのOSSコントリビュート hatenablog://entry/6802418398311543945 2024-12-22T12:00:00+09:00 2024-12-22T12:36:30+09:00 こちらはエムスリー Advent Calendar 2024 22日目の記事です。 こんにちは。2024/10 から AI・機械学習チームにジョインした鴨田です。 私が所属するAIチームには、機械学習やソフトウェア開発に深い知見を持つエンジニアが集まっています。日々のコードレビューや技術議論を通じて、「こんな考え方があったのか」「この実装方法は勉強になる」と新しい発見の連続です。今回のOSSコントリビュートは、チームメンバーがspotify/luigiの問題を特定し、コントリビュートの機会として共有してくれたことがきっかけでした。 本ブログでは私がエンジニア人生で初めてOSSコントリビュートし… <p>こちらは<a href="https://qiita.com/advent-calendar/2024/m3">エムスリー Advent Calendar 2024</a> 22日目の記事です。</p> <p>こんにちは。2024/10 から AI・機械学習チームにジョインした鴨田です。 私が所属するAIチームには、機械学習やソフトウェア開発に深い知見を持つエンジニアが集まっています。日々のコードレビューや技術議論を通じて、「こんな考え方があったのか」「この実装方法は勉強になる」と新しい発見の連続です。今回のOSSコントリビュートは、チームメンバーが<a href="https://github.com/spotify/luigi">spotify/luigi</a>の問題を特定し、コントリビュートの機会として共有してくれたことがきっかけでした。</p> <p>本ブログでは私がエンジニア人生で初めてOSSコントリビュートした話を書いていきます。初めてのコントリビュート系の記事は既にたくさんの人に掲載されていますので<a href="#f-2d1fcea9" id="fn-2d1fcea9" name="fn-2d1fcea9" title="私が閲覧した記事:参考記事1、参考記事2">*1</a>、この記事ではAIチームの文化も絡めてお伝えしたいと思います。</p> <p><figure class="figure-image figure-image-fotolife" title="趣味で行っているボルダリングの大会予選に出場したときの様子"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241222/20241222120012.jpg" width="800" height="600" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>趣味で行っているボルダリングの大会予選に出場したときの様子</figcaption></figure></p> <ul class="table-of-contents"> <li><a href="#OSSコントリビュートの動機">OSSコントリビュートの動機</a></li> <li><a href="#コントリビュートの実施">コントリビュートの実施</a><ul> <li><a href="#Git-forkってなんだ">Git forkってなんだ?</a></li> <li><a href="#PRの作成">PRの作成</a></li> <li><a href="#生成AIを活用したDescription本文作成">生成AIを活用したDescription(本文)作成</a></li> </ul> </li> <li><a href="#やってみての感想">やってみての感想</a><ul> <li><a href="#意外とすんなり終わった">意外とすんなり終わった</a></li> <li><a href="#GitHubコマンドが便利">GitHubコマンドが便利</a></li> <li><a href="#わいわいサポートしていただける環境に助けられた">わいわいサポートしていただける環境に助けられた</a></li> <li><a href="#luigi-360がリリースされました">luigi 3.6.0がリリースされました!</a></li> </ul> </li> <li><a href="#まとめ">まとめ</a></li> <li><a href="#We-are-hiring-">We are hiring !!</a><ul> <li><a href="#エンジニア採用ページはこちら">エンジニア採用ページはこちら</a></li> <li><a href="#カジュアル面談もお気軽にどうぞ">カジュアル面談もお気軽にどうぞ</a></li> <li><a href="#インターンも常時募集しています">インターンも常時募集しています</a></li> </ul> </li> </ul> <h2 id="OSSコントリビュートの動機">OSSコントリビュートの動機</h2> <p>エムスリー株式会社が公開しているOSSの中にgokartという機械学習パイプラインツールがあります。これはSpotify社が開発したluigiのラッパーとして開発されました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fspotify%2Fluigi" title="GitHub - spotify/luigi: Luigi is a Python module that helps you build complex pipelines of batch jobs. It handles dependency resolution, workflow management, visualization etc. It also comes with Hadoop support built in." class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/spotify/luigi">github.com</a></cite> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fm3dev%2Fgokart" title="GitHub - m3dev/gokart: Gokart solves reproducibility, task dependencies, constraints of good code, and ease of use for Machine Learning Pipeline." class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/m3dev/gokart">github.com</a></cite></p> <p>gokartによってどのような課題が解決できるかはAdvent Calendar 2 日目で池嶋さんが書かれた記事に詳しく記載されていますので、ぜひご覧ください。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.m3tech.blog%2Fentry%2F2024%2F12%2F02%2F170000" title="MLOpsの「あるある」課題の解決と、そのためのライブラリgokart - エムスリーテックブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.m3tech.blog/entry/2024/12/02/170000">www.m3tech.blog</a></cite></p> <p>ある日、AIチームでgokartを利用したプロダクトの実行時エラーを解析していたところ、「<code>luigi.TupleParameter().parse()</code>はStringを受け取るとエラーにならずに、文字ごとに分解しTupleに変換してしまう」という、luigi起因の問題を確認しました。</p> <p>luigiでは設定値の型チェックを実行時に行っており、静的解析ができない仕様となっています。</p> <p>例えば次のようなケースで問題が発生します:</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># config.cfg</span> [TaskA] tuple_param = <span class="synConstant">&quot;('hoge')&quot;</span> <span class="synComment"># これはstr型です。本来は('hoge',)のような形式を想定。(カンマが大事)</span> <span class="synComment"># pipeline.py</span> <span class="synStatement">class</span> <span class="synIdentifier">TaskA</span>(luigi.Task): tuple_param:Tuple[<span class="synIdentifier">str</span>,...] = luigi.TupleParameter() <span class="synStatement">def</span> <span class="synIdentifier">run</span>(self): <span class="synComment"># tuple_paramは('h', 'o', 'g', 'e')として解釈される</span> <span class="synComment"># 実行時までこの問題に気づけない</span> </pre> <p>このような問題は、機械学習パイプラインの安定運用において重要な課題となり、特に長時間実行される処理の途中で意図しない処理が発生するとリソースの無駄遣いにつながってしまいます。 今回の場合は、<code>luigi.TupleParameter().parse()</code>に文字列型が渡ってしまったときにエラーとするような処理を入れることにしました。</p> <p>問題の原因とおおよその対処法がチャットにて議論された後に、AIチーム内でコントリビュートを促す投稿がされました。</p> <p><figure class="figure-image figure-image-fotolife" title="AIチーム内でコントリビュートを促す投稿。"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241222/20241222120015.png" width="800" height="88" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>AIチーム内でコントリビュートを促す投稿。</figcaption></figure></p> <p>エムスリーにジョインしたばかりの私はとにかく貢献したい気持ちが高まっていた時期でもあり、すぐに該当問題の修正に名乗り出ました。</p> <h2 id="コントリビュートの実施">コントリビュートの実施</h2> <p>冒頭にも記載しましたが、既にたくさんの人がコントリビュート系記事を記載されていますので、手順について詳しく記載しません。私の気づきや実装を中心に記載したいと思います。</p> <h3 id="Git-forkってなんだ">Git forkってなんだ?</h3> <p>エムスリーや私の今までの開発は少人数チームが多く、業務では単一リモートブランチで開発を行ってきたので、fork文化に触れる機会がありませんでした</p> <p>多くの記事では、まず最初にGitリポジトリをforkし、forkしたリポジトリに対して変更を加える手順が紹介されていました。</p> <p>双方リポジトリを複製することに変わりはないですが、forkを利用するとオリジナルリポジトリとの繋がりを維持したまま、独立して開発可能とのことでした。<a href="#f-8e36743d" id="fn-8e36743d" name="fn-8e36743d" title="参考にした記事:https://qiita.com/morioka1206/items/6f777c060b88f4a7f3ce">*2</a></p> <p>forkしてレポジトリが乱立すると開発状況が見にくくなるかわりに、開発中のものはPR・マージされない限り本家レポジトリに影響を及ぼさない、というOSSや大規模開発ならではのメリットがあるわけですね。</p> <h3 id="PRの作成">PRの作成</h3> <p>今回の場合、文字列型を渡した際に”文字ごとに分解しTupleに変換されてしまう”のは意図した挙動ではないので、文字列型を渡した際はエラーになるように修正しました。</p> <pre class="code lang-diff" data-lang="diff" data-unlink> except (ValueError, TypeError): <span class="synSpecial">- return tuple(literal_eval(x)) # if this causes an error, let that error be raised.</span> <span class="synIdentifier">+ result = literal_eval(x)</span> <span class="synIdentifier">+ # t_str = '(&quot;abcd&quot;)'</span> <span class="synIdentifier">+ # Ensure that the result is not a string to avoid cases like ('a','b','c','d')</span> <span class="synIdentifier">+ if isinstance(result, str):</span> <span class="synIdentifier">+ raise ValueError(&quot;Parsed result cannot be a string&quot;)</span> <span class="synIdentifier">+ return tuple(result) # if this causes an error, let that error be raised.</span> </pre> <p>作成したPRは<a href="https://github.com/spotify/luigi/pull/3323">こちら</a>です。<a href="#f-e8686bea" id="fn-e8686bea" name="fn-e8686bea" title="同じAIチームメンバーが出しているPRも参考にさせていただきました">*3</a></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fspotify%2Fluigi%2Fpull%2F3323" title="Validate str type before tuple cast by ikyasam18 · Pull Request #3323 · spotify/luigi" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/spotify/luigi/pull/3323">github.com</a></cite></p> <h3 id="生成AIを活用したDescription本文作成">生成AIを活用したDescription(本文)作成</h3> <p>生成AIとの対話は次の流れで実施し、Descriptionを作成しました。</p> <p>英語でPR作成することに一番ハードルを感じていましたが、生成AIを活用することによってストレスなく作業できました。</p> <ol> <li>「PRのテンプレートとPRの内容を元に適宜質問しながら対話的に作成してほしい」 と依頼</li> <li>生成AIからの質問に回答しながら、最終的な文章を作成。 <ol> <li>具体的にどのような問題やバグを解決するものか?</li> <li>どのようにテストしたか?</li> </ol> </li> <li>テストを作成していなかったので、テストを実装 <ol> <li>生成AIにテスト実装内容を連携し適切なコミットメッセージを提案してもらう</li> </ol> </li> <li>最終的なDescriptionの作成を生成AIに依頼</li> </ol> <p><figure class="figure-image figure-image-fotolife" title="生成AIを用いてPR内容を作成している様子"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241222/20241222120018.png" width="800" height="714" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>生成AIを用いてPR内容を作成している様子</figcaption></figure></p> <p>生成したDescriptionがどれほど違和感のない言い回しだったかわかりませんが、無事マージしていただけたので最低限実装の内容や解決したい課題については伝わったのかなと思いました。</p> <h2 id="やってみての感想">やってみての感想</h2> <h3 id="意外とすんなり終わった">意外とすんなり終わった</h3> <p>OSSコントリビュートはとてもハードルが高いイメージでしたが、いざ終わってみると「またやってみようかな」と思えるくらいあっさり終わりました。</p> <h3 id="GitHubコマンドが便利">GitHubコマンドが便利</h3> <p>AIチームの北川さんに教えていただき早速使ってみたのですが、とても便利だったので共有します。</p> <p>従来リポジトリのforkはGitHubなどのホスティングサービス上で操作を行う必要がありました。 一方、GitHub CLI(Command Line Interface)のコマンド<code>gh repo fork</code> は 、以下のような特徴があります。</p> <ul> <li>コマンドラインから直接GitHubリポジトリをフォークできる</li> <li>フォーク後に自動的にローカルクローンを作成するオプションがある。</li> <li>リモートの設定(upstream等)を自動的に行える。</li> </ul> <p>個人的には” リモートの設定(upstream等)を自動的に行える”にとても助けられ、手順が簡略化され操作ミスを減らせたと感じています。</p> <h3 id="わいわいサポートしていただける環境に助けられた">わいわいサポートしていただける環境に助けられた</h3> <p>実際の作業では、テストコードの実装場所や効果的なGitコマンドの使い方など、具体的で実践的なアドバイスをいただきました。特に印象的だったのは、自身の業務に直接は関係なくてもチームメンバーが積極的にサポートしてくれる姿勢です。「これってどうすればいいですか?」という初歩的な質問にも、いつも丁寧に回答いただき、OSSコントリビュートという新しいチャレンジを楽しく進めることができました。</p> <p>AIチームのコミュニケーションについては<a href="https://www.m3tech.blog/entry/2024/12/07/110000">アドベントカレンダー7日目の記事</a>に一部記載されていますのでよろしければそちらもご覧ください!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.m3tech.blog%2Fentry%2F2024%2F12%2F07%2F110000" title="リモートワーク時代を生き抜くAI・機械学習チームの働き方 - エムスリーテックブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.m3tech.blog/entry/2024/12/07/110000">www.m3tech.blog</a></cite></p> <p>チームでは引き続きOSSコントリビュートのチャンスがたくさんあるので、今後も積極的にチャレンジしていきたいと思います!</p> <h3 id="luigi-360がリリースされました">luigi 3.6.0がリリースされました!</h3> <p>New Contributorsとして紹介されており、「ちゃんと紹介してくれている!」と嬉しくなる一方で、リポジトリの発展に関わる一員としての自覚がより一層強まりました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fspotify%2Fluigi%2Freleases%2Ftag%2Fv3.6.0" title="Release 3.6.0 · spotify/luigi" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/spotify/luigi/releases/tag/v3.6.0">github.com</a></cite></p> <h2 id="まとめ">まとめ</h2> <p>今回は私がエンジニア人生で初めてOSSコントリビュートした話とAIチームの文化についてご紹介しました。 みなさんのOSSコントリビュートデビューのきっかけにしていただけたり、AIチームの温かい文化を少しでも感じていただけたら嬉しいです。</p> <h2 id="We-are-hiring-">We are hiring !!</h2> <p>エムスリーでは絶賛エンジニアを募集中です! OSSコントリビュートに限らずさまざまなことにチャレンジしてみたい方は是非ご応募ください!</p> <h3 id="エンジニア採用ページはこちら">エンジニア採用ページはこちら</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/">jobs.m3.com</a></cite></p> <h3 id="カジュアル面談もお気軽にどうぞ">カジュアル面談もお気軽にどうぞ</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2Fcasual-session%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/casual-session/">jobs.m3.com</a></cite></p> <h3 id="インターンも常時募集しています">インターンも常時募集しています</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fopen.talentio.com%2Fr%2F1%2Fc%2Fm3%2Fhomes%2F4354" title="【新卒】エンジニアリンググループ 募集一覧 / エムスリー株式会社" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://open.talentio.com/r/1/c/m3/homes/4354">open.talentio.com</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-2d1fcea9" id="f-2d1fcea9" name="f-2d1fcea9" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">私が閲覧した記事:<a href="https://qiita.com/yoyoyo_pg/items/476411a3a63d9c7524e6">参考記事1</a>、<a href="https://speakerdeck.com/youkidearitai/hazimetenoosskontoribiyuto">参考記事2</a></span></p> <p class="footnote"><a href="#fn-8e36743d" id="f-8e36743d" name="f-8e36743d" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">参考にした記事:<a href="https://qiita.com/morioka1206/items/6f777c060b88f4a7f3ce">https://qiita.com/morioka1206/items/6f777c060b88f4a7f3ce</a></span></p> <p class="footnote"><a href="#fn-e8686bea" id="f-e8686bea" name="f-e8686bea" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">同じAIチームメンバーが出している<a href="https://github.com/spotify/luigi/pull/3315">PR</a>も参考にさせていただきました</span></p> </div> kamo2kun 「ギーク」が集う学び舎 - 11年以上続くM3 Tech Talkを支える情熱に迫る hatenablog://entry/6802418398312980897 2024-12-21T12:00:00+09:00 2024-12-21T12:00:01+09:00 エムスリーでは、社員の技術力向上と交流を目的とした社内勉強会「M3 Tech Talk」を定期的に開催しています。Tech Talkは、2013年から続く歴史ある勉強会で、毎回様々なテーマの発表が行われ、社員から好評を得ています。 今回は、6年半という長きにわたりTech Talkのオーガナイザーを務めた星川にインタビューを行い、Tech Talkの裏側や、オーガナイザーとしての苦労、そしてTech Talkに対する熱い想いを伺いました。 <p><em>この記事は<a href="https://qiita.com/advent-calendar/2024/m3">エムスリーAdvent Calendar 2024</a> 21日目の記事です</em></p> <p><span class="hidden"> こんにちは、星川です。 </span></p> <p>エムスリーでは、社員の技術力向上と交流を目的とした社内勉強会「M3 Tech Talk」を定期的に開催しています。Tech Talkは、2013年から続く歴史ある勉強会で、毎回様々なテーマの発表が行われ、社員から好評を得ています。</p> <p><span class="hidden"> 本記事では、このTech Talkのオーガナイザーを今年途中までの約6年半務めたことについて、Geminiに根掘り葉掘り聞いてもらったインタビューをお届けします。 </span> <span class="after-hidden"> 今回は、6年半という長きにわたりTech Talkのオーガナイザーを務めた星川にインタビューを行い、Tech Talkの裏側や、オーガナイザーとしての苦労、そしてTech Talkに対する熱い想いを伺いました。 </span></p> <p><button class="hidden" id="back-to-about">元の位置に戻る</button></p> <p><figure class="figure-image figure-image-fotolife" title="12/20のオフライン開催では、普段関西に住んでいるメンバーも集まり大盛況に終わった"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241221/20241221120009.jpg" width="800" height="420" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>12/20のオフライン開催では、普段関西に住んでいるメンバーも集まるなど大盛況に終わった</figcaption></figure></p> <ul class="table-of-contents"> <li><a href="#M3-Tech-Talkとは">M3 Tech Talkとは?</a></li> <li><a href="#オーガナイザーとしての苦労と工夫">オーガナイザーとしての苦労と工夫</a></li> <li><a href="#6年半のモチベーション">6年半のモチベーション</a></li> <li><a href="#Tech-Talkがもたらすもの">Tech Talkがもたらすもの</a></li> <li><a href="#今後の展望">今後の展望</a></li> <li><a href="#まとめ">まとめ</a></li> <li><a href="#この記事について">この記事について</a></li> <li><a href="#プロンプトについて">プロンプトについて</a></li> <li><a href="#実際使ってみての感想">実際使ってみての感想</a></li> <li><a href="#まとめ-1">まとめ</a></li> <li><a href="#We-are-hiring">We are hiring!!</a></li> </ul> <style> .hidden { display: none; } .revail .hidden { display: inherit; animation: blur 2s ease 2.5s backwards reverse; -webkit-animation: blur 2s ease 2.5s backwards reverse; -moz-animation: blur 2s ease 2.5s backwards reverse; } .revail .after-hidden { animation: blur 2s ease 1.5s forwards; -webkit-animation: blur 2s ease 1.5s forwards; -moz-animation: blur 2s ease 1.5s forwards; max-height: 0px; overflow: hidden; display: inline-block; } ul.table-of-contents > li:nth-child(n+7):nth-child(-n+10) { display: none; } .revail ul.table-of-contents > li:nth-child(6) { display: none; } .revail ul.table-of-contents > li:nth-child(n+7):nth-child(-n+10) { display: inherit; } .lines { width: 100%; display: flex; vertical-align: middle; margin-bottom: 16px; } span.bubbles { white-space: break-spaces; display: inline; flex: 1; background-color: #fff8e8; padding: 8px; margin-left: 16px; border-radius: 4px; } .lines.interviewer span.bubbles { background-color: #eee; } span.bubbles:before { content: ""; background-color: #fff8e8; rotate: 45deg; width: 16px; height: 16px; display: block; position: absolute; margin-left: -16px; margin-top: 16px; z-index: 0; } .lines.interviewer span.bubbles:before { background-color: #eee; } span.icon { width: 64px; height: 64px; display: inline-block; border-radius: 32px; margin-right: 8px; background-size: contain; border: 0.5px solid #eeeeee; } .lines.interviewer span.icon{ background-image: url("https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241221/20241221120016.jpg"); } .lines.oboenikui span.icon { background-image: url("https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241221/20241221120013.png"); } span.icon:after { display: block; position: relative; width: 64px; margin-top: 64px; text-align: center; font-size: 0.6em; } @media (max-width: 767px) { span.icon { width: 48px; height: 48px; border-radius: 24px; margin-right: 4px; } span.icon:after { width: 48px; margin-top: 48px; } span.bubbles:before { margin-top: 12px; z-index: 0; } } .lines.oboenikui span.icon:after { content: "星川"; } .entry.revail .lines.interviewer span.icon:after { content: "Gemini"; } #ai-conclusion { display: block; overflow: hidden; } #ai-conclusion:after { content: "星川へのインタビューを通して、M3 Tech Talkが単なる社内勉強会ではなく、社員同士の交流を深め、学びを促進し、そして会社全体の成長を支える重要な役割を担っていることを実感しました。6年半もの間、オーガナイザーとしてTech Talkを支え続けてきた情熱と、M3のエンジニアたちの技術に対する探究心、互いに高め合う社風に深く感銘を受けました。"; } #まとめ { overflow: hidden; } #spacer { height: 4000px; } .entry.revail #spacer { animation: erase-space 2s ease forwards; -webkit-animation: erase-space 2s ease forwards; -moz-animation: erase-space 2s ease forwards; } .entry.revail #まとめ, .entry.revail #ai-conclusion { animation: partial-blur 3s ease forwards; -webkit-animation: partial-blur 3s ease forwards; -moz-animation: partial-blur 3s ease forwards; } a.disappear { animation: blur 2s ease forwards; -webkit-animation: blur 2s ease forwards; -moz-animation: blur 2s ease forwards; } @keyframes partial-blur { from { opacity: 100; transform: translate(0, 0); max-height: 500px; } to { filter: blur(50px); opacity: 0; transform: translate(100px, 0); max-height: 0; } } @keyframes blur { from { filter: blur(0px); opacity: 100; transform: translate(0, 0); max-height: 500px; } to { filter: blur(50px); opacity: 0; transform: translate(100px, 0); max-height: 0; } } @keyframes erase-space { from { height: 4000px } to { height: 0; } } </style> <h2 id="M3-Tech-Talkとは">M3 Tech Talkとは?</h2> <div class="lines interviewer"> <span class="icon"></span> <span class="bubbles">星川さん、本日はよろしくお願いいたします。6年半もの間、M3 Tech Talkのオーガナイザーを務められたとのことですが、まずはTech Talkについて簡単にご紹介いただけますか?</span> </div> <div class="lines oboenikui"> <span class="icon"></span> <span class="bubbles">はい、よろしくお願いします。M3 Tech Talkは、隔週で金曜日に開催している社内の技術勉強会です。2013年から継続して開催しており、昨日の開催で260回目を迎えました。普段は毎回3~4名の方に15分ほどのLTをしてもらっています。</span> </div> <div class="lines interviewer"> <span class="icon"></span> <span class="bubbles">ありがとうございます。15分ほどのLTということですが、内容は技術的なものに限られているのでしょうか? また、発表者はどのように選ばれているのですか?</span> </div> <div class="lines oboenikui"> <span class="icon"></span> <span class="bubbles">ソフトウェアエンジニアが多数のため、IT技術についての発表が多いですが、発表内容は基本的には登壇者に任せられています。過去には、DIYで子どものためのおもちゃを作る話のようなハードウェア寄りの話もあったり、もっと違う分野だとプロジェクトマネージャーや企画系の職種の方が登壇してくださったり、病気の経験を基に健康について話される方もいらっしゃいました。</span> </div> <p><br/> <figure class="figure-image figure-image-fotolife" title="IT技術以外の話がエムスリー公式テックチャンネルのTop3に入っていたりする"> <iframe width="560" height="315" src="https://www.youtube.com/embed/7JLA0a_ENHQ?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen title="なぜなぜ分析の地獄【M3 Tech Talk 第171回】"></iframe><cite class="hatena-citation"><a href="https://www.youtube.com/watch?v=7JLA0a_ENHQ">www.youtube.com</a></cite> <figcaption>IT技術以外の話がエムスリー公式テックチャンネルのTop3に入っていたりする</figcaption></figure></p> <p><br/></p> <div class="lines oboenikui"> <span class="icon"></span> <span class="bubbles">発表者の決定方法は挙手制です。登壇者が少なかった時期は他のメンバーから推薦されて発表する方もいらっしゃいましたが、現在は自主的に発表する方だけで枠が埋まるようになっています。私の知る限りでは持ち回り制にしていた時期はありません。</span> </div> <div class="lines interviewer"> <span class="icon"></span> <span class="bubbles">なるほど、技術的な内容に限らず、多様なテーマの発表があるんですね。企画系の職種の方や、ご自身の病気について話された方がいたというのは興味深いです。発表者が自主的に手を挙げるというのは、それだけ社員の方々が積極的に学びを深め、共有したいという意識が高いということでしょうか?</span> </div> <div class="lines oboenikui"> <span class="icon"></span> <span class="bubbles">そうですね、そういった文化は根付いていると思います。弊社のエンジニアリンググループを形容する単語として「ギーク」が多用されるほど、技術が好きなエンジニアが多く在籍しています。そういう背景から、技術的なトピックを「面白い」と感じるメンバーが多くいるので、自ずとそのような意識が高くなっているのだと思います。</span> </div> <div class="lines interviewer"> <span class="icon"></span> <span class="bubbles">なるほど。「ギーク」という言葉が多用されるほど、技術好きなエンジニアが多いというのは素晴らしいですね。Tech Talkのような場があることで、そういった方々が知識や経験を共有し、互いに刺激し合うことができるというのは、会社にとっても大きなメリットになりそうです。</span> </div> <h2 id="オーガナイザーとしての苦労と工夫">オーガナイザーとしての苦労と工夫</h2> <div class="lines interviewer"> <span class="icon"></span> <span class="bubbles">ところで、星川さんは6年半という長い間、Tech Talkのオーガナイザーを務めてこられましたが、その中で何か苦労されたことや、大変だったことはありますか?</span> </div> <div class="lines oboenikui"> <span class="icon"></span> <span class="bubbles">色々ありましたね…… まず2020年のコロナ禍で、みんなで集まれなくなったことでしょうか。それまでは社内の広い会議室で開催していましたが、集まることもできなくなってしまったので、一時休止となりました。 2ヶ月間の休止の後に、Zoomを使ってオンライン開催するようになり、それが今でも定着しています。(※) 他には、なかなか登壇者が集まらなかったことや、自分が休暇を取りづらかったことなどもあります笑 <span style="font-size:0.8em">※ 現在は年2回ほど有志者が企画し、オフライン開催も行われている</span></span> </div> <div class="lines interviewer"> <span class="icon"></span> <span class="bubbles">コロナ禍で一時休止になったり、オンライン開催への移行など、運営方法も大きく変わったんですね。発表者が集まらなかった時期もあったとのことですが、どのように乗り越えられたのでしょうか?</span> </div> <div class="lines oboenikui"> <span class="icon"></span> <span class="bubbles">発表者の集まり具合というのは波があって、人の入れ替わりや役職が変わるタイミングで増減する傾向がありました。登壇に積極的な人が入社したり、逆にそういった方が退職したりすると、発表者が減ってしまうこともありましたね。 逆に発表したい人が増えすぎて、毎週開催していた時期もあったんですよ。</span> </div> <div class="lines oboenikui"> <span class="icon"></span> <span class="bubbles">オーガナイザーとしては、どんな時でも最低でも隔週開催を継続することを目標にしていました。挙手制なので、発表者が減ってきている傾向はつかみやすいんです。 傾向を感じたときは、登壇未経験の方が発表しやすいように、5分LT会やチーム特集回といった企画を実施しました。チーム特集回は、同じチームのメンバーがチームで利用している技術などを紹介する回ですね。特定の技術に焦点を当てた特集回を開催することもありました。 それ以外にも、私自身Tech Talkでよく発表しているのですが、登壇者が少ない回には、積極的に発表するようにしていました。</span> </div> <div class="lines oboenikui"> <span class="icon"></span> <span class="bubbles">それと、これは私がやったことではないのですが、AIチームのメンバーは入社後すぐにTech Talkで発表してくれるんです。そういった雰囲気を作ってくれているのは、本当にありがたいですね。</span> </div> <p><br/></p> <p><figure class="figure-image figure-image-fotolife" title="AIチームは年末にチーム内でインパクトの大きかったPRを投票し合うのが恒例になっている"> <iframe width="560" height="315" src="https://www.youtube.com/embed/2uhesE2jp5c?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen title="2022年度AIチーム最高のMR•PRベスト10決定戦【M3 Tech Talk 第211回】"></iframe><cite class="hatena-citation"><a href="https://www.youtube.com/watch?v=2uhesE2jp5c">www.youtube.com</a></cite> <figcaption>AIチームは年末にチーム内でインパクトの大きかったPRを投票し合うのが恒例になっている</figcaption></figure></p> <p><br/></p> <h2 id="6年半のモチベーション">6年半のモチベーション</h2> <div class="lines interviewer"> <span class="icon"></span> <span class="bubbles">お話をお伺いしていると、Tech Talkを盛り上げようと、星川さん自身も相当な努力をされてきたんですね。6年半もオーガナイザーを続けられたモチベーションは何だったのでしょうか?</span> </div> <div class="lines oboenikui"> <span class="icon"></span> <span class="bubbles">ずっとやりすぎて当たり前になっていたのであまり考えたことがなかったですが、まずTech Talkが好きだから続けられたんだと思います。また、社外でもカンファレンス運営に携わっているので、こういった社内コミュニティ活動の役回りが苦ではなかったから、というのもあると思います。 司会は持ち回り制でも良かったんじゃないかと思っていますが、伝統的にこうだったのであまり考えていませんでした笑</span> </div> <div class="lines oboenikui"> <span class="icon"></span> <span class="bubbles">何にせよ、私の前任者も一緒にカンファレンス運営をしている方ですし、今のオーガナイザーも別のカンファレンス運営に携わっていて、やはり相性は良いのかなと思います。</span> </div> <div class="lines interviewer"> <span class="icon"></span> <span class="bubbles">Tech Talkが好きで、それが続けられた一番の理由というのは素敵ですね! 確かに、カンファレンス運営に携わっている方だと、オーガナイザーの役割にもスムーズに馴染めそうですね。 星川さんのTech Talkに対する想いが伝わってきます。</span> </div> <h2 id="Tech-Talkがもたらすもの">Tech Talkがもたらすもの</h2> <div class="lines interviewer"> <span class="icon"></span> <span class="bubbles">ところで、Tech Talkは社内勉強会ということですが、参加者にとってどのようなメリットがあると感じていますか? また、会社全体への影響についてはいかがでしょうか?</span> </div> <div class="lines oboenikui"> <span class="icon"></span> <span class="bubbles">まず参加者は技術について学べるのはもちろんのこと、社内の他チームの業務について学べることもあります。また現在弊社はほとんどの方がリモートで業務をしているため、チーム外のメンバーとの関わりは多くありません。その中で、登壇者だけでなく参加者もチャットでワイワイ盛り上がったり、質疑をする機会があるため、交流の場としての役割もあります。</span> </div> <div class="lines oboenikui"> <span class="icon"></span> <span class="bubbles">また、登壇者が許可したものについては録画をYouTubeで公開しているため、社外の方からも雰囲気が伝わると好評です。</span> </div> <div class="lines interviewer"> <span class="icon"></span> <span class="bubbles">なるほど。Tech Talkは、単に技術を学ぶだけでなく、他チームの業務内容を知ったり、社員同士の交流を深める場にもなっているんですね。リモートワークが中心の今、このような機会はますます重要になっているのではないでしょうか。</span> </div> <div class="lines interviewer"> <span class="icon"></span> <span class="bubbles">YouTubeで公開されているというのも素晴らしいですね。社外の方にもM3の技術力や社風をアピールできますし、参加者にとっても自分の発表を多くの人に見てもらえるというのは、モチベーション向上に繋がりそうです。</span> </div> <h2 id="今後の展望">今後の展望</h2> <div class="lines interviewer"> <span class="icon"></span> <span class="bubbles">最後に、Tech Talkを今後どのように発展させていきたいか、展望をお聞かせください。</span> </div> <div class="lines oboenikui"> <span class="icon"></span> <span class="bubbles">オーガナイザーとしては引退しましたが、今後も登壇者として盛り上げていきたいです。今までもYouTuber風の動画にするなど、型にはまらない発表が強みなので、今後もそういうのを続けていきたいです!</span> </div> <p><br/></p> <p><figure class="figure-image figure-image-fotolife" title="どうしても録画映像を映す時間が多くなるので、いっそのことDaVinci Resolveで発表を作った回"> <iframe width="560" height="315" src="https://www.youtube.com/embed/iXzADTQY_Gw?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen title="オタマトーンの音をMIDIに変換して演奏してみた【M3 Tech Talk 第199回】"></iframe><cite class="hatena-citation"><a href="https://www.youtube.com/watch?v=iXzADTQY_Gw">www.youtube.com</a></cite> <figcaption>どうしても録画映像を映す時間が多くなるので、いっそのことDaVinci Resolveで発表を作った回</figcaption></figure></p> <p><br/></p> <div class="lines interviewer"> <span class="icon"></span> <span class="bubbles">YouTuber風の動画ですか! それは面白そうですね。ぜひ、今後の発表も楽しみにしています! 本日は貴重なお話を聞かせていただき、ありがとうございました。星川さんのTech Talkに対する熱い想いがひしひしと伝わってきました。この記事を通して、M3の社風や技術力、そしてTech Talkの魅力が、多くの方に伝われば幸いです。 本日は本当にありがとうございました!</span> </div> <div class="lines oboenikui"> <span class="icon"></span> <span class="bubbles">ありがとうございました!</span> </div> <h2 id="まとめ">まとめ</h2> <p><span id="ai-conclusion"></p> <p></span></p> <div id="spacer"> </div> <script> window.addEventListener("load", () => { const backButton = document.getElementById("back-to-about") backButton.addEventListener("click", () => { window.scroll({ top: document.getElementById("この記事について").offsetTop - 100, behavior: "smooth", }) }) const title = document.querySelector(".entry-title > a"); const entry = document.querySelector(".entry") if (location.hash === '#view-changed') { entry.classList.add("revail"); title.innerText = "M3 Tech Talkのオーガナイザーを6年半務めたことについて、Geminiにインタビューされてみた"; return; } const spacer = document.getElementById("spacer"); const conclusion = document.getElementById("ai-conclusion"); const matome = document.getElementById("まとめ"); const delay = (ms) => new Promise(res => setTimeout(res, ms)) const onScroll = async () => { if (window.scrollY >= spacer.offsetTop - 400) { location.hash = '#view-changed' document.removeEventListener("scroll", onScroll); entry.classList.add("revail"); await delay(1000) window.scroll({ top: 0, behavior: "smooth" }); await delay(500); title.classList.add("disappear"); await delay(2000); title.innerText = "M3 Tech Talkのオーガナイザーを6年半務めたことについて、Geminiにインタビューされてみた" title.classList.remove("disappear"); } else { const filterValue = `blur(${((window.innerHeight - 400) - (spacer.offsetTop - window.scrollY - 400)) / (window.innerHeight - 400) * 50}px)`; matome.style.filter = filterValue; conclusion.style.filter = filterValue; } } document.addEventListener("scroll", onScroll); }) </script> <h2 id="この記事について">この記事について</h2> <p>もうお気付きかと思いますが、この記事は星川が書いています。今までのパートは、AI (Gemini 1.5 Pro) にインタビューしてもらったときの会話でした。</p> <p>はじめは記事のアイデア出しの壁打ちとして会話をしていたのですが、いっそインタビューとして公開してしまった方が、むしろ自分では書けない情報を出せる思ったのでこのような形で公開しました。</p> <h2 id="プロンプトについて">プロンプトについて</h2> <p>次のようなプロンプトでインタビューアーになってもらいました。</p> <pre class="code" data-lang="" data-unlink>エムスリー社のテックブログの記事を書くため、インタビューアーとして、エムスリーの社内勉強会「M3 Tech Talk」のオーガナイザーを6年半務めた星川貴樹氏にインタビューする。 苦労話や、継続のための秘訣など、読者が気になることを質問する。 最後に記事としてまとめる。</pre> <h2 id="実際使ってみての感想">実際使ってみての感想</h2> <p>まず背景として、私は記事の執筆が苦手です。それを踏まえた上で、自分が1から執筆した場合には書かなかっただろう、ということまで引き出してくれているので、有用であったと感じています。例えば、「私自身のモチベーション」については私が回顧録を書いたとしたら絶対に入れなかったと思います。</p> <p>また、言いたいことについてうまくまとめられないときや、細かい言い回しが気になるときに、筆が止まってしまうことがあります。AI相手であれば箇条書きでも問題ないですし、いい感じにまとめてもらうことも可能なので、人間よりむしろやりやすい面もありました。</p> <p><figure class="figure-image figure-image-fotolife" title="一時的に会話としてまとめられなくても、箇条書きで問題なく会話を続けられる"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241221/20241221120006.png" width="339" height="800" loading="lazy" title="" class="hatena-fotolife" style="width:300px" itemprop="image"></span><figcaption>一時的に会話としてまとめられなくても、箇条書きで問題なく会話を続けられる</figcaption></figure></p> <p>答えを考える時間が欲しい時も、何時間後でも会話を再開できるので、その点も人間より便利な点です。</p> <p>今回はブログ記事としてそのまま公開しましたが、壁打ちにもっと活用したいと思える良い体験を得られました。 本当に便利な世の中になりましたね。</p> <h2 id="まとめ-1">まとめ</h2> <p>まずTech Talkのオーガナイザーを長く務めた経験について書かせていただきました。11年以上続けられているのは登壇者、参加者の熱意によるもので、決して私や歴代オーガナイザーだけの功績ではないです。そのため今回書いたことを実践しても必ずうまくいくという話ではないと思いますが、少しでも参考になったらと思っております。</p> <p>また、テキスト生成AIを今まではあまり壁打ち相手として使ってこなかったのですが、インタビューアーを演じさせてみて、予想以上に効果を感じられました。 世の中には既に多くの生成AIを用いた壁打ちについての記事があるので、それらの情報も活用しながら今後も利用していきたいと思えました。</p> <h2 id="We-are-hiring">We are hiring!!</h2> <p>エムスリーエンジニアリンググループでは、一緒に働く仲間を募集しています! 人間とのカジュアル面談は、以下URLよりご応募をお待ちしています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/">jobs.m3.com</a></cite></p> oboenikui BigQueryでJSONをテーブルに自動変換できるようにしてみた hatenablog://entry/6802418398313013956 2024-12-20T12:04:14+09:00 2024-12-20T12:04:14+09:00 こちらはエムスリー Advent Calendar 2024の20日目の記事です。 デジスマチームの田口です。 デジスマ診療(以降デジスマ)はQRコードによるチェックインや自動後払い、オンライン診療など新しい医療体験を提供するサービスです。 www.youtube.com ありがたいことにサービスは成長し続けており、これに伴ってKPIの深掘りや施策の効果検証のためのデータ分析もより積極的に行われるようになってきました。 デジスマチームではデータ分析基盤にBigQueryを利用しており、Amazon Auroraや各種ログデータをBigQueryに連携し、様々な分析をしています。 多種多様なデー… <p>こちらは<a href="https://qiita.com/advent-calendar/2024/m3">エムスリー Advent Calendar 2024</a>の20日目の記事です。</p> <p>デジスマチームの田口です。 デジスマ診療(以降デジスマ)はQRコードによるチェックインや自動後払い、オンライン診療など新しい医療体験を提供するサービスです。</p> <p><iframe width="560" height="315" src="https://www.youtube.com/embed/3FxYz5H6LBw?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen title="さあ、はじめよう。デジスマ診療"></iframe><cite class="hatena-citation"><a href="https://www.youtube.com/watch?v=3FxYz5H6LBw">www.youtube.com</a></cite></p> <p>ありがたいことにサービスは成長し続けており、これに伴ってKPIの深掘りや施策の効果検証のためのデータ分析もより積極的に行われるようになってきました。</p> <p>デジスマチームではデータ分析基盤にBigQueryを利用しており、Amazon Auroraや各種ログデータをBigQueryに連携し、様々な分析をしています。 多種多様なデータを連携している都合上、データの中にはJSON形式のカラムのものもあり、この内容を検索や集計したいケースも出てきます。 分析する中でJSON形式のデータをテーブルに変換したくなったため、JSONのカラムをテーブルに変換する方法を考えてみました。</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#サンプルデータ">サンプルデータ</a></li> <li><a href="#JSON関数を用いた分析">JSON関数を用いた分析</a></li> <li><a href="#JSONをテーブルに変換">JSONをテーブルに変換</a><ul> <li><a href="#1-UDF-JSONキーと値を同時に取得">1. UDF: JSONキーと値を同時に取得</a></li> <li><a href="#2-一時テーブルの作成-JSONをキーと値のペアに展開">2. 一時テーブルの作成: JSONをキーと値のペアに展開</a></li> <li><a href="#3-ピボットクエリの動的生成">3. ピボットクエリの動的生成</a></li> </ul> </li> <li><a href="#おわりに">おわりに</a></li> <li><a href="#We-are-hiring">We are hiring!!</a></li> </ul> <h2 id="はじめに">はじめに</h2> <p>デジスマでBigQueryに連携しているデータの中には、構造的なデータや配列を表現したいものをJSON形式で保存しているものもあります。 これはデータ保存の観点では柔軟ですが、それ故にSQLで分析する際は工夫が必要です。</p> <h2 id="サンプルデータ">サンプルデータ</h2> <p>本記事では、こちらの <code>1.7m users (504.3 MB)</code> データをサンプルとして利用します。</p> <p><a href="https://jsoneditoronline.org/indepth/datasets/json-file-example/#a-large-json-document">Exploring the power of JSON: a real-life JSON file example collection</a></p> <pre class="code lang-json" data-lang="json" data-unlink><span class="synSpecial">[</span> <span class="synSpecial">{</span> &quot;<span class="synStatement">id</span>&quot;: <span class="synConstant">0</span>, &quot;<span class="synStatement">name</span>&quot;: &quot;<span class="synConstant">Elijah</span>&quot;, &quot;<span class="synStatement">city</span>&quot;: &quot;<span class="synConstant">Austin</span>&quot;, &quot;<span class="synStatement">age</span>&quot;: <span class="synConstant">78</span>, &quot;<span class="synStatement">friends</span>&quot;: <span class="synSpecial">[</span> <span class="synSpecial">{</span> &quot;<span class="synStatement">name</span>&quot;: &quot;<span class="synConstant">Michelle</span>&quot;, &quot;<span class="synStatement">hobbies</span>&quot;: <span class="synSpecial">[</span> &quot;<span class="synConstant">Watching Sports</span>&quot;, &quot;<span class="synConstant">Reading</span>&quot;, &quot;<span class="synConstant">Skiing &amp; Snowboarding</span>&quot; <span class="synSpecial">]</span> <span class="synSpecial">}</span>, <span class="synSpecial">{</span> &quot;<span class="synStatement">name</span>&quot;: &quot;<span class="synConstant">Robert</span>&quot;, &quot;<span class="synStatement">hobbies</span>&quot;: <span class="synSpecial">[</span> &quot;<span class="synConstant">Traveling</span>&quot;, &quot;<span class="synConstant">Video Games</span>&quot; <span class="synSpecial">]</span> <span class="synSpecial">}</span> <span class="synSpecial">]</span> <span class="synSpecial">}</span>, <span class="synSpecial">{</span> &quot;<span class="synStatement">id</span>&quot;: <span class="synConstant">1</span>, &quot;<span class="synStatement">name</span>&quot;: &quot;<span class="synConstant">Noah</span>&quot;, &quot;<span class="synStatement">city</span>&quot;: &quot;<span class="synConstant">Boston</span>&quot;, &quot;<span class="synStatement">age</span>&quot;: <span class="synConstant">97</span>, &quot;<span class="synStatement">friends</span>&quot;: <span class="synSpecial">[</span> <span class="synSpecial">{</span> &quot;<span class="synStatement">name</span>&quot;: &quot;<span class="synConstant">Oliver</span>&quot;, &quot;<span class="synStatement">hobbies</span>&quot;: <span class="synSpecial">[</span> &quot;<span class="synConstant">Watching Sports</span>&quot;, &quot;<span class="synConstant">Skiing &amp; Snowboarding</span>&quot;, &quot;<span class="synConstant">Collecting</span>&quot; <span class="synSpecial">]</span> <span class="synSpecial">}</span>, ... <span class="synSpecial">]</span> <span class="synSpecial">}</span>, ... <span class="synSpecial">]</span> </pre> <p>サンプルのデータは上記のような構造になっています。 BigQueryには、上記データを1つずつ <code>user_json</code> カラムに格納した <code>users</code> テーブルを作成します。</p> <table> <thead> <tr> <th>カラム名</th> <th>データ型</th> <th>値の例</th> </tr> </thead> <tbody> <tr> <td>user_json</td> <td>JSON</td> <td>{"id":0,"name":"Elijah","city":"Austin","age":78,...}</td> </tr> </tbody> </table> <h2 id="JSON関数を用いた分析">JSON関数を用いた分析</h2> <p>BigQueryでは<a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#json_type">JSON型</a>をサポートしており、<a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/json_functions">JSONを操作する関数</a>も様々なものが提供されています。</p> <p>例えば、年齢が50歳以上のユーザーを取得するには次のようなクエリになります。</p> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synStatement">SELECT</span> JSON_VALUE(user_json, <span class="synSpecial">'</span><span class="synConstant">$.id</span><span class="synSpecial">'</span>) <span class="synSpecial">AS</span> id, JSON_VALUE(user_json, <span class="synSpecial">'</span><span class="synConstant">$.name</span><span class="synSpecial">'</span>) <span class="synSpecial">AS</span> name, JSON_VALUE(user_json, <span class="synSpecial">'</span><span class="synConstant">$.age</span><span class="synSpecial">'</span>) <span class="synSpecial">AS</span> age, <span class="synSpecial">FROM</span> test.users <span class="synComment">-- `test` データセットにテーブルを作成</span> <span class="synSpecial">WHERE</span> <span class="synIdentifier">CAST</span>(JSON_VALUE(user_json, <span class="synSpecial">'</span><span class="synConstant">$.age</span><span class="synSpecial">'</span>) <span class="synSpecial">AS</span> int64 ) &gt;= <span class="synConstant">50</span> <span class="synSpecial">ORDER</span> <span class="synSpecial">BY</span> <span class="synIdentifier">CAST</span>(id <span class="synSpecial">AS</span> int64) </pre> <p>このクエリの結果(一部)は次のようになります。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241220/20241220120415.png" width="800" height="261" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/json_functions#json_value">JSON_VALUE</a>関数を用いることで、JSONの値から特定のフィールドの値を抽出できます。 フィールドは<a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/json_functions#JSONPath_format">JSONPath</a>形式で指定します。</p> <p>このようにJSON関数を用いることで、JSONのフィールドで分析できます。</p> <h2 id="JSONをテーブルに変換">JSONをテーブルに変換</h2> <p>JSON関数によってある程度JSONフィールドの値を利用した分析が可能になりますが、別のテーブルとjoinする場合など、辛いケースが出てきます。 そこで、JSONフィールドをフラットなテーブルで表現できないか考えます。</p> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synStatement">SELECT</span> JSON_VALUE(user_json, <span class="synSpecial">'</span><span class="synConstant">$.id</span><span class="synSpecial">'</span>) <span class="synSpecial">AS</span> id, JSON_VALUE(user_json, <span class="synSpecial">'</span><span class="synConstant">$.name</span><span class="synSpecial">'</span>) <span class="synSpecial">AS</span> name, JSON_VALUE(user_json, <span class="synSpecial">'</span><span class="synConstant">$.age</span><span class="synSpecial">'</span>) <span class="synSpecial">AS</span> age, JSON_VALUE(user_json, <span class="synSpecial">'</span><span class="synConstant">$. friends.name</span><span class="synSpecial">'</span>) <span class="synSpecial">AS</span> friend_names, ... <span class="synSpecial">FROM</span> test.users </pre> <p>上記のようにフィールドを1つずつ指定することによってフラットな形式に変換することはできますが、フィールド数が多い場合や構造が複雑な場合は指定が手間になります。またJSONの構造が変わった時に変換のクエリも修正する必要があります。</p> <p>そこで、JSONの実データからスキーマを自動的に抽出し、フラットなテーブルにするSQLを考えました。</p> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synComment">-- 1. UDF: JSONキーと値を同時に取得</span> <span class="synStatement">CREATE</span> TEMP <span class="synSpecial">FUNCTION</span> extract_keys_and_values(input STRING) RETURNS ARRAY&lt;STRUCT&lt;key STRING, value STRING&gt;&gt; LANGUAGE js <span class="synSpecial">AS</span> <span class="synSpecial">&quot;&quot;&quot;</span> <span class="synConstant"> function flattenObj(obj, parent = '', res = []) {</span> <span class="synConstant"> for (let key in obj) {</span> <span class="synConstant"> const propName = parent ? parent + '.' + key : key;</span> <span class="synConstant"> if (typeof obj[key] === 'object' &amp;&amp; obj[key] !== null) {</span> <span class="synConstant"> flattenObj(obj[key], propName, res);</span> <span class="synConstant"> } else {</span> <span class="synConstant"> res.push({ key: propName, value: String(obj[key]) });</span> <span class="synConstant"> }</span> <span class="synConstant"> }</span> <span class="synConstant"> return res;</span> <span class="synConstant"> }</span> <span class="synConstant"> return flattenObj(JSON.parse(input));</span> <span class="synSpecial">&quot;&quot;&quot;</span>; <span class="synComment">-- 2. 一時テーブルの作成: JSONをキーと値のペアに展開</span> <span class="synStatement">CREATE</span> TEMP <span class="synSpecial">TABLE</span> temp_table <span class="synSpecial">AS</span> <span class="synStatement">SELECT</span> row_num, kv.key, kv.value <span class="synSpecial">FROM</span> ( <span class="synStatement">SELECT</span> *, ROW_NUMBER() OVER () <span class="synSpecial">AS</span> row_num, <span class="synSpecial">FROM</span> test.users ), UNNEST(extract_keys_and_values(TO_JSON_STRING(user_json))) <span class="synSpecial">AS</span> kv; <span class="synComment">-- 3. ピボットクエリの動的生成</span> <span class="synStatement">EXECUTE</span> <span class="synSpecial">IMMEDIATE</span> ( <span class="synStatement">SELECT</span> FORMAT(<span class="synSpecial">&quot;&quot;&quot;</span> <span class="synConstant"> SELECT * EXCEPT(row_num)</span> <span class="synConstant"> FROM temp_table</span> <span class="synConstant"> PIVOT (</span> <span class="synConstant"> ANY_VALUE(value) </span> <span class="synConstant"> FOR REPLACE(key, '.', '__') IN (%s)</span> <span class="synConstant"> )</span> <span class="synConstant"> ORDER BY CAST(id AS INT64)</span> <span class="synConstant"> </span><span class="synSpecial">&quot;&quot;&quot;</span>, STRING_AGG(FORMAT(<span class="synSpecial">'</span><span class="synConstant">&quot;%s&quot;</span><span class="synSpecial">'</span>, <span class="synIdentifier">REPLACE</span>(key, <span class="synSpecial">'</span><span class="synConstant">.</span><span class="synSpecial">'</span>, <span class="synSpecial">'</span><span class="synConstant">__</span><span class="synSpecial">'</span>)), <span class="synSpecial">'</span><span class="synConstant">,</span><span class="synSpecial">'</span>)) <span class="synSpecial">FROM</span> ( <span class="synStatement">SELECT</span> <span class="synStatement">DISTINCT</span> key <span class="synSpecial">FROM</span> temp_table ) ); </pre> <p>このようなSQLを実行することによって、<code>user_json</code> を次のようなテーブルに変換できます。 このSQLについて詳しく見ていきます。</p> <h3 id="1-UDF-JSONキーと値を同時に取得">1. UDF: JSONキーと値を同時に取得</h3> <p>はじめに、JSONのキーと値のペアを出力する関数を作成します。 BigQueryでは<a href="https://cloud.google.com/bigquery/docs/user-defined-functions?hl=ja">ユーザー定義の関数</a>を作成することができ、SQLまたはJavaScriptコードで処理を定義できます。 ここでは、JSON文字列を入力とし、キーと値のペアを出力します。ネストされたキーは <code>parent.child</code> 形式に変換します。また、値はすべてSTRING型で返却されます。</p> <pre class="code js" data-lang="js" data-unlink>[ { key: &#34;id&#34;, value: &#34;0&#34; }, { key: &#34;name&#34;, value: &#34;Elijah&#34; }, { key: &#34;age&#34;, value: &#34;78&#34; }, { key: &#34;friends.0.name&#34;: &#34;Michelle&#34; }, { key: &#34;friends.0.hobbies.0&#34;: &#34;Watching Sports&#34; }, ... ]</pre> <h3 id="2-一時テーブルの作成-JSONをキーと値のペアに展開">2. 一時テーブルの作成: JSONをキーと値のペアに展開</h3> <p>次に、この関数を利用してキーと値のペアに展開し、一時テーブルに保存します。 一時テーブルの構造は次のようになります。</p> <table> <thead> <tr> <th>row_num</th> <th>key</th> <th>value</th> </tr> </thead> <tbody> <tr> <td>1</td> <td>id</td> <td>0</td> </tr> <tr> <td>1</td> <td>name</td> <td> Elijah</td> </tr> <tr> <td>1</td> <td>age</td> <td> 78</td> </tr> <tr> <td>1</td> <td>friends.0.name</td> <td> Michelle</td> </tr> <tr> <td>1</td> <td>friends.0.hobbies.0</td> <td>Watching Sports</td> </tr> <tr> <td>...</td> <td></td> <td></td> </tr> <tr> <td>2</td> <td>id</td> <td>1</td> </tr> <tr> <td>2</td> <td>name</td> <td> Noah</td> </tr> <tr> <td>2</td> <td>age</td> <td>97</td> </tr> <tr> <td>2</td> <td>friends.0.name</td> <td> Oliver</td> </tr> <tr> <td>2</td> <td>friends.0.hobbies.0</td> <td>Watching Sports</td> </tr> </tbody> </table> <p>後で各ユーザー毎にグルーピングできるよう一意になる値を持っておく必要があります。今回は <code>ROW_NUMBER</code> 関数で行番号を付与するようにしました。</p> <h3 id="3-ピボットクエリの動的生成">3. ピボットクエリの動的生成</h3> <p>最後に、2.で作成した一時テーブルについて、キー毎に列を作成するピボットテーブルを動的に生成します。 <code>SELECT DISTINCT key FROM temp_table</code> でJSONのキーの一覧を取得し、これをピボットの列として指定します。 また、<a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#execute_immediate">EXECUTE IMMEDIATE</a>を利用し、IN句の条件を動的に組み立てています。 <code>REPLACE(key, '.', '__')</code> は、テーブルの列名に <code>.</code> が使えないためアンダースコア <code>__</code> に書き換えています。</p> <p>このようなクエリを実行することで、最終的にJSONデータをフラット化したテーブルを出力することができます。</p> <table> <thead> <tr> <th>id</th> <th>name</th> <th>age</th> <th>friends__0__name</th> <th>friends__0__hobbies__0</th> <th>...</th> </tr> </thead> <tbody> <tr> <td>0</td> <td> Elijah</td> <td>78</td> <td> Michelle</td> <td>Watching Sports</td> <td>...</td> </tr> <tr> <td>1</td> <td> Noah</td> <td>97</td> <td> Oliver</td> <td>Watching Sports</td> <td>...</td> </tr> </tbody> </table> <h2 id="おわりに">おわりに</h2> <p>BigQueryのJSONカラムに対して、JSON関数で分析する方法や、フラットなテーブルとして出力する方法を紹介しました。</p> <p>ここまで書いておいてになりますが、BigQueryでは公式で<a href="https://cloud.google.com/bigquery/docs/loading-data-cloud-storage-json?hl=ja">ファイルからJSONデータを読み込む方法</a>が提供されているため、データをBigQueryに連携する前に取得できるのであれば、このような機能を利用した方が吉です。 独自のクエリをメンテナンスする必要もなく、また今回のようなSQLはJSONカラムをスキャンするのにコストがかかるため、奥の手として使うのが良いでしょう。</p> <h2 id="We-are-hiring">We are hiring!!</h2> <p>エムスリーでは絶賛エンジニアを募集中です! デジスマ診療以外にも様々なプロダクトがありますので、ご興味ある方は是非カジュアル面談等ご応募ください!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/">jobs.m3.com</a></cite></p> ken_tunc インターンではじめてのプロダクト開発を経験した話【ソフトウェアエンジニアインターン参戦記】 hatenablog://entry/6802418398311074642 2024-12-19T17:00:00+09:00 2024-12-19T17:00:09+09:00 はじめに 11月末から2週間、エンジニアリンググループ AI・機械学習チームでの新卒ソフトウェアエンジニアインターンに参加した山口 (@chimaki_yama821)です。この記事では私が取り組んだことと2週間の感想について書いたので、エムスリーが気になっているという他の学生の参考になれば幸いです。 最終日に撮ってもらった写真 <h2 id="はじめに">はじめに</h2> <p>11月末から2週間、エンジニアリンググループ AI・機械学習チームでの新卒ソフトウェアエンジニアインターンに参加した山口 (<a href="https://x.com/chimaki_yama821">@chimaki_yama821</a>)です。この記事では私が取り組んだことと2週間の感想について書いたので、エムスリーが気になっているという他の学生の参考になれば幸いです。</p> <p><figure class="figure-image figure-image-fotolife" title="最終日に撮ってもらった写真"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241219/20241219170028.jpg" width="800" height="600" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>最終日に撮ってもらった写真</figcaption></figure></p> <h2 id="インターンの課題について">インターンの課題について</h2> <h3 id="やったことの概要">やったことの概要</h3> <p>インターンでは、「動画のタグづけをする」というタスクについて取り組んでいました。扱いたい対象となる動画について確認したのちに、</p> <ol> <li>アウトプットイメージの検討</li> <li>タグづけアルゴリズムの実装</li> <li>クラウドへのデプロイ(途中で終了)</li> </ol> <p>ということに取り組みました。ここで、「タグづけ」とは以下の図のようなことを意味しています。</p> <p><figure class="figure-image figure-image-fotolife" title="動画のタグづけのイメージ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241219/20241219170025.png" width="800" height="359" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>動画のタグづけのイメージ</figcaption></figure></p> <h3 id="扱う動画について">扱う動画について</h3> <p>エムスリーのサービスには、MRさん<a href="#f-0c976af9" id="fn-0c976af9" name="fn-0c976af9" title=" MR とは医薬情報担当者の略で、医師や薬剤師へ薬剤の説明をする製薬会社の営業担当者のこと。">*1</a> が医師に薬剤の説明動画を提供するというプラットフォームがあります。そこで、私はこのインターンで<strong>「MRさんが提供している5分〜10分ほどの薬剤に関する説明動画に対して使いやすい形でタグづけをする」</strong>というタスクをいただき、これについて取り組んできました。</p> <h2 id="やったこと">やったこと</h2> <h3 id="アウトプットイメージの検討">アウトプットイメージの検討</h3> <p>上記のような動画へタグづけを行う、ということで <strong>誰が・どのように嬉しいか?</strong> ということを考慮しながらアウトプットイメージを検討しました。メンターさんとの 1on1 や他の社員さんとのコミュニケーションを通じて、以下の三つにつながると嬉しいと考えました:</p> <h4 id="-コンテンツ作成者が次の動画作成に役立つ">① コンテンツ作成者が次の動画作成に役立つ</h4> <p>過去に作成した動画についての「s秒からt秒までで〇〇を話していた」という情報と視聴者がどこまで見たかの情報を合わせて「タグごとの視聴率」を取得することができます。これを得ることができれば、コンテンツ作成者が <strong>次の動画作成時に気を付けるべきこと</strong>が分かります。</p> <p>例えば、下の図では、 "foo", "bar" について話している時には視聴率が高かったものの "baz" についての話で多くの視聴者が脱落して視聴率が下がってしまっていることが分かります。そこで、次の動画を作る時には "baz" を分かりやすくする・ "qux" を先に説明する、などの工夫をしようと考えることができます。</p> <p><figure class="figure-image figure-image-fotolife" title="内容ベースでの視聴率分析"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241219/20241219170015.png" width="800" height="403" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>内容ベースでの視聴率分析</figcaption></figure></p> <h4 id="-医師が見たいところを見るのに役立つ">② 医師が見たいところを見るのに役立つ</h4> <p>次に、ある動画についてのタグづけを行うことによって、医師(動画視聴者)が <strong>見たいところを選んで見るのに役立つ</strong> と思います。下の図のように、時刻と話している内容をリンク付きで用意することによって、例えば「薬剤Xについては大体知っているがZとの併用効果が気になる」という視聴者が 3:55 から動画を見れば良いことが分かります。</p> <p><figure class="figure-image figure-image-fotolife" title="チャプターがついた動画"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241219/20241219170018.png" width="800" height="316" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>チャプターがついた動画</figcaption></figure></p> <h4 id="-他のデータと組み合わせてより深い分析をするのに役立つ">③ 他のデータと組み合わせてより深い分析をするのに役立つ</h4> <p>最後に、作成したタグとユーザーごとの視聴状況を元に、さらなる分析に繋げることに役立つと考えます。例えば、薬剤Xについての動画で "Zとの併用効果" の部分を見ていないユーザーの方に対して、Zとの併用効果のみの短い動画や記事をレコメンドすることに繋げることができます。また、 「"Zとの併用効果" をみたユーザーでの薬剤Xの利用率」などを分析してさらに他のこともできるかもしれません。</p> <p>以上を踏まえて、インプットとして受け取った動画に対して「タグ+開始時刻+終了時刻」というデータを自動生成することを目指すことになりました。この形でアウトプットが得られれば①〜③のいずれにも使いやすいと考えたためです。他の人が使いやすくするために、<strong>「GCS に配置された動画に対してタグづけを行い BigQuery に保存するバッチ処理」</strong> という形を最終系として目標に掲げました。</p> <p><figure class="figure-image figure-image-fotolife" title="目指すもの"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241219/20241219170011.png" width="800" height="299" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>目指すもの</figcaption></figure></p> <h3 id="タグづけアルゴリズムの実装">タグづけアルゴリズムの実装</h3> <p>実際に動画に対してタグづけを行うにあたっては、 OpenAI の Whisper で文字起こしを行い、その後の処理に GPT を使いました。このうち、 Whisper についてはローカルの CPU 環境でモデルを動かしました。</p> <h4 id="Whisper-による文字起こし">Whisper による文字起こし</h4> <p>Whisper を使うことで、そこそこ高速に文字起こしをすることができました。また、動画で話されていた内容を約1文単位で「文字起こし+開始時刻+終了時刻」という形で得ることもできます。</p> <p>Whisper にはパラメータサイズの異なる複数のモデルがあるのですが、今回はいくつか試して十分な正確さがあって比較的高速に動作するという点で 'medium' (要求 RAM: 5GB ほど)を使用しました。日本語で専門用語を多く含む動画に対しても正確に文字起こしすることができていて、とても役立つものだと感じました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fopenai%2Fwhisper" title="GitHub - openai/whisper: Robust Speech Recognition via Large-Scale Weak Supervision" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/openai/whisper">github.com</a></cite></p> <h4 id="GPT-によるタグづけ">GPT によるタグづけ</h4> <p>Whisper によって取得した文字情報を、GPT によって構造化することでタグづけを行いました。1文ずつでは開始時刻・終了時刻がわかっているため、それをいくつかのセクションに分割して各セクションの概要をタグとして取得する、ということを目指しました。</p> <p>色々と試していく中で、下の図のように「まず文字起こしした全文をセクションごとに分けて要約させて、次にその出力も用いて1文ずつ各セクションに配置する」ということを行いました。一度の生成ではうまくいかなかったのですが、最初に要約を作成させて一度の生成での仕事を減らすことでやりたいことが実現できました。モデルは gpt-4o-mini を使いました<a href="#f-ea19c49a" id="fn-ea19c49a" name="fn-ea19c49a" title="100万トークンの入力でも0.15ドルなのでほとんど無料!">*2</a>。</p> <p><figure class="figure-image figure-image-fotolife" title="GPTによるタグづけ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241219/20241219170021.png" width="800" height="353" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>GPTによるタグづけ</figcaption></figure></p> <h3 id="クラウドへのデプロイ">クラウドへのデプロイ</h3> <p>上記のようにして動画からタグづけを行うまでの処理はコードを実行することでできるようになったのですが、それをどのようにして動かすかが問題になりました。インターン後半ではこの部分に取り組んでいました。</p> <h4 id="機能追加と-GitLab-リポジトリ作成">機能追加と GitLab リポジトリ作成</h4> <p>まずは作成したタグを BigQuery にアップロードする部分と動画を GCS からダウンロードする部分を Python で実装しました。どちらも公式の Python Client が用意されているため比較的容易に行うことができました。</p> <p>これらの機能が整ったところで「プロダクトとしてリポジトリを作ろう」という話になり、 Vermouth(ベルモット) <a href="#f-6ba712db" id="fn-6ba712db" name="fn-6ba712db" title="AIチームでは現在お酒の名前からプロダクト名をつけていて、vから始まるお酒の名前を付けて!となった。難読。">*3</a>という名前で作成しました。 これまで自分はアプリ開発の経験がほとんどなかったため、コードの整理や Poetry などのパッケージ管理ツールの活用には苦戦しました。しかし、MR(マージリクエスト)を作成してレビューを受け、そのフィードバックを踏まえて修正とマージを繰り返すことで、徐々にリポジトリを充実させていくことができました。</p> <h4 id="コンテナでの動作とデプロイ">コンテナでの動作とデプロイ</h4> <p>次にローカルのコンテナ上で動かせるようにしました。Docker はほとんど触れたことがなかったのですが、AI チームの皆さんに丁寧に教えていただくことで何をしているか理解しながらコマンドを実行したり Dockerfile を書いたりすることができました。</p> <p>最後に GKE 上にデプロイしてバッチ処理を行う、というのを途中までやりました。Terraform での BigQuery, GCS リソース管理や権限設定、k8s や Skaffold など、はじめて触れるものだらけでとても勉強になりました。最後まで動かすことはできなかったのはとても悔しいですが、貴重な経験ができました。</p> <h2 id="感想">感想</h2> <p>色々なことに挑戦できてとても楽しい10日間でした。特に、毎日のメンターさんとの 1on1 で今後の方針や困っていることの質問などをしているときに、 <strong>すぐに答えを言わずにこちらに考えさせてくれた</strong> のがよかったです。例えば最初に述べた「何が嬉しいか検討」のところではこちらが答えたことに対して何度も深掘り質問をしてくださったおかげで一人では考えられなかったことまで整理できました。後半にコンテナ周りの分からないことだらけで困っていた時に同じことを何度か聞いてしまったり忘れてしまったりしたことがあったので、聞いた内容をきちんと整理することは今後の課題だと感じました。</p> <p>初日と最終日以外はリモートでのインターンということで集中して取り組めるか不安でしたが、とても充実した期間になりました。毎朝のミーティングや仕事とは関係ない雑談をする夕会でカメラオンで話す機会があることで、コミュニケーションが取りやすいのがよかったです。また、slack での会話や zoom での会話がとても賑やか<a href="#f-f507ee2a" id="fn-f507ee2a" name="fn-f507ee2a" title="15分のミーティングで100件以上のチャットが流れていてすごかった">*4</a>なのもはじめは驚きましたが働きやすさにつながっていると感じました。<a href="https://www.m3tech.blog/entry/2024/12/07/110000">氏家さんの記事</a> で AI・機械学習チームのリモートワークに関することが書かれているので気になる方はこちらもご覧ください。</p> <p>上に述べたことのほかにも、社員さんとの 1on1 ・技術共有会・tech talk など色々なことができました。1on1 ではエムスリーに関する話や就活の話をたくさんできてとても勉強になりました。技術共有会では技術書や論文の輪読会が行われていました。インターン期間中にここで発表もしたかったけど時間が足りずできなかったのが残念。</p> <h2 id="最後に">最後に</h2> <p>私は競プロやデータ分析コンペでちょっとしたコードを書く程度しかしておらず、研究でもコードを書くことがないのですが、今回の経験を通じて自分で何かアプリを作ってみたいと思うようになりました。技術力がついただけでなく、考え方や技術への姿勢まで変わった気がします。</p> <p>メンターの北川さん(<a href="https://x.com/kitagry">@kitagry</a>)、AI・機械学習チームの皆さん、本当にありがとうございました!</p> <p>エムスリーに興味を持っている学生の皆さん、ぜひインターン参加してみてください!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fopen.talentio.com%2Fr%2F1%2Fc%2Fm3%2Fhomes%2F4354" title="【新卒】エンジニアリンググループ 募集一覧 / エムスリー株式会社" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://open.talentio.com/r/1/c/m3/homes/4354">open.talentio.com</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-0c976af9" id="f-0c976af9" name="f-0c976af9" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"> MR とは医薬情報担当者の略で、医師や薬剤師へ薬剤の説明をする製薬会社の営業担当者のこと。</span></p> <p class="footnote"><a href="#fn-ea19c49a" id="f-ea19c49a" name="f-ea19c49a" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">100万トークンの入力でも0.15ドルなのでほとんど無料!</span></p> <p class="footnote"><a href="#fn-6ba712db" id="f-6ba712db" name="f-6ba712db" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">AIチームでは現在お酒の名前からプロダクト名をつけていて、vから始まるお酒の名前を付けて!となった。難読。</span></p> <p class="footnote"><a href="#fn-f507ee2a" id="f-f507ee2a" name="f-f507ee2a" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text">15分のミーティングで100件以上のチャットが流れていてすごかった</span></p> </div> chimaki_yama821 「知ってるつもり」からの脱却:売上構造の解像度を上げるプロダクトマネジメント hatenablog://entry/6802418398312037989 2024-12-19T11:00:00+09:00 2024-12-19T11:00:01+09:00 この記事はエムスリーAdvent Calendar 2024の19日目の記事です。 こんにちは。エンジニアリンググループ プロダクト支援チームでプロダクトマネージャーをしている中村です。私は現在、医師向けのライブ動画配信サービスである「Web講演会」というプロダクトにプロダクトマネージャーとして携わっています。今回の記事では、Web講演会のグロースに取り組む中での学びをご紹介します。 はじめに 「知らないこと」を認識する 「知らないこと」を知るためにやったこと フォーカスする(まずはみかんの売上を2倍に伸ばす) まとめ:プロダクトマネージャーは自信と謙虚さのバランスが大事 We are hir… <p>この記事はエムスリー<a href="https://qiita.com/advent-calendar/2024/m3">Advent Calendar 2024</a>の19日目の記事です。</p> <p>こんにちは。エンジニアリンググループ プロダクト支援チームでプロダクトマネージャーをしている中村です。私は現在、医師向けのライブ動画配信サービスである「Web講演会」というプロダクトにプロダクトマネージャーとして携わっています。今回の記事では、Web講演会のグロースに取り組む中での学びをご紹介します。</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#知らないことを認識する">「知らないこと」を認識する</a></li> <li><a href="#知らないことを知るためにやったこと">「知らないこと」を知るためにやったこと</a></li> <li><a href="#フォーカスするまずはみかんの売上を2倍に伸ばす">フォーカスする(まずはみかんの売上を2倍に伸ばす)</a></li> <li><a href="#まとめプロダクトマネージャーは自信と謙虚さのバランスが大事">まとめ:プロダクトマネージャーは自信と謙虚さのバランスが大事</a></li> <li><a href="#We-are-hiring">We are hiring!!</a></li> </ul> <h2 id="はじめに">はじめに</h2> <p>突然ですが、先日開催されたpmconf2024は参加されましたでしょうか。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2F2024.pmconf.jp%2F" title="プロダクトマネージャーカンファレンス 2024 | pmconf 2024" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://2024.pmconf.jp/">2024.pmconf.jp</a></cite></p> <p>私は1日目にオンラインで参加しました。どのセッションも非常に見応えがあり勉強になったのですが、中でも印象に残ったのが<a href="https://x.com/Haruki_Sonehara">曽根原さん</a>のセッションでした。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2F2024.pmconf.jp%2Fsessions%2FuhfjCACy" title="プロダクトマネージャーの良い意思決定についてQuestする |pmconf2024" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://2024.pmconf.jp/sessions/uhfjCACy">2024.pmconf.jp</a></cite></p> <p>その中で語られていた「プロダクトのインパクトは自分達の意思決定レベル以上に上ブレすることはない」というメッセージが胸に突き刺さっています。</p> <p>というのも、先日Web講演会について取締役CTO兼VPoPの山崎と合宿(という名のMTG)を行ったのですが、そこでの学びが先の「プロダクトのインパクトは自分達の意思決定レベル以上に上ブレすることはない」というメッセージと重なったからです。具体的には、意思決定のレベルをあげるためには売上構造の解像度を上げる必要がある、ということを学びました。</p> <p>ちなみにエムスリーのプロダクトマネージャーが実施する「合宿」については次の記事でご紹介しておりますので、よろしければご覧ください。<iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.m3tech.blog%2Fentry%2F2024%2F10%2F01%2F110000" title="プロダクトの成長を加速させる会議:エムスリーのプロダクトマネージャーが実践する「合宿」とは - エムスリーテックブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.m3tech.blog/entry/2024/10/01/110000">www.m3tech.blog</a></cite></p> <p>ここで、プロダクトマネージャーであれば担当プロダクトの売上構造は理解していて当然のことだと思われるのではないでしょうか。当然高いレベルで理解されているプロダクトマネージャーもいらっしゃると思います。ただ、もしかすると「知ってるつもり」の罠にはまっているケースもあるかもしれません。</p> <h2 id="知らないことを認識する">「知らないこと」を認識する</h2> <p>合宿ではまず山崎からWeb講演会の売上構造やKPIについて質問を受け、実際のKPI管理表を出してKPIの説明をしていきました。私は担当プロダクトマネージャーとして当然説明できると思っていました(何なら合宿で説明できるよう事前準備も入念にしていました)。もちろん答えられることもありましたが、答えられないこともありました。たとえば売上がどのように成立しているかをKPI分解して説明することはできても、いつ、何を、どのように売れば利益が増えるのかという点で答えられないことがありました。</p> <p>このことで、自分が「知ってるつもり」になっており、実は把握できていない部分があるということに気が付きました。</p> <p>ちなみにこれだけ書くと、プロダクトマネージャーとしてただ事業理解が甘かったことを指摘されたように見えるかもしれませんが、そうではなく「知らないこと」がまだあると気づけたことが合宿の成果物となりました。合宿の最後に山崎からは「知らないことがまだあると分かって良かったですね」という非常にポジティブな言葉をもらい、自分の知識の外側にあるものを明らかにしていき、どうしたら売上を増やせるのかを解き明かしていく取り組みに対し、非常に前向きな気持になれました。</p> <p>ちなみに次の本が、この「知ってるつもり」現象について書かれており、面白かったです。</p> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B09DRT8X8H?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="sponsored noopener"><img src="https://m.media-amazon.com/images/I/41+947uh+LL._SL500_.jpg" class="hatena-asin-detail-image" alt="知ってるつもり 無知の科学 (ハヤカワ文庫NF)" title="知ってるつもり 無知の科学 (ハヤカワ文庫NF)"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B09DRT8X8H?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="sponsored noopener">知ってるつもり 無知の科学 (ハヤカワ文庫NF)</a></p><ul class="hatena-asin-detail-meta"><li><span class="hatena-asin-detail-label">作者:</span><a href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%C6%A5%A3%A1%BC%A5%D6%A5%F3%20%A5%B9%A5%ED%A1%BC%A5%DE%A5%F3" class="keyword">スティーブン スローマン</a>,<a href="https://d.hatena.ne.jp/keyword/%A5%D5%A5%A3%A5%EA%A5%C3%A5%D7%20%A5%D5%A5%A1%A1%BC%A5%F3%A5%D0%A5%C3%A5%AF" class="keyword">フィリップ ファーンバック</a></li><li>早川書房</li></ul><a href="https://www.amazon.co.jp/dp/B09DRT8X8H?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="sponsored noopener">Amazon</a></div></div></p> <p>また、自らの考えを疑い、思い込みを手放すことについては次の本にも詳しく記載されています。</p> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B09X6LPM31?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="sponsored noopener"><img src="https://m.media-amazon.com/images/I/51I2Kd+e+gL._SL500_.jpg" class="hatena-asin-detail-image" alt="THINK AGAIN 発想を変える、思い込みを手放す" title="THINK AGAIN 発想を変える、思い込みを手放す"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B09X6LPM31?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="sponsored noopener">THINK AGAIN 発想を変える、思い込みを手放す</a></p><ul class="hatena-asin-detail-meta"><li><span class="hatena-asin-detail-label">作者:</span><a href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%C0%A5%E0%A1%A6%A5%B0%A5%E9%A5%F3%A5%C8" class="keyword">アダム・グラント</a></li><li>三笠書房</li></ul><a href="https://www.amazon.co.jp/dp/B09X6LPM31?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="sponsored noopener">Amazon</a></div></div></p> <h2 id="知らないことを知るためにやったこと">「知らないこと」を知るためにやったこと</h2> <p>今回分かった「知らないこと」について合宿の中で立てた仮説を検証するために、社内の関係者に話を聞きに行き、ファクトを収集しました。</p> <p>たとえば、八百屋の売上の場合、購入者数×客単価と分解できます<a href="#f-97a33f67" id="fn-97a33f67" name="fn-97a33f67" title="突然八百屋が出てくるのは、アナロジーをしばしば活用するエムスリーのプロダクト支援チームの特徴でもありますのでご笑覧くださいw">*1</a>が、その解像度は関係者へのインタビューの前後で大きく異なりました。</p> <ul> <li>Before:「売上=購入者数×客単価」を単なる数式としてとらえ、購入者を増やす方法、客単価を上げる方法、とふんわりと考えている状態</li> <li>After:旬のみかんなら利益率が高く仕入れを増やすことも可能だからもっと売って欲しい。というように、いつ、何を、どのように売れば利益が増えるかを理解している状態</li> </ul> <p>※上記はあくまで例です。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241219/20241219110008.png" width="800" height="420" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="フォーカスするまずはみかんの売上を2倍に伸ばす">フォーカスする(まずはみかんの売上を2倍に伸ばす)</h2> <p>上記のステップでみかんを売れば利益が増えるということが分かったため、みかんの売上を2倍にする方法を考えます。ここでのポイントは、八百屋全体の売上のことは一旦置いておき、徹底してみかんの売上を2倍にすることにフォーカスすることです。</p> <p>なぜ、全体の売上でなくみかんの売上を2倍にしたいのかというと、みかんの売上が2倍にできないならば八百屋全体の売上を2倍にはできないからです。実際に、横展開のことは一旦考えずに特定の案件にフォーカスすることで、全体のKPIを伸ばそうとしていたときには出てこなかったアイデアが次々と出てきました。現在はそれらのアイデアをインパクトの大きいものから順に実施し効果を検証しています。ちなみにこの話は「八百屋理論」として次の記事で詳しく解説されています。<iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Flogmi.jp%2Fmain%2Ftechnology%2F328184" title="KPIの成長を最大化する「八百屋理論」と「F1理論」 PMF→拡散フェーズを生き残る2つの考え方 | ログミーBusiness" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://logmi.jp/main/technology/328184">logmi.jp</a></cite></p> <p>八百屋の売上を上げるためにみかんをもっと売って欲しい、ということに気づかなければみかんの売上を2倍にするというアプローチには結びつきませんでした。ここでは八百屋を例に語りましたが、実際のプロダクトマネジメントにおいても同様です。</p> <h2 id="まとめプロダクトマネージャーは自信と謙虚さのバランスが大事">まとめ:プロダクトマネージャーは自信と謙虚さのバランスが大事</h2> <p>今回の記事では、事業をグロースさせるためには売上構造の解像度を上げる必要があり、その解像度が施策の精度を左右するということを書きました。 特に、自分が事業のことを分かっているという思い込みをいかに捨てられるか、常に自分が知らないことは何かを自問自答し、売上構造の解像度を高めていけるか、ということの重要性と難しさを学びました。</p> <p>このことについて、次の記事ではプロダクトマネージャーに必要な「謙虚さ」として語られていますので、最後にご紹介します。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Flogmi.jp%2Fmain%2Ftechnology%2F328007" title="“デキるPM”と“イマイチなPM”の「ホウレンソウ」の差 成長機会を失わないために、ジュニアPMがやりたいこと | ログミーBusiness" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://logmi.jp/main/technology/328007">logmi.jp</a></cite></p> <blockquote><p>山崎:「これはうまくいくと思っている。けれどもそうでないかもしれない。もっと良い方法があるかもしれない。」「自分はこれで成長できると思っている。だけどもっと良いアドバイスがもらえるかもしれない」と、やはり常に自信と謙虚さのバランスは持っておくのがいいですね。一流プロダクトマネージャーは必ず持っています。</p></blockquote> <p>最後まで読んでくださりありがとうございます。</p> <h2 id="We-are-hiring">We are hiring!!</h2> <p>エムスリーエンジニアリンググループでは、一緒に働く仲間を募集しています! まずはカジュアル面談から、以下URLよりご応募をお待ちしています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/">jobs.m3.com</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-97a33f67" id="f-97a33f67" name="f-97a33f67" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">突然八百屋が出てくるのは、アナロジーをしばしば活用するエムスリーのプロダクト支援チームの特徴でもありますのでご笑覧くださいw</span></p> </div> kananakamu Javaバッチのクラウド移行プロジェクトの泥臭い挑戦 hatenablog://entry/6801883189128074680 2024-12-18T17:00:00+09:00 2024-12-18T17:08:15+09:00 この記事はエムスリー Advent Calendar 2024 の18日目の記事です。 こんにちは、基盤チームエンジニアの桑原です。最近はKeychron Q11を購入してキーボードライフを楽しんでいます。昨日はキースイッチの交換に失敗し、10年ぶりにはんだ付けをして何とか事なきを得ました。 はんだ付けしたら巨大な鉛の塊ができてしまったの図 本日は私が半期取り組んでいたJavaバッチのオンプレからクラウドリフトプロジェクトについて紹介します。なかなか泥臭い作業が多かったのですが、ありのままの仕事内容をお伝えします。 概要 システムの概要 プロジェクトの概要 プロジェクトの背景 脱オンプレを進め… <p>この記事は<a href="https://qiita.com/advent-calendar/2024/m3">エムスリー Advent Calendar 2024</a> の18日目の記事です。</p> <p>こんにちは、基盤チームエンジニアの桑原です。最近はKeychron Q11を購入してキーボードライフを楽しんでいます。昨日はキースイッチの交換に失敗し、10年ぶりにはんだ付けをして何とか事なきを得ました。</p> <p><figure class="figure-image figure-image-fotolife" title="はんだ付けしたら巨大な鉛の塊ができてしまったの図"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241218/20241218170007.jpg" width="800" height="600" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>はんだ付けしたら巨大な鉛の塊ができてしまったの図</figcaption></figure></p> <p>本日は私が半期取り組んでいたJavaバッチのオンプレからクラウドリフトプロジェクトについて紹介します。なかなか泥臭い作業が多かったのですが、ありのままの仕事内容をお伝えします。</p> <ul class="table-of-contents"> <li><a href="#概要">概要</a></li> <li><a href="#システムの概要">システムの概要</a></li> <li><a href="#プロジェクトの概要">プロジェクトの概要</a><ul> <li><a href="#プロジェクトの背景">プロジェクトの背景</a></li> <li><a href="#脱オンプレを進めるにあたってのハードル">脱オンプレを進めるにあたってのハードル</a><ul> <li><a href="#レガシー">レガシー</a></li> <li><a href="#全体像の見えなさ">全体像の見えなさ</a></li> </ul> </li> <li><a href="#実際の移行作業">実際の移行作業</a><ul> <li><a href="#方針決定">方針決定</a></li> <li><a href="#EC2へのリフト">EC2へのリフト</a></li> <li><a href="#全体像">全体像</a></li> </ul> </li> <li><a href="#技術的な課題と解決策">技術的な課題と解決策</a><ul> <li><a href="#パフォーマンスの悪化">パフォーマンスの悪化</a></li> <li><a href="#解決">解決</a></li> </ul> </li> <li><a href="#移行後の効果">移行後の効果</a></li> <li><a href="#成果と今後の展望">成果と今後の展望</a></li> </ul> </li> <li><a href="#まとめ">まとめ</a></li> <li><a href="#We-are-Hiring">We are Hiring!</a></li> </ul> <h2 id="概要">概要</h2> <p>本ブログの目的は、実際の業務内容をオープンにして移行プロジェクトを詳細に紹介することです。</p> <p>今回のプロジェクトでは、OSの保守期限が近い古いオンプレサーバーで動いていたJavaバッチをそのままEC2にリフトしました。また、PHPの部分はAPI GatewayとLambdaを利用して若干の変更を加えました。インフラの管理にはTerraformとAnsibleを使用しています。</p> <p>フルクラウド化を目指しており、今後の進捗についても報告していく予定です。</p> <h2 id="システムの概要">システムの概要</h2> <p>会員向けメールマガジンに差し込む広告エリア(カスタマイズエリア、CA)に、会員毎に最適な広告を決定し差し込むためのシステムです。</p> <p><figure class="figure-image figure-image-fotolife" title="プロジェクト初期概要 "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241218/20241218170010.png" width="701" height="441" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>プロジェクト初期概要 </figcaption></figure> メール配信システムから広告エリアの情報と対象の会員のリストが渡され、ファイルに永続化します。そのファイルをcronでJavaバッチが起動し広告内容を決定し、結果をファイルに吐き出ししています。</p> <h2 id="プロジェクトの概要">プロジェクトの概要</h2> <h3 id="プロジェクトの背景">プロジェクトの背景</h3> <p>オンプレサーバーは帳票環境と呼ばれており、維持費や保守期限の観点から全社的にリプレイスが進められています。本システムも例に漏れず、保守性や安全面の観点から脱オンプレが必要になりました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.m3tech.blog%2Fentry%2F2024%2F12%2F13%2F110000" title="gitレポジトリ考古学に使う道具 - エムスリーテックブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.m3tech.blog/entry/2024/12/13/110000 ">www.m3tech.blog</a></cite></p> <h3 id="脱オンプレを進めるにあたってのハードル">脱オンプレを進めるにあたってのハードル</h3> <h4 id="レガシー">レガシー</h4> <p>歴史的経緯が積み重なり、Javaバッチのソースコードにはカスタマイズエリア出力のため以外のソースコードも大量に含まれています。また、本システムのソースコードは全く別のサービスもビルドしていたため迂闊に手を入れてしまうとそちらに影響を与えてしまう可能性があるリスクもありました。さらに、歴史あるシステムであり、コンテナイメージ化しようとしても一筋縄ではいかない環境でした。</p> <h4 id="全体像の見えなさ">全体像の見えなさ</h4> <p>大規模なJavaバッチのソースコード、シェルスクリプト、ビルドファイルなど、範囲が広い上に他のシステムと同居しているインフラだったため、調査範囲や脱オンプレの作業範囲、完了条件など全体像を把握するのが難しい状態でした。</p> <h3 id="実際の移行作業">実際の移行作業</h3> <h4 id="方針決定">方針決定</h4> <p>JavaバッチとそのInput/Outputには手を一切入れずにクラウドへリフトするという方針を立て、必要最小限の工数で脱オンプレを実現することを最優先にしました。そのため当初構想にはあったもののJavaのコンテナイメージ化は優先度を落としスコープ外に、また、本来はファイルIFの廃止やバッチの進捗通知の改善も行いたかったのですが、肝心の脱オンプレの実現が遅れてしまうため、これもスコープ外としました。</p> <p>Javaバッチのコンテナイメージ化ができないので、自然とクラウドはEC2をベースとして他の必要なサービスを追加していくことにしました。</p> <h4 id="EC2へのリフト">EC2へのリフト</h4> <p>Java自体はさすが <code>Write once, run anywhere</code> であって、心配はしていませんでしたし、実際特に問題なくリフト自体はできました。ただ、周辺のスクリプトの移行はなかなか骨が折れました。Javaのビルドやデプロイはオンプレサーバー上で独自のビルドスクリプトが使われていたので呼び出されている別のスクリプト、必要なライブラリのインストール、ディレクトリ構造などを調査して一歩づつ解決していきました。この作業が泥臭さのピークだったと思います。全部読んだつもりでも動かしていると呼び出しているスクリプトが漏れていたり、暗黙的に必要としているライブラリに気づくことができずに密かにエラーも吐かずに落ちていくのを繰り返しているときは心に来るものがありました。</p> <p>また今回はIaCにAnsibleを選択しました。始めてのAnsibleだったのですが、書き方のレビューはSREチームも協力してくれたため品質担保を取りながら進めることができました。チームの垣根が低いので必要ならチームまたがってレビューを気軽にできる・してもらえる文化なのはとても好きです。</p> <h4 id="全体像">全体像</h4> <p><figure class="figure-image figure-image-fotolife" title="クラウド移行後"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241218/20241218170013.png" width="800" height="545" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>クラウド移行後</figcaption></figure></p> <p>Java以外のPHPなどはEC2に入れたくなかったので API Gateway + Lambda(Python) にするなど、クラウドリフトだけでなくクラウドシフトも多少同時に取り込んでいます。</p> <h3 id="技術的な課題と解決策">技術的な課題と解決策</h3> <h4 id="パフォーマンスの悪化">パフォーマンスの悪化</h4> <p>本番切り替えギリギリのタイミングでパフォーマンスが悪化していることに気づきました。オンプレよりも性能を上げたはずのEC2でもなぜか処理時間が4,5倍に伸びるケースがありました(30分だったケースが2時間になるなど)。オンプレ時代と異なるのはサーバーがクラウドに置かれていることくらいであり、ソースコードやDBはオンプレ時代と同じ環境なのにです。</p> <p>調査を進めていくとどうやらネットワーク遅延が原因だったことがわかりました。オンプレ時代はアプリケーションとデータベースが物理的に近い位置にいたため、アプリケーションをクラウドリフトしたことで(データベースはオンプレのまま)、ネットワーク遅延の影響をもろに受けました。1件1件のネットワーク遅延は数ミリ秒程度であっても、それが数十万の会員数 × 数個の広告の掛け合わせで遅延が無視できない状況になりました。</p> <h4 id="解決">解決</h4> <p>NW起因の性能悪化だったのでサーバーのスペックアップなどでも解決は難しく、DBのチューニングも効果的ではない状態で頭を抱えました。バッチ処理が遅れてしまうと適切な広告を差し込むことができなくなり機会損失にもつながってしまいます。</p> <p>今回はInputファイルをあらかじめn分割して、Javaのプロセスもn等分して起動するという結構な力技で解決しました。これであればJavaバッチに手をいれることなくスレッド競合を気にすることもなくなり、比較的安全に並行処理を行うことができたため今思うとスピード感と品質のバランスをとったアイデアであるなと、提案してくれたメンバーに感謝です。</p> <p>Javaバッチはもともと複数のプロセスが同時に起動しうるため適切なメモリ設定も悩みました。数十万件の処理に必要なメモリサイズと同時並行稼働数を考慮してOSのスペックも決めて行く必要がありました。 今回はjstatでメモリダンプを定期的に取得しながらメモリ使用量の推移を見て、色々条件を変えながらテストを繰り返し、必要最小限のJavaメモリ量を決定していきました。</p> <p><figure class="figure-image figure-image-fotolife" title="50万件実行時のjstatグラフ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241218/20241218170016.png" width="800" height="494" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>50万件実行時のjstatグラフ</figcaption></figure></p> <h3 id="移行後の効果">移行後の効果</h3> <p>今まではオンプレという特性上、他のサービスが同居していたため権限管理として問題があってもSREチームへ作業依頼や調査依頼も必要になっていましたが、EC2を自分たちでも管理するようになったので、調査に必要な権限等は自分たちで取得もできるためシステムにおけるチームの自立性も高まったと思います。</p> <h3 id="成果と今後の展望">成果と今後の展望</h3> <p>来年末には本システムが強く依存している商用DBからの脱却も、コストダウンのため必須になります。そのためここから大規模なシステムの改修が必要になっていきます。今回スコープ外にしたJavaバッチ本体の改修も不可避なので、思い切ったリアーキテクティングも含めてもっとクラウドネイティブな作りに持っていくことができたらいいなと思います。</p> <h2 id="まとめ">まとめ</h2> <p>泥臭い調査や作業を積み重ねて脱オンプレ、クラウドリフトが完遂しました! サーバーの場所がオンプレからクラウドに移動しただけでもNW遅延が無視できない状態になって困るいい経験ができました。</p> <h2 id="We-are-Hiring">We are Hiring!</h2> <p>エムスリーはまだまだJavaやKotlinなどのJVM系言語によるシステム開発も行っております! 興味を持たれたらぜひこちらから!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2F+" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/ ">jobs.m3.com</a></cite></p> kuwavar GPUで高速なモデル推論を実現するために考えること -FlashAttentionはなぜ高速か- hatenablog://entry/6802418398311015222 2024-12-17T18:00:00+09:00 2024-12-20T17:23:27+09:00 こちらはエムスリー Advent Calendar 2024 17日目の記事です。 AI・機械学習チームの髙橋です。チームでは先週からNeurIPS読み会が開催されており、"Deep Learning Architecture, Infrastructure"という深層学習のアーキテクチャに関するセッションを担当しました。その中でも興味深い一本として"You Only Cache Once: Decoder-Decoder Architectures for Language Models"という論文を勉強会まとめブログで紹介してます。 www.m3tech.blog この論文ではLLMの推論… <p>こちらは<a href="https://qiita.com/advent-calendar/2024/m3">エムスリー Advent Calendar 2024</a> 17日目の記事です。</p> <p>AI・機械学習チームの髙橋です。チームでは先週からNeurIPS読み会が開催されており、"Deep Learning Architecture, Infrastructure"という深層学習のアーキテクチャに関するセッションを担当しました。その中でも興味深い一本として"You Only Cache Once: Decoder-Decoder Architectures for Language Models"という論文を勉強会まとめブログで紹介してます。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.m3tech.blog%2Fentry%2F2024%2F12%2F14%2F121059" title="NeurIPS2024が開催中なので、エムスリー AI・機械学習チームの推し論文を勝手に紹介するぜ! - エムスリーテックブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.m3tech.blog/entry/2024/12/14/121059">www.m3tech.blog</a></cite> この論文ではLLMの推論時に用いられるKey Value Cacheという仕組みに着目してGPUメモリの占有量やスループットを大幅に向上させるアーキテクチャを提案していました。非常にモデルが大規模化した昨今、この論文のようにGPUが持つハードウェア特性に応じてモデルのアーキテクチャを改良することがより一層求められています。</p> <p>この類の技術で有名なものとしてFlashAttentionというLLMのAttention機構の推論を高速化するアルゴリズムがあり、論文では最大6倍程度Attention機構の推論速度が向上することが報告されています。しかし、FlashAttentionは行列計算の演算量を削減するわけではありません。それなのになぜこれほど大幅な高速化が達成できたのでしょうか。</p> <p>答えはFlashAttentionの論文タイトルにもなっているとおり"Fast and Memory-Efficient Exact Attention with <span style="font-size: 120%">IO-Awareness</span>"、I/Oが大幅な速度向上の鍵です。 ではなぜこれほどGPUでのI/OがAttention機構の計算でクリティカルなのか、今回は行列演算とI/O、そのバランスに着目して実際の計算量を見積もりしつつGPU上でのモデル推論速度を考えていきます。</p> <h2 id="モデル推論速度を決める2大要素">モデル推論速度を決める2大要素</h2> <p>さて、LLMを始めとする深層学習モデルのアーキテクチャを考えるときには常に性能と推論速度のバランスを考えることになります。 ここでは性能は一旦置いておいて、推論速度を改善したいとき最もよく使われる指標はモデルの演算回数(FLOPs)です。</p> <p>しかし、直感に反して必ずしもGPUでの推論速度は演算回数に比例しません。LLM以外、例えば画像分類モデルでも、MobileNetで提案されたDepth-Wise Convolutionを用いると計算量は何分の一かに削減し、CPU上では実際にそれ相応に推論速度が向上しますが、GPU上での推論速度は実はそれほど速くなりません。</p> <p>このことは例えば<a href="https://openaccess.thecvf.com/content_CVPR_2020/papers/Radosavovic_Designing_Network_Design_Spaces_CVPR_2020_paper.pdf">Designing Network Design Spaces</a>が指摘していて、GPUでの推論時間はFLOPsよりモデルが推論時に中間レイヤーで生成するActivationの総量のほうがより強い相関を持つことが示されています。</p> <p>なぜこうなるかといえばGPUのように高速な行列演算を実現しているハードウェアではメモリのI/O、データ転送速度のほうがボトルネックになることが度々生じるためです。</p> <h2 id="GPUに搭載される2種類のメモリ">GPUに搭載される2種類のメモリ</h2> <p>GPUには大きく分けて2種類のメモリ、HBM(High Bandwidth Memory)とSRAM (Static Random Access Memory)が搭載されています。HBMはいわゆるGPUメモリと呼ばれているもので、例えばNVIDIA GPU H100は141GBのHBMがあり、LLMの推論時にはモデルのパラメータやアクティベーションのキャッシュを保存します。一方でSRAMはGPUで実際に演算を行うためのデータを一時的に保持するためのメモリ空間です。つまり演算を行うにはHBMからSRAMにデータを送り、その演算結果をSRAMからHBMに書き戻す処理が発生します。</p> <p>SRAMは非常に高価な代物で、256KBのアクセスタイムが非常に高速なSRAM<a href="#f-f3b9466d" id="fn-f3b9466d" name="fn-f3b9466d" title="H100だと132個のStreaming Multiprocessor(SM)に1つずつ256KBのSRAMが搭載されています。">*1</a>だと<a href="https://www.mouser.jp/c/semiconductors/memory-ics/sram/">1個あたり1000円以上で売られています(リンクから検索)</a>。NVIDIAがどのスペックのSRAMをいくらで仕入れているかはわかりませんが、H100のようなGPU1基作るのにSRAMメモリのみで数十万円(SMに加えてL2 Cache用に数十MB)を必要とします。また半導体製造技術の観点からもSRAMの物理サイズを小さくすることは難しく、いくらお金を積んでも大量のSRAMを搭載したGPUを製造することは現状では困難なようです。</p> <p>したがって特に昨今の大規模化したLLMの学習/推論ではいかにして限られたHBM↔SRAMのデータ転送帯域を効率的に使うかが推論速度を決定することになります。</p> <h2 id="簡略化したモデルでTransformerの計算量を考える">簡略化したモデルでTransformerの計算量を考える</h2> <p>ここではGPUを1. 行列演算器 2. ベクトル演算器 3. HBM 4. SRAMの4要素だけ構成されると仮定して、LLMの処理の一部を切り出して行列演算とHBM↔SRAM間のデータ転送をモデル化してみましょう。</p> <p><figure class="figure-image figure-image-fotolife" title="H100を簡略化したモデル"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241220/20241220172346.png" width="800" height="397" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>H100を簡略化したモデルの図。演算速度は1000TFLOPs、HBM↔SRAM間の転送速度は1.5T要素/secと仮定する。</figcaption></figure></p> <p>ベクトル演算器はSoftmax関数など行列積以外の処理を行う演算器ですが、LLMで生じるべクトル演算は行列積と比べて微々たるものなので計算量の推計からは除外します。</p> <p>H100の公称では、FP16での演算処理速度は約1000TFLOPs、一方でSRAM↔HRAMのデータ転送の速度は約3TByte/secです。(すべてFP16でデータ転送すると仮定すると1.5T要素/sec) したがって、演算回数がデータ転送量の1000/1.5=<u>660倍</u>程度だと釣り合い、これよりデータ転送量が多くなる(比が660倍より小さい値になる)とI/Oのほうが支配的になると理解できます。<a href="#f-f0d3c6cb" id="fn-f0d3c6cb" name="fn-f0d3c6cb" title="現実にこの演算/データ転送速度を使いきれることはありませんが、ここでは理想化して考えます。">*2</a></p> <h3 id="QKV-projectionのケース">QKV projectionのケース</h3> <p>LLMのEmbedding入力をQuery, Key, ValueにProjection(QKV projection)する処理を例にして考えてみましょう。</p> <p><figure class="figure-image figure-image-fotolife" title="QKV projectionのアルゴリズムと計算量"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241220/20241220172328.png" width="800" height="291" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>QKV projectionのアルゴリズムと計算量。tex記法が崩れてしまったため画像化している。</figcaption></figure></p> <p>各記号は以下の数値を表しています。</p> <ul> <li>B: バッチサイズ</li> <li>S: 系列長</li> <li>E: Embedding次元</li> <li>H: Head次元 (head_dim × head_num)</li> </ul> <p>HBM↔SRAMのデータのやりとりを図にしてみるとこのようになります。</p> <p><figure class="figure-image figure-image-fotolife" title="QKV ProjectionでのHBM↔SRAM間のデータ移動"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241220/20241220172337.png" width="800" height="441" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>QKV ProjectionでのHBM↔SRAM間のデータ移動</figcaption></figure></p> <p>QKV projectionでは演算回数はデータ転送量の<u>1600倍</u>となり、よって行列演算のほうがボトルネックになります。<a href="#f-b9361d1e" id="fn-b9361d1e" name="fn-b9361d1e" title="最近のGPU性能は行列演算性能のほうがデータ転送速度よりも速いペースで向上しているので、将来的にはこれも逆転するかもしれません。">*3</a></p> <h3 id="Attention機構の場合">Attention機構の場合</h3> <p>さきほどはQKV projectionを例にしましたが次にAttention機構を同様に考えてみましょう。冒頭で述べたFlashAttentionなどを使わずPyTorchでナイーブに実装するとAttentionは以下のアルゴリズムで計算され計算量は次のとおりになります。</p> <p><figure class="figure-image figure-image-fotolife" title="通常のAttentionアルゴリズムと計算量"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241220/20241220172330.png" width="800" height="495" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>通常のAttentionアルゴリズムと計算量</figcaption></figure></p> <p>各記号は以下の数値を表しています。</p> <ul> <li>B: バッチサイズ</li> <li>S: 系列長</li> <li>D: head_dim次元</li> </ul> <p>QKV projectionのときと同様にHBM↔SRAMのデータ移動を図式化すると次のようになります。</p> <p><figure class="figure-image figure-image-fotolife" title="AttentionでのHBM↔SRAM間のデータ移動、Attention Map Pを計算するステップ6まで"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241220/20241220172340.png" width="800" height="407" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>AttentionでのHBM↔SRAM間のデータ移動、Attention Map Pを計算するステップ6まで</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="AttentionでのHBM↔SRAM間のデータ移動、ステップ7から9まで"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241220/20241220172343.png" width="797" height="607" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> <figcaption>AttentionでのHBM↔SRAM間のデータ移動、ステップ7から9まで</figcaption></figure></p> <p>演算回数はデータ転送量の<u>90倍</u>にしかならず、I/Oによって律速され大幅に推論速度が低下することになります。</p> <h4 id="なぜIOが支配的か">なぜI/Oが支配的か</h4> <p>これには大きく分けて2つの理由があります。</p> <p>1つは、Attentionの演算/データ転送コストが共に<u>系列長Sの2乗</u>と<u>head_dim次元D</u>に比例し、S>Dであることです。結果として、係数を除くと演算/データ転送コストは<u>head_dim次元D</u>の分しか差が出ずI/Oが支配的になります。</p> <p>もう1つは、アルゴリズムに冗長なHBM↔SRAMの転送が含まれている点です。ステップ4,6ではSoftmaxを実行するためだけに(B, S, S)の転送が生じています。また、ステップ5では一度SRAMに置いてあったPをステップ7でもう一度HBMから転送しています。</p> <h2 id="FlashAttentionによる大幅なIO改善">FlashAttentionによる大幅なI/O改善</h2> <p>FlashAttentionはAttention機構の計算がI/Oによって律速されていることに着目して提案された手法です。通常のAttentionとほぼ等価な計算結果が得られ推論が高速化されるため、PyTorch2.xでは標準利用できるなど現在では非常に普及した技術となっています。</p> <p>I/O改善を目的として、FlashAttentionは以下の方針で導出されたアルゴリズムです。</p> <blockquote><ol> <li>Sequence方向にQKVをブロック分けする</li> <li>ブロックごとにAttentionの最終出力まで計算する</li> <li>ブロック間の依存関係は動的計画法的に更新していく</li> </ol> </blockquote> <p>簡単に補足すると、1のブロック化はGPUの各SMに搭載されるSRAMの容量は限られているためで、ブロック化することでSRAMの容量内でブロックごとの処理をできるようにしています。 2はブロックごとにSoftmaxに相当する部分を計算してそのブロックの出力Oにあたる部分まで計算するため、通常のAttention処理で生じたSoftmaxを計算するためのデータ転送を省くことができます。</p> <p>実際の処理を書き下すことは省きますが、演算回数とデータ転送量はそれぞれ以下のとおりになります。</p> <p><figure class="figure-image figure-image-fotolife" title="FlashAttentionの計算量"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241220/20241220172334.png" width="800" height="134" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>FlashAttentionの計算量</figcaption></figure></p> <p>ここで新たに登場した<strong>k</strong>はGPUに搭載されているSMあたりのSRAMの容量に依存しているハイパーパラメータ(SRAM容量が大きければ大きいほどブロックサイズは大きくできる)で論文中ではA100 GPUでk=256を採用していました。</p> <p>通常のAttention処理と比較すると演算回数/SRAM↔HRAMのデータ転送量はおおよそ3倍になっておりI/O律速を大幅に改善できていることが分かります。</p> <p>これはブロック化によってこれまでSの2乗に比例していたデータ転送量の項が<u>D/k</u>分削減できること、加えてXやPなどを一々HBMに書き戻さなくてよくなっているためです。</p> <h2 id="まとめ">まとめ</h2> <p>GPUで高速なモデル推論を実現するにはモデルの演算量のみではなくHBM↔SRAM間のデータ転送量にも着目することが大事です。</p> <p>特に近年はモデルが大規模化したことでハードウェア側の制約に着目した技術の重要度がますます高まっていきそうです。こうした研究や技術を引き続き追っていきたいと思います。</p> <h2 id="We-are-hiring-">We are hiring !!</h2> <p>エムスリーAI・機械学習チームではLLMや臨床AIに取り組んでいます。モデルのアーキテクチャや計算アルゴリズムにワクワクする皆さん、エムスリーAI・機械学習チームで一緒に機械学習エンジニアやりましょう! また、学生の皆さん向けには機械学習・MLOpsインターンも募集してます。ぜひ一緒に論文を読みサービス開発していきましょう。</p> <p>エムスリーでは、コンピュータビジョン・機械学習はもちろん、最新技術へのアンテナが高い仲間を歓迎しています。新卒・中途それぞれの採用、カジュアル面談やインターンも常時募集しています!</p> <h3 id="エンジニア採用ページはこちら">エンジニア採用ページはこちら</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/">jobs.m3.com</a></cite></p> <h3 id="カジュアル面談もお気軽にどうぞ">カジュアル面談もお気軽にどうぞ</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2Fcasual-session%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/casual-session/">jobs.m3.com</a></cite></p> <h3 id="インターンも常時募集しています">インターンも常時募集しています</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fopen.talentio.com%2Fr%2F1%2Fc%2Fm3%2Fhomes%2F4354" title="【新卒】エンジニアリンググループ 募集一覧 / エムスリー株式会社" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://open.talentio.com/r/1/c/m3/homes/4354">open.talentio.com</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-f3b9466d" id="f-f3b9466d" name="f-f3b9466d" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">H100だと132個のStreaming Multiprocessor(SM)に1つずつ256KBのSRAMが搭載されています。</span></p> <p class="footnote"><a href="#fn-f0d3c6cb" id="f-f0d3c6cb" name="f-f0d3c6cb" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">現実にこの演算/データ転送速度を使いきれることはありませんが、ここでは理想化して考えます。</span></p> <p class="footnote"><a href="#fn-b9361d1e" id="f-b9361d1e" name="f-b9361d1e" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">最近のGPU性能は行列演算性能のほうがデータ転送速度よりも速いペースで向上しているので、将来的にはこれも逆転するかもしれません。</span></p> </div> eufo 友達の友達が多い!友達パラドックスについて hatenablog://entry/6802418398312053436 2024-12-17T13:54:47+09:00 2024-12-17T13:54:47+09:00 エンジニアチームのUK. ( @ukohank517 )です。このブログはエムスリーアドベントカレンダー15日目の記事です。 今日は友達に関すパラドックス(Friendship Paradox)について紹介したいと思います! はじめに 友達パラドックスとは? 友達数のシミュレーションをしてみよう まとめ 追記 We are hiring !! エンジニアの採用ページはこちら カジュアル面談もお気軽にどうぞ インターンも常時募集しています はじめに 実は10年も前の論文で発表された内容なんですが、最近見かけて読んだら思わず「えっ、こんなことあるんだ!」と驚いてしまったんです。 気になる方はこちら… <p>エンジニアチームのUK. ( @ukohank517 )です。このブログは<a href="https://qiita.com/advent-calendar/2024/m3">エムスリーアドベントカレンダー</a>15日目の記事です。 今日は友達に関すパラドックス(Friendship Paradox)について紹介したいと思います!</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#友達パラドックスとは">友達パラドックスとは?</a></li> <li><a href="#友達数のシミュレーションをしてみよう">友達数のシミュレーションをしてみよう</a></li> <li><a href="#まとめ">まとめ</a></li> <li><a href="#追記">追記</a></li> <li><a href="#We-are-hiring-">We are hiring !!</a><ul> <li><a href="#エンジニアの採用ページはこちら">エンジニアの採用ページはこちら</a></li> <li><a href="#カジュアル面談もお気軽にどうぞ">カジュアル面談もお気軽にどうぞ</a></li> <li><a href="#インターンも常時募集しています">インターンも常時募集しています</a></li> </ul> </li> </ul> <h1 id="はじめに">はじめに</h1> <p>実は10年も前の論文で発表された内容なんですが、最近見かけて読んだら思わず「えっ、こんなことあるんだ!」と驚いてしまったんです。</p> <p>気になる方はこちらのリンクから論文をチェックしてみてくださいね:</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.nature.com%2Farticles%2Fsrep04603" title="Generalized friendship paradox in complex networks: The case of scientific collaboration - Scientific Reports" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.nature.com/articles/srep04603">www.nature.com</a></cite></p> <h1 id="友達パラドックスとは">友達パラドックスとは?</h1> <p>友達パラドックス(Friendship Paradox)とは、シンプルに言うと、「あなたの友達はあなたよりも友達が多い傾向にある」という現象です。</p> <p>でも、みなさんもこんな風に感じたことってありませんか?</p> <p>「あの人、なんでこんなに友達が多いんだろう?」 SNSでフォロワー数が多いと、「ああ、私は友達少ないなぁ…」ってちょっと落ち込んじゃうこと。 私の実体験だと、大学の昼休みに友達と食堂に行って、列に並んでたら、友達がすれ違った人のほとんどに挨拶していたんです。その光景を見て「こいつがコミュニケーションの怪物だ!」って感じたこと、ありますよね。</p> <p>でも、これって実は気のせいではなく、統計的に証明された現象なんですよ! この現象があることを、私はある論文を読んで知りました。なお実際にシミュレーションしても同じ結果が得られます!</p> <h1 id="友達数のシミュレーションをしてみよう">友達数のシミュレーションをしてみよう</h1> <p>実際に「本当に友達の方が友達が多いのか?」をシミュレーションしてみました。 次のコードを使って、簡単な統計シミュレーションを行いました。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> networkx <span class="synStatement">as</span> nx <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt <span class="synPreProc">import</span> random <span class="synComment"># ランダムグラフを作成する関数(友人数の分布を制御)</span> <span class="synStatement">def</span> <span class="synIdentifier">generate_biased_graph</span>(num_nodes): graph = nx.Graph() graph.add_nodes_from(<span class="synIdentifier">range</span>(num_nodes)) <span class="synStatement">for</span> node <span class="synStatement">in</span> graph.nodes(): <span class="synComment"># 各ノードの友人数を確率的に決定</span> <span class="synStatement">if</span> random.random() &lt; <span class="synConstant">0.5</span>: <span class="synComment"># 50%の確率で少ない友人</span> num_friends = random.randint(<span class="synConstant">1</span>, <span class="synConstant">3</span>) <span class="synStatement">else</span>: <span class="synComment"># 50%の確率で多い友人</span> num_friends = random.randint(<span class="synConstant">5</span>, <span class="synConstant">10</span>) <span class="synComment"># エッジをランダムに生成</span> possible_friends = <span class="synIdentifier">list</span>(<span class="synIdentifier">set</span>(graph.nodes()) - {node} - <span class="synIdentifier">set</span>(graph.neighbors(node))) friends = random.sample(possible_friends, <span class="synIdentifier">min</span>(num_friends, <span class="synIdentifier">len</span>(possible_friends))) graph.add_edges_from((node, friend) <span class="synStatement">for</span> friend <span class="synStatement">in</span> friends) <span class="synStatement">return</span> graph <span class="synComment"># ノード数</span> num_nodes = <span class="synConstant">30</span> graph = generate_biased_graph(num_nodes) <span class="synComment"># 各ノードの友人の数と友人の友人の平均数を計算</span> friend_counts = [] friends_of_friends_avg_counts = [] <span class="synStatement">for</span> node <span class="synStatement">in</span> graph.nodes(): friends = <span class="synIdentifier">list</span>(graph.neighbors(node)) friend_counts.append(<span class="synIdentifier">len</span>(friends)) <span class="synStatement">if</span> <span class="synIdentifier">len</span>(friends) &gt; <span class="synConstant">0</span>: friends_of_friends = [<span class="synIdentifier">len</span>(<span class="synIdentifier">list</span>(graph.neighbors(friend))) <span class="synStatement">for</span> friend <span class="synStatement">in</span> friends] friends_of_friends_avg_counts.append(np.mean(friends_of_friends)) <span class="synStatement">else</span>: friends_of_friends_avg_counts.append(<span class="synConstant">0</span>) <span class="synComment"># グラフ形式でノードと関係性を可視化</span> plt.figure(figsize=(<span class="synConstant">12</span>, <span class="synConstant">8</span>)) pos = nx.spring_layout(graph, seed=<span class="synConstant">42</span>) <span class="synComment"># ノードの配置を決定</span> nx.draw(graph, pos, with_labels=<span class="synIdentifier">True</span>, node_size=<span class="synConstant">500</span>, node_color=<span class="synConstant">&quot;skyblue&quot;</span>, font_size=<span class="synConstant">10</span>, font_color=<span class="synConstant">&quot;black&quot;</span>, edge_color=<span class="synConstant">&quot;gray&quot;</span>) plt.title(<span class="synConstant">&quot;Visualization of the Biased Random Graph&quot;</span>) plt.show() <span class="synComment"># 友人の数と友人の友人の平均数のヒストグラムを表示</span> plt.figure(figsize=(<span class="synConstant">12</span>, <span class="synConstant">8</span>)) plt.hist(friend_counts, bins=<span class="synConstant">20</span>, color=<span class="synConstant">&quot;skyblue&quot;</span>, alpha=<span class="synConstant">0.7</span>, label=<span class="synConstant">&quot;Friend Counts&quot;</span>) plt.hist(friends_of_friends_avg_counts, bins=<span class="synConstant">10</span>, color=<span class="synConstant">&quot;gray&quot;</span>, alpha=<span class="synConstant">0.7</span>, label=<span class="synConstant">&quot;Friends of Friends Avg Counts&quot;</span>) plt.xlabel(<span class="synConstant">&quot;Number of Friends&quot;</span>) plt.ylabel(<span class="synConstant">&quot;Number of Nodes&quot;</span>) plt.title(<span class="synConstant">&quot;Histogram of the Number of Friends and the Average Number of Friends of Friends&quot;</span>) plt.legend() plt.show() <span class="synComment"># 全体の友人の平均数と友人の友人の平均数を計算</span> avg_friend_counts = np.mean(friend_counts) avg_friends_of_friends_avg_counts = np.mean(friends_of_friends_avg_counts) <span class="synIdentifier">print</span>(f<span class="synConstant">&quot;Average number of friends: {avg_friend_counts}&quot;</span>) <span class="synIdentifier">print</span>(f<span class="synConstant">&quot;Average number of friends of friends: {avg_friends_of_friends_avg_counts}&quot;</span>) </pre> <p>このシミュレーションでは、30人のグループにおいて、半分の人々は少ない友達(1~3人)、半分の人々は多い友達(10人以上)を持つように分けています。そして、全体の友達関係は下記のようなネットワーク図で可視化されます。</p> <p><figure class="figure-image figure-image-fotolife" title="友達ネットワーク図"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241217/20241217135457.png" width="800" height="559" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>友達ネットワーク図</figcaption></figure></p> <p>その後、友達の数と、友達の友達の数をそれぞれヒストグラム上で表現すると:</p> <p><figure class="figure-image figure-image-fotolife" title="友達の数、友達の友達の数のヒストグラム"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241217/20241217135453.png" width="800" height="567" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>友達の数、友達の友達の数のヒストグラム</figcaption></figure></p> <p>ヒストグラムから、友達の数を統計すると友達が少ない人もいれば多い人もいます。しかし友達の友達の統計は、全体の右寄りになっており、結果として、過半数の人は、友達の数よりも友達の友達が多くなる傾向が見られました。なお、全員に対して友人の平均数が9.7人前後でですが、友人の友人の平均数は11.3人前後とちょっと多めになります。</p> <p>さて、なぜ友達の友達の方が多くなるのでしょうか? その理由は簡単です。<strong>友達が多い人は、あなたと友達になる確率が高い</strong>からです。</p> <p>ちなみに、友達の友達の数の期待値は次の式で表されます:</p> <div align='center' class='scroll'> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0AE%20%3D%20%5Cmu%20%2B%20%5Cfrac%7B%5Csigma%5E2%7D%7B%5Cmu%7D%20%5C%5C%0A%5Cmu%20%3D%20%E5%85%A8%E3%81%A6%E3%81%AE%E4%BA%BA%E3%81%AE%E5%B9%B3%E5%9D%87%E5%8F%8B%E4%BA%BA%E6%95%B0%E3%80%81%20%5Csigma%20%3D%20%E5%8F%8B%E4%BA%BA%E6%95%B0%E3%81%AE%E6%A8%99%E6%BA%96%E5%81%8F%E5%B7%AE%0A" alt=" \displaystyle E = \mu + \frac{\sigma^2}{\mu} \\ \mu = &#x5168;&#x3066;&#x306E;&#x4EBA;&#x306E;&#x5E73;&#x5747;&#x53CB;&#x4EBA;&#x6570;&#x3001; \sigma = &#x53CB;&#x4EBA;&#x6570;&#x306E;&#x6A19;&#x6E96;&#x504F;&#x5DEE; "/> </div> <p>繰り返しになりますが、論文の本文をぜひ一度読んでみましょう:</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.nature.com%2Farticles%2Fsrep04603" title="Generalized friendship paradox in complex networks: The case of scientific collaboration - Scientific Reports" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.nature.com/articles/srep04603">www.nature.com</a></cite></p> <h1 id="まとめ">まとめ</h1> <p>友情のパラドックスと同じような事象は、実は社会の至るところで見られます。たとえば、航空会社が「平均の満席率は高くない」と統計で言っても、実際に乗っているお客さんにアンケートを取ると、「混んでる!」と感じる人が多く集まるのが常ですよね。これは、混んでいる便に乗っている人が多いからです。</p> <p>また、社会全体で見れば、技術好きな人は少数派かもしれませんが、この記事を読んでいるあなたは、ほぼ間違いなく技術が好きだと思います。だって、アドベントカレンダーを読むってことは、少なくともある程度の技術に興味がある証拠ですから。</p> <p>友情のパラドックスをさらに広げると、友達の数だけじゃなくて、友達が自分より優れていると感じることも説明できちゃうんです。</p> <p>でも、ここでネガティブに捉える必要はまったくありません! この現象は、基本的にネットワークの構造が生み出すものですし、それがあなたの価値に影響を与えるわけではないからです。</p> <p>SNSが普及した今の時代では、こうしたパラドックスをより感じやすい環境が整っています。ただし、こういった現象があることを知っていれば、他の人との比較に対して、少し肩の力を抜いて前向きに受け止められるかもしれません。</p> <p>最後に、「隣の芝生は青い」というのは、実は普通のことだということを理解してもらえれば嬉しいです。</p> <h1 id="追記">追記</h1> <p>advent of codeもやっております! ちょっと先週今週寝る時間も削って色々やって遅れ気味ですが、頑張って追い上げますので、興味ある方は下記リードボードに参加して一緒に今月解きましょう</p> <p><code>3533898-f84e2f85</code></p> <h1 id="We-are-hiring-">We are hiring !!</h1> <p>エムスリーでは技術大好きなギークエンジニアを募集しています! 少しでも興味がある方は、下記URLからカジュアル面談をご応募ください!</p> <h2 id="エンジニアの採用ページはこちら">エンジニアの採用ページはこちら</h2> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/">jobs.m3.com</a></cite></p> <h2 id="カジュアル面談もお気軽にどうぞ">カジュアル面談もお気軽にどうぞ</h2> <p>(ばんくしさんと話せるチャンス! @vaaaaanquish )</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2Fcasual-session%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/casual-session/">jobs.m3.com</a></cite></p> <h2 id="インターンも常時募集しています">インターンも常時募集しています</h2> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fopen.talentio.com%2Fr%2F1%2Fc%2Fm3%2Fhomes%2F4354" title="【新卒】エンジニアリンググループ 募集一覧 / エムスリー株式会社" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://open.talentio.com/r/1/c/m3/homes/4354">open.talentio.com</a></cite></p> ukohank517 リサーチプロダクトチームがここ最近やってることをざっくり紹介するぜ hatenablog://entry/6802418398312011175 2024-12-16T18:30:00+09:00 2024-12-16T19:03:15+09:00 こんにちは、エムスリーエンジニアリンググループ/ BIR(Business Intelligence and Research) チーム の遠藤(@en_ken)です。 BIRはアンケートシステムをはじめとするリサーチプロダクトを開発しているチームです。チームの詳しい紹介は次の資料を読んでいただければと思います。 speakerdeck.com ここ1年ほどでチームの様相は様変わりしました。 一時期エンジニア3,4名ほどしかいなかったチーム状況から現在はエンジニアだけでも9名まで増え、PdMが2名参画したことで開発状況もこれまで以上に活況になってきています。開発スタイルも個人開発に近かったもの… <p>こんにちは、エムスリーエンジニアリンググループ/ BIR(Business Intelligence and Research) チーム の遠藤(<a href="https://x.com/en_ken">@en_ken</a>)です。</p> <p>BIRはアンケートシステムをはじめとするリサーチプロダクトを開発しているチームです。チームの詳しい紹介は次の資料を読んでいただければと思います。</p> <p><iframe id="talk_frame_1125741" class="speakerdeck-iframe" src="//speakerdeck.com/player/77c248784c9c49b49ad993e931ff8c24" width="710" height="399" style="aspect-ratio:710/399; border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen"></iframe> <cite class="hatena-citation"><a href="https://speakerdeck.com/m3_engineering/introduction-of-m3-research-product-team">speakerdeck.com</a></cite></p> <p>ここ1年ほどでチームの様相は様変わりしました。 一時期エンジニア3,4名ほどしかいなかったチーム状況から現在はエンジニアだけでも9名まで増え、PdMが2名参画したことで開発状況もこれまで以上に活況になってきています。開発スタイルも個人開発に近かったものからチーム開発にシフトしました。</p> <p>チームの状況だけではなく、我々の開発運用しているアンケートシステムについてもリアーキテクトの方向性を明確にしたことで、チーム全体でそこに向けて開発を進めています。今回の記事では、BIRのここ最近のアンケートシステム開発の概要を紹介させていただきます。</p> <h2 id="アンケートシステムのアーキテクチャと課題">アンケートシステムのアーキテクチャと課題</h2> <p>冒頭で紹介した通り、BIRは主要プロダクトとしてアンケートシステムの開発を行っています。 2024年初時点で、BIRで管理していたアンケートシステムのアーキテクチャ概要は次のようになっていました。</p> <p><figure class="figure-image figure-image-fotolife" title="24年初頭時点のアーキテクチャ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241216/20241216183003.png" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>24年初頭時点のアーキテクチャ</figcaption></figure></p> <p>アンケートを回収するには、会員の方に「アンケートの存在を認知してもらいアクセスしてもらう」こと、そして実際に「アンケートに回答してもらう」ことが必要になりますが、このアーキテクチャではその役割を明確に分離しています。 すなわち、アンケートの配信管理を行うシステム(Dolphin)が会員のアクセスを集めることに責任を持ち、集めたアクセスは各アンケートシステムにリダイレクトし、個別のアンケートの回答処理は各アンケートシステムが受け持つという構造です。(詳しくは前TLの<a href="https://services.google.com/fh/files/events/d1-da-09.pdf">滝安さんの発表資料</a>を参照のこと)</p> <p>アンケートの督促や流入のコントロールをDolphinに集約するこの構造自体は非常に機能しています。一方で、各アンケートシステム側は問題点を抱えていました。</p> <ul> <li>機能の重複</li> <li>システムのレガシー化</li> </ul> <p>の2点です。</p> <p>歴史的な経緯で旧来のアンケートシステムを整理統合しないまま、新たなアンケートシステムが立ち上げられてきた結果、年初の時点で計5つのアンケートシステムが運用され、4つのシステムがそれぞれにアンケート回答画面を保有していました。 このことは、例えば、回答画面UIを画一的に刷新したい場合、システム数分だけ工数が膨れ上がることを意味しています。</p> <p>また、年初時点で10年近く運用されているシステムも存在していました。ここまで10年選手でサービスを支えてくれたという意味では非常に優秀なシステムではありますが、 近年は継続的なメンテナンスがされてこなかった背景もあり、今後のアップデートが難しい状態にありました。</p> <p>こうした状況は、ビジネスの要求にアジリティ高く応えていく上で足かせとなっていました。</p> <h2 id="リアーキテクチャの方針">リアーキテクチャの方針</h2> <p><figure class="figure-image figure-image-fotolife" title="なりたい姿"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241216/20241216183010.png" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>なりたい姿</figcaption></figure></p> <p>こうした状況を打破するため、チームでは次の様なリアーキテクチャの方針をもとに整理を進めています。</p> <ul> <li>アンケート回答機能は1つのシステムに集約する。</li> <li>アンケート配信形式のユースケースによる複雑さは、各アンケート管理側のシステムが吸収する。</li> </ul> <p>エムスリーのアンケートには用途に応じて、単発のアンケートの他に、数ヶ月に1回など定期的に収集する形式、常時回答できる形式などのアンケート配信形式があります。既存のアンケートシステムを整理していくと、設問形式などアンケート内容自体の特殊性というよりも、この配信形式のバリエーションに対応するために立ち上げられてきた側面が強いという結論に至りました。したがって、配信形式の複雑さを分離できれば、理想的には柔軟性の高い1つの回答システムと3つ程度の管理システムに整理統合できるという仮説を持って開発を進めていくことにしました。</p> <h2 id="これまでの開発とこれから">これまでの開発とこれから</h2> <p><figure class="figure-image figure-image-fotolife" title="24年末現在のアーキテクチャ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241216/20241216183007.png" width="800" height="450" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>24年末現在のアーキテクチャ</figcaption></figure></p> <p>現在はこのゴールを目指して開発を進めており、11月には新アーキテクチャの方針に沿った常設型アンケートシステムの1つ(MedicalChannelMix)のリプレースが完了しました。</p> <p>このリプレースのため、汎用的なアンケート回答システムであるLion、常設型アンケート管理システムZebraというシステムを新たにリリースしました。</p> <p>BIRはサーバーサイドの言語はGoをファーストチョイスとするチームですが、Lionはフロントエンド側の複雑なアンケート定義型の扱いやすさを鑑みてチーム初のサーバーサイドTypeScriptを採用しています。</p> <p>また、上記の図の外にはなりますが、アンケート回答データをMedicalChannelMixのデータプロダクトとして提供するためのETLパイプライン Rhinoも同時期にリリースしています。こちらもDataformをチームで初めて採用するなど、新しい技術へのトライも行っています。</p> <p>現時点では、まだ1システムのリプレースのみであるため、機能重複の解消には至っていませんが、10年もののレガシーシステムの整理はこれで完了しました。</p> <p>今後は、常設型の他のアンケートをLionとZebraに整理統合していくことが直近のミッションになり、その後他のシステムも整理していく予定です。</p> <h2 id="まとめ">まとめ</h2> <p>我々のチームの直近の開発をかいつまんで駆け足で紹介しました。</p> <p>上記では「汎用的なアンケート回答システム」とサラッと書いていますが、実際にはアンケートの定義をどう汎化していくか、アンケート回答システム側に配信方式の複雑さをいかに持ち込ませないかなど設計課題は多く、日々新たな知見が得られています。</p> <p>また、Lionはあまり世の中に知見が少ないと思われるサーバーサイドTypeScript x Cloud Spannerの組み合わせで利用しており、こうした開発の詳細についてもまた別の機会に紹介できればと思います。</p> <h2 id="We-are-hiring">We are hiring!!</h2> <p>エムスリーエンジニアリンググループでは、一緒に働く仲間を募集しています!</p> <p>BIRの開発に少しでも興味を持った方は、まずはカジュアル面談から、以下URLよりご応募をお待ちしています。</p> <h3 id="エンジニア採用ページはこちら">エンジニア採用ページはこちら</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/">jobs.m3.com</a></cite></p> <h3 id="カジュアル面談もお気軽にどうぞ">カジュアル面談もお気軽にどうぞ</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2Fcasual-session%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/casual-session/">jobs.m3.com</a></cite></p> <h3 id="インターンも常時募集しています">インターンも常時募集しています</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fopen.talentio.com%2Fr%2F1%2Fc%2Fm3%2Fhomes%2F4354" title="【新卒】エンジニアリンググループ 募集一覧 / エムスリー株式会社" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://open.talentio.com/r/1/c/m3/homes/4354">open.talentio.com</a></cite></p> enkn NeurIPS2024が開催中なので、エムスリー AI・機械学習チームの推し論文を勝手に紹介するぜ! hatenablog://entry/6802418398309287870 2024-12-14T12:10:59+09:00 2024-12-15T00:22:33+09:00 こんにちは。エンジニアリンググループのAI・機械学習チームに所属している鴨田 です。 このブログはエムスリーアドベントカレンダー14日目の記事です。 弊チームでは毎週1時間の技術共有会を実施しており、各自が担当するプロダクトの技術や、最近読んだ論文を紹介しています。今週はNeurIPS2024が開催されていることもあり、同学会の論文読み会となりました。1セッション1名の担当で、各自がセッション内で気になった論文の詳細を解説します。本ブログではその一部として、セッションごとの「推し論文」を紹介します。 DALL-E 3で生成した「機械学習エンジニアが勉強会でお気に入りの記事について楽しそうに雑談… <p>こんにちは。エンジニアリンググループのAI・機械学習チームに所属している鴨田 です。</p> <p>このブログは<a href="https://qiita.com/advent-calendar/2024/m3">エムスリーアドベントカレンダー</a>14日目の記事です。</p> <p>弊チームでは毎週1時間の技術共有会を実施しており、各自が担当するプロダクトの技術や、最近読んだ論文を紹介しています。今週は<a href="https://nips.cc/Conferences/2024">NeurIPS2024</a>が開催されていることもあり、同学会の論文読み会となりました。1セッション1名の担当で、各自がセッション内で気になった論文の詳細を解説します。本ブログではその一部として、セッションごとの「推し論文」を紹介します。</p> <p><figure class="figure-image figure-image-fotolife" title="DALL-E 3で生成した「機械学習エンジニアが勉強会でお気に入りの記事について楽しそうに雑談している様子」"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241214/20241214121122.png" width="800" height="457" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>DALL-E 3で生成した「機械学習エンジニアが勉強会でお気に入りの記事について楽しそうに雑談している様子」</figcaption></figure></p> <ul class="table-of-contents"> <li><a href="#You-Only-Cache-Once-Decoder-Decoder-Architectures-for-Language-Models">You Only Cache Once: Decoder-Decoder Architectures for Language Models</a><ul> <li><a href="#推しポイント">推しポイント</a></li> </ul> </li> <li><a href="#Decompose-Analyze-and-Rethink-Solving-Intricate-Problems-with-Human-like-Reasoning-Cycle">Decompose, Analyze and Rethink: Solving Intricate Problems with Human-like Reasoning Cycle</a><ul> <li><a href="#推しポイント-1">推しポイント</a></li> </ul> </li> <li><a href="#RG-SAN-Rule-Guided-Spatial-Awareness-Network-for-End-to-End-3D-Referring-Expression-Segmentation">RG-SAN: Rule-Guided Spatial Awareness Network for End-to-End 3D Referring Expression Segmentation</a><ul> <li><a href="#推しポイント-2">推しポイント</a></li> </ul> </li> <li><a href="#GIC-Gaussian-Informed-Continuum-for-Physical-Property-Identification-and-Simulation">GIC: Gaussian-Informed Continuum for Physical Property Identification and Simulation</a><ul> <li><a href="#推しポイント-3">推しポイント</a></li> </ul> </li> <li><a href="#Convolutional-Differentiable-Logic-Gate-Networks">Convolutional Differentiable Logic Gate Networks</a><ul> <li><a href="#推しポイント-4">推しポイント</a></li> </ul> </li> <li><a href="#Unlocking-the-Capabilities-of-Thought-A-Reasoning-Boundary-Framework-to-Quantify-and-Optimize-Chain-of-Thought">Unlocking the Capabilities of Thought: A Reasoning Boundary Framework to Quantify and Optimize Chain-of-Thought</a><ul> <li><a href="#推しポイント-5">推しポイント</a></li> </ul> </li> <li><a href="#Guiding-a-Diffusion-Model-with-a-Bad-Version-of-Itself">Guiding a Diffusion Model with a Bad Version of Itself</a><ul> <li><a href="#推しポイント-6">推しポイント</a></li> </ul> </li> <li><a href="#We-are-hiring-">We are hiring !!</a><ul> <li><a href="#エンジニア採用ページはこちら">エンジニア採用ページはこちら</a></li> <li><a href="#カジュアル面談もお気軽にどうぞ">カジュアル面談もお気軽にどうぞ</a></li> <li><a href="#インターンも常時募集しています">インターンも常時募集しています</a></li> </ul> </li> </ul> <h2 id="You-Only-Cache-Once-Decoder-Decoder-Architectures-for-Language-Models">You Only Cache Once: Decoder-Decoder Architectures for Language Models</h2> <ul> <li>セッション: 6D: Deep Learning Architecture, Infrastructure</li> <li>著者: Yutao Sun, Li Dong, Yi Zhu, Shaohan Huang, Wenhui Wang, Shuming Ma, Quanlu Zhang, Jianyong Wang, Furu Wei</li> <li><p>論文リンク: <a href="https://arxiv.org/pdf/2405.05254">https://arxiv.org/pdf/2405.05254</a></p></li> <li><p>紹介者: 高橋 (機械学習エンジニア)</p></li> </ul> <h3 id="推しポイント">推しポイント</h3> <p><figure class="figure-image figure-image-fotolife" title="図は論文のFigure 2より引用。"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241214/20241214121104.png" width="377" height="416" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>図は論文のFigure 2より引用。</figcaption></figure></p> <p>LLM全盛の昨今ではTransformerの推論速度や要するHBMメモリの削減が運用コストにクリティカルに効いてきます。技術トレンドとしてもAttention機構に関わる部分の研究は多く、例えばGPUアーキテクチャの性質に適したFlashAttentionであったり、Attentionが系列長Nに対してO(N<sup>2</sup>)の計算コストを要するところO(N)にするアーキテクチャが数多く提案されています。</p> <p>今回選んだYou Only Cache One (YOCO)ではdecoder-decoderとよばれるアーキテクチャを提案し、同規模のTransformerと比べてスループットの10倍近い高速化を達成しています。</p> <p>肝となるのはkey-value cache (KV cache)をレイヤーごとではなく、モデルで1つのみのKV cacheを生成する (global KV cache)という点です。論文のFigure 2のとおり、アーキテクチャ全体としては前半のself-decoderと後半のcross-decoderから構成されます。self-decoderではRetentive Networksを採用してO(N)の計算コストで global KV Cacheを生成します。cross-decoderではglobal KV cacheを利用して通常のTransformerのAttention機構と同様の計算をします。</p> <p>気になるのはLLMとしての性能ですが、language modeling, needle-in-a-haystack (長文からの情報抽出タスク), question asnweringといった複数のタスクで同規模のTransformerと同程度かやや上回るパフォーマンスを達成しており非常に効果的なアーキテクチャであることが示されています。</p> <p>AIチームではLLMを扱うタスクが増えてきており、特に推論時のスループットやコストは実際にプロダクトでLLMを活用する際に重要なため、こうした技術を抑えておくことは大事だと思い推薦しました。</p> <h2 id="Decompose-Analyze-and-Rethink-Solving-Intricate-Problems-with-Human-like-Reasoning-Cycle">Decompose, Analyze and Rethink: Solving Intricate Problems with Human-like Reasoning Cycle</h2> <ul> <li>セッション: 3B: Natural Language Processing</li> <li>著者: Shangzi Xue, Zhenya Huang, Jiayu Liu, Xin Lin, Yuting Ning, Binbin Jin, Xin Li, Qi Liu</li> <li>論文リンク: <a href="https://openreview.net/forum?id=NPKZF1WDjZ">Decompose, Analyze and Rethink: Solving Intricate Problems with Human-like Reasoning Cycle | OpenReview</a></li> <li>紹介者: 池嶋 (機械学習エンジニア)</li> </ul> <h3 id="推しポイント-1">推しポイント</h3> <p><figure class="figure-image figure-image-fotolife" title="論文のFig1"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241214/20241214121128.png" width="800" height="351" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>論文のFig1</figcaption></figure></p> <p>この論文では、LLMで複雑な質問への推論性能を上げる目的で、問題文を分解、推論、評価、再構築というステップで解いていく、DeAR (Decompose-Analyze-Rethink)というフレームワークを提案しています。既存手法である、Chain-of-ThoughtやTree-of-Thoughtでも問題文を分解して途中問題を解いたり、推論結果を評価して良さそうなものを深堀りしたりできましたが、途中問題の推論間違いを訂正できないという弱点がありました。DeARでは、LLM自身でセルフチェックをしたり、再構築のステップでどの部分問題の回答を使うべきかをLLMに判断させたりして、この課題を解消しています。</p> <p>結果はもちろんSoTAで、既存のChain-of-ThoughtやTree-of-Thoughtよりも性能が上がっていると説明しています。人間の推論方法を模倣して、問題文を分解・再構築することで性能が上がるというアイディアが面白いし、再現もしやすいのでインパクトのある論文かなと考えました。</p> <p>ただ、論文では評価にはGPT-3.5を使用しており、GPT4の場合は、より難度の高い問題で差が出ると主張しています。たしかに論文で示されたLLMで解くのが難しいとされる問題文を試しにGPT4<a href="#f-3f36e1e7" id="fn-3f36e1e7" name="fn-3f36e1e7" title="OpenAI o1シリーズには内部的にChain-of-Thoughtが採用されているようです。https://platform.openai.com/docs/guides/reasoning">*1</a>に入れてみると、0-shotプロンプトにも関わらず正解してしまっていました。LLM自身の進化もよく見れる面白いpaperだなと感じました。</p> <h2 id="RG-SAN-Rule-Guided-Spatial-Awareness-Network-for-End-to-End-3D-Referring-Expression-Segmentation">RG-SAN: Rule-Guided Spatial Awareness Network for End-to-End 3D Referring Expression Segmentation</h2> <ul> <li>セッション: 1B: Human-AI Interaction</li> <li>著者: Changli Wu, Qi Chen, Jiayi Ji, Haowei Wang , Yiwei Ma, You Huang, Gen Luo, Hao Fei , Xiaoshuai Sun , Rongrong Ji</li> <li>論文リンク: <a href="https://arxiv.org/pdf/2412.02402">https://arxiv.org/pdf/2412.02402</a></li> <li>紹介者: 鴨田 (機械学習エンジニア)</li> </ul> <h3 id="推しポイント-2">推しポイント</h3> <p><figure class="figure-image figure-image-fotolife" title="図は論文のFigure 2より引用。"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241214/20241214121107.png" width="720" height="419" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>図は論文のFigure 2より引用。</figcaption></figure> 3D Referring Expression Segmentation はテキストによる参照表現に基づいて点群データ内の3D物体をセグメンテーションする研究分野で、自律型ロボットや自動運転への応用が期待されています。</p> <p>今回選んだRG-SANでは、既存研究で課題だったインスタンス間の空間的な位置関係の考慮を改善し、複数の同種オブジェクトが存在する複雑なシーンでの性能向上を実現しています。</p> <p>この論文のキーポイントはテキストに登場する物体の3D空間での位置を特定・更新するText-driven Localization Module(TLM)とターゲットオブジェクトの位置情報のみで学習可能とするRule-guided Weak Supervision(RWS)の導入です。TLMによって物体間の空間的関係性をより正確に理解でき、RWSによって限られた教師データを最大限に活用しながら効率的かつ効果的な学習を可能にしています。</p> <p>課題に対して効果的にアプローチする点と、特徴の次元射影をStep-by-stepで実施しているのがモデルの複雑性に合わせた施策で良いなと思い推薦しました。</p> <h2 id="GIC-Gaussian-Informed-Continuum-for-Physical-Property-Identification-and-Simulation">GIC: Gaussian-Informed Continuum for Physical Property Identification and Simulation</h2> <ul> <li>セッション: 4D: Machine Vision</li> <li>著者: Junhao Cai, Yuji Yang, Weihao Yuan, Yisheng He, Zilong Dong, Liefeng Bo, Hui Cheng, Qifeng Chen</li> <li>論文リンク: <a href="https://arxiv.org/pdf/2406.14927">https://arxiv.org/pdf/2406.14927</a></li> <li>紹介者: 大垣 (機械学習エンジニア)</li> </ul> <h3 id="推しポイント-3">推しポイント</h3> <p><figure class="figure-image figure-image-fotolife" title="論文Fig.1より"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241214/20241214121100.png" width="800" height="475" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>論文Fig.1より</figcaption></figure></p> <p><strong>人間は一度物が落下して衝突するビデオを見れば、その物がどのような特性を持つか予想できる</strong></p> <p>Gaussian Splattingによる3D復元の拡張として、物理特性も含めた3D復元をするのがモチベーション。特にポイントなのは、学習可能なパラメタにするために、物理特性を、”変形の予測”として定式化している点。ある物体の連続した動画を、移動と変形のパラメタで記述し、再現された動画と実際の動画の距離が近くなるように学習する (b)。また、実際の動画自体も、2Dに射影して距離を取るのではなく、そこから3D復元したものとの距離を取ることで問題を一段階解きやすくしてる (a)。</p> <p>そうすることで、推論時には、動画の一部を入れればその後の変形が予測できたり、ロボットアームで保持して力を入れた状態での変形が予測できたりというもの。 Gaussian SplattingやNeRFなど、物体を点群ではなく場として捉えるという研究が賢くて好きなので、物理特性も変形という場の拡張を導入するアイディアが美しいと思います。 今後よりreal-worldのデータから計測できるようになったり、2stageじゃなくend-to-endになったり、今後の発展の可能性も大きいと感じます。</p> <h2 id="Convolutional-Differentiable-Logic-Gate-Networks">Convolutional Differentiable Logic Gate Networks</h2> <ul> <li>セッション: 5C: Machine Vision</li> <li>著者: Felix Petersen, Hilde Kuehne, Christian Borgelt, Julian Welzel, Stefano Ermon</li> <li><p>論文リンク: <a href="https://arxiv.org/pdf/2411.04732">https://arxiv.org/pdf/2411.04732</a></p></li> <li><p>紹介者: 中村伊吹 (ソフトウェアエンジニア)</p></li> </ul> <h3 id="推しポイント-4">推しポイント</h3> <p><figure class="figure-image figure-image-fotolife" title="図は論文のFigure 3より引用。"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241214/20241214121110.png" width="318" height="233" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>図は論文のFigure 3より引用。</figcaption></figure> 機械学習モデルの効率的な推論を実現するための新しいアプローチとして、Convolutional Differentiable Logic Gate Networks (LogicTreeNet)を提案しています。ロジックゲートネットワークを直接学習する手法を開発し、畳み込み演算やプーリング、残差接続の概念をロジックゲートネットワークに導入しています。</p> <p>今はソフトウェアエンジニアとしてAIチームで働いていますが、学生時代はエッジデバイスにおける深層学習モデルについて研究していました。この論文ではシンプルな発想で理解しやすく、かつ精度も高いモデルが実現されています。エッジにおいて効率的に動く深層学習モデルに応用できそうな研究だなと思い、めちゃくちゃワクワクしました。</p> <h2 id="Unlocking-the-Capabilities-of-Thought-A-Reasoning-Boundary-Framework-to-Quantify-and-Optimize-Chain-of-Thought">Unlocking the Capabilities of Thought: A Reasoning Boundary Framework to Quantify and Optimize Chain-of-Thought</h2> <ul> <li>セッション: 3B: Natural Language Processing</li> <li>著者: Qiguang Chen, Libo Qin, Jiaqi Wang, Jinxuan Zhou, Wanxiang Che</li> <li>論文リンク: <a href="https://arxiv.org/abs/2410.05695">https://arxiv.org/abs/2410.05695</a></li> <li>紹介者: 氏家 (機械学習エンジニア)</li> </ul> <h3 id="推しポイント-5">推しポイント</h3> <p><figure class="figure-image figure-image-fotolife" title="図は論文のFigure 1より引用。"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241214/20241214121114.png" width="798" height="353" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>図は論文のFigure 3より引用。</figcaption></figure> 近年LLMの推論精度を上げる目的として、思考過程を出力させ段階的に推論させるChain-of-Thoughtという手法が提案されています。</p> <p>この論文では、Chain-of-Thought(CoT)を定量評価し最適化するため、Reasoning Boundary (RB; LLMにとってのタスクの難易度のようなもの)というフレームワークを提案しています。CoTによりタスクを分解した際に、元のタスクのRBを分解後のタスクのRBの重み付き調和平均に分解することで、CoTを理論的に説明付けようとしています。</p> <p>この論文に限らず、新たな手法が出て、後から理論的な研究が進むのは工学的でとても好きなので推薦しました。CoTなどLLMを取り巻く研究は普段使いするLLMのプロンプトにも応用できるものもありますし、定期的にウォッチしていきたいです。</p> <h2 id="Guiding-a-Diffusion-Model-with-a-Bad-Version-of-Itself">Guiding a Diffusion Model with a Bad Version of Itself</h2> <ul> <li>セッション: 4B: Diffusion-based Models</li> <li>著者: Tero Karras, Miika Aittala, Tuomas Kynkäänniemi, Jaakko Lehtinen, Timo Aila, Samuli Laine</li> <li>論文リンク: <a href="https://arxiv.org/abs/2406.02507">https://arxiv.org/abs/2406.02507</a></li> <li>紹介者: 農見 (機械学習エンジニア)</li> </ul> <h3 id="推しポイント-6">推しポイント</h3> <p><figure class="figure-image figure-image-fotolife" title="CFGは強くしすぎるとプロンプトには忠実なもののシンプルすぎる画像になってしまうがautoguidanceだと強めても品質の良い画像が得られている"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241214/20241214121117.png" width="670" height="457" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>CFGは強くしすぎるとプロンプトには忠実なもののシンプルすぎる画像になってしまうがautoguidanceだと強めても品質の良い画像が得られている</figcaption></figure></p> <p>画像生成AIで広く用いられている「Classifier-Free Guidance (CFG)」は、プロンプト(指示)ありの画像とプロンプトなしの画像の差分を強調することで、指示内容に忠実で高品質な画像を生成する技術です。</p> <p>この論文では、CFGがなぜ高品質な画像を生成できるのか、そのメカニズムを明らかにしました。具体的には、CFGは、条件付きモデル(プロンプトあり)という比較的品質の高いモデルの出力と、条件なしモデル(プロンプトなし)という学習難易度が高く品質が低いモデルの出力の差分に着目し、その差分を使い高品質なモデル側に寄せていくことで、生成画像の品質を向上させています。この論文では、CFGの仕組みをさらに発展させ、最終学習を終えた高品質なモデルと、学習途中やパラメータ数の少ない比較的品質の低いモデルとの差分を利用する「autoguidance」という新たな手法を提案しました。autoguidanceを用いることで、CFGが抱えていた課題、すなわちプロンプトに忠実すぎるあまり画像が単純化してしまう傾向を緩和し、より多様性のある画像を生成できるようになりました。</p> <p>とはいえ欠点もあり、今の大規模画像生成モデルは段階的に学習されることが多く出力の分布が似ている弱いモデルを得られないという話はありました。ですが、今まで広く使われてるCFGの仕組みを明らかにし、より良い改善方法を作った点が面白いなと思いました。</p> <h2 id="We-are-hiring-">We are hiring !!</h2> <p>NeurIPSの論文の面白さにワクワクする皆さん、エムスリーAI・機械学習チームで一緒に機械学習エンジニアやりましょう! また、学生の皆さん向けには機械学習・MLOpsインターンも募集してます。ぜひ一緒に論文を読みサービス開発していきましょう。</p> <p>エムスリーでは、コンピュータビジョン・機械学習はもちろん、最新技術へのアンテナが高い仲間を歓迎しています。新卒・中途それぞれの採用、カジュアル面談やインターンも常時募集しています!</p> <h3 id="エンジニア採用ページはこちら">エンジニア採用ページはこちら</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/">jobs.m3.com</a></cite></p> <h3 id="カジュアル面談もお気軽にどうぞ">カジュアル面談もお気軽にどうぞ</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2Fcasual-session%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/casual-session/">jobs.m3.com</a></cite></p> <h3 id="インターンも常時募集しています">インターンも常時募集しています</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fopen.talentio.com%2Fr%2F1%2Fc%2Fm3%2Fhomes%2F4354" title="【新卒】エンジニアリンググループ 募集一覧 / エムスリー株式会社" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://open.talentio.com/r/1/c/m3/homes/4354">open.talentio.com</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-3f36e1e7" id="f-3f36e1e7" name="f-3f36e1e7" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">OpenAI o1シリーズには内部的にChain-of-Thoughtが採用されているようです。<a href="https://platform.openai.com/docs/guides/reasoning">https://platform.openai.com/docs/guides/reasoning</a></span></p> </div> kamo2kun 【pmconf 2024】クライス&カンパニーのDiscord企画振り返り:プロダクトマネージャーにとってのホームランとは? hatenablog://entry/6802418398311284757 2024-12-13T20:44:34+09:00 2024-12-14T11:53:22+09:00 こんにちは、こんばんは。今年も年末年始は12/25〜1/5まで連休を取ってみた取締役CTO兼VPoPの山崎です。最近はUL装備*1を充実させる日々を過ごしております。 本ブログはプロダクトマネージャー Advent Calendar 2024の14日目の記事です*2。 プロダクトマネージャー Advent Calendar 2023は乗り遅れて枠を取れなかったのですが、その前のプロダクトマネージャー Advent Calendar 2022には以下の記事をポストしていますので、是非ごらんください。 www.m3tech.blog 先日のUL装備キャンプ。Enlightened Equipmen… <p>こんにちは、こんばんは。今年も年末年始は12/25〜1/5まで連休を取ってみた取締役CTO兼VPoPの山崎です。最近はUL装備<a href="#f-929e7fb9" id="fn-929e7fb9" name="fn-929e7fb9" title="ULとはUltra Lightの省略で超軽量装備でキャンプや登山などを楽しむジャンルです。">*1</a>を充実させる日々を過ごしております。</p> <p>本ブログは<a href="https://adventar.org/calendars/10453">プロダクトマネージャー Advent Calendar 2024</a>の14日目の記事です<a href="#f-c38afab8" id="fn-c38afab8" name="fn-c38afab8" title="が、明日用事があるので3時間早いですが、フライングリリースさせて頂きました。">*2</a>。</p> <p>プロダクトマネージャー Advent Calendar 2023は乗り遅れて枠を取れなかったのですが、その前のプロダクトマネージャー Advent Calendar 2022には以下の記事をポストしていますので、是非ごらんください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.m3tech.blog%2Fentry%2F2022%2F12%2F25%2F203858" title="エムスリーが誇る最強のプロダクトマネージャー育成環境:プロダクトマネージャー定例 - エムスリーテックブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.m3tech.blog/entry/2022/12/25/203858">www.m3tech.blog</a></cite></p> <p><figure class="figure-image figure-image-fotolife" title="先日のUL装備キャンプ。Enlightened EquipmentのRevelation 850 30Fを手に入れたので2℃の環境で使ってみました。コット使っている段階でULじゃないじゃん?とか、切り株の上にあるのはアイロンストーブ?とかのツッコミはご遠慮くださいw。"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241213/20241213204435.jpg" width="800" height="600" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>先日のUL装備キャンプ。Enlightened EquipmentのRevelation 850 30Fを手に入れたので2℃の環境で使ってみました。コット使っている段階でULじゃないじゃん?とか、切り株の上にあるのはアイロンストーブ?とかのツッコミはご遠慮くださいw。</figcaption></figure></p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#プロダクトマネージャーにとってのホームランとは">プロダクトマネージャーにとってのホームランとは?</a><ul> <li><a href="#シリーズ優勝">①シリーズ優勝</a></li> <li><a href="#試合に勝つ">②試合に勝つ</a></li> <li><a href="#ホームランやヒットを打って点を取る">③ホームランやヒットを打って点を取る</a></li> <li><a href="#打席に立ってバットを振る">④打席に立ってバットを振る</a></li> <li><a href="#練習してチームの信頼を得る">⑤練習してチームの信頼を得る</a></li> </ul> </li> <li><a href="#なぜこの方法が最強なのか誰も最強と言ってないw">なぜこの方法が最強なのか(誰も最強と言ってないw)</a></li> <li><a href="#まとめ">まとめ</a></li> <li><a href="#We-are-hiring">We are hiring!</a></li> </ul> <h3 id="はじめに">はじめに</h3> <p>皆さん、今年の12/5〜6に開催された日本最大級のプロダクトマネージャーの祭典、pmconf2024、参加されたでしょうか?もちろんされましたよね? 今年は史上初の2 Days開催となり、私は主に1日目のオンラインセッションに参加していました<a href="#f-12a6680c" id="fn-12a6680c" name="fn-12a6680c" title="タイミー山口さんの基調講演や、Linkedin曽根原さんのセッション、本当に素晴らしく、勉強になりました!今からセッション動画の公開が楽しみです。">*3</a>。 運営の皆さん、登壇者の皆さん、参加者の皆さん、本当にお疲れさまでした。 今回は参加者ニーズの読みが甘く、うっかり公募に落ちてしまった<a href="#f-40fed5f3" id="fn-40fed5f3" name="fn-40fed5f3" title="しっかり運営が審査しているという証拠と思います!">*4</a>ので、気を取り直して久しぶりの参加者としての参加になりました。</p> <p>いや、なるはずでした(笑)。</p> <p>というのも、なんと開催10日前の11/25にクライス&amp;カンパニーの松永さんからご連絡頂き、恒例のDiscordイベントへの登壇オファーが<a href="#f-ff1eaa91" id="fn-ff1eaa91" name="fn-ff1eaa91" title="もちろん、数ヶ月前によかったら参加してもらえませんか、という打診は頂いておりました。クライス&amp;amp;カンパニーの松永さん、山本さん、お声がけありがとうございました。">*5</a>! ということで急遽、「プロダクトマネージャーにとってのホームランとは?(仮題)」という議題で、クライス&amp;カンパニーさんが運営している人気のPodcast、PMキャリアラジオの公開セッションを行うことになりました。</p> <p>クライス&amp;カンパニーさんが運営している人気のPodcast、プロダクトマネージャーのキャリアラジオ<a href="#f-7286913d" id="fn-7286913d" name="fn-7286913d" title="https://open.spotify.com/show/2BDUeF54OrvyAL9adfqZrD?si=6dc92491b21b4760">*6</a>については、過去何度か出演させてもらっているので、よかったら下記を聞いてみてください。</p> <p><iframe style="border-radius: 12px" width="100%" height="352" title="Spotify Embed: プロダクトリーダーの履歴書" frameborder="0" allowfullscreen allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" loading="lazy" src="https://open.spotify.com/embed/playlist/5pV5dOMJ92cUkWxkVwkl8y?utm_source=oembed"></iframe><cite class="hatena-citation"><a href="https://open.spotify.com/playlist/5pV5dOMJ92cUkWxkVwkl8y?si=I5t0bxjFSHaCvI8zHzmyrA">open.spotify.com</a></cite></p> <p>ちなみにpmconf2023で来場者アンケート満足度1位タイだったらしい、昨年の私の登壇はこちらです^^。こちらもよかったらご覧ください。</p> <p><iframe width="560" height="315" src="https://www.youtube.com/embed/BCt10qefqlw?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen title="プロダクトと事業を無限にスケールするための最強のロードマップの作り方 - #pmconf2023"></iframe><cite class="hatena-citation"><a href="https://youtu.be/BCt10qefqlw?si=tO9Q_OUsxMWPmw8h">youtu.be</a></cite></p> <h3 id="プロダクトマネージャーにとってのホームランとは">プロダクトマネージャーにとってのホームランとは?</h3> <p>さて、本題です。ことの発端としては、クライス&amp;カンパニーの松永さん<a href="#f-2e207a12" id="fn-2e207a12" name="fn-2e207a12" title="https://x.com/kandc_matsunaga">*7</a>から以下の企画案を頂きました。</p> <blockquote><p>もしよろしければ、コンテンツとしては、「プロダクトマネージャーにとってのホームランとは?(仮題)」というような形で考えています。 山崎さんの記事や登壇で使われている言葉はPM界隈に浸透していると思っており、改めて「ホームランを打つ」「打席に立つ」などのPM流行語(?)を作った山崎さんご本人からもともとの言葉の定義などをお話しいただいたり、どうすればホームランバッターになれるのかなどお話しいただきたいなと! もっとこういう内容の方が、などご要望有ればぜひお知らせください。 ご検討のほど、何卒よろしくお願い致します!</p></blockquote> <p>確かにそうだよな〜と思いつつ何を話そうか考えていったのですが、結論としては、「結論から話すのが良さそうだ」と思い至り、当日、クライス&amp;カンパニーの山本さん<a href="#f-2ca01c1f" id="fn-2ca01c1f" name="fn-2ca01c1f" title="https://x.com/kreis_yamamoto">*8</a>からの「まず打席とは?」という丁寧なエスコートをちゃぶ台返しして、「そもそも目的はシリーズ優勝です」なんていうとんでもない返答になった次第です。</p> <p>当日、参加されなかった方のために補足すると、まず最大の目的は(野球で例えると)①シリーズ優勝<a href="#f-6149afc2" id="fn-6149afc2" name="fn-6149afc2" title="もっと言えば10年、20年シリーズ優勝し続けることかなと思います。">*9</a>であり、そのためには②試合に勝つ必要があり、そのためには③ホームランやヒットを打って点を取る必要があり、そのためには④打席に立ってバットを振る必要があり、そのためには⑤練習してチームの信頼を得る必要があるということなんじゃないか、というのが今のところの私の考えです。以下、当日の流れに沿って、補足しながら簡単に振り返ります。</p> <h4 id="シリーズ優勝">①シリーズ優勝</h4> <p>シリーズ優勝とは、企業が顧客から高く評価され、十分な収益が上がっている状態です。 全てのプロダクトマネジメントは、このための方法論に過ぎません。 短期的な収益ではなく、プロダクトに支えられた盤石な収益が、企業に長期的な収益をもたらします。</p> <p>ほら、だんだんシリーズ優勝したチームが顧客に喜ばれている姿が思い浮かんできましたよね?</p> <p>横道さんが翻訳担当され、11月販売開始となったマーティ・ケイガンの最新刊「TRANSFORMED」でもプロダクト・オペレーティング・モデルがこれからの企業に大きな収益をもたらす可能性を示しています。</p> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B0DJGL9CS2?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="sponsored noopener"><img src="https://m.media-amazon.com/images/I/51Hf1YackeL._SL500_.jpg" class="hatena-asin-detail-image" alt="TRANSFORMED イノベーションを起こし真のDXへと導くプロダクトモデル" title="TRANSFORMED イノベーションを起こし真のDXへと導くプロダクトモデル"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B0DJGL9CS2?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="sponsored noopener">TRANSFORMED イノベーションを起こし真のDXへと導くプロダクトモデル</a></p><ul class="hatena-asin-detail-meta"><li><span class="hatena-asin-detail-label">作者:</span><a href="https://d.hatena.ne.jp/keyword/%A5%DE%A1%BC%A5%C6%A5%A3%A1%A6%A5%B1%A1%BC%A5%AC%A5%F3" class="keyword">マーティ・ケーガン</a></li><li>日本能率協会マネジメントセンター</li></ul><a href="https://www.amazon.co.jp/dp/B0DJGL9CS2?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="sponsored noopener">Amazon</a></div></div></p> <h4 id="試合に勝つ">②試合に勝つ</h4> <p>シリーズ優勝するためには、試合で勝たなくてはなりません。少なくとも競合企業より、試合に勝つ必要があります。 試合に勝つとは、一つのプロダクトやサービスが顧客から高く評価され、十分な収益が上がっている状態です。 全てのプロダクトマネジメントは、このための方法論に過ぎないとも言えそうです。</p> <p>試合に勝つ確率、回数が多ければ多いほど、シリーズ優勝に近づきます。</p> <p>その企業で提供しているプロダクトやサービスの多くが顧客から高く評価され、十分な収益が上がっていれば、その企業が顧客から高く評価され、十分な収益が上がるというのは当たり前といえば当たり前ですよね<a href="#f-dbb1dbcf" id="fn-dbb1dbcf" name="fn-dbb1dbcf" title="もちろん、大きな不祥事やスコンプライアンス違反がない、という前提です。">*10</a>。</p> <h4 id="ホームランやヒットを打って点を取る">③ホームランやヒットを打って点を取る</h4> <p>試合に勝つためには、ホームランやヒットを打って点を取らなければなりません。少なくとも競合サービスより、点を取る必要があります。 ホームランやヒットを打って点を取るとは、一つのプロダクトやサービスの機能や仕様が顧客から高く評価され、十分な収益が上がっている状態です。 これまた、全てのプロダクトマネジメントは、このための方法論に過ぎないとも言えるかも知れません。</p> <p>ホームランやヒットを打って点を取る確率、回数が多ければ多いほど、試合に勝つことに近づきます。</p> <p>ちなみに、ホームランやヒットを打って点を取る確率、回数は、その裏にあるプロダクトの世界観に大きく関連しています。 そのプロダクトの世界観が顧客に魅力的で、拡張可能である場合、そこから多くのホームランやヒットが生まれる可能性が高まります。 本件については、これだけでブログ1本かけそうなので、また別の機会で詳しく解説したいと思います<a href="#f-a8849d48" id="fn-a8849d48" name="fn-a8849d48" title="そのためにも、Xのポストやコメント、よろしくお願いしますw">*11</a>。</p> <h4 id="打席に立ってバットを振る">④打席に立ってバットを振る</h4> <p>ホームランやヒットを打って点を取るためには、打席に立ってバットを振らねばなりません。 打席に立ってバットを振るとは、プロダクトやサービスの一つ一つの機能や仕様を任せてもらえるという状態です。 ここまで来るとプロダクトマネジメントの一つ一つのテクニックそのものになってきましたよね。</p> <p>プロダクトやサービスの一つ一つの機能や仕様を任せてもらえるなかで、自分が実際にホームランやヒットを打てるようになっていくのです。</p> <p>このとき、闇雲にバットを振ったとしても、効率的にホームランやヒットが打てるようになるとは限らないことに注意が必要です。 というのも、効率的にホームランやヒットが打てるようになるためには、成功している先輩PdMの姿を見て、または話を聞いて、自分自身がホームランやヒットが打てるようになる可能性が高いからです。 本件については、一人で振っていくほうが大事なのでは?など、賛否両論あると思いますので、こちらもコメントやご意見頂けると嬉しいです。</p> <h4 id="練習してチームの信頼を得る">⑤練習してチームの信頼を得る</h4> <p>打席に立ってバットを振るためには、練習してチームの信頼を得る必要があります。 練習してチームの信頼を得るとは、プロダクトチームから、プロダクトやサービスの一つ一つの機能や仕様を任せても大丈夫だと認識してもらうことです。 今度はプロダクトチームの中での信頼関係の話になってきましたね。</p> <p>実は、これがプロダクトマネージャーにとって究極的に重要です。 そして、ジュニアのプロダクトマネージャーにとって、最も困難なハードルとも言えるかも知れません。</p> <p>というのも、プロダクトの成功=試合に勝てるか、シリーズ優勝できるかは、プロダクトチーム全員のキャリアを左右する可能性があります。 そのためには打席は貴重であり、可能であれば確実に点が取れる選手を打席に立たせたいというチームの想いがあるからです。 その期待に応え、チームの信頼を得るためにはチーム内での発言や意思決定の鋭さ(=精度)が全てを左右すると言っても過言ではありません。</p> <p>現実問題として、わざわざ負けたくないのですからね。プロダクトマネージャーはおしなべて、これを理解しておくべきです。</p> <h3 id="なぜこの方法が最強なのか誰も最強と言ってないw">なぜこの方法が最強なのか(誰も最強と言ってないw)</h3> <p>実は、これらの話は、プロダクトマネージャーに限ったことではありません。 エンジニアでも、デザイナーでも同じです。 もしかしたらマーケターやセールス、カスタマーサクセスでも同じかも知れません。 シンプルでわかりやすく、かつプロダクトチームの全ての職種に対して応用範囲が広いことが最強たる所以です。</p> <p>要は、普段の仕事の細部で光る意思決定の鋭さを見せ、より大きなチャンスを勝ち取り、そこで成果を出してさらに信頼を得る、というどんどん大きくなっていくサイクルが、結果的に組織的にも大きな成果につながる、というシンプルな勝利の法則はビジネス的に最強といえば最強ですよね。</p> <p>特に、エンジニアに練習が必要だ、という話については、以下の「情熱プログラマー」という本に詳しいので、是非エンジニア出身のプロダクトマネージャーは一度読んでみることをオススメします<a href="#f-d08e14f3" id="fn-d08e14f3" name="fn-d08e14f3" title="情熱プログラマー「15. 一に練習、二に練習」を参照のこと。">*12</a>。</p> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B01IGW5MQ0?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="sponsored noopener"><img src="https://m.media-amazon.com/images/I/61K3lgxqKGL._SL500_.jpg" class="hatena-asin-detail-image" alt="情熱プログラマー ソフトウェア開発者の幸せな生き方" title="情熱プログラマー ソフトウェア開発者の幸せな生き方"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B01IGW5MQ0?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="sponsored noopener">情熱プログラマー ソフトウェア開発者の幸せな生き方</a></p><ul class="hatena-asin-detail-meta"><li><span class="hatena-asin-detail-label">作者:</span><a href="https://d.hatena.ne.jp/keyword/%A3%C3%A3%E8%A3%E1%A3%E4%A3%C6%A3%EF%A3%F7%A3%EC%A3%E5%A3%F2" class="keyword">ChadFowler</a>,<a href="https://d.hatena.ne.jp/keyword/%A4%C7%A4%D3%A4%A2%A4%F3%A4%B0%A4%EB" class="keyword">でびあんぐる</a></li><li>オーム社</li></ul><a href="https://www.amazon.co.jp/dp/B01IGW5MQ0?tag=hatena-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="sponsored noopener">Amazon</a></div></div></p> <h3 id="まとめ">まとめ</h3> <p>いかがだったでしょうか?</p> <p>プロダクトマネージャー Advent Calendar 2024のネタとして何を書こうか色々と迷ったのですが、今回は「【pmconf 2024】クライス&amp;カンパニーのDiscord企画振り返り:プロダクトマネージャーにとってのホームランとは?」と第してブログ記事としてまとめてみました。 当日、多くの方にpmconf2024のクライス&amp;カンパニーのDiscord企画に参加頂きましたが、その内容を改めて皆さんにお伝えできて良かったです。</p> <p>反響があれば、クライス&amp;カンパニーさんのプロダクトマネージャーのキャリアラジオや記事などでより詳しい話を取り上げてもらえる可能性もあるとかないとか<a href="#f-1061d9d2" id="fn-1061d9d2" name="fn-1061d9d2" title="クライス&amp;amp;カンパニーの松永さん、山本さん、是非よろしくお願いしますw。">*13</a>聞いていますので、是非、はてぶやXで感想などポスト頂ければ幸いです。 プロダクト開発で世界を変えようとしている読者の皆さんに、少しでも参考になれば幸いです。<a href="https://twitter.com/yamamuteking">@yamamuteking</a>のフォローもお忘れなく。</p> <p>ということで、今年もあと半月ちょっととなりました。私もまだまだ年内の仕事がてんこ盛りですが、頑張って消化していきましょうw。</p> <p><figure class="figure-image figure-image-fotolife" title="冬キャンプが気持ちの良い季節になりました。お気に入りの温度計はThermoProの室内室外同時表示のやつと、SwitchBotの屋外用温度計。前者はキャンプ中にスマホを見る必要がないのが特徴で、後者はログが全部スマホに転送できるのが特徴。"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241213/20241213204439.jpg" width="800" height="600" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>冬キャンプが気持ちの良い季節になりました。お気に入りの温度計はThermoProの室内室外同時表示のやつと、SwitchBotの屋外用温度計。前者はキャンプ中にスマホを見る必要がないのが特徴で、後者はログが全部スマホに転送できるのが特徴。</figcaption></figure></p> <h3 id="We-are-hiring">We are hiring!</h3> <p>エムスリーでは、多数のチャレンジに果敢にアタックする力と愛を兼ね備えたエンジニア、QA、デザイナー、プロダクトマネージャーを絶賛募集中です。</p> <p>特にプロダクトマネージャーにとっては打席数も多く、プロダクトマネジメントの基礎知識を習得しながら、社会にインパクトのある収益性の高いプロダクトを開発する経験、ホームランの打ち方を学べるを学べる良い環境だと思います。</p> <p>また、腕に覚えのあるソフトウェアエンジニアやQA、プロダクトデザイナーの方からの応募も絶賛募集中です!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fproduct%2F" title="M3, Inc. The Power of Medical Innovation Better Life, Better Together|エムスリー株式会社" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/product/">jobs.m3.com</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-929e7fb9" id="f-929e7fb9" name="f-929e7fb9" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">ULとはUltra Lightの省略で超軽量装備でキャンプや登山などを楽しむジャンルです。</span></p> <p class="footnote"><a href="#fn-c38afab8" id="f-c38afab8" name="f-c38afab8" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">が、明日用事があるので3時間早いですが、フライングリリースさせて頂きました。</span></p> <p class="footnote"><a href="#fn-12a6680c" id="f-12a6680c" name="f-12a6680c" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">タイミー山口さんの基調講演や、Linkedin曽根原さんのセッション、本当に素晴らしく、勉強になりました!今からセッション動画の公開が楽しみです。</span></p> <p class="footnote"><a href="#fn-40fed5f3" id="f-40fed5f3" name="f-40fed5f3" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text">しっかり運営が審査しているという証拠と思います!</span></p> <p class="footnote"><a href="#fn-ff1eaa91" id="f-ff1eaa91" name="f-ff1eaa91" class="footnote-number">*5</a><span class="footnote-delimiter">:</span><span class="footnote-text">もちろん、数ヶ月前によかったら参加してもらえませんか、という打診は頂いておりました。クライス&amp;カンパニーの松永さん、山本さん、お声がけありがとうございました。</span></p> <p class="footnote"><a href="#fn-7286913d" id="f-7286913d" name="f-7286913d" class="footnote-number">*6</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://open.spotify.com/show/2BDUeF54OrvyAL9adfqZrD?si=6dc92491b21b4760">https://open.spotify.com/show/2BDUeF54OrvyAL9adfqZrD?si=6dc92491b21b4760</a></span></p> <p class="footnote"><a href="#fn-2e207a12" id="f-2e207a12" name="f-2e207a12" class="footnote-number">*7</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://x.com/kandc_matsunaga">https://x.com/kandc_matsunaga</a></span></p> <p class="footnote"><a href="#fn-2ca01c1f" id="f-2ca01c1f" name="f-2ca01c1f" class="footnote-number">*8</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://x.com/kreis_yamamoto">https://x.com/kreis_yamamoto</a></span></p> <p class="footnote"><a href="#fn-6149afc2" id="f-6149afc2" name="f-6149afc2" class="footnote-number">*9</a><span class="footnote-delimiter">:</span><span class="footnote-text">もっと言えば10年、20年シリーズ優勝し続けることかなと思います。</span></p> <p class="footnote"><a href="#fn-dbb1dbcf" id="f-dbb1dbcf" name="f-dbb1dbcf" class="footnote-number">*10</a><span class="footnote-delimiter">:</span><span class="footnote-text">もちろん、大きな不祥事やスコンプライアンス違反がない、という前提です。</span></p> <p class="footnote"><a href="#fn-a8849d48" id="f-a8849d48" name="f-a8849d48" class="footnote-number">*11</a><span class="footnote-delimiter">:</span><span class="footnote-text">そのためにも、Xのポストやコメント、よろしくお願いしますw</span></p> <p class="footnote"><a href="#fn-d08e14f3" id="f-d08e14f3" name="f-d08e14f3" class="footnote-number">*12</a><span class="footnote-delimiter">:</span><span class="footnote-text">情熱プログラマー「15. 一に練習、二に練習」を参照のこと。</span></p> <p class="footnote"><a href="#fn-1061d9d2" id="f-1061d9d2" name="f-1061d9d2" class="footnote-number">*13</a><span class="footnote-delimiter">:</span><span class="footnote-text">クライス&amp;カンパニーの松永さん、山本さん、是非よろしくお願いしますw。</span></p> </div> yamazaki-m3 gitレポジトリ考古学に使う道具 hatenablog://entry/6802418398310878092 2024-12-13T11:00:00+09:00 2024-12-13T11:00:03+09:00 こちらはエムスリーAdvent Calendar 2024 13日目の記事です。 こんにちは、エムスリーエンジニアリンググループの福林 (@fukubaya) です。 今回は、長年運用されてきたレポジトリをgitを使って発掘する上で使っている道具を紹介します。 福岡タワー(ふくおかタワー)は、福岡県福岡市早良区のシーサイドももち地区にあるランドマークタワー(電波塔)。本文には関係ありません。 帳票環境 レポジトリからDBテーブルの使用箇所を探す スケジュール設定だけ消される gitレポジトリ考古学 ファイルが消えたコミットを知りたい ある記述が消えた(追加された)コミットを知りたい あるテーブ… <p>こちらは<a href="https://qiita.com/advent-calendar/2024/m3">エムスリーAdvent Calendar 2024</a> 13日目の記事です。</p> <p>こんにちは、エムスリーエンジニアリンググループの福林 (<a href="https://x.com/fukubaya">@fukubaya</a>) です。</p> <p>今回は、長年運用されてきたレポジトリをgitを使って発掘する上で使っている道具を紹介します。</p> <p><figure class="figure-image figure-image-fotolife" title="福岡タワー(ふくおかタワー)は、福岡県福岡市早良区のシーサイドももち地区にあるランドマークタワー(電波塔)。本文には関係ありません。"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m3tech/20241213/20241213110005.jpg" width="601" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>福岡タワー(ふくおかタワー)は、福岡県福岡市早良区のシーサイドももち地区にあるランドマークタワー(電波塔)。本文には関係ありません。</figcaption></figure></p> <ul class="table-of-contents"> <li><a href="#帳票環境">帳票環境</a><ul> <li><a href="#レポジトリからDBテーブルの使用箇所を探す">レポジトリからDBテーブルの使用箇所を探す</a></li> <li><a href="#スケジュール設定だけ消される">スケジュール設定だけ消される</a></li> </ul> </li> <li><a href="#gitレポジトリ考古学">gitレポジトリ考古学</a><ul> <li><a href="#ファイルが消えたコミットを知りたい">ファイルが消えたコミットを知りたい</a></li> <li><a href="#ある記述が消えた追加されたコミットを知りたい">ある記述が消えた(追加された)コミットを知りたい</a></li> <li><a href="#あるテーブルが使われているバッチを探したい">あるテーブルが使われているバッチを探したい</a></li> <li><a href="#大量にgit-grepしたい">大量にgit grepしたい</a></li> <li><a href="#git-grepの結果がEUC-JPでもUTF-8でも正しく日本語を出力する">git grepの結果がEUC-JPでもUTF-8でも正しく日本語を出力する</a></li> </ul> </li> <li><a href="#まとめ">まとめ</a></li> <li><a href="#We-are-hiring">We are hiring!</a></li> </ul> <h2 id="帳票環境">帳票環境</h2> <p>弊社には「帳票環境」と呼ばれる、各種レポートを生成したり、調査のための環境があります。 簡単に言えばm3.comの本番サービスのデータのレプリカDBと、このDBのデータを分析したり、レポートや別のサービスで使うためのデータを生成するバッチの実行環境です。</p> <p>長年運用されてきた環境ですが、オンプレ環境で維持のコストもかかるため、実行環境のクラウド移行も含めてリプレイスを進めています。 その中でも、レプリカDBが稼働しているHWの保守が2025年度末で切れるので、このタイミングまでにDBのレプリカ先をBigQueryに移行しDB自体を停止するためバッチの調査、移行を実施しています。</p> <h3 id="レポジトリからDBテーブルの使用箇所を探す">レポジトリからDBテーブルの使用箇所を探す</h3> <p>帳票環境のバッチの大半はm3reportsと呼ばれるレポジトリにあります。 m3reportsは、バッチのソースコードと実行スケジュールの設定ファイルを管理しているレポジトリで、調べてみると2013年からあります。</p> <pre class="code bash" data-lang="bash" data-unlink>% git log --reverse --pretty=&#39;format:%ad&#39; Mon Apr 8 06:36:49 2013 +0000</pre> <p>他にも legacy_reporting と呼ばれるレポジトリはさらに古く2008年からありました。</p> <pre class="code bash" data-lang="bash" data-unlink>% git log --reverse --pretty=&#39;format:%ad&#39; Wed Nov 12 09:26:19 2008 +0000</pre> <p>基本的には、このm3reportsを探せば帳票環境で使われているDBテーブルが分かるので、 テーブルを使っているバッチを止めたり、書き換えて別のバッチに移行させています。</p> <h3 id="スケジュール設定だけ消される">スケジュール設定だけ消される</h3> <p>m3reportsには、バッチの本体のソースコードとバッチの実行スケジュールが書かれた設定ファイルがあります。</p> <pre class="code lang-conf" data-lang="conf" data-unlink><span class="synComment"># 設定ファイルの例</span> <span class="synComment"># バッチbatch_02 月、火、水、木、金、土に実行する。batch_01が正常完了したら実行する。</span> batch_02 batch_01 wday=Mon,Tue,Wed,Thu,Fri,Sat /path/to/script.sh </pre> <p>テーブルの名前でgit grepすれば、そのテーブルを使っているバッチを探すことができます。 しかし、長年運用されているため、使わなくなったバッチは設定ファイルからは実行スケジュールは消されますが、 不要になったバッチスクリプトだけが残っていることが多いです。 つまり、DBテーブルを使っているバッチがレポジトリにあったとしても、実際にはもうバッチは起動していないことが多いです。</p> <p>DBテーブルが使われているかを正確に知るには、スケジュール設定に書かれていない、もう実行されないバッチを同定してレポジトリから消し、 今もバッチとして使われているファイルだけを残すことが必要です。</p> <h2 id="gitレポジトリ考古学">gitレポジトリ考古学</h2> <p>gitレポジトリを発掘し、コミットログやコメントから先人たちの意図を読み出しながら、ソースコードを整理していきます。 コミットした当人はすでにいなかったり、当時のことを覚えていなかったりするので、コミットログやコメントから意図を読みとることが必要です。 これは考古学です。</p> <h3 id="ファイルが消えたコミットを知りたい">ファイルが消えたコミットを知りたい</h3> <p>あるバッチAが参照しているファイルBが見当たらないけど、そのバッチファイルAはすでにスケジュール実行されてなくて問題は起きていない。 バッチAを消したいけど、本当に消してよいのか(間違ってバッチAの実行が止められているだけかもしれない)を判断するため、参照先ファイルBが消えた時のコミット、コミットメッセージを確認したい時に使います。</p> <pre class="code bash" data-lang="bash" data-unlink>git log -- path/to/file</pre> <p>これは <code>path/to/file</code> に関するコミットのログを出力します。ログの一番最後が削除なので1履歴に限定してもよいです。</p> <pre class="code bash" data-lang="bash" data-unlink>git log -1 -- path/to/file</pre> <p>なお <code>--</code> はコミットリビジョンなのかパスなのか曖昧になる場合に間に入れるだけで意味はないです。</p> <pre class="code bash" data-lang="bash" data-unlink>git log path/to/log fatal: ambiguous argument &#39;path/to/log&#39;: unknown revision or path not in the working tree. Use &#39;--&#39; to separate paths from revisions, like this: &#39;git &lt;command&gt; [&lt;revision&gt;...] -- [&lt;file&gt;...]&#39;</pre> <h3 id="ある記述が消えた追加されたコミットを知りたい">ある記述が消えた(追加された)コミットを知りたい</h3> <p>あるバッチAのファイルはあるが、設定ファイルからは呼び出されていない。 おそらく設定ファイルで実行スケジュールだけ消したがバッチファイルは使わないのに消されずに残っている。 それを確かめるため、設定ファイルから実行スケジュールを消したコミットを調べたい時に使います。</p> <pre class="code bash" data-lang="bash" data-unlink>git log -p -S &#34;検索対象文字列&#34;</pre> <p><code>-S&lt;gring&gt;</code> は変更(追加/削除)を検索します。 <code>-p</code> は実際の変更内容のdiffを表示させます。コミットだけでよければ <code>-p</code> は不要です。</p> <h3 id="あるテーブルが使われているバッチを探したい">あるテーブルが使われているバッチを探したい</h3> <p>「帳票環境」のレプリカDBのテーブルを停止するため、そのテーブルを使っているバッチを探し出したいです。 これは単純にgit grepを実行すればよいです。</p> <pre class="code bash" data-lang="bash" data-unlink>git grep --name-only &#34;テーブル名&#34;</pre> <p>これでテーブル名が含まれるファイル名の一覧が得られるのですが、テーブル名が部分文字列になっている場合も含まれてしまいます。 <code>"user"</code> と検索したら <code>"user"</code> だけでなく <code>"user_address"</code> も <code>"admin_user"</code> もヒットしてしまいます。</p> <p>そのため、正規表現で検索することで可能な限り正確に検索できるようにします。</p> <pre class="code bash" data-lang="bash" data-unlink>git grep -E -i -e &#34;(^|[^A-Za-z0-9_])targettext([ \&#34;&#39;\(\)]|$)&#34;</pre> <p><code>-E</code> は拡張正規表現、<code>-i</code> は大文字小文字を区別しない、<code>-e</code> は検索する文字列を正規表現で指定します。 <code>(^|[^A-Za-z0-9_])</code> は行頭かテーブル名の区切りになる文字を指定しています。 <code>([ \"'\(\)]|$)</code> はテーブル名の終わりか行末を指定しています。</p> <p>以下のファイルを検出できるか試します。</p> <pre class="code bash" data-lang="bash" data-unlink># target targettext schema.targettext export TBL=&#34;targettext&#34; export TBL=&#39;targettext&#39; select count(1) from (select x from targettext) tbl=TARGETTEXT # not target tmp_targettext targettext_1</pre> <p>これで部分文字列を避けてより正確に検索できます。</p> <pre class="code bash" data-lang="bash" data-unlink>git grep -E -i -e &#34;(^|[^A-Za-z0-9_])targettext([ \&#34;&#39;\(\)]|$)&#34; test.txt:2:targettext test.txt:3:schema.targettext test.txt:4:export TBL=&#34;targettext&#34; test.txt:5:export TBL=&#39;targettext&#39; test.txt:6:select count(1) from (select x from targettext) test.txt:7:tbl=TARGETTEXT</pre> <p>git grep は <code>-e</code> を複数回重ねて指定できますが、これは遅いです。できるだけ正規表現で一つにまとめるようにしましょう。</p> <pre class="code bash" data-lang="bash" data-unlink># 複数回 time git grep -E -i -e &#34;(^|[^A-Za-z0-9_])targettext &#34; -e &#34;(^|[^A-Za-z0-9_])targettext\&#34;&#34; -e &#34;(^|[^A-Za-z0-9_])targettext&#39;&#34; -e &#34;(^|[^A-Za-z0-9_])targettext\(&#34; -e &#34;(^|[^A-Za-z0-9_])ta rgettext\)&#34; -e &#34;(^|[^A-Za-z0-9_])targettext$&#34; git grep -E -i -e &#34;(^|[^A-Za-z0-9_])targettext &#34; -e -e -e -e -e 8.96s user 0.32s system 274% cpu 3.383 total # 1個 time git grep -E -i -e &#34;(^|[^A-Za-z0-9_])targettext([ \&#34;&#39;\(\)]|$)&#34; git grep -E -i -e &#34;(^|[^A-Za-z0-9_])targettext([ \&#34;&#39;\(\)]|$)&#34; 1.57s user 0.40s system 295% cpu 0.667 total</pre> <h3 id="大量にgit-grepしたい">大量にgit grepしたい</h3> <p>「帳票環境」のレプリカDBは1000以上のテーブルがあります。 これらのテーブル1つ1つに対してgit grepを実行するのには時間がかかります。 さらに、検索対象レポジトリがm3reports以外にもあって、それらも検索する となるとテーブル数×レポジトリ数だけgit grepを実行しないといけません。 そこで、大量のgit grepを実行するために、goで並行実行させることにしました。</p> <p>まずは、git grepを実行する関数を作ります。</p> <pre class="code lang-go" data-lang="go" data-unlink><span class="synStatement">func</span> grepRepoBase(ctx context.Context, repopath <span class="synType">string</span>, ops ...<span class="synType">string</span>) *bufio.Scanner { ctx, cancel := context.WithTimeout(ctx, <span class="synConstant">180</span>*time.Second) <span class="synStatement">defer</span> cancel() args := <span class="synStatement">append</span>([]<span class="synType">string</span>{<span class="synConstant">&quot;--no-pager&quot;</span>, <span class="synConstant">&quot;-c&quot;</span>, <span class="synConstant">&quot;core.quotepath=false&quot;</span>, <span class="synConstant">&quot;-c&quot;</span>, <span class="synConstant">&quot;grep.linenumer=true&quot;</span>, <span class="synConstant">&quot;-c&quot;</span>, <span class="synConstant">&quot;grep.patternType=fixed&quot;</span>, <span class="synConstant">&quot;-C&quot;</span>, repopath, <span class="synConstant">&quot;grep&quot;</span>, <span class="synConstant">&quot;--no-color&quot;</span>, <span class="synConstant">&quot;--line-number&quot;</span>, <span class="synConstant">&quot;-E&quot;</span>, <span class="synConstant">&quot;-i&quot;</span>}, ops...) cmd := exec.CommandContext(ctx, <span class="synConstant">&quot;git&quot;</span>, args...) buf := &amp;<span class="synType">bytes.Buffer</span>{} cmd.Stdout = buf cmd.Run() <span class="synStatement">return</span> bufio.NewScanner(buf) } </pre> <p><code>repopath</code> はレポジトリがあるファイルパスです。<code>-C</code> で指定することで、レポジトリの外のディテクトリからでも実行できます。 出力結果を1行ずつ読み込むため<code>bufio.Scanner</code> を返します。 また、並行処理するため<code>context.Context</code> を使ってタイムアウトを設定しています。</p> <pre class="code lang-go" data-lang="go" data-unlink><span class="synComment">// tableName を検索</span> s := grepRepoBase(context.Background(), repopath, <span class="synConstant">&quot;-e&quot;</span>, fmt.Sprintf(<span class="synConstant">&quot;(^|[^a-zA-Z0-9_])%s([ </span><span class="synSpecial">\r\t</span><span class="synConstant">'</span><span class="synSpecial">\&quot;\\</span><span class="synConstant">)</span><span class="synSpecial">\\</span><span class="synConstant">(]|$)&quot;</span>, tableName)) <span class="synStatement">for</span> s.Scan() { line := ReadBytes(s.Bytes()) ... } </pre> <p>これをsemaphoを使って同時実行数を50までに抑えながら並行して実行します。</p> <pre class="code lang-go" data-lang="go" data-unlink> <span class="synComment">// 結果を書くためのmutex</span> <span class="synError"> </span> <span class="synStatement">var</span> resultMutex sync.Mutex <span class="synComment">// 並列実行</span> sem := semaphore.NewWeighted(<span class="synConstant">50</span>) wg := sync.WaitGroup{} logger.Info(<span class="synConstant">&quot;start check&quot;</span>) <span class="synStatement">for</span> _, table := <span class="synStatement">range</span> tables { <span class="synStatement">for</span> _, repo := <span class="synStatement">range</span> REPO_PATHS { <span class="synStatement">if</span> err := sem.Acquire(context.Background(), <span class="synConstant">1</span>); err != <span class="synStatement">nil</span> { <span class="synStatement">panic</span>(err) } wg.Add(<span class="synConstant">1</span>) <span class="synStatement">go</span> <span class="synType">func</span>(table <span class="synType">string</span>, repo <span class="synType">string</span>) { now := time.Now() <span class="synStatement">defer</span> sem.Release(<span class="synConstant">1</span>) <span class="synStatement">defer</span> wg.Done() <span class="synComment">// ここでgit grep実行</span> resultMutex.Lock() <span class="synComment">// 結果記録</span> resultMutex.Unlock() logger.Info(<span class="synConstant">&quot;check done&quot;</span>, <span class="synConstant">&quot;table&quot;</span>, table, <span class="synConstant">&quot;repo&quot;</span>, repo, <span class="synConstant">&quot;time(ms)&quot;</span>, time.Since(now).Milliseconds()) }(table, repo) } } wg.Wait() logger.Info(<span class="synConstant">&quot;check done&quot;</span>) </pre> <p>これで大量のgit grepを並行して実行できます。</p> <h3 id="git-grepの結果がEUC-JPでもUTF-8でも正しく日本語を出力する">git grepの結果がEUC-JPでもUTF-8でも正しく日本語を出力する</h3> <p>m3reportsを含むレポジトリは10年以上前からあるため、文字コードが混在しています。 古いバッチはEUC-JPで書かれていることが多いですが、最近作られたものはUTF-8で書かれています。 git grepの出力は元の文字コードに関係なく出力されるので、文字コードを固定にしてデコードすると化けてしまいます。 そこで、EUC-JPでデコードしてエラーだったらUTF-8でデコードする処理を入れています。 幸いShift-JISが使われていることはないので、EUC-JPだけをチェックすればよいです。</p> <pre class="code lang-go" data-lang="go" data-unlink><span class="synStatement">func</span> ReadBytes(b []<span class="synType">byte</span>) <span class="synType">string</span> { <span class="synComment">// UTF-8としてデコードできればそのまま返す</span> <span class="synStatement">if</span> utf8.Valid(b) { <span class="synStatement">return</span> <span class="synType">string</span>(b) } <span class="synComment">// EUC-JPでデコードしてみる</span> <span class="synStatement">var</span> tmpbuf <span class="synType">bytes.Buffer</span> ic := transform.NewWriter(&amp;tmpbuf, japanese.EUCJP.NewDecoder()) _, err := ic.Write(b) <span class="synComment">// エラーなら空文字を返しておく</span> <span class="synStatement">if</span> err != <span class="synStatement">nil</span> { <span class="synStatement">return</span> <span class="synConstant">&quot;&quot;</span> } err = ic.Close() <span class="synStatement">if</span> err != <span class="synStatement">nil</span> { <span class="synStatement">return</span> <span class="synConstant">&quot;&quot;</span> } <span class="synComment">// デコードした文字列</span> <span class="synStatement">return</span> tmpbuf.String() } </pre> <h2 id="まとめ">まとめ</h2> <p>「帳票環境」のリプレイスのため、10年もののgitレポジトリを発掘するための道具を紹介しました。</p> <ul> <li>ファイルが消えたコミットを探す</li> <li>ある記述が消えた(追加された)コミットを探す</li> <li>あるテーブルが使われているバッチを探す</li> <li>大量にgit grepを実行</li> <li>EUC-JPでもUTF-8でも正しく日本語を出力する</li> </ul> <h2 id="We-are-hiring">We are hiring!</h2> <p>10年ものから先月できたもの、もちろんこれからできる新たなものまで多様なサービスを一緒に開発してくれる仲間を募集中です。お気軽にお問い合わせください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobs.m3.com%2Fengineer%2F" title="エンジニア採用サイト|採用情報 | 医療×テクノロジーの可能性は無限大" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobs.m3.com/engineer/">jobs.m3.com</a></cite></p> fukubaya