Je souhaite supprimer toutes les données d'un type spécifique dans Google App Engine. Quelle est la Meilleure façon de procéder? J'ai écrit un script de suppression (bidouillage), mais comme il y a tellement de données, le délai d'attente de Est écoulé après quelques centaines d'enregistrements.
Le réponse officielle de Google indique que vous devez supprimer des fragments répartis sur plusieurs requêtes. Vous pouvez utiliser AJAX, meta refresh , ou demander votre URL à partir d'un script jusqu'à ce qu'il ne reste plus aucune entité.
Je suis en train de supprimer les entités par leur clé, et cela semble être plus rapide.
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
depuis le terminal, je lance curl -N http: // ...
Vous pouvez maintenant utiliser l'administrateur de magasin de données pour cela: https://developers.google.com/appengine/docs/adminconsole/datastoreadmin#Deleting_Entities_in_Bulk
Si j'étais une personne paranoïaque, je dirais que Google App Engine (GAE) ne nous a pas permis de supprimer facilement les données si nous le souhaitions. Je vais sauter la discussion sur la taille des index et sur la façon dont ils traduisent 6 Go de données en 35 Go de stockage (facturé). C'est une autre histoire, mais ils ont des moyens de contourner ce problème - limiter le nombre de propriétés sur lesquelles créer un index (index générés automatiquement) et ainsi de suite.
La raison pour laquelle j'ai décidé d'écrire cet article est que je dois "détruire" tous mes types dans un bac à sable. J'ai lu à ce sujet et finalement fini avec ce code:
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");
}
}
J'ai plus de 6 millions de disques. C'est beaucoup. Je n'ai aucune idée du coût de la suppression des enregistrements (peut-être plus économique de ne pas les supprimer). Une autre alternative serait de demander une suppression pour l’application complète (bac à sable). Mais ce n'est pas réaliste dans la plupart des cas.
J'ai décidé d'aller avec de plus petits groupes de disques (dans la requête facile). Je sais que je pourrais aller pour 500 entités, mais j’ai commencé à recevoir des taux d’échec très élevés (fonction re delete).
Ma demande de l'équipe GAE: veuillez ajouter une fonctionnalité permettant de supprimer toutes les entités du même type lors d'une seule transaction.
Vraisemblablement, votre bidouillage ressemblait à ceci:
# 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))
Comme vous le dites, s'il y a suffisamment de données, vous allez atteindre le délai d'expiration de la demande avant qu'il ne passe par tous les enregistrements. Vous devrez ré-appeler cette demande plusieurs fois de l'extérieur pour vous assurer que toutes les données ont été effacées. assez facile à faire, mais à peine idéal.
La console d'administration ne semble offrir aucune aide, car (d'après ma propre expérience), elle ne semble autoriser que les entités d'un type donné à être répertoriées, puis supprimées page par page.
Lors des tests, j'ai dû purger ma base de données au démarrage pour supprimer les données existantes.
J'en déduirais que Google fonctionne sur le principe que le disque est bon marché et que les données sont donc généralement orphelines (index des données redondantes remplacées), plutôt que supprimées. Étant donné qu'une quantité fixe de données est actuellement disponible pour chaque application (0,5 Go), cela n'aide pas beaucoup les utilisateurs autres que ceux de Google App Engine.
Essayez d’utiliser App Engine Console vous n’aurez même pas à déployer de code spécial
J'ai essayé db.delete (résultats) et App Engine Console, et aucun ne semble fonctionner pour moi. La suppression manuelle d'entrées dans la Visionneuse de données (limite augmentée jusqu'à 200) ne fonctionnait pas non plus, car j'ai téléchargé plus de 10000 entrées. J'ai fini d'écrire ce script
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)
L'astuce consistait à inclure la redirection en HTML au lieu d'utiliser self.redirect. Je suis prêt à attendre la nuit pour me débarrasser de toutes les données de mon tableau. Espérons que l’équipe GAE facilitera l’abandon des tables à l’avenir.
Le moyen le plus rapide et le plus efficace de gérer la suppression en bloc sur le magasin de données consiste à utiliser le nouveau mapper API annoncé sur le dernier - Google I/O .
Si votre langue de choix est Python , il vous suffit d’enregistrer votre mappeur dans un fichier mapreduce.yaml et de définir une fonction comme celle-ci:
from mapreduce import operation as op
def process(entity):
yield op.db.Delete(entity)
Sur Java vous devriez jeter un oeil sur cet article qui suggère une fonction comme celle-ci:
@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());
}
Un conseil. Je vous suggère de connaître le remote_api pour ces types d’utilisation (suppression en bloc, modification, etc.). Mais, même avec les API distantes, la taille des lots peut être limitée à quelques centaines à la fois.
Malheureusement, il est impossible de supprimer facilement en bloc. Votre meilleur choix est d'écrire un script qui supprime un nombre raisonnable d'entrées par appel, puis de l'appeler de manière répétée - par exemple, en renvoyant une redirection 302 à votre script de suppression chaque fois qu'il y a davantage de données à supprimer, puis en les récupérant avec "wget - -max-redirect = 10000 "(ou un autre grand nombre).
Oui, vous pouvez: Accéder à Datastore Admin, puis sélectionner le type d’entité à supprimer et cliquer sur Supprimer. Mapreduce se chargera de la suppression!
Avec Django, configurez l'URL:
url(r'^Model/bdelete/$', v.bulk_delete_models, {'model':'ModelKind'}),
Vue d'installation
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)))
Puis lancez en PowerShell:
$client = new-object System.Net.WebClient
$client.DownloadString("http://your-app.com/Model/bdelete/?limit=400")
Si vous utilisez Java/JPA, vous pouvez faire quelque chose comme ceci:
em = EntityManagerFactoryUtils.getTransactionalEntityManager(entityManagerFactory)
Query q = em.createQuery("delete from Table t");
int number = q.executeUpdate();
Vous trouverez les informations relatives à Java/JDO à l’adresse suivante: http://code.google.com/appengine/docs/Java/datastore/queriesandindexes.html#Delete_By_Query
Sur un serveur dev , on peut accéder au répertoire de son application, puis l’exécuter comme ceci:
dev_appserver.py --clear_datastore=yes .
Cela démarrera l'application et effacera le magasin de données. Si vous avez déjà une autre instance en cours d'exécution, l'application ne pourra pas se lier à l'IP nécessaire et par conséquent ne pourra pas démarrer ... et effacer votre magasin de données.
Cela a fonctionné pour moi:
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)
Vous pouvez utiliser les files de tâches pour supprimer des morceaux de 100 objets, par exemple. La suppression d'objets dans GAE indique la limitation des capacités d'administration dans GAE. Vous devez travailler avec des lots sur 1000 entités ou moins. Vous pouvez utiliser l'outil bulkloader qui fonctionne avec csv mais la documentation ne couvre pas Java. J'utilise GAE Java et ma stratégie de suppression implique de disposer de 2 servlets, un pour effectuer la suppression et un autre pour charger la tâche. les files d'attente. Lorsque je veux effectuer une suppression, j'exécute le servlet de chargement de la file d'attente, il charge les files d'attente, puis GAE se met au travail en exécutant toutes les tâches de la file d'attente.
Comment le faire: Créez un servlet qui supprime un petit nombre d'objets. Ajoutez le servlet à vos files d'attente de tâches. Rentrez chez vous ou travaillez sur autre chose;) Vérifiez le magasin de données de temps en temps ...
J'ai une banque de données avec environ 5 000 objets que je purge chaque semaine et le nettoyage prend environ 6 heures, donc je lance la tâche vendredi soir. J'utilise la même technique pour charger en masse mes données, ce qui arrive par exemple. être environ 5000 objets, avec environ une douzaine de propriétés.
Pour supprimer toutes les entités d'un type donné dans Google App Engine, procédez comme suit:
from google.cloud import datastore
query = datastore.Client().query(kind = <KIND>)
results = query.fetch()
for result in results:
datastore.Client().delete(result.key)
Merci à tous les gars, j'ai ce dont j'ai besoin. :RÉ
Cela peut être utile si vous devez supprimer de nombreux modèles de base de données, vous pouvez l’envoyer dans votre terminal. Vous pouvez également gérer vous-même la liste de suppression dans DB_MODEL_LIST.
Supprimer DB_1:
python bulkdel.py 10 DB_1
Supprimer toutes les bases de données:
python bulkdel.py 11
Voici le fichier 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 )
Et voici la version modifiée du code de 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
Et bien sûr, vous devez mapper le lien vers le modèle dans un fichier (comme main.py dans GAE),;)
Si des gars comme moi en ont besoin en détail, voici une partie de 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)