株式会社ヘンリー エンジニアブログ

株式会社ヘンリーのエンジニアが技術情報を発信します

イオンスマートテクノロジーさん、DELTA さんと「シネマ de LT会〜あなたのナレッジ大上映〜」を開催しました #CinemaDeLT

ヘンリーで SRE をやっている id:nabeop です。

2025/05/14 にシアタス調布のイオンシネマでイオンスマートテクノロジーさん、DELTA さんと合同で「シネマ de LT会〜あなたのナレッジ大上映〜」を開催しました。

見慣れたはずのシアター入口のディスプレイに勉強会の案内が...!!

続きを読む

SREチームを作るうえで大切にしていること

株式会社ヘンリーでSREをしている戸田(id:eller)です。ひとりめSREとしてヘンリーにジョインしてから約3年、現在ではSREも3人のチームになりました。それでも事業計画に対してはまだ足りていないので、SREエンジニア採用を継続的に行っています。

私がSREチームを作るのは前職から続けて2回目なのですが、いずれの場合でも重要だと考えていることがあり、カジュアル面談でもよく話しています。今回はそのエッセンスをまとめて、同業はもちろん弊社への転職を検討されている方にも参考にしていただければと思っています。

前提としての「チームの目的」

チームを作っていくためには、そのチームの目的や存在意義を明確にする必要があります。SREチームであれば、何のSite Reliabilityをなぜ、どのようにしたいかを明確にする必要があります。 そしてこれらを明確にするためには、事業やサービスについての理解が不可欠です。

弊社のサービスはレセプトコンピュータ一体型のクラウドネイティブ電子カルテです。ITエンジニア向けには「SaaSとして稼働する医療機関向け基幹システムのSREチーム」だと説明するのが通りが良いと思っています。命もお金も要配慮個人情報も扱うシステムであり、高い信頼性とセキュリティが求められるのが特徴です。一方で顧客である中小病院にはシステム運用の専門家がいないことも多いので、インフラやアーキテクチャやサービス運用といった自社がコントロールするものにとどまらず顧客によるサービスの利用や運用も含めて支援する必要があります。

また弊社のサービスはTypeScriptとKotlinで書かれた複数のサーバで構成されており、さらに院内システム(検査など)との連携のために院内ネットワークにインストールいただくアプリケーションも存在します。院内のネットワークや端末は弊社が保守するものではないことや、連携が止まるとお客様の業務が止まることも踏まえて、よりオブザーバビリティが求められるシステムであると言えます。

最後に弊社のサービスは業界的には後発であり、まだ機能は充分ではないことを意識する必要があります。機能開発や改善の頻度も高く、毎週変更をデプロイしており、高速な価値検証をしようという経営方針が徹底されています。ですからデプロイやリリースを安全かつ高頻度に行うことが大切です。

以上をまとめると、ヘンリーにおけるSREは医療ドメインへの理解を持ちながら、高いオブザーバビリティと安全かつ高頻度なデプロイやリリースを実現することが求められています。

SREチームを作るうえで大切にしていること

こうしたの目的を踏まえて、私がSREチームを作るうえで大切にしているのは次の3点です。

運開分離をせず、サイロを壊す

これは最近 id:horsewin理想駆動ラジオでも触れてくれているのですが、弊社ではプロダクト開発エンジニアがTerraformを通じてインフラを触ることもDatadogを見ることもありますし、逆にSREがプロダクトコードを書くことも普通にあります。といいますかSREは運用担当ではなく、またプロダクト開発エンジニアも開発専任ではありません。デプロイやリリースは全チームで膝を(オンラインで)突き合わせてやりますし、障害発生時も全チームがウォールームに(オンラインで)集まって対応します。

そもそもSREが生まれた背景ないしDevOpsの流れを踏まえると、運開分離をしないのが普通ということになるかと思いますが、ここは重要な観点なのでもう少し説明します。

弊社のように数多くの試行錯誤が必要でサービスそのものが固まっていない場合は、運開分離によるサイロ化と速度の低下は大きなデメリットとなります。また開発のために有用なインプットは運用からこそ得られるので、開発が運用を見ることは非常に重要です。ですからSREは運用をするだけではなく、プロダクト開発エンジニアが目指す運用ができるように基盤を整えたりイネーブリングしたりすることを意識しています。そのためにはデプロイ時の留意点やログラベルの使い方、Datadogの使い方、Honeycombの使い方、tfactionの使い方などをきちんとドキュメントに落とし、GitHubリポジトリやオンボーディング資料からリンクさせるなどして、新しくジョインしたプロダクト開発エンジニアでも短期間でキャッチアップできる状態を作っています。

ちなみにこの文書化は弊社全体の特徴でもあります。この技術ブログも継続的に発信できていますし、技術書典に合同誌を出したのも書くことが習慣化されているからできたことです。カジュアル面談でも弊社を知っていただいたきっかけとしてよく伺うので、やっててよかったと本当に思います。

ドメインへの理解を深める

