Webアプリの安全装置は簡単につけられる

Webに限らずコンピュータシステム一般に言えることなのだが、 簡単な安全装置をつけるのを怠ることで何が起きるかという事例がこちら。

asahi.com: みずほ証券、誤注文で270億円の損失 (2005/12)
「1株61万円で売り」とすべきところを「1円で61万株を売り」にして しまったとのこと。 すかさず買いを入れて数億円規模の利益を得てしまったデイトレーダーもいるとかいないとか。 うらやまし。(笑)
ネットでテニスコートを不正予約 (nikkansports.com 2005/11)
市区町村などの公営テニスコートのネット予約システムの不備を突いて、 本来は申し込めないはずの1ヶ月以上先の日程について予約を入れた輩がいたらしい。 不審に思った他のユーザーの指摘であっさり発覚。

いずれの場合も、安全装置をつけることは簡単だったはずだ。

株の例で問題となったJCOM社の発行済み株式総数は4万株くらいだったらしい。 だったら60万株もの売買が成立するはずが無い。 「発行済み株式総数を超える数値の売買注文はありえないので却下」 というごく当たり前かつ簡単なロジックを入れることを誰かが怠った。

テニスコートの例も、「予約できるのは1ヶ月先まで」という仕様なのなら、 「現在日時+1ヶ月 以上の日付は却下」というロジックを入れるだけだ。難しくない。

ちょっと話は変わるが、コンビニやスーパーマーケットで ノートPCの「液晶パネルだけ」みたいな不思議な端末を首からぶら下げて、 商品棚の前でパネルをピッピッと押している店員さんを見たことはないだろうか? あれは発注端末である。例えば日付を入れて「牛乳」の欄に「10」を入れると 本部を通じて卸業者に仕入れ注文が飛び、牛乳10本が指定日に届くという仕組み。 昔コンビニでバイトしていた筆者の知人はためしにと思って牛乳1000本という 入力をしてみたら「過剰注文!ほんとによろしいですか?」みたいな 警告メッセージが出たとのこと。 なるほど、何百種類もの商品を日々点検して品切れの無いように発注している 店員さんなのだから、こういう入力ミスはありうることだ。 見事な安全装置である。

でも、別にここまでユーザーフレンドリーな安全装置じゃなくたっていいのだ。

例えば、小売店舗なりECサイトなりのポイントシステムがあるとしよう。 データベースにPostgreSQLを使っているとして、 会員テーブルに会員番号とポイント残高が記録されるとする。

CREATE TABLE kaiin_table (
    kaiin_id int4,
    point    int4
)
貯まったポイントを使って買い物をするときはpointからそのぶんを減算するわけだ。
UPDATE kaiin_table 
    set 
        point = point - 使ったポイント 
    where
        kaiin_id=その人の会員番号
みたいに。

ここでもしも仮に、「自分の持っている以上のポイントを使う」 あるいは極端な話「1億ポイント使う」というどう見てもありえない命令が来てしまったら、 当然、却下しなければならない。

どういうチェックロジックを入れたらいいだろう? 「ポイント残高 > 使用ポイント数」が一般的だが、 いちいちポイント残高をDBから引っ張ってこなければならない。面倒だ。 「1億 > 使用ポイント数」とか? いやそれだとしきい値を1億にするか1万にするか考えなければならないし、 そもそもポイントシステム用のチェックロジックにしてはあいまいだ。

筆者だったらこうする。

create table kaiin_table (
    kaiin_id int4,
    point    int4,
    constraint pointcheck check(point >= 0)
)
たったこれだけ。DB定義の段階で1行追加するだけである。 「ポイント残高はゼロ以上であって、ゼロ未満=負の値になることはありえない」 というごく常識的な仕様を、データベースにきちんと定義しているだけのことだ。 たったこれだけで、上のUPDATE文で使用ポイント数がいくらであっても結果としてポイント残高が負の値になってしまうようなら却下される。自分の持つポイント数以上のポイントを使用して買い物されてしまうことを防ぐ効果がある (注文処理とポイント減算処理とでちゃんとトランザクションとっていればの話であることは当然として)。 買い物での使用以外にもポイントをとりあつかうほかの処理(他社のポイントとの交換とか)があっても、 同様に安全装置として働いてくれるだろう。

