Удалить все данные для вида в Google App Engine - PullRequest
44 голосов
/ 20 сентября 2008

Я хотел бы стереть все данные для определенного вида в Google App Engine. Что лучший способ сделать это? Я написал сценарий удаления (взломать), но так как есть так много данных истекло время ожидания после нескольких сотен записей.

Ответы [ 19 ]

27 голосов
/ 21 июня 2009

Я сейчас удаляю сущности по их ключу, и похоже, что это быстрее.

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: //...

23 голосов
/ 19 сентября 2011

Теперь вы можете использовать администратора хранилища данных для этого: https://developers.google.com/appengine/docs/adminconsole/datastoreadmin#Deleting_Entities_in_Bulk

10 голосов
/ 10 декабря 2009

Если бы я был параноиком, я бы сказал, что 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: добавьте функцию для удаления всех объектов одного вида в одной транзакции.

9 голосов
/ 15 ноября 2008

Попробуйте использовать Консоль App Engine , тогда вам даже не придется развертывать какой-либо специальный код

9 голосов
/ 20 сентября 2008

Предположительно, ваш хак был примерно таким:

# 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.

7 голосов
/ 27 ноября 2008

Я пробовал db.delete (результаты) и консоль App Engine, но ни один из них, похоже, не работает для меня. Удаление записей из 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 упростит отбрасывание таблиц в будущем.

6 голосов
/ 23 сентября 2008

Официальный ответ от Google заключается в том, что вы должны удалять куски, распределенные по нескольким запросам. Вы можете использовать AJAX, meta refresh или запросить URL-адрес из сценария, пока не останется никаких сущностей.

5 голосов
/ 09 сентября 2010

Самый быстрый и эффективный способ обработки массового удаления в Datastore - использование нового mapper API , анонсированного в последней Google I / O .

Если ваш язык выбора 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());
}
4 голосов
/ 09 сентября 2009

Один совет. Я предлагаю вам узнать remote_api для этих типов использования (массовое удаление, изменение и т. Д.) Но даже при использовании удаленного API размер пакета может быть ограничен несколькими сотнями за раз.

3 голосов
/ 20 сентября 2008

К сожалению, нет способа легко выполнить массовое удаление. Лучше всего написать сценарий, который удаляет разумное количество записей за вызов, а затем вызывать его повторно - например, если ваш сценарий удаления возвращает перенаправление 302 всякий раз, когда есть еще данные для удаления, а затем выбирает его с помощью "wget ​​- -max-redirect = 10000 "(или другое большое число).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...