SREとしてサービスのReliabilityを作るうえで、顧客によるサービスの利用や運用も含めて支援することが必要であると書きました。言い換えると弊社サービスの信頼性は、クラウド上で動作するITシステムだけではなくお客様のご理解や実施いただいている運用にも左右されます。院内ネットワークや端末の性能に起因する体験の悪化もありますし、お客様の現場で行われる医療情報の扱いしだいでは個人情報が充分に守られないことも考えられます。こうした課題に対しても調査したりお客様にご説明できたりする体制を作ることがSREチームには求められます。

また、こうしたドメインの理解はオブザーバビリティを改善することにも役立ちます。医療現場では外来受付やバイタル測定のような毎日行う業務はもちろん、電子レセプト提出のように月初に行う業務や、データ提出のように3ヶ月に1度だけ行う業務があります。このような理解があると、これらの業務に関するサービスの平常状態を定義するのに役立ちます。実際弊社のDatadogには、電子レセプト提出やデータ提出に特化したダッシュボードが用意されています。

弊社ではドメイン勉強会をはじめとした学習機会が提供されています。また私は医療ドメインの知識を持たずに入社したこともあり、医療情報技師の資格を取ることや読書などを通じてドメインの理解を深めるようつとめています。SREチームとしてもドメインについて学んだことを互いに教え合うなどしてお客様業務の理解につとめていくことが重要だと考えています。

個々人の強みを活かす

目標を踏まえると「医療ドメインを理解しつつインフラからアプリ、果てはオブザーバビリティまで何でも対応できるエンジニア」が必要だと言えますが、そんな人はまずいません。 私自身、医療ドメインとアプリ、JVM運用などは得意としている一方で、ネットワークやインフラについてはまだ経験が不足していると感じています。もちろんスキルや知識ではなく気質に起因する課題もあります。

ですからSREエンジニア個々人の強みを活かすことが大切だと感じています。たとえば2人目SREの id:nabeop がジョインしてくれて、SREとしての基礎固めはもちろん、OpenTelemetryの導入や監視の改善といった「私ひとりではできていなかったこと」が数多く解決されました。技術勉強会も開催され、チーム間の情報流通やコミュニケーション機会として楽しく継続できています。また id:sumiren_t がジョインしてくれてからたったの3ヶ月でオブザーバビリティ周りの改善が数多く実施され、どこで何が起きているかを素早く正確に、かつ合理的なコストで把握できるようになりました。

図1 独自の強みを持つSREメンバーたち

個々人の強みを活かすためには、チーム全員で目標意識が共有されていることや、共通言語が整理されていること、オープンに議論できる土台があることが重要だと考えています。

今のSREチームではScrumしたいと思いつつあまりできていない認識はあるのですが、それでも四半期ごとの目標設定とその継続的な更新は意識して行っています。少なくとも毎月1回は目標を見直し、明らかに達成不可能な目標のドロップや、新しい材料が揃って意思決定が進められるようになった目標の更新などを全員で行っています。

またチームの誰もがPRをレビューできる体制を継続するために、PR依頼先をラウンドロビン方式で決めています。自分がPRレビューをする前提があるので、他のメンバーが担当している課題について朝会で積極的に聞く姿勢ができますし、PR作成側も理解してもらえるように自然と説明責任を果たすようになると考えています。

最後のオープンに議論できる土台は、心理的安全性はもちろんですが、弊社のバリューであるドオープンを守ることに繋がります。これは人数が少ないのでまだそこまで難しくないのですが、それでもオンラインランチ会を開催して業務と関係ない交流機会を作るようにしています。また個人的にも技術勉強会に積極参加するとか、業務に関係ないコミュニケーションをSlackのtimesでするとか、ダジャレに心血を注ぐとか、やれることはやっているつもりです。

まとめ

株式会社ヘンリーのSREチームでは、医療機関向け基幹システムをお客様にご提供するために、運開分離をせず個々人の強みを活かす組織づくりを心がけています。そのためにバリューのひとつであるドオープンを踏まえて、プロダクト開発エンジニアに対するドキュメントやイネーブリングの提供を行うとともに、SREチーム内で協力して動くための目標設定やコミュニケーション設計、学習機会醸成を行っています。

株式会社ヘンリーのSREチームでは、医療 DX のど真ん中であるクラウドネイティブのレセコン一体型電子カルテを一緒に開発してくれる仲間を募集しています。医療 DXやオブザーバビリティ、サービス基盤の構築やプロダクト開発エンジニアへのイネーブリング提供に興味がある方はぜひカジュアル面談しましょう。また2025年6月5日に弊社エンジニアに会えるHenry Engineer Meetupの開催を予定しておりますので、こちらも合わせてご検討ください。お待ちしております!

jobs.henry-app.jp

ヘンリー理想駆動ラジオ始めました

id:Songmu です。4月から非常勤となりフェローというタイトルを拝命しました。技術広報の支援を中心に活動していきます。

