H-E-BにおけるエンタープライズHaskellの導入と運用
(blog.haskell.org)- 435を超える店舗と物流センターのサプライチェーンを近代化する過程でHaskellを導入し、8年後には複数チームが管理する約100万行のコードが中核プロダクションシステムで稼働している
- Haskellはランタイムエラーの種類を減らし、GHCベースのリファクタリングやレガシー連携を支援する一方、SDK不足に伴う独自保守や高度な型機能の複雑さを受け入れる必要がある
- 成功の可否は言語そのものよりもチームの力量と文化に大きく左右され、Haskellの専門家がいなかったチームではスペースリークや中核開発者の離脱により2つのプロジェクトでは定着しなかった
- GHCランタイムがH-E-Bの本番障害の原因になったことはなく、HLS・OpenTelemetry・プロファイリングなどエコシステムの発展により、プロダクションHaskellは競争力のある選択肢へと変わった
- 高度な機能は小さなライブラリと明確なインターフェースの背後に限定し、テスト・可観測性・知識共有を並行して進めるべきであり、コーディングエージェントの時代にはGHCの型検証と高速フィードバックがさらに重要になる
メインフレーム近代化の課題
- テキサス最大の非上場企業である小売業者H-E-Bの顧客は、社内システムよりも店舗の棚に商品が並んでいるかを重視する
- 何十年にもわたりCOBOLメインフレームは安定稼働してきたが、この10年でカーブサイド受け取り・宅配・リアルタイム在庫・店内検索といった技術主導の機能が登場した
- 435を超える店舗とそれを支える物流センターへとサプライチェーン運用が拡大する中で、既存の中核システムには当初の設計範囲を超える要求が突きつけられた
- メインフレーム近代化の最大の難しさは、コードをクラウドへ移すことではなく、数十年にわたりレガシーなビジネスロジックに蓄積された組織固有の知識を保全することにある
- 数百のテーブルを扱うモノリスを、自律的なエンジニアリングチームが所有するマイクロサービスへ分割する作業は、技術的にも政治的にも巨大で、コストも高い
Haskell導入と運用規模
- コンサルタントは、社内で最も大きいメインフレームアプリケーションを置き換える新規プロジェクトの共通言語としてHaskellを提案した
- 実験として始まった取り組みは、複数の中核サプライチェーン向けプロダクションシステムへと成長した
- 導入から8年後には、異なるドメインの複数チームが約100万行に及ぶHaskellコードを保守している
- 長期にわたる本番運用を通じて、実際の成果だけでなく技術的・組織的な難しさも明らかになった
ビジネス面で得られた利点
- 企業が求めているのは、遅延評価・純粋関数・不変データそのものではなく、40年間null pointer exceptionなしで低保守コストのまま動く次世代COBOLのようなシステムである
- Haskellアプリケーションは、命令型・動的型付けプログラムで一般的なランタイムエラーの種類を根本的に取り除く
- バグが完全になくなるわけではないが、残る問題はnull pointer exceptionや型の取り違えよりもビジネスロジックの誤りに集中する
- 数年にわたるストラングラーフィグ(strangler fig)方式のレガシー移行において、GHCは安全なリファクタリングを支援する
- 要件が固まるまで反復的に修正できるため、ウォーターフォールではなくアジャイルに移行できる
- 企業は固定化した誤った成果物に先行投資する代わりに、段階的に成果を測定できる
- Haskellパーサーは、固定長のIBM copybookのように数千行に及ぶレガシー形式をそのまま契約として扱える
- 強力な**外部関数インターフェース(FFI)**により、Cライブラリしか提供しない既存サービスとも連携できる
- H-E-Bは
simdjsonC++、OracleODPI-C、IBM MQをラップするHaskellライブラリを書いた
- H-E-Bは
SDK不足と契約の所有権
- Haskell向けSDKが不足している場合、必要なクライアントや統合コードを自前で作り、継続的に保守しなければならない
- その代わり、契約を自ら所有し、ベンダーロックインを避け、上流プロジェクトの保守者が修正版をリリースするのを待たずに済む
- H-E-Bは中核依存関係については、このトレードオフは十分に価値があると判断した
- コーディングエージェントは、GoライブラリをHaskellへ移植するような機械的作業をうまく処理でき、言語間の開発速度差を縮められる
- ただし単純な翻訳では、精緻なプロンプトがなければHaskell型システムの利点を十分に活かせないことがある
組み込みDSLによる多言語統合
- Haskellは**組み込みDSL(eDSL)**のホスト言語として強みを持つ
- 中央チームがドメインモデルやビジネスロジックを一度定義すれば、他チームが使う言語向けに安全な型付きコードを生成できる
- 他チームはHaskellを直接書かなくても、そのシステムと統合できる
- Hydraは、Uberのグラフデータ作業から発展したオープンソースDSLフレームワークである
- Haskellでブートストラップされ、Java・Python・Scalaなどの意味的に等価な実装を生成する
- 共通のテストスイートにより、言語ごとの実装の等価性を保証する
- H-E-Bは、機微な財務計算をHaskell・TypeScript・Javaチームで共有できるHaskell DSLを試作している
- 双方向JSONスキーマDSLも構築した
- autodocodecに似ているが、利用者が独自の型クラスで中核的な意味体系を拡張できるtagless finalエンコーディングを採用している
- スキーマのバージョン管理、ドキュメント・OpenAPI生成、回帰・ゴールデンテストを標準化する
- 一時的なインタープリタを書いて、JSONスキーマをPostgreSQLのテーブル定義へ変換したり、
aesonをsimdjsonに置き換えて追加検証を適用したりできる
採用とエンジニアリング文化
- 現代的な言語もHaskellの影響を受けた型安全性・純粋性・並行性の機能を数多く提供しており、実際の違いは言語が促すエンジニアリング文化にある
- Haskellは、正確さと抽象化設計を重視する開発者を引き寄せる
- Haskell職の採用は、経営陣が予想したほど難しくなかった
- サプライチェーンに特別な関心がなくても、Haskellそのものを理由に応募する候補者がいた
複雑性予算の限界
- Haskellは古い言語であり、いくつもの欠点があるため、最新のGHCリリースに追随することが重要である
- チームに専門家がいなければ、1つの言語的な欠陥がプロジェクト全体に広がりうる
- 初期には、熟練コンサルタントが言語拡張やユーザー定義演算子の過剰使用を防ぐガードレールを整えた
- GHC2021・GHC2024の言語エディションが存在しなかった時期には、各チームが基本拡張セットを自分たちで管理していた
- これは、新規入社者が貢献前に習得すべき言語範囲を制限する複雑性予算として機能した
- 依存関係そのものも複雑性予算を消費するため、組織全体での強制は難しい
servantはAPIを型レベルDSLとして表現し、ユーザー定義の結合子(combinator)拡張にはtype familyと難解なエラーが伴うhaxlは並行作業を一括処理するため、すべてのデータソースを結果型でインデックス付けされたGADTとしてモデル化する
- 開発者が熟練するほど、GADT・type family・generic・tagless final・arrow・free structure・higher-kinded data typeパターンを導入したくなる誘惑が強まる
- すでに複雑なビジネスドメインの上にこうした概念が積み重なると、保守しやすさという約束は弱まる
- エコシステムに慣れるにつれて高度な機能が徐々に増えるため、複雑性の増大は後になって表面化することがある
退屈なHaskellの運用原則
- 組織として退屈なHaskellを推奨するのは容易だが、強制するのは難しい
- 高度なHaskellを妥当なインターフェースの背後にカプセル化すれば、影響範囲を抑え、複雑性予算を少数の専門家が管理する小さなライブラリに限定できる
- 言語レベルの複雑さはHaskell固有の問題ではないが、Haskellは利用可能な高度ツールの幅がとりわけ広い
- コード行数やトークン使用量よりも、次のGHCアップグレードでどれだけのコードが壊れるかのほうが、Haskellプロジェクトの複雑さをよく示す
定着しなかった2つのプロジェクト
- Haskellはあるチームの力を高める一方で、別のチームには障壁にもなりうる。H-E-Bでも異なる理由から2つのプロジェクトに定着しなかった
- 1つ目の事例では、Haskell経験のない熟練開発者たちが他チームのプロジェクトテンプレートや慣習に従ったが、**スペースリーク(space leak)**によってプログラム全体が停止した
- 強いHaskell開発者がチームを導けなかったため、この1つの問題がプロジェクト中断につながった
- 2つ目の事例では、Haskellを支持しアプリケーションを作ったエンジニアたちが、優先順位の変更で別プロジェクトへ移った
- これは言語そのものとは無関係の理由だった
- どちらのチームにも既存技術へ戻る選択肢があり、Haskellが生産性を高めると確信できなかった
- Spring Bootテンプレートのような逃げ道があると、厄介なスペースリークを最後まで診断する動機が弱くなりうる
- LLMがコードベース全体を機械的に翻訳できるようになったことで、書き換えの選択肢はさらに広がっている
- 長期的な防衛線は、賢すぎて書き換えにくいコードではなく、Haskellを保守し続けたいと思うチームである
本番障害と診断
- Haskellを受け入れたチームでも、いくつもの深刻な問題が発生した
- カスタム参照カウントでdouble-freeエラーが発生した
- あるプロジェクトでは、暴走するスペースリークを解決するため並列ガベージコレクションを無効化した
- 無限ループは、発見自体が難しく、特に恐ろしいバグだった
- GHCランタイムがH-E-Bの本番障害の根本原因になったことは一度もない
- 定常時のスループットとメモリ使用量はH-E-Bのワークロードで十分に競争力があり、問題事例は一般的な状況ではなく例外だった
- エンジニアたちは、セグメンテーション違反のコアダンプ、ガベージコレクション指標、ヒーププロファイルを追跡して問題を解決した
- 強力な型システムを理由にテストカバレッジを犠牲にしてはならない
- Haskellアプリケーションでは単純なエラー種別がコンパイル段階で除去されるため、カオスエンジニアリングによって実際のロジック障害やリソース障害をより直接的に露出させられる
変化した開発ツールとエコシステム
- 初期システムはGHC 8.2で始まり、当時は主要プラットフォーム向けAPIクライアントやSDKの大半を自前で書かなければならなかった
- Haskell Language Serverは存在せず、GHCupは登場したばかりで、ランタイムプロファイリングも難しかった
- Java IDEに慣れた開発者はHaskellのツール環境を使いにくいと感じ、H-E-BはVS Code利用者向けのDebug Adapter Protocolサポート開発を後援したこともある
- 現在では、プロダクション級のHaskell開発が可能な水準を超え、競争力のある水準へと進化している
- 開発ツールにはHLS、
ghcid、ghciwatch、static-ls、tricorder、エディタプラグインがある- 一部は豊かなGHCi中心の開発のためにGHCプラグインを使い、一部はVS CodeとHLSを使う
- 開発者が好みのツールを選べるようにしている
- 可観測性には
hs-opentelemetryとopentelemetry-autoを用いているopentelemetry-autoは、Haskell関数にOTel spanを自動挿入するGHCプラグインである- Java・Pythonと比べても同等の計測環境を提供できる
プロダクションライブラリとランタイム改善
- サービスでは
warp、aeson、attoparsec、stm、conduit、servant、orville-postgresqlのようなライブラリを利用している - エコシステムに必要な機能がなければ、自前のライブラリを開発する
- arbiterはH-E-Bの業務から生まれたトランザクションベースのPostgreSQLジョブキューで、現在本番運用されている
- GHCは、ランタイム診断、コンパイラ性能、eventlog streamingのような監視機能、そして例外注釈を改善し続けている
- GHC 9.2でinfo table profilingが導入されて以降、スペースリークへの懸念は大きく減った
社内コミュニティと知識の分散
- H-E-Bは社内ライブラリを重視しており、開発者が社内プロジェクトとオープンソースプロジェクトの両方に参加することを奨励している
- 何年にもわたり隔週のHaskell勉強会を運営し、エンジニアが調査結果を共有し、他のメンバーを教育してきた
- エンタープライズ組織のサイロを減らすには、チーム間でアイデアやパターンが行き交う必要がある
- より多くのエンジニアがコードベースを読み、所有するようになれば、1人の退職がプロジェクトを揺るがすバス係数リスクを下げられる
高度機能を許容する基準
- 緩やかな基本ルールを設けつつ、複雑性予算をどこに使うかは技術リードが最終判断する
- 保存された個人識別情報(PII)を暗号化しなければならないなら、機密データ漏えいをランタイム事故ではなくコンパイルエラーに変える型レベル保証は投資に値するかもしれない
- 4つのAPI契約をtrees-that-growパターンで統合する作業は、外部スキーマをtype familyで覆い隠すほどの価値がないかもしれない
- 退屈なHaskellは、型レベルプログラミングや高度なGHC機能を禁止するのではなく、原則ある慣習を定めたうえで、逸脱コストを評価する
- 主な慣習は「遠く離れた場所で予期しない作用が起きないこと」である
TemplateHaskellによるコード生成やGenericベースのシリアライズロジックは避ける- ボイラープレートは手で書き、プログラマに挙動を明示的に示す
- quasiquoterや
Genericallyによるderivingを完全に禁止しているわけではない
- Webフォームに型レベル状態機械を実装したり、通常のADTで十分な場面でGADTを使ったりする場合には、健全な懐疑とチームレベルの反論が必要である
- 高度なHaskellはライブラリにも本番アプリケーションにも存在するが、実験の範囲は制限しなければならない
- 仕事と技術的な遊びのあいだを調整する過程で、実際のエンジニアリングが行われる
これからのHaskellとコーディングエージェント
- 長年GHCを使う中で少数のバグを見つけて報告したが、業務を止めるほどの問題はなかった
- 本番事故はまれで、型エラーやランタイムよりもビジネスロジックで発生することのほうがはるかに多かった
- コーディングエージェントが書くコードの比率が高まるにつれ、ボトルネックはコード作成から検証へ移りつつある
- GHCの型システムは、人が安全にリファクタリングするのを助けると同時に、エージェントが幻覚で作ったコードを実行前に拒否する高速フィードバックループを提供する
- 産業界でHaskellの可能性を広げるには、実際の本番成功事例がさらに共有される必要がある
1件のコメント
Lobste.rs のコメント
テキサス以外ではあまり知られていないかもしれませんが、H-E-B はテキサスを象徴する機関で、州政府のもう一つの部門と言ってもよいほど規模と影響力が大きいです。
多くの店舗にトルティーヤ製造設備があり、大規模生産品としては品質も素晴らしいです。さらに Haskell も使っているそうです。
H-E-B のような企業が COBOL を置き換えるために Haskell を選んだという事実には本当に驚きます。
コーディングエージェントが Go ライブラリを Haskell に機械的に移植できるため開発速度の差が縮まったと言いながら、巧妙なプロンプトなしでは型システムを十分に活用できないかもしれないとすぐに認めているこの2文は、互いに矛盾する問題を示しています。
一方で、些細なエラーがコンパイル段階で取り除かれるため、カオスエンジニアリングでインフラ障害を注入した際に、動的型付け言語のノイズではなく実際の論理・リソースのエラーが表面化する点は、Haskell を検討する最も説得力のある理由に見えます。
先週 Bazel で分離された Haskell ビルドを構成しようとしましたが、すぐ使える解決策は見つかりませんでした。
rules_haskellは基本的に分離されておらず、一般的なアプローチはnixpkgsに大きく依存しているようです。Haskell はシステムの C++ リンカーに依存しているように見えますが、これが現在の実情なのか、それとも見落としている代替案があるのか気になります。