続・StringPropertyの最大サイズ

メモ - Twisted Mind
こちらで昨日の記事を確認していただいております。検証コード付き。ありがとうございます。というか開発サーバに確認用コンソールがあるなんて知りませんでした。

というわけで、手元でも検証。
開発サーバを適当に立ち上げ、以下のURLにアクセス。

http://localhost:8080/_ah/admin/interactive

コンソールに以下のコードを入れ、500文字の'あ'がStringPropertyに格納できることを確認してみます。

from google.appengine.ext import db

class Spam(db.Model):
  eggs = db.StringProperty()

Spam(eggs=''.join(['あ' for i in range(500)])).put()

実行結果は以下の通り。

BadValueError: Property eggs is 1500 bytes long; it must be 500 or less. Consider Text instead, which can store strings of any length.

あっるぇぇぇ???? エラー? 500文字の"あ"は1500byteになるから格納できないと言っている? ちょ、Google App EngineのMLに「ドキュメントでStringPropertyの上限値が500byteになってるけど、500文字の間違いじゃない? 500文字の'あ'が登録できちゃってるし」って投稿したのに。超恥ずかしい。

泣きそうになりながら、よくわからないなりに覚えたばかりのUnicode記法を使ってみる('あ'の前のu)。

from google.appengine.ext import db

class Spam(db.Model):
  eggs = db.StringProperty()

Spam(eggs=''.join([u'あ' for i in range(500)])).put()

今度は、何事もなく処理終了……

なんかおかしくない?
ということで、GAEのコードを追っかけてみた。Pythonチュートリアル本を片手に。
ここらへんが怪しいかなーというのが、以下の箇所(google/appengine/api/datastore_types.py:956)。

      if not isinstance(v, Text):
        if isinstance(v, Link):
          max_len = _MAX_LINK_PROPERTY_LENGTH
        else:
          max_len = _MAX_STRING_LENGTH
        if len(v) > max_len:
          raise datastore_errors.BadValueError(
            'Property %s is %d bytes long; it must be %d or less. '
            'Consider Text instead, which can store strings of any length.' %
            (name, len(v), max_len))
      pbvalue.set_stringvalue(unicode(v).encode('utf-8'))

"if len(v)" というのがあやしい。unicode文字列とそれ以外とで返値が違うんじゃない? 以下検証コード。

print len("あ")
print len(u"あ")
3
1

やっぱり。len()関数は、非unicode文字列(str型?)だと単純なバイト数(今回はたまたまUTF-8のページから実行しているので、ひらがな一文字で3byte)、unicode型だと文字数でカウントする模様(サロゲートペアだとどうなるかとか、興味は尽きないがテストがめんどくさいので省略)。
んで上掲のコードを見る限り、

  • StringPropertyにunicode文字列を渡すと、バイト数でなく文字数でサイズ上限を計算してしまう(ひらがな500文字が許容されてしまう)。
  • 文字列は(引用コードの最終行を読む限り)UTF-8に変換されてからストレージに格納される。UTF-8だと1文字が最大で6byteになるので、6*500で3000byteがストレージに格納される可能性がある。

ということなんかなあ、と想像する次第。

最初はドキュメントの間違い(500byteじゃなくて500文字が上限じゃない?)と思っていたんですが、もしかするとGAEにおける上限値チェックのロジックが間違っているんじゃなかろうかという気がしてきました。どうなんだろ。defectとして登録した方がええんやろうか。