その一環として、「ヘンリー理想駆動ラジオ」というヘンリーの開発・運営の様子をお届けするポッドキャストを始めました。「理想駆動」というのはヘンリー社の大事な行動指針の一つであり、そこから命名しました。ヘンリーの開発や技術にご興味の方は購読やハッシュタグでの感想ポストをしていただけると嬉しいです。

まずは、私が聞き手となって、エンジニアや開発に関わる方へのインタビューを実施していきます。当ブログで書き起こしも順次公開予定です。インタビュー以外のコンテンツも公開していきたいと目論んでいます。何かこういった話が聞きたいなどの感想も歓迎です。

既に、当ブログでも常連の id:ellerid:agtn のエピソードを配信しているので是非聞いてみてください。

ヘンリー理想駆動ラジオを聞いて、ヘンリーの採用に興味を持った方からの連絡もお待ちしています!

jobs.henry-app.jp

エンドポイントカットのトレースサンプリングを提案したい

株式会社ヘンリーでオブザーバビリティを担当しているsumirenです。

先日のHoneycombでスパンを削減する - 株式会社ヘンリー エンジニアブログ という記事で、スパン削減にはトレースカットとトレース横断の2つのアプローチがある話をしたうえで、トレース横断での削減について紹介しました。 この記事ではトレースカットでの削減についてヘンリーでの事例を紹介します。

スパン削減の戦略(再掲)

前の記事でも紹介したとおり、スパン削減には大きく2つの方向性があります。(アプリケーションを直す以外)

  1. トレースカットでのアプローチ
    • トレースカットでサンプリングする
    • 例:正常なトレースは1%だけサンプルする、無料ユーザーのトレースは1%だけサンプルするなど
  2. トレース横断的なアプローチ
    • スパン種類ごとにドロップの条件をつける
    • 例:SET TRANSACTIONのスパンは正常かつ高速ならドロップする

この記事で議論したいのは前者のアプローチです。紹介したいのは前者のアプローチで、便宜上トレースサンプリングと呼ぶことにします。 このアプローチにおいては、トレースをドロップすると判断したとき、含まれるスパンが基本的には全てドロップされます。

主要なトレースサンプリングの戦略とトレードオフ

一般に普及している2つのトレースサンプリングについて紹介します。

1. 確率サンプリング

(狭義の)確率サンプリングでは、事前に決めたパーセンテージ等で、トレースに対して無作為にサンプリングするかドロップするかを決めます。

魅力

この方法は導入が容易である点が魅力です。サンプリングレートを環境変数等で固定する場合は、スパン量の予算を実績値で割って計算するだけです。また、マイクロサービス側への設定も容易で、たとえばJVMなら直接Java Agentの設定値にotel.traces.sampler=parentbased_traceidratioという設定値が用意されていたりします。

無作為にサンプリングするため、Honeycombでスパンを集計したときに得られるメトリクスが大きく狂わない点も魅力です。

トレードオフ

この方法では確率のみでトレースをドロップしてしまうため、障害が発生したときなどにトレースを探すのが難しくなります。例えばヘンリーではSentryのIssueにトレースIDを付与しているので、Slackに通知が来た時点でそのトレースIDでHoneycombを見に行きたいです。エラー数が多くない場合、そもそも1つも当該エラーのトレースが取れていない可能性もあります。

2. エラー有無や応答速度ベースのサンプリング

エラーが発生したかや応答速度ベースでトレースをサンプリングする方法もあります。例えばエラーのときは必ずサンプルする等です。

魅力

この方法では、先ほど述べたようなインシデント発生時にSentryのIssueからトレースを見るといったオペレーションがスピーディにできます。また、障害とは非同期的なパフォーマンス分析会などでも、応答時間の遅いトレースをドロップしてしまっている懸念が小さくなります。

インシデント対応や異常への対処において必要なトレースが確実に取れているという信頼性が、この方法の主な魅力です。

トレードオフ

この方法は導入に一定のハードルがあります。ある程度処理が進んだ後でサンプリング有無を決める必要があるため、OpenTelemetry Collectorを多段構成にするなどしてテイルサンプリングを行う必要があります。構築と運用の人的コストや認知負荷、インスタンス台数が増えることによるコスト増なども想定されます。

一般に理想の最終形とされがちなテイルサンプリングですが、Honeycombのような強力なスパン分析の機能を持つオブザーバビリティバックエンドにおいては、集計値が狂う懸念もあります。異常を含むトレースのスパンを多く集めることになるので、エラーレートやレイテンシの集計値が実態と乖離することが想像されます。

加えて、システムの振る舞いに関して、エラーでなければ異常ではないのか?と考えると、必ずしもそうとは言えません。例えばバグにより会計の金額が1円ずれてもシステム上はエラーであると判断できませんが、実際には異常です。医療会計の複雑性はヘンリーの技術戦略上重要なテーマであり、こうした異常はむしろヘンリーにおいてオブザーバビリティが最優先でカバーしたい部分です。

価値のあるトレースかどうかを分けるディメンションを探す

