実装
それでは、実際にコードを書いていきましょう。今回も、GitHubにソースコードを上げていますので、こちらをベースに説明していきます。
今回は機能追加ということで、前回作成したクラスを継承する形で実装をしています。
リレーションシップの作成
最初にリレーションシップの作成するコードです。
リレーションシップの作成時にセカンダリインデックスを作成します。Github上では以下のURLです。
リレーションシップRowとリレーションシップの最新順インデックスRowに関しては前回と同じです。
プロパティごとに、方向(INCOMING、OUTGOING)×順序(ASC、DESC)の4つのセカンダリインデックスのRowをPutします。
- 91~105行目で、各プロパティについてセカンダリインデックスのPutオブジェクトを作成しています。
- 108行目で、最新順インデックスRowと同時に、batchでPutしています。
リレーションシップの削除
次にリレーションシップの削除するコードです。
リレーションシップの作成時に、セカンダリインデックスを作成します。Github上では以下のURLです。
- https://github.com/brfrn169/codezine/blob/master/src/main/java/graphdb2/GraphDbServiceImpl.java#L118
リレーションシップの作成と同様に、リレーションシップRowとリレーションシップの最新順インデックスRowに関しては前回と同じです。
- 196~209行目で、各プロパティについてセカンダリインデックスのDeleteオブジェクトを作成しています。
- 212行目で、最新順インデックスRowと同時に、batchでDeleteしています。
リレーションシップのプロパティの更新・削除時
リレーションシップのプロパティの更新・削除時のコードは、Github上では以下のURLです。
- https://github.com/brfrn169/codezine/blob/master/src/main/java/graphdb2/GraphDbServiceImpl.java#L295
こちらも、リレーションシップRowとリレーションシップの最新順インデックスRowに関しては前回と同じです。
セカンダリインデックスでは、RowKeyにリレーションシップの値が含まれているので、それが更新・削除されたときに、セカンダリインデックスのRowを削除・追加する必要があります。これは、最新順インデックスでは必要ありませんでした。
ここで、今回作成しているグラフDBのもう一つの整合性の問題について書きます。先程も書いたように、HBaseは基本的にはRow内のトランザクションしかサポートしていないので、Rowの削除・追加はアトミックに行われません。セカンダリインデックスの更新時に、それを使用した隣接リレーションシップの取得をすると、不整合な状態が見えてしまいます。
この不整合な状態は、削除・追加を行う順番によって2種類存在します。
削除してから追加を行うと、一瞬だけ更新されたリレーションシップがなくなったように見えます。追加してから削除を行うと、一瞬だけ更新されたリレーションシップが2つあるように見えます。
今回作成しているグラフDBでは、追加してから削除を行い、2重で取得されてしまった場合に片方を結果に含めないことで、この不整合問題を対処しています。
実際に取得する際には、件数を限定して取る場合がほとんどなので、その中で2重で取得されてしまった場合のみに片方を結果に含めないようにします。このようにすることで、片方を結果に含めないという処理の負荷を軽減しています。
それでは、コードの説明に戻ります。
- 395~418行目でセカンダリインデックスのPutオブジェクトを作成しています。
- 420~432行目でセカンダリインデックスのDeleteオブジェクトを作成しています。
- 435~440行目で先程の不整合問題に対処するために、まずbatchでPutしてから、その後にDeleteをしています。
隣接リレーションシップの取得(セカンダリインデックスを使用)
最後に、隣接リレーションシップをセカンダリインデックスを用いて取得するコードです。
- https://github.com/brfrn169/codezine/blob/master/src/main/java/graphdb2/GraphDbServiceImpl.java#L222
FilterやSortによってどのセカンダリインデックスを使うかを選択し、Scanをかけるという流れになっています。具体的にFilterやSortによってどのセカンダリインデックスを使うかを選択する処理は、実装レベルではScanのstartRowとstopRowをどのように決めるかという話になります。この処理は、createSecondaryIndexScanで行っています。
簡単に説明すると、Sortによって、昇順用のセカンダリインデックスを使うか、あるいは降順用のセカンダリインデックスを使うかを決めます。さらに、FilterのOperatorによって、セカンダリインデックスのどの範囲をScanするかを導き出し、startRowとstopRowを決めています。
この処理の詳細については、多少複雑になっていますが、実際にコードを読んでみるとお分かりいただけると思います。また、247行目のSet型のnodeIdSetを用いることで、すでに結果に入っているノードIDを結果に含めない処理を行っています。
これは、先ほど書いたセカンダリインデックスの不整合問題に対処するためです。
まとめ
今回は、前回作成したグラフDBにリレーションシップのプロパティによるセカンダリインデックス機能を追加しました。
Row内のみという限定されたトランザクションしかないHBaseにおいて、どのようにセカンダリインデックスを実現するかについて参考になれば幸いです。
次回は、さらに別の機能を追加していきたいと思います。
また、株式会社サイバーエージェントでは、Hadoop/HBaseエンジニアを募集しています。ご興味のある方はこちらからエントリーしていただければと思います。エンジニア>R&Dエンジニア>R&Dエンジニアを選択しエントリーしていただければ幸いです。