Sat, June 20, 2009
Google App Engine データストア上のデータをまとめて消す方法
Google App Engineでデータストアに入れたデータを削除したい場合、
どうすればいいのか。
件数が少なければ
管理コンソール(Datastore - Data Viewer)上で削除することができる。
しかし件数が多いととてもこれでは対応できない。
Google App EngineのDatastoreの中身を全部消す とか、 GoogleAppEngineでremote_apiを使う では、件数が多い場合の対応方法が紹介されていたが、ちょっと面倒そうに思ったので、 以下の方法で対処することに。
普通にアプリケーション中にデータストア削除インタフェースを用意
アイデア自体は普通です。
データストア中に MyDataというデータモデルがあれば、そこにある実体データは、以下のコードに削除できます。
for data in MyData.all():
data.delete()
/RemoveAll などのURLを呼び出したら、このコードを実行するようにすればよい。
問題に対処
ただし、この方法では、データストアに登録されているデータ量が多い場合エラーになります。
その理由は、GAEの以下のような制限にひっかかってしまうためです。
- 一定時間内にリクエストを返すべし
- 一度に取り出せる量が1000件まで
- 使えるリソースの上限を超えた
したがって、これを回避するため以下のようにします。
- /RemoveAll というURLにアクセスするとデータストアを削除するプログラムを起動する
- /RemoveAll では、一度に20件程度、GAEの制約に触れない範囲で削除を実行する
- /RemoveAll 完了後、HTML metaの refresh によるリダイレクトにより ブラウザが 10秒後に再度自動的に /RemoveAll を呼び出す
このようにして、/RemoveAllにアクセスすると一度に20件ずつデータを削除するを ひたすら削除対象のデータ無くなるまで繰り返すようにします。
redirect間隔は10秒より短くても問題ないかもしれません。 必要なら管理コンソールで負荷状況を見て調整します。
具体的には
以上のアイデアを具体的にコードで表現すると以下のようになります。
from google.appengine.ext import webapp
from google.appengine.ext import db
from google.appengine.ext.webapp.util import run_wsgi_app
class MyData(db.Model):
pass
class RemoveAll(webapp.RequestHandler):
def get(self) :
limit = 10
list=self.getMyList(limit)
if len(list)<1:
self.response.headers['Content-Type'] = 'text/plain'
self.response.out.write( 'OK remove all data.' )
return
for c in list:
c.delete()
self.response.headers['Content-Type'] = 'text/html'
self.response.out.write( '<html>' )
self.response.out.write( '<head>' )
self.response.out.write( '<META HTTP-EQUIV="REFRESH" CONTENT="10;URL=/RemoveAll">')
self.response.out.write( '</head>' )
self.response.out.write( '<body>' )
self.response.out.write( 'Again after 10seconds.' )
self.response.out.write( '</body>' )
self.response.out.write( '</html>' )
def getMyList(self,limit):
query = MyData.gql('LIMIT '+str(limit))
return query[0:min(query.count(),limit)]
application = webapp.WSGIApplication(
[
('/RemoveAll', RemoveAll)
],
debug=True)
def main():
run_wsgi_app(application)
if __name__ == "__main__":
main()
データモデルが二種類以上ある場合...
以下のようにすると、モデルの種類が多い場合に便利です。
from google.appengine.ext import webapp
from google.appengine.ext import db
from google.appengine.ext.webapp.util import run_wsgi_app
class MyData1(db.Model):
pass
class MyData2(db.Model):
pass
class AbstractRemoveAll(webapp.RequestHandler):
def get(self) :
#limit = 50
limit = 100
list = self.getMyList(limit)
if list==None :
self.response.headers['Content-Type'] = 'text/plain'
self.response.out.write( 'Error.' )
return
if len(list)<1:
self.response.headers['Content-Type'] = 'text/plain'
self.response.out.write( 'OK remove all data.' )
return
for c in list:
c.delete()
self.response.headers['Content-Type'] = 'text/html'
self.response.out.write( '<html>' )
self.response.out.write( '<head>' )
self.response.out.write( '<META HTTP-EQUIV="REFRESH" CONTENT="10;URL='+self.getMyUrl()+'">')
self.response.out.write( '</head>' )
self.response.out.write( '<body>' )
self.response.out.write( '<p>' )
self.response.out.write( len(list) )
self.response.out.write( '</p>' )
self.response.out.write( 'Again after 10seconds.' )
self.response.out.write( '</body>' )
self.response.out.write( '</html>' )
def getMyUrl(self) :
return '/RemoveAll'
def getMyList(self,limit):
return []
class RemoveAll1(AbstractRemoveAll):
def getMyUrl(self) :
return '/RemoveAll1'
def getMyList(self,limit):
q=MyData1.gql('LIMIT '+str(limit))
return q[0:min(q.count(),limit)]
class RemoveAll2(AbstractRemoveAll):
def getMyUrl(self) :
return '/RemoveAll2'
def getMyList(self,limit):
q=MyData2.gql('LIMIT '+str(limit))
return q[0:min(q.count(),limit)]
application = webapp.WSGIApplication(
[
('/RemoveAll2', RemoveAllPage2),
('/RemoveAll1', RemoveAllPage1)
],
debug=True)
def main():
run_wsgi_app(application)
if __name__ == "__main__":
main()