Отказоустойчивые обновления хранилища данных в ядре приложения - PullRequest
2 голосов
/ 27 сентября 2010

В хранилище данных движка приложения, конечно же, время простоя .Тем не менее, я хотел бы иметь «отказоустойчивый» 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

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

Ответы [ 2 ]

2 голосов
/ 28 сентября 2010

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

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

Одна потенциальная проблема заключается в том, что задачи ограничены 10 КБ данных , поэтому это не сработает, если у вас есть объект, размер которого больше, чем когда-то использованный.

...