Форум » LetoDB, HbNetio. » LetoDb fork » Ответить

LetoDb fork

PSP: https://github.com/elchs/LetoDBf https://github.com/elchs/LetoDBf/blob/master/README.md Кто-нибудь пробовал или использует в продакшене?

Ответов - 125, стр: 1 2 3 4 5 6 7 All

PSP: Попробовал. Наткнулся на вылет OrdKeyNo(), которого не было с "оригинальным" LetoDB. На этом эксперименты пока закончены)

Sergy: Использую, доволен. Оригинальную ("нашу") версию не юзал, начал сразу с форка. Был момент, когда Rolf (elch) пропал куда-то из эфира и стало немного ссыкотно. Сейчас вроде активничает. Если есть что обсудить - welcome.

Dima: Sergy пишет: Оригинальную ("нашу") версию не юзал, начал сразу с форка. А что есть какие то полезные плюшки в форке в отличии от нашей ?


Sergy: Dima пишет: А что есть какие то полезные плюшки в форке в отличии от нашей ? На этот вопрос грамотно ответить не смогу, тк плотному знакомству с оригинальным вариантом что то постоянно мешало. Например: 1) необходимость (?) указания для каждой операции адреса коннекта для каждого файла, например: вместо привычного USE tablename ... писать USE (cConnectPath+"tablename") ... Может это и не так на самом деле, но в каждом примере фигурировал примерно такой синтаксис команд. Слишком много кода перелелывать ради не очень понятной перспективы. В форке делается один раз Leto_Connect("191.168.x.x:2812") и всё. Все остальные USE, INDEX, SELECT и тп работают вообще без каких либо модификаций по сравнению с привычным DBFNTX. Единственное что - пришлось кое-где пошаманить с фильтрами, чтобы они фильтровали на сервере, а не на клиенте. Для этого заменяю потихоньку, одно за другим, выражения вроде: SET FILTER TO (table->date >= d1) .AND. (table->date <= d2) на tmp := "(table->date >= 0d" +DTOS(d1)+ ") .AND. (table->date <= 0d" +DTOS(d2)+ ")" SET FILTER TO &tmp Процесс творческий и небыстрый, зато результат на удивление приятный в плане скорости работы. 2) Мелочь конечно, но сама идея передавать при каждом вызове Leto_Somefunc(...) неведомую мне UserStru противоречит моим принципам программирования. Если есть некий набор данных, который нужен серверу, а не клиенту - зачем его хранить на клиенте и каждый раз передавать серверу при вызове функции ? Пусть сервер его сам у себя и хранит. Места в памяти и на диске достаточно. Ну и пара мелочей: 3) Рольф организовал "двухканальный" обмен данными между клиентом и сервером. Т.е. после каждой операции передачи пакета не теряется время на получение сигнала ACK (пакет получен), а сразу передаётся следующий - один за одним. Для обмена сообщениями о некорректном приеме пакетов (NO-ACK) используется второй порт (обычно 2813). И по нему 99,99% времени ничего не передается вообще - тк в большинстве случаев проблем передачи нет как таковых. В случае получения NO-ACK происходит генерация ошибки стандартным способом. Таким образом, вместо миллионов ACK-ACK-ACK..., забивающих канал связи и фрагментирующих пакеты - клиент и сервер изредка (!) кидают NO-ACK по соседнему порту, что должно давать прирост производительности. Я лично не проверял, но почему-то верю ... ))) 4) Не знаю, было ли реализовано в оригинале, но Рольф рекомендует после Leto _Connect(...) сразу вызывать Leto_ToggleZip(1) - чтобы включить быстрый алгоритм LZ4 для сжатия пакетов на лету. При копировании обычных dbf дает прирост производительности 20-40%, что само по себе неплохо. Это я проверял лично. Вот такие мои впечатления за примерно полгода знакомства с "crazy russians db engine, modified with german accuracy..." или что то типа того, как писал Рольф.

SergKis: Sergy пишет Для этого заменяю потихоньку, одно за другим, выражения вроде: это, точно, надо делать ? в примере test_filt.prg есть[pre2] PRIVATE nNumTop, nNumBot ... nNumTop := 1004 nNumBot := 1010 SET FILTER TO NUM >= nNumTop .AND. NUM <= nNumBot ? "with FORCEOPT = .F." ? DbFilter(), "; optimized:", LETO_ISFLTOPTIM() ? #ifndef __XHARBOUR__ /* -> RTE cause of missing filter sync */ SET( _SET_FORCEOPT, .T. ) #endif SET FILTER TO NUM >= nNumTop .AND. NUM <= nNumBot ? "with FORCEOPT = .T." ? DbFilter() ? "--> optimized:", LETO_ISFLTOPTIM() ... [/pre2] readme_rus.txt функции dbSetFilter(). Фильтр, который может быть выполнен на сервере, называется оптимизированным. Если фильтр не может быть выполнен на сервере, он является неоптимизированным. Такой фильтр является медленным, поскольку с сервера все равно запрашиваются все записи, которые затем фильтруются на клиенте. Чтобы задать оптимизированный фильтр, необходимо, чтобы в логическом выражении для фильтра отсутствовали переменные или функции, определенные на клиенте. Чтобы проверить, является ли фильтр оптимизированным, надо вызвать функцию LETO_ISFLTOPTIM().

SergKis: PS. Сори, не туда нажал. т.е. в "нашей" версии из за переменных фильтр не оптимизирован, а тут показан, что оптимизирован.

Sergy: SergKis пишет: т.е. в "нашей" версии из за переменных фильтр не оптимизирован, а тут показан, что оптимизирован. Не очень понял про отличия "нашей" и "форк" версии в отношении оптимизированных фильтров, но компиляция и запуск test_filt.prg выдает следующее: [pre2]Testing filtering for DBFCDX ordSetFocus( "NAME" ) seek 1001 Petr 09/02/18 - Ok seek 1010 Andrey 18/02/18 - Ok NUM >= 1004 .AND. NUM <= 1010 ; optimized: .F. go top 1005 Alexey 13/02/18 - Ok go bottom 1008 Vladimir 16/02/18 - Ok seek 0 / / - Ok seek 1010 Andrey 18/02/18 - Ok with FORCEOPT = .F. NUM >= nNumTop .AND. NUM <= nNumBot ; optimized: .F. with FORCEOPT = .T. NUM >= nNumTop .AND. NUM <= nNumBot --> optimized: .F. go top 1005 Alexey 13/02/18 - Ok go bottom 1008 Vladimir 16/02/18 - Ok seek 0 / / - Ok seek 1010 Andrey 18/02/18 - Ok cName $ NAME ; 'Alex' $ NAME - optimized: .F. go top 1003 Alexander 11/02/18 - Ok go bottom 1005 Alexey 13/02/18 - Ok seek 0 / / - Ok count 42 - Ok [/pre2]

SergKis: Sergy пишет выдает следующее Ясненько. Я см. только по тексту примера, нет установленного форк, из него понял, как понял Увидел, прицепленный rdd SIXCDX, что не плохо.[pre2] leto_DbDriver( [ <cNewDriver> ], [ <cMemoType> ], [ <nBlocksize> ] ) ==> aInfo убрааны LETO_SETSEEKBUFFER( nRecsInBuf ) ==> 0 ! DEPRECATED ! LETO_SETFASTAPPEND( lFastAppend ) ==> .F. ! DEPRECATED ! не использовал. Leto_FCopyToSrv( cLocalFileName, sServerFileName[, nStepSize ] ) ==> lSuccess Leto_FCopyFromSrv( cLocalFileName, sServerFileName[, nStepSize ] ) ==> lSuccess Copy a file from/ to client to/ from server, where: инересненько с переменными PUBLIC\PRIVATE пробовал ? cExpr := Leto_VarExprCreate( "Memvar1 [, MemvarX ]", @aArr ) Repeatedly call after each occasion of changed value: Leto_VarExprSync( aArr ) Clean up with: Leto_VarExprClear( aArr ) LETO_VAREXPRTEST( cExpression ) ==> lContainMemvar Return TRUE ( .T. ) if <cExpression> contains memvars LETO_VAREXPRCREATE( cExpression [, @aLetoVar ] ) ==> cModifiedExpression ... [/pre2] если объявленное в readme.txt работает, то есть движение вперед

Sergy: До серверных процедур (RPC, UDF) и обмена переменными пока не дошел. Пока изучаю возможности фонового взаимодействия с LetoDB сервером в отдельных потоках. В данный момент - не очень понимаю, что принципиально мешает передавать от клиента на сервер массивы и хэши. Пусть даже в текстовом виде. "Наверху" пусть примут строку, преобразуют ее один раз в переменную, а дальше - у меня много таких вот конструкций: SET FILTER TO table->client $ hListSelectedClients Работает шустро, удобно и понятно: в хэш можно выбрать хоть одного клиента, хоть сотню - на производительности это почти никак не скажется. ASCAN() по массиву выбранных клиентов тормозит: чем больше клиентов, тем сильнее. Сам бы подкинул Рольфу идею, но похоже, что я ему уже мозг вынес и он поставил меня в игнор...

SergKis: Sergy пишет что принципиально мешает передавать от клиента на сервер массивы и хэши массивы передаются, как параметры в UDF функции и возврат обратно. массив в hash в udf получить просто, т.е. исп. вместо set filter ... udf функцию для уст.\снятия фильтра с hash. еще вариант, можно исп. leto_SetEnv( xScope, xScopeBottom, xOrder, cFilter, lDeleted ) aRecNo := leto_dbEval() leto_ClearEnv( xScope, xScopeBottom, xOrder, cFilter ) в "нашей" версии aRecNo положить в SkipBufer и обработать записи DO WHILE ! eof() ... SKIP ENDDO в "форк" не знаю, можно ли так делать ?

SergKis: посмотрел "форк" на наличие HB_FUNC[pre2] LETO_PARSEREC( cRecBuf ) --> nil LETO_PARSERECODRS( cRecBuf ) --> nil не увидел, не есть хорошо режим работы * UDF_dbEval function returns buffer with records by order <xOrder>, and for condition, * defined in <xScope>, <xScopeBottom>, <cFilter>, <lDeleted> parameters * Function call from client: leto_ParseRecords( leto_Udf('UDF_dbEval', <xScope>, <xScopeBottom>, <xOrder>, <cFilter>, <lDeleted> ) ) while ! eof() ... skip enddo dbInfo( DBI_CLEARBUFFER ) удобен [/pre2]

SergKis: Sergy пишет у меня много таких вот конструкций: SET FILTER TO table->client $ hListSelectedClients можно заменить на cKli := ',' while !eof() cKli += alltrim(table->client)+',' skip end потом SET FILTER TO &( ','+table->client+',' $ '"'+cKli+'"' )

Sergy: SergKis пишет: массивы передаются, как параметры в UDF функции и возврат обратно. массив в hash в udf получить просто, т.е. исп. вместо set filter ... udf функцию для уст.\снятия фильтра с hash. еще вариант, можно исп. leto_SetEnv( xScope, xScopeBottom, xOrder, cFilter, lDeleted ) aRecNo := leto_dbEval() leto_ClearEnv( xScope, xScopeBottom, xOrder, cFilter ) в "нашей" версии aRecNo положить в SkipBufer и обработать записи DO WHILE ! eof() ... SKIP ENDDO в "форк" не знаю, можно ли так делать ? Больше 20 лет занимаюсь Clipper/Harbour - но честно говоря, почти ничего не понял из вышесказанного, сорри Что за SkipBuffer и зачем в него класть aRecNo ?

Sergy: SergKis пишет: посмотрел "форк" на наличие HB_FUNC LETO_PARSEREC( cRecBuf ) --> nil LETO_PARSERECODRS( cRecBuf ) --> nil не увидел, не есть хорошо режим работы [pre2] * UDF_dbEval function returns buffer with records by order <xOrder>, and for condition, * defined in <xScope>, <xScopeBottom>, <cFilter>, <lDeleted> parameters * Function call from client: leto_ParseRecords( leto_Udf('UDF_dbEval', <xScope>, <xScopeBottom>, <xOrder>, <cFilter>, <lDeleted> ) ) while ! eof() ... skip enddo dbInfo( DBI_CLEARBUFFER ) [/pre2] удобен Не пользовался, но в readme.txt есть пометка: see new LETO_DBEVAL() as powerful alternative, or sample of UDF_dbEval() in tests/letoudf.prg

Sergy: SergKis пишет: можно заменить на cKli := ',' while !eof() cKli += alltrim(table->client)+',' skip end потом SET FILTER TO &( ','+table->client+',' $ '"'+cKli+'"' ) В паре мест делаю примерно похожим образом, но это 'костыли': чем длиннее строка, тем медленнее она будет обрабатываться. Хотелось-бы нормальное решение.

Dima: Sergy пишет: Хотелось-бы нормальное решение. В Leto Павел делал BM фильтры , не подходит ?

Sergy: Dima пишет: В Leto Павел делал BM фильтры , не подходит ? Поддержка rushmore bitmap фильтров в форке никуда не делась и для многократной выборки - вполне: сначала выбрать нужные записи, потом уже по ним работать. Например в DbEdit(). А для единичного отчета, когда Пете нужен отчет по всем его клиентам из Пензы за текущий квартал, а Васе - из Уфы за предыдущий год, то получается у каждого - минимум два прохода по таблице продаж. Причем первый из них - так и не оптимизирован хэшем, а второй - вообще лишний.

SergKis: Sergy пишетпочти ничего не понял из вышесказанного Leto сервер может подключать hrb файл с пользовательскими функциями при наличии оного. Пример letoudf.hrb, получаемый из letoudf.prg. Функции вызываются с клиента, исполняются на сервере. Им могут передаваться параметры типов: C,N,L,D,A, которые можно применять на сервере. В "нашей" версии первым в функцию передается nUserStru. Условно можно назвать номером соединения со стороны сервера с клиентом. Кроме получения данных соединения, nUserStru можно исп. как префикс файлов (подкаталог) для получаемых на сервере файлов выборок и возврата на клиента списка имен файлов, для открытия и дальнейшего исп. на клиенте, с удалением после работы. То, что в "форк" убран nUserStru, не уверен что это хорошо, надо посмотреть. В "форк" LETO_DBEVAL( [ <cBlock> ], [ <cFor> ], [ <cWhile> ], [ nNext ], [ nRecord ], [ lRest ] ) ==> aResults возвращает массив RecNo для исп. в BM фильтрах или собственными dbGoto(...) В "нашей" версии кроме BM фильтров и dbGoto(...) есть механизм на сервере заполнения SkipBuffer (это буфер для nn записей), исп. при работе leto серверов (можно уст. кол-во строк в буфере есть ф-я). По этому буферу идет работа на клиенте dbSkip() и т.д.. Формируем SkipBuffer выполнением ф-ии на сервере leto_ParseRecords( leto_Udf('UDF_dbEval', <xScope>, <xScopeBottom>, <xOrder>, <cFilter>, <lDeleted> ) ) на входе leto_ParseRecords строка. Получается аналог SET FILTER TO ..., т.е. минимум изм. кода старой проги. Далее работа в цикле. В "форк" отсутствие leto_ParseRecords, по мну, БАААЛЬШОЙ минус, не смертельный, но ...

Sergy: Более-менее понятно, но очень уж зависимо от конкретной реализации. Пытаюсь по максимуму использовать стандартизованный интерфейс харборовских RDD и не использовать без особой необходимости Leto_xxx(). Ведь если через пару-тройку лет появится какой-нибудь WinterDB - опять всё переписывать? У меня половина юзеров работает с базой удаленно, через RDP. Для них - данные локальны и выборка идет через dbfntx. Другя половина - как обычно, через сеть. Сначала полностью через dbfntx, сейчас - без особой спешки все больше и больше операций переключаю на LetoDBf. Форк интересен (для меня) тем, что открыв таблицу один раз VIA "dbfntx" (rdp-локально) или VIA "leto" (по сети) я больше ничего не модифицирую в коде. Максимум - оптимизирую фильтр для выполнения на сервере. Но он точно так же будет работать как с leto, так и с dbfntx. Пока оставил на "сладкое" некоторые моменты в программе, которые нельзя решить "влоб": как например, не могу отказаться от простого фильтра по хэшу или фильтра, реагирующего на нажатия кнопок на клавиатуре (живой поиск), например: SET FILTER TO MyFilter() Да, сделав специфические заточки под Leto, я потеряю совместимость с dbfntx. Пока непонятно - будет ли стоить овчинка выделки. Проект большой и раскорячить его мне не хотелось бы. В данный момент у меня даже сетевые юзеры при отсутствии коннекта с LetoDB получат сообщение при входе, но продолжат работать, как ни в чем ни бывало. Чуть медленнее, чем "обычно" (к которому они уже привыкли), но работать. Понятно, что и локальных rdp-юзеров можно переключить полностью на локальный Leto сервер. И более того - с точки зрения целостности данных так и сделаю. Но процесс перевода небыстр, а для юзеров нужны всё новые и новые фичи, а не только ускорение старых. Такие вот мысли...

SergKis: Sergy пишет опять всё переписывать? можно исп. обертки функции\классы\препроцессор(leto_file.ch в "форк") If RddName() == 'LETO' ... Else ... EndIf исп. leto_sum(...), leto_group(...), можно пробнуть адаптировать их под работу без сервера или Пашу попросить это сделать, если очень надо. Но процесс перевода небыстр, а для юзеров нужны всё новые и новые фичи, а не только ускорение старых. На новые фичи перейти мне удалось только внедрением letodb с новой организацией базы на cdx (старая ntx), т.е. загрузка на сервер старой и организация переноса изменений (записи таблиц) с новой базы в старую. Новые фичи работают на letodb, старые как было, постепенно старые режимы переключаю на letodb



полная версия страницы