知的構造を格納するためのデータベース

我々が扱っている知識は木構造であることが多い。

RDBに格納するためには煩雑なデータ設計を行わなければならない

データ設計等を行うこと無く木構造を格納できるデータベースが望ましい

そこで当研究室では、非破壊的木構造データベースJungleの開発をおこなっている

Jungleの表現力、機能の十分性検証、及び性能実証実験を行いたい

そのために、Jungle上に組織の許認可管理アプリケーションmaTrixを実装した

組織の中の許認可管理アプリケーションmaTrix

人、組織、役割等の木構造データを保持しており

木構造のデータはIdでお互いに参照を行っている

許認可の判断はアクセスルールが記述されたポリシーファイルにそって行われる

ポリシーファイルは主に、誰が (Target)、何を (Redource)、どうできるか(Action) の3つの要素で記述されている

非破壊的木構造データベースJungle

Jungleは、複数の木の集合からなり、木はノードの集合で出来ている

ノードは、自身の子供のリストと、属性名(Key)と属性値(Value)のデータの組を持つ

Jungleは、非破壊的木構造であるため、一度作成した木を破壊することはない

Jungleのデータの編集

新しい木構造を作成することでデータの編集を行う

変更がないノードは共有する

木の更新は、ルートノードをCASを用いて入れ替えることで行う

データの上書きが無いため、読み込み中にロックをかける必要がない

Jungle上でのmaTrixのデータ構造の表現

Jugnleは木構造のデータをそのまま格納できるため、maTrixのデータを一部を除きそのまま格納できる

maTrixの人物TreeをJungleに格納した図の一部を以下に示す

Jungle上でのIdを使った木の相互参照

組織構造は複数の木構造を持ち、お互いのノード参照し合っている

ex. 人物と役割は、idを用いてお互いのノードを参照する

JungleのIndex

Jungleは過去のTreeを全て保持しているため、Treeのversion毎にIndexを持っている必要がある

version毎にIndexを作るとメモリを多量使用してしまう

過去のIndexとデータを共有するIndexを実装した

複数のversionのIndexがあっても、データの差分しかメモリは使用されない

非破壊TreeMapの実装

当初は、FunctionalJavaのTreeMapを使用してIndexの実装を行った

FunctionalJavaにはバグがあり、性能が出なかった

新しく非破壊TreeMapを実装した

TreeMapのアルゴリズムには赤黒木を採用した

その結果Jungleの性能は劇的に向上した

木構造の親を取得するIndex

Jungleは子から親への参照は存在しないため、自身の親を返すParentIndexの実装を行った

許認可Queryの実装

maTrixを用いた許認可は、ポリシーファイルを参照し行われる

ポリシーファイルには、subject(誰が)、Resource(何に対して)、Action(何が出来るか)を記述する

maTrixにおける許認可判断において、データに対するアクセスは、subject部分で検索関数を用いて行う

Jungle上にmaTrixを実装するにあって、検索関数の実装を行った

Treeの検索

データアクセス関数を実装するため、Traverserに検索関数findを実装する

InterfaceTraverser traverser = tree.getTraverser(boolean useIndex);

TraverserはTreeのNodeを走破する機能を持ったクラスです

TreeからgetTraverserで取得可能

引数で検索を行う際にIndexを使用するかどうかを選択できる

Treeの検索

public Iterator<TreeNode> find(Query query,String key, String searchValue);

findは以下のように定義されており、引数に

探索の条件を記述する関数boolean condition(TreeNode)を定義したQuery

Indexを使う検索に使用するString 属性名、String 属性値のペア

の3つを取る

条件に一致したNodeのIteratorを返す

Iteratorは、複数の値から1つずつ順番に取得するためのinterfaceである

public interface Query {
   boolean condition(TreeNode _node);
}

findの使用例

InterfaceTraverser personTraverser = personTree.getTraverser(true);
Iterator<TreeNode> personIdpairIterator = personTraverser.find((TreeNode node) -> {
  String personId = node.getAttributes().getString("Person-id");
  if (personId.equals("p:4"))
    return true;
  return false;
}, "Person-id", "p:4");

