Python Native APIでNoSQLデータベースにアクセス

Primary tabs

NoSQLデータベースという言葉を聞かれたことがあると思います。色々な定義がありますが、簡単に言えば、文字通りSQLを使わない、つまりリレーショナルデータベース(RDB)以外のデータベースのことを指すのが一般的です。

InterSystems IRIS Data Platformでは、テーブルを定義してSQLでデータにアクセスできます。ですから、InterSystems IRIS Data Platformは厳密にNoSQLデータベースというわけではありません。しかし、InterSystems IRISの高パフォーマンスを支える「グローバル」は、40年も前からInterSystemsのコア技術として、現代で言うNoSQLデータベースを提供してきました。本稿では、InterSystems IRISの「グローバル」でグラフ構造を作り、それをPythonでアクセスする方法を紹介します。

本稿で説明する内容は動画でも公開しています。ぜひご覧ください。

NoSQL

NoSQLに分類されるデータベースには様々なデータモデルを扱うものがあります。以下に代表的なものを挙げます。

  • Key-Value: キーと値の対応関係を保持する。代表的なデータベース: Redis
  • Document: JSONやXMLをデータモデルとするもの。代表的なデータベース: MongoDB
  • Graph: 辺とノードからなるグラフ構造をモデルとするもの。代表的なデータベース: Neo4j

その他、数え切れないほどのデータモデルと製品が存在します。

 

NoSQL登場の背景

なぜNoSQLというものが登場し、普及に至っているのでしょうか?本稿では、次のような理由を挙げたいと思います。

  • テーブルで表すのが自然ではないデータ構造がある: 理論的にはどんなデータ構造もテーブルで表し、SQLでアクセスできますが、SQLが複雑になったり、パフォーマンスが出ないなどの問題が発生するケースがあります。そのような場合、テーブルではなく、その構造に合ったデータモデルを使用するほうが良い結果を生みます。
  • 分散環境への最適化:NoSQLの活用が活発になった一つの分野はFacebookなどのSNSのインフラです。大量のデータ、多数のユーザをサポートし、高可用性を得るため、データベースを分散させるのが必須です。RDBは、必ずしも分散環境で最適な能力を発揮できません。また、次に挙げるRDBのトランザクションに対する考え方も、分散環境では実装が難しくなる要因です。
  • トランザクション処理に対する要求:トランザクション処理とデータの整合性は、RDBが得意とする機能です。銀行口座を管理するシステムなどでは、トランザクション処理とデータの整合性の厳密なサポートが必須なのは言うまでもありません。しかし、トランザクション処理におけるデータの整合性の維持は、分散環境では実装が大変困難で、特にパフォーマンス性能との両立が難しいと言われています。したがって、厳密な整合性の維持よりも、分散環境におけるパフォーマンスや可用性の向上を優先したい場合、RDBよりもNoSQLの方がニーズにマッチする場合があります。

グローバル

InterSystems IRISでは、グローバルと呼ばれるデータ構造がデータベースのコアに採用されています。グローバルは、次の図のように、配列変数のようなモデルです。

グローバルは事前に定義が必要ありません(スキーマレス)ので、データ構造を非常に柔軟に設計できます。また、ディスク上、メモリキャッシュ上、どちらにおいても、データは常にサブスクリプト(キー)の順にソートされていますので、データアクセスの局所化・高速化、断片化の防止を図れます。

グローバルは配列変数のような形をしていますが、実際、IRIS ObjectScriptというIRISのスクリプト言語では、メモリ上の変数とほぼ同じシンタックスで使用でき、プログラム開発の見通しが良くなります。グローバルの詳細については、グローバル使用法のドキュメントを参照してください。

Pythonからアクセス

Python Native APIを使って、Pythonからグローバルを操作する例を紹介します。

題材としては、Standford大学のSNAPにある"WikiSpeedia"のデータを利用します。WikiSpeediaとは、与えられた言葉から目標とする言葉に、Wikipediaのリンクだけを辿ってたどり着く速さを競うゲームです。SNAPには、実際のユーザが辿ったリンクの情報が約11万件ありますので、それをInterSystems IRISのグローバルに格納しました。

