MongoDB の DBRef で参照先が削除された場合 ( mongoengine )

MongoDB の DBRef に cascade が欲しい

昔から今においても RDB をいじってる人間として、欲しい気持ち感じて辛まるんですが無いんですかね。

参照切れが起きている DBRef を検出するクエリ

SQL なら書けるのに、MongoDB では書けないんですかね。 書きたくて震えます。

参照切れの発生

ゆとりデベロッパーなので、MongoDB を使うときに ODM(Object-Document Mapper) を使っています。 Python の開発では主に mongoengine を使っています。

from mongoengine import (
    Document,
    ReferenceField
)

class Foo(Document):
    # some fields


class Bar(Document):
    foreign = ReferenceField(Foo, dbref=True)

上記のようにコレクションが定義されていると仮定して、例えば Foo のドキュメント foo を消してしまいます。 もし、Bar のドキュメントのうち、 foo を参照しているものが無ければ何の問題も無いのですが、もし Bar のドキュメント bar が foo を参照していると、参照切れが発生します。

mongoengine は参照切れが起きるとどういう挙動をするか

参照切れが起きていない状態では、bar.foreign を参照すると foo が返ってきます。 当然ですね。

では、参照切れが起きた状態で bar.foreign を参照するとどうなるのか。 ぼく的には何らかの例外とかが発生すると嬉しいのですが、実際には bson.dbref.DBRef のインスタンスが返ってきます。

mongoengine は MongoDB を操作するために、pymongo を使用しているので、1つ下のレイヤーで扱われているデータをそのまま返してしまうわけですね。 気が利かないですね。

従って、例えば bar.foreign.get_some_filed() というメソッドを呼びだそうとすると、AttributeError 例外が発生します。 この例外は想定外なので、多くの場合ハンドリングされず、一番上まで昇ってきます。 ウェブアプリだと500 Internal Error を返します。 これは良くないですね。

検出する

本当は参照切れを発生させないことが一番なんですが、仕方ない場面だって十分にあります。 そこで、どういう対策をするのかといえば、次の2つをやっておけば良いんじゃないでしょうか。

  1. AttributeError をハンドリングする
  2. 定期的にバッチ処理をして参照切れをなくす

1 の方法は、なるべく mongoengine.objects の参照を行なっている層の近くでやると良いですね。 あるいは、AttributeError の検出ではなく、bar.foreign にアクセスするときに、isinstance(bar.foreign, Foo) とかでチェックすると確実かも知れません。 そして、ただ検出/チェックするだけでなく、見つけたら参照切れを解消してしまうと良いですね。

まぁ、2 の方法は、データが大きくなればなるほど大変なので、1 で事足りるならそれで十分ですね。

なんかとてもに眠い上、定期考査期間中なので、よくわからないテンションで自分でも何言ってるか分からない記事ですが、とにかくそういうことです。