Google App Engine で特定の種類のデータをすべて消去したいと考えています。これを行う最善の方法は何ですか?削除スクリプト(ハック)を書いたのですが、データが多すぎて数百件でタイムアウトしてしまいました。
19 に答える
現在、エンティティをキーで削除していますが、より高速なようです。
from google.appengine.ext import db
class bulkdelete(webapp.RequestHandler):
def get(self):
self.response.headers['Content-Type'] = 'text/plain'
try:
while True:
q = db.GqlQuery("SELECT __key__ FROM MyModel")
assert q.count()
db.delete(q.fetch(200))
time.sleep(0.5)
except Exception, e:
self.response.out.write(repr(e)+'\n')
pass
ターミナルから curl -N http://... を実行します。
そのために Datastore Admin を使用できるようになりました: https://developers.google.com/appengine/docs/adminconsole/datastoreadmin#Deleting_Entities_in_Bulk
私が偏執狂的な人なら、Google App Engine (GAE) は、必要に応じてデータを簡単に削除できないと言うでしょう。インデックス サイズと、それらが 6 GB のデータを 35 GB のストレージ (課金対象) に変換する方法についての説明はスキップします。それは別の話ですが、それを回避する方法があります-インデックスを作成するプロパティの数を制限します(自動生成されたインデックス)など。
私がこの投稿を書くことにした理由は、サンドボックスですべての種類を「核攻撃」する必要があるからです。私はそれについて読んで、最終的にこのコードを思いつきました:
package com.intillium.formshnuker;
import java.io.IOException;
import java.util.ArrayList;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.labs.taskqueue.QueueFactory;
import com.google.appengine.api.labs.taskqueue.TaskOptions.Method;
import static com.google.appengine.api.labs.taskqueue.TaskOptions.Builder.url;
@SuppressWarnings("serial")
public class FormsnukerServlet extends HttpServlet {
public void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
response.setContentType("text/plain");
final String kind = request.getParameter("kind");
final String passcode = request.getParameter("passcode");
if (kind == null) {
throw new NullPointerException();
}
if (passcode == null) {
throw new NullPointerException();
}
if (!passcode.equals("LONGSECRETCODE")) {
response.getWriter().println("BAD PASSCODE!");
return;
}
System.err.println("*** deleting entities form " + kind);
final long start = System.currentTimeMillis();
int deleted_count = 0;
boolean is_finished = false;
final DatastoreService dss = DatastoreServiceFactory.getDatastoreService();
while (System.currentTimeMillis() - start < 16384) {
final Query query = new Query(kind);
query.setKeysOnly();
final ArrayList<Key> keys = new ArrayList<Key>();
for (final Entity entity: dss.prepare(query).asIterable(FetchOptions.Builder.withLimit(128))) {
keys.add(entity.getKey());
}
keys.trimToSize();
if (keys.size() == 0) {
is_finished = true;
break;
}
while (System.currentTimeMillis() - start < 16384) {
try {
dss.delete(keys);
deleted_count += keys.size();
break;
} catch (Throwable ignore) {
continue;
}
}
}
System.err.println("*** deleted " + deleted_count + " entities form " + kind);
if (is_finished) {
System.err.println("*** deletion job for " + kind + " is completed.");
} else {
final int taskcount;
final String tcs = request.getParameter("taskcount");
if (tcs == null) {
taskcount = 0;
} else {
taskcount = Integer.parseInt(tcs) + 1;
}
QueueFactory.getDefaultQueue().add(
url("/formsnuker?kind=" + kind + "&passcode=LONGSECRETCODE&taskcount=" + taskcount).method(Method.GET));
System.err.println("*** deletion task # " + taskcount + " for " + kind + " is queued.");
}
response.getWriter().println("OK");
}
}
私は600万件以上のレコードを持っています。それは沢山。レコードを削除するための費用がいくらになるかわかりません (削除しない方が経済的かもしれません)。もう 1 つの方法は、アプリケーション全体 (サンドボックス) の削除を要求することです。しかし、それはほとんどの場合現実的ではありません。
レコードのグループを小さくすることにしました (簡単なクエリで)。500 個のエンティティを使用できることはわかっていますが、非常に高い確率で失敗するようになりました (再削除機能)。
GAE チームからの私の要求: 単一のトランザクションで同じ種類のすべてのエンティティを削除する機能を追加してください。
おそらくあなたのハックは次のようなものでした:
# Deleting all messages older than "earliest_date"
q = db.GqlQuery("SELECT * FROM Message WHERE create_date < :1", earliest_date)
results = q.fetch(1000)
while results:
db.delete(results)
results = q.fetch(1000, len(results))
あなたが言うように、十分なデータがある場合、それがすべてのレコードを通過する前に、リクエストのタイムアウトに達するでしょう。すべてのデータが確実に消去されるようにするには、外部からこのリクエストを複数回再起動する必要があります。簡単にできますが、理想的とは言えません。
管理コンソールは、(私自身の経験から)特定のタイプのエンティティのみをリストしてからページごとに削除できるように見えるため、何の助けにもならないようです。
テストするとき、既存のデータを取り除くために、起動時にデータベースをパージする必要がありました。
このことから、Googleはディスクが安価であるという原則に基づいて動作しているため、データは削除されるのではなく、通常は孤立している(冗長データのインデックスが置き換えられている)と推測されます。現在、各アプリで利用できるデータの量は固定されているため(0.5 GB)、GoogleAppEngine以外のユーザーにとってはあまり役に立ちません。
App Engine Consoleを使用してみてください。そうすれば、特別なコードをデプロイする必要もありません。
db.delete(results) と App Engine Console を試しましたが、どれもうまくいかないようです。10000 を超えるエントリをアップロードしたため、Data Viewer からエントリを手動で削除しても (上限を 200 まで増やしました)、機能しませんでした。このスクリプトを書き終えました
from google.appengine.ext import db
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
import wsgiref.handlers
from mainPage import YourData #replace this with your data
class CleanTable(webapp.RequestHandler):
def get(self, param):
txt = self.request.get('table')
q = db.GqlQuery("SELECT * FROM "+txt)
results = q.fetch(10)
self.response.headers['Content-Type'] = 'text/plain'
#replace yourapp and YouData your app info below.
self.response.out.write("""
<html>
<meta HTTP-EQUIV="REFRESH" content="5; url=http://yourapp.appspot.com/cleanTable?table=YourData">
<body>""")
try:
for i in range(10):
db.delete(results)
results = q.fetch(10, len(results))
self.response.out.write("<p>10 removed</p>")
self.response.out.write("""
</body>
</html>""")
except Exception, ints:
self.response.out.write(str(inst))
def main():
application = webapp.WSGIApplication([
('/cleanTable(.*)', CleanTable),
])
wsgiref.handlers.CGIHandler().run(application)
トリックは、self.redirect を使用する代わりに、html にリダイレクトを含めることでした。テーブル内のすべてのデータを削除するために、一晩待つ準備ができています。願わくば、GAE チームが将来、テーブルを簡単にドロップできるようにしてくれることを願っています。
グーグルからの公式の答えは、あなたが複数の要求にまたがるチャンクで削除しなければならないということです。エンティティがなくなるまで、AJAX、メタリフレッシュを使用するか、スクリプトからURLをリクエストできます。
Datastore で一括削除を処理する最速かつ効率的な方法は、最新のGoogle I/Oで発表された新しいマッパー APIを使用することです。
選択した言語がPythonの場合、マッパーをmapreduce.yamlファイルに登録し、次のような関数を定義するだけです。
from mapreduce import operation as op
def process(entity):
yield op.db.Delete(entity)
Java では、次のような関数を提案するこの記事を参照する必要があります。
@Override
public void map(Key key, Entity value, Context context) {
log.info("Adding key to deletion pool: " + key);
DatastoreMutationPool mutationPool = this.getAppEngineContext(context)
.getMutationPool();
mutationPool.delete(value.getKey());
}
1 つのヒント。これらのタイプの使用 (一括削除、変更など) については、 remote_apiについて理解することをお勧めします。ただし、リモート API を使用しても、バッチ サイズは一度に数百に制限される可能性があります。
残念ながら、一括削除を簡単に行う方法はありません。最善の策は、呼び出しごとに適切な数のエントリを削除するスクリプトを作成し、それを繰り返し呼び出すことです。 -max-redirect=10000" (またはその他の大きな数値)。
はい、できます。データストア管理に移動し、削除するエンティティ タイプを選択して、[削除] をクリックします。Mapreduce が削除を担当します。
Java/JPA を使用している場合は、次のようなことができます。
em = EntityManagerFactoryUtils.getTransactionalEntityManager(entityManagerFactory)
Query q = em.createQuery("delete from Table t");
int number = q.executeUpdate();
Java/JDO 情報は次の場所にあります: http://code.google.com/appengine/docs/java/datastore/queriesandindexes.html#Delete_By_Query
django を使用して、URL をセットアップします。
url(r'^Model/bdelete/$', v.bulk_delete_models, {'model':'ModelKind'}),
セットアップ ビュー
def bulk_delete_models(request, model):
import time
limit = request.GET['limit'] or 200
start = time.clock()
set = db.GqlQuery("SELECT __key__ FROM %s" % model).fetch(int(limit))
count = len(set)
db.delete(set)
return HttpResponse("Deleted %s %s in %s" % (count,model,(time.clock() - start)))
次に、powershell で実行します。
$client = new-object System.Net.WebClient
$client.DownloadString("http://your-app.com/Model/bdelete/?limit=400")
タスク キューを使用して、たとえば 100 個のオブジェクトのチャンクを削除できます。GAE でのオブジェクトの削除は、GAE での管理機能がいかに制限されているかを示しています。1000 個以下のエンティティでバッチを操作する必要があります。csv で動作するバルクローダー ツールを使用できますが、ドキュメントは Java をカバーしていません。私は GAE Java を使用しており、削除の戦略には 2 つのサーブレットが含まれます。1 つは実際に削除を行うためのもので、もう 1 つはタスク キューをロードするためのものです。削除したいときは、キューをロードするサーブレットを実行し、キューをロードしてから、GAE がキュー内のすべてのタスクを実行します。
方法: 少数のオブジェクトを削除するサーブレットを作成します。サーブレットをタスク キューに追加します。家に帰るか、別の作業をしてください ;) ときどきデータストアをチェックしてください ...
毎週消去する約 5000 個のオブジェクトを含むデータストアがあり、消去に約 6 時間かかるため、金曜日の夜にタスクを実行します。同じ手法を使用して、たまたま約 5000 個のオブジェクトであり、約 12 個のプロパティを持つデータを一括読み込みします。
みんなありがとう、私は必要なものを手に入れました。:D
これは、削除するデータベースモデルがたくさんある場合に便利です。ターミナルでディスパッチできます。また、DB_MODEL_LIST の削除リストを自分で管理することもできます。
DB_1 を削除します。
python bulkdel.py 10 DB_1
すべてのデータベースを削除:
python bulkdel.py 11
ここにbulkdel.pyファイルがあります:
import sys, os
URL = 'http://localhost:8080'
DB_MODEL_LIST = ['DB_1', 'DB_2', 'DB_3']
# Delete Model
if sys.argv[1] == '10' :
command = 'curl %s/clear_db?model=%s' % ( URL, sys.argv[2] )
os.system( command )
# Delete All DB Models
if sys.argv[1] == '11' :
for model in DB_MODEL_LIST :
command = 'curl %s/clear_db?model=%s' % ( URL, model )
os.system( command )
そして、これは alexandre fiori のコードの修正版です。
from google.appengine.ext import db
class DBDelete( webapp.RequestHandler ):
def get( self ):
self.response.headers['Content-Type'] = 'text/plain'
db_model = self.request.get('model')
sql = 'SELECT __key__ FROM %s' % db_model
try:
while True:
q = db.GqlQuery( sql )
assert q.count()
db.delete( q.fetch(200) )
time.sleep(0.5)
except Exception, e:
self.response.out.write( repr(e)+'\n' )
pass
もちろん、モデルへのリンクをファイル (GAE の main.py など) にマップする
必要があります。
from google.appengine.ext import webapp
import utility # DBDelete was defined in utility.py
application = webapp.WSGIApplication([('/clear_db',utility.DBDelete ),('/',views.MainPage )],debug = True)
これは私のために働いた:
class ClearHandler(webapp.RequestHandler):
def get(self):
self.response.headers['Content-Type'] = 'text/plain'
q = db.GqlQuery("SELECT * FROM SomeModel")
self.response.out.write("deleting...")
db.delete(q)
JavaScript では、次のようにすると、ページ上のすべてのエントリが削除されます。
document.getElementById("allkeys").checked=true;
checkAllEntities();
document.getElementById("delete_button").setAttribute("onclick","");
document.getElementById("delete_button").click();
削除したいエンティティがある管理ページ (.../_ah/admin) にいるとします。