もちろんこれではユーザーフレンドリーとはならない。 アプリケーションレベルの安全装置じゃないからだ。 これだけだと、上で書いたコンビニの発注端末みたいなエラーメッセージを出すことはできないので、エラー処理は別途書く必要がある。書かなければ、 なんだか意味不明なエラーをユーザーに見せることになるだろう。 しかし実際に1億ポイントをご利用のご注文が成立してしまうよりは百倍マシである。

さして複雑でもない常識レベルの仕様を(できればシステム上の低レベル層で)たった1行書くだけで、入力ミスであれ悪意の操作であれ多くのの想定外な現象について水際で防ぐことができる。それがフェイルセーフなシステムへの第一歩だ。 こんなところで力説するまでもないことだと思う。しかしそれを怠ったがための事件が起きている。

追記:
こんな記事があった。

東証には、異常な売買注文がきても、成立前にチェックする仕組みがない。「銘柄ごとに発行株式数を確認するとシステムに負荷がかかり過ぎる」との理由からだ。
東証、誤発注のジェイコム株売買停止に 事態収拾見えず (asahi.com 2005/12)
システムに負荷がかかりすぎる??そぉかあ? 上場企業全部あわせても1万社にも満たない。 そんなテーブルを検索するのにかかる負荷なんてビビたるものだ。 発行済み株式の総数なんてそういつもいつも変わるもんじゃないからキャッシュしておいて性能を上げることだってできるはずだ。 「はじめからそんなチェックロジックは考えてなかった」が実際のところなんじゃないだろうか。

2006/1/21 追記
みずほ証券が発表した今後の対策に次のようにあった。

・・・人為的な入力ミス等による誤発注に対するシステム的ガードを強化いたします。・・・
(1) 発注不可となる上限金額及び上限数量を変更
(2) 発行済み株式総数を基準とした上限数量の設定
・・・
こうした基本的チェックロジックの必要性を知るために使った勉強料が数百億。高くついたものだ。

トラックバックURL

このエントリーのトラックバックURL:
http://www.ywcafe.net/mt/mt-tb.cgi/508

トラックバック

» To Pay the Price of the Error is System from 404 Blog Not Found
今回のジェイコム株の事故で、同様の感慨を持ったGeek達は多いのではないか。 大事故の予兆をさぐる 宮城 雅子 人間の問題? | Ok... 続きを読む

コメント

報道によるとみずほ側の端末が警告を出したのに
オペレータが無視してしまったとのこと。
コンビニの例と同じですね。

空売りという取引もあるので、
残り株数がマイナスになる事は有り得ます。
検索対象のテーブルは1万件以下かもしれませんが、
その検索が時間あたり何回行われ、
何秒以内で応答すべきかが問題になると思います。
別に東証を弁護するわけではありませんが、ご参考まで。

JCOMじゃないですよ。
ジェイコムです。
CATV会社になってしまいます。
今回の件で、いい迷惑だったのは、J:COMかも。

トラブルが起こってからならいくらでもいえますよ。今現在でもトラブルの可能性のあるものはいくらでもあると思いますが事前にそれを仕様に盛り込んで「これで?万円です」って顧客に言ったらそんなのいらないって言われる可能性が高いと思いますけどね。今回のも何十万株もの大量発注があった場合どうするかをもし事前に議論したとしても、警告画面を出してるし、そんな注文出すほうの責任ってなると思いますが。

>「発行済み株式総数を超える数値の売買注文はありえないので却下」というごく当たり前かつ簡単なロジックを入れることを誰かが怠った。

「発行済み株式総数」は、あまり変化しない「定数」だと思ってませんか?
実際には増資や株式分割、転換社債の行使などなどで日々変化します。
全ての銘柄について株式総数を管理して、売買のたびにチェックする
ことは、けっして「簡単なロジック」の追加ですむものではありません。
やるとしたら、新たなサブシステムの追加になります。

またサブシステムが増えることにより追加の運用が発生し
そこで事故が起こる可能性も増えます。

証券会社が払えるぎりぎり限度いっぱい搾り取れる限り搾り取るまではミスとならないシステムができたりして。

生かさず殺さず。

コメントする

(初めてのコメントの時は、コメントが表示されるためにこのブログのオーナーの承認が必要になることがあります。承認されるまでコメントは表示されませんのでしばらくお待ちください)


画像の中に見える文字を入力してください。