Я хотел бы стереть все данные определенного типа в Google App Engine. Как лучше всего это сделать? Я написал сценарий удаления (взломать), но, поскольку данных так много, истекло время ожидания после нескольких сотен записей.
Удалить все данные для вида в Google App Engine
Ответы (19)
официальный ответ от Google заключается в том, что вам нужно удалить в блоки распределены по нескольким запросам. Вы можете использовать AJAX, метаобновление или запросить URL-адрес из скрипта, пока не останется никаких сущностей. .
В настоящее время я удаляю объекты по их ключу, и, похоже, это быстрее.
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: // ...
Теперь вы можете использовать для этого администратора хранилища данных: https://developers.google.com/appengine/docs/adminconsole/datastoreadmin#Deleting_Entities_in_Bulk
Если бы я был параноиком, я бы сказал, что Google App Engine (GAE) не упростил нам удаление данных, если мы этого хотим. Я пропущу обсуждение размеров индексов и того, как они преобразуют 6 ГБ данных в 35 ГБ хранилища (за которые взимается плата). Это другая история, но у них есть способы обойти это - ограничить количество свойств для создания индекса (автоматически сгенерированные индексы) и так далее.
Причина, по которой я решил написать этот пост, заключается в том, что мне нужно «уничтожить» все мои виды в песочнице. Я читал об этом и, наконец, придумал такой код:
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");
}
}
У меня более 6 миллионов записей. Это много. Я понятия не имею, сколько будет стоить удаление записей (может быть, более экономично не удалять их). Другой альтернативой может быть запрос на удаление всего приложения (песочницы). Но в большинстве случаев это нереально.
Я решил использовать меньшие группы записей (в простом запросе). Я знаю, что могу использовать 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 ГБ), это не очень поможет пользователям, не использующим Google App Engine.
Я пробовал db.delete (results) и App Engine Console, и, похоже, ни один из них у меня не работает. Удаление записей из Data Viewer вручную (увеличенный лимит до 200) тоже не помогло, так как я загрузил более 10000 записей. Я закончил писать этот скрипт
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)
Хитрость заключалась в том, чтобы включить перенаправление в html вместо использования self.redirect. Я готов подождать ночь, чтобы избавиться от всех данных в моей таблице. Надеюсь, команда GAE упростит удаление таблиц в будущем.
date
раньше определенного Date
. Я обнаружил несколько опечаток в вашем решении, но меня беспокоят другие опечатки, т.к. я не совсем понимаю код. Например, inst
против ints
, как устанавливается первое значение txt
?, какова цель 5
? Так что, если вы обнаружили другие опечатки, укажите их. Мой главный вопрос: похоже, вы удаляете 100 или меньше записей; можно ли for
изменить на while db.delete(results):
, добавив break
в конец предложения except
, или это нарушит код?
- person zerowords; 06.10.2012
Самый быстрый и эффективный способ обработки массового удаления в Datastore - использование нового mapper API объявлено о последнем Google I / O.
Если вы предпочитаете язык Python, вам просто нужно зарегистрировать свой mapper в файле 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());
}
Один совет. Предлагаю вам ознакомиться с remote_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 позаботится об удалении!
На dev-сервере можно выполнить cd к своему приложению каталог, затем запустите его следующим образом:
dev_appserver.py --clear_datastore=yes .
Это запустит приложение и очистит хранилище данных. Если у вас уже запущен другой экземпляр, приложение не сможет привязаться к необходимому IP-адресу и, следовательно, не сможет запуститься ... и очистить хранилище данных.
Вы можете использовать очереди задач для удаления фрагментов, скажем, из 100 объектов. Удаление объектов в GAE показывает, насколько ограничены возможности администратора в GAE. Вы должны работать с партиями не более 1000 объектов. Вы можете использовать инструмент массовой загрузки, который работает с CSV, но документация не охватывает java. Я использую GAE Java, и моя стратегия удаления включает в себя 2 сервлета: один для фактического удаления, а другой - для загрузки очередей задач. Когда я хочу выполнить удаление, я запускаю сервлет загрузки очереди, он загружает очереди, а затем GAE приступает к работе, выполняя все задачи в очереди.
Как это сделать: создать сервлет, удаляющий небольшое количество объектов. Добавьте сервлет в очереди задач. Идите домой или займитесь чем-нибудь другим;) Проверяйте хранилище данных почаще ...
У меня есть хранилище данных с примерно 5000 объектами, которые я очищаю каждую неделю, и на очистку уходит около 6 часов, поэтому я запускаю задачу в пятницу вечером. Я использую тот же метод для массовой загрузки своих данных, которые составляют около 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
Удалить всю БД:
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 )
А вот и модифицированная версия кода Александра Фьори.
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
И, конечно же, вы должны сопоставить ссылку с моделью в файле (например, main.py в 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)
Чтобы удалить все объекты данного типа в 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) с объектами, которые хотите удалить.