ほとんどのエンジニアは本番のMUMPSを書くことはない。私はやった — 国家レベルのヘルスケアをカバースケールでLinuxベースのfis GT.M上で動くVistAのPharmacyとBillingパッケージで。カンファレンスでこれを言うたびに誰かが冗談を言っているか尋ねる。冗談ではない。実際の様子はこうだ。
VistAは抽象化ではなく、コードベースだ
VistA(Veterans Health Information Systems and Technology Architecture)はVAのオープンソースEHRだ。1970年代から継続的に開発されている。そのPharmacyパッケージは地球上で最も古く実績ある薬剤調剤システムの一つだ。「実績がある」というのは:40年以上のVAプログラマー、予算サイクル、MUMPSバージョン、ハードウェア移行、そして議会を生き延びたということだ。
コードベースは美しくない。設計されていない。積み重なっている — 主に死んでいるか引退した人々によって層を重ねられ、育てられ、パッチされ、拡張された。ルーティンのコメントヘッダーは1987年からだ。; MODIFIED BY DPT 3/12/89 — DO NOT DELETEを見つけるだろう。その下にあるものを絶対に削除しない。なぜなら、それが何をするかはわからないし、まだ雇われている誰もわからないからだ。
これが40年のアップタイムを持つとき本番がどんな様子かだ。
GT.MはMUMPSだが、Linuxで出荷されて非常に速い
「MUMPS」と言うとき、たいてい二つのランタイムのどちらかを意味する:InterSystemsのCaché(今はIRIS)、またはFIS(以前はGreystone Technology、それでGという)のGT.M。GT.Mはオープンソースで、Linuxで動き、ほとんどのVistAインストールが使うものだ。VAが使うランタイムだ。私が使ったランタイムだ。
GT.Mは — ここで慎重になりたい — ストレージエンジンとして本当に印象的だ。MUMPSグローバルをジャーナルレベルのクラッシュ安全性を持つディスク上のB-treeとして実装している。SET ^PSDRUG(drugIen,"QTY")=qtyはデータベースサーバーへのラウンドトリップではない。GT.MがフラッシュしてジャーナリングするローカルB-treeへの書き込みだ。MUMPSルーティンを実行するプロセスとストレージエンジンは同じプロセスだ。ネットワークホップはない。ORMはない。クエリプランナーはない。
これはおもちゃのように聞こえる。適度なハードウェア上の1990年代のシステムが書き込みが重いワークロードでShiny Spring Boot APIを追い抜くディスペンシングスループットベンチマークを見るまで。そのとき理解できる:アプリケーションロジックとビットの間には何もない。
Pharmacyルーティンを書くことが実際にどんな様子か
Pharmacyパッケージは薬品在庫、調剤オーダー、薬物相互作用チェック、IV混合レコードを管理する。MUMPSで。^PS*グローバルで(PS = Pharmacy System)。
調剤ルーティンはおおよそこんな感じだ:
DISPENSE(DFN,DRUG,QTY) ;dispense DRUG to patient DFN
N RESULT,AVAIL
L +^PSDRUG(DRUG,"STOCK"):5 E D ERRLK Q
S AVAIL=$G(^PSDRUG(DRUG,"QTY"))
I AVAIL<QTY D INSUF Q
S ^PSDRUG(DRUG,"QTY")=AVAIL-QTY
S ^PSDRUG(DRUG,"LAST")=$$NOW^XLFDT()
S RESULT="OK"
L -^PSDRUG(DRUG,"STOCK")
Q RESULT
眺めてどうぞ。待っている。
NはNEW(ローカル変数スコープ)。L +^GLOBAL:timeoutはロック取得。$G()はデフォルト付きGET(nullポインターエラーは決してない、MUMPSは$Gがある)。SはSET。QはQUIT。$$は外部関数を呼ぶ — $$NOW^XLFDT()はXLFDTルーティンのNOWラベルを呼ぶ。FileManフォーマットのタイムスタンプを返す(FileManの日付は別の投稿だ、触れないようにしよう)。
グローバル^PSDRUGは階層的だ:トップレベルのサブスクリプトは薬品内部エントリ番号(IEN)で、その下に「QTY」、「STOCK」、「LAST」などの名前付きサブキーがある。これがスキーマだ。スキーマファイルはない。スキーマはコードの中に暗黙的にある。GT.MのD ^%Gユーティリティでコードを読んでグローバルを眺めることで学ぶ。
薬物相互作用チェックは特定の獣だ
VistAで書いた最も不安を引き起こすコードは薬物相互作用チェックに関連していた。^PSSDIと^PSDRUGグローバルは相互作用データを保持し、Pharmacyパッケージには調剤オーダーが確認される前に発火して、新しい薬が患者のアクティブな投薬リストにある何かと相互作用するかどうかをチェックするルーティンがある。
これをストレスフルにするのはMUMPSではない。MUMPSはただの構文だ。ロジックが安全ネットだということがストレスフルにする。作業をダブルチェックするダウンストリームサービスはない。ルーティンが実行され、薬剤師が結果を見て、もし相互作用チェックロジックにバグがあれば、フラグを立てるべきだった薬の組み合わせが調剤される。
それらのルーティンを執拗にテストした。GT.MのM-Unit(1995年に設計されていたらJUnitがこんな感じというテストフレームワーク)がベストフレンドになった。既知の相互作用でシナリオを設定した — ワルファリンとアスピリン、メトトレキサートとNSAID — フラグが一貫するまで実行した。これはおそらく私のプロフェッショナルキャリアの中で最もコードに対して慎重だった時だ。
HL7メッセージングパッケージで奇妙なことになった
VistAのHL7パッケージはある場合にHL7 v2仕様のクリーンアップより年数が古い。HL7メッセージを解析して生成するルーティンはMUMPSで文字列スライスをやっている — セグメントを抽出するための$E(MSG,start,end)、連結するための_、デリミタで分割するピース関数。
そこで見つけたもの:明らかに戻って直さなかった人々からの; KLUDGE — MO WILL FIX LATERコメントを持つ患者人口統計セグメントのエッジケース。いくつかを直した。残りのためにメモを残した。レガシーコードはこうして生き延びる — クリーンアップされることによってではなく、負荷を担うkludgeの周りに情報量が増えていくコメントを蓄積することによって。
AFAQでのGT.M DBコネクター作業
EHSでのVistA時間の後、私はVA VistAコンポーネントの一部の上にEHR/EMRスイートを構築していたAFAQに移った。すぐに直面したギャップ:GT.MにはモダンなDBコネクターがなかった。JDBCなし。RESTアダプターなし。Javaアプリやウェブフロントエンドが1990年代からの古代のTCPベースのRPCレイヤーを使わずにそれに話しかける方法がなかった。
新しいコネクターを作った。課題はGT.MのネイティブアクセスメカニズムがインプロセスMUMPSかその$gtm_dist C API — GT.MルーティンをCから呼び出し、グローバルにアクセスするCライブラリ — のどちらかだということだ。Spring Bootバックエンドが直接呼び出せるようにJNIレイヤーでそれをラップした。美しくはなかった。JNI境界をまたいだエラーハンドリングは特に創造的な体験だった。しかし機能し、ラウンドトリップレイテンシを十分に削減して、EHRのチャートロード時間が「恥ずかしい」から「許容できる」になった。
そのプロジェクトからのより深いインサイト:GT.Mはシンプルだから速い。その上に追加するすべての抽象レイヤー — REST、JNI、TCP RPC — はそのシンプルさのコストをいくらか払う。コツは、消費システムが話しかけるのに必要なだけの抽象化を追加して、それ以上追加しないことだ。
私が持ち帰ったもの
国家スケールのヘルスケアシステムで本番MUMPSを書くことはキャリアパスとして推薦しない。それと取り換えることもしない。
モダンなバックエンド作業では教えてくれないものを教えてくれたものはここにある:
- ストレージコストは常に存在する。ほとんどの言語はただそれを隠す。 MUMPSでは、すべての
S ^global(key)=valueはストレージ操作だ。書き込みがコストを持つことを決して忘れない。Javaでは、HibernateセッションがリクエストごとにDB問い合わせを400回始めるまで常に忘れる。 - スキーマ設計は、スキーマファイルがなくても依然として設計だ。 MUMPSグローバルサブスクリプトの階層はデータモデルだ。悪いキーの選択は、それらのグローバルを読むすべてを書き直さなければ修正できない悪いアクセスパターンにカスケードする。
- クラッシュ安全性はあなたが思うよりも重要だ。 GT.Mのジャーナリングはデータ破損イベントなしにVistAインストールが年単位で動くことを意味する。私のPostgresデータベースは同じ保証を達成するために慎重なトランザクション衛生を必要とする。MUMPSモデルは安全なパスをデフォルトのパスにする。
今はほとんどJavaとTypeScriptを書いている。データベースはPostgresだ。ランタイムにはGCがある。しかしスキーマ設計をレビューしたりトランザクション境界について議論したりするとき、脳の一部はまだあのGT.Mシェルにいて、^PSDRUGグローバルを眺めて調剤サージの午前3時の読み取りがどんな様子かを考えている。
それはノスタルジアではない。それは教育だ。