このように考えていくと、トレースサンプリングにおいて確率サンプリングという無作為のサンプリング以上に適したサンプリング戦略を見つけることは、「自社において、あるトレースの有用性と強く相関するディメンションはどれか?それは十分多くのスパンを削減できる相関関係になっているか?」という問いに答えることと同義と言えます。

ヘンリーにおいては、エラー有無というディメンションではトレースの有用性との相関性が弱かったということです。「エラーではなくても異常」がありえて、かつそれがサンプルされることの重みが大きいためです。

他にも、以下のようなディメンションでのサンプリングが思いつきます。特にルートスパン等を生成する時点でわかる属性であればヘッドサンプリングも可能です。

  • テナントIDベースでのサンプリング(大口顧客を高く重みをつける)
  • 契約プランベースでのサンプリング(有料プランは重みをつける)
  • 地域ベースでのサンプリング(注力市場ほど高く重みをつける)
  • リリーストグルベースでのサンプリング(限定公開対象は高く重みをつける)
  • エンドポイントベースでのサンプリング(新機能やリスクの高い機能は高く重みをつける)
  • etc.

エンドポイントカットのトレースサンプリングという提案

こうしたことを踏まえて様々なディメンションを検討した結果、ヘンリーではエンドポイントベースでサンプリングを行うようにしました。

トレースの有用性との相関性

ヘンリーにおいては、GraphQLオペレーションの種類がトレースの有用性と大きく相関すると判断しました。医療会計に絡む部分は100%サンプルしたいためです。

それ以外にも、List系のQueryオペレーションなど、引数が少ないものは障害リスク等も低い傾向にあります。逆に、Mutation系は引数も多くステートフルであるため、障害リスクが高くサンプリングしておきたいです。

削減できるスパン量

削減できるスパン量からもこの相関性は都合が良いものでした。前回の記事で書いたとおり、Top5のオペレーションだけで全体の30%のスパンを占めていました。そして、これらは1つを除き全てQueryオペレーションでした。

それに加え、Top5以下もスパン消費が多いものはほとんどがQuery系であったため、たくさん生成されている上に有用性の低いスパンを削れると言えます。

トレードオフ

N+1やキャッシュ不足によりQuery系が大量スパンを生成している状態というのは、マシンリソースの無駄遣いをそのまま可視化できている状態とも言えます。これをドロップすれば、パフォーマンス改善余地を全体感を持って見ることが難しくなると言えます。

そのうえで、ヘンリーにおいては、パフォーマンスよりも振る舞いにフォーカスしてオブザーバビリティに取り組むほうが技術戦略として適切であると判断しました。医療会計の振る舞いに対するトレーサビリティが最も重要だからです。

加えて、新機能開発にあたり「設計が悪いとトレースが使えなくなる」といったプレッシャーがプロダクトチームにあることで、長期的にパフォーマンスの高いソフトウェアを作るカルチャーにつながる側面もあると考えています。ドロップする既存のGraphQLオペレーションについても、ルートスパンだけ残すことで最低限呼び出し回数やレイテンシの異常に気づき、サンプリング復活につなげる機会を残すことはできます。

実装例

要件

ルートスパンは残すようにし、ルートスパンが特定GraphQLオペレーションなら以下を全て削る、という仕様にしました。

これにより、集計時にis_rootで絞ればドロップ対象のオペレーションでも全体レイテンシや呼び出し回数などが出せる状態を維持できました。加えて、ルートスパンには手動計装によりテナントIDやフィーチャーフラグの状態、エラーメッセージなどもすべて入っており、こうした集計も十分トラブルシュートの役に立ちます。

設計

全体像

スパン生成時点でGraphQLオペレーションの種類はわかるため、アプリケーション上でカスタムのSamplerを作成するヘッドサンプリングでの実現が可能です。

デフォルトのサンプリングロジックはParentBasedになっており、「親のサンプリング状況に従う」となります。ヘンリーではBFFをルートとして裏にマイクロサービスがいるため、以下のような修正になります。

  • BFFのサンプラーはカスタムサンプラーを実装する
  • マイクロサービスのサンプラーはAlwaysOnをやめてParentBasedにする

BFFのサンプラーの設計

ParentBasedSamplerSamplerのコンポジションになっていて、以下のように5つのパターンに対してSamplerを定義することができます。 今回はParentBasedSamplerを使いつつ、そこに渡すSamplerを自前で実装します。

  • ルートスパンを生成するときのサンプリングロジック(今回は必ずほしいのでAlwaysOnSamplerを渡す)
  • 子スパンを生成するときで親スパンがサンプルされている場合
    • 親スパンが自サービス内で生成されている場合(今回はここでドロップ判断したいためカスタムサンプラーを書いて渡す)
    • 親スパンが上流サービスで生成されている場合(今回はルートにあたりサービスなので関係なし)
  • 子スパンを生成するときで親スパンがドロップされている場合
    • 親スパンが自サービス内で生成されている場合(今回はルートは必ずサンプルするため孫スパンになる。当然いらないためAlwaysOffSamplerを渡す)
    • 親スパンが上流サービスで生成されている場合(今回はルートにあたりサービスなので関係なし)

