Question

Le moteur app datastore, bien sûr, a . Cependant, je voudrais avoir un "fail-safe" mis qui est plus robuste face à des erreurs de datastore (voir la motivation ci-dessous). Il semble que la file d'attente des tâches est un endroit évident de différer lorsque l'écrit datastore est indisponible. Je ne connais pas d'autres solutions que (autres que l'expédition de données à un tiers via urlfetch).

Motivation : J'ai une entité qui vraiment doit être mis dans le magasin de données - montrant simplement un message d'erreur à l'utilisateur ne fera pas. Par exemple, peut-être un effet secondaire a eu lieu qui ne peut pas être facilement défait (peut-être une certaine interaction avec un site tiers).

Je suis venu avec une enveloppe simple qui (je pense) fournit une raisonnable « fail-safe » mettre (voir ci-dessous). Voyez-vous des problèmes avec cela, ou avoir une idée pour une mise en œuvre plus robuste? (Note:. Merci aux suggestions affichées dans les réponses de Nick Johnson et Saxon Druce, ce message a été modifié avec quelques améliorations au code)

import logging
from google.appengine.api.labs.taskqueue import taskqueue
from google.appengine.datastore import entity_pb
from google.appengine.ext import db
from google.appengine.runtime.apiproxy_errors import CapabilityDisabledError

def put_failsafe(e, db_put_deadline=20, retry_countdown=60, queue_name='default'):
    """Tries to e.put().  On success, 1 is returned.  If this raises a db.Error
    or CapabilityDisabledError, then a task will be enqueued to try to put the
    entity (the task will execute after retry_countdown seconds) and 2 will be
    returned.  If the task cannot be enqueued, then 0 will be returned.  Thus a
    falsey value is only returned on complete failure.

    Note that since the taskqueue payloads are limited to 10kB, if the protobuf
    representing e is larger than 10kB then the put will be unable to be
    deferred to the taskqueue.

    If a put is deferred to the taskqueue, then it won't necessarily be
    completed as soon as the datastore is back up.  Thus it is possible that
    e.put() will occur *after* other, later puts when 1 is returned.

    Ensure e's model is imported in the code which defines the task which tries
    to re-put e (so that e can be deserialized).
    """
    try:
        e.put(rpc=db.create_rpc(deadline=db_put_deadline))
        return 1
    except (db.Error, CapabilityDisabledError), ex1:
        try:
            taskqueue.add(queue_name=queue_name,
                          countdown=retry_countdown,
                          url='/task/retry_put',
                          payload=db.model_to_protobuf(e).Encode())
            logging.info('failed to put to db now, but deferred put to the taskqueue e=%s ex=%s' % (e, ex1))
            return 2
        except (taskqueue.Error, CapabilityDisabledError), ex2:
            return 0

Demande gestionnaire pour la tâche:

from google.appengine.ext import db, webapp

# IMPORTANT: This task deserializes entity protobufs.  To ensure that this is
#            successful, you must import any db.Model that may need to be
#            deserialized here (otherwise this task may raise a KindError).

class RetryPut(webapp.RequestHandler):
    def post(self):
        e = db.model_from_protobuf(entity_pb.EntityProto(self.request.body))
        e.put() # failure will raise an exception => the task to be retried

ne pas attendre à utiliser pour tous mettre - la plupart du temps, montrant un message d'erreur est très bien. Il est tentant de l'utiliser pour chaque vente, mais je pense que parfois il peut être plus déroutant pour l'utilisateur si je leur dis que leurs changements apparaîtront plus tard (et continuer à leur montrer les anciennes données jusqu'à ce que le datastore est de retour et les met différés execute).

Était-ce utile?

La solution

Votre approche est raisonnable, mais a plusieurs mises en garde:

  • Par défaut, une opération de vente réessaiera jusqu'à ce qu'il est à court de temps. Puisque vous avez une stratégie de sauvegarde, vous pouvez renoncer plus tôt -. Dans ce cas, vous devez fournir un paramètre à l'appel rpc de méthode, en spécifiant un délai personnalisé
  • Il n'y a pas besoin de mettre un compte à rebours explicite -. La file d'attente des tâches réessaiera opérations ne pour vous à des intervalles de plus en plus
  • Protocol Buffers ont un codage de chaîne naturelle qui est beaucoup plus efficace -
  • Vous n'avez pas besoin d'utiliser cornichon. Voir ce post pour une démonstration de la façon de l'utiliser.
  • Comme saxon indique, la file d'attente des tâches charges utiles sont limitées à 10 kilo-octets, de sorte que vous pourriez avoir du mal avec de grandes entités.
  • Plus important encore, cela change le modèle de cohérence datastore de « fortement en accord » à « terme cohérente ». Autrement dit, la vente que vous à la file d'attente en file d'attente des tâches pourrait être appliquée à tout moment à l'avenir, tout changement écrasant ont été faites dans l'intervalle. Un certain nombre de conditions de course sont possibles, essentiellement rendre les transactions inutiles s'il y a met en attente sur la file d'attente des tâches.
scroll top