Как правльно сделать Free транзакции на "мертвом" соединении?

Вопросы программирования и использования среды Lazarus.

Модератор: Модераторы

Ответить
GreyCrazyWolf
новенький
Сообщения: 15
Зарегистрирован: 02.03.2023 14:23:57

Как правльно сделать Free транзакции на "мертвом" соединении?

Сообщение GreyCrazyWolf »

Добрейшего времени суток.
Возник следующий вопрос. Написал приложение использующее самописный пул запросов к БД PostgreSQL. Все в общем работает, есть одно небольшое но: если соединение "падает", например закрыто сервером по таймауту, и в этот момент была открыта транзакция при попытке "Destroy" начинается ругань на " Operation cannot be performed on an active transaction".
Попробовал сделать так

Код: Выделить всё

/// деструктор
destructor TPgConnection.Destroy;
begin
  // "прибиваем" транзакцию
  if Assigned(fSQLTransaction) then
     begin
       try
           if fSQLTransaction.Active then
              fSQLTransaction.Rollback;
       except
         on E: Exception do
            begin
                 LogToFile('CONN_DESTRUCTOR', Format('Ignoring exception during transaction rollback on destroy: %s', [E.Message]), Warning);
            end;
       end;
       fSQLTransaction.DataBase := nil;
     end;
  // "прибиваем"  соединение
  if Assigned(fPQConnection) then
     begin
       try
           if fPQConnection.Connected then
              fPQConnection.Connected := False;
       except
           on E: Exception do
            begin
                 LogToFile('CONN_DESTRUCTOR', Format('Ignoring exception during connection on destroy: %s', [E.Message]), Warning);
            end;
       end;
     end;

   try
     LogToFile('CONN_DESTRUCTOR', Format('Destroing connection %s', [GUIDToString(Guid)]), Info);
     fSQLTransaction.Free;
     fPQConnection.Free;
     LogToFile('CONN_DESTRUCTOR', Format('Destroy connection %s', [GUIDToString(Guid)]), Info);
   except
     on E: Exception do
        begin
           LogToFile('CONN_DESTRUCTOR', Format('Exception during free component on destroy connection %s: %s', [GUIDToString(Guid), E.Message]), Warning);
        end;
   end;
   inherited Destroy;
end;                   
все равно в лог падает 'Ignoring exception during transaction rollback on destroy"
Оно как бы и нечего, но как-то неаккуратненько :roll:
Аватара пользователя
WAYFARER
энтузиаст
Сообщения: 567
Зарегистрирован: 09.10.2009 00:00:04
Откуда: г. Курган

Сообщение WAYFARER »

Проверяйте не только активна ли транзакция, но и живо ли подключение.

Код: Выделить всё

if Assigned(fSQLTransaction) then
  begin
    // Rollback имеет смысл только если соединение живое
    if Assigned(fPQConnection) and fPQConnection.Connected and fSQLTransaction.Active then
    begin
      try
        fSQLTransaction.Rollback;
      except
         ...
      end;
    end;
    fSQLTransaction.DataBase := nil;
    FreeAndNil(fSQLTransaction);
  end;
GreyCrazyWolf
новенький
Сообщения: 15
Зарегистрирован: 02.03.2023 14:23:57

Сообщение GreyCrazyWolf »

WAYFARER писал(а): 15.01.2026 19:15:53 Проверяйте не только активна ли транзакция, но и живо ли подключение.
Спасибо, попробовал, ну оно все - Operation cannot be performed on an active transaction.
Собсвенно, в лог падает такое.

Код: Выделить всё

20:15:04;Connection {73B663AD-61D2-4C5D-9B83-B46AA590406B} ping failed with exception: TPQConnection : Preparation of query failed.  (PostgreSQL: сервер неожиданно закрыл соединение
	Скорее всего сервер прекратил работу из-за сбоя
	до или в процессе выполнения запроса.
);crazywolf;Error
20:15:04;Found and destroying a stale connection during validation.;crazywolf;Warning
20:15:16;Ignoring exception during transaction rollback on destroy: TPQConnection : нет соединения с сервером
  (PostgreSQL: );crazywolf;Warning
20:15:17;Failed to DESTROY a ManagedConnection. Error: Operation cannot be performed on an active transaction;crazywolf;Error
Я так пнимаю, ситуация следующая: серевр закрыл соединение по таймауту, но стандартная компонента Lazarusa думает что

Код: Выделить всё

 fPQConnection.Connected = True (хотя на самом деле нет) 
и честно пытается сделать RollBack.
В принципе оно и не страшно (данные все равно уже куку, а сервер, по идее, уже сам rollback сделал). Интересно чисто с точки зрения сделать правильно и красиво :roll:

Код: Выделить всё

destructor TPgConnection.Destroy;
begin
  // "прибиваем" транзакцию
  if Assigned(fSQLTransaction) then
     begin
       // Rollback имеет смысл только если соединение живое
       if Assigned(fPQConnection) and fPQConnection.Connected and fSQLTransaction.Active then
       begin
         try
           fSQLTransaction.Rollback;
         except
           on E: Exception do
              begin
                 LogToFile('DESTRUCTOR_WARN', Format('Ignoring exception during transaction rollback on destroy: %s', [E.Message]), Warning);
              end;
         end;
       end;
       fSQLTransaction.DataBase := nil;
     end;
  // "прибиваем"  соединение
  if Assigned(fPQConnection) then
     begin
       try
           if fPQConnection.Connected then
              fPQConnection.Connected := False;
       except
           on E: Exception do
            begin
                 LogToFile('DESTRUCTOR_WARN', Format('Ignoring exception during connection on destroy: %s', [E.Message]), Warning);
            end;
       end;
     end;

   try
     LogToFile('DESTRUCTOR_INFO', Format('Destroing connection %s', [GUIDToString(Guid)]), Info);
     fSQLTransaction.Free;
     fPQConnection.Free;
     LogToFile('DESTRUCTOR_INFO', Format('Destroy connection %s', [GUIDToString(Guid)]), Info);
   except
     on E: Exception do
        begin
           LogToFile('DESTRUCTOR_WARN', Format('Exception during free component on destroy connection %s: %s', [GUIDToString(Guid), E.Message]), Warning);
        end;
   end;
   inherited Destroy;
end; 
Аватара пользователя
Снег Север
долгожитель
Сообщения: 3072
Зарегистрирован: 27.11.2007 15:14:47
Контактная информация:

Сообщение Снег Север »

Не знаю конкретно про PostgreSQL, но общее для всех серверных баз данных, что поручать следить за транзакциями клиенту - грубейшая ошибка. Всё транзакционное должно делаться только на сервере, а клиент присоединяется для справки: прошло/ не прошло и почему, если не прошло и для получения новой выборки. Поэтому большинство грамотно спроектированных клиентских приложений не пытаются долго удерживать соединения, а предпоситают их переустанавливать при каждом обращении к серверу.
sts
энтузиаст
Сообщения: 549
Зарегистрирован: 04.04.2008 12:15:44
Откуда: Тольятти

Сообщение sts »

Снег Север писал(а): 06.02.2026 12:22:53 Поэтому большинство грамотно спроектированных клиентских приложений не пытаются долго удерживать соединения, а предпоситают их переустанавливать при каждом обращении к серверу.
это от бедности, т.е. вынужденное решение, еслиб это было предпочтительным способом то в бд не было бы таких вещей как контекст и права доступа, слишком много времени на это уходит
Аватара пользователя
Снег Север
долгожитель
Сообщения: 3072
Зарегистрирован: 27.11.2007 15:14:47
Контактная информация:

Сообщение Снег Север »

Права доступа тут как-то "перпендикулярны", а от бедности как раз "много времени уходит". Любой удалённый коннект, дело ненадёжное по определению. Поэтому всё критичное по времени и по обрывам надо делать встроенными процедурами на серваке, у него на то голова большая и ноги быстрые. А клиент даёт короткие команды и получает выжимки результатов. Всё иное - ересь от времен нищебродов с локально развернотыми серверами.
sts
энтузиаст
Сообщения: 549
Зарегистрирован: 04.04.2008 12:15:44
Откуда: Тольятти

Сообщение sts »

с точностью до наоборот, проблемы с коннектами начались около 10 лет назад, 15 лет до этого они весели месяцами без обрывов, тогда вообще не было необходимости в механизмах восстановления соединений. опятьже причина в бедности, упало базовое качество сетевого оборудования и серверного ПО уровня ОС, производители начали экономить на разработчиках.
GreyCrazyWolf
новенький
Сообщения: 15
Зарегистрирован: 02.03.2023 14:23:57

Сообщение GreyCrazyWolf »

Снег Север писал(а): 06.02.2026 12:22:53 Поэтому большинство грамотно спроектированных клиентских приложений не пытаются долго удерживать соединения, а предпоситают их переустанавливать при каждом обращении к серверу.
Спорить не буду, сам делал и делаю так на .Net c Ораклом и MS SQL. Однако когда на конретной разработке Lazarus + PostgreSQL, на довольно загруженной системе, столкнулись с проблемой = гуглеж дал вариант, что в данном случае нужен пул соединений.
Что и было реализовано и заработало с нужной производительностью.
Аватара пользователя
alexs
долгожитель
Сообщения: 4069
Зарегистрирован: 15.05.2005 23:17:07
Откуда: г.Ставрополь
Контактная информация:

