2010-09-26 2 views
2

В хранилище данных приложения приложений, конечно же, есть downtime. Тем не менее, я хотел бы иметь «отказоустойчивый» put, который более устойчив перед ошибками хранилища данных (см. Мотивацию ниже). Кажется, что очередь задач - это очевидное место, чтобы отложить записи, когда хранилище данных недоступно. Я даже не знаю никаких других решений (кроме отправки данных третьим лицам через urlfetch).Неудачные обновления хранилища данных на движке приложения

Мотивация: У меня есть объект, который действительно нужно положить в хранилище - просто отображается сообщение об ошибке пользователю не будет делать. Например, возможно, произошел какой-то побочный эффект, который нельзя легко отменить (возможно, некоторое взаимодействие с сторонним сайтом).

Я придумал простую упаковку, которая (я думаю) обеспечивает разумный «отказоустойчивый» (см. Ниже). Вы видите какие-либо проблемы с этим или имеете идею более надежной реализации? (Примечание. Благодаря предложениям вывешенные в ответах Ник Джонсон и Саксонская Друце, это сообщение было отредактировано с некоторыми улучшениями в коде)

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 

Запрос обработчик для выполнения этой задачи:

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 

I не рассчитывать использовать это для каждый положить - в большинстве случаев, показывая сообщение об ошибке, просто отлично. Заманчиво использовать его для каждого помета, но я думаю, что иногда это может быть более запутанным для пользователя, если я скажу им, что их изменения появятся позже (и продолжают показывать им старые данные, пока хранилище резервных копий не вернется и отложенные puts execute).

+0

Смежный вопрос: [Есть ли корреляция между датастором и временем простоем очереди задач?] (Http://stackoverflow.com/questions/3800252/datastore-and-task-queue-downtime-correlation) –

ответ

2

Ваш подход является разумным, но имеет несколько предостережений:

  • По умолчанию операция положить повторит, пока не закончится время. Поскольку у вас есть стратегия резервного копирования, вы можете отказаться от нее раньше - в этом случае вы должны указать параметр rpc для вызова метода put, указав конечный срок.
  • Нет необходимости устанавливать явный обратный отсчет - очередь задач будет повторять неудачные операции для вас с возрастающими интервалами.
  • Вам не нужно использовать pickle - Буферы протокола имеют естественную строчную кодировку, которая намного эффективнее. См. this post для демонстрации того, как его использовать.
  • Как указывает Саксон, полезная нагрузка очереди задач ограничена 10 килобайтами, поэтому у вас могут быть проблемы с крупными объектами.
  • Самое главное, что это изменяет модель согласованности хранилища данных от «сильно последовательной» до «в конечном итоге последовательной». То есть, помещенный в очередь очереди задач может быть применен в любое время в будущем, перезаписав любые изменения, которые были сделаны в промежуточный период. Любое количество условий гонки возможно, по существу, делая транзакции бесполезными, если есть очереди, ожидающие очереди задач.
+0

Спасибо за подробную обратную связь; Я обязательно включу эти мысли. Единственная причина, по которой я установил обратный отсчет, - это то, что я решил, что это будет гарантировать, что очередь задач не сразу попытается переустановить сущность (поскольку она просто не удалась, возможно, ей нужно дать немного времени (возможно, значение по умолчанию 60 с слишком велико], если проблема преходяща, например расщепление планшета и т. д.]). –

1

Одна потенциальная проблема заключается в том, что tasks are limited to 10kb of data, так что это не сработает, если у вас есть сущность, которая больше, чем когда-то маринованная.

+0

Хорошая точка ; к счастью, мне не нужно беспокоиться об этом для сущностей, для которых я использую это.Но я обновляю docstring кода, чтобы отразить этот предел. –

Смежные вопросы