注意点

Context PropagationのPropagatorを自作している場合は、サンプリングフラグを正しく実装しないと下流マイクロサービス側のParentBasedの動きが狂うため注意ください。

カスタムサンプラーの実装例

TypeScriptでの実装例です。ParentBasedSamplerlocalParentSampledに渡します。

export class GraphQLLocalParentSampler implements Sampler {
  private discardList: Set<string>;
  constructor(discardList: string[]) {
    this.discardList = new Set(discardList);
  }

  shouldSample(parentContext: Context | undefined): SamplingResult {
    const span = parentContext ? trace.getSpan(parentContext) : undefined;

    // TODO: これ以外にattrのgetterを得る方法見つからず。暇なときになおしてくれる人を募集してます
    const readableSpan = span as unknown as ReadableSpan;
    const operationName = readableSpan?.attributes?.["graphql.operation.name"] as string | undefined;

    if (typeof operationName !== "string") {
      return { decision: SamplingDecision.RECORD_AND_SAMPLED };
    }

    // 親スパンが当該operationならドロップする。このあとはこのParentBasedSamplerに渡しているlocalParentNotSampledが動く想定
    if (this.discardList.has(operationName)) {
      return { decision: SamplingDecision.NOT_RECORD };
    }

    return { decision: SamplingDecision.RECORD_AND_SAMPLED };
  }
}

OpenTelemetryにおいてローカル内で値を取り回すベストプラクティスがなかなか見つからず、OpenTelemetryのためだけにアプリケーションのリクエストコンテキストにステートを追加したくなかったため、このように実装しています。 一度サンプル側に入ればあとはlocalParentNotSampledが動くので、1リクエストあたりほぼ1回しか来ないため、性能面の懸念もないと判断し、これで良しとしました。 ベストプラクティスをご存じの方がいましたら、ぜひ発信いただけると幸いです。

結果

特定のエンドポイントだけ大きくスパン数を削減することができました(一瞬スパン数がゼロになっていますが、こちらは別件です)。

エンドポイントカットのSamplerが動いている図

まとめ

自社の向き合っている市場やプロダクト特性次第で最適なサンプリング戦略は変わると筆者は考えています。必ずしもテイルサンプリングが必要とは限りません。

そのうえで、エンドポイントカットのヘッドサンプリングは、エラーや応答時間ベースのテイルサンプリングと同じくらい汎用性が高く、多くの組織にとって優れた戦略になりうるのではないでしょうか。

ぜひ自社に最適なサンプリング戦略をチームで議論してみてください。

Honeycombでスパンを削減する

株式会社ヘンリーでオブザーバビリティを担当しているsumirenです。

ヘンリーではHoneycombのProプラン、15億スパンの契約をしています。

GraphQLのresolverなどあまりにスパン量が多いものは導入時に削りましたが、顧客数が増えたり新規機能が増えたことでQuotaを超えてしまうようになってきたため、既存スパンを分析したうえで削減を行いました。

トレース集計やHoneycomb素晴らしさを示すことにつながるため、主にスパンの分析についてシェアします。

スパン削減の戦略

スパン削減には大きく2つの方向性があります。(アプリケーションを直す以外)

  1. トレースカットでのアプローチ
    • トレースカットでサンプリングする
    • 例:正常なトレースは1%だけサンプルする、無料ユーザーのトレースは1%だけサンプルするなど
  2. トレース横断的なアプローチ
    • スパン種類ごとにドロップの条件をつける
    • 例:SET TRANSACTIONのスパンは正常かつ高速ならドロップする

両方の可能性を探るために、まずはHoneycomb上でスパンを集計していきます。

Honeycomb上でのスパン傾向の分析

Honeycombはトレースをスパンレベルで集計できる点が強みです。つまり、1トレース10スパンで3トレースあったら、30レコードに対してクエリや集計がかける形になります。

トレースカットでの分析

まずはトレースカットでのアプローチから検討します。Honeycombではスパンのトレースのルートを辿ってその属性でGROUP BYする機能(root. 関係フィールド)があります。どういうエントリポイントにスパンが集中しているか、全体感を知るには、以下のように集計できます。圧倒的にBFFをルートとするトレースにスパンが集中していることがわかります。

トレースカットでの本番のスパンの偏り(全体像)

どのようなカットでトレースを見ていくかは様々ですが、機能ごとの設計品質などを見るには、エンドポイント別などで見るのがよいでしょう。BFFをルートとするトレースをさらに分類し、どのGraphQLオペレーションがルートであるスパンが多いか見てみます。ヘンリーではBFFにはカスタムでオペレーション名をルートスパンに入れてあるため、以下のように集計できます。

GraphQLオペレーションカットでのスパンの偏り

この結果を見ると、当該期間のスパン全部(本番環境のみ)を集計すると3.8億スパン程度のため(下図)、例えばトップ5のGraphQLオペレーションを全部捨てるだけ30%程度を削減できることがわかります。 これは魅力的です。実際、障害リスクが高いのはむしろスパン消費の低いWRITE系の処理である、といった側面もあり、合理性もあります。