Сообщение alexs »

Снег Север писал(а): 06.02.2026 12:22:53 Не знаю конкретно про PostgreSQL, но общее для всех серверных баз данных, что поручать следить за транзакциями клиенту - грубейшая ошибка. Всё транзакционное должно делаться только на сервере, а клиент присоединяется для справки: прошло/ не прошло и почему, если не прошло и для получения новой выборки. Поэтому большинство грамотно спроектированных клиентских приложений не пытаются долго удерживать соединения, а предпоситают их переустанавливать при каждом обращении к серверу.
Надо различать стиль кодинга. Для класической двухзвенки - постоянное соединение наиболее правильное решение. Но тут многие упёрлись в ограничение железа при одновременных конектах >1000. Придумали пулы соединений. Но даже пул - это всё равно набор постоянно открытых подключений расшаренных между подключениями.

А постоянные переподключения - это уже стиль веба. И даже тут пул - это самое правильное решение. Момент установления подключения - достаточно затратен по ресурсам.

Кстати - на личном опыте. PG у меня держит под 1000 подключений норм. По производительности очень хорошо максимально закачать базу в ОЗУ.
Аватара пользователя
WAYFARER
энтузиаст
Сообщения: 567
Зарегистрирован: 09.10.2009 00:00:04
Откуда: г. Курган

Сообщение WAYFARER »

alexs писал(а): 11.02.2026 16:43:58 А постоянные переподключения - это уже стиль веба.
Немного дополню. Это не совсем стиль веба. Это работает только на небольших проектах.
Во-первых действительно дикий оверхед на коннект/дисконнект (TCP/TLS + auth + старт backend-процесса) - PG тратит ресурсы на процессы, а не на работу запросов.
По сути такой подход будет бутылочным горлышком. Особенно если PG работает на Windows, так как он "процессный", а не "потоковый".
Во-вторых легко упереться в max_connections.
По этому там где это можно в вебе надо использовать пул соединений (например PgBouncer).

alexs писал(а): 11.02.2026 16:43:58 Для класической двухзвенки - постоянное соединение наиболее правильное решение. Но тут многие упёрлись в ограничение железа при одновременных конектах >1000.
Но если говорить о двухзвенке то здесь же совсем другая история получается. Держим соединения открытыми - упираемся в железо. А использование transaction pooling ломает всё, что зависит от сессионного состояния, а еще пул теряет смысл при long-lived транзакциях, т.е. одни проблемы пул решает, но взамен создает ограничения. Session pooling уже без таких ограничений, но проблемы производительности и масштабирования по факту не решает.

Потому при большом количестве юзеров и наличии сложной бизнес-логики (особенно на уровне сессии) я не вижу ничего криминального в том что бы открывать и закрывать соединения по необходимости. Тогда 1000 пользователей не будет создавать 1000 коннектов одновременно.
По опыту - в вебе на 1000 юзеров одновременно в онлайне приходится в среднем примерно всего 3-5 одновременных коннектов. Я понимаю что в моем случае срок жизни коннекта составляет 20–200 мс, а в корпоративных системах будут долгие запросы и транзакции, но в любом случае 1000 юзеров не даст 1000 одновременных коннектов при таком подходе. По моему сплошные плюсы))
Да, будут затраты на открытие подключения, но с другой стороны постоянная стоимость снизится.
alexs писал(а): 11.02.2026 16:43:58 PG у меня держит под 1000 подключений норм.
Это только на подключения примерно 10-20гб надо, а ведь еще ваша тысяча пользователей запросы выполняет.
Сколько shared_buffers и work_mem ? И сколько вообще памяти на сервере? А ЦПУ как?
Аватара пользователя
alexs
долгожитель
Сообщения: 4069
Зарегистрирован: 15.05.2005 23:17:07
Откуда: г.Ставрополь
Контактная информация:

Сообщение alexs »

WAYFARER писал(а): 12.02.2026 16:58:42 Это только на подключения примерно 10-20гб надо, а ведь еще ваша тысяча пользователей запросы выполняет.
Сколько shared_buffers и work_mem ? И сколько вообще памяти на сервере? А ЦПУ как?
Вернуться к началу
Рамы там сейчас кажется 150 гиг. База 100 гиг точно уже.
В основном конечно идёт наполнение документами. Нагрузка моментная не сильно большая - 5-10 одномоментных запросов на вставку/изменение - это всё микросекундные запросы. Но бывают тяжёлые аналитические. Хотя там стараемся всё укладывать в 20-40 сеунд. Больше минуты - это уже караул. Приходится в ночных работах делать большие пересчёты под эти отчёты.
Ответить