if (personIdpairIterator.hasNext())
  TreeNode node = personIdpairIterator.next();
  1. Treeから木の探索を行うInterfaceTraverserを取得する
  2. findを実行する。引数には、検索条件が記述されたQuery、Person-Id、p:4を与える。(以下Queryの中の解説)
  3. nodeから属性名Person-idとペアになっている属性値を取得する
  4. 返り値がp:4と一致するかどうかを調べる
  5. 一致した場合trueを一致しなかった場合falseを返す
  6. 帰ってきたIteratorからNodeを取得する

maTrix上での許認可判断例(1)

情報工学科の学生にのみ貸し出されるPCをAさんが借りようとした場合の許認可判断

  1. Aさん(Subject)が、学科のノートPC(Resource)の借りる(Action)ために、maTrixに貸出許可を求める
  2. maTrixは、PCの貸出許可を与えるかを判断するためのポリシーファイルを参照する
  3. maTrixはAさんの役割を取得する
  4. Aさんが情報工学科の役割を持っていた場合貸出許可を与える

maTrixは、3番目の処理で役割を取得する関数roleIdsを使用している

役割を取得する関数

roleIdsは、人、組織の役割を取得する関数

public Iterator<String> searchPersonRoleIds(String version, String id, LinkedList<String> filterIds) 

roleIdsは引数に

  • 検索を行う組織構造のversion
  • 役割を取得したい人 or 組織のId
  • 検索対象を絞り込むために使用するフィルター
  • の3つをとり、返り値に役割のIteratorを返します

    roleIdsのフィルター

    人や組織は、複数の組織に所属することが可能

    組織に所属する際に役割が与えられる

    例. 人が大学に所属していると、「学生」や「教授」という役割が与えられる

    フィルターを使うと組織が与える役割で結果を絞り込める

    roleIdsのコード

      JungleTree personTree = getTree(version, "Person");

    maTrixは組織構造を版管理している

    getTree(String Version, String TreeName)は、VersionとTreeNameで指定された木を返す

    今回はroleIdsの引数で指定されたversionの人物Treeを取得している

    roleIdsのコード

      InterfaceTraverser traverser = personTree.getTraverser(useIndex);
      Iterator<TreeNode> personNodeIterator = traverser.find((TreeNode node) -> {
          String nodeId = node.getAttributes().getString("Person-id");
          if (nodeId.equals(id))
          return true;
          return false;
          }, "Person-id", id);

    getTreeで取得した木から、getTraverserで木を探索する機能を持ったTraverserを取得する

    取得したTraverser.findで、属性名 Person-id、属性値 引数で指定されたidのペアを持つノードのIteratorを検索する

    roleIdsのコード

    TreeNode PersonNode = personNodeIterator.next();
    TreeNode parentOrganizationsNode = PersonNode.getChildren().at(5).b();
    Iterator<TreeNode> OrganizationMappedByRoleIterator = parentOrganizationsNode.getChildren().iterator();
    return new Iteratorr<String>() {
    ………
    };

    findで取得したノードから、その人が持っている役割を持つノードのIteratorを取得する

    idを持っているノードの5番目の子供の下に役割のデータが格納されている

    なので、PersonNode.getChildren().at(5).b();で5番目の子供を取得

    その下に役割のデータが入ったノードがあるので、子供のiteratorを取得する

    検索条件にあった役割を返すiteratorを定義する

    roleIdsのコード

    定義したiteratorのhasNext

    public boolean hasNext() {
      if (OrganizationMappedByRoleIterator.hasNext())
        roleRefId = search();
      else
        roleRefId = null;
      if (roleRefId != null)
        return true;
      return false;
    }

    取得した役割のIteratorの中身がある場合はsearch()を実行する

    search()は条件にあった役割を返す

    中身がない場合はroleRefId = nullを行う

    roleRefIdがnullの場合、条件にあった役割は無いのでfalseを返し

    roleRefIdに値がある場合はtrueを返す

    next()では、roleRefIdの値を返せば良い

    roleIdsのコード

    for (; OrganizationMappedByRoleIterator.hasNext(); ) {
      TreeNode OrganizationMappedByRole = OrganizationMappedByRoleIterator.next();
      TreeNode organizationRefIdNode = OrganizationMappedByRole.getChildren().at(0).b();
      String organizationRefId = organizationRefIdNode.getAttributes().getString("text-organizationRefId");
      if (!filterIds.contains(organizationRefId) && !filterIds.isEmpty())
        continue;
      TreeNode roleRefIdNode = OrganizationMappedByRole.getChildren().at(1).b();
      return roleRefIdNode.getAttributes().getString("text-roleRefId");
    }
    return null;

    役割のデータが入ったノードのiteratorをfor文で回して役割を取得する

    iteratorで取得できるノードの0番目の子供には役割を与える組織idが

    1番目のノードには与えられる役割idを保持している

    0番目の子供から組織idを取得し検索結果の絞込を行う

    その後1番目のノードから役割idを返す

    役割のノードのiteratorが空になったら、nullを返す

    木構造の親を辿るQuery

    maTrixで許認可を判断する際に、木構造の親を辿る検索が必要になる場合がある

    以下に親を辿る検索を行う例を記す

    1. Aさんが、maTrixに工学部の学生にのみ貸出を行っている書籍の貸出許可を求める
    2. Aさんの所属している組織の情報を取得する(情報工学科)
    3. 情報工学科の親の情報を取得する(工学部)
    4. maTrixは、Aさんに工学部に所属しているため本の貸出を許可する

    3番目の処理で親Nodeを返すParentIndexを使用する

    親を辿るQueryのコード

    以下に親の取得を行う部分のコードを記述する

    idには工学部のidが、orgNodeにはAさんが所属している組織のidを持ったノードが入っている

    do {
      String orgId = orgNode.getAttributes().getString("Organization-id");
      if (id.equals(orgId))
        return true;
      parentOrgNodeOptional = parentIndex.get(orgNode);
      if (parentOrgNodeOptional.isPresent())
        orgNode = parentOrgNodeOptional.get();
      else
        return false;
    }while(true);
    1. orgNodeから組織のidを取得する
    2. idと取得した組織のidを比較する
    3. 一致した場合はtrueを、しなかった場合はorgNodeの親を取得する

    以上の処理を取得できる親ノードが無くなるまで行う

    ベンチマーク

    Jungle上にmaTrixの実装を行ったので、性能測定を行った

    測定はmongoDBとの比較とマルチプロセッサー上でのJungleの読み書き性能の2つを行った

    木構造をそのまま格納できること、属性名を指定することで属性値が取得できるなど、Jungleと似た特徴を持っているmongoDBを比較対象に選択した

    測定環境

    今回の測定は以下の環境で行った

  • Mac OS X 10.10.2
  • 2*2.66 GHz 6-Core Intel Xeon
  • Memory 16GB 1333MHz DDR3
  • java 1.8.0-45
  • mongoDB 3.0.2
  • javascript V8JavaScriptengine
  • mongoDBとの比較

    1万人のデータをmongoDBとJungleに格納し、データの検索にかかる時間の測定を行った

    各DBに挿入するデータは以下のような構造を持つ

    {PersonId: 固有のPersonId ,roleRefIds:ランダムな役割Id}

    mongoDBとの比較

    mongoDBでのデータの読み込みは以下のように行った

      var personData = db.person1.find({PersonId:"p:9"}).next();
      var roleId = personData.roleId

    dbからpersonIdがp:9のuserを取得する

    取得したuserのroleIdを取得する

    mongoDBとの比較

    Jungleのデータの読み込みは以下のように行った

    InterfaceTraverser traverser = tree.getTraverser(true);   Iterator<TreeNode> it = traverser.find((TreeNode node) -> {
      String nodeId = node.getAttributes().getString("Person-id");
      if (nodeId.equals("p:9"))
        return true;
      return false;
    }, "Person-id", "r:9");
    if (it.hasNext()) {
      TreeNode targetNode = it.next();
      String targetRoleId = targetNode.getAttributes().getString("roleId");
    }

    TreeからpersonIdがp:9のノードを取得する

    取得したノードからroleIdを取得する

    mongoDBとの比較


    Jungleの方がmongoDBより高速で動いた理由として、
    通信の有無が大きい

    mongoDBはMongoShellからデータベースにアクセスする際に
    通信を行っている

    それに対しJungleは、通信を行わないため高速に動作する

    データへのアクセスもmongoDBは
    ディスクにあるデータに対しmmapを用いて行う

    Jungleは初めからメモリにあるデータにアクセスする

    その部分も差が出た理由の1つである

    その結果JungleはmongoDBの約500倍の性能が出ている

    マルチプロセッサー上でのJungleの読み書き性能

    Jungleがマルチプロセッサ上で性能が出るか測定を行った

    複数スレッドからJungleに読み込みだけを行った場合と、1スレッドだけ書き込みを行い続け、
    残りのスレッドで読み込み行った場合で測定を行った。

    読み込みではmaTrixで実際に使われている検索関数isActive(personId , version)を用いた

    この関数引数で与えた人が、maTrixにアカウントを持っているか調べる関数である

    マルチプロセッサー上でのJungleの読み書き性能


    Jungleでは、書き込みと読み込みを同時に行っても
    性能低下はおこらなかった

    ハイパースレッディングに引っかかるまでは
    リニアに性能が出ている

    書き込みと読み込みを同時に行った場合、
    読み込みのCPUCOUNTが2の時だけ性能が出ていない

    これは、書き込みを連続で行っているため
    読み込みと書き込みで競合が起きている

    しかし読み込みのスレッドが増えると
    読み込みが優先されるので性能が出るようになっている

    読み込みと書き込み同時に行っている方が
    CPUCOUNT11で性能上昇が止まっているのは
    書き込みに1スレッド使用しているからである

    考察

    性能評価では、JungleのほうがMongoDBより高速に動いたが、全てのアプリケーションでJungleの方が早いわけではない

    Jungleは、木構造の形と使われ方がアルゴリズム的に一致している場合に性能が出る

    Jungleに向いているアプリケーション

    Jungleは、書き込み時の処理が木の大きさで変わる

    そのため、比較的小さな木にデータがたくさんあり、木の深い部分に対し変更があまり行われないアプリケーションが得意

    また、書き込みより読み込みを重視したDBであるため、読み込みが多いアプリケーションが性能が出る

    そのため、Jungleの上に構築するアプリケーション例としてBBSやWEBページなどがある

    Jungleに不向きなアプリケーション

    逆に、1つの大きな木に対し変更が頻繁に行われるようなアプリケーションは向いていない

    そういった場合、木を分割して複数の小さな木にすることで性能を上げることは可能である

    しかし、木を分割するための設計手法がまだ無いので、設計手法の確立は今後の課題である

    今後の課題

    1. RDBとの比較

      もともとmaTrixはRDB上で動いているアプリケーションなので、RDB版maTrixとの性能測定を行いたい

    2.データの書き出し部分の改良

      Jungleはデータをディスクに書き出すことが可能である

      書きだしたデータを読み込む機能もあるのでトラブルが発生し、Jungleが強制終了されても元の状態に復旧できる

      今の実装は、データの書き出しはcommit時に毎回行っているため書き出しが非効率的である

      できれば複数回分の変更をバッファリングしておいて一気に書き出しを行いたい

      ある値を行ったり来たりするような、書き出す必要が無いデータもあるため、書き出す部分の最適化を行う必要がある

    今後の課題

    3.過去のデータの掃除

      Jungleは非破壊でデータを保持し続けるため、非常に多くのメモリを消費する

      ある程度のタイミングで過去のデータをメモリから掃除する必要がある

      しかし、データを掃除するタイミングは、Jungle上に実装するアプリケーションによって変わる

      そこを含め、データを掃除するAPIの設計を行う必要がある

    4.分散環境でのmaTrixの構築

      Jungleはもともと分散データベースである

      分散版Jungle上にmaTrixを構築し、性能測定を行いたい

    今後の課題

    5.設計手法の確立

      Jungleは木構造のデータをそのまま格納できる

      木のサイズが大きいと、データ更新の負荷が大きくなるため分割を行う必要がある

      分割を行った木同士の参照はIdを用いた間接的なものになる

      このようにJungleはRDBと異なり格納するデータの自由度が大きい

      なのでJungleの設計手法を確立させる必要がある

    まとめ

    当研究室で開発している非破壊的木構造データベースJungle上に許認可管理アプリケーションmaTrixを実装した

    その際必要になった検索APIやIndexの実装を行った

    またFunctionalJavaは性能が出なかったので、非破壊TreeMapを自作した

    その結果Jungleの性能は劇的に上昇した

    性能測定ではmongoDBより高速に動き、マルチプロセッサ上でも並列に動作した