@detcelfer

Exception возникающий при работе с postgres поднимается на 2 уровня в приложении через 2 глухих try/except. Как такое возможно?

Процесс являет собой систему поддержания актуальных данных, выкачивая новые данные по API и складывая в бд
логика:
1. приложение делает запрос к postgres бд, в таблицу, в поисках самого большого timestamp.
2. Получив timestamp из бд, приложение отправляет запрос к API "верни все записи с timestamp >= timestamp_<из_бд>"
3. Получив ответ от API (список записей), приложение сохраняет по одной записи из списка в бд
эти шаги находятся в бесконечном цикле.
особенности:
1. если новых данных у сервера с API нет. вернётся одна запись которая уже существует в бд, у которой и был взят наибольший timestamp. Так что если на API сервере не появляется новых записей, в бд будет постоянно происходить попытка одной и той же записи.
2. в таблице существует constraint непозволяющий одинаковым записям попадать в таблицу
Ну а теперь код:
# верхний уровень
def trade_update_from_last_rec(model, api):
    try:
        # получаем timestamp из бд
        if model.objects.exists():
            latest_record = model.objects.order_by('-create_time_ms').first()
            last_timestamp = int(latest_record.create_time)
        else:
            
            # если записей нет timstamp ставим 10 минут назад от настоящего времени
            ten_minutes_ago = int((datetime.now() - timedelta(minutes=10)).timestamp())
            last_timestamp = ten_minutes_ago

        # запрос к API
        trades_lst = api.get_records(_from=last_timestamp)
        if trades_lst: 
            load_trades_to_db(trades_lst, model) # передаем список вернувшихся записей в load_trades_to_db
            return
    except Exception as e:
        log.error(f'update from last rec exception: {e, traceback.format_exc()}')
    return


# средний уроверь
def load_trades_to_db(trades_lst, model):
    for rec in reversed(trades_lst):
        try:
            model.loader(dic) # передаем по одной записи в метод django модели для записи в бд
        except Exception as e: # глухой try/except блок
            log.error(f"load_trades_to_db exception: rec: {rec, e, traceback.format_exc()}")

   
# нижний уровень. нас интересует метод loader
class Trade(models.Model):
    class Meta:
        db_table = 'trade'
        constraints = [
            models.UniqueConstraint(fields=['trade_id', 'order_id'], name='unique_trade_order') # констрейнт, ограничивающий дублирование записей в таблице
        ]

    create_time = models.BigIntegerField(null=True)
    create_time_ms = models.DecimalField(max_digits=20, decimal_places=6, null=True)
    order_id = models.BigIntegerField(null=True)
    trade_id = models.BigIntegerField(null=True)
    
    @classmethod
        def loader(cls, parsed_data):
            try:
                cls.objects.create(**parsed_data)
                log.info(
                    f"trade loader: record updated in db. id:{parsed_data['trade_id']}, order_id: {parsed_data['order_id']}, {parsed_data['create_time']}")

            except IntegrityError as e: # обычно отлавливание записи дубликатов ловится этим эксептом (но не всегда)
                log.debug(f"spot user trade loader: this trade is already in database."
                          f" id:{parsed_data['trade_id']}, order_id: {parsed_data['order_id']}, {parsed_data['create_time']}")

            except DatabaseError as e:
                log.error(
                    f"trade loader: database error while loading record: {e}, {traceback.format_exc()}"
                    f"id:{parsed_data['trade_id']}, order_id: {parsed_data['order_id']}, {parsed_data['create_time']}"
                )

            except Exception as e: # глухой try/except
                log.error(
                    f"trade loader: unexpected error while loading record: {e}, {traceback.format_exc()}"
                    f"id:{parsed_data['trade_id']}, order_id: {parsed_data['order_id']}, {parsed_data['create_time']}"
                )
            return


По идее все дублирующие записи должны обрабатываться try/except блоком метода 'loader' django модели . Так и происходит почти всегда. однако, по какой-то причине некоторые из этих ошибок умудряются подняться на 2 уровня приложения. и ошибка ловится на уровне функции trade_update_from_last_rec. Причем, сам лог очень странный. С одной стороны он говорит о том, что проблема с дублированием констрейнта, а с другой ссылается вообще на другой кусок кода, не там где происходит попытка записи.
лог1

update from last rec exception: IntegrityError: duplicate key value violates unique constraint "unique_trade_order_14701796"
DETAIL: Key (trade_id, order_id)=(9157177867, 585183597083) already exists.

Traceback (most recent call last):
File "/path/to/site-packages/django/db/backends/utils.py", line 89, in _execute
return self.cursor.execute(sql, params)
psycopg2.errors.UniqueViolation: duplicate key value violates unique constraint "unique_trade_order_14701796"
DETAIL: Key (trade_id, order_id)=(9157177867, 585183597083) already exists.

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
File "/path/to/trade_provider/utils.py", line 108, in trade_update_from_last_rec
latest_record = model.objects.order_by('-create_time_ms').first()
File "/path/to/site-packages/django/db/models/query.py", line 1057, in first
for obj in queryset[:1]:
File "/path/to/site-packages/django/db/models/query.py", line 398, in __iter__
self._fetch_all()
File "/path/to/site-packages/django/db/models/query.py", line 1881, in _fetch_all
self._result_cache = list(self._iterable_class(self))
File "/path/to/site-packages/django/db/models/query.py", line 91, in __iter__
results = compiler.execute_sql(
File "/path/to/site-packages/django/db/models/sql/compiler.py", line 1562, in execute_sql
cursor.execute(sql, params)
File "/path/to/site-packages/django/db/backends/utils.py", line 102, in execute
return super().execute(sql, params)
File "/path/to/site-packages/django/db/backends/utils.py", line 67, in execute
return self._execute_with_wrappers(
File "/path/to/site-packages/django/db/backends/utils.py", line 80, in _execute_with_wrappers
return executor(sql, params, many, context)
File "/path/to/site-packages/django/db/backends/utils.py", line 84, in _execute
with self.db.wrap_database_errors:
File "/path/to/site-packages/django/db/utils.py", line 91, in __exit__
raise dj_exc_value.with_traceback(traceback) from exc_value
File "/path/to/site-packages/django/db/backends/utils.py", line 89, in _execute
return self.cursor.execute(sql, params)
django.db.utils.IntegrityError: duplicate key value violates unique constraint "unique_trade_order_14701796"
DETAIL: Key (trade_id, order_id)=(9157177867, 585183597083) already exists.


попутно возникают ошибки при работе функции см. лог2, 3 (не влезли в форму вопроса).:
https://pastebin.com/xS0gn8rf
https://pastebin.com/rva53tBF

Почему-то проблема возникает именно с этой функцией. в других местах приложения всё работает норм.
Основная задача понять причины подобной работы обработки исключений а так же понять причины возникновения ошибки.
Дополнительно:
  • в логах бд ничего интересного. только ошибки о попытке записи в таблицу с дублирующим констрейнтом, как и ожидалось
  • между приложением и бд стоит pgboucer.
  • в лимит сессий не упираюсь судя по мониторингу бд/pgboucer
  • Вопрос задан
  • 37 просмотров
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы