Google App Engine で特定の種類のデータをすべて削除する
-
01-07-2019 - |
質問
Google App Engineの特定の種類のデータをすべて消去したいと考えています。これを行うための最良の方法は何ですか?削除スクリプト(ハック)を書きましたが、数百のレコードの後にはタイムアウトが非常に多いためです。
他のヒント
現在、エンティティをキーで削除していますが、その方が速いようです。
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 チームからのリクエスト:単一のトランザクションで同じ種類のすべてのエンティティを削除する機能を追加してください。
使ってみてください App Engine コンソール 特別なコードをデプロイする必要さえありません
おそらくあなたのハックは次のようなものでした。
# 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)であることを考えると、Google App Engine 以外のユーザーにとってはあまり役に立ちません。
db.delete(results) と App Engine Console を試しましたが、どれもうまく機能しないようです。10000 を超えるエントリをアップロードしたため、データ ビューアからエントリを手動で削除する (制限が 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 チームが将来的にテーブルを削除しやすくすることを願っています。
データストアの一括削除を処理する最も速く効率的な方法は、新しい マッパーAPI 最新の日に発表されました Google I/O.
あなたの選択した言語が パイソン, にマッパーを登録するだけです。 マップリデュース.yaml ファイルを作成し、次のような関数を定義します。
from mapreduce import operation as op
def process(entity):
yield op.db.Delete(entity)
の上 ジャワ 見てみるといいよ この記事 これは次のような関数を提案します。
@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 つあります。を知ることをお勧めします リモートAPI このような用途 (一括削除、変更など) に適しています。ただし、リモート API を使用しても、一度にバッチ サイズが数百に制限されることがあります。
残念ながら、一括削除を簡単に行う方法はありません。最善の策は、呼び出しごとに適切な数のエントリを削除するスクリプトを作成し、それを繰り返し呼び出すことです。たとえば、削除するデータがさらにあるたびに削除スクリプトに 302 リダイレクトを返し、それを「wget -」で取得します。 -max-redirect=10000" (またはその他の大きな数値)。
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")
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
はい、次のことができます:[データストア管理] に移動し、削除するエンティティ タイプを選択して [削除] をクリックします。Mapreduce が削除を処理します。
で 開発サーバー, 、アプリのディレクトリに cd して、次のように実行できます。
dev_appserver.py --clear_datastore=yes .
これにより、アプリが起動し、データストアがクリアされます。すでに別のインスタンスが実行されている場合、アプリは必要な IP にバインドできないため、起動に失敗し、データストアをクリアできません。
タスク キューを使用して、たとえば 100 個のオブジェクトの塊を削除できます。GAE でオブジェクトを削除すると、GAE の管理機能がいかに制限されているかがわかります。1000 エンティティ以下のバッチを処理する必要があります。CSV で動作するバルクローダー ツールを使用することもできますが、ドキュメントでは Java については説明されていません。私は GAE Java を使用しており、私の削除戦略には 2 つのサーブレットが含まれており、1 つは実際の削除を実行し、もう 1 つはタスク キューをロードします。削除を行う場合は、キュー読み込みサーブレットを実行し、キューを読み込み、GAE がキュー内のすべてのタスクを実行し始めます。
どうやってするの:少数のオブジェクトを削除するサーブレットを作成します。サーブレットをタスク キューに追加します。家に帰るか、他の何かに取り組む;)頻繁にデータストアをチェックしてください...
毎週パージする約 5000 個のオブジェクトを含むデータストアがあり、クリーンアップには約 6 時間かかるため、金曜日の夜にタスクを実行します。同じ手法を使用して、約 12 のプロパティを持つ約 5000 個のオブジェクトであるデータを一括読み込みします。
これは私にとってはうまくいきました:
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)
みんな、ありがとう、必要なものは手に入れた。:D
これは、削除するデータベース モデルがたくさんある場合に便利です。ターミナルでディスパッチできます。また、DB_MODEL_LIST の削除リストを自分で管理することもできます。
DB_1 を削除します:
python bulkdel.py 10 DB_1
すべての DB を削除:
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 など)。;)
私のような人が詳細を必要とする場合に備えて、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)
Google App Engine で特定の種類のすべてのエンティティを削除するには、次の手順を実行するだけです。
from google.cloud import datastore
query = datastore.Client().query(kind = <KIND>)
results = query.fetch()
for result in results:
datastore.Client().delete(result.key)
JavaScript では、次のようにページ上のすべてのエントリが削除されます。
document.getElementById("allkeys").checked=true;
checkAllEntities();
document.getElementById("delete_button").setAttribute("onclick","");
document.getElementById("delete_button").click();
削除するエンティティが含まれる管理ページ (.../_ah/admin) にいるとします。