グラフ構造

ここで、グラフ構造について説明します。

図に示したのがグラフ構造の基本です。グラフは、ノードと辺から構成されます。辺は、2つのノードを結びつけるものです。

例えば、Facebookの友達の関係は、ノード=ユーザ、辺=友達関係 とすれば表現できます。また、ノード=駅、辺=ある駅の隣の駅、辺のプロパティ=駅と駅の距離 とすれば、路線検索に使えるデータ構造が実現できます。

本稿の例では、ノード=Wikipediaの記事(見出し語)、辺=WikiSpeediaのユーザがリンクをクリックした元の記事とリンク先の記事の関係 としてグラフ構造を利用します。

グローバルの構造

グローバルの構造を見てみましょう。

^Links("Tokyo", "18th_century")=""
^Links("Tokyo", "London")=""
^Links("Osaka", "Aquarium")=""

例えば、一番上の行は、ユーザが"Tokyo"という記事から"18th_century"(18世紀)という記事へのリンクをクリックしたことを表します。また、3行目は、"Osaka"から"Aquarium"(水族館)へクリックしたことを表します。

グローバルは、サブスクリプト(キー)の左から右の方向でアクセスすることに最適化されています。したがって、

^Links("元記事”,"クリック先の記事")=""

という構造が最適であることが分かると思います。また、今回は値は""ですが、辺に何らかの属性を持たせたい場合は、それをグローバルの値に設定すれば良いと思います。

Pythonのコード

Pythonのコードについて説明します。まずは、importです。

import irisnative
import networkx as nx

Python Native APIを使用するためには、irisnativeモジュールをインポートします。このモジュールは、IRISをインストールした際、dev/pythonディレクトリに置かれる.whlファイルをpipコマンドでpythonの環境にインストールしておきます。また、グラフ構造保持のために、NetworkXを使用していますので、それもimportしておきます。

connection = irisnative.createConnection("localhost", 9091, "user", "horita","horita")
iris_native = irisnative.createIris(connection)

この2行では、createConnection()でIRISへの接続を行い、createIris()でグローバル操作のインターフェースオブジェクトを生成しています。

def addNodes(key, g, d):
    g.add_node(key)
    if d > 3:
        return
    
    iter = iris_native.iterator("Links", key)
    nodelist = [k for k,v in iter.items()]

    # デモのため、辿るリンクを3つに限定 
    random.shuffle(nodelist)
    nodelist = nodelist[0:3]

    edgelist = [(key, n) for n in nodelist]
    g.add_nodes_from(nodelist)
    g.add_edges_from(edgelist)
    
    for subk in nodelist:
        addNodes(subk, g, d + 1)

addNodes()関数の定義です。この関数は、与えられたグラフのノードからリンクを辿ってグラフ構造をNetworkXのオブジェクトとして構築する関数です。再帰呼び出しによって、3階層目まで辿るようにしています。一番のポイントは、

    iter = iris_native.iterator("Links", key)
    nodelist = [k for k,v in iter.items()]

という箇所です。iris_native.iterator("Links", key)では、Python Native APIによって、サブスクリプトのイテレータを作成しています。例えば、key='Tokyo'の場合は、^Links("Tokyo", *)の*の部分にある文字列について繰り返すイテレータになります。

そのイテレータをPythonのforで繰り返し、リストを作ります。このように、グローバルのイテレータがPythonの繰り返しモデルにうまく適合して、シンプルなコードになっていることが分かります。

curkey = 'Tokyo'
G = nx.Graph()
addNodes(curkey, G, 1)

そして、"Tokyo"という言葉をキーとしてaddNodes()を呼び出します。これで、"Tokyo"という言葉から辿られたリンクの繋がりが得られます。それが次の図です。

"Tokyo"という言葉からクリックされた言葉のネットワークが表現されています。

まとめ

InterSystems IRISのグローバルは、NoSQLデータベースが選択されるようなニーズで力を発揮します。また、Python Native APIによって、Pythonのプログラムから自然な形でグローバルを操作することが可能になります。

ぜひ一度試してみてください。また、質問などをこの記事のコメントに書いて頂ければ大変嬉しいです。