tag:blogger.com,1999:blog-84364109391487342202024-03-14T01:57:29.526+09:00雲の上のクラスタcloudysunny14http://www.blogger.com/profile/09953267600910932223noreply@blogger.comBlogger20125tag:blogger.com,1999:blog-8436410939148734220.post-48807000141077677372013-01-13T12:36:00.001+09:002013-02-05T23:22:35.301+09:00ブログ移動しました今後は<a href="http://www.cloudcluster.cloudysunny14.org/">Above the clouds</a>に書いていきます。<br />
テーマはWeb全般です。<br />
<br />
<br />
<br />
<br />cloudysunny14http://www.blogger.com/profile/09953267600910932223noreply@blogger.com0tag:blogger.com,1999:blog-8436410939148734220.post-1022453696821909452012-12-02T17:54:00.002+09:002012-12-02T17:54:40.634+09:00Google App Engine Python NDB を使ってみた。(7)<br />
<b><span style="font-size: large;">NDB Transactions</span></b><br />
<br />
トランザクションとは一連のオペレーションのセットについて完全に成功かまたは完全に失敗のどちらかにする制御のことである。アプリケーションは複数の計算とオペレーションを一つのトランザクションで行う事が出来る。NDBのAsynchronous APIを使用すると、独立している処理であれば、複数のトランザクションを同時に管理する事が出来る。Synchronous APIは@ndb.transactional()デコレータを使用する事によって簡単にTransactionを制御できる。<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
key = ndb.Key(Greeting, 'joe')
@ndb.transactional
def greet():
# 'key' here uses the key variable in the outer scope;
# the callback function is a closure.
ent = key.get()
if ent is None:
ent = Greeting(key=key, message='Hey Joe')
ent.put()
return ent
greet()
]]>
</script>
競合が発生した場合は、失敗するが、NDBはその失敗したトランザクションを何回か自動的にリトライを行う。従って、その関数はリトライによって複数回呼び出される事がある。リトライの回数はデフォルトで3回である。もしそれでもトランザクションが失敗する場合は、NDBがTransactionFailedErrorを送出する。リトライ回数はtransactional()のretries=Nで設定することが出来る。リトライ回数を0にすると、それは一回だけ試みる事になり、リトライは行わない。リトライ回数のNはトランザクションが試みるであるトータルのN+1回の事である。<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
@ndb.transactional(retries=1) # Total of 2 tries
def greet():
# do greeting
]]>
</script>
トランザクションでは、先祖クエリだけを許している。デフォルトでは、トランザクションは同じエンティティグループに所属するエンティティにのみ作用する。(同じ先祖キーを持つエンティティ達)<br />
<br />
また、xg=Trueを設定することで、cross-group("XG")transactionsも使う事が出来る(最大5エンティティグループまで)<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
@ndb.transactional(xg=True)
def greet_a_variety_of_things():
# do greeting
</script>
もし関数が例外を送出した場合はトランザクションは即時に中断され、NDBはそれを見れるようにする為にもう一度例外を送出する。暗黙にトランザクションを失敗されるにはndb.Rollback例外を送出することで、可能である。(関数の戻り値はNoneになる)これはリトライの機構は持っていない。<br />
<br />
常にトランザクション内の処理にしたくない場合もあるが、代わりにデコレート関数の@ndb.transactionalの代わりにcallback関数のndb.transaction()を使う事ができる。<br />
<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
def get_or_insert(keyname):
key = ndb.Key(Greeting, keyname)
ent = key.get()
if ent is None:
ent = Greeting(key=key, message='Hey Rodrigo')
ent.put()
return ent
moraes = ndb.transaction(lambda: get_or_insert('rodrigo'))
</script>
コードがトランザクション内で実行されているかどうかをテストする場合はin_transaction()関数を使えば可能である。<br />
<br />
関数が呼びだされた時のトランザクションの振る舞いをどのようにしたら良いかを指定する事が出来る。@ndb.non_transactionalデコレータを指定するとトランザクション内で実行しない方がいい処理となり、トランザクション内で呼び出された場合は、トランザクション外で実行される事になる。@ndb.transactionalデコレータとndb.transaction関数にpropagationキーワード引数をもった関数がある。例えば、関数が呼び出された場合に新しく、独立したトランザクションにしたい場合は、デコレータを下記のようにする。<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
@ndb.transactional(propagation=ndb.TransactionOptions.INDEPENDENT)
def IndependentGreet():
# ... greet ...
</script>
propagationについては[<a href="https://developers.google.com/appengine/docs/python/ndb/functions#context_options">Context Options and Transaction Options</a>]にリストアップされている。<br />
<br />
トランクションの振る舞いとNDBのキャッシュの振る舞いはどのようになるか分からない場合に混乱を招く事がある。例えば、エンティティをトランザクション内で更新しているが、コミットしていない場合に、NDBのキャッシュ上は更新されているが、Datastoreでは更新されていないのである。<br />
<br />
今回は、トランザクションについて紹介した。<br />
トランザクションの振る舞いをデコレータで制御できるのは便利である。<br />
<br />
次回は、NDB Administrationについて。cloudysunny14http://www.blogger.com/profile/09953267600910932223noreply@blogger.com0tag:blogger.com,1999:blog-8436410939148734220.post-67644026405881684762012-11-25T18:18:00.001+09:002012-11-25T18:36:29.717+09:00Google App Engine Python NDB を使ってみた。(6)<h3>
<span style="font-size: large;">
NDB Queries(2)</span></h3>
<div>
<br /></div>
<div>
前回の続き。</div>
<div>
<span style="font-size: large;"><b>・</b></span><span style="font-size: large; font-weight: bold;">Structured Propertyに対するフィルタリング</span></div>
<div>
<div>
<br /></div>
<div>
QueryはStructed Propertyのフィールドの値に対して直接フィルタリングする事が出来る。</div>
<div>
例えば、cityがAmsterdamのContactオブジェクトに対してQueryを定義すると下記のとおりになる。</div>
</div>
複合したフィルタリングを用いたい場合は、以下のように定義する。<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
Contact.query(Contact.address.city == 'Amsterdam', # Beware!
Contact.address.street == 'Spear St')
]]>
</script>
上記で抽出されるContactはcityがAmsterdamかほかのcityでadressがSpear Stになる。しかし、少なくとも等価のフィルターになる。もし単一の結果を返却したい場合は以下のように定義する。<br />
このテクニックを使うと、プロパティのサブエンティティがNoneと等価の場合はクエリで無視される。プロパティがデフォルト値を持っている場合にQueryで無視したい場合は明示的にNoneを設定する必要がある。それか、Queryで指定しているフィルターをデフォルト値と一致させる必要がある。例えば、Addressモデルがcoutryプロパティのデフォルト値をdefault='us'と設定している場合、上記例では、countryが'us'のContactのみを返す事になる。他のcountryを持つContactを抽出したい場合は、filterをAddress(city='San Francisco', street='Spear St', country=None).のようにする必要がある。<br />
<br />
サブエンティティのプロパティのどこかにNOneが設定されている場合はそれらは無視される。従って、Noneが設定されてるサブエンティティへのfilterは意味をなさない。<br />
<br />
<b><span style="font-size: large;">・Projection Queries</span></b><br />
<br />
Queryにprojectionを指定することができる。これは検索したいプロパティのリストで、もしprojectionを指定した場合は、NDBはそれぞれのEntityから全ての値は取得しない。<br />
projectionで指定したプロパティの値しか取得しない。これはQueryのindexより取得するものなので、indexされている必要がある。数個の大きいEntityから小さいプロパティのいくつかが抽出したい場合に有用であり、Fetchが効率的に行われる。もしprojectionを指定せずに行うとndb.UnprojectedPropertyError.例外を送出することになる。<br />
<br />
データストアよりArticleを取得する例では、分類学者は彼らの記事に適用されているタグの作成者を知りたいと仮定すると、必要なのはauthorとtagsの情報だけなので、projectionを以下のように指定する。<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
qry = Article.query()
articles = qry.fetch(20, projection=[Article.author, Article.tags])
for article in articles:
code here can use article.author, article.tags
but cannot use article.title
]]>
</script>
Repeated Properties:例では、Aritcle.tagsはrelated propertyとなっている。これは、repeated property はインデックスされていて、(projectionはindexから取得する)projectionクエリは格納されたentityへ複数のentityをフェッチする。もしArticle(author='Guido', tags=['python', 'jython'])のようなEntityが存在した場合に、projectionクエリはArticle(author='Guido', tags=['python'])とArticle(author='Guido', tags=['jython'])の二つのEntityを返却する。<br />
<br />
Structured Properties:インデックスされたstructured propertyのサブプロパティを射影する事ができる。下記のように射影を指定することができる。<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
Contact.query().fetch(projection=["name", "address.city"])
]]>
</script>
<br />
<br />
<b><span style="font-size: large;">・String値によってPropertyを指定する</span></b><br />
<div>
<br /></div>
<div>
時々String値によってフィルター、順序するプロパティをクエリーで指定したい場合があるが、例えば、ユーザーが指定したtags:pythonのようなサーチクエリを使いたい場合は以下のようにする。</div>
<div>
<br /></div>
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
Article.query(Article."tags" == "python") # does NOT work
]]>
</script>
もし、ModelがExpndo Modelの場合は、GenericPropertyを使ってフィルタリングする事ができる。Expandoは動的プロパティを使うので、<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
FlexEmployee.query(ndb.GenericProperty('location') == 'SF')
]]>
</script>
このGenericPropertyはExpando Modelでなくても使用する事ができるが、もし自分で確保したプロパティのみを指定させたい場合は、_properties属性を使用することもできる。<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
Article.query(Article._properties[keyword] == value)
]]>
</script>
または、getattr()を使用する事もできる。<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
Article.query(getattr(Article, keyword) == value)
]]>
</script>
getattr()と_propertiesの違いは、getattr()はPython上のproperyの名前を使い、_propertiesはインデックスされたデータストアのプロパティの名前を使用する。下記のように定義した場合のみ、そのような挙動をする。<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
class Article(ndb.Model):
title = StringProperty('t')
]]>
</script>
<br />
これは、Python上ではtitleだが、データストア上はtとなる。<br />
<br />
以下のようなアプローチでも使用することができる。<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
FlexEmployee.query().order(ndb.GenericProperty('location'))
Article.query().order(Article._properties[keyword])
]]>
</script>
<b><span style="font-size: large;">・Query Iterators</span></b><br />
<br />
Queryが処理されている間は、iteratorオブジェクトとして保持されている。(アプリケーションでこれらが使用される場合はほとんどない。fetch(20)のような方が普通かもしれない)基本的には以下の2点でiteratorオブジェクトを取得することができる。<br />
<br />
・QueryからPythonのiter()関数を呼んで使う場合<br />
・Queryオブジェクトのiter()メソッドを呼ぶ場合<br />
<br />
一つ目は、 PythonのforループをつかってQueryが終わるまでループする方法<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
for greeting in greetings:
self.response.out.write('<blockquote>
%s</blockquote>
' %
cgi.escape(greeting.content))
]]>
</script>
二つ目は、Queryオブジェクトのiter()メソッドを使用する方法で、iteratorの振る舞いに影響を与える為にiteratorにオプションを渡す事ができる。例えば、keyのみのクエリーをforループで使用する場合。<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
for key in qry.iter(keys_only=True):
print key
]]>
</script>
Query iteratorsは他に以下のような便利なメソッドがある。<br />
__iter__(), next(), has_next(), probably_has_next(), cursor_before(), cursor_after()<br />
<br />
<b><span style="font-size: large;">・Query Cursors</span></b><br />
<br />
クエリカーソルは、クエリ内の再開ポイントを表す小さな不透明なデータ構造です。これは、その時点での結果をユーザーに見せる場合に便利である。また、長いデータなどで、一時停止して処理したい場合などにも使える。典型的な方法では、Queryのfetch_page()メソッドを使用する方法で、fetch()と同じように動作するが、返却値としてresults, cursor, moreを返却する。moreフラッグは更に結果が存在する事を示し、UIはこれを、例えばNextPageボタンをリンクとして設置したりできる。後続のページをリクエストする場合は、返却値のcursorのfetch_page()を呼ぶ。<br />
<br />
したがって、ユーザーにその時点の全ての検索結果を表示する場合に以下のようにコーディングすることができる。<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
from google.appengine.datastore.datastore_query import Cursor
class List(webapp2.RequestHandler): # Handle requests like /list?cursor=1234567
def get(self):
self.response.out.write('<html><body>')
curs = Cursor(urlsafe=self.request.get('cursor'))
greets, next_curs, more = Greeting.query().fetch_page(10, start_cursor=curs)
for greeting in greets:
self.response.out.write('<blockquote>%s</blockquote>' %
cgi.escape(greeting.content))
if more and next_curs:
self.response.out.write('<a href="/list?cursor=%s">More...</a>' %
next_curs.urlsafe())
self.response.out.write('</body></html>')
]]>
</script>
<br />
urlsafe()とCursor(urlsafe=s)をシリアライズとでシリアライズに使用する場合には注意が必要。これは、クライアントに一回のリクエストでcursorを渡すことができるのと、後のリクエストにデシリアライズして使用することができる。<br />
<br />
注意:fetch_page()メソッドは結果がそれ以上無い場合もcursorを返却するが、それは保証がない。Noneが返却されるだろう。moreフラッグも注意が必要でこれは、iteratorのprobably_has_next()を使用しているので、時々、Trueでも次のページが空の場合もある。<br />
<br />
いくつかのNDB Queryは、cursorをサポートしていないが、これを解消することができる。QueryでIN,ORまたは!=を使用すると、キーで順序指定されない限りcursorとして動作しない。<br />
もしアプリケーションで、順序を指定しないでfetch_page()を呼んだ場合は、BadArgumentErroが送出される。User.query(User.name.IN(['Joe', 'Jane'])).order(User.name).fetch_page(N)これではエラーになるので、User.query(User.name.IN(['Joe', 'Jane'])).order(User.name, User.key).fetch_page(N)とすれば問題ない。<br />
<br />
pagingの代わりにqueryの結果を取得する場合は、queryのiter()メソッドを正確なポイントで使用するとよい。ただ、produce_cursor=Trueをiter()に渡して上げる必要がある。正しい場所で、iteratorを使用した後は、cursor_afterを呼ぶ必要がある。(または、同様に、cursor_beforeをcursorの前に呼ぶ)cursor_after()またはcursor_before()を呼び出すと、cursorを抽出する為に、クエリの一部を再実行すると、ブロックされるかもしれないので、<br />
注意が必要。<br />
<br />
cursorをさかのぼって結果取得する場合は以下のようにする。<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
# Set up.
q = Bar.query()
q_forward = q.order(Bar.key)
q_reverse = q.order(-Bar.key)
# Fetch a page going forward.
bars, cursor, more = q_forward.fetch_page(10)
# Fetch the same page going backward.
rev_cursor = cursor.reversed()
bars1, cursor1, more1 = q_reverse.fetch_page(10, start_cursor=rev_cursor)
]]>
</script>
<span style="font-size: large;"><b>・それぞれのEntityへ呼び出す関数をMappingする</b></span><br />
<br />
Account Entityに関連するMessageをQueryによって取得したいと仮定すると以下のようにコーディングすることができる。<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
for message in qry:
key = ndb.Key('Account', message.userid)
acct = key.get()
]]>
</script>
しかしながら、これは非効率である。以下のようにcallback関数を使用することができる。<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
def callback(message):
key = Key('Account', message.userid)
acct = key.get()
return message, acct
pairs = qry.map(callback)
# Now pairs is a list of (message, account) tuples.
]]>
</script>
<br />
このバージョンでは、並列化することができるので、forループよりすこし早くすることができる。しかし、get()メソッドでcallback()を使用しても同期処理になってしまうので、asyncronous getを使用するといい。<br />
<div>
<br /></div>
<div>
<b><span style="font-size: large;">・GQL</span></b></div>
<div>
<br /></div>
<div>
NDBで以下のようにGQLを使用することもできる。</div>
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
qry = ndb.gql("SELECT * FROM Account WHERE spam > :1")
qry2 = qry.bind(10)
]]>
</script>
または、<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
qry = ndb.gql("SELECT * FROM Account WHERE spam > :1", 10)
]]>
</script>
<br />
bind()関数は元と同じ新しいqueryを返却する。<br />
<br />
SQLに慣れている場合、GQLを使用して誤った仮定には注意。 GQLはNDBのネイティブクエリAPIに変換される。これは、それらがデータベース·サーバーに送信される前にAPIの呼び出しがSQLに変換され、典型的なオブジェクト·リレーショナル·マッパー(SQLAlchemyのか、Djangoのデータベースのサポートなど)とは異なる。 GQLは、データストアの変更(挿入、削除または更新)をサポートしていないので、クエリのみをサポートしている。<br />
<br />
今回は、Queryについて2回にわたって紹介した。<br />
しかし、翻訳がつたないのと理解不足があるので、<br />
いずれは、実際に使用してみた例を紹介したいと思う。<br />
<br />
次回は、Transactionsについて。cloudysunny14http://www.blogger.com/profile/09953267600910932223noreply@blogger.com0tag:blogger.com,1999:blog-8436410939148734220.post-14881079548243092182012-11-18T16:43:00.000+09:002012-11-19T19:24:20.445+09:00Google App Engine Python NDB を使ってみた。(5)<h3>
NDB Queries</h3>
アプリケーションはクエリーを使用したフィルターの基準によって指定した検索にマッチするEntityをDatastoreから検索できる。<br />
<br />
<b>・概要</b><br />
アプリケーションはクエリーを使用したフィルターの基準によって指定した検索にマッチするEntityをDatastoreから検索できる。例えば、アプリケーションはクエリーを使ってTrackとGuestbooksを一つのguestbookから日付順に検索できるようにしたままに出来る。<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
from google.appengine.ext import ndb
class Greeting(ndb.Model):
"""Models an individual Guestbook entry with content and date."""
content = ndb.StringProperty()
date = ndb.DateTimeProperty(auto_now_add=True)
@classmethod
def query_book(cls, ancestor_key):
return cls.query(ancestor=ancestor_key).order(-cls.date)
class MainPage(webapp2.RequestHandler):
def get(self):
guestbook_name = self.request.get('guestbook_name')
ancestor_key = ndb.Key("Book", guestbook_name or "*notitle*")
greetings = Greeting.query_book(ancestor_key).fetch(20)
self.response.out.write('<html><body>')
for greeting in greetings:
self.response.out.write('%s' % cgi.escape(greeting.content))
]]>
</script>
<br />
いくつかのクエリーは他より更に複合にできるが、Datastoreはインデックスを事前に構築しておく必要がある。これらの事前に構築するindexはindex.yamlというconfiguration fileで定義できる。開発サーバーでは、クエリーを実行する為に必要なindexを指定していなくても動作するのだが、これは開発サーバーが自動的にindex.yamlを追加するからである。しかし実際のWebSiteでは指定していないindexが必要になると失敗する。したがって、典型的な赤井発サイクルでは、開発サーバーで新しいクエリを試して自動的に更新されたindex.yamlをWebSiteのindex.yamlとして更新する。 <br />
また、index.yamlをアプリケーションとは別に更新できる。<br />
もしDatastoreにたくさんのEntityが存在するとき、インデックスを作成するのに長い時間がかかる。今回の場合、新しいindexを使用するコードをアップデートかける前に、index定義を更新するのが賢明。アドミンコンソールからindex構築の状況を確認することができる。<br />
<br />
Datastoreは一致条件に (the==operator)と比較に(<,<=,> and >= operatros)をサポートしている。複数のフィルターをANDを使用する事によって結合できるが、いくつかの制限がある(以下を参照)<br />
<br />
さらに、APIは!=とグループのフィルタリングをORで結合できるのと、INが使える。INはPythonのinのようにlistの中で一致する要素を検査する。これらは1対1でDatastoreのオペレーションではない。従って、相対的に少し風変わりで遅い。これらは結果のストリームをインメモリでマージする実装をしている。p!=vは実装上p<v OR p>vとなっている。<br />
<br />
制限:データストアはいくつかの制限を強制している。これらを違反すると例外を送出する原因となる。例えば、複数のプロパティーに対する不等号のフィルタリングをたくさん結合したり、異なるプロパティーでソートを行う不等号の結合フィルターは現在全て禁止されている。複数のプロパティを参照するフィルターもまた時々セカンダリーindexの設定が必要になる。<br />
<br />
非サポート:Datastoreは一部の文字を使用する検索、大文字小文字区別なし、また全文検索はサポートしていない。これらの大文字小文字区別なしと全文検索でさえ実装するにはcomputedプロパティーを使用する。<br />
<br />
<b>・Propertyの値でフィルタリングする</b><br />
通常は与えられたkindの全てのEntityを検索したくない。だいたいはいくつかのプロパティーに対して範囲を指定したい。<br />
プロパティはフィルタ<br />
ーを表すクエリーによって操作できる。例えば、useridが42をもつEntityを抽出すると以下のような表現になる。<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
qry = Account.query(Account.userid == 42)
]]>
</script>
<br />
もしuseridがAccount内で一つである事が確実であれば、useridはキーとして扱うだろう。Account.get_by_id()にした方が早いからである。<br />
NDBは以下のオペレーションをサポートしている。<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
property == value
property < value
property <= value
property > value
property >= value
property != value
property.IN([value1, value2])
]]>
</script>
不等のフィルタリングを行う場合は、以下のような文法を使える。<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
qry = Account.query(Account.userid >= 40)
]]>
</script>
これはuseridが40以上のEntityを探すクエリーとなる。<br />
<br />
!=とINは実装上、他のオペレーションとの複合である。また、これらは少し風変わりな説明である。<br />
<br />
複合フィルターは以下のように指定する。<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
qry = Account.query(Account.userid >= 40, Account.userid < 50)
]]>
</script>
<br />
この複合フィルターの引数は、useridが40以上から50未満のEntityを返すクエリーとなるが、前述のとおりDatastoreは、複数のプロパティで不等号を使用したクエリーを拒否する。<br />
<br />
代わりに全体のクエリーフィルターを一つで表現する。多分もっと便利にクエリーを積み上げることが出来る事を見つけるであろう。<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
qry1 = Account.query() # Retrieve all Account entitites
qry2 = qry1.filter(Account.userid >= 40) # Filter on userid >= 40
qry3 = qry2.filter(Account.userid < 50) # Filter on userid < 50 as well
]]>
</script>
qry3は前述の例のqrtと等価である。このクエリーオブジェクトは不変であり、qry2はqry1に影響を与えず、また、qry3もqry1とqry2に影響は与えない。<br />
<br />
<b>・!= と IN のオペレーション</b><br />
!=(不等)とIN(メンバーシップ)オペレーションはORを使った他のオペレーションとの複合である。まずは以下。<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
property!=value
]]>
</script>
は実装上<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
(property < value) OR (property > value)
]]>
</script>
例えば、<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
qry = Article.query(Article.tags != 'perl')
]]>
</script>
これは以下と等価<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
qry = Article.query(ndb.OR(Article.tags < 'perl',
Article.tags > 'perl'))
]]>
</script>
<br />
Note:多分驚くであろうが、このクエリーは、perlタグを含まないEntityを探すよりむしろ最低でも一つのタグがperlでない全てのエンティティーを探している。例えば次のエンティティはそのタグの一つとしてperlを持っているのみも関わらず、結果に含まれるだろう。<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
Article(title='Perl + Python = Parrot',
stars=5,
tags=['python', 'perl'])
]]>
</script>
しかしながら、これは含まれないであろう。
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
Article(title='Introduction to Perl',
stars=3,
tags=['perl'])
]]>
</script>
<br />
perlと同じタグが含まれていないエンティティを抽出する為の方法はない。<br />
また、INオペレーションについては、以下の表現ができる。<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
property IN [value1, value2, ...]
]]>
</script>
<br />
これはリストの値のメンバーシップを検索する。実装上は以下の通り<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
(property == value1) OR (property == value2) OR ...
]]>
</script>
例は下記<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
qry = Article.query(Article.tags.IN(['python', 'ruby', 'php']))
]]>
</script>
以下とも等価である。<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
qry = Article.query(ndb.OR(Article.tags == 'python',
Article.tags == 'ruby',
Article.tags == 'php'))
]]>
</script>
<br />
ORをしようすると重複した結果は得られない。<br />
<br />
<b>・Repeated Propertiesへのクエリー</b><br />
Articleクラスはrepeatedプロパティへのクエリの例としても先行したセクションとして定義した。特にフィルターのように、<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
Article.tags == 'python'
]]>
</script>
<br />
上記Article.tagsはrepeatedプロパティにも関わらず、一つの値を使った。すると、このプロパティはlistオブジェクトと比べることが出来なくなる。また、フィルターのように、<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
Article.tags.IN(['python', 'ruby', 'php'])
]]>
</script>
<br />
この場合は、tagsのプロパティーがlistの['python','ruby','php']を持っているEntityを探す事とは全く違う。これはtagsの値が['python','ruby','php']のうち最低でも一つが含まれるEntityを探す。<br />
Noneはクエリーで検索する事が出来ない。<br />
<br />
<b>・ANDとORを複合して使う</b><br />
ANDとORオペレーションをネストして使う事が出来る。<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
qry = Article.query(ndb.AND(Article.tags == 'python',
ndb.OR(Article.tags.IN(['ruby', 'jruby']),
ndb.AND(Article.tags == 'php',
Article.tags != 'perl'))))
]]>
</script>
<br />
<br />
ORの実装の為に、ORを複合しすぎたクエリーは例外とともに失敗するであろう。これらのフィルターは下記のように、一つのレベルのANDとネストされたクエリーの最上位にあるOR<br />
を表現する為に正規化される。この拡張は、既に!=とINに与えられた拡張とともに、ブール式のための論理和標準形を得るための標準的な規則を使用する。要するに、上記の例の正規化された形は、(非公式の表記法を使用する)<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
OR(AND(tags == 'python', tags == 'ruby'),
AND(tags == 'python', tags == 'jruby'),
AND(tags == 'python', tags == 'php', tags < 'perl'),
AND(tags == 'python', tags == 'php', tags > 'perl'))
]]>
</script>
<br />
<br />
注意:<br />
この正規化は複合の爆発の可能性がある。<br />
<br />
<b>・ソート順序を指定する</b><br />
order()メソッドを使用してクエリーの結果の順序を指定する事が出来る。このメソッドはlist引数を受け取り、それぞれ、プロパティーのオブジェクトかまたは、それの昇順降順を指定できる。<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
qry = Greeting.query().order(Greeting.message, -Greeting.userid)
]]>
</script>
<br />
この検索は、messageプロパティーの値で昇順にしたGreeting Entityを検索する。<br />
連続したmessageプロパティをuseridを降順にソートした結果を返却するためには、複合のorderを呼び出せば可能である。<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
qry = Greeting.query().order(Greeting.message).order(-Greeting.userid)
]]>
</script>
order()でフィルタを組み合わせたとき、データストアは、特定の組み合わせを拒否する。特に、不等号フィルタで最初の並べ替え順序を(もしあれば)を使用した場合、フィルタと同じプロパティを指定する必要がある。また、時々セカンダリインデックスを設定する必要がある。<br />
<br />
<b>・先祖クエリ</b><br />
先祖クエリはクエリの結果を先祖から制約する。<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
key = ndb.Key(BlogPost, 12345)
qry = Comment.query(ancestor=key)
]]>
</script>
ソート順とフィルターの複合で使用できる。<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
qry = Comment.query(Comment.tags == 'python', ancestor=key).order(Comment.date)
]]>
</script>
同じ先祖のEntityへの操作となるので、これは特にtransaction内で便利である。<br />
<br />
<b>・Queryオブジェクトの属性</b><br />
Queryオブジェクトは以下のような読み込み専用の属性を持っている。<br />
kind<span class="Apple-tab-span" style="white-space: pre;"> </span>str<span class="Apple-tab-span" style="white-space: pre;"> </span>None<span class="Apple-tab-span" style="white-space: pre;"> </span>Kind <span class="Apple-tab-span" style="white-space: pre;"> </span>name (usually the class name)<br />
ancestor<span class="Apple-tab-span" style="white-space: pre;"> </span>Key<span class="Apple-tab-span" style="white-space: pre;"> </span>None<span class="Apple-tab-span" style="white-space: pre;"> </span>Ancestor <span class="Apple-tab-span" style="white-space: pre;"> </span>specified to query<br />
filters<span class="Apple-tab-span" style="white-space: pre;"> </span>Filter<span class="Apple-tab-span" style="white-space: pre;"> </span>Node<span class="Apple-tab-span" style="white-space: pre;"> </span>None<span class="Apple-tab-span" style="white-space: pre;"> </span>Filter expression<br />
orders<span class="Apple-tab-span" style="white-space: pre;"> </span>Order<span class="Apple-tab-span" style="white-space: pre;"> </span>None<span class="Apple-tab-span" style="white-space: pre;"> </span>Sort <span class="Apple-tab-span" style="white-space: pre;"> </span>orders<br />
<div style="font-weight: bold;">
<br /></div>
<div>
str()とかrepr()を呼んで内容を表示するときに表現できる。</div>
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
print Employee.query()
Query(kind='Employee')
print Employee.query(ancestor=Key(Manager, 1))
Query(kind='Employee', ancestor=Key('Manager', 1))
]]>
</script>
<br />
<br />
今回はQueriesについての前半を記載した。<br />
ここまでは特にNDBに特化した機能などはほとんどなかったが、<br />
次回もQueriesについてだが、NDB特有のmapなど登場する予定。<br />
<br />cloudysunny14http://www.blogger.com/profile/09953267600910932223noreply@blogger.com0tag:blogger.com,1999:blog-8436410939148734220.post-84668282600395162652012-11-11T14:42:00.001+09:002012-11-11T14:44:22.519+09:00Google App Engine Python NDB を使ってみた。(4)<h3>
<b>
プロパティーのサブクラスを書く(Writing Property Subclasses)</b></h3>
<div>
<br /></div>
プロパティクラスはサブクラス化できるよう設計されている。しかし普通は既存のプロパティクラスの方が簡単にサブクラス化できる。<br />
全ての特別なプロパティ属性とpublicと考えられる属性もアンダースコアから始まる名前を持っている。この理由はStructuredPropertyがネストされたプロパティーのアンダースコアの付いていない属性を使用するからで、これはサブプロパティにクエリを指定する為に不可欠である。<br />
<br />
プロパティクラスと既存のサブクラスは構成可能な(またはスタッカブル)検証および変換APIを使用してサブクラス化する事が出来る。これらはいくつかの用語定義を必要とする。<br />
<br />
・ユーザーの値(user value)は、アプリケーションが使用する標準の属性を持つエンティティに設定されるまたは、アクセスされるような値。<br />
・基本の値(base value)はデータストアからシリアライズまたはでシリアライズされるような値。<br />
<br />
シリアライズ可能な値とuser valueの間で特定の変換を実装する場合は二つのメソッドを実装した方がよい。to_base_type()と_from_base_type()がそれで、これらは構成可能なAPIという意味でsuper()メソッドは呼ばない方がよい。<br />
<br />
APIは今までよりも洗練されたuser-baseな変換を行えるstacking classesをサポートしている。user-to-base変換はbase-to-user 変換でより洗練から更に洗練へ変換している間に更に洗練からより洗練へ行く。例えば、BlobProperty,TextPropertyとStringPropertyの関係や、例えば、TextPropertyはBlobPropertyを継承している。それは必要な動作のほとんどを継承しているので、そのコードは簡単なもの。<br />
加えて、_to_base_type()と_from_base_type(),_validate()メソッドもまた、変換APIである。<br />
<br />
バリデーションAPIはuser valuesへの緩い制約と厳密な制約とを区別する。 緩い制約の値は厳密な値の集合の上位集合である。_validate()メソッドは緩い値をとり、また必要であれば、厳密な値へ変換する。これは、プロパティの値を取得する場合に、唯一厳密な値が返されるときにプロパティー値を設定するときは、緩い制約の値が受け入れられていることを意味する。もし変換が必要ない場合は、_validate()は多分Noneを返す。もし外部からの引数が、緩い値を設定した場合は、_validate()は例外を送出すべきでTypeErrorかdatasore_error.BadValueErrorが好ましい。<br />
<br />
_validate(),_to_base_Type(),_from_base_type()の操作が必要が無い場合:<br />
・None:これらはNoneと一緒に呼び出せない。(また、Noneを返却する場合、それは変換が必要ない事を意味する)<br />
・Repeated values:基盤で_from_base_type()か_to_base_typeをそれぞれのアイテム毎に呼ぶ。<br />
・base valuesからuservaluesを区別する場合:基盤では変換APIがよびだされる。<br />
・比較する場合:比較演算は、オペランドに_to_base_type()を呼び出す。<br />
・base valueとuser valueを区別する場合:基盤で_from_base_type()がbase valueと一緒に呼び出される事と、_to_base_type()がuser valueと一緒に呼び出される事を保証している。<br />
<br />
例えば、本当に長いintegerを必要とすると仮定する。標準のIntegerPropertyは64bitのintegerしかサポートしていない。長いintegerを保存する場合はStringとして格納しなくてはならない。変換を操作するpropertyクラスが良い。アプリケーションでそのプロパティを使うと恐らくこのようになるであろう。<br />
<div>
<br /></div>
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
# Imports.
from google.appengine.ext import ndb
from somewhere import LongIntegerProperty
# Define an entity class holding some long integers.
class MyModel(ndb.Model):
name = ndb.StringProperty()
abc = LongIntegerProperty(default=0)
xyz = LongIntegerProperty(repeated=True)
# Create an entity and write it to the Datastore.
ent = MyModel(name='booh', xyz=[10**100, 6**666])
assert ent.abc == 0
key = ent.put()
# Read an entity back from the Datastore and update it.
ent = key.get()
ent.abc += 1
ent.xyz.append(ent.abc//3)
ent.put()
# Query for a MyModel entity whose xyz contains 6**666.
# (NOTE: using ordering operations don't work, but == does.)
results = MyModel.query(MyModel.xyz == 6**666).fetch(10)
]]>
</script>
これはシンプルで素直だ。またこのデモは標準のプロパティのオプションも使用している。<br />
LongIntegerPropertyクラスの所有者はこれらの作業を取得する任意の定型を記述するひつようがないので、喜んでいるでしょう。他のプロパティのサブクラスを定義するのは簡単だ。下記が例<br />
<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
class LongIntegerProperty(ndb.StringProperty):
def _validate(self, value):
if not isinstance(value, (int, long)):
raise TypeError('expected an integer, got %s' % repr(value))
def _to_base_type(self, value):
return str(value) # Doesn't matter if it's an int or a long
def _from_base_type(self, value):
return long(value) # Always return a long
]]>
</script>
例えば、ent.abc = 42をエンティティのプロパティに設定した際_validate()メソッドが呼びだされて、値はエンティティに格納される。また、エンティティをDatastoreに格納する際は_to_base_type()が呼び出され、string値に変換する。そして、その値はStringProperyによってシリアライズされる。Datastoreからエンティティを読み戻した場合に逆算が起こる。<br />
StringPropertyとPropertyクラスはシリアライズやデシリアライズ、デフォルト値の設定、repeated propertyの値を操作するような他の世話を一緒にする。<br />
<br />
不等号を扱うにはさらに仕事とが必要である。下記の例では、固定長文字列として整数を格納した値の最大サイズを課している。<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
class BoundedLongIntegerProperty(ndb.StringProperty):
def __init__(self, bits, **kwds):
assert isinstance(bits, int)
assert bits > 0 and bits % 4 == 0 # Make it simple to use hex
super(BoundedLongIntegerProperty, self).__init__(**kwds)
self._bits = bits
def _validate(self, value):
assert -(2 ** (self.bits - 1)) <= value < 2 ** (self.bits - 1)
def _to_base_type(self, value):
# convert from signed -> unsigned
if value < 0:
value += 2 ** self._bits
assert 0 <= value < 2 ** self.bits
# Return number as a zero-padded hex string with correct number of digits:
return '%0*x' % (self._bits // 4, value)
def _from_base_type(self, value):
value = int(value, 16)
if value >= 2 ** (self._bits - 1):
value -= 2 ** self._bits
return value
]]>
</script>
<br />
これは、LongIntegerPropertyと同じ方法で使用され、整数をプロパティのコンストラクターで設定されることを期待している。BoudedLongIntegerProperty(1024)のように。<br />
<br />
他のプロパティの型も同じような方法でサブクラスにできる。<br />
<br />
このアプローチはstructured dataでも動作する。日付の範囲を表すPythonのFuzzyDateクラスを持っていると仮定する。それはfistとlastのフィールドを持っていて、日付の範囲を開始と終了で格納している。<br />
<div>
<br /></div>
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
from datetime import date
class FuzzyDate(object):
def __init__(self, first, last=None):
assert isinstance(first, date)
assert last is None or isinstance(last, date)
self.first = first
self.last = last or first
]]>
</script>
StructuredPropertyから派生しているFuzzyDateProperyを作成することが出来る。しかし残念ながら、後者は昔ながらのPythonクラスでは動作しない。Modelのサブクラスが必要である。中間モデルとしてModelのサブクラスを定義しよう。<br />
<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
class FuzzyDateModel(ndb.Model):
first = ndb.DateProperty()
last = ndb.DateProperty()
]]>
</script>
次に、FuzzyDateとFuzzyDataModelを変換する為にFuzzyDateモデルのmodelclassの属性のコードと、_to_base_type()と_from_base_typeメソッドを定義したSturucturedProperyのサブクラスを定義する。<br />
<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
class FuzzyDateProperty(StructuredProperty):
def __init__(self, **kwds):
super(FuzzyDateProperty, self).__init__(FuzzyDateModel, **kwds)
def _validate(self, value):
assert isinstance(value, FuzzyDate)
def _to_base_type(self, value):
return FuzzyDateModel(first=value.first, last=value.last)
def _from_base_type(self, value):
return FuzzyDate(value.first, value.last)
]]>
</script>
アプリケーションでは以下のように使用する。<br />
<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
# Class to record historic people and events in their life.
class HistoricPerson(ndb.Model):
name = ndb.StringProperty()
birth = FuzzyDateProperty()
death = FuzzyDateProperty()
# Parallel lists:
event_dates = FuzzyDateProperty(repeated=True)
event_names = ndb.StringProperty(repeated=True)
columbus = HistoricPerson(
name='Christopher Columbus',
birth=FuzzyDate(date(1451, 8, 22), date(1451, 10, 31)),
death=FuzzyDate(date(1506, 5, 20)),
event_dates=[FuzzyDate(date(1492, 1, 1), date(1492, 12, 31))],
event_names=['Discovery of America'])
columbus.put()
# Query for historic people born no later than 1451.
results = HistoricPerson.query(HistoricPerson.birth.last <=
date(1451, 12, 31)).fetch()
]]>
</script>
<br />
FuzzyDateProperyにFuzzyDateオブジェクトのようにdateオブジェクトを格納したいと仮定すると、_validate()メソッドは以下のように変更する。<br />
<div>
<br /></div>
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
def _validate(self, value):
if isinstance(value, date):
value = FuzzyDate(value)
assert isinstance(value, FuzzyDate)
return value # Must return the converted value!
]]>
</script>
以下のようにFuzzyDateProperyクラスの代わりに使用する事も出来る。<br />
<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
class MaybeFuzzyDateProperty(FuzzyDateProperty):
def _validate(self, value):
if isinstance(value, date):
return FuzzyDate(value)
# Otherwise, return None and leave validation to the base class
]]>
</script>
<br />
MaybeFuzzyDateProperyフィールドを割り当てるときにMaybeFuzzyDateProperty._validate()とFuzzyDateProperty._validate()が両方呼び出される。同じ事が_to_base_type()と_from_base_type()に適用される。スーパークラスとサブクラス内のメソッドは暗黙的に結合される。<br />
<br />
今回は自前でプロパティを用意したい場合の方法を紹介した。<br />
次回はNDBのQueryについて。cloudysunny14http://www.blogger.com/profile/09953267600910932223noreply@blogger.com0tag:blogger.com,1999:blog-8436410939148734220.post-38140798130151837242012-11-03T19:39:00.002+09:002012-11-03T19:46:36.406+09:00Google App Engine Python NDB を使ってみた。(3)<h3>
<b>【NDB プロパティについて】</b></h3>
<div>
<b><br /></b></div>
<div>
<div>
NDBのエンティティはプロパティの定義ができる。</div>
<div>
エンティティはデータを保持するために用いるPythonのクラスと似ている。</div>
<div>
それらはデータベースのスキーマにも似ている。</div>
<div style="font-weight: bold;">
<br /></div>
</div>
<div>
<b><span style="font-size: large;">・最初に</span></b></div>
典型的なアプリケーションはデータモデルをModelクラスを継承したクラスにプロパティを定義する。<br />
<div>
<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
from google.appengine.ext import ndb
class Account(ndb.Model):
username = ndb.StringProperty()
userid = ndb.IntegerProperty()
email = ndb.StringProperty()
]]>
</script>
上記はusername, userid,emailがAccountのプロパティとなっている。<br />
いくつか違う型のプロパティを用いているが、手軽に日付、時間を表す事が出来て、自動更新機能も付いるプロパティもある。<br />
アプリケーションはプロパティに特別な振る舞いを指定でき、それらは簡易な検証や、デフォルト値設定、インデックスの変更などができる。<br />
モデルは複合プロパティを持つ事が出来る。また、リストのようなRepeated propertiesも設定できる。構造体プロパティはオブジェクトのように、読み込み専用計算プロパティは関数により定義できる。プロパティに多方面の他のプロパティを定義することが簡単にできる。Expandoモデルは動的にプロパティを設定できる。<br />
<br />
<b><span style="font-size: large;">・プロパティの型</span></b><br />
<br />
NDBは以下のプロパティの型をサポートしている。<br />
<br />
IntegerProperty:64-bitのInteger型<br />
FloatProperty:倍精度のfoating-point numberの型<br />
BooleanProperty:Boolean値<br />
StringProperty:Unicodeの文字列で500文字以内。インデックスされる。<br />
TextProperty:Unicodeの文字列で長さに制限はないが、インデックスできない。<br />
BlobProperty:byte配列を格納する。もし500文字以内の文字列であれば、インデックスされる。インデックスしないのであれば、長さに制限はない。圧縮もできる。<br />
DateTimeProperty:日付や時間<br />
DateProperty:日付<br />
TimeProperty:時間<br />
GeoPtProperty:位置情報。これはbdb.GeoPtオブジェクトでlatとlonをどちらもfloatで持っている。ndb.GeoPt(52.34, 4.88)やndb.GeoPt("52.37, 4.88")などで生成できる。<br />
KeyProperty:DatastoreのKey、kind="カインド"を指定すれば、キーの割当にいつもカインドを示すことが必須にできる。それは文字列かModelのサブクラスであろう。<br />
BlobKeyProperty:古いdb API(BlobReferenceProperty)に対応しているがプロパティの値はBlobKeyの代わりにBlobInfoとなる。BlobInfoは使用しているBlobInfo(blobkey)から構築できる。<br />
UserProperty:ユーザーのプロパティ<br />
StructuredProperty:値により内部的に別のモデルのカインドを含む<br />
LocalStructuredProperty:StructuredPropertyに似ているが、ディスク上では不透明なblobとして表現され、インデックスはされない。圧縮可能。<br />
JsonProperty:Pythonのjsonモジュールを使ってシリアライズしたオブジェクトを設定できる。JSONシリアライズしてblobとしてdatastoreに格納される。デフォルトでインデックスはされない。圧縮可能。<br />
PickleProperty:pickleプロトコルを使ってシリアライズされたPythonのオブジェクトを設定できる。pickleシリアライズされたデータをblobとしてdatastoreへ格納する。デフォルトでインデックスはされない。圧縮可能。<br />
GenericProperty:汎用的な値で、主にExpandoクラスで使われる。ただし、明示的に使用可能な型はint,long,float,bool,str,unicode,datetime,Key,BlobKey,GeoPt,User,Noneとなっている。<br />
ComputedProperty:UDFによって他のプロパティより計算された値を設定できる。<br />
<br />
いくつかのプロパティはオプションの引数のcompressedを使用する事ができる。もし、プロパティがcompressed=Trueで設定されていた場合はデータはgzip圧縮されてディスク上に格納される。これにより格納容量は小さくできるが、エンコードとデコードにCPUを消費する。<br />
<div>
<br /></div>
<div>
<b><span style="font-size: large;">・プロパティのオプション</span></b></div>
<br />
<div>
<div>
ほとんどのプロパティの種類は、いくつかの標準的な引数をサポートしている。</div>
<div>
第一引数はDatastoreネームを指定する任意の引数。これによってカプリケーションの観点よりもデータストアに別の名前を与える事ができる。一般的な使用方法はデータストアのスペースを減らす目的で使う。データストアは短縮されたプロパティ名を使う。以下が参考。</div>
<div>
意味を持つ一文字など設定するとよい。</div>
</div>
<div>
<br /></div>
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
class Employee(ndb.Model):
full_name = ndb.StringProperty('n')
retirement_age = ndb.IntegerProperty('r')
]]>
</script>
<br />
これは特にrepeated property でEntityごとに多くの値を期待している場合<br />
に有効。<br />
さらに、ほとんどのプロパティはkeyword argumentsをサポートしている。<br />
<br />
詳細は<a href="https://developers.google.com/appengine/docs/python/ndb/properties">Argument&Type</a>を参照<br />
<br />
<b><span style="font-size: large;">・Repeated Properties</span></b><br />
<br />
どのプロパティもrepeated = Trueにするとrepeated propertyになる。<br />
そのプロパティは基クラスのリストの値になる。<br />
下記が参考。プロパティの値はIntegerProperty のリストとなる。<br />
データストアはそのようなプロパティに複数の値が表示される場合がある。独立したインデックスレコードは各値の為に作成されるこれはクエリセマンティクスに影響を与える。<br />
以下が例。<br />
<div>
<br /></div>
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
class Article(ndb.Model):
title = ndb.StringProperty()
stars = ndb.IntegerProperty()
tags = ndb.StringProperty(repeated=True)
art = Article(title='Python versus Ruby',
stars=3,
tags=['python', 'ruby'])
art.put()
]]>
</script>
以下がEntity<br />
<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
name = 'Python versus Ruby'
stars = 3
tags = 'python' # repeated = True
tags = 'ruby' # repeated = True
]]>
</script>
<br />
tagsプロパティの検索をする場合にこのEntityはpythonとrubyどちらも満たす。<br />
<br />
もし、repeated propertyを更新する場合は、新しいリストを割り当てるかその場で既存のリストを変更する事が出来る。新しいリストを割り当てた場合は即時に型チェックが行われる。例えば[1,2]をart.tagsに割り当てると例外が起こる。もし、既存のリストを更新した場合は即時に型チェックは行われない。代わりにDatastoreへ書き込むときに型チェックが行われる。<br />
データストアはrepeated propertyのリストの順番を保持する。<br />
<br />
<b><span style="font-size: large;">・日付と時間のプロパティ</span></b><br />
<br />
<div>
<div>
日付と時間は3つのプロパティの型が有効である。</div>
<div>
・DateProperty</div>
<div>
・TimeProperty</div>
<div>
・DateTimeProperty</div>
<div>
<br /></div>
<div>
これらの値はPythonのdatetimeモジュールのdata, time, datetime,クラスに対応する型である。3つの中で最も一般的なのはDateTimePropertyでカレンダーの日付と日付の時間を意味する。また時折便利な特別な使い方として、ちょうどの時間が必要な場合(例えば誕生日、ミーティングの時間)がある。技術的な理由としてDatePropertyとTimePropertyはDateTimePropertyのサブクラスである。ただこの継承関係に依存しない方がよい。また、この継承関係は基本になっているクラスdatetimeの継承関係とは違う。</div>
<div>
<br /></div>
<div>
Note:App EngineのクロックタイムはUTCで設定されている。もしPOSIX timestampsかtime tuplesに変換したデータで現在時間を使おうとする事に関係する。明白なタイムゾーンの情報をDatasotereに格納する際に与えられない。もし、ローカル時間などで現在時刻を使うときは注意が必要である。</div>
<div>
<br /></div>
<div>
それぞれのプロパティは下記のkeyword argmentを使う事ができる。</div>
<div>
auto_now_add: Entityが作られた際に自動的に現在時刻が設定される。</div>
<div>
auto_now: Entityが更新された際に自動的に現在時刻が設定される。</div>
<div>
これらのオプションはrepeated=Trueと複合することができない。どちらもデフォルト値はFalseでもし両方Trueにした場合はauto_nowが優先される。auto_now_add=Trueは上書きすることが出来るが、auto_now=Trueはできない。自動的な値はEntityが書き込まれるまで設定されない。これらのオプションはdynamic defaultsを提供していない。</div>
</div>
<div>
<br /></div>
<div>
<b><span style="font-size: large;">・Structured プロパティ</span></b></div>
<br />
<div>
<div>
構造化されたプロパティを設定する事が出来る。以下が例である。</div>
<div>
Contactモデルクラスは住所をリストで持っている。</div>
</div>
<div>
<br /></div>
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
class Address(ndb.Model):
type = ndb.StringProperty() # E.g., 'home', 'work'
street = ndb.StringProperty()
city = ndb.StringProperty()
class Contact(ndb.Model):
name = ndb.StringProperty()
addresses = ndb.StructuredProperty(Address, repeated=True)
guido = Contact(name='Guido',
addresses=[Address(type='home',
city='Amsterdam'),
Address(type='work',
street='Spear St',
city='SF')])
guido.put()
]]>
</script>
以下のような一つのEntityが作成される。<br />
<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
name = 'Guido'
addresses.type = 'home'
addresses.type = 'work'
addresses.street = None
addresses.street = 'Spear St'
addresses.city = 'Amsterdam'
addresses.city = 'SF'
]]>
</script>
<br />
Entityを読み直すとContactエンティティを正確に再構築する。しかし、Adressインスタンスはモデルクラスと同じ構文を使用して定義されているが、Entityではない。これらはKeyを持っていない。Contactエンティティから独立して取得することができない。個々のフィールドへのクエリーとしてならアプリケーションで可能である。(Structured プロパティへのフィルタリングを参照)adress.type, address.streedとaddress.cityはDatastoreの観点では並行だが、NDBはこの側面を隠している。NDBでは関連するAdressインスタンスのリストとして構築している。<br />
プロパティオプションを指定する事も出来る(indexedなど)この場合は、第二引数にDatastoreの名前を指定している。<br />
もしStruectureプロパティへのクエリが必要ない場合はLocalStructuredPropertyを代わりに使用する事が出来る。Pythonコードとしては同じであるが、データストアは各AdressのBlobを見ている。例で作成したEntityは以下のようになる。<br />
<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
name = 'Guido'
address = <opaque blob for {'type': 'home', 'city': 'Amsterdam'}>
address = <opaque blob for {'type': 'work', 'city': 'SF',
'street': 'Spear St'}>
]]>
</script>
Entityは正しく読み戻される。このタイプのプロパティは常にインデックスは無いので、Adressをクエリで検索することは出来ない。<br />
<br />
<b><span style="font-size: large;">・Computed プロパティ</span></b><br />
<br />
Computedプロパティは読み込み専用で、アプリケーションが提供する関数で計算した結果を設定することが出来る。計算された値はクエリとDatastoreビュアーの為に書き込まれるが、格納された値はDatastoreから読み戻された際は無視される。値は、関数が呼びだされた際に再計算される。以下が例。<br />
<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
class SomeEntity(ndb.Model):
name = ndb.StringProperty()
name_lower = ndb.ComputedProperty(lambda self: self.name.lower())
x = SomeEntity(name='Nick')
]]>
</script>
格納されたEntityのプロパティの値は以下のとおり<br />
<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
name = 'Nick'
name_lower = 'nick'
]]>
</script>
もしnameをNickieに変更した場合にname_lowerはnickieを返す。<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
x.name = 'Nick'
assert x.name_lower == 'nick'
x.name = 'Nickie'
assert x.name_lower == 'nickie'
]]>
</script>
<br />
Note:もしクエリーで計算した値を使いたい場合は、ComputedPropertyを使う。もし派生バージョンのPythonコードを使いたい場合はregular methodを定義するか@propertyを使うとよい。<br />
<br />
<b><span style="font-size: large;">・ProtoRPC Message プロパティ</span></b><br />
<br />
ProtoRPC API は 構造データの為にMessageオブジェクトを使用する。これらはRPCリクエスト、レスポンス、等を表現できる。NDBはMessageオブジェクトをEntityのプロパティとして定義できる。Messageサブクラスを定義すると仮定すると、<br />
<div style="font-weight: bold;">
<br /></div>
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
from protorpc import messages
class Note(messages.Message):
text = messages.StringField(1, required=True)
when = messages.IntegerField(2)
]]>
</script>
NDBのmsgprop APIを使用してDatastoreへEntityのプロパティとして格納できる。<br />
<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
from google.appengine.ext import ndb
from google.appengine.ext.ndb import msgprop
class NoteStore(ndb.Model):
note = msgprop.MessageProperty(Note, indexed_fields=['when'])
name = ndb.StringProperty()
my_note = Note(text='Excellent note', when=50)
ns = NoteStore(note=my_note, name='excellent')
key = ns.put()
new_notes = NoteStore.query(NoteStore.note.when >= 123).fetch()
]]>
</script>
<br />
もしクエリで検索したい場合はインデックスする必要がある。MessagePropertyへのindexed_fieldsを指定する事が出来る。<br />
MessagePropertyは通常のプロパティオプションの全てはサポートしていない。<br />
以下がサポートしているオプション<br />
・name<br />
・repeated<br />
・required<br />
・default<br />
・choices<br />
・validator<br />
・verbose_name<br />
<br />
Message プロパティはそれ自体にindexを使う事は出来ない。(フィールド名をしていすることで、indexできる)<br />
<br />
ネストしたmessageも使用できる。<br />
<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
class NoteBook(messages.Message):
notes = messages.MessageField(Note, 1, repeated=True)
class SignedStoreableNotebook(ndb.Model):
author = ndb.StringProperty()
nb = msgprop.MessageProperty(NoteBook,
indexed_fields=['notes.text', 'notes.when'])
]]>
</script>
<br />
MessagePropertyは特別なオプションprotocolを指定できる。これはdatastoreへmessageオブジェクトをどのようにシリアライズして格納するかを指定できる。protocolの値はprotorpc.remote.Protocolsクラスの名前を使用できる。サポートするprotocolはprotobufとprotojsonでデフォルトはprotobuf<br />
msgpropはEnumPropertyも定義できる。このプロパティはprotorpc.messages.Enumの値をエンティティとして格納できる。以下が例。<br />
<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
class Color(messages.Enum):
RED = 620
GREEN = 495
BLUE = 450
class Part(ndb.Model):
name = ndb.StringProperty()
color = msgprop.EnumProperty(Color, required=True)
p1 = Part(name='foo', color=Color.RED)
print p1.color # prints "RED"
]]>
</script>
<br />
EnumPropertyはintegerとして格納される。事実としてEnumPropertyはIntegerPropertyのサブクラスである。また、既に格納されているEnumの名前を変更することは出来るが、再度採番することはできない。<br />
<br />
EnumPropertyは以下のオプションをサポートしている。<br />
・name<br />
・indexed<br />
・repeated<br />
・required<br />
・default<br />
・choices<br />
・validator<br />
・verbose_name<br />
<br />
今回はプロパティについて紹介した。<br />
構造化されたプロパティをそのまま格納できるのは便利だと思った。<br />
実は実際に使った事がないので、近いうちに使ってみたい。<br />
次回は、プロパティのサブクラスについて。</div>
cloudysunny14http://www.blogger.com/profile/09953267600910932223noreply@blogger.com0tag:blogger.com,1999:blog-8436410939148734220.post-18497357989246204182012-10-14T18:46:00.000+09:002012-10-14T23:56:31.345+09:00Google App Engine Python NDB を使ってみた。(2)<h3>
【NDBエンティティとキー】</h3>
<div>
<div>
Datastoreに格納されているオブジェクト(エンティティ)はndb.Modelのインスタンスで、アプリケーションではndb.Modelのサブクラスとして定義する。</div>
<div>
エンティティはkeyで識別され、アプリケーションのDatastore内でユニークとなる。</div>
</div>
<div>
<br /></div>
<div>
<h3>
・概要</h3>
<div>
<br /></div>
<div>
以下のようなエンティティクラスを定義する。</div>
</div>
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
from google.appengine.ext import ndb
class Account(ndb.Model):
username = ndb.StringProperty()
userid = ndb.IntegerProperty()
email = ndb.StringProperty()
]]>
</script>
それぞれのエンティティはアプリケーション内のDatastoreで一意のキーで識別される。<br />
キーがカインドと識別子を構成しているのが最も簡単なフォームである。<br />
普通はカインドの名前はモデルクラス名と同じにする。上記の例ではAccount<br />
だが、method _get_kind()を上書きすることで、名前を変える事も出来る。<br />
識別子は、アプリケーションまたはデータストアで自動的に生成された整数の数字IDか割り当てられたいずれかのキー名の文字列である。<br />
エンティティのキーは他のキーを親のキーとして示す事が出来る。これを”エンティティのキーの親”と呼ぶ。よくエンティティーの親とも呼ばれる。<br />
コンテキストに依存することはエンティティのキーの親を意味する事か、エンティティはキーのキーを持っている事になる。ルートのエンティティーを持っていないエンティティはそれは親のエンティティで再起的にそれは先祖である。<br />
エンティティはデータストアにいるが、階層の構成はファイルシステムの階層と同様である。エンティティの連続処理はルートのエンティティから始まって、親から子へと続く。<br />
特定のエンティティにつながる、そのエンティティの祖先パスを構成している。<br />
<br />
完全な一意キーは、つまり連続するカインド識別子を自身のエンティティーまで指定したその先祖キーである。<br />
Keyクラスのコンストラクターは連続するカインドと識別子を指定すすことを許可している。その返却値のキーがそのエンティティに対応するキーとなる。<br />
下記の例はメッセージのリビジョンを示すもので、rev_keyが所有者に所属している事になる。<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
rev_key = ndb.Key('Account', 'Sandy', 'Message', 'greeting', 'Revision', '2')
]]>
</script>
上記例で注意が必要なのがリストの最後で2という数字を使っているがこれは特殊で数字キーを使うことはできるが、これは少しトリッキーになる。詳細は数値キーを使うを参照。<br />
<br />
ルートエンティティ用としては下記のよう先祖パスは空で、自身のカインドと識別子のみで構成されている。<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
sandy_key = ndb.Key('Account', 'Sandy')
]]>
</script>
代わり以下のようにモデルクラスを直接代入できる。<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
sandy_key = ndb.Key(Account, 'Sandy')
]]>
</script>
下記例では指定しているキーはすべて等価である。<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
k1 = ndb.Key('Account', 'Sandy', 'Message', 'greetings', 'Revision', '2')
k2 = ndb.Key(Revision, '2', parent=ndb.Key('Account', 'Sandy', 'Message', 'greetings'))
k3 = ndb.Key(Revision, '2', parent=ndb.Key(Account, 'Sandy', Message, 'greetings'))
]]>
</script>
<br />
<h3>
</h3>
<h3>
・エンティティの作成</h3>
<br />
モデルクラスのコンストラクターを呼び出すことでエンティティを作成する事が出来る。<br />
プロパティの設定はkeyword argumentsで指定する。<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
sandy = Account(username='Sandy',
userid=123,
email='sandy@gmail.com')
]]>
</script>
ここで作成したオブジェクトはput()メソッドを呼びだす事によりデータストアへ格納さる。返却値はkeyとなっている。<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
sandy_key = sandy.put()
]]>
</script>
代わりに以下のようにプロパティを直接指定できる。<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
sandy = Account()
sandy.username = 'Sandy'
sandy.userid = 123
sandy.email = 'sandy@gmail.com'
]]>
</script>
以下のようにpopulate()を使うこともできる。<br />
<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
sandy = Account()
sandy.populate(username='Sandy',
userid=123,
email='sandy@gmail.com')
]]>
</script>
また、プロパティのタイプはいろいろなタイプが使えるがタイプチェックが行われる。<br />
下記の例だと、StringPropertyとIntegerPropertyである。<br />
<h3>
</h3>
<h3>
<br /></h3>
<h3>
・キーからエンティティの取得</h3>
<br />
エンティティーのキーを与えれば、データストアからエンティティを取得できる。<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
sandy = sandy_key.get()
]]>
</script>
キーのメソッドであるkind()とidはそれぞれカインドと識別子を返却する。<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
kindString = rev_key.kind() # returns "Revision"
ident = rev_key.id() # returns "2"
]]>
</script>
parent()メソッドはキーの親のエンティティを返却する。<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
greeting_key = rev_key.parent()
]]>
</script>
また、URLに埋め込むためにキーをエンコードした形で取得できる。<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
urlString = rev_key.urlsafe()
]]>
</script>
この生成方法ではagVoZWxsb3IPCxIHQWNjb3VudBiZiwIMのようなキーを返却するが、そこからエンティティも取得できる。<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
rev_key = ndb.Key(urlsafe=urlString)
revision = rev_key.get()
]]>
</script>
<br />
注意:URL-safeは暗号化されない。以下のように簡単に複合化できてしまうので注意。<br />
なので、e-mailアドレスとかは暗号化したもので使用する。<br />
<h3>
</h3>
<h3>
<br /></h3>
<h3>
・エンティティの更新</h3>
<br />
エンティティの更新はデータストアから取得したエンティティの編集を行い、データストアに戻す。<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
sandy = key.get()
sandy.email = 'sandy@gmail.co.uk'
sandy.put()
]]>
</script>
この場合のput()の返却値は同じなので無視してよい。<br />
<h3>
</h3>
<h3>
<br /></h3>
<h3>
・エンティティの削除</h3>
<br />
エンティティが必要なくなったらデータストアから削除する。keyのdelete()メソッドで<br />
削除できる。<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
sandy.key.delete()
]]>
</script>
このメソッドは常にNoneを返却する。<br />
<h3>
</h3>
<h3>
<br /></h3>
<h3>
・複数のキー、エンティティを処理する。</h3>
<br />
get(),put()はRPC呼び出しを行っているので、ループを使って処理するしかなく非効率だったが、下記のメソッドで速くできる。<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
list_of_keys = ndb.put_multi(list_of_entities)
list_of_entities = ndb.get_multi(list_of_keys)
ndb.delete_multi(list_of_keys)
]]>
</script>
<br />
<h3>
<br /></h3>
<h3>
【Expandoモデル】</h3>
<br />
時々事前にプロパティを指定したくないときがある。その場合に特別なクラスExpandoを使う。Expandoは、割り当てられた任意の属性(限り、それはアンダースコアで始まらないように)がデータストアに保存されるように、そのエンティティの動作を変更する。たとえば、以下のように。<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
class Mine(ndb.Expando):
pass
e = Mine()
e.foo = 1
e.bar = 'blah'
e.tags = ['exp', 'and', 'oh']
e.put()
]]>
</script>
このデータストアへの書き込みはfooプロパティはInteger値で1barプロパティはString値'blah'でtagsプロパティはString値の繰り返しで'exp','and','oh'となっている。プロパティはインデックスされそれを_propertiesで参照することが出来る。<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
print e._properties
{'foo': GenericProperty('foo'), 'bar': GenericProperty('bar'),
'tags': GenericProperty('tags', repeated=True)}
]]>
</script>
データストアから値を取得する事によって作成されたExpandoはデータストアに保存されたすべてのプロパティーとプロパティー値を持っている。<br />
アプリケーションはExpandoのサブクラスとして定義して前もってプロパティを持つ事もできる。<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
class FlexEmployee(ndb.Expando):
name = ndb.StringProperty()
age = ndb.IntegerProperty()
e = FlexEmployee(name='Sandy', location='SF')
]]>
</script>
上記の例ではnameプロパティはSandyでageはNone、動的プロパティlocationは'SF'となる。<br />
Expandoのサブクラスに_default_indexed = Falseを指定することでインデックスから外すことができる。<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
class Specialized(ndb.Expando):
_default_indexed = False
e = Specialized(foo='a', bar=['b'])
print e._properties
{'foo': GenericProperty('foo', indexed=False),
'bar': GenericProperty('bar', indexed=False, repeated=True)}
]]>
</script>
_default_indexedをExpandoエンティティにセットすることができる。この場合は事後に指定したプロパティすべてに適用される。<br />
他の便利なテクニックとしてはクエリに動的プロパティを使う事ができる。<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
FlexEmployee.query(FlexEmployee.location == 'SF')
]]>
</script>
ただ、プロパティを持っていない場合もあるので、下記のように指定する。<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
FlexEmployee.query(ndb.GenericProperty('location') == 'SF')
]]>
</script>
<br />
<h3>
</h3>
<h3>
<br /></h3>
<h3>
【モデルのフック】</h3>
<br />
NDBは軽量なフック機構を提供している。フックを利用することで、形式の処理を実現する事ができる。例としてはModelはget()のあとで様々な処理をするが、それを同期的にまたは非同期、複数など特別なメソッドを作れる。以下の例では、フックで様々な取得方法を提供している。<br />
フックは下記で便利<br />
・クエリーのキャッシュ<br />
・ユーザー毎のデータストア監視<br />
・データストアへのトリガー<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
from google.appengine.ext import ndb
class Friend(ndb.Model):
name = ndb.StringProperty()
def _pre_put_hook(self):
# inform someone they have new friend
@classmethod
def _post_delete_hook(cls, key, future):
# inform someone they have lost a friend
f = Friend()
f.name = 'Carole King'
f.put() # _pre_put_hook is called
fut = f.key.delete_async() # _post_delete_hook not yet called
fut.get_result() # _post_delete_hook is called
]]>
</script>
もし非同期APIをフックとして使う場合はcheck_result()かget_result()かTaskletによるyieldingメソッドを呼びだすことがトリガーになる。<br />
事後フックはRPCが成功していることは確認しない。失敗しても実行される。<br />
すべての事後フックはFuture属性を持つ。このFutureオブジェクトは動作の結果を保持している。get_result()メソッドを呼びだせば、結果を取得できる。フックが呼び出された時点でFutureは完了しているので、get_result()でブロックされない事を確認できる。<br />
事前フックで例外が発生すると、その場所をとってから、要求を防ぐことになる。<br />
フックは_asyncメソッド内でトリガされるが、事前にRPCフックでtasklets.Returnを上げることによって、RPCを先取りすることはできない。<br />
<h3>
</h3>
<h3>
<br /></h3>
<h3>
【数値キーを使う】</h3>
<br />
キーはカインドとIDの直列だが、アプリケーションとネームスペースないで、一意のキーを持っているか確認したくなる。アプリケーションがID指定なしでエンティティを作成すると、自動で数値キーが割り当てられる。アプリケーションが手動でID(数値の)を取り出すと、データストアは自動でIDを生成するが、既に使用されているIDを選択する場合がある。<br />
回避するためには、アプリケーションが予約した数値の範囲を使用するようにするとよい。<br />
(数値IDを使わなければ問題ない)<br />
下記のように予約IDの範囲を指定できる。下記は100個のIDを割り当てている。<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
first, last = MyModel.allocate_ids(100)
]]>
</script>
親キーと一緒に指定もできる。<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
first, last = MyModel.allocate_ids(100, parent=p)
]]>
</script>
キーの取り出し方は下記の通りで、first, lastの範囲でキーが割り当てられている。<br />
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
keys = [Key(MyModel, id) for id in range(first, last+1)]
]]>
</script>
これらのキーはデータストアの内部的なID生成で既に割り当てられていないIDである事は保証していない。また将来的に生成されるIDも同様である。
しかし、allocate_ids()が返すIDは、データストア内で存在するかどうかは確認しない。
代わりに下記のように最大値からID割り当てることもできる。
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
first, last = MyModel.allocate_ids(max=N)
]]>
</script>
このフォームはN以下のIDを確実に返す。返却されるfirstとlastは予約されたIDの範囲を示す。
アプリケーションはトランザクション内でallocate_ids()を呼ぶことができない。
まあ、数値IDはあんまり使わない方が良さそう(感想)
<br />
次回は、プロパティについて。<br />
<br />cloudysunny14http://www.blogger.com/profile/09953267600910932223noreply@blogger.com0tag:blogger.com,1999:blog-8436410939148734220.post-55049683971782494102012-10-01T14:25:00.001+09:002012-10-14T23:56:31.347+09:00Google App Engine Python NDB を使ってみた。(1)<br />
今回は何回かにわたって<a href="https://developers.google.com/appengine/">GoogleAppEngine</a>の<a href="http://www.blogger.com/"><span id="goog_1251910494"></span>NDB<span id="goog_1251910495"></span></a>という<br />
<a href="https://developers.google.com/appengine/docs/python/datastore/overview">Datastore</a>のAPIを紹介する。<br />
というかリファレンスに載っているExampleとともに<br />
自分の理解のために使ってみようと思う。<br />
<br />
<h3>
Python NDB の概要</h3>
<br />
<a href="https://developers.google.com/appengine/docs/python/ndb/">NDB API</a>は通常の<a href="https://developers.google.com/appengine/docs/python/datastore/overview">Datastore</a>のアクセスモジュールと使い方はほとんど変わらないが、<br />
いくつかの便利な機能が追加されている。<br />
自動でキャッシュを使ったり、クエリーやトランザクションなど構造化された<br />
データレコードを格納する事に適している。<br />
<br />
<h3>
NDBの基本的な使い方</h3>
<div>
<br /></div>
<div>
<div>
<b>●始めに</b></div>
<div>
<a href="http://www.blogger.com/">NDB</a>は通常の<a href="https://developers.google.com/appengine/docs/python/datastore/overview">Datastore</a>のオブジェクト同様に、一つまたは複数のプロパティーを持つエンティティーとしてデータを格納する。</div>
<div>
<a href="http://www.blogger.com/">NDB</a>は一つのトランザクションで複数の処理をまとめることが出来る。</div>
<div>
もし処理が失敗したらロールバックされる。</div>
<div>
これらは複数のユーザーが同時にアクセスしたり操作したりできるとても有用なものである。</div>
<div>
<a href="http://www.blogger.com/">NDB</a>は<a href="https://developers.google.com/appengine/docs/python/memcache/usingmemcache">Memcache</a>サービスを使用してキャッシュを行う。</div>
<div>
これは頻繁に同じエンティティーにアクセスする際に有用であり、高速で処理が行える。</div>
<div>
<a href="http://www.blogger.com/">NDB</a>も通常の<a href="https://developers.google.com/appengine/docs/python/datastore/overview">Datastore</a>と同様modelを定義する。</div>
<div>
modelとはデータベースのスキーマのようなものである。</div>
<div>
基礎となる<a href="https://developers.google.com/appengine/docs/python/datastore/overview">Datastore</a>がこれらのデータオブジェクトを格納する方法は非常に柔軟である。</div>
<div>
例えば、二つの異なるプロパティを持つエンティティーを同じカインドに格納する事ができる。<a href="http://www.blogger.com/">NDB</a>は型のチェックを行うが、それは必須ではない。</div>
<div>
それぞれのエンティティはキーを持っていてそれらはアプリケーション内で一意である。</div>
<div>
キーは親を持つ事ができて親子関係を作る事ができる。親の無いキーはルートと呼ぶ。</div>
<div>
キーが同じルートを持つエンティティはエンティティグループまたはグループを形成することができる。</div>
<div>
エンティティが別のグループに属している場合は、</div>
<div>
これらのエンティティへの変更は、しばしば "順不同"が発生するように見えるかもしれない。</div>
<div>
エンティティは、アプリケーションのセマンティクスとは無関係である場合、それは問題が、</div>
<div>
それらを作成するときに、いくつかのエンティティの変更が一貫していなければならない場合、それらを同じグループの一部にする必要がある。</div>
<div>
<br /></div>
<div>
<b>テストコードで実際に動作させてみた。</b></div>
<br /></div>
<script class="brush:python; gutter:false;" title="ndb_demo.py" type="syntaxhighlighter">
<![CDATA[
from google.appengine.ext import ndb
class Greeting(ndb.Model):
"""Models an individual Guestbook entry with content and date."""
content = ndb.StringProperty()
date = ndb.DateTimeProperty(auto_now_add=True)
@classmethod
def query_book(cls, ancestor_key):
return cls.query(ancestor=ancestor_key).order(-cls.date)
]]>
</script>
<script class="brush:python; gutter:false;" title="ndb_test.py" type="syntaxhighlighter">
<![CDATA[
class NDBTest(unittest.TestCase):
"""Test for ndb example"""
def setUp(self):
unittest.TestCase.setUp(self)
self.appid = "testapp"
self.version_id = "1.23456789"
os.environ["APPLICATION_ID"] = self.appid
os.environ["CURRENT_VERSION_ID"] = self.version_id
os.environ["HTTP_HOST"] = "localhost"
self.memcache = memcache_stub.MemcacheServiceStub()
self.datastore = datastore_file_stub.DatastoreFileStub(
self.appid, "/dev/null", "/dev/null")
self.blob_storage_directory = tempfile.mkdtemp()
blob_storage = file_blob_storage.FileBlobStorage(
self.blob_storage_directory, self.appid)
self.blobstore_stub = blobstore_stub.BlobstoreServiceStub(blob_storage)
apiproxy_stub_map.apiproxy.RegisterStub("memcache", self.memcache)
apiproxy_stub_map.apiproxy.RegisterStub("datastore_v3", self.datastore)
apiproxy_stub_map.apiproxy.RegisterStub("blobstore", self.blobstore_stub)
if __name__ == "__main__":
unittest.main()
]]>
</script><b>
・データの格納と抽出</b><br />
<br />
<script class="brush:python; gutter:false;" title="ndb_test.py" type="syntaxhighlighter">
<![CDATA[
def testStoringData(self):
greeting = Greeting(parent=ndb.Key("Book", "GuestBook"),
content = "content")
greeting.put()
#fetchFromDataStore
ancestor_key = ndb.Key("Book", "GuestBook")
greetings = Greeting.query_book(ancestor_key).fetch(20)
for greeting in greetings:
self.assertTrue("content", greeting.content)
print(greeting.content)
]]>
</script>
<br />
ここでは、カインドBookに一件エンティティ格納している。<br />
格納した後にFetchを行って、抽出されたエンティティの検証を行っている。<br />
Greetingオブジェクトのputメソッドを呼び出して格納している。<br />
新たにGreetingオブジェクトを格納する場合は、すべて同じBookのエンティティグループ<br />
となる。(親が同じ)すなわち、先祖クエリを使用している。<br />
次にエンティティの抽出を行っている。<br />
一般的に<a href="http://www.blogger.com/">NDB</a>のクエリーはカインドでエンティティーをフィルタする。<br />
このサンプルではGreetingエンティティクラスにquery_bookというクラスメソッドを定義することにより、エンティティを返却する為のクエリーを生成している。<br />
上記例では親のキーを指定することにより抽出するクエリーを実行している。<br />
すべてのクエリは、インデックス、希望の順番でクエリの結果を含むテーブルを使用している。基礎となる<a href="https://developers.google.com/appengine/docs/python/datastore/overview">Datastore</a>は、自動的にシンプルなインデックス(1つのプロパティのみを使用したインデックス)を維持する。<br />
またこれらはindex.yamlで定義したインデックスも使用できる。<br />
<br />
<b>実行結果</b><br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
$ python ndb_test.py
content
.
----------------------------------------------------------------------
Ran 1 test in 0.038s
OK
]]>
</script>
<br />
<b>・<a href="http://www.blogger.com/">NDB</a>のデータ書き込みについて</b><br />
コミット段階では、基礎となる<a href="https://developers.google.com/appengine/docs/python/datastore/overview">Datastore</a>サービスでは、変更をコミットする。<br />
<a href="http://www.blogger.com/">NDB</a>は影響を受けるエンティティ/エンティティのそのキャッシュを無効にする。<br />
したがって将来の(およびキャッシュ)からではなく、キャッシュから古い値を読んでの基礎となるデータストアを読み込む。<br />
そして基礎となるデータストアは、変更を適用する。それがグローバルクエリには表示され、最終的には一貫性の変化を読み取ることができる。(結果整合性)<br />
<br />
データ(例えば、put()の)書き込みのNDB関数はキャッシュ無効化した後に返し、適用は非同期で行われる。<br />
<br />
Commitフェーズ中に障害が発生した場合、自動再試行がありますが、障害が引き続き発生する場合は、アプリケーションが例外を受け取る。<br />
コミット段階が成功して適用に障害が発生した場合、次のいずれかが発生したときに、適用フェーズは最後までロールフォワードされる。<br />
<br />
・データストアは適用が不完全に終わったジョブを継続的に一掃し、エンティティへの変更をまだ受け取っていないインデックスとエンティティに対して、書き込みをロール フォワードします。<br />
・次回このエンティティ グループでトランザクションを書き込むか、トランザクションを開始する際に、データストアは最初にロール フォワードを行い、ログ内のデータに基づいて、このコミット済みで未適用の書き込みの適用を完了します。<br />
<br />
このような振る舞いはアプリケーションにどのように影響するかというと、<br />
変化は完全に数百ミリ秒かそこらの<a href="http://www.blogger.com/">NDB</a>関数が戻った後に、基礎となるデータストアに適用されない場合がある。(結果整合性)<br />
つまり変更が適用されている間にクエリを発行すると矛盾した状態が表示される場合がありる。書き込みのタイミングとクエリの詳細については、アプリケーションEngine.readsのトランザクション分離を参照とのこと。<br />
<br />
<b>●Django</b><br />
Djangoでも<a href="http://www.blogger.com/">NDB</a>は使える。<br />
<br />
<b>●QuotaとLimits</b><br />
最大エンティティーサイズ:1 megabyte<br />
最大トランザクションサイズ:10 megabytes<br />
1つのエンティティへの最大インデックス数:20000<br />
1つのエンティティへの複合インデックス数:2 megabytes<br />
<br />
今回は、<a href="http://www.blogger.com/">NDB</a>の概要について紹介してみた。<br />
次回は<a href="http://www.blogger.com/">NDB</a>のエンティティとKeyについて紹介する予定。cloudysunny14http://www.blogger.com/profile/09953267600910932223noreply@blogger.com0tag:blogger.com,1999:blog-8436410939148734220.post-58206125695922722472012-09-09T18:32:00.002+09:002012-09-09T18:42:03.636+09:00GoogleAppEngineのデータをGoogleBigQueryで扱ってみる今回は再び<a href="https://developers.google.com/appengine/">GoogleAppEngine</a>。<br />
<br />
今年の五月に正式版がリリースされたばかりのサービス<a href="https://developers.google.com/bigquery/">BigQuery</a>と<a href="https://developers.google.com/appengine/">GoogleAppEngine</a>を連携させてみるのが今回のテーマ。<br />
まず、今年のGoogle I/Oで発表された「<a href="https://developers.google.com/bigquery/articles/datastoretobigquery#definepipeline">Building data pipelines at Google Scale</a>」を紹介する。<br />
<br />
このデモンストレーションは<a href="https://developers.google.com/appengine/">GoogleAppEngine</a>のDataStoreのデータをCSVに変換し、<a href="https://developers.google.com/storage/">GoogleCloudStorage</a>にファイル出力する。そのデータを<a href="https://developers.google.com/bigquery/">GoogleBigQuery</a>の解析に使う。<br />
という流れになっている。<br />
<br />
<h4>
【手順】</h4>
1.プロジェクト作成<br />
・まず必要な準備は<a href="https://developers.google.com/bigquery/docs/overview">GoogleBigQuery API</a>と<a href="https://developers.google.com/storage/">GoogleCloudStorage</a>を利用可能な状態にする必要がある。これは<a href="https://code.google.com/apis/console/">Google APIs Console</a>から可能だ。<br />
・TeamよりAccountSettingを行って、<a href="https://developers.google.com/appengine/">GoogleAppEngine</a>からアクセスできるようにしよう。<br />
・<a href="https://developers.google.com/bigquery/">GoogleBigQuery</a>のプロジェクトIDを覚えておこう。#project:以降の数字部分(https://code.google.com/apis/console/?pli=1#project:111111:bigquery)<br />
<br />
2.テストデータの準備<br />
・<a href="https://developers.google.com/storage/">GoogleCloudStorage</a>にbucketを作成しよう。ここではdatastore_csvoutputという名前で作成。<br />
・<a href="https://developers.google.com/bigquery/docs/browser_tool">BigQueryWebUI</a>よりdatasetの作成も行おう。ここではdatasetという名前で作成。<br />
<br />
3.<a href="http://code.google.com/p/appengine-mapreduce/">MapReduce</a>アプリケーションを作成<br />
・今回のプロジェクトは<a href="http://code.google.com/p/appengine-mapreduce/">MapReduce</a>フレームワークを使用して、データの作成を行う。<br />
・<a href="http://code.google.com/p/appengine-mapreduce/">MapReduce</a>のインプットデータとなるProductSalesDataのプロパティーは以下のとおりとなる。<br />
<script class="brush:python; gutter:false;" title="main.py" type="syntaxhighlighter">
<![CDATA[
class ProductSalesData(db.Model):
product_id = db.IntegerProperty(required=True)
date = db.DateTimeProperty(verbose_name=None,
auto_now=True,
auto_now_add=True)
store = db.StringProperty(required=True)
]]>
</script>
・モックデータの作成
<script class="brush:python; gutter:false;" title="main.py" type="syntaxhighlighter">
<![CDATA[
class AddDataHandler(webapp.RequestHandler):
def get(self):
for i in range(0,9):
data = ProductSalesData(product_id=i,
store='Store %s' % str(i))
self.response.out.write('Added sample Datastore entity #%s<b>' % str(i))
data.put()
self.response.out.write('<a href="/start">Click here</a> to start the Datastore to BigQuery pipeline.')
]]>
</script>
・各種プロパティの設定
<script class="brush:python; gutter:false;" title="main.py" type="syntaxhighlighter">
<![CDATA[
SCOPE = 'https://www.googleapis.com/auth/bigquery'
PROJECT_ID = 'XXXXXXXXXX' # Your Project ID here
BQ_DATASET_ID = 'datastore_data'
GS_BUCKET = 'datastore_csvoutput'
ENTITY_KIND = 'main.ProductSalesData'
]]>
</script>
・MapperPipelineを作成<br />
<script class="brush:python; gutter:false;" title="main.py" type="syntaxhighlighter">
<![CDATA[
class DatastoreMapperPipeline(base_handler.PipelineBase):
def run(self, entity_type):
output = yield mapreduce_pipeline.MapperPipeline(
"Datastore Mapper %s" % entity_type,
"main.datastore_map",
"mapreduce.input_readers.DatastoreInputReader",
output_writer_spec="mapreduce.output_writers.FileOutputWriter",
params={
"input_reader":{
"entity_kind": entity_type,
},
"output_writer":{
"filesystem": "gs",
"gs_bucket_name": GS_BUCKET,
"output_sharding":"none",
}
},
shards=10)
yield CloudStorageToBigQuery(output)
]]>
</script>
MapperPIpelineのパラメータの説明をすると、<br />
第一パラメータはpipelineの名前、第二パラメータはmap関数、次の2つのパラメータはinput_readerとoutput_readerリーダーの指定、input_readerとoutput_readerにはbucket名やACLなどのパラメータを設定することができる。最後にshard数。<br />
<br />
続いてMap関数について<br />
<script class="brush:python; gutter:false;" title="main.py" type="syntaxhighlighter">
<![CDATA[
def datastore_map(entity_type):
data = db.to_dict(entity_type)
resultlist = [data.get('product_id'),
timestamp_to_posix(data.get('date')),
data.get('store')]
result = ','.join(['"%s"' % field for field in resultlist])
yield("%s\n" % result)
]]>
</script>
ここでは二つの事を行っている。一つはCSVフォーマットに整形しているのと、もう一つは<a href="https://developers.google.com/bigquery/">GoogleBigQuery</a>のdatasetとして扱う為にDateTimeのtimestampをPOSIXへと変換している。<br />
<br />
・<a href="https://developers.google.com/bigquery/docs/overview">GoogleBigQuery</a>へデータを格納<br />
<a href="https://developers.google.com/bigquery/docs/overview">GoogleBigQuery</a>のdatasetへと登録するpipeline処理は下記の通りとなっている。<br />
<a href="https://developers.google.com/bigquery/docs/overview">GoogleBigQuery</a>サービスのAPIを使用してdatasetを格納する。その際jobの定義を行うJSONオブジェクトが必要となる。<br />
<script class="brush:python; gutter:false;" title="main.py" type="syntaxhighlighter">
<![CDATA[
def build_job_data(table_name, files):
return {"projectId": PROJECT_ID,
"configuration":{
"load": {
"sourceUris": files,
"schema":{
"fields":[
{
"name":"product_id",
"type":"INTEGER",
},
{
"name":"date",
"type":"INTEGER",
},
{
"name":"store",
"type":"STRING",
}
]
},
"destinationTable":{
"projectId": PROJECT_ID,
"datasetId": DATASET_ID,
"tableId": table_name,
},
"maxBadRecords": 0,
}
}
}
]]>
</script>
以上で準備は完了。<br />
・デプロイ<br />
下記のようなハンドラーを作成する。<br />
<script class="brush:python; gutter:false;" title="main.py" type="syntaxhighlighter">
<![CDATA[
class DatastoretoBigQueryStart(webapp.RequestHandler):
def get(self):
pipeline = DatastoreMapperPipeline(ENTITY_KIND)
pipeline.start()
path = pipeline.base_path + "/status?root=" + pipeline.pipeline_id
self.redirect(path)
application = webapp.WSGIApplication(
[('/start', DatastoretoBigQueryStart),
('/add_data', AddDataHandler)],
debug=True)
]]>
</script>
<br />
<h4>
【実行】</h4>
・モックデータをDatastoreへ格納、pipeline処理実行<br />
http://your-app.appspot.com/add-dataにアクセスするとデータが格納される。<br />
続いて、pipelineを実行する。<br />
http://your-app.appspot.com/startにアクセスするとpipeline処理が実行される。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://1.bp.blogspot.com/-CoAoKEPi_xI/UExbUHJNhJI/AAAAAAAAAFs/WaAvBSuhORQ/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-09-09+16.39.15%EF%BC%89.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="73" src="http://1.bp.blogspot.com/-CoAoKEPi_xI/UExbUHJNhJI/AAAAAAAAAFs/WaAvBSuhORQ/s200/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-09-09+16.39.15%EF%BC%89.png" width="200" /></a></div>
<br />
<br />
<br />
<br />
<br />
<br />
・<a href="https://developers.google.com/storage/">GoogleCloudStorage</a>に格納されたCSVファイル<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://3.bp.blogspot.com/-XI_Aus-Ys5A/UExcUOXlM-I/AAAAAAAAAF0/jh9X7s8QfMA/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-09-09+16.40.05%EF%BC%89.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-XI_Aus-Ys5A/UExcUOXlM-I/AAAAAAAAAF0/jh9X7s8QfMA/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-09-09+16.40.05%EF%BC%89.png" /></a></div>
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
このようなCSVファイルが確認できる。<br />
<br />
・<a href="https://developers.google.com/bigquery/docs/browser_tool">BigQueryWebUI</a>よりQueryを実行<br />
<br />
<a href="https://developers.google.com/bigquery/docs/browser_tool">BigQueryWebUI</a>でインポートされたデータが確認できる。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://1.bp.blogspot.com/-LH67FfOlWRU/UExc7PIEOPI/AAAAAAAAAF8/030fL4JFBSY/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-09-09+16.40.34%EF%BC%89.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="60" src="http://1.bp.blogspot.com/-LH67FfOlWRU/UExc7PIEOPI/AAAAAAAAAF8/030fL4JFBSY/s200/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-09-09+16.40.34%EF%BC%89.png" width="200" /></a></div>
<br />
<br />
<br />
<br />
<br />
Queryを実行してみる。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://4.bp.blogspot.com/-iz4rM9LMUlU/UExdC-9ROjI/AAAAAAAAAGE/Iwv2-qw0PzM/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-09-09+16.41.49%EF%BC%89.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="114" src="http://4.bp.blogspot.com/-iz4rM9LMUlU/UExdC-9ROjI/AAAAAAAAAGE/Iwv2-qw0PzM/s200/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-09-09+16.41.49%EF%BC%89.png" width="200" /></a></div>
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
【まとめ】<br />
今回は<a href="https://developers.google.com/appengine/">GoogleAppEngine</a>と<a href="https://developers.google.com/storage/">GoogleCloudStorage</a>と<a href="https://developers.google.com/bigquery/docs/overview">GoogleBigQuery</a>を連携させてみた。<br />
興味深いのはやはり<a href="https://developers.google.com/storage/">GoogleCloudStorage</a>と<a href="https://developers.google.com/bigquery/docs/overview">GoogleBigQuery</a>の連携である。<br />
今回はDataStoreに格納されているデータを一度<a href="https://developers.google.com/storage/">GoogleCloudStorage</a>に格納させて<a href="https://developers.google.com/bigquery/docs/overview">GoogleBigQuery</a>にインポートという回りくどい(?)と思われる手法をとっているが、<br />
効率的に<a href="https://developers.google.com/appengine/">GoogleAppEngine</a>側のアプリケーションの出力を直接<a href="https://developers.google.com/storage/">GoogleCloudStorage</a>に行い、<a href="https://developers.google.com/bigquery/docs/overview">GoogleBigQuery</a>にインポートの方が、効率的に思われる。<br />
いずれにしてもアプリケーションの幅が広がることには違いない。<br />
現在開発中のプロジェクトにも取り込みたいと思っている。<br />
次回も<a href="https://developers.google.com/appengine/">GoogleAppEngine</a>関連のエントリーにしようかな。<br />
つづく。cloudysunny14http://www.blogger.com/profile/09953267600910932223noreply@blogger.com0tag:blogger.com,1999:blog-8436410939148734220.post-47083077849153504772012-08-20T21:10:00.000+09:002012-09-09T18:34:04.741+09:00Bixoを使ってみる。(2)<br />
今回は<a href="http://openbixo.org/">Bixo</a>を使ってWebマイニングをやってみる。<br />
前回と同様に疑似分散環境で実行する。<br />
<br />
まず、<a href="http://openbixo.org/documentation/getting-started/">GettingStart</a>に従って最新のディストリビューションファイルを<br />
ダウンロードすると同封されているexampleパッケージ(モジュール)が<br />
あるのでMavenでビルドする。<br />
<br />
<h3>
【WebMiningTool】</h3>
今回はWebMiningToolを起動する。<br />
大まかな処理の流れを説明すると前回紹介したWebクロール処理の後に<br />
クロール処理で取得したコンテンツをパースし、<br />
外部リンクの一覧を抽出する処理と、コンテンツから抽出した<br />
ページのテキストを構文解析して、リソースファイルとして<br />
取り込まれるnagative-phrases.txtとpositive-phrases.txtに記載されている<br />
それぞれの文言よりそのページのスコアを算出する処理が行われる。<br />
最終的な出力は、ページのコンテンツ一覧(content)とクロール結果(crawldb)と<br />
外部リンク一覧(results)とステータス(status)となっている。<br />
<br />
<h3>
【実行】</h3>
exampleには既にbixoの実行用シェルがbinフォルダに存在するので、<br />
ターミナルから下記コマンドを実行する。<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
bin/bixo webmining -agentname test-agent.com -workingdir /output/
]]>
</script>
<br />
<h3>
【実行結果】</h3>
<div>
今回も[ループ数-タイムスタンプ]という名前でディレクトリが作成されている。</div>
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
drwxr-xr-x 3 test admin 102 8 19 20:52 0-20120819T205230/
drwxr-xr-x 6 test admin 204 8 19 20:53 1-20120819T205230/
drwxr-xr-x 6 test admin 204 8 19 20:54 2-20120819T205330/
]]>
</script>
<div>
各ディレクトリにはcontent, crawldb, results, statusが格納されている。</div>
<br />
各ディレクトリの中には出力結果されたファイルが格納されている。<br />
contentに格納されている内容は下記の通り。<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
!http://cloudysunny14.blogspot.jp/!http://cloudysunny14.blogspot.jp/?9=?? bixo.datum.ContentBytes<!DOCTYPE html>
<html b:version='2' class='v2' dir='ltr'>
<head>
<meta content='IE=EmulateIE7' http-equiv='X-UA-Compatible'/>
<meta content='width=1100' name='viewport'/>
<meta content='text/html; charset=UTF-8' http-equiv='Content-Type'/>
]]>
</script>
こんな感じでクロールしたHTMLが入ってる。<br />
<br />
続いてcrawldb<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
http:/ 0 UNFETCHED 0.0 0.0
http://1.bp.blogspot.com/-3bom1jzhwYU/T_loDFHJaTI/AAAAAAAAAE4/q2YwXx1HpdM/s1600/%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88%ef%bc%882012-07-08+16.44.33%ef%bc%89.png 0 UNFETCHED 0.0 0.0
http://1.bp.blogspot.com/-C3ES6bzftl0/UAO1onV6DJI/AAAAAAAAAFU/JB66hIgFLXs/s1600/%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88%ef%bc%882012-07-16+15.32.01%ef%bc%89.png 0 UNFETCHED 0.0 0.0
http://2.bp.blogspot.com/-5lXqZ_wD-5Y/T_lmkv7JhoI/AAAAAAAAAEI/Utsi-lnlPkU/s1600/%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88%ef%bc%882012-07-08+16.35.49%ef%bc%89.png 0 UNFETCHED 0.0 0.0
]]>
</script>
続いてresults<br />
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
http://cloudysunny14.blogspot.jp/ http://2.bp.blogspot.com/-5lXqZ_wD-5Y/T_lmkv7JhoI/AAAAAAAAAEI/Utsi-lnlPkU/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-07-08+16.35.49%EF%BC%89.png
http://cloudysunny14.blogspot.jp/ http://2.bp.blogspot.com/-DowOVhVoL0s/T_lmucpkdeI/AAAAAAAAAEQ/4Ox89hY9szQ/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-07-08+16.40.40%EF%BC%89.png
http://cloudysunny14.blogspot.jp/ http://2.bp.blogspot.com/-UVuiVAHssfc/T_lnL_ugfVI/AAAAAAAAAEY/VQZ1aOEqq7I/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-07-08+16.42.12%EF%BC%89.png
http://cloudysunny14.blogspot.jp/ http://2.bp.blogspot.com/-vhqVzHHH5U4/T_lncMHn7MI/AAAAAAAAAEg/GxEwFAl4w9g/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-07-08+16.42.31%EF%BC%89.png
http://cloudysunny14.blogspot.jp/ http://3.bp.blogspot.com/-MfpaokKcuew/T_lnrYD7mlI/AAAAAAAAAEo/mwncO6RoLdE/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%8
]]>
</script>
最後にstatus
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
http://cloudysunny14.blogspot.jp/ FETCHED content-type text%2Fhtml%3B+charset%3DUTF-8 cache-control private%2C+max-age%3D0 x-content-type-options nosniff x-xss-protection 1%3B+mode%3Dblock expires Sun%2C+19+Aug+2012+04%3A24%3A03+GMT last-modified Sun%2C+12+Aug+2012+12%3A49%3A05+GMT etag %222ee54a3e-e395-443e-be02-9cdfbc620a68%22 content-length 30368 server GSE content-encoding gzip date Sun%2C+19+Aug+2012+04%3A24%3A03+GMT null 1345350254064 173.194.38.108 CustomFields-status UNFETCHED CustomFields-linksscore 0.0 CustomFields-pagescore 0.0 CustomFields-limited false
]]>
</script>
<br />
見てみると分かる通り、スコアが0になってしまっている。<br />
どうもフレーズの抽出に失敗しているようで、原因は調査中。<br />
<br />
実はこのExampleでページランクのアルゴリズムを実装しているのかと<br />
思っていたのだが(ただの先入観)<br />
実際はコンテンツのフレーズをポジティブフレーズ、ネガティブフレーズの<br />
出力確率によってページのスコア付けをするというとても単純なアルゴリズムで<br />
スコアを算出していた。<br />
<br />
まあ、いずれにしろWebクローリングが手軽にできて、<br />
なおかつ<a href="http://www.cascading.org/">Cascading</a>で実装されているので、機能を付加して<br />
データマイニングもできるという素晴らしいツールにはほかならない。<br />
確率スコアのSubassemblyだけでなく、クラス分類のSubassemblyなど<br />
応用してみても面白そう。<br />
実装できたらいいなー。と希望的観測ですが。。<br />
また次回は<a href="https://developers.google.com/appengine/">GAE</a>に戻るかなー。<br />
<br />cloudysunny14http://www.blogger.com/profile/09953267600910932223noreply@blogger.com0tag:blogger.com,1999:blog-8436410939148734220.post-12113644704346162572012-08-12T21:47:00.001+09:002012-09-09T18:34:09.589+09:00Bixoを使ってみる。(1)今回は、<a href="http://www.cascading.org/">Cascading</a>関連のプロダクト、<a href="http://openbixo.org/">Bixo</a>について。<br />
<br />
<h3>
<a href="http://openbixo.org/">Bixo</a>とは</h3>
<div>
<div>
<a href="http://openbixo.org/">Bixo</a>とは<a href="http://hadoop.apache.org/mapreduce/">Hadoop</a>上で動作するCascadingのPipeにより構成される</div>
<div>
Webマイニングツールキットである。</div>
<div>
カスタマイズされた<a href="http://www.cascading.org/">Cascading</a>の<a href="http://docs.cascading.org/cascading/2.0/userguide/html/ch07.html#N21296">PipeAssembly</a>を構築することによって、</div>
<div>
ユースケースに適用したWebマイニングアプリケーションを作成することが</div>
<div>
出来る。</div>
</div>
<div>
<br /></div>
<div>
<h3>
<a href="http://openbixo.org/">Bixo</a>のアーキテクチャ</h3>
</div>
<div>
<div>
<a href="http://openbixo.org/">Bixo</a>はいくつかの<a href="http://www.cascading.org/">Cascading</a>のOperationとSubassembiesによって構成されていて、</div>
<div>
HTMLのWebページをフェッチする為にこれらが組み合わされている。</div>
<div>
HTMLのWebページをパースした結果が出力される。</div>
</div>
<div>
<br /></div>
<div>
<div>
FetchSubassemblyはURLよりWEBページのフェッチを行い、</div>
<div>
StatusDatums(フェッチの状況を保持している)とFetchedDatum(フェッチしtWebページの情報を保持している)が出力される。</div>
<div>
Parse SubassemblyはWebページをフェッチした結果のコンテンツを処理するSubAssemblyで、HTMLからテキストを抽出するのが典型的</div>
</div>
<div>
<br /></div>
<h3>
Fetch処理の流れ</h3>
<div>
<div>
FetchSubassemblyは効率的にWebページのフェッチを行う為に、</div>
<div>
いくつかのフェーズによって成り立っている。</div>
<div>
ケースによって異なるが一般的には下記のとおり。</div>
</div>
<div>
<div>
<br /></div>
<div>
1.ホスト名でグループ化</div>
<div>
2.ホスト名IPアドレスに解決する処理と、robots.txtよりURLをフィルターする。</div>
<div>
3.フィルタリング、グループ化したIPアドレスをさらに制限する(オプション)</div>
<div>
4.URLの数と、クロールの遅延に基づいてフェッチ時間を割り当てる。</div>
<div>
5.グループ化されたURLによってReducerを振り分ける</div>
<div>
6.並列にFetch処理を行う。</div>
</div>
<div>
<br /></div>
<h3>
疑似分散環境で実行</h3>
<div>
<a href="http://openbixo.org/documentation/getting-started/">GettingStart</a>に従って、最新のディストリビューションファイルを<br />
ダウンロードする。そして実行。<br />
<br /></div>
<div>
【実行】</div>
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
$ bin/bixo crawl -agentname test_user_agent -domain google.com -outputdir output/ -numloops 3
]]>
</script>
【出力結果】<br />
下記のように[ループ数-タイムスタンプ]という名前でディレクトリが作成されいてる。
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
drwxr-xr-x 5 test staff 170 8 12 15:32 0-20120812T152902/
drwxr-xr-x 7 test staff 238 8 12 15:29 1-20120812T152902/
drwxr-xr-x 8 test staff 272 8 12 15:35 2-20120812T153014/
drwxr-xr-x 8 test staff 272 8 12 15:32 3-20120812T153125/
]]>
</script>
<br />
<div>
各ディレクトリには以下のようにcrawldb,status,content,parseが格納されている。</div>
<script class="brush:python; gutter:false;" type="syntaxhighlighter">
<![CDATA[
-rw-r--r-- 1 test staff 23412 8 12 15:30 1-SimpleCrawlTool.log
drwxr-xr-x 6 test staff 204 8 12 15:29 content/
drwxr-xr-x 6 test staff 204 8 12 15:30 crawldb/
drwxr-xr-x 6 test staff 204 8 12 15:29 parse/
drwxr-xr-x 6 test staff 204 8 12 15:29 status/
]]>
</script>
今回はここまで。<br />
次回は出力情報についてと、フェッチしたデータをもとに<br />
データマイニングを行うところまでやる予定。cloudysunny14http://www.blogger.com/profile/09953267600910932223noreply@blogger.com0tag:blogger.com,1999:blog-8436410939148734220.post-34743918025591988402012-08-06T11:44:00.000+09:002012-08-06T11:46:58.452+09:00GoogleAppEngine MapReduceとGoogleCloudStorageを連携させてみた<br />
また久しぶりのエントリになってしまったが、<br />
実はひそかに<a href="https://developers.google.com/appengine/docs/python/overview?hl=ja">Python</a>を勉強していたのだ。<br />
<br />
<a href="http://code.google.com/p/appengine-mapreduce/">GAE MapReduce</a>はJava版を以前紹介したが、Mapperのみの実装だったので、<br />
なんだか物足りなさを感じていた。<br />
しかし、<a href="https://developers.google.com/appengine/docs/python/overview?hl=ja">Python</a>版はなんとShuffle,Reduceまでフル実装だった!<br />
<a href="https://developers.google.com/appengine/docs/python/overview?hl=ja">Python</a>に乗り換えた理由である。<br />
<br />
ただ、実際に動作させてみると分かるが、中間ファイルの読み書きの多さが<span style="background-color: white;">欠点に感じる</span><br />
<span style="background-color: white;">(Shard数を16で動作させるとDatastore Write Operationsが一気に60%くらいになった)</span><br />
ここで、<a href="http://cloud.google.com/products/cloud-storage.html">GoogleCloudStorage</a>に入力ファイル、中間ファイル、出力ファイルを格納してみてはどうだろうか。と考えた。<br />
※実際は<a href="http://code.google.com/p/appengine-pipeline/">PipelineAPI</a>でバンバン読み書きしてるからっぽいけど。。^^;<br />
<br />
<a href="http://code.google.com/p/appengine-mapreduce/">GAE MapReduce</a>のソースをみる限り出力ファイルは<a href="http://cloud.google.com/products/cloud-storage.html">GoogleCloudStorage</a>に対応しているように見えたが、入力ファイルについては対応していないようだった。<br />
早速作ってみた。<br />
<a href="https://github.com/cloudysunny14/appengine-mapreduce2GCS">GoogleStorageLineInputReader</a>という<a href="http://cloud.google.com/products/cloud-storage.html">GoogleCloudStorage</a>より入力ファイルを読み込むクラスを作成した。<br />
ちょうど本家<a href="http://hadoop.apache.org/mapreduce/">Hadoop MapReduce</a>のTextInputFormatのように値はテキストファイルの1行、キーはその値の開始位置というレコードを出力する。<br />
<div>
<br /></div>
<div>
中間ファイル、出力ファイルは<a href="http://cloud.google.com/products/cloud-storage.html">GoogleCloudStorage</a>向けに出力するようにパッチを当てた。</div>
<div>
<br /></div>
<div>
<a href="https://github.com/cloudysunny14/appengine-mapreduce2GCS">ソース</a>は<a href="https://github.com/cloudysunny14/appengine-mapreduce2GCS">GitHub</a>に公開してあるのでそちらで参照して欲しい。</div>
<div>
<br /></div>
<div>
<div>
早速動かしてみよう。</div>
<div>
今回もWordCountだ。w</div>
</div>
<div>
<div>
入力ファイルは<a href="http://cloud.google.com/products/cloud-storage.html">GoogleCloudStorage</a>にアップロードしておく必要がある。</div>
<div>
それと、中間ファイル、出力ファイルが格納されるバケットも予め用意しておく必要がある。</div>
</div>
<div>
<a href="http://2.bp.blogspot.com/-5lXqZ_wD-5Y/T_lmkv7JhoI/AAAAAAAAAEI/Utsi-lnlPkU/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-07-08+16.35.49%EF%BC%89.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="126" src="http://2.bp.blogspot.com/-5lXqZ_wD-5Y/T_lmkv7JhoI/AAAAAAAAAEI/Utsi-lnlPkU/s320/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-07-08+16.35.49%EF%BC%89.png" width="320" /></a></div>
<div>
<br />
<br />
<br />
<br />
<br />
<a href="http://2.bp.blogspot.com/-DowOVhVoL0s/T_lmucpkdeI/AAAAAAAAAEQ/4Ox89hY9szQ/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-07-08+16.40.40%EF%BC%89.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="48" src="http://2.bp.blogspot.com/-DowOVhVoL0s/T_lmucpkdeI/AAAAAAAAAEQ/4Ox89hY9szQ/s320/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-07-08+16.40.40%EF%BC%89.png" width="320" /></a><br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
入力ファイルは以下の内容</div>
<div>
<br /></div>
<br />
<script class="brush:java" title="WordCount.txt" type="syntaxhighlighter">
<![CDATA[
TO BE OR NOT TO BE
FOO BAR FOO BAR FOO
]]>
</script>
<br />
<div>
Mapper,Reducer,MapreducePipelineの実装<br />
<br /></div>
<div>
</div>
<script class="brush:python; gutter:false;" title="wordcount.py" type="syntaxhighlighter">
<![CDATA[
def split_into_sentences(s):
"""Split text into list of sentences."""
s = re.sub(r"\s+", " ", s)
s = re.sub(r"[\\.\\?\\!]", "\n", s)
return s.split("\n")
def split_into_words(s):
"""Split a sentence into list of words."""
s = re.sub(r"\W+", " ", s)
s = re.sub(r"[_0-9]+", " ", s)
return s.split()
def word_count_map(data):
"""Word Count map function."""
k, v = data
for s in split_into_sentences(v):
for w in split_into_words(s.lower()):
yield (w, "")
def word_count_reduce(key, values):
"""Word count reduce function."""
yield "%s: %d\n" % (key, len(values))
class WordCountPipeline(base_handler.PipelineBase):
"""A pipeline to run Word count demo.
Args:
blobkey: blobkey to process as string. Should be a zip archive with
text files inside.
"""
def run(self):
output = yield mapreduce_pipeline.MapreducePipeline(
"word_count",
"wordcount.word_count_map",
"googlestorage.shuffler.ShufflePipeline",
"wordcount.word_count_reduce",
"googlestorage.input_readers.GoogleStorageLineInputReader",
"googlestorage.output_writers.GoogleStorageOutputWriter",
mapper_params={"file_paths": "/gs/sample_test/WordCount",
"gs_bucket_name": "temp_test",
"gs_acl": "public-read"},
shuffler_params={"gs_bucket_name": "temp_test",
"mime_type": "text/plain",
"gs_acl": "public-read"},
reducer_params={"gs_bucket_name": "output_test",
"mime_type": "text/plain",
"gs_acl": "public-read"},
shards=2)
]]>
</script>
<br />
<div>
そして実行。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://2.bp.blogspot.com/-UVuiVAHssfc/T_lnL_ugfVI/AAAAAAAAAEY/VQZ1aOEqq7I/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-07-08+16.42.12%EF%BC%89.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="180" src="http://2.bp.blogspot.com/-UVuiVAHssfc/T_lnL_ugfVI/AAAAAAAAAEY/VQZ1aOEqq7I/s320/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-07-08+16.42.12%EF%BC%89.png" width="320" /></a></div>
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Shuffleフェーズ<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://2.bp.blogspot.com/-vhqVzHHH5U4/T_lncMHn7MI/AAAAAAAAAEg/GxEwFAl4w9g/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-07-08+16.42.31%EF%BC%89.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-vhqVzHHH5U4/T_lncMHn7MI/AAAAAAAAAEg/GxEwFAl4w9g/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-07-08+16.42.31%EF%BC%89.png" /></a></div>
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
完了。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://3.bp.blogspot.com/-MfpaokKcuew/T_lnrYD7mlI/AAAAAAAAAEo/mwncO6RoLdE/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-07-08+16.42.48%EF%BC%89.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="146" src="http://3.bp.blogspot.com/-MfpaokKcuew/T_lnrYD7mlI/AAAAAAAAAEo/mwncO6RoLdE/s320/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-07-08+16.42.48%EF%BC%89.png" width="320" /></a></div>
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
うん、結果バッチリ。<br />
<a href="http://2.bp.blogspot.com/-l3yw3hddUOY/T_ln7mcZHDI/AAAAAAAAAEw/OgJz8PN3oaY/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-07-08+16.44.17%EF%BC%89.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="39" src="http://2.bp.blogspot.com/-l3yw3hddUOY/T_ln7mcZHDI/AAAAAAAAAEw/OgJz8PN3oaY/s320/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-07-08+16.44.17%EF%BC%89.png" width="320" /></a><br />
<br />
<br />
<a href="http://1.bp.blogspot.com/-3bom1jzhwYU/T_loDFHJaTI/AAAAAAAAAE4/q2YwXx1HpdM/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-07-08+16.44.33%EF%BC%89.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-3bom1jzhwYU/T_loDFHJaTI/AAAAAAAAAE4/q2YwXx1HpdM/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-07-08+16.44.33%EF%BC%89.png" /></a><br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
今回はまだ小規模なデータしか扱ってないので、<br />
まだ課題は見つからなかったが、これから中規模、大規模と<br />
チャレンジしていきたい(クラウド貧乏にならないように注意が必要だがw)<br />
<br />
あと、<a href="http://www.python.jp/Zope">Python</a>もまだ触りたてなので、磨いていきたい。<br />
次回も引き続き<a href="http://code.google.com/p/appengine-mapreduce/">GAE MapReduce</a>で遊んでみようかな。<br />
(<a href="http://cloud.google.com/products/compute-engine.html">Google Compute Engine</a>も気になる。。)乞うご期待。<br />
<br />
</div>cloudysunny14http://www.blogger.com/profile/09953267600910932223noreply@blogger.com0tag:blogger.com,1999:blog-8436410939148734220.post-32767331149583721962012-07-29T16:21:00.000+09:002012-08-20T15:01:13.200+09:00GAE Python urlfetchサービスを用いたテストについて<br />
今回は、<a href="https://developers.google.com/appengine/">GAE</a>でアプリケーションを開発する際のTipsを紹介。<br />
<br />
<h3>
・目的</h3>
<a href="https://developers.google.com/appengine/">GAE</a><span style="background-color: white;"> Pythonで<a href="https://developers.google.com/appengine/docs/python/urlfetch/">urlfetchサービス</a>を用いて通信で外部のコンテンツを取得する</span><br />
処理のテストを行う場合、<br />
外部への接続が切断されている、または外部のデータが変更された等で<br />
テストが失敗するという事態が起こる事を避ける為、<br />
実際の通信はせずにローカルのコンテンツ(ファイルなど)を使用してレスポンス<br />
する。<br />
<br />
<h3>
・方法</h3>
<a href="https://developers.google.com/appengine/">GAE</a>では各種サービスをRPC経由で呼び出している。<br />
開発環境では、RPCをフックしているstubを使用して実際の通信を行う処理を<br />
行っている。<br />
今回はそのstubの実装を変更し、レスポンスを独自に組み立てて返却する。<br />
<br />
下記がコード<br />
<script class="brush:python; gutter:false;" title="testutil.py" type="syntaxhighlighter">
<![CDATA[
class URLFetchServiceMock(apiproxy_stub.APIProxyStub):
"""Mock for google.appengine.api.urlfetch."""
def __init__(self, service_name="urlfetch"):
super(URLFetchServiceMock, self).__init__(service_name)
def set_return_values(self, return_value):
self.return_values = return_value
def _Dynamic_Fetch(self, request, response):
return_values = self.return_values
response.set_content(return_values.get("content", ""))
response.set_statuscode(return_values.get("status_code", 200))
for header_key, header_value in return_values.get("headers", {}).items():
new_header = response.add_header()
new_header.set_key(header_key)
new_header.set_value(header_value)
response.set_finalurl(return_values.get("final_url", request.url))
response.set_contentwastruncated(return_values.get("content_was_truncated",
False))
self.request = request
self.response = response
class FetchTestBase(unittest.TestCase):
"""Base class for fetcher."""
def setUp(self):
unittest.TestCase.setUp(self)
# Regist API Proxy
apiproxy_stub_map.apiproxy = apiproxy_stub_map.APIProxyStubMap()
# Regist urlfetch stub
self._urlfetch_mock = URLFetchServiceMock()
apiproxy_stub_map.apiproxy.RegisterStub("urlfetch",
self._urlfetch_mock)
def setReturnValue(self, **kwargs):
""" set the return value."""
self._urlfetch_mock.set_return_values(kwargs)
def tearDown(self):
unittest.TestCase.tearDown(self)
]]>
</script>
<script class="brush:python; gutter:false;" title="urlfetch_test.py" type="syntaxhighlighter">
<![CDATA[
class UrlFetcheTest(testutil.FetchTestBase):
""" test urlfetch for local """
def testFetch(self):
self.setReturnValues(content="some content")
result = urlfetch.fetch(url="http://uso800.co.jp/detarame")
self.assertEquals(200, result.status_code)
self.assertEquals("some content", result.content)
]]>
</script>
<br />
<br />
テストで使い回しが効くようにベースクラスとして作成した。<br />
まず、apiproxy_stub_mapに各種サービスの独自に実装したstubを設定する。<br />
今回はurlfetchなので、サービス名は"urlfetch"とする。<br />
設定したstubクラスには_Dynamic_Fetchというメソッドが実装されているが、<br />
RPCが実行されると使われるAPIProxyStub#MakeSyncCallで動的呼び出しが<br />
行われているメソッドである。<br />
このメソッドをオーバーライドし、独自のレスポンスを組みたてて返却する。<br />
このようにモックを使用したテストを行いたい場合は、どのサービスでも<br />
応用が効くはず。お試しあれ。<br />
<br />cloudysunny14http://www.blogger.com/profile/09953267600910932223noreply@blogger.com0tag:blogger.com,1999:blog-8436410939148734220.post-86909200709164529102012-07-16T17:47:00.000+09:002012-07-17T12:05:56.464+09:00GoogleAppEngine MapReduceとGoogleCloudStorageを連携させてみた(2)今回も前回に引き続き<a href="https://developers.google.com/appengine/docs/python/dataprocessing/">GAE MapReduce</a>。<br />
前回<a href="https://developers.google.com/appengine/?hl=ja">GAE</a>と<a href="http://cloud.google.com/products/cloud-storage.html">GCS</a>で連携してMapReduceを実行してみたが、<br />
今回は性能評価として、<span style="background-color: white;">以下を実施してみた。</span><br />
<span style="background-color: white;"><br /></span><br />
<h3>
<span style="background-color: white; font-size: large;">実施内容</span></h3>
<span style="background-color: white;">10MB(MegaByte)のテキストファイル※に対して</span><span style="background-color: white;">WordCountを行い性能評価を行った。</span><br />
<span style="background-color: white;">※妥当かどうかは微妙です。</span><span style="background-color: white;">。決してビッグデータとは言えない。。</span><br />
<span style="background-color: white;"><br /></span><br />
<h4>
<span style="background-color: white;">●</span><span style="background-color: white;">評価環境</span></h4>
<span style="background-color: white;">【</span><a href="https://developers.google.com/appengine/docs/python/gettingstarted/?hl=ja" style="background-color: white;">Google App Engine</a><span style="background-color: white;">】</span><br />
Runtime:Python<br />
SystemStatus:Normal<br />
Latency:Normal<br />
<br />
<h4>
●計測で使用したWordCountプログラム</h4>
前回のエントリで紹介した<a href="http://cloud.google.com/products/cloud-storage.html">GCS</a>上のファイルを読み込むInputReaderと<br />
中間ファイル、出力ファイルを<a href="http://cloud.google.com/products/cloud-storage.html">GCS</a>上に出力するMapreducePipelineを使用した。<br />
※バグがあったので修正したものを<a href="https://github.com/cloudysunny14/appengine-mapreduce2GCS">コミット</a>しました。<br />
<br />
<h2>
<span style="font-size: large;">計測結果</span></h2>
<div>
<br /></div>
<h3>
【WordCount処理時間】</h3>
<br />
<br />
<br />
<a href="http://2.bp.blogspot.com/-KT6KACmTH3I/UAOxkumGXxI/AAAAAAAAAFE/BUGLpLbqH9Q/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-07-16+15.15.00%EF%BC%89.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-KT6KACmTH3I/UAOxkumGXxI/AAAAAAAAAFE/BUGLpLbqH9Q/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-07-16+15.15.00%EF%BC%89.png" /></a><br />
<br />
<br />
<br />
<span style="background-color: white;"><br /></span><br />
<span style="background-color: white;"><br /></span><br />
<span style="background-color: white;"><br /></span><br />
<span style="background-color: white;"><br /></span><br />
<span style="background-color: white;"><br /></span><br />
<br />
<br />
上記結果から見て取れるようにshard数4〜8で処理時間は大幅に減少している。<br />
また、shard数16から32は収束状態となった。<br />
この結果からファイルサイズ10MB程度であれば、shard数8から16程度までが<br />
妥当な数となり、8以下だと処理時間が増加し、16以上だとオーバーワーク気味に<br />
なってしまう。<br />
(pipeline処理が行うDatastoreへのRead/Writeオペレーションがshard数に比例して増加する為。shard数32以上で実施したらあっという間にOverQuotaになった。)<br />
<br />
<h3>
【Instance数】</h3>
<br />
<br />
<br />
<a href="http://3.bp.blogspot.com/-d_Iw2A5_ZBk/UAOz5KZQ92I/AAAAAAAAAFM/LNOCFiPLdh0/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-07-16+15.25.06%EF%BC%89.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-d_Iw2A5_ZBk/UAOz5KZQ92I/AAAAAAAAAFM/LNOCFiPLdh0/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-07-16+15.25.06%EF%BC%89.png" /></a><br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<span style="background-color: white;">上記結果から見て取れるようにshard数4〜16まではshard数に比例して</span><br />
スケールしている事がわかる。<br />
また、shard数<span style="background-color: white;">16〜32はほぼ収束状態となった。</span><br />
<br />
<h3>
【Memory Usage(MB)】</h3>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://1.bp.blogspot.com/-C3ES6bzftl0/UAO1onV6DJI/AAAAAAAAAFU/JB66hIgFLXs/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-07-16+15.32.01%EF%BC%89.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-C3ES6bzftl0/UAO1onV6DJI/AAAAAAAAAFU/JB66hIgFLXs/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-07-16+15.32.01%EF%BC%89.png" /></a></div>
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<span style="background-color: white;"><br /></span><br />
<span style="background-color: white;"><br /></span><br />
<span style="background-color: white;"><br /></span><br />
<span style="background-color: white;">これはあまり参考にならなかった。。</span><br />
<br />
<br />
<h3>
【shard数に於けるMap処理】</h3>
1Task(約15sec)で処理するMap数についてまとめてみた。<br />
<br />
<br />
■shard数4<br />
・Overview<br />
Elapsed time: 00:04:46<br />
・Counters<br />
io-write-bytes: 30277632 (105865.85/sec avg.)<br />
io-write-msec: 8140 (28.46/sec avg.)<br />
mapper-calls: 93762 (327.84/sec avg.)<br />
mapper-walltime-ms: 565100 (1975.87/sec avg.)<br />
(平均処理数:2391Map処理/Task)<br />
<br />
■shard数8<br />
・Overview<br />
Elapsed time: 00:03:53<br />
・Counters<br />
io-write-bytes: 30474240 (130790.73/sec avg.)<br />
io-write-msec: 6602 (28.33/sec avg.)<br />
mapper-calls: 93762 (402.41/sec avg.)<br />
mapper-walltime-ms: 591540 (2538.8/sec avg.)<br />
<br />
(平均処理数:2256<span style="background-color: white;">Map処理/Task</span><span style="background-color: white;">)</span><br />
<span style="background-color: white;"><br /></span><br />
■shard数16<br />
Overview<br />
Elapsed time: 00:01:55<br />
Counters<br />
io-write-bytes: 30408704 (264423.51/sec avg.)<br />
io-write-msec: 4249 (36.95/sec avg.)<br />
mapper-calls: 93762 (815.32/sec avg.)<br />
mapper-walltime-ms: 667741 (5806.44/sec avg.)<br />
(平均処理数:2000Map処理/Task)<br />
<br />
■shard数32<br />
Overview<br />
Elapsed time: 00:01:55<br />
Counters<br />
io-write-bytes: 30539776 (265563.27/sec avg.)<br />
io-write-msec: 5755 (50.04/sec avg.)<br />
mapper-calls: 93762 (815.32/sec avg.)<br />
mapper-walltime-ms: 712984 (6199.86/sec avg.)<br />
(平均処理数:1706Map処理/Task)<br />
<br />
上記のとおり<span style="background-color: white;">mapper-callsを見る限り、shard数に応じて1秒間に処理するMap数は</span><br />
<span style="background-color: white;">増加しているように見える。</span><br />
しかし、shard数によって1Task(1MapperTaskが15秒間に行うMap処理数)<br />
のMap処理の処理数にあまり変化がないようだ<span style="background-color: white;">。</span><br />
<span style="background-color: white;">よってshard数によってGCSの読み書きレイテンシに影響はなく、</span><br />
1Taskだいたい決まった数のMap処理を行う事ができる。<br />
すなわちMap処理に関してだが、純粋にshard数に応じて、処理数が<br />
比例するであろうと分かる結果であった。<br />
※shard数32だけ例外だが、10MB程度だと1Mapperで数Taskほどしか<br />
動かなかったので、妥当な結果が得られなかったためであろう。。(^^;)<br />
<br />
<h3>
おまけ</h3>
<h3>
<span style="background-color: white;">【</span><a href="https://developers.google.com/appengine/docs/python/dataprocessing/">appengine-mapreduce</a> VS <span style="background-color: white;"><a href="https://github.com/cloudysunny14/appengine-mapreduce2GCS">appengine-mapreduce2GCS</a> </span><span style="background-color: white;">】</span></h3>
<span style="background-color: white;"><br /></span><br />
GCS連携版の<a href="https://github.com/cloudysunny14/appengine-mapreduce2GCS">appengine-mapreduce2GCS</a>と<a href="https://developers.google.com/appengine/docs/python/dataprocessing/">appengine-mapreduce</a>のBlobLineInputReaderを使用したもので、WordCount対決を行ってみた。<br />
<br />
・WordCount処理対象<br />
10MB(MegaByte)のテキストファイルをshard数16でWordCount<br />
<br />
●結果<br />
【WordCount処理時間】<br />
・<a href="https://developers.google.com/appengine/docs/python/dataprocessing/">appengine-mapreduce</a><span style="background-color: white;">: </span><span style="background-color: white;">00:09:53.225</span><br />
<span style="background-color: white;">・</span><a href="https://github.com/cloudysunny14/appengine-mapreduce2GCS">appengine-mapreduce2GCS</a><span style="background-color: white;">: </span><span style="background-color: white;">00:07:42.04</span><br />
<span style="background-color: white;"><br /></span><br />
【Instance数】<br />
・<a href="https://developers.google.com/appengine/docs/python/dataprocessing/">appengine-mapreduce</a><span style="background-color: white;">:最大30Instances</span><br />
<span style="background-color: white;">・</span><a href="https://github.com/cloudysunny14/appengine-mapreduce2GCS">appengine-mapreduce2GCS</a><span style="background-color: white;">:最大24Instances</span><br />
<span style="background-color: white;"><br /></span><br />
【Map処理】<br />
・<a href="https://developers.google.com/appengine/docs/python/dataprocessing/">appengine-mapreduce</a><span style="background-color: white;">:</span><br />
<span style="background-color: white;">Overview</span><br />
Elapsed time: 00:02:23<br />
Counters<br />
io-write-bytes: 30900224 (216085.48/sec avg.)<br />
io-write-msec: 9142 (63.93/sec avg.)<br />
mapper-calls: 93762 (655.68/sec avg.)<br />
mapper-walltime-ms: 822627 (5752.64/sec avg.)<br />
(平均処理数:1762<span style="background-color: white;">Map処理/Task</span><span style="background-color: white;">)</span><br />
<br />
<span style="background-color: white;">・</span><a href="https://github.com/cloudysunny14/appengine-mapreduce2GCS" style="background-color: white;">appengine-mapreduce2GCS</a>:<br />
<span style="background-color: white;">Overview</span><br />
Elapsed time: 00:01:44<br />
Counters<br />
io-write-bytes: 30277632 (291131.08/sec avg.)<br />
io-write-msec: 3940 (37.88/sec avg.)<br />
mapper-calls: 93762 (901.56/sec avg.)<br />
mapper-walltime-ms: 662981 (6374.82/sec avg.)<br />
(平均処理数:2012<span style="background-color: white;">Map処理/Task</span><span style="background-color: white;">)</span><br />
<br />
なんと!<a href="http://cloud.google.com/products/cloud-storage.html">GCS</a>連携しているMapReduce処理のほうが早いという結果になった。<br />
(Outputが正しいか検証したが、両者とも問題なかった。)<br />
上記から見て取れるようにio-write-bytesに関して<a href="http://cloud.google.com/products/cloud-storage.html">GCS</a>の方が、<br />
高速であることが分かる。<br />
(ただし、<a href="http://cloud.google.com/products/cloud-storage.html">GCS</a>のほうはバケットに溜まったファイル群をリフレッシュしていた。<br />
これが要因だとすると、不要なファイルは出来るだけ消しておくほうが良い?)<br />
<br />
<h3>
【まとめ】</h3>
今回は10MBというビッグデータとは決して言えないファイルを対象とした<br />
検証であったので、妥当とは言えないかもしれない。<br />
ただ、<a href="https://developers.google.com/appengine/?hl=ja">GAE</a>で手軽に並列処理を行えるというのはとても画期的で、<br />
有用であると考えられる。(中規模のデータであれば十分)<br />
今後はさらに発展させたMapReduceのアプリケーションをGAEで<br />
作っていけたらと考えてる。<br />
また、この計測結果も更新していこうかな。<br />
<br />
<br />
<br />
<div>
<br /></div>
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />cloudysunny14http://www.blogger.com/profile/09953267600910932223noreply@blogger.com0tag:blogger.com,1999:blog-8436410939148734220.post-83035934126299221762012-06-24T16:36:00.000+09:002012-08-06T11:49:18.289+09:00Cascading使ってみた久々の更新になってしまった。前回<a href="http://code.google.com/p/appengine-pipeline/">Google App Engine Pipeline API</a>を紹介したが、<br />
その中で<a href="http://www.cascading.org/">Cascading</a>というキーワードが出てきたと思う。<br />
今回はずっと気になっていた<a href="http://www.cascading.org/">Cascading</a>とやらHadoopの関連プロジェクトについて触れてみたい。<br />
ただWordCountをやってみるのはもう飽きたのでw<br />
今回はより実用的にApacheログの解析をやってみたい。<br />
<br />
下記のログファイルを解析に使う。<br />
<br />
<br />
<script class="brush:java; gutter:false;" title="log.txt" type="syntaxhighlighter">
<![CDATA[
75.185.76.245 - - [01/Sep/2007:00:01:03 +0000] "POST /mt-tb.cgi/235 HTTP/1.1" 403 174 "-" "Opera/9.10 (Windows NT 5.1; U; ru)" "-"
68.46.103.112 - - [01/Sep/2007:00:01:17 +0000] "POST /mt-tb.cgi/92 HTTP/1.1" 403 174 "-" "Opera/9.10 (Windows NT 5.1; U; ru)" "-"
76.197.151.0 - - [01/Sep/2007:00:02:24 +0000] "POST /mt-tb.cgi/274 HTTP/1.1" 403 174 "-" "Opera/9.10 (Windows NT 5.1; U; ru)" "-"
63.123.238.8 - - [01/Sep/2007:00:02:49 +0000] "GET /robots.txt HTTP/1.1" 200 0 "-" "Mozilla/2.0 (compatible; Ask Jeeves/Teoma)" "-"
63.123.238.8 - - [01/Sep/2007:00:02:49 +0000] "GET / HTTP/1.1" 200 34293 "-" "Mozilla/2.0 (compatible; Ask Jeeves/Teoma)" "-"
12.215.138.88 - - [01/Sep/2007:00:03:31 +0000] "POST /archives/000180.html HTTP/1.1" 405 315 "http://www.example.org/mt-comments.cgi" "Opera/9.10 (Windows NT 5.1; U; ru)" "-"
78.84.186.161 - - [01/Sep/2007:00:04:00 +0000] "POST /mt-tb.cgi/247 HTTP/1.1" 403 174 "-" "Opera/9.10 (Windows NT 5.1; U; ru)" "-"
190.72.193.209 - - [01/Sep/2007:00:04:09 +0000] "POST /mt-tb.cgi/330 HTTP/1.1" 403 174 "-" "Opera/9.10 (Windows NT 5.1; U; ru)" "-"
68.102.166.126 - - [01/Sep/2007:00:07:51 +0000] "POST /archives/000228.html HTTP/1.1" 405 315 "http://www.example.org/mt-comments.cgi" "Opera/9.10 (Windows NT 5.1; U; ru)" "-"
122.152.128.48 - - [01/Sep/2007:00:10:14 +0000] "GET / HTTP/1.1" 200 34293 "-" "Baiduspider+(+http://www.baidu.com/search/spider_jp.html)" "-"
----------- Omitted -----------
]]>
</script>
<br />
Cascadingアプリケーションのソースは下記<br />
<br />
<script class="brush:java; gutter:false;" title="App.java" type="syntaxhighlighter">
<![CDATA[
public class App {
public static void main( String[] args ) {
String inputPath = arg[0];
String outputPath = arg[1];
Scheme sinkScheme = new TextLine();
Tap sink = new Hfs( sinkScheme, outputPath, SinkMode.REPLACE );
//define what the input file looks like, "offset" is bytes from beginning
TextLine scheme = new TextLine( new Fields( "offset", "line" ) );
Tap logTap = new Hfs( scheme, inputPath );
Fields apacheFields = new Fields( "ip", "time", "method", "event", "status", "size" );
// define the regular expression to parse the log file with
String apacheRegex = "^([^ ]*) +[^ ]* +[^ ]* +\\[([^]]*)\\] +\\\"([^ ]*) ([^ ]*) [^ ]*\\\" ([^ ]*) ([^ ]*).*$";
// declare the groups from the above regex we want to keep. each regex group will be given
// a field name from 'apacheFields', above, respectively
int[] allGroups = {1, 2, 3, 4, 5, 6};
// create the parser
RegexParser parser = new RegexParser( apacheFields, apacheRegex, allGroups );
Pipe importPipe = new Each( "import", new Fields( "line" ), parser, Fields.RESULTS );
importPipe = new GroupBy(importPipe, new Fields("ip", "event"));
Aggregator count = new Count( new Fields( "count" ) );
importPipe = new Every( importPipe, count );
//initialize app properties, tell Hadoop which jar file to use
Properties properties = new Properties();
AppProps.setApplicationJarClass( properties, App.class );
//plan a new Flow from the assembly using the source and sink Taps
//with the above properties
FlowConnector flowConnector = new HadoopFlowConnector( properties );
Flow flow = flowConnector.connect( "word-count", logTap, sink, importPipe );
//execute the flow, block until complete
flow.complete();
}
}
]]>
</script>
<br />
処理の内容について、細かく説明するのはまた次回以降。(まだ調査、勉強中です。。)<br />
大まかに処理の流れを追うと入力データ(ソース)から必要な部分を抽出し、<br />
<div class="p1">
<span style="background-color: white;">"ip"</span><span class="s1" style="background-color: white;">, </span><span style="background-color: white;">"time"</span><span class="s1" style="background-color: white;">, </span><span style="background-color: white;">"method"</span><span class="s1" style="background-color: white;">, </span><span style="background-color: white;">"event"</span><span class="s1" style="background-color: white;">, </span><span style="background-color: white;">"status"</span><span class="s1" style="background-color: white;">, </span><span style="background-color: white;">"size"</span>という項目をもつタプル(データベースの行やレコードとよく似ている)に<span style="background-color: white;">整形する。</span><br />
<span style="background-color: white;">更に"ip","event"でグループ化、カウントを行い出力データ(シンク)を生成する。</span></div>
<div class="p1">
<br /></div>
<div class="p1">
今回は疑似分散環境で動作させてみる。</div>
<script class="brush:java; gutter:false;" title="" type="syntaxhighlighter">
<![CDATA[
$hadoop jar App.jar cascading.thirdparty.subassembly.App input/ output/
12/06/24 15:44:24 INFO util.HadoopUtil: resolving application jar from found main method on: cascading.thirdparty.subassembly.App
12/06/24 15:44:24 INFO planner.HadoopPlanner: using application jar: null
12/06/24 15:44:24 INFO property.AppProps: using app.id: 8031DEC84B0D8AA1D084C3455DC88A39
12/06/24 15:44:24 INFO util.Version: Concurrent, Inc - Cascading 2.0.0
12/06/24 15:44:24 INFO flow.Flow: [apache-log-analyze] starting
12/06/24 15:44:24 INFO flow.Flow: [apache-log-analyze] source: Hfs["TextLine[['offset', 'line']->[ALL]]"]["input"]"]
12/06/24 15:44:24 INFO flow.Flow: [apache-log-analyze] sink: Hfs["TextLine[['offset', 'line']->['ip', 'event', 'count']]"]["output"]"]
12/06/24 15:44:24 INFO flow.Flow: [apache-log-analyze] parallel execution is enabled: true
12/06/24 15:44:24 INFO flow.Flow: [apache-log-analyze] starting jobs: 1
12/06/24 15:44:24 INFO flow.Flow: [apache-log-analyze] allocating threads: 1
12/06/24 15:44:24 INFO flow.FlowStep: [apache-log-analyze] starting step: (1/1) output
12/06/24 15:44:25 WARN mapred.JobClient: No job jar file set. User classes may not be found. See JobConf(Class) or JobConf#setJar(String).
12/06/24 15:44:25 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
12/06/24 15:44:25 WARN snappy.LoadSnappy: Snappy native library not loaded
12/06/24 15:44:25 INFO mapred.FileInputFormat: Total input paths to process : 1
12/06/24 15:44:25 INFO flow.FlowStep: [apache-log-analyze] submitted hadoop job: job_201206241526_0002
12/06/24 15:45:15 INFO util.Hadoop18TapUtil: deleting temp path output/_temporary
]]>
</script>
<br />
<div class="p1">
</div>
<div class="p1">
結果はこのとおり</div>
<script class="brush:java; gutter:false;" title="" type="syntaxhighlighter">
<![CDATA[
$ hadoop dfs -cat output/part-00000
12.215.138.88 /archives/000180.html 5
12.215.138.88 /mt-tb.cgi/95 5
122.148.222.155 /mt-tb.cgi/80 5
122.152.128.48 / 5
122.152.128.49 /robots.txt 5
125.16.138.136 /index.xml 5
125.244.54.130 /mt-comments.cgi 5
128.6.210.68 /archives/000183.html 5
128.6.210.68 /mt-comments.cgi 5
130.206.134.214 /mt-tb.cgi/62 5
141.158.109.113 /mt-tb.cgi/132 5
146.164.34.14 /mt-tb.cgi/295 5
148.221.145.254 /mt-tb.cgi/93 5
163.10.22.32 /archives/000204.html 5
163.10.22.32 /mt-comments.cgi 5
190.72.193.209 /mt-tb.cgi/330 5
190.8.194.30 /mt-tb.cgi/203 5
193.193.255.155 /mt-comments.cgi 5
193.47.137.246 /mt-comments.cgi 5
195.76.242.227 /archives/000259.html 5
196.36.198.91 /mt-comments.cgi 5
200.195.95.38 /mt-tb.cgi/62 5
200.31.42.3 /mt-tb.cgi/119 5
200.31.42.3 /mt-tb.cgi/203 5
200.83.4.3 /mt-comments.cgi 5
202.84.119.137 /archives/000021.html 5
204.186.14.178 /archives/000211.html 5
206.51.233.54 /archives/000276.html 5
----------- Omitted -----------
]]>
</script>
<br />
<div class="p1">
"ip"<span class="s1">,</span><span class="s1"> </span>"event"でグループ化され、カウントを算出できている。(※単純にIPアドレス毎のアクセス数)<br />
今回のサンプルのようにログやWebクロールした結果などをパースしたりフィルタリングを行い、出力されたデータを使って、例えば<a href="http://mahout.apache.org/">Mahout</a>の入力データにするなど面白いかもしれない。<br />
今回はSubAssembly(再利用可能なパイプ)やCascade(複数のFlowを連結する)など<a href="http://www.cascading.org/">Cascading</a>において特徴的とも言える柔軟性を利用しなかったが、今後それらについても調査し、紹介したいと思っている。<br />
次回は、最近興味を持っている機械学習のエントリでもしてみるかな。<br />
<br /></div>cloudysunny14http://www.blogger.com/profile/09953267600910932223noreply@blogger.com0tag:blogger.com,1999:blog-8436410939148734220.post-14261007193237885862012-05-27T21:38:00.000+09:002012-08-06T11:52:00.618+09:00Google App Engine PipelineAPI (1)前回に引き続き、<a href="http://code.google.com/p/appengine-pipeline/">PipelineAPI</a>について。<br />
<br />
<br />
<h3>
なぜPipeline処理?</h3>
<span id="goog_507153582"></span><span id="goog_507153583"></span><a href="http://hadoop.apache.org/">Hadoop</a> <a href="http://www.cascading.org/">Cascading</a>(Apacheプロジェクトではない)でも用いられている。<a href="http://www.cascading.org/">Cascading</a>は、<a href="http://hadoop.apache.org/">Hadoop</a>の<a href="http://hadoop.apache.org/mapreduce/">MapReduce</a>を隠蔽(抽象化)するライブラリー。<a href="http://www.cascading.org/">Cascading</a>では<a href="http://hadoop.apache.org/mapreduce/">MapReduce</a>タスクをPipeという単位で記述し、Pipeをつなげて処理を行う。<a href="http://hadoop.apache.org/mapreduce/">MapReduce</a>の複合ジョブを効率的に行う事が出来る。<br />
基本的な<a href="http://hadoop.apache.org/mapreduce/">MapReduce</a>ではReducerの結果をMapperに渡すなどは出来ない。<br />
<a href="https://developers.google.com/appengine/">GoogleAppEngine</a> <a href="http://code.google.com/p/appengine-pipeline/">PipelineAPI</a>でもMapperとの複合を主な用途として紹介している。<br />
<br />
<br />
<h3>
Pipeline処理とは</h3>
コンピュータにおける処理要素を直列に連結し、ある要素の出力が次の要素の入力となるように配置して処理することである。コンピュータ等の高速化技術の一つである。パイプラインの各要素は並列またはタイムスライス化して実行される。<br />
(出典:Wikipedia <a href="http://ja.wikipedia.org/wiki/%E3%83%91%E3%82%A4%E3%83%97%E3%83%A9%E3%82%A4%E3%83%B3%E5%87%A6%E7%90%86">パイプライン処理</a>)<br />
<br />
<br />
<h3>
GoogleAppEngine PipelineAPIの処理概要</h3>
PipelineAPIでは、Jobクラスを実装する事により、Pipeline処理を記述していく。<br />
並列したJobはバリア同期によって統合される。<br />
各Job(Record)は自Taskの状態とOutputSlot(出力)とバリア同期の為の情報を持っている。<br />
処理のおおまかな流れは下図の通り(<a href="http://www.google.com/events/io/2011/index-live.html">Google I/O 2011</a>より)<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://3.bp.blogspot.com/-ej_SSB5S-GU/T8IeJS_UcrI/AAAAAAAAAD4/sBSsh7NxIB8/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-05-27+21.27.36%EF%BC%89.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-ej_SSB5S-GU/T8IeJS_UcrI/AAAAAAAAAD4/sBSsh7NxIB8/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-05-27+21.27.36%EF%BC%89.png" /></a></div>
<br />
<br />
<br />
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<div>
Multiply、AddはJobで、SlotA、SlotB、SlotCは出力スロットとなっている。</div>
<div>
Barrierはバリア同期を行っている処理でSlotA,SlotBが埋まる事(Fill)によって、</div>
<div>
Addが実行(Run)される事を示している。</div>
</div>
<div>
<br /></div>
<div>
<div>
Taskの状態遷移</div>
<div>
【FAN_OUT】Jobの入力処理(Task)を起動させるTask</div>
<div>
【HANDLE_SLOT_FILLED】入出力スロットが埋められたら動くTask</div>
<div>
【RUN_JOB】バリア同期によるロックが解放されたら動くTask(Jobを実行する)</div>
<div>
【FINALIZE_JOB】出力スロットを埋める。</div>
<div>
【DELETE_PIPELINE】PipeLine処理終了</div>
</div>
<div>
<br /></div>
<div>
<div>
<h3>
処理の詳細について</h3>
</div>
<div>
(次回以降にまた調べてみたところを紹介する予定)</div>
</div>
<div>
<br /></div>
<div>
<div>
・バリア同期について</div>
<div>
バリア同期が行われているTaskはRUN_JOBとFINALIZE_JOB。</div>
<div>
バリア同期の情報はwaitingOnMeKeysとして、自Jobが実行するための</div>
<div>
埋め待ちSlotの情報を保持している。</div>
</div>
<script class="brush:java; gutter:false;" title="PipelineManager.java" type="syntaxhighlighter">
<![CDATA[
// For each slot that the barrier is waiting on...
for (SlotDescriptor sd : barrier.getWaitingOnInflated()) {
// see if it is full.
if (!sd.slot.isFilled()) {
logger.finest("Not filled: " + sd.slot);
shouldBeReleased = false;
break;
}
}
if (shouldBeReleased) {
//Generate task of RUNJob and FinalizeJob.
}
]]>
</script>
<br />
<div>
<br />
・FutureValue,ImidiateValueについて<br />
FutureValueは処理待ちのJobのOutPutSlotの情報を保持している。<br />
ImidiateValueは言葉の通り即値であり、HANDLE_SLOT_FILLEDのTaskが直後に行われる。<br />
<br />
・Slotの配置(Keyの採番)について<br />
同期化されたメソッドによって採番されている。(Slotの競合は起きない)<br />
<br />
<h3>
課題、その他</h3>
・非同期パイプライン、人間のジョブを挟むパイプライン処理<br />
・各ステージの分割の仕方、最適化について<br />
・どれだけスケールするか、Quotaについて等<br />
・AppEngineMapperとの複合(ここはあまり興味なくなってきたかも)<br />
<br />
引き続きGoogleAppEngine PipelineAPIをいじくってみる。<br />
でも気になるから、Cascadingもさわってみるかな。<br />
<br />
続く。</div>cloudysunny14http://www.blogger.com/profile/09953267600910932223noreply@blogger.com0tag:blogger.com,1999:blog-8436410939148734220.post-29724551817443325792012-05-20T21:10:00.000+09:002012-08-06T11:45:17.808+09:00Google App Engine Pipeline APIを使ってみた。先日<a href="https://developers.google.com/appengine/">Google App Engine</a>の<a href="http://code.google.com/p/appengine-mapreduce/">MapReduce</a>を使ってみた記事を書いてみたが、<br />
関連して<a href="http://code.google.com/p/appengine-pipeline/">PipelineAPI</a>の存在を知った。<br />
今回は<a href="http://code.google.com/p/appengine-pipeline/">PipelineAPI</a>について使ってみた感想を書く。<br />
<br />
まず、<a href="http://code.google.com/p/appengine-pipeline/">PipelineAPI</a>について、<br />
<a href="http://code.google.com/p/appengine-pipeline/">PipelineAPI</a>は、複雑で時間を消費するワークフローを接続し処理する。<br />
APIの主要な使用ケースは<a href="https://developers.google.com/appengine/">Google App Engine</a> <a href="http://code.google.com/p/appengine-mapreduce/">MapReduce</a>との接続である。<br />
(訳:筆者※英語力低)<br />
<br />
こんな図形まで載っている。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://2.bp.blogspot.com/-HuUJJ_RyauY/T7jPRsbmqPI/AAAAAAAAACs/ffGo7A4TrL0/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-05-20+20.01.35%EF%BC%89.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-HuUJJ_RyauY/T7jPRsbmqPI/AAAAAAAAACs/ffGo7A4TrL0/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-05-20+20.01.35%EF%BC%89.png" /></a></div>
<br />
<br />
<br />
<br />
<br />
<br />
<br />
この図形の示す意味は、下記のとおり。<br />
<br />
<br />
フレームワークは、ユーザが1つの仕事の出力が1つ以上の仕事の入力になる多数の仕事の準備を表現することを可能にします。<br />
これらの準備は、最もAの出力がBの入力スロットに向けられるべきであることを仕事Aから仕事Bの中の入力スロットのうちの1つまでの有向辺が示す一種の有向グラフと評することができます。<br />
例えば、私たちは、3つの整数入力をとる仕事、x、y、zを構築するようにDiffJobとMultJobを使用し、次の計算[(x -y)*(x -z)]を行ないます-2.<br />
その計算は次の仕事グラフとして表現されるかもしれません。<br />
(グラフは右から左まで読みます。)<br />
(Powered by Excite.翻訳)<br />
<div>
<br /></div>
<div>
なるほど、要は処理を<s>分散</s>並行させる事ができるんだな。(ん、分かりづらい?;)</div>
<div>
<br /></div>
<div>
ま、やっぱり実際に触ってみる方が早い。</div>
<div>
<a href="http://code.google.com/p/appengine-pipeline/wiki/GettingStartedJava">GettingStart</a>に従って、プロジェクト作成してみよう。</div>
<div>
サンプルでは文字の出現回数について集計を行うサンプルがあったが、</div>
<div>
ヒヨッコHadooperとしてはWordCountがやりたい。</div>
<div>
早速作ってみた。</div>
<script class="brush:java; gutter:false;" title="WordCountExample.java" type="syntaxhighlighter">
<![CDATA[
public class WordCountExample {
public static class WordCounter extends Job1<SortedMap<String, Integer>, String> {
@Override
public Value<SortedMap<String, Integer>> run(String text) {
String[] splits = text.split("\r\n");
List<FutureValue<SortedMap<String, Integer>>> countsForWords =
new LinkedList<FutureValue<SortedMap<String, Integer>>>();
for (String record:splits) {
countsForWords.add(futureCall(new MapperJob(), immediate(record)));
}
return futureCall(new ReduceJob(), futureList(countsForWords));
}
}
public static class MapperJob extends Job1<SortedMap<String, Integer>, String> {
@Override
public Value<SortedMap<String, Integer>> run(String word) {
return immediate(countStrings(word));
}
}
public static SortedMap<String, Integer> countStrings(String text) {
SortedMap<String, Integer> stringMap = new TreeMap<String, Integer>();
StringTokenizer tokenizer = new StringTokenizer(text);
while (tokenizer.hasMoreTokens()) {
incrementCount(tokenizer.nextToken(), 1, stringMap);
}
return stringMap;
}
public static class ReduceJob extends
Job1<SortedMap<String, Integer>, List<SortedMap<String, Integer>>> {
@Override
public Value<SortedMap<String, Integer>> run(List<SortedMap<String, Integer>> listOfMaps) {
SortedMap<String, Integer> totalMap = new TreeMap<String, Integer>();
for (SortedMap<String, Integer> stringMap : listOfMaps) {
for (Entry<String, Integer> pair : stringMap.entrySet()) {
incrementCount(pair.getKey(), pair.getValue(), totalMap);
}
}
return immediate(totalMap);
}
}
private static void incrementCount(String string, int increment, Map<String, Integer> stringMap) {
Integer countInteger = stringMap.get(string);
int count = (null == countInteger ? 0 : countInteger) + increment;
stringMap.put(string, count);
}
}
]]>
</script>
<br />
<div>
<a href="http://code.google.com/p/appengine-pipeline/">PipelineAPI</a>の詳細については追って調査し、紹介しようと思う。<br />
MapReduceっぽく実装してみただけで、本家のMapReduceとは<br />
かけ離れているので注意。<br />
Exampleで使用してるViewをそのまま使って、Let's Deploy!<br />
<br />
文字列を適当に入力。そして実行。<br />
<a href="http://4.bp.blogspot.com/-N4oviS_xnrI/T7jahFv7rQI/AAAAAAAAADE/jrowgSXHnW4/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-05-20+20.47.50%EF%BC%89.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-N4oviS_xnrI/T7jahFv7rQI/AAAAAAAAADE/jrowgSXHnW4/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-05-20+20.47.50%EF%BC%89.png" /></a><br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
処理中…。<br />
<a href="http://3.bp.blogspot.com/-33cjl0jbOfs/T7jbEx5AhjI/AAAAAAAAADU/EgobVlJtu4M/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-05-20+20.48.03%EF%BC%89.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-33cjl0jbOfs/T7jbEx5AhjI/AAAAAAAAADU/EgobVlJtu4M/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-05-20+20.48.03%EF%BC%89.png" /></a><br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
完了。ちゃんと数えられている。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://3.bp.blogspot.com/-8PXaMMbktbo/T7jbWzBu7eI/AAAAAAAAADc/3tdpeMT-x9M/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-05-20+20.48.10%EF%BC%89.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-8PXaMMbktbo/T7jbWzBu7eI/AAAAAAAAADc/3tdpeMT-x9M/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-05-20+20.48.10%EF%BC%89.png" /></a></div>
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
管理コンソール。今回は子のプロセスが3つ。<br />
<a href="http://3.bp.blogspot.com/-iSvyygdzOTI/T7jbljIC7YI/AAAAAAAAADk/9Rurb7F_XBg/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-05-20+20.48.34%EF%BC%89.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="146" src="http://3.bp.blogspot.com/-iSvyygdzOTI/T7jbljIC7YI/AAAAAAAAADk/9Rurb7F_XBg/s320/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-05-20+20.48.34%EF%BC%89.png" width="320" /></a><br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
子のプロセスの詳細も見れる。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://4.bp.blogspot.com/-rnuYRGTJ8O4/T7jb5HmLLDI/AAAAAAAAADs/HutjSlUvu9M/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-05-20+20.48.45%EF%BC%89.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="184" src="http://4.bp.blogspot.com/-rnuYRGTJ8O4/T7jb5HmLLDI/AAAAAAAAADs/HutjSlUvu9M/s320/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-05-20+20.48.45%EF%BC%89.png" width="320" /></a></div>
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
使ってみた感想。正直面白い。これは<a href="http://hadoop.apache.org/mapreduce/">MapReduce</a>も夢じゃない。はず。<br />
(処理時間は正直早いとは言えないが、少量データだし。)<br />
<a href="https://developers.google.com/appengine/">Google App Engine</a> <a href="http://code.google.com/p/appengine-pipeline/">PipelineAPI</a>の中身までまだ追いきれていないが、<br />
これは<a href="http://hadoop.apache.org/mapreduce/">MapReduce</a>とはまた違ったアプローチで大量データの処理も<br />
できるのではなかろうか。うん、なんか面白そう。<br />
<br />
今度は、 <a href="http://code.google.com/p/appengine-pipeline/">PipelineAPI</a>の中身に迫ってみるか。<br />
(<a href="https://developers.google.com/appengine/">Google App Engine</a> <a href="http://code.google.com/p/appengine-mapreduce/">MapReduce</a>…、とりあえず、頭の片隅には入れておこう)<br />
<br />
<br />
<br />
<a name='more'></a><br />
<br />
<br /></div>cloudysunny14http://www.blogger.com/profile/09953267600910932223noreply@blogger.com0tag:blogger.com,1999:blog-8436410939148734220.post-80242918491649203722012-05-13T19:49:00.000+09:002012-06-10T09:37:26.679+09:00Twitter Ambroseを使ってみた。GitHubを眺めていたら気になるプロダクトがあったので、<br />
早速使ってみた。<br />
<br />
<a href="https://github.com/twitter/ambrose">Twitter Ambrose</a>とは、<br />
MapReduceジョブをリアルタイムにビジュアル化してモニタリングするツール。<br />
要は、MapReduceジョブを見える化して最適化のお手伝いをするツールである。<br />
<br />
こんな感じ。<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://2.bp.blogspot.com/-fGH2rk5iTy0/T69___RwKNI/AAAAAAAAACU/gx9TsLdKBFI/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-05-13+18.31.12%EF%BC%89.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="210" src="http://2.bp.blogspot.com/-fGH2rk5iTy0/T69___RwKNI/AAAAAAAAACU/gx9TsLdKBFI/s320/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-05-13+18.31.12%EF%BC%89.png" width="320" /></a></div>
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<a href="http://twitter.github.com/bootstrap/">Bootstrap</a>を使ったUIで洗練されていてカッコいい。<br />
このグラフの意味するものは、下記の通り。(直訳)<br />
<br />
円の上の弧セグメントはそれぞれMapReduceジョブを表わします。<br />
ジョブ同士の依存性は、セグメントを接続する弦によって表わされます。<br />
灰色のジョブはまだ走っていません。明るいグリーン・ジョブは走っています。<br />
また、ライトグリーン仕事は終わります。<br />
ジョブがそれぞれ二分されることに着目してみてください。<br />
弧の2分の1の上の弦は先行ジョブに接続します。その一方で他方の半分上の弦は後継者仕事に接続しています。<br />
例えば、仕事より下の図形では、10と13は前任者を持っていません。<br />
また、仕事8および18はブタ・ワークフローでの最終仕事です。<br />
示された弦図形が私たちの最初であることに注目する、<br />
ワークフローの視覚化で通過する、また、改良の余地があります。<br />
私たちは、ワークフローDAGのグラフのように、同様に他のビジュアル化を支援したい。<br />
改善されたビジュアル化を開発する場合は、必ず私たちに引くことリクエストを送ってください!<br />
(powered by Exceite.翻訳)<br />
<br />
<br />
<br />
で、早速使ってみた。<br />
今回は、Hadoopの疑似分散環境で実行してみる。<br />
また、実行環境は<a href="http://pig.apache.org/">Pig</a>を選択した。<br />
<br />
テストデータとして以下のような入力データを準備<br />
<br />
<pre class="brush:java" title="one.txt">a A 1
b B 2
c C 3
a AA 11
a AAA 111
</pre>
<br />
実行するPigスクリプトは<br />
<br />
<pre class="brush:java" title="pigScript.pig">one = load 'input/one.txt';
grouped = GROUP one BY $0;
summed = FOREACH grouped GENERATE group, SUM(one.$2);
DUMP summed;
</pre>
<br />
<br />
そして、実行してみた。
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://3.bp.blogspot.com/-TvGURHxP-WU/T6-J3B9-NmI/AAAAAAAAACg/fdsm-48mdc8/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-05-13+17.55.43%EF%BC%89.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="202" src="http://3.bp.blogspot.com/-TvGURHxP-WU/T6-J3B9-NmI/AAAAAAAAACg/fdsm-48mdc8/s320/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-05-13+17.55.43%EF%BC%89.png" width="320" /></a></div>
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
んー、実にシンプル。疑似分散環境(対象データの量が少ない)だから仕方ない。<br />
でもすんなり導入できそう。<br />
<br />
現在、サポートしている実行環境はPigだけで、<br />
今後、サポートする実行環境を増やしていくそう。<br />
<br />
Twitter AmbroseをGAEで動かすぞ。と意気込んではみたが、<br />
課題が。(まずはMapReduceをなんとかせんと、そしてPig…orz)<br />
<br />
ってまあ僕自身もまだ生まれたてのヒヨッコHadooperなので、<br />
まずは、見た目重視ってことで。<br />
<br />
ではでは、次回はGAEのMapReduceの続きでも。<br />
<br />
<br />
<br />cloudysunny14http://www.blogger.com/profile/09953267600910932223noreply@blogger.com0tag:blogger.com,1999:blog-8436410939148734220.post-35728534455242064002012-05-12T22:26:00.001+09:002012-06-10T09:38:04.649+09:00Google App EngineのMapReduceを使ってみた。<span class="Apple-style-span" style="color: #222222; font-family: inherit; line-height: 16px;"><em style="color: black; font-style: normal;">遅ればせながら <a href="http://code.google.com/p/appengine-mapreduce/">GoogleAppEngine MapReduce</a> を使ってみた。</em></span><br />
<div>
<span class="Apple-style-span" style="font-family: inherit; line-height: 16px;">まずは、<a href="http://code.google.com/p/appengine-mapreduce/wiki/GettingStartedInJava">GettingStart</a>に従って、Jarファイルを生成する。</span></div>
<div>
<span class="Apple-style-span" style="font-family: inherit; line-height: 16px;"><br /></span></div>
<div>
<span class="Apple-style-span" style="font-family: inherit; line-height: 16px;">生成されたJarファイルは6つ。これらを使ってExampleを動かしてみるのが今回の目標。</span></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="http://3.bp.blogspot.com/-IQmAiG0OxrU/T65Jq0FkyfI/AAAAAAAAABs/sQomakmq3zE/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-05-12+20.28.35%EF%BC%89.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><span class="Apple-style-span" style="font-family: inherit;"><img border="0" height="75" src="http://3.bp.blogspot.com/-IQmAiG0OxrU/T65Jq0FkyfI/AAAAAAAAABs/sQomakmq3zE/s320/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-05-12+20.28.35%EF%BC%89.png" width="320" /></span></a></div>
<div>
<span class="Apple-style-span" style="font-family: inherit; line-height: 16px;"><br /></span></div>
<div>
<span class="Apple-style-span" style="font-family: inherit; line-height: 16px;"><br /></span></div>
<div>
<span class="Apple-style-span" style="font-family: inherit; line-height: 16px;"><br /></span></div>
<div>
<span class="Apple-style-span" style="font-family: inherit; line-height: 16px;"><br /></span></div>
<div>
<span class="Apple-style-span" style="font-family: inherit; line-height: 16px;"><br /></span></div>
<div>
<span class="Apple-style-span" style="font-family: inherit; line-height: 16px;"><br /></span></div>
<div>
<span class="Apple-style-span" style="font-family: inherit; line-height: 16px;">続いて、プロジェクトの作成を行う。</span></div>
<div>
<span class="Apple-style-span" style="font-family: inherit; line-height: 16px;">個人的にGoogleAppEngine(以後、GAE)のアプリケーションを作るときは、</span></div>
<div>
<span class="Apple-style-span" style="font-family: inherit;"><span class="Apple-style-span" style="line-height: 16px;"><a href="http://sites.google.com/site/slim3documentja/">Slim3</a>ベースのプロジェクトを使うのが楽チンなので、今回も</span><span class="Apple-style-span" style="line-height: 16px;"><a href="http://sites.google.com/site/slim3documentja/">Slim3</a></span><span class="Apple-style-span" style="line-height: 16px;">で。</span></span></div>
<div>
<span class="Apple-style-span" style="font-family: inherit; line-height: 16px;"><br /></span><br />
<span class="Apple-style-span" style="font-family: inherit;">Exampleでは、"PBFVotes"というKindに400件のEntityを挿入してそのうち"skub"プロパティーの値で"pro"と"anti"、それぞれ設定されている件数を数えるというもの。</span><br />
<span class="Apple-style-span" style="font-family: inherit;">ちなみに付属のExampleではMapper処理完了のコールバックを登録していないものなので、</span><br />
<span class="Apple-style-span" style="font-family: inherit;">最終的な集計結果は得られない。Mapper処理完了のコールバックを登録して、実行しよう。</span><br />
<span class="Apple-style-span" style="font-family: inherit;"><br /></span><br />
<span class="Apple-style-span" style="font-family: inherit;">実行結果。めっちゃログ吐くし。。</span><br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://4.bp.blogspot.com/-7nUp67PpqAE/T65gSZB8qDI/AAAAAAAAAB4/co7cMrdfA3I/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-05-12+22.05.50%EF%BC%89.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><span class="Apple-style-span" style="clear: left; float: left; font-family: inherit; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="174" src="http://4.bp.blogspot.com/-7nUp67PpqAE/T65gSZB8qDI/AAAAAAAAAB4/co7cMrdfA3I/s320/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-05-12+22.05.50%EF%BC%89.png" width="320" /></span></a></div>
<span class="Apple-style-span" style="font-family: inherit;"><br /></span><br />
<span class="Apple-style-span" style="font-family: inherit;"><br /></span><br />
<span class="Apple-style-span" style="font-family: inherit;"><br /></span><br />
<span class="Apple-style-span" style="font-family: inherit;"><br /></span><br />
<span class="Apple-style-span" style="font-family: inherit;"><br /></span><br />
<span class="Apple-style-span" style="font-family: inherit;"><br /></span><br />
<span class="Apple-style-span" style="font-family: inherit;"><br /></span><br />
<span class="Apple-style-span" style="font-family: inherit;"><br /></span><br />
<span class="Apple-style-span" style="font-family: inherit;"><br /></span><br />
<span class="Apple-style-span" style="font-family: inherit;"><br /></span><br />
<br />
<span class="Apple-style-span" style="font-family: inherit;">でも結果バッチリ。</span><br />
<a href="http://1.bp.blogspot.com/-EFDQSjJQqY4/T65gtgs9ZkI/AAAAAAAAACA/vIt2YzNL5QE/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-05-12+22.07.30%EF%BC%89.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><span class="Apple-style-span" style="clear: left; float: left; font-family: inherit; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="50" src="http://1.bp.blogspot.com/-EFDQSjJQqY4/T65gtgs9ZkI/AAAAAAAAACA/vIt2YzNL5QE/s320/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-05-12+22.07.30%EF%BC%89.png" width="320" /></span></a><span class="Apple-style-span" style="font-family: inherit;"><br /></span><br />
<span class="Apple-style-span" style="font-family: inherit;"><br /></span><br />
<span class="Apple-style-span" style="font-family: inherit;"><br /></span><br />
<br />
<span class="Apple-style-span" style="font-family: inherit;">さらに管理コンソール付き。なんかかっこいい。</span><br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://2.bp.blogspot.com/-fLZhxhAyooU/T65hHlz4daI/AAAAAAAAACI/zoNJMlEhw8E/s1600/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-05-12+22.09.24%EF%BC%89.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><span class="Apple-style-span" style="clear: left; float: left; font-family: inherit; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="121" src="http://2.bp.blogspot.com/-fLZhxhAyooU/T65hHlz4daI/AAAAAAAAACI/zoNJMlEhw8E/s320/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%EF%BC%882012-05-12+22.09.24%EF%BC%89.png" width="320" /></span></a></div>
<span class="Apple-style-span" style="font-family: inherit;"><br /></span><br />
<span class="Apple-style-span" style="font-family: inherit;"><br /></span><br />
<span class="Apple-style-span" style="font-family: inherit;"><br /></span><br />
<span class="Apple-style-span" style="font-family: inherit;"><br /></span><br />
<span class="Apple-style-span" style="font-family: inherit;"><br /></span><br />
<span class="Apple-style-span" style="font-family: inherit;"><br /></span><br />
<span class="Apple-style-span" style="font-family: inherit;"><br /></span><br />
<br />
使ってみた感想。<br />
<span class="Apple-style-span" style="font-family: inherit;">今回は少ないデータ量だったので、あまり分散された感と処理が速くなった感はない。</span><br />
<span class="Apple-style-span" style="font-family: inherit;">しかもMapフェーズまでは本家Hadoop MapReduceっぽいのだが、</span><br />
<span class="Apple-style-span" style="font-family: inherit;">以降、Shuffle,Sort,Reduceフェーズがなく、尻切れとんぼ(言葉が悪いが)感が否めない。</span><br />
<span class="Apple-style-span" style="font-family: inherit;">ただ、Mapperを独自実装したり、Shard数を増やしてみたりといろいろ試しがいはある。</span><br />
<span class="Apple-style-span" style="font-family: inherit;"><a href="http://www.publickey1.jp/blog/11/mapreducegoogle_app_engine.html">グーグル、フル機能のMapReduceをGoogle App Engineで提供へ</a> 記事でもあるように、</span><br />
<span class="Apple-style-span" style="font-family: inherit;">Python版はフル機能が実装されているらしい。Javaの近いうちにフル機能が実装されるのだろうか。</span><br />
<span class="Apple-style-span" style="font-family: inherit;">期待して待っていよう。</span><br />
<span class="Apple-style-span" style="font-family: inherit;">それかShuffle以降の実装も独自にすすめてみるのも。</span><br />
<span class="Apple-style-span" style="font-family: inherit;">AppEngineReducerという呼ばれる気配のないクラスがあったし。</span><br />
<span class="Apple-style-span" style="font-family: inherit;">次回も引き続きGAE Mapreduce。中身に迫る。(予定)</span><br />
<span class="Apple-style-span" style="font-family: inherit; font-size: x-small;"><br /></span><br />
<span class="Apple-style-span" style="font-family: inherit; font-size: x-small;"><br /></span><br />
<span class="Apple-style-span" style="font-family: inherit; font-size: x-small;"><br /></span><br />
<span class="Apple-style-span" style="font-family: inherit; font-size: x-small;"><br /></span><br />
<span class="Apple-style-span" style="font-family: Arial, Verdana, sans-serif; font-size: x-small;"><br /></span></div>cloudysunny14http://www.blogger.com/profile/09953267600910932223noreply@blogger.com0tag:blogger.com,1999:blog-8436410939148734220.post-48596005291621911682012-05-06T12:17:00.001+09:002012-06-10T09:38:14.703+09:00Ohlohj Ver1.0.0リリース<span class="Apple-style-span" style="font-family: inherit;"><a href="https://github.com/cloudysunny14/Ohlohj">Ohlohj</a> Ver1.0.0をリリース致しました。</span><br />
<span class="Apple-style-span" style="font-family: inherit;"><a href="https://github.com/cloudysunny14/Ohlohj">Ohlohj</a>は<a href="https://www.ohloh.net/">Ohloh</a>というオープンソース<span class="Apple-style-span" style="line-height: 22px;">ソフトウェア開発を見通すことを目的とした、Webサービス</span><span class="Apple-style-span" style="line-height: 22px;">スイートとオンラインコミュニティ</span><span class="Apple-style-span" style="line-height: 22px;">プラットフォームを擁するウェブサイトで、サポートするREST APIのJavaラッパが</span><a href="https://github.com/cloudysunny14/Ohlohj">Ohlohj</a><span class="Apple-style-span" style="color: #331144;"> </span><span class="Apple-style-span" style="line-height: 22px;">となります。</span></span><br />
<span class="Apple-style-span" style="font-family: inherit;"><a href="https://github.com/cloudysunny14/Ohlohj">Ohlohj</a><span class="Apple-style-span" style="color: #331144;"> </span><span class="Apple-style-span" style="line-height: 22px;">は</span><a href="https://www.ohloh.net/">Ohloh</a><span class="Apple-style-span" style="line-height: 22px;">非公式ライブラリです。</span></span><br />
<span class="Apple-style-span" style="font-family: inherit;"><span class="Apple-style-span" style="line-height: 22px;">XMLの解析やOAuth認証など面倒な作業はすべて</span><a href="https://github.com/cloudysunny14/Ohlohj">Ohlohj</a><span class="Apple-style-span" style="color: #331144;"> </span><span class="Apple-style-span" style="line-height: 22px;">が処理します。</span></span><br />
<span class="Apple-style-span" style="line-height: 22px;"><span class="Apple-style-span" style="font-family: inherit;">まずはohlohj.OhlohAPIインターフェースのJavadocを見るのが早いです。</span></span><br />
<span class="Apple-style-span" style="line-height: 22px;"><span class="Apple-style-span" style="font-family: inherit;">また、Google APP Engine上でも動作し、標準で非同期処理をサポートしています。</span></span><br />
<span class="Apple-style-span" style="line-height: 22px;"><span class="Apple-style-span" style="font-family: inherit;"><br /></span></span><br />
<span class="Apple-style-span" style="line-height: 22px;"><b><span class="Apple-style-span" style="font-family: inherit;">使い方</span></b></span><br />
<span class="Apple-style-span" style="line-height: 22px;"><span class="Apple-style-span" style="font-family: inherit;">・標準のWebアプリケーションの場合</span></span><br />
<span class="Apple-style-span" style="line-height: 22px;"><span class="Apple-style-span" style="color: #333333;"><span class="Apple-style-span" style="font-family: inherit;">ohlohj-core-1.0.0.jarを</span></span></span><span class="Apple-style-span" style="color: #333333; line-height: 22px;"><a href="https://github.com/cloudysunny14/Ohlohj/downloads">Downloadページ</a></span><span class="Apple-style-span" style="line-height: 22px;"><span class="Apple-style-span" style="color: #333333;"><span class="Apple-style-span" style="font-family: inherit;">よりDownloadし、クラスパスに通すだけ。後は好きなAPIを呼び出してください。</span></span></span><br />
<span class="Apple-style-span" style="line-height: 22px;"><span class="Apple-style-span" style="color: #333333;"><span class="Apple-style-span" style="font-family: inherit;">・Google APP Engine上で動作させる場合</span></span></span><br />
<span class="Apple-style-span" style="color: #333333; line-height: 22px;">l</span><span class="Apple-style-span" style="font-family: inherit;"><span class="Apple-style-span" style="line-height: 22px;"><span class="Apple-style-span" style="color: #333333;">ohloh-core-1.0.0.jarと</span></span><span class="Apple-style-span" style="color: #333333; line-height: 22px;">ohlohj-appengine-1.0.0.jarを</span></span><span class="Apple-style-span" style="color: #333333; line-height: 22px;"><a href="https://github.com/cloudysunny14/Ohlohj/downloads">Downloadページ</a></span><span class="Apple-style-span" style="font-family: inherit;"><span class="Apple-style-span" style="color: #333333; line-height: 22px;">よりDownloadしクラスパスに通せば、非同期処理が行えます。</span></span><br />
<span class="Apple-style-span" style="color: #333333; line-height: 22px;"><span class="Apple-style-span" style="font-family: inherit;">(上記標準のWebアプリケーションと同じ手順でも動作いたしますが、非同期処理はできません)</span></span><br />
<span class="Apple-style-span" style="color: #333333; line-height: 22px;"><span class="Apple-style-span" style="font-family: inherit;"><br /></span></span><br />
<span class="Apple-style-span" style="color: #333333; font-family: inherit;"><span class="Apple-style-span" style="line-height: 22px;"><b>ライセンス</b></span></span><br />
<span class="Apple-style-span" style="font-family: inherit;"><a href="https://github.com/cloudysunny14/Ohlohj">Ohlohj</a><span class="Apple-style-span" style="color: #331144;"> は <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a> に基づいてリリースされています。</span></span><br />
<span class="Apple-style-span" style="font-family: inherit;"><span class="Apple-style-span" style="color: #331144; font-size: 14px;"><br /></span></span><br />
<span class="Apple-style-span" style="font-family: inherit;"><span class="Apple-style-span" style="color: #331144; font-size: 14px;"><br /></span></span><br />
<span class="Apple-style-span" style="color: #331144; font-family: Verdana, Arial, sans-serif; font-size: 14px;"><br /></span>cloudysunny14http://www.blogger.com/profile/09953267600910932223noreply@blogger.com0