当該期間における全体の件数カウント

一方で、実はこうしたトレースカットでのスパン削減はやや敷居が高いものです。あるスパンをドロップするかは親スパンに依存するため、単純にOpenTelemetry CollectorのFilter Processorなどで十分に削減することができません。OpenTelemetry Collector側で多段構成を組む・アプリケーション側のサンプラーをカスタムで書くなどの手法が必要になります。

最終的にはGraphQLオペレーションごとにサンプリング可能とする実装を行ったのですが、この記事では割愛します。

トレース横断での分析

次に、トレース横断で削れるスパンを分析していきます。どのようなディメンションで見るかはアイデア次第ですが、筆者は全体感を知るのにスパンの種類別に見ていくのが好みです。以下のようにname等で集計できます。トレースカットで見ていないため、先ほどと異なりGROUP BYにroot.の記載がなくなっています。

トレース横断でのスパン分析(全体像)

HikariDatasource.getConnectionだけで全体の20%ほどを占めており、非常に支配的です。他にもGoogle CloudのIAMやmetadata、ログ関連のBigQueryへの挿入など、オブザーバビリティ上はあまり重要ではなく、そこまで遅くもないスパンが非常に大きな割合を占めていることがわかります。

今回はこれらのスパンを削減することにしました。

各スパンの詳細分析

あまり重要ではないスパンとはいえ、もしこれらのスパンが遅かったりエラーとなった場合には知りたいです。例えば、たまたまIAMが遅かっただけのときにアプリケーションの問題を疑ってしまうといったノイズは割けたいからです。一方で、スパンの削減量を増やすうえではなるべく多くの場合にスパンを削りたいため、ここにはトレードオフがあり、バランスを最適化するには現状分析が必要です。

ここでは最も支配的だったgetConnectionだけを例に取ります。

HoneycombではP99などの統計値だけではなくヒートマップが出せるため、これを使うことで具体的に削減割合を見積もることができます。

getConnectionなど個別スパンの応答速度分析

これを見ると、2ms以下が支配的であり、「3ms以上の場合には捨てない」といった保守的な設定にしても、大部分のスパンを削減できることがわかります。

ちなみにHoneycombでは、ヒートマップ以外にも、数式を書いてGROUP BYすることで特定のms以上と未満の集計をするなども可能です。

スパンのフィルタ

トレース横断でスパンを削る分には、OpenTelemetry CollectorでFilter Processorを書くだけなので簡単です。 0.110.0では以下のように書くと応答時間でのフィルタが動作しました。HTTP系をurl.fullで絞る場合などは、これに加えてIsMatchなどを使います。

  filter/traces:
    error_mode: ignore
    traces:
      span:
        - 'name == "HikariDataSource.getConnection" and end_time_unix_nano - start_time_unix_nano <= 2000000 and status.code == 0'

簡単そうに思えて、意外と公式ドキュメントから良いサンプルが見つからず、色々試すなかでうまくいったものを採用しました。 ナノ秒で書かざるをえない点など気に入ってはいないので、公式やコミュニティが推奨する書き方などご存知でしたらぜひご教示ください。

難しいプロダクトが成長している今だからこそ、ソフトウェアエンジニアリングでデリバリーを3倍速くしたい

こんにちは。ヘンリーでエンジニアをしている agatan です。 私は現在医事会計チームに所属し、医療事務の皆様が使う会計システム(レセコン)としての側面について Henry の開発に携わっています。

日々開発をする中で、「デリバリーが3倍速ければいろんな問題が解決するのになー」と考えていることがよくあり、そのための障壁や打ち手を考えるために現状の開発の課題感を整理していました。 実はこれはヘンリーのソフトウェアエンジニアリングの向き合っている難しさ(とそこに挑戦する面白さ)の芯の一つなのではないかと思い、せっかくなので記事にしてみることにしました。

弊社の行動指針に「ドオープン」(自身の考えを積極的に共有する)というものがあるのですが、同僚から「もっとこうするべきだという考え方を出していくのがリーダーシップですよ」とドオープンにフィードバックされました。この記事は、その通りだなと思って社内ブログに書いた記事の外部公開版です。)

私たちが開発しているレセコン一体型電子カルテ「Henry」は、複雑な医療保険制度や診療報酬制度に対応しながら、医療現場の業務を支えるプロダクトです。この複雑さゆえに開発は非常に挑戦的ですが、同時にエンジニアとして成長できる魅力的な環境でもあります。 この記事では、そんな特殊かつ奥深い領域での開発課題と、それに対してどのようにアプローチしようとしているのかについてお伝えします。 私たちが日々格闘している課題の本質と、それを解決するための取り組みを知っていただくことで、ヘンリーでの開発の面白さを感じていただければ幸いです。

ここで述べる内容は、あくまでもヘンリーの製品開発の一面に過ぎません。医事会計システムのなかでも複数の側面がありますし、電子カルテ(臨床)側のチームではまた違った課題と挑戦が複数存在しています。 他の側面に関しては、下記の記事を始めとして、ヘンリーエンジニアブログをご覧いただければと思います。

