1つのEventに複数のTagを与えるのなら、 EventとTagの関係はmany-to-many 参照に
するのが自然かと思いますがどうでしょう。
# GAEはmany-to-manyなpropertyは提供してないみたいですね。
# 自分で明示的に中間テーブル(e.g. EntryTag)を作って、自分で管理せよということのようで。
# http://blog.arbingersys.com/2008/04/google-app-engine-better-many-to-many.html
で、count/created/last_useはキャッシュとして保持する値かと思いますが、
利用の仕方/頻度にもよりますがキャッシュは当初は考えなくてよいのではないでしょうか。
countはタグクラウドあたりで頻繁に使うからキャッシュしたいということかもしれませんが、
毎回集計することにして表示側でキャッシュするのもありではないかと思います
(GAEでdjango的表示cacheが使えるかは未確認ですが)。
必要になったらTagへの参照と集計結果を保持するキャッシュ用のCloudCacheというテーブル
でも作って定期的に集計&更新するというのもありかもしれません。
クラウドであれば正確さは不要でしょうからこのような方法でもよいのではないかと思います。
またcreatedやlast_useは使う局面が「Tagの詳細表示」とかなのであれば、Eventにcreatedを
持たせてTagからEventが参照できれば十分なのではないかと思います。
というか、Eventにはcreatedがほしいです。
--
Shun-ichi GOTO
おふたりともご意見ありがとうございます。たいへん参考になります。まずお
ふたりへのお返事をした後に、私の現在の意見を述べさせていただいてます。
少し長くなってしまいますが、ご了承ください。
2008/7/6 Shun-ichi GOTO <shunic...@gmail.com>:
> 現場でどのような議論がなされたか知らないので出すぎた意見かもしれませんが、
いえいえ。ご意見をいただけるのはとても嬉しいです。
> 議事録にてタグを扱うモデルについて「まずいかも」とのことだそうですので、
> その辺りについて感じたところを。
> (私自身はGAEは未経験ですがDjango DBやSQLObjectはほどほどに使ってます)
>
> 1つのEventに複数のTagを与えるのなら、 EventとTagの関係はmany-to-many 参照に
> するのが自然かと思いますがどうでしょう。
>
> # GAEはmany-to-manyなpropertyは提供してないみたいですね。
> # 自分で明示的に中間テーブル(e.g. EntryTag)を作って、自分で管理せよということのようで。
> # http://blog.arbingersys.com/2008/04/google-app-engine-better-many-to-many.html
はい。きちんと正規化するなら Many to Many ですね。ただし後にも述べます
が、現在は Many to Many を採用する亊に乗り気ではありません。主な原因は
検索のパフォーマンスです。
> で、count/created/last_useはキャッシュとして保持する値かと思いますが、
> 利用の仕方/頻度にもよりますがキャッシュは当初は考えなくてよいのではないでしょうか。
>
> countはタグクラウドあたりで頻繁に使うからキャッシュしたいということかもしれませんが、
> 毎回集計することにして表示側でキャッシュするのもありではないかと思います
> (GAEでdjango的表示cacheが使えるかは未確認ですが)。
> 必要になったらTagへの参照と集計結果を保持するキャッシュ用のCloudCacheというテーブル
> でも作って定期的に集計&更新するというのもありかもしれません。
> クラウドであれば正確さは不要でしょうからこのような方法でもよいのではないかと思います。
このご意見は大変参考になりました。後に述べますが、GAE では memcache を
使って何でもキャッシュできますのでそれを使うのが良いと思い始めています。
> またcreatedやlast_useは使う局面が「Tagの詳細表示」とかなのであれば、Eventにcreatedを
> 持たせてTagからEventが参照できれば十分なのではないかと思います。
> というか、Eventにはcreatedがほしいです。
個人的には created, last_use は無くしてしまっても良いかもしれないと考え
はじめました。またご指摘のとおり、 Event に created は必要ですね。
2008/7/6 kwin <kwi...@gmail.com>:
> これは私も実際に試してみました。
> http://webdba.blogspot.com/2008/06/many-to-many-join.html
> 一応 many-to-many も考慮しているという感じですが
> back-references というのは負荷がかかるようなので扱い注意という感じが
> しました。
たいへん参考になります。私もパフォーマンスを考えて正規化を捨てる亊を考
えています。
> とにかくDocs に明記されているのですが、
> fetch() returns a maximum of 1000 results は実際に体験してみてショックでした。
> http://webdba.blogspot.com/2008/06/fetch-returns-maximum-of-1000-results.html
> ここにも count の結果を持つ例がありましたが、こうした工夫が必要であると思います。
> http://sites.google.com/site/io/working-with-google-app-engine-models
はい。正確に値が取りたい時はカウンターを作って総数を入れておくのが良さ
そうですよね。
> ある程度かためた段階で実際にデータを入れて動かしてみたいですね。
確かに、未知の世界なのでこれは大事だと思います。
最後に私の現在の意見を述べますね。
0. Event に created は必要(何故か忘れていました。小田切さんありがとうご
ざいます)
1. あくまで Tag は Event 自身の ListProperty に格納したい
この理由は、下記のような Gql を使いたいからです。(tag_index には小文
字に正規化された Tag が入っているとします)
Event.Gql("where tag_index == :1 order by created desc", "python")
私が未熟なのかもしれませんが Many to Many を使った場合このようなクエ
リーを効果的に行うのはちょっと難しいような気がします。何か良い方法が
あれば教えてください。
2. その上で 「Tag が今までに使われた回数」と「Tag がここ一ヶ月で使われ
た回数」を知りたいと思います。今は下記のような設計を考えています。
TagCount(BaseModel):
tag_name = db.StringProperty(required=True)
count = db.IntegerProperty(default=0)
recent_count = db.IntegerProperty(default=0)
Event が追加、更新(特に Tag が)された時に、その Event に関係する
Tag について使われている数を取得します。
Event.Gql(
"where tag_index == :1" order by created desc", "python"
).fetch(1000).count()
Event.Gql(
"where tag_index == :1 and created > :2" order by created desc",
"python", date_time_month_ago
).fetch(1000).count()
で、get_or_insert で tag 名を key_name にして取得した TagCount イン
スタンスに値を設定し、put() します。
もし count の値や TagCount のクエリー結果を使う時は memcache を使っ
て一定時間値をキャッシュします。
Event が追加・更新されるのはあまり頻繁には起こらない亊なので、上記の
ような Global Counter を使っても大丈夫な気がします。また、このカウン
トはシリアスなデータでは無いので、Event と TagCount の更新両方をトラ
ンザクションで囲む必要は無いと考えています。
また、count が 1000 以上取れない問題に関しては、「Tag が 1000 回以上
使われている」=「非常に使用頻度が高い」と考えてしまえば、当面 1000
以上は数える必要は無いだろうと思っています(楽観的すぎるかも)。
上記のような設計はいかがでしょうか? 意見をお聞かせいただけると嬉しいで
す。
Happy coding :-)
-- Takashi Matsuo
お名前を間違えました。 created を指摘していただいたのは後藤さんですね。
お許しください orz
2008/7/6 Takashi Matsuo <matsuo....@gmail.com>:
なるほど。改めてドキュメントやVideoを見てみました。
djangoのデータモデルをベースにしてるということで、ついいろいろな面で
期待をしてしまっていましたが、クエリに費やすCPU時間の制約や、クエリ結果が
キャッシュされないなどのメモリリソースの制約など、GAE特有の制約は当然
あるわけだから、近年しみついてしまった富豪的なやりかたは捨てて「工夫」
することが重要な気がしてきました。
それにしても count()でも1000で切られるとは。
これって、とんでもなく大きなエントリ数でもそれなりに動かすには
WEBページ検索結果やGmailでの検索結果でもそうですが、
ある程度の処理時間消費で抑えるためにはこういう制約が必要になってくる
ということなのかもしれませんね。
--
Shun-ichi GOTO
なるほど。
それでは、count() を使うのはやめて、Tag が新しく付いた時と Tag が消され
た時に TagCounter を増減する亊にしましょうか。ただその場合 Event の更新
と TagCounter の更新をトランザクションで囲むのがちょっと難しいですよね。
( Event と TagCounter を parent - child の関係にするのって無理っぽくな
いですか )
トランザクションで囲まないとすると counter の増減に失敗した場合は値を捨
ててしまう亊になりそうですかね。パフォーマンスのためなら、私はこれを許
容できますが...
> ここ http://jp.twisternow.net/ などもGAE 対応でなんだか苦労している感じですし、
>
> http://sites.google.com/site/io/building-scalable-web-applications-with-google-app-engine
> これをみてみても、 GAE では batch での count は高くつく! counter をつくれ!
> といっているように感じます。
>
> この資料に汎用的な counter の model があり、参考になりそうです。
> http://webdba.blogspot.com/2008/07/building-scalable-web-applications.html
>
> # shard_name のあたりがまだよく理解できていないのですが
多分今回のケースでは、Tag のカウントはグローバルカウンターで済むと思い
ます。そんなに write されないと思うので。
> あと Event の model には EventIndex のような parent をつくるのがよさそうです。
> http://webdba.blogspot.com/2008/07/building-scalable-web.html
これは確かにそうですね。Event に連番を付けるのは良いと思います。これも
グローバルな index で済むと思います。
>> "where tag_index == :1 and created > :2" order by created desc",
>> "python", date_time_month_ago
>> ).fetch(1000).count()
> 1000 件以上のものは、"いっぱい"という対応でもいいと思うのですが、
> 数百件の検索でも Count できなくなることもありそう、といいますか、
なるほど。
それでは、count() を使うのはやめて、Tag が新しく付いた時と Tag が消され
た時に TagCounter を増減する亊にしましょうか。ただその場合 Event の更新
と TagCounter の更新をトランザクションで囲むのがちょっと難しいですよね。
これは確かにそうですね。Event に連番を付けるのは良いと思います。これも
> あと Event の model には EventIndex のような parent をつくるのがよさそうです。
> http://webdba.blogspot.com/2008/07/building-scalable-web.html
グローバルな index で済むと思います。
Models
class GlobalIndex(db.Model):
max_index = db.IntegerProperty(required=True,
default=0)
class BlogEntry(db.Model):
index = db.IntegerProperty(required=True)
title = db.StringProperty(required=True)
body = db.TextProperty(required=True)
>> > 1000 件以上のものは、"いっぱい"という対応でもいいと思うのですが、
>> > 数百件の検索でも Count できなくなることもありそう、といいますか、
>>
> すみません、1000件以上の count は 1001 などの値が返されてきて、
> これ自体ですぐにエラーになることはありませんでした。
>
count() ができたとしても重い亊に変わりはなさそうですよね。
>> なるほど。
>>
>> それでは、count() を使うのはやめて、Tag が新しく付いた時と Tag が消され
>> た時に TagCounter を増減する亊にしましょうか。ただその場合 Event の更新
>> と TagCounter の更新をトランザクションで囲むのがちょっと難しいですよね。
>
> トランザクションの囲み方については、まだあまりよくわかっていないのですが、
> いずれにしろ更新系の処理がある場合、無理っぽいですね。
>
> # 純粋にナンバリングしていくのなら資料にあるような例もいいかもしれませんが
複数の更新をトランザクションで囲みたい場合、全ての Entity を同じ
Entity Group に所属させておく必要があります。しかも parent は後から変更
できません。
Tag は後から変更可能なので Event の更新と TagCount のカウントアップ・ダ
ウンを同一トランザクションで囲むのは無理そうです。
>> > あと Event の model には EventIndex のような parent をつくるのがよさそうです。
>> > http://webdba.blogspot.com/2008/07/building-scalable-web.html
>>
>> これは確かにそうですね。Event に連番を付けるのは良いと思います。これも
>> グローバルな index で済むと思います。
>
> http://sites.google.com/site/io/building-scalable-web-applications-with-google-app-engine
> この資料の スライド29 (以下に示す) に GlobalIndex とあったのですが、これは次のスライド30に
> blog_index = BlogIndex.get_by_key_name(blogname) とあるので
> BlogIndex に読み直して動かしてみたのですが、よかったのか???
>
>
> Building a Blog: Models
>
> Models
>
> class GlobalIndex(db.Model):
> max_index = db.IntegerProperty(required=True,
> default=0)
>
> class BlogEntry(db.Model):
> index = db.IntegerProperty(required=True)
> title = db.StringProperty(required=True)
> body = db.TextProperty(required=True)
>
> グローバルな index の例など参考となるものがありましたら、
> よろくお願いいたします。
田中さんの理解で合っています。全ての Event は唯一存在する GlobalIndex
の子供にしてしまうわけです。Event 自体の更新時には、全ての Event がロッ
クされてしまいますが、Event の更新はそんなに頻繁にあるわけでは無いので
大丈夫というわけです(本当かな?ちょっとだけ不安)。
# Blog の例では、Blog の名前毎にひとつ GlobalIndex を作っているけれども、
# 今回の Event の場合は GlobalIndex は一つで済むはずです。
こうする以外に、全ての Event に確実に順番を付ける方法は無いはずです。全
ての Event に順番を付ければ、確実にページネーションの処理などが行えます。
また、処理が重いかどうかは別として、1000 以上の count も正確に行えるよ
うになります。
最後にもうひとつ、自分で下記のようなモデルを提案していましたが、
recent_count の扱いは難しいですね。これこそバッチ的に処理をしないとすぐ
に意味の無いデータになってしまいます。
TagCount(BaseModel):
tag_name = db.StringProperty(required=True)
count = db.IntegerProperty(default=0)
recent_count = db.IntegerProperty(default=0)
まあ、モデルはこのままにしておいて、 recent_count については後で処理方
法を考える亊にすれば良いかな...
複数の更新をトランザクションで囲みたい場合、全ての Entity を同じ
Entity Group に所属させておく必要があります。しかも parent は後から変更
できません。
Tag は後から変更可能なので Event の更新と TagCount のカウントアップ・ダ
ウンを同一トランザクションで囲むのは無理そうです。
全ての Event は唯一存在する GlobalIndex
の子供にしてしまうわけです。Event 自体の更新時には、全ての Event がロッ
クされてしまいますが、Event の更新はそんなに頻繁にあるわけでは無いので
大丈夫というわけです(本当かな?ちょっとだけ不安)。
# Blog の例では、Blog の名前毎にひとつ GlobalIndex を作っているけれども、
# 今回の Event の場合は GlobalIndex は一つで済むはずです。
こうする以外に、全ての Event に確実に順番を付ける方法は無いはずです。全
ての Event に順番を付ければ、確実にページネーションの処理などが行えます。
また、処理が重いかどうかは別として、1000 以上の count も正確に行えるよ
うになります。
なんかこの議論、meet-at ML限定でするのはすごくもったいない気がしてきました。(^^;
MLを分けることの難しいところなわけですが、
いっそ分けずに meet-at を「チャレンジプロジェクト」とでも呼んで
Google-App-Engine-Japan ML上で開発するというのもアリなんじゃないか、
という気もしてます。
--
Shun-ichi GOTO
2008/7/8 Shun-ichi GOTO <shunic...@gmail.com>:
>
> 2008/07/08 21:12 kimio tanaka <kwi...@gmail.com>:
>>>
>>> 複数の更新をトランザクションで囲みたい場合、全ての Entity を同じ
>>> Entity Group に所属させておく必要があります。しかも parent は後から変更
>>> できません。
>>>
>>> Tag は後から変更可能なので Event の更新と TagCount のカウントアップ・ダ
>>> ウンを同一トランザクションで囲むのは無理そうです。
>>
>>
>> 同じ Entity Group でないものは、1つのトランザクションに入れられない
>> とういことに はじめて気がつきました!
実際にまじめに実装しようと思わないと細かいところには気づかないものです
よね。
> なんかこの議論、meet-at ML限定でするのはすごくもったいない気がしてきました。(^^;
> MLを分けることの難しいところなわけですが、
> いっそ分けずに meet-at を「チャレンジプロジェクト」とでも呼んで
> Google-App-Engine-Japan ML上で開発するというのもアリなんじゃないか、
> という気もしてます。
確かにこのような議論は RDBMS から Datastore に移行する人、移行しようと
思っている人には有用だと思います。後でまとめて Google-App-Engine-Japan
の資産にするつもりです。
また、実は Google-App-Engine-Japan の方で、Google I/O のセッションの中
から 'Building scalable web applications with Google App Engine'[1] を
和訳したものを使ってオンラインプレゼンテーションをしようと考えています。
1. http://sites.google.com/site/io/building-scalable-web-applications-with-google-app-engine