CachéとMUMPS
InterSystemsがISMからOpen MそしてCachéをリリースするに至った経緯は、明らかにMUMPSからの決別であったことは疑いようがない。
1997年にCachéを発表してから20年以上の月日が経過したわけであるが、その意図とは裏腹にMUMPSからの決別が完了したかと言えば、
残念ながらそうとは言えない。
市場の認識は未だにCaché=MUMPSである。 少なくとも日本においては。
いまさらながらそれを払拭したいと思い、この文章を書いている。
そしてこの文章を書いている内にインターシステムズは、Cachéに代わる後継製品としてInterSystems IRISデータプラットフォーム(以下IRISと表記)をリリースした。
IRIS=MUMPSとは絶対言わせたくないという思いを新たに強くしている。
MUMPSの問題点
一部に未だに強力な支持者がいるMUMPSであるが、システム開発環境としては様々な問題がある。
データモデル
MUMPSのデータベースのコアであるグローバル(多次元配列変数)は、いくつかのすばらしい特性をもっており、Caché, IRISでもそれを踏襲しているわけであるが、最大の問題は、
データ設計の自由度が高すぎる点である。
これにはもちろん良い面もあるのだが、データモデリングが属人化しやすく、ソフトウェアが大きくなるにつれ、複数人数で開発を分担するようになると
ソフトウェアの開発管理が難しくなる。
まだデータモデルを熟考することなく実装が可能であり、簡単に途中で路線変更することも可能なため、実際に付け刃的な対応を繰り返していくことも多い。
これが開発の初期段階では開発生産性が劇的に上がったような錯覚をもたらし、未だにMUMPSの生産性の高さを喧伝する人々にはこの発想から抜け切れていない人々が多い。
これは言わば慢性疾患のようであり、そういう付け刃的な対処を繰り返してもなんとか対処できてしまうわけで(徐々に症状は悪化するのだが、
顕著な症状として現れるのには時間がかかり、薬を飲んでいればとりあえずしのげる慢性疾患のようである)、ほかのシステムで同様のことを行えば、即座に破たんする
ところそのまま延命してしまう。
しかし悪影響はボディーブローのように効いていき、徐々に開発生産性の低下、保守性の低下につながっていく。
実際問題としてMUMPSベースのシステムは20年以上の月日を経て、未だに稼働しているシステムも少なくないが、多かれ少なかれこの潜在的な問題を抱えているはずだ。
もうひとつの問題点は、MUMPSで生成されたデータベースを外部に簡便に公開するしくみがない点である。
これがデータの2次利用を制限してしまう。
データは利活用されてなんぼである。
公開が必要になった時点で、求められる形式のファイルを生成するプログラムを作成しなければならない。
違う形式、違うデータ毎にそのプログラムを個別に作成しなければならない。
いかにプログラミングの生産性が高いといえどもこれは積もり積もれば相当な手間となる。
これと関連することであるが、そのデータモデルとそれを利用するビジネスロジックが密結合している点も問題となる。
先述のようにMUMPSではデータ設計の自由度が非常に高く、スキーマという概念がない。
スキーマ定義の代わりにデータ構造の追加、変更は、MUMPSのプログラミング言語であるM言語のプログラミングによって行われる。
そのプログラミングによるデータモデルの変更(データ構造の追加や項目の追加)が予期せぬ他のビジネスロジックの誤動作を発生させることも起こりうる。
スキーマレスという属性が、データモデルのドキュメント化の面でも問題となる。
ソフトウェアシステムは経年によりいろいろなものがかわっていく特性があるが、データモデルもその影響を受ける。
スキーマレスの場合、何かが変わったという情報がソースコードの中に埋もれてしまい、それに同期する形でデータ設計ドキュメント
の類のものをメンテナンスしないと実際の構造とその設計情報の齟齬が簡単に発生してしまう。
現実問題としてそういう設計情報はよほどうまく管理していかないとソースコードと完全に同期した形で最新状態にアップデートすることは難しい。
スキーマつまり辞書情報があるのであれば、それが現在動作している最新状態であるので、上記の問題は避けることができる。
またその辞書情報を元にデータベース設計書的なものを自動生成することもさほど難しくはない。
大規模システムになるとそのデータを扱っているプログラムが多岐にわたり、その全体でどういう風にデータを生成、参照、更新、削除しているかをすべて
把握しないと実際にどういうデータモデルが採用されているかを俯瞰するのは大変困難になっていく。
そうすると、そのデータモデルに構造を追加したい場合にその影響がどう及ぶのかを検証するために多くの時間を要するかもしれず、保守生産性に悪影響
を及ぼすようになる。
M言語
MUMPSのプログラミング言語であるM言語にも様々な問題がある。
変数のスコープ
変数のスコープ制御は、他の一般的な言語と異なり、大域変数がデフォルトである。
つまり変数の適用範囲がモジュール(M言語ではルーチンもしくはサブルーチンと呼ぶ)間をまたがる仕様となっている。
この結果としてプログラムが大きくなり複数のルーチン、サブルーチンで構成されるようになると、変数名の使用が重複(いわゆるバッティング)して、予期せぬ変数更新などが
発生してしまう。
これは大規模システム開発にとって致命的な問題である。
もちろんNewコマンドというコマンドを用いることにより、変数のスコープをルーチン、サブルーチンに限定することはできるが、そのコマンドの引数に対象の変数を明示的に指定
する必要があり、変数が大量にあると非常にめんどくさい。
Newコマンドには排他Newというのがあり、この場合には、指定変数以外の変数のスコープを限定できるが、これは逆に性能的に大きなペナルティがあり、使用が推奨されない。
シンタックス上の様々な制限
コマンドとパラメータの間はスペース1個だけとか、コマンドを複数行にまたがってかけない、コマンドを1行に複数かける、コマンド、関数名に省略形が使えるなど
現在一般的なプログラミング言語からみると非常に奇異な仕様が多い。
おそらく入力の手間を極力抑えるための工夫や当時の貧弱なマシン環境でも軽快に動作させるため、シンタックスに様々な制約を設けることで対応したのだろうと想像している。
結果、書くスピードは速い(かもしれない)けれどもソースを見るという観点では、極めて見やすくないソースコードが出来上がる。
他言語に慣れた開発者がそのソースコードを見た瞬間に非常に違和感を感じるところである。
見やすくないコードということは、結局内容の理解が難しいということにつながる。
Caché, IRISがMUMPSから踏襲したもの
上記のように様々な問題のあるMUMPSであるが、一方でデータベースエンジンとしてのいくつかの素晴らしい特性を持っており、それはそのままCaché, IRISに踏襲されている。
グローバル(多次元配列変数)
Caché, IRISでもデータ実体はMUMPSのものと基本的には変わらないグローバルという構造を踏襲している。
グローバルは別名多次元配列変数という注釈をつけているように、実体はプログラミング言語の変数である。
M言語には永続変数という概念があり、その永続変数をグローバル変数と呼んでいる。
一般的な言語の変数と同様にメモリーに存在する一般的な変数はローカル変数と呼ばれる。
グローバル変数というと一般的な言語では大域変数を意味することが多いが、M言語では、意味が異なる点注意が必要である。
また変数に複雑な構造のデータを保持できるように多次元配列の要素に正の整数だけではなく、可変長の文字列が使える。
変数の要素に正の整数ではなく可変長文字列が持てることのメリットは、その変数の表現形式を見れば、そのデータの
意味が想像しやすくなる点である。
この多次元配列で表現されるデータの箱をグローバルノードと呼び、1次元目のノード、2次元目のノード、3次元目のノードという風に
データを階層的あるいは木構造的に表現することができる。
そして、各次元の要素(添え字と呼ばれることもある)は、その階層構造のノードにアクセスするためのキーの役割を持っている。
さらに重要な特性は、それらのキーを与えると母体のノード数が大量にあったとしても、該当するノードデータを素早く取得できるようになっている。
これはデータベースの要件として基本的に重要な特性である。
またそのキーという特性を利用して複数の階層構造を結び付けてネットワーク型のデータ構造も表現可能である。
主としてメインフレーム上に存在していた階層型データベースやネットワーク型データベースとの構造的な違いは、それらがデータ間の関係を表現する際
に物理的なリンクに使用していたのに対して、グローバルの場合、リレーショナルモデルと同様に外部キーという論理リンクになっている点があげられる。
物理リンクの場合、データの関係性の変化に伴いスキーマの変更が必要になると、以前の定義でリンクされた関係性を新しい定義でどのように対応すれば良い
か悩ましいケースがある。
つまり不必要になったその古い物理リンクをどう処理するかということである。
グローバルの場合、データの関係性は、その外部キーの結びつきとなるため、関係性が変わったとしてもその結合に必要な値を変更することで柔軟に対応ができる。
このようなメカニズムによって他のプログラミング言語で構造体とよばれるようなデータを構成することができる。
しかもそれらをメモリー上の変数だけでなくグローバル、つまり永続媒体上の変数に保存可能である。
グローバル変数とローカル変数は、変数名の先頭に^(サーカムフレックス)記号がつくかつかないかで区別される。
このことはつまりローカル変数とグローバル変数に永続性という属性以外の表現上の違いがないことを意味する。
また実際に変数に値を割り当てたり参照する操作はSetコマンドで行われるが、ローカル変数とグローバル変数の間で操作上の違いはない。
ローカル変数は、そのプログラムが動作しているプロセスのプライベートメモリーに保持される。
グローバル変数は、その名前空間(ネームスペース)に紐づけられたデータベースファイルに保持される。
しかしグローバル変数は、直接そのデータベースファイルに読み書きされるかというと、そうではなく必ず共有メモリーを経由してデータベースファイルと連携される。
ここで遅延書き込みというテクニックを使い、データアクセスを効率化している。
したがって各プロセスは、グローバル変数を操作する際には、基本的には共有メモリーを使用する。
つまりグローバルとはインメモリーデータベースなのである。
しかもデータベースのデータを基本的にメモリー上で処理する特性を生かし、一般的なデータベースシステムでは、行わない処理も行っている。
それは、データの整理整頓である。
MUMPS(およびCaché, IRIS)では、グローバルを一般的にB-Tree(Balanced Tree)構造と呼ばれる形式でデータを保持する。
この構造では、あるデータを素早く探し出すには、ある一定の規則に基づいてデータをきれいに並べる必要がある。
そのため、新たにデータを追加する際、そのキー値(簡単に言うとその配列の要素の値を要素の数(次元の数)分結合した値)がそれにふさわしい場所
に配置される様に並べ替えというのが行われる。
つまりそのデータが本来配置されるべき場所を探し出し、そこに存在しているデータを押しのけて、そのデータが居座る。
結果、前に存在していたデータはその後ろに追いやられる。さらにその後ろのデータもその影響で後ろにずらされる。
実際には、データベースファイル内ではブロックという単位でデータは保持されており、こういう並べ替えのためにブロックにはある程度の余裕
があるのが普通であるが、状況に応じて後ろにずれる操作が雪崩のように連鎖することもありうる。
逆にデータが削除される際には、今度は逆に削除されたデータの処にその後ろのデータが手繰り寄せられる。
実際にはもう少し複雑なメカニズムがあるが、ここでその詳細を述べるつもりはない。
要するにここで言いたいことは、極めてまじめにデータの並べ替えを行っているということである。
逆にいうと他のデータベースシステムでここまでまじめに並べ替えを行っているシステムはおそらくないのではないかと思う。
何故ならば、これらの処理はまじめにやればそれなりの処理コストがかかるからである。
処理コストがかかるということはつまり処理が遅くなるということである。
一般的なデータベースシステムはこの遅くなるという特性を嫌って、これをまじめには行わない。
(個別のシステムが実際どう処理しているかは私もよく知らないが、おそらくこれは間違っていないと思う)
その結果、発生することは、データベースの経年劣化、つまり断片化の発生である。
要するにデータの構成が継ぎはぎだらけになって、アクセスが非効率化するということである。
これを解消する操作がデータベース再編成と呼ばれるものになるが、再編成というと聞こえが良いが、実際には全てのデータを外出しして、再度入れなおす
という操作に他ならない。
再編成を行うとデータの構成が変わるため、インデックスの再構築という作業も行う必要がある。
この作業はデータの容量が増えるにつれ、処理時間も増えるし、その頻度に応じて作業負担も増えてくる。
再編成を実行する際には一般的にそのシステムを停止して行う必要があるため、業務を停止する必要がある。
そうすると、業務を止められる時間の制約によって再編成が行えないという事態も起こりうる。
実際、再編成をあきらめ、非効率化したデータベースシステムをだましだまし運用している所も多いことだろう。
コストのかかる処理をまじめに行えるのもグローバルが基本的にインメモリーデータベースだからだ。
結果としてグローバルではデータの再編成は基本的に必要ない。
(正確に言うと、ディスク上の物理的な連続性は経年劣化することはあり得る。但し、この経年劣化で影響を受けるのは、主としてシーケンシャル
アクセスであり、ランダムアクセスはさほど影響を受けない。その場合、再編成作業を行うと、シーケンシャルアクセスの劣化が解消されるケース
がある。 ただし昨今のストレージシステムでは、ディスクアレイの採用やシリコン媒体の使用などにより物理的なデータ配置をそれほど重視しなくても
よくなってきている)
多次元データベース
この多次元という言葉は、データベースの世界では誤解を招きやすい言葉である。
データベースの世界では、多次元というと真っ先にイメージされるのが、OLAPつまり多次元分析データベースである。
グローバルの多次元は先述のようにOLAPで意味する所の多次元とはニュアンスが少し違う。
OLAPの多次元は、次元というよりは多角的な分析といったほうが意味が合っていると思う。
もちろんOLAPのデータはその多角的な属性を階層的に表現できるので、グローバルの多次元構造にマッピングすることは可能でありかつ容易である。
但し、分析の際に求められる多角的という属性は、人が探索的にデータを操作することを支援するものであるので、生来的にアドホックな属性を持つ。
実際の所、アドホックにグローバルの階層構造を利用して多角的な分析用のデータを生成するのは不可能ではないが、分析毎にそういう構造を動的に生成するのはコストがかかる。
クエリに対応した事前集計を行っておき、それを多次元配列に落とし込むということも理屈上ではあり得るが、現実にはバリエーションが多すぎて実用に耐えられない。
Caché, IRISは、OLAP用の多角的な分析エンジンを内蔵しているが、使用しているメカニズムは純粋な多次元配列とは異なる。
ここでは多次元配列は、集計結果のキャッシュとして使われることのほうが多い。
Caché, IRISのOLAPシステムでは、キューブと呼ばれるもので分析データを構成するが、その実体は、キューブという言葉から想像されるような多次元構造のデータではなく、
フラットなファクトテーブルとそれに様々な属性(角度)で素早くデータを抽出できるインデックス(トランザクショナルビットマップインデックスと呼んでいる)
と集計処理を高速化するためのインデックス(トランザクショナルビットスライスインデックス)で構成される。
問い合わせ毎にファクトテーブルとそれらのインデックスを使用して、問い合わせを動的に実行する。
その結果えられる階層構造はまさしくその多次元配列変数に展開される。
しかしこれはあくまでも表示用あるいはのちの似たようなクエリーの実行の際のキャッシュとして使用される。
一方グローバルの多次元とは、これはあくまでも配列変数の次元のことを言っている。
実際には階層構造、木構造ととらえたほうが理解しやすい。
世の中のデータは階層的に表現できるケースが多いので、そういう階層構造のデータを表現するのは非常に簡単である。
リレーショナルデータベースが主流になる前は、階層型データベースやネットワーク型データベースが普及していた事実ともこれは符号する。
スパース(まばらな)配列
グローバルの特性としてスパース配列というのがある。
これは、つまりこういうことだ。
通常の配列変数の場合には、必要な領域は、各次元の要素数の掛け算で求められる。
A(10,100)の配列変数だったら10*100で1000個分のデータ領域を取ればよい。
しかし、配列の要素に可変長の文字列が使えるということは、事前にどれだけ領域を確保すれば良いかを決めることはできない。
(要素の数が定まらないので)
従って、必要に応じて領域を確保していくしか方法がない。
そうすると論理的には存在しうるが、実際には存在しないノードには領域が割り当てられないということになる。
つまり存在しないノードには物理的な領域がないということになる。
この状態をデータが飛び飛びに存在するということで、スパース(まばらな)配列と呼んでいる。
スパース配列の利点として、存在しないデータが物理的な領域を取ることがないため、必要な領域を削減できるという説明をすることがある。
これは、事実であるが、実用的な観点では事実ではないとも言える。
スキーマを必要とする一般的なデータベースの場合、定義されている項目のための領域は、そのレコード(RDBの場合には行)上にその項目
に対して値がない場合にも最低限の領域が必要となる。(データベースシステムの実装により領域の取り方には差があるだろうが、
ある種のプレースホルダー的な領域がどうしても必要になると思われる。)
従って、レコード毎にデータの持ち方にばらつきのある(データ項目は非常に多いけれども実際に値の入っている項目は少ない)場合に領域効率
が悪くなるのだが、グローバルの場合、逆に効率が良いという説明である。
しかし、これは例えば以下のようにデータレコードを保持する場合にのみ正しい
例えば商品情報を以下のように持つと仮定しよう。
項目として
商品名
重量
価格
というのがあるとして
^PRODUCT(pid,"商品名") = 掃除機
^PRODUCT(pid,"重量") = 5kg
^PRODUCT(pid,"価格") = 50000
と表現してみる。
ここでpidは商品番号の値が入るとする。
仮にある商品の重量情報がわからない、または重量データはオプションで良いとすると、
商品によっては重量情報のあるものもあれば、ないものも存在しうる。
^PRODUCT("A12345","商品名") = 掃除機
^PRODUCT("A12345","重量") = 5kg
^PRODUCT("A12345","価格") = 50000
^PRODUCT("A12346","商品名") = 歯ブラシ
^PRODUCT("A12346","価格") = 500
という感じである。
しかし、こういうデータのデザインは、データ処理的にはパフォーマンス上良くないと言われており、推奨されていない。
推奨デザインは、一般的なデータベースと同様に
レコードは1グローバルノードで保持し、レコードは複数カラム(フィールド)を持つ
つまり
^PRODUCT("A12345") = $LISTBUILD("掃除機","5kg",50000)
^PRODUCT("A12346") = $LISTBUILD("歯ブラシ",,500)
という形式である。
ちなみに$LISTBUILDというのは、MUMPSには存在しないCaché, IRIS専用の関数であり、LIST形式のデータを生成する関数である。
MUMPSの場合には、CSV形式のような区切り文字(デりミタ)を決めて、そのデリミタ単位に文字列を区切ることでリスト形式のデータを表現していた。
例えば
ABC^123^あいうえお
という文字列は、^を区切り文字とした3つの項目(ABC 123 あいうえお)で構成されているデータという表現形式である。
このデリミタ方式の欠点は、データとしてその区切り文字を使えないという点と
文字列の断片を先頭から順番に取得する場合(これは非常に多い処理パターンだが)の処理効率が悪く、デリミタで区切られる項目数が多い場合に
処理オーバーヘッドが大きいという点である。
この欠点を解消したのが上記のCaché, IRIS専用のLIST形式である。
いずれにしろデータを一行で持つ場合には、存在しないデータに対して領域が必要となる。
つまり推奨形式ではスパース配列の恩恵を被ることはない。
スパース配列の利点は、項目数は非常に多いが、実際には全ての項目にデータが入ることがまれなデータの格納効率に優れているのと、
非常に変化の激しいデータでどんどん新しい項目が追加されるようなデータへの対応に優れる(いわゆるスキーマエボリューション)点である。
しかしこういう要件には、古くはXMLや最近ではJSON形式でデータを持つことで解決させるケースが増えてきている。
インターシステムズも最近ではこういう要件の解決には、JSONの使用を推奨している。
このことと関連してインターシステムズは、マルチモデルの中で、ドキュメント型と言われるデータ形式のサポートを強化している。
XMLやJSONでデータを持つことの欠点は、汎用的なクエリー言語であるSQLが使えないという点である。
XMLにはXPATHやXQUERYが存在するが普及しているとは言い難い。
JSONに関して業界として独自のクエリ言語を開発するよりは、そのままSQLを使えるようにしようという方向になっている。
インターシステムズもその方向に追従する予定である。
ちなみにスキーマレボリューションについて一般的なリレーショナルデータベースには難しい問題がある。
それはスキーマに項目を追加すると、その項目の追加前にデータを生成していた場合に新しいスキーマ構造に対応するために
それらの古いデータに対して何らかのコンバージョンをおこなわなければならない点である。
生成したデータが既にかなり大きい場合には、そのコンバージョンにはかなりの時間がかかることが想定される。
項目数が運用開始後も何回も更新されるとその都度コンバージョンを実施するのも大変である。
これを事前に防ぐために予備の項目を用意しておき、必要に応じてその予備項目を割り当てて項目名を変更するということも行われているだろう。
(予備項目をいくつとっておくべきかなかなか判断が難しいところだろう)
Caché, IRISの場合、スキーマレボリューションの際のデータコンバージョンは必要ない。
Caché, IRISの行の内部表現は前出の$LIST構造と呼ばれるもので表現されるが、この構造では取得不能だった項目はNULLとして取り扱われる。
これはスキーマに項目を追加する前に生成されたデータに対して、その新しい項目を取得する処理を行ったときの典型的なパターンである。
なのでCaché, IRISで項目を追加することは、一般的なリレーショナルデータベースに比較すると容易であると言える。
階層構造
グローバルの多次元構造は、階層構造(木構造)をそのまま表現できる。
つまりグローバルの1次元目のノードが親、その下の2次元目のノードが子、その下の3次元目のノードが孫のように表現できる。
こういう構造のデータは世の中にあふれているし、万人に理解しやすいモデルである。
そしてプログラミングモデルとしてこういう構造を取り扱うのは比較的単純である。
簡単にいってしまうと
階層構造のデータにアクセスする方法は、その階層の特定のノードに直接アクセスする方法と
(ノードにアクセスするためのキーを指定して)
ある特定のノードを起点にして後続のノードに順番にアクセスする方法(降順、昇順)
の2つしかない
この2つの組み合わせでどんな構造のデータも処理することができる。
(厳密に言うともう少しバリエーションがあるが)
一方、階層構造を処理する方法は、多次元構造以外にもある。
例えば前出の$LIST構造は、ネスト(入れ子)することが可能で、その入れ子状態を階層と考えることも可能である。
Caché, IRISではグローバルの多次元構造を使って、複雑なオブジェクトを表現するという表現を見ることがあるが、これは真実ではないとまで
は言えないまでも、誤解を招きやすい表現である。
オブジェクトの複雑な構造といっているものの内、いわゆる入れ子や繰り返し構造には、実際には多次元構造を使っているわけではなく、$LISTの入れ子
構造を使用している。
また、この様な階層構造もJSON形式でも表現できる。
JSONの利用が普及してきている理由も複雑な構造のデータを簡単にひとまとめにしてデータ交換できるからである。
XMLも同様であるが、より軽量なJSONが好まれている。
しかし様々な形式の中間データを永続データで保持できる特性はプログラミング上の柔軟性という観点では他の言語にはない利点でもある。
例えば、MUMPSプログラマー(Caché, IRISプログラマーでも)がプログラムのデバッグ時によく使う方法として中間データをグローバルに保存し、プログラムがどう動いたかを実行後に見ることができる。
この方法は一般的なIDEのデバッガーを使ったインタラクティブな方法よりむしろ効率がいい場合も多い。
ある大学の先生は、自分の研究用のシミュレーションプログラムの複雑な中間モデルの表現にグローバルの柔軟な構造を使っている。
ライフサイクルの比較的短いデータ(二次利用される可能性の低い)に関しては、こういう使い方は十分利にかなっている。
実際、Caché, IRISのオブジェクトモデルの中でこの階層構造を利用している部分もある
1つは、オブジェクトの継承関係の表現方法として。
グローバルの階層構造はまさしくクラスの階層を表現するのにぴったりの方法である
2つ目は、親子関係のリレーションシップ表現である。
この親子というネーミングも若干誤解を招きやすいが、一対他のリレーションシップの内、関係性が強固なもの、より具体的にはライフサイクルが同期するエンティティ同士の関係を表現する
ことに適している。
例えば、注文と注文明細のような関係性である。
注文がキャンセルされたら注文明細が単独で存在するのは通常意味がないので、一蓮托生でその明細も削除するのが普通である。
ここでグローバルの階層構造を使うメリットは、近傍配置により一度のディスクアクセスで関連する情報を取得できるということであるが、親子という程度ではさほどのメリットは得られない。
親子孫さらに親子孫ひ孫といった階層が深くなった場合に効果が顕著になってくるが、実際にはそんなに深くライフサイクルが同期するケースは少ないので実用的なメリットは疑問符がつく
3つ目は、ビットマップインデックスでの利用である
ビットマップインデックスは、他のRDBMSでも実装例がある。
例えばOracleは、ビットマップインデックスをサポートしている。
しかし、その使用用途は、データウェアハウス用に限定されていて、トランザクション処理には使用しないように推奨されている。
その理由は、単純で対象レコード数が増大すると、それにつれて必要となるビットマップインデックスのビット数が増大するため、トランザクションを迅速に処理するのが困難になるからである。
。
レコード数が100万件あると、対象条件毎に(例えば性別が男性という条件)100万ビット必要となる
しかもその100万ビット中の特定のレコードに対応する条件ビットの値を変えると、その一ビットだけ変えれば良いわけではなく、全体の100万ビットを書き戻さなければならない。
(トランザクションログも更新前の100万ビットと更新後の100万ビットを保持しなければならない)
Caché, IRISは、この問題をチャンクという単位で全体ビットを分割して保持することによって、解決している
そしてこのチャンクを表現するためにグローバルの階層構造を利用している
以下は実際のビットマップインデックスの例である。
現在、1チャンク当たりのビット数は64,000ビットである。
これば様々テストケースでベンチマークを行った結果、この値が最適であるという判断となり値が決まった。
計算式により、ID500,000の該当ビットがどのチャンクの何番目かのビットか特定できる。
さらにチャンク内のビットはあるアルゴリズムにより圧縮される。
(ビットの値の並びに偏りがあることを利用している、つまり0が連続でつながっていたり、その逆で1が連続している場合に圧縮できる)
そして、全てのビットが0の場合には、そのチャンクごと存在しなくてもよい。
これは、前出のスパース配列の有効な利用例である。
この様な実装によりCaché, IRISのビットマップインデックスは、一般的なビットマップインデックスに比較して圧倒的にフットプリントが少なくなり、トランザクション処理での利用に問題がなくなった。
最後に
まだまだ書き足らないような気もするが、とりあえず終わりとしたい。
書ききれていないことの1つは、ここで述べているMUMPSの問題点をCaché, IRISがどのように解決しているかであるが、それはまたの機会としたい。
とはいえ、最新のIRIS上でさえ、古いあまりくせのないMUMPSプログラムはそのまま動作する。
動作はするが、IRIS上で動作させたからといってそのMUMPSプログラムは、ここで述べた問題点を内在したままである。
その問題を解消するためにはIRISが推奨する作法を踏襲する必要がある。
まあ、いま動いているものは無理に書き換える必要もない。
但し、新たに作るシステムは、新しい作法に合わせて作ってほしいと願っている。
IRISではCaché以上にオープン性を重視しており、その中でもプログラミング言語の選択の自由を推進している。
CachéでもJavaや.Netなどの様々なプログラミング言語を使えるオプションを用意していたが、データベースアクセスの部分に関しては、Caché ObjectScript
(IRISのリリースに伴い、InterSystems ObjectScriptと改名)を推奨していたが、IRISではデータベースアクセスに関しても他言語でもObjectScriptと遜色のない機能、性能を提供し、
ObjectScriptを使用しなくてもよい環境を提供しようとしている。
もちろんObjectScriptは今後もなくなるわけではない(IRISでは様々なコードを自動生成しているが、その自動生成されるコードは今後もObjectScriptである)
しかし、マーケットを拡大するには、世の中の開発者が好むプログラミング言語に寄り添う必要があるという判断である。
そして最後に残るのはやはりグローバルである。
これこそが我々のデータプラットフォームの競争力の源泉である。
なおここで書いている内容は、これを書いている私個人の見解であり、インターシステムズの公式見解ではない点、断っておく。