dev.henry.jp

製品開発の構成要素とその課題認識

私たちの製品開発は、ディスカバリーとデリバリーという構成要素から成ります。ざっくりした説明ですが、ディスカバリーは「何を作るべきか」を見つけるプロセスで、デリバリーは「それを実際に作る」プロセスです。

複雑な製品開発における課題は、ディスカバリーの課題として注目されることが多々あります。 ヘンリーにおいても同様で、より多くのロール(お客様を含む)が複合的に関連するプロセスでもあり、ディスカバリーの課題感を感じる人や課題解決に動く人も多くなりやすい傾向にあります。

事実として私たちの製品は、専門性の高い複数の専門職の方々が相互に作用し合う複雑な業務フローを理解する必要があり、ディスカバリー難易度は非常に高いですし、確かにそこに重要な課題はあります。 このあたりは、代表の逆瀬川も記事にしている通り、全員が向き合う価値のある重要な要素です。

note.com

最近のソフトウェア開発界隈では、エンジニアもディスカバリーに積極的に関わるべきという流れが強まっていますが、あえて私はソフトウェアエンジニアとして、「デリバリー自体にも大きな課題があり、それに真剣に向き合うことが重要だ」と考えています。

一般的に言っても、デリバリーに一切の課題がない開発現場などありえませんし、デリバリーに課題があるとしてそれを解決できるのは主にエンジニアの役割です。 ディスカバリーへの関与も重要ですが、その前にデリバリーそのものの改善に向き合うことが大切だと考えています。

(もちろん、この手の課題は複合・相互的なものであり、どちらか一方しかアプローチできないものではありませんから、二者択一でどちらかに責任を押し付ける必要はありません。私自身の実際の業務としても、ディスカバリー的な領域に関わっていることのほうがむしろ多いくらいではあります。)

デリバリーの課題は速度

デリバリーの課題とは、端的に言えば「速度」です。

バグや障害発生率も当然課題としてはありますが、多くの場合これも速度によって解決可能だと考えています。 速度が遅ければ、スケジュール逼迫によるQA不足や考慮漏れで障害が発生することは想像に難くありません。 さらに、速度を向上させるための施策は、概ねバグ・障害発生率にもポジティブな影響を与えます。(もちろん、適切な方法で実施した場合という前提ですが。)

デリバリーが3倍速くなるとどうなるか?

3倍速のデリバリーが達成された世界を想像してみます。(3倍という数字に厳密な意味はありません。直感的に理解しやすい数字として挙げています。)

まず、会社全体の運営として不確実性が大幅に下がります。「あの機能がいついつできるはずだから〜」という計画を立てて行動することは、我々のようなスタートアップでは必要なことですが、単純に開発が遅ければその不確実性は高くなります。

当初の想定通りのスケジュールですべてがうまくいくことはそもそも珍しいわけですが、もしこれが3倍速で開発できるなら、開発の不確実性が下がるタイミングもぐっと手前になり、計画の信頼性が高まります。

また、ディスカバリーもより積極的な探索が可能になります。「試してみる」ことのコストが下がるため、より多くの仮説を検証でき、結果的により良い製品を作れる可能性が高まります。

顧客からのフィードバックをもらえるタイミングが3倍早くなればどうでしょうか?間違ったものを作ってしまったとして、根本的な修正を行うコストが3倍低かったらどうでしょうか?

もちろんそれでも事前検証の価値は依然として高いわけですが、検証方法や深さなどにより幅広い選択肢が生まれることは間違いありません。

そして、当たり前ですが、お客様により速くより多くの価値が提供できるようになります。ひいては社会にもです。

3倍速くするには?

どうしたら3倍速くなるでしょうか?

単純に「とにかく手を速くたくさん動かす」「とにかく人を増やす」ことでは、たぶん3倍速くはなりません。ソフトウェア開発では人月の神話といって、人が増えても必ずしも速くならないことは一般的に広く知られています。

質とスピードの考え方も重要です。保守性が高くなければ、継続的なソフトウェア開発で速度を出し続けることはできません。

今のヘンリーの開発について私が思う改善ポイント(の一部)として、以下のようなものがあります。

  1. 構成要素(「機能」とか「データ」)の混線・依存が強く、ある要素だけを切り取って独立に開発することができないこと
    • すべての構成要素とそれにまつわる制度・業務を脳内に入れないと自信を持って開発できない状況では、いちいち確認や理解のための時間が必要になる
  2. 1の結果、とにかく優れたスキルを持つソフトウェアエンジニアが、ソフトウェアエンジニアとして能力を最大限発揮できる環境が整っていないこと
    • ソフトウェアエンジニアリングで突破力のある人が、より活躍できる環境にしていきたい

この手の問題については、「分割統治」が基本戦略です。困難は分割せよ、というやつです。実際のところ3倍速くなるかはわからないですが、ヘンリーの向き合っているような複雑なドメインにおいても有効なはずだと思っています。

(分割統治はチーム分割を内包する概念ですが、プロダクトチームとしての分割だけを意味するわけではありません。コードベースだけの分割も非常に重要な戦略です。)

速く、スケールする開発を続けるために

Henry の開発に必要な知識は、

  • 制度の知識
  • 業務の知識
  • コードベースの知識
  • 技術の知識

の4つに分類できます。

「技術の知識」については、スキルのあるソフトウェアエンジニアであればあまり問題にならず、今のヘンリーでは課題感はあまりありません。

一方で、医事会計システム側を開発するチームに関しては、これまでも長らく「制度の知識」に縛られた構造になっていました。

「制度の知識」は専門性が高いので、そこに習熟することを明確に役割として持ったチームを作ろう、という考え方にもとづいたチーム設計で、「その制度を理解できた(できる)人(チーム)は貴重だから、その制度にまつわる実装はその人(チーム)に集中させよう」という方向性の最適化が行われていました。

これはフェーズの問題なので、これまでが間違っていたというわけではありませんが、現状の規模、またこれから先のスケールに対しての戦略としてはハマらない、と思っています。

具体例がないとわかりにくいので、いくつか挙げると(といいつつドメイン知識がないとわからない説明ですみません。雰囲気を感じ取ってもらえると。)

  • 「この診療行為を算定するときは、レセプトファイルにこのような順番で出力する必要があるので、このように自動算定しておく」
  • 「この公費制度で会計する場合は、レセプト上の一部負担金はこのように出力し、窓口会計ではこのように出力する」
  • 「この公費制度はレセプト上このように出力する必要があるので、そのように公費が使われていないときはバリデーションで警告する」

このような開発は、従来「レセプト出力機能」と「自動算定」など、またがる領域の両方に詳しくないと実装できませんでした。したがって同じチームに包括されている必要があったのです。

しかし、いま「制度の知識」「業務の知識」「コードベースの知識」のすべてが、一人の人間の頭に入る規模ではなくなってきています。

そこで必要なのは、開発対象を分割することです。 「制度の知識」を「技術の知識」と同じような『ヘンリーのエンジニアならみな知っている(調べられる)』土台にし、そのうえで「ある制度の知識」に依拠する実装をするチームが、複数あっても構わない、といえる状態を目指します。

「ある制度の知識」に依拠する実装をするチームが、複数あっても構わない、といえる状態にするには、何が必要でしょうか?

分解すると、

  • その制度を理解できる人を増やせる
  • ある制度に依拠する実装が分散しても開発効率が落ちない

ことが重要です。

「その制度を理解できる人を増やせる」については、実は現状では大きな障壁はないのではないかと思っています。今のヘンリーの開発チームには、制度とシステムの両方に非常に詳しいドメインエキスパートが在籍しています。そのような環境であれば、多くのエンジニアが制度を理解できるはずです。

「ある制度に依拠する実装が分散しても開発効率が落ちない」はソフトウェアアーキテクチャの最適化によって達成されます。構成要素の分解と境界面の明確化を行っていく必要があります。

そのうえで、私たちのプロダクト開発における考え方のベストプラクティスを構築していくことも重要です。

たとえば

  • 一定の重複処理・重複作業を許容したほうがマシになるケースがあるということを認める
    • そのような無駄を許容することでどんなスケーラビリティを獲得しようとしているのかを広く理解する
  • 他のコンポーネントをなるべく信用しない方針を取る
    • 一つの制度を表現するのに、「こっちのコンポーネントのこの部分で計算する値をいじっておいて、あっちのコンポーネントで後からそれを使って補正する」みたいな作り方をしない

といった考え方です。

おわりに

ソフトウェアエンジニアとして、デリバリーをもっと速くすることでいろんな課題が解決すると信じていますし、そこにもっと向き合っていかなければならないと思っています。

ここに書いた内容は青写真感のある内容ですが、もっと足元の改善ポイント、たとえばリファクタリングやツールの改善によって速度をあげられる部分もたくさんあります。

最近の実践例としては、

  • 大きくなってきたチームを分割し、より小さく速く挑戦し失敗し学べる体制を整えつつある
  • モノレポ化とモジュール分割によって、コードベースとしての分割統治が進んできている
  • AI コーディングツールの導入によって、開発の生産性は相当に向上している
  • 主要なコンポーネントのうち負債が大きくなっていたものをリニューアルし、新規制度への対応工数の大幅な削減とスケールする状況を実現している

など、デリバリーを速くするための具体的な活動も進んできています。

社会的意義やお客様にとっての価値に対して貢献し続ける意識を前提に、ソフトウェアエンジニアとしてしっかりソフトウェアエンジニアリングの改善にも向き合っていきます。 また、ソフトウェアエンジニアリング力がより効果的に発揮できるような土台の改善も行っていきます。 ヘンリーはソフトウェアエンジニアを積極的に募集しています!興味を持っていただけた方は、ぜひお話させてください!

jobs.henry-app.jp