Zeos ZQuery - чудовищный расход памяти в Windows

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

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

Zeos ZQuery - чудовищный расход памяти в Windows

Сообщение Aleks69 » 14.07.2019 14:51:25

Добрый день!

В потоке (на каждый коннект в Indy) создаю ZConnection, потом ZQuery, потом выборка 10 строк (с лимитом) из таблицы без BLOB полей в MySQL. И тут же добавление записи. Потом ZQuery и ZConnection уничтожаются.
Все работает стабильно, но после вставленных 20 000 строк приложение стало занимать в памяти до 200мб, с 10 мб при запуске.

Получается, ZQuery.Close; ZQuery.Free и ZConnection.Connected:=False; ZConnection.Free недостаточно?
В порядке бреда делаю еще ZQuery.EmptyDataSet - но это похоже никак не помогает.

Да, еще наблюдение - память, похоже, уходит в ZQuery. Оставил в обработчике только создание/подключение/отключение/уничтожение ZConnection - память не теряется.

В чем может быть дело?

P.S. Сейчас в таблице более 80 000 строк и продолжает работать без вылетов.
Приложение в памяти (по диспетчеру задач) стало занимать с 10 мб при запуске 903 мб теперь !!!
Aleks69
новенький
 
Сообщения: 20
Зарегистрирован: 29.03.2009 14:25:01

Re: Zeos ZQuery - чудовищный расход памяти в Windows

Сообщение olegy123 » 15.07.2019 06:44:35

что говорит netstat? есть ли много TIME-WAIT?

какая логика при работе с БД и Инди. Где уничтожается объекты?
Какой инди 9 или 10?

Добавлено спустя 2 минуты 30 секунд:
ZQuery может быт не главным виновником
olegy123
энтузиаст
 
Сообщения: 1473
Зарегистрирован: 25.02.2016 12:10:20

Re: Zeos ZQuery - чудовищный расход памяти в Windows

Сообщение Aleks69 » 15.07.2019 09:26:01

Добрый день!

Про Indy я, признаюсь, написал для простоты. Строго говоря, это Intraweb, основанный на Indy (10 версии видимо). Обработка в событии IWServerControllerBaseBeforeDispatch. Все на одной машине. В MySQL Workbench отлично видно, что записи исправно поступают в базу. А тестовый код вот, если его просто поместить под кнопку (можно и таймер наверное) после нескольких сот нажатий отлично видно, как растет объем памяти, потребляемой программой.

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

      ZConnection:=TZConnection.Create(Nil);
      ZConnection.User:='user';
      ZConnection.Password:='11112222';
      ZConnection.Database:='db_video';
      ZConnection.HostName:='192.168.1.152';
      ZConnection.LibraryLocation:='C:\Program Files (x86)\MySQL\MySQL Server 5.7\lib\libmysql.dll';
      ZConnection.Port:=3307;
      ZConnection.Protocol:='mysqld-5';
      ZConnection.Connected:=True;

      ZQuery:=TZQuery.Create(Nil);
      ZQuery.Connection:=ZConnection;

      ZQuery.SQL.Clear;
      ZQuery.SQL.Add('Select * from  record_table limit 0,1');

       ZQuery.Open;

       CreateGUID(MyGUID);
       Str_MyGUID:=GUIDToString(MyGUID);  // P_Key
       Delete(Str_MyGUID,1,1);
       Delete(Str_MyGUID,37,1);

      ZQuery.Append;

      ZQuery.FieldByName('record_id').AsString:=Str_MyGUID;
      ZQuery.FieldByName('unix_timestamp').AsInteger :=DateTimeToUnix(Now)-10800;
      ZQuery.FieldByName('time_begin').AsInteger :=DateTimeToUnix(Now)-10800;
      ZQuery.FieldByName('room_id').AsString:='6D4E0BC7-4265-41B9-8DE6-A6C54B28805E';

      ZQuery.Post;


      Str_MyGUID:='';

     Finally

        ZQuery.EmptyDataSet;
        ZQuery.Close;
        ZQuery.Free;

        ZConnection.Connected:=False;
        ZConnection.Free;

    End;
Aleks69
новенький
 
Сообщения: 20
Зарегистрирован: 29.03.2009 14:25:01

Re: Zeos ZQuery - чудовищный расход памяти в Windows

Сообщение debi12345 » 15.07.2019 10:54:02

Соберите Вашу прогу с отладочной инфой и запустите в ее Valgrind (специальная прога для обнаружения проблем с памятью) и после этого посмотрите лог-файл. 99.99% что сразу увидите корень проблемы :wink:
Аватара пользователя
debi12345
долгожитель
 
Сообщения: 5295
Зарегистрирован: 10.05.2006 23:41:15
Откуда: Ташкент (Узбекистан)

Re: Zeos ZQuery - чудовищный расход памяти в Windows

Сообщение olegy123 » 16.07.2019 20:28:53

Indy держит сокет даже при завершения подключения, пока сама система не утилизирует, а утилизировать она может и через 2 суток. это плохо если на сокет понавешана логика, Вот весит кусок в памяти логики, только потому что система не утилизировала сокет.
но это на стороне сервера, Спрятанная логика работы с сокетом. Там сделано все чтобы компонентами можно было подключать и отключать модули. SSL, IdMessage. и др.
Так что делать нагруженные приложения на Indy это рискованно.


Aleks69 писал(а): А тестовый код вот, если его просто поместить под кнопку (можно и таймер наверное) после нескольких сот нажатий отлично видно, как растет объем памяти, потребляемой программой.

попробуйтe FreeAndNil(ZQuery); FreeAndNil(ZConnection) вместо ZQuery.Free и ZConnection.Free; я точно не помню, это меня спасало при написании при высоконагруженных сервисов. Потери в памяти свелись к нулю, когда я стал FreeAndNil вместо Free делать, вроде в куче не совсем утилизируются объекты, либо сборщик мусора очень ленивый, не поспевает убирать

Кстати потери памяти хорошо тестятся при проверки кучи, есть такая галка при компиляции и отладки. Если программа завершилась с текстом в каких местах есть потери. то значит прога не сбалансированная. Хорошо помогает этот check heap.

Сам рост памяти возможен даже из-за системы, линух этим страдает. Если работаете с переменным массивом данных то рост обеспечен, линух сильно не стремится утилизировать мусор и освобождать память, он лучше возьмет из новых участков. Возможно в Windows лучше работа идёт.
Кстати в Java еще хуже обстоят дела. У меня Android Studio при 4Гб оперативы плохо вел себя, только при 8Гб стал лучше работать.

Так что лучше чекайте Heap - 100% гарантия на текучесть памяти. Правда есть еще libmysql.dll там может быть "открытия чудные" ждут.

Добавлено спустя 6 минут 9 секунд:
debi12345 писал(а):Соберите Вашу прогу с отладочной инфой и запустите в ее Valgrind

я не осилил Valgrind, у меня логи не читались Valgrind

Добавлено спустя 2 минуты 25 секунд:
Heap чем хорош, тем что если вы хотите видить действинную чистоту то нужно позаботится об правильном завершении проги. Он вынуждает делать корректный выход из программы..
olegy123
энтузиаст
 
Сообщения: 1473
Зарегистрирован: 25.02.2016 12:10:20

Re: Zeos ZQuery - чудовищный расход памяти в Windows

Сообщение serbod » 21.07.2019 21:12:08

1. Connection достаточно одного на всех и не надо его туда-сюда создавать и удалять.
2. Лучше использовать ZROQuery с опцией OneDirection.
3. Для одинаковых запросов с разными параметрами лучше иметь постоянные ZQuery, а не создавать каждый раз.
4. Возможно, это проблема в сочетании с конкретной БД. Можно попробовать "родные" лазаревские компоненты.
Аватара пользователя
serbod
постоялец
 
Сообщения: 422
Зарегистрирован: 16.09.2016 11:03:02
Откуда: Минск

Re: Zeos ZQuery - чудовищный расход памяти в Windows

Сообщение nic1982 » 11.09.2019 11:02:49

У меня были похожие проблемы со стандартными компонентами и я решил их так:
Код: Выделить всё
  // отключаемся
  FreeAndNil(SQLiteQuery);
  FreeAndNil(SQLiteTransaction);
  FreeAndNil(SQLiteConnection);


Добавлено спустя 4 минуты 28 секунд:
Данную идею взял по моему отсюда https://www.gunsmoker.ru/2009/04/freeandnil-free.html
nic1982
новенький
 
Сообщения: 37
Зарегистрирован: 17.05.2011 16:34:05

Re: Zeos ZQuery - чудовищный расход памяти в Windows

Сообщение CynicRus » 11.09.2019 12:38:48

Поделюсь кодом, который работает уже много лет 24/7, и нет никаких проблем. Каждый подпилит под себя при необходимости:
Код: Выделить всё
unit usqladapter;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, LCLIntf, LCLType, LMessages, ZConnection, ZAbstractConnection,
  ZDataset, uconfig, ulogger;

type

  { TSqlAdapter }

  TSqlAdapter = class
  private
    FWindowHandle: THandle;
  public
    constructor Create(WndH: THandle = -1);
    destructor Destroy; override;

    function CreateConnection(const Hostname: string; const Port: integer;
      const Login, Password, DbName: string): TZConnection; overload;
    function CreateConnection: TZConnection; overload;
    function CreateQuery: TZQuery;

    procedure FieldsToList(Fields: array of string; List: TStringList;
      const Prefix: string = '');

    function InsertFields(Fields: array of string): string; overload;
    function InsertFields(Fields: TStringList): string; overload;
    function UpdateFields(Fields: array of string; const Prefix: string = ''): string;
      overload;
    function UpdateFields(Fields: TStringList; const Prefix: string = ''): string;
      overload;

    procedure DeleteRecord(const Table, Field, Value: string; RaiseException: boolean);
    procedure ImportData(const SqlData: string);
    procedure ErrorHandler(sMethodName: string; E: Exception);
    procedure FreeQuery(Query: TZQuery);
    procedure Log(sLogString: string);
    procedure Send(Msg: cardinal; W: WParam; L: LParam);
  end;

implementation

{ TSqlAdapter }

constructor TSqlAdapter.Create(WndH: THandle = -1);
begin
  FWindowHandle := WndH;
end;

destructor TSqlAdapter.Destroy;
begin
  inherited Destroy;
end;

function TSqlAdapter.CreateConnection(const Hostname: string;
  const Port: integer; const Login, Password, DbName: string): TZConnection;
begin
  Result := TZConnection.Create(nil);
  Result.LibraryLocation := 'libmysql.dll';
  Result.Protocol := 'mysql';
  Result.HostName := Hostname;
  Result.Port := Port;
  Result.User := Login;
  Result.Password := Password;
  Result.Database := DbName;
end;

function TSqlAdapter.CreateConnection: TZConnection;
begin
  Result := CreateConnection(Config.Server, Config.Port, Config.Login,
    Config.Password, Config.DataBaseName);
end;

function TSqlAdapter.CreateQuery: TZQuery;
var
  Conn: TZConnection;
begin
  Conn := CreateConnection;
  Conn.UseMetadata := False;
  Result := TZQuery.Create(nil);
  Result.Connection := Conn;
end;

function TSqlAdapter.InsertFields(Fields: array of string): string;
var
  strList: TStringList;
begin
  strList := TStringList.Create;
  try
    FieldsToList(Fields, strList);
    Result := InsertFields(strList);
  finally
    strList.Free;
  end;
end;

function TSqlAdapter.InsertFields(Fields: TStringList): string;
var
  Count: integer;
begin
  Result := '(';
  for Count := 0 to Fields.Count - 1 do
  begin
    if Count > 0 then
      Result := Result + ', ';
    Result := Result + '`' + Fields[Count] + '`';
  end;

  Result := Result + ') VALUES (';
  for Count := 0 to Fields.Count - 1 do
  begin
    if Count > 0 then
      Result := Result + ', ';
    Result := Result + ':' + Fields[Count];
  end;
  Result := Result + ')';
end;

function TSqlAdapter.UpdateFields(Fields: array of string; const Prefix: string): string;
var
  Count: integer;
begin
  Result := '';
  for Count := 0 to Length(Fields) - 1 do
  begin
    if Count > 0 then
      Result := Result + ', ';
    Result := Result + '`' + Prefix + Fields[Count] + '`=' + Prefix + Fields[Count];
  end;
end;

function TSqlAdapter.UpdateFields(Fields: TStringList; const Prefix: string): string;
var
  Count: integer;
begin
  Result := '';
  for Count := 0 to Fields.Count - 1 do
  begin
    if Count > 0 then
      Result := Result + ', ';
    Result :=
      Result + '`' + Prefix + Fields[Count] + '`=' + ':' + Prefix + Fields[Count];
  end;
end;

procedure TSqlAdapter.DeleteRecord(const Table, Field, Value: string;
  RaiseException: boolean);
var
  Query: TZQuery;
begin
  Query := CreateQuery;
  try
    try
      Query.SQL.Text := 'DELETE FROM `' + Table + '` WHERE `' + Field + '`=' + Value;
      Query.ExecSQL;
      if (RaiseException) and (Query.RowsAffected = 0) then
        raise Exception.Create('ErrRecordNotFound');
    except
      on E: Exception do
      begin
        ErrorHandler('DeleteRecord: ' + Table, E);
        raise;
      end;
    end;
  finally
    FreeQuery(Query);
  end;
end;

procedure TSqlAdapter.ImportData(const SqlData: string);
var
  Query: TZQuery;
begin
  Query := CreateQuery;
  try
    Query.SQL.Text := SqlData;
    try
      Query.ExecSQL;
    except
      on E: Exception do
      begin
        ErrorHandler('ImportData', E);
        raise;
      end;
    end
  finally
   // Send(WM_IMPORTED_DATA, 0, integer(@CDR));
    FreeQuery(Query);
  end;
end;

procedure TSqlAdapter.FieldsToList(Fields: array of string; List: TStringList;
  const Prefix: string);
var
  Count: integer;
begin
  for Count := 0 to Length(Fields) - 1 do
    List.Add(Prefix + Fields[Count]);
end;

procedure TSqlAdapter.FreeQuery(Query: TZQuery);
var
  Conn: TZAbstractConnection;
begin
  Conn := Query.Connection;
  if Conn.Connected then
    Conn.Disconnect;
  if Query <> nil then
    Query.Free;
  if Conn <> nil then
    Conn.Free;
end;

procedure TSqlAdapter.ErrorHandler(sMethodName: string; E: Exception);
begin
  Log(sMethodName + ': ' + E.Message);
end;

procedure TSqlAdapter.Log(sLogString: string);
var
  filename: string;
  logFile: TextFile;
begin
  filename := 'log_' + FormatDateTime('DD_MM_YYYY', Now) + '.txt';
  AssignFile(logFile, GetCurrentDir + '\logs\' + filename);
  if not FileExists(GetCurrentDir + '\logs\' + filename) then
  begin
    rewrite(logFile);
  end
  else
  begin
    append(logFile);
  end;
  writeln(logFile, sLogString);
  CloseFile(logFile);
end;

procedure TSqlAdapter.Send(Msg: cardinal; W: WParam; L: LParam);
begin
  if (FWindowHandle > -1) then
    SendMessage(FWindowHandle, Msg, W, L);
end;

end.
CynicRus
постоялец
 
Сообщения: 100
Зарегистрирован: 28.06.2012 14:31:11


Вернуться в Lazarus

Кто сейчас на конференции

Сейчас этот форум просматривают: Google [Bot], Yandex [Bot] и гости: 4

Рейтинг@Mail.ru