Как мне избежать отправки дублирующих писем с помощью mailgun, taskqueue и ndb? - PullRequest
0 голосов
/ 25 апреля 2019

Я использую Taskqueue API для отправки нескольких электронных писем небольшими группами с mailgun. Мой код выглядит примерно так:

class CpMsg(ndb.Model):
    group = ndb.KeyProperty()
    sent = ndb.BooleanProperty()
    #Other properties


def send_mail(messages):
    """Sends a request to mailgun's API"""
    # Some code
    pass


class MailTask(TaskHandler):
    def post(self):
        p_key = utils.key_from_string(self.request.get('p'))
        msgs = CpMsg.query(
            CpMsg.group==p_key,
            CpMsg.sent==False).fetch(BATCH_SIZE)

        if msgs:
            send_mail(msgs)

            for msg in msgs:
                msg.sent = True

            ndb.put_multi(msgs)

            #Call the task again in COOLDOWN seconds

Приведенный выше код работал нормально, но, согласно документации, API Taskqueue гарантирует, что задача будет доставлена ​​ не менее один раз, поэтому задачи должны быть идемпотентными. Теперь, в большинстве случаев, это относится к приведенному выше коду, так как он получает только те сообщения, у которых свойство 'sent' равно False. Проблема состоит в том, что запросы ndb, не относящиеся к предкам, только в конечном итоге согласованы, что означает, что если задача выполняется дважды подряд, запрос может вернуть устаревшие результаты и включить только что отправленные сообщения.

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

Должен ли я использовать предка для выполнения запросов? Или, может быть, есть способ настроить mailgun, чтобы избежать отправки одного и того же письма дважды? Должен ли я просто принять риск того, что в некоторых редких случаях несколько писем могут быть отправлены более одного раза?

1 Ответ

1 голос
/ 09 мая 2019

Один из возможных способов избежать возможного препятствия согласованности состоит в том, чтобы сделать запрос keys_only, а затем перебрать ключи сообщений, чтобы получить фактические сообщения путем поиска по ключу (строгая согласованность), проверить, является ли msg.sent Истиной, ипропустить отправку этих сообщений в таком случае.Что-то вроде этого:

    msg_keys = CpMsg.query(
        CpMsg.group==p_key,
        CpMsg.sent==False).fetch(BATCH_SIZE, keys_only=True)
    if not msg_keys:
        return

    msgs = ndb.get_multi(msg_keys)
    msgs_to_send = []

    for msg in msgs:
         if not msg.sent:
             msgs_to_send.append(msg)
    if msgs_to_send:
        send_mail(msgs_to_send)

        for msg in msgs_to_send:
            msg.sent = True

        ndb.put_multi(msgs_to_send)

Вы также должны были бы сделать свой вызов post транзакционным (с декоратором @ndb.transactional()).

Это должно устранить дубликаты, вызванныезапрос возможной согласованности.Однако все еще есть место для дубликатов, вызванных повторными попытками транзакции из-за конфликта хранилища данных (или по любой другой причине) - так как вызов send_mail() не идемпотентен.Отправка одного сообщения за раз (возможно, с использованием очереди задач) может снизить вероятность этого.См. Также GAE / P: безопасность транзакций с вызовами API

...