Торнадо встроил сопрограммы и сохранение данных в БД

Стек, который я использую, включает в себя торнадо (асинхронный) и mongodb(мотор) У меня есть следующий алгоритм для обработки данных запросов:

  1. данные из входящего запроса сохраняются в коллекции событий
  2. данные сохраняются в единую коллекцию событий

Вот код обработчика запроса:

class EventHandler(BaseHandler):
    """ Handles all event requests
    """

    @gen.coroutine
    def post(self):
        """ Posts an event data
        """
        yield gen.Task(self.check_auth)
        self.validate_data()
        yield self._save_user()

        status_msg = yield self.save_entity()
        yield self.save_event()

        self.set_status(200, reason="OK, {}".format(status_msg))

А вот код для методов, вызываемых из обработчика запросов

@gen.coroutine
def save_entity(self):
    """ Saves event entity data for proper collection. Entities: orders, pageviews, users etc
    """
    event = self.data.get("event_type")
    if event not in self._event_schema_map.keys():
        raise Return("No specific entity, just event")
    try:
        if event == "cart_add":
            msg = yield gen.Task(self._save_product)
        elif event == "cart_delete":
            msg = yield gen.Task(self._delete_product)
        elif event == "pageview":
            msg = yield gen.Task(self._save_pageview)
        elif event == "order_complete":
            msg = yield gen.Task(self._save_order)
        elif event in ["email_known", "email_form"]:
            msg = yield gen.Task(self._save_email)
    except Exception as e:
        raise HTTPError(500, log_message=str(e))
    raise Return(msg)

@gen.coroutine
def save_event(self, event=None, event_type=None, event_data=None):
    """ Saves event data to db. Works both as standalone method and as plug-in method
    :param event: event name
    :param event_type: event type
    :param event_data: dict with event-specific infoelements data
    """
    yield self.motor.events.insert(
        {
            "client_id": self.data.get("client_id"),
            "user_id": self.data.get("user_id"),
            "timestamp": datetime.now(),
            "event": self.data.get("event", event),
            "event_type": self.data.get("event_type", event_type),
            "event_data": self.data.get("event_data", event_data),
            "event_url": self.data.get("event_url"),
            "utms": self.data.get("utms"),
            "analytics_short": self.data.get("analytics_short"),
            "analytics_long": self.data.get("analytics_long")
        }
    )

Все _save_%smth% это просто моторные действия CRUD, инкапсулированные в вызовах функций и заключенные в @engine декораторы, как следующие:

@gen.engine
def _save_product(self, callback=None):
    """ Adds product to user's cart
    """
    cart_data = self.data.get("event_data")[0]
    try:
        yield self.motor.users.update(
            {"_id": self.data["user_id"], "client_id": self.data["client_id"]},
            {
                '$set': {
                    "cart_updated_at": datetime.now(),
                    "reminder": False,
                },
                '$push': {
                    "items": {
                        "product_id": cart_data.get("product_id"),
                        "image": cart_data.get("image"),
                        "title": cart_data.get("title"),
                        "price": int(cart_data.get("price"))
                    }
                }
            },
            upsert=True
        )
    except Exception as e:
        raise HTTPError(500, log_message=str(e))
    callback("New product in cart record added")

@gen.engine
def _save_order(self, callback=None):
    """ Saves order data to user's orders
    """
    order_data = self.data.get("event_data")
    try:
        yield self.motor.orders.update(
            {"user_id": self.data["user_id"], "client_id": self.data["client_id"]},
            {
                '$push': {
                    "orders": {
                        "completed_at": datetime.now(),
                        "analytics_short": self.data["analytics_short"],
                        "analytics_long": self.data["analytics_long"],
                        "utms": self.data["utms"],
                        "items": [
                            {
                                "product_id": i["product_id"],
                                "price": int(i["price"]),
                                "quantity": int(i["quantity"])
                            }
                            for i in order_data
                        ]
                    }
                }
            },
            upsert=True,
        )
    except Exception as e:
        raise HTTPError(500, log_message="Error in order updating: {}".format(e))
    try:
        yield self.motor.users.update(
            {"_id": self.data["user_id"], "client_id": self.data["client_id"]},
            {
                "$unset": {
                    "cart_created_at": '',
                    "cart_updated_at": '',
                    "reminder": '',
                    "items": ''
                }
            }
        )
    except Exception as e:
        raise HTTPError(500, log_message="Error in cart updating: {}".format(e))
    callback("Order record added")

Таким образом, данные запроса сохраняются дважды в разных коллекциях: "конкретный" в функции save_entity и "универсальный" в функции save_event. Но на самом деле я вижу, что часто (примерно в 50% случаев) пропускаются (данные не сохраняются) и выполняется второе сохранение.

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

Поэтому я пытаюсь понять, как такая ситуация может произойти. Я предполагаю, что функция save_entity плохо спроектирована и из-за нескольких встроенных функций сам запрос завершается, а данные просто не сохраняются в БД. Может ли это быть?

В UPD добавлен производственный код, поэтому теперь ситуация станет более понятной. Надеюсь:)UPD 2 добавил несколько методов CRUD

1 ответ

Для обновления требуется два параметра: запрос, который указывает, какие документы обновлять, и документ обновления. Запрос следует тому же синтаксису, что и для find() или find_one(). Документ обновления имеет два режима: он может заменить весь документ или обновить некоторые поля документа. "update" также принимает некоторые необязательные параметры, включая "multi" и "upsert". Для получения дополнительной информации о методе "update" см. Учебник:

http://motor.readthedocs.org/en/stable/tutorial.html

В вашем коде вы называете "update" первым параметром, а не вторым. Я ожидаю, что ваш код выдает "TypeError: update() отсутствует 1 обязательный позиционный аргумент:" документ "", и исключение проглатывается или теряется в файле журнала где-то выше в вашей цепочке вызовов.

Другие вопросы по тегам