Процесс являет собой систему поддержания актуальных данных, выкачивая новые данные по 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