Комментарии 21
А ей все так же нужен целый инстанс postgess, чтобы проверка запросов сработала?
А вот сразу пара вопросов.
Номер раз: в какой момент происходит построение плана запроса? Вот пишем:
let query = sqlx::query("SELECT TBL1FLD1, TBL2FLD2 FROM TBL1 JOIN TBL2 ON TBL2FLD1 = TBL1FLD1 WHERE TBL1FLD3 < 5")
.execute(&pool)
.await
.unwrap();
Когда будет план запроса построен? Во время компиляции (и где он сохранится при этом?) или в рантайме?
Запрос ведь может быть достаточно сложным. Например:
with CLIENTTYPE as (
select C4CTP,
cast(
case
when C4CBQ = 'C'
and C4IND = 'N'
and C4CIGR <> '12' then 'U'
else 'F'
end as char(1)) CLTP
from C4PF
where ((C4IND = 'Y'
and C4CBQ in ('B', 'C', 'N'))
or (C4IND = 'N'
and C4CBQ = 'C'))
),
NMCCHANGED as (
select distinct NMCCUS,
NMCCLC,
GFCPNC NMCCPNC,
CLTP NMCCLTP
from NMCPF
join GFPF
on (GFCUS, GFCLC) = (NMCCUS, NMCCLC)
join CLIENTTYPE
on GFCTP = C4CTP
where NMCDT >= 1230405
and NMCNTP not in ('B')
),
DULCHANGED as (
select distinct RDKCUS,
RDKCLC,
GFCPNC RDKCPNC,
CLTP RDKCLTP
from RDKPF
join GFPF
on (GFCUS, GFCLC) = (RDKCUS, RDKCLC)
join CLIENTTYPE
on GFCTP = C4CTP
where RDKDTM >= 1230405
)
select
coalesce(GFCUS, RDKCUS, NMCCUS) CUS,
coalesce(GFCLC, RDKCLC, NMCCLC) CLC,
coalesce(GFCPNC, RDKCPNC, NMCCPNC) CPNC,
coalesce(GFCRF, '0') CRF,
coalesce(BGDTBR, 0) DTBR,
coalesce(CLTP, RDKCLTP, NMCCLTP) CLTP,
cast(
case
when GFDCC > 0
or GFDEL = 'Y' then 'X'
when GFCOD >= 1230405 then 'N'
else ' '
end as char(1)) CHK,
case
when GFDLM >= 1230405 then 'Y'
else ''
end CHKGFBG,
case
when RDKCUS is not NULL then 'Y'
else ''
end CHKRDK,
case
when NMCCUS is not NULL then 'Y'
else ''
end CHKNMC
from GFPF
join CLIENTTYPE
on (C4CTP) = (GFCTP)
left join BGPF
on (BGCUS, BGCLC) = (GFCUS, GFCLC)
and CLTP = 'F'
full join NMCCHANGED
on (NMCCUS, NMCCLC) = (GFCUS, GFCLC)
full join DULCHANGED
on (RDKCUS, RDKCLC) = (GFCUS, GFCLC)
and CLTP = 'F'
where GFDLM >= 1200325
or RDKCUS is not NULL
or NMCCUS is not NULL
И план для него будет тоже достаточно сложен:
![](https://webcf.waybackmachine.org/web/20240831110918/https://habrastorage.org/getpro/habr/upload_files/c50/f35/c2a/c50f35c2ad5028ae12f0f5dc54a169a1.png)
Построение плана в рантайме занимает определенное время...
Номер два: допустим используем конструкцию типа
#[derive(sqlx::FromRow)]
struct Foo {
id: i32,
message: String
}
Что там нужно написать, если в структуре записи есть поля типов varchar, decimal, numeric, date, time, timestmp? В языке эти типы как-то поддерживаются?
Учитывая, что программа компилируется для запуска везде, а не на конкретном сервере - варианта "строить план запроса при компиляции" просто нет.
Что же до типов данных - в языке уж точно найдётся куда эти типы отобразить, всё же это современный ЯП где можно вводить свои типы данных. Вопрос надо задавать не "есть ли в языке эти типы", а "есть ли в драйвере поддержка этих типов"?
И ответ тут- да, есть. Вот тут есть список что тут куда отображается: https://docs.rs/sqlx-postgres/0.7.2/sqlx_postgres/types/index.html
Учитывая, что программа компилируется для запуска везде,
Что значит "везде"? Я так понимаю, что подобные программы пишутся под конкретную БД и завязаны на структуру конкретной БД.
Построение плана запроса в рантайме кратно снижает производительность. Из личной практики - в использумом у нас языке есть т.н. "embedded sql" - возможность вставлять в код sql запросы. Не какими-то инстансами, на напрямую:
exec sql declare curRDMS12Clients cursor for
with Holders as
(
select F0UCUS1,
F0UCLC1,
F0UCUS2,
F0UCLC2
from F0UPF F0U
join FPKPF FPK on (FPKSCON, FPKCTP) = (F0U.F0USCON, 'F')
where (F0UCUS2 <> F0UCUS1 or F0UCLC2 <> F0UCLC1)
and F0UCD >= :$PDate
and F0USCID in ('00', '01', '02', '03', '14', '16')
and F0USCON like 'P%'
),
CheckOwner as
(
select RDKCUS OWNCUS,
RDKCLC OWNCLC,
RDKSER OWNSER,
RDKNUM OWNNUM,
RDKOPN OWNOPN,
RDKEDT OWNEDT
from RDKPF RDK1
where (RDKUCD, RDKSDL, RDKOSN) =
('001', 'Y', 'Y')
and RDKEDT < :$PDate
),
CheckHolder as
(
select RDKCUS HLDCUS,
RDKCLC HLDCLC,
RDKSER HLDSER,
RDKNUM HLDNUM,
RDKOPN HLDOPN,
RDKEDT HLDEDT
from RDKPF RDK2
where (RDKUCD, RDKSDL, RDKOSN) =
('001', 'Y', 'Y')
and RDKEDT <= :$FDate
and not exists
(
select SCAN
from SCPF
where (SCAN, SCAI17, SCAI30, SCAI80) =
(RDK2.RDKCUS, 'N', 'N', 'N')
and SCNANC in ('40817', '40820', '42301', '42601')
)
and (
(RDK2.RDKEDT >= :$PDate and
not exists
(
select RDKMCUS
from RDKMPF
where (RDKMCUS, RDKMCLC, RDKMSER, RDKMNUM, RDKMUCD, RDKMTP) =
(RDK2.RDKCUS, RDK2.RDKCLC, RDK2.RDKSER, RDK2.RDKNUM, '001', 'I')
)
) or
(RDK2.RDKEDT < :$PDate and
not exists
(
select RDKMCUS
from RDKMPF
where (RDKMCUS, RDKMCLC, RDKMSER, RDKMNUM, RDKMUCD, RDKMTP) =
(RDK2.RDKCUS, RDK2.RDKCLC, RDK2.RDKSER, RDK2.RDKNUM, '001', 'J')
)
)
)
)
select F0UCUS1,
F0UCLC1,
F0UCUS2,
F0UCLC2,
coalesce(HLDSER, ''),
coalesce(HLDNUM, ''),
coalesce(HLDOPN, 0),
coalesce(HLDEDT, 0),
coalesce(OWNSER, ''),
coalesce(OWNNUM, ''),
coalesce(OWNOPN, 0),
coalesce(OWNEDT, 0),
cast (case
when (HLDEDT is not null and HLDEDT >= :$PDate)
then 'Y'
else 'N'
end as char(1)) FLAGI,
cast (case
when (HLDEDT is not null and HLDEDT < :$PDate)
then 'Y'
else 'N'
end as char(1)) FLAGJ,
cast (case
when OWNEDT is not null
then 'Y'
else 'N'
end as char(1)) FLAGH
from Holders
left join CheckHolder on (HLDCUS, HLDCLC) = (Holders.F0UCUS2, Holders.F0UCLC2)
left join CheckOwner on (OWNCUS, OWNCLC) = (Holders.F0UCUS1, Holders.F0UCLC1)
where HLDSER is not null
or (OWNSER is not null and
not exists
(
select RDKMCUS
from RDKMPF
where (RDKMCUS, RDKMCLC, RDKMSER, RDKMNUM, RDKMUCD, RDKMTP) =
(Holders.F0UCUS2, Holders.F0UCLC2, CheckOwner.OWNSER, CheckOwner.OWNNUM, '001', 'H')
));
далее
exec sql open curRDMS12Clients;
и затем
exec sql fetch curRDMS12Clients for :sqlRows rows into :dsSQLData;
где dsSQLData массив структур
dcl-ds t_dsSQLData qualified template;
CUS1 char(6) inz;
CLC1 char(6) inz;
CUS2 char(6) inz;
CLC2 char(6) inz;
HSER char(10) inz;
HNUM char(35) inz;
HOPN zoned(7: 0) inz;
HEDT zoned(7: 0) inz;
OSER char(10) inz;
ONUM char(35) inz;
OOPN zoned(7: 0) inz;
OEDT zoned(7: 0) inz;
FLGI char(1) inz;
FLGJ char(1) inz;
FLGH char(1) inz;
end-ds;
dcl-ds dsSQLData likeds(t_dsSQLData) dim(sqlRows);
Куда блоками по sqlRows элементов читаются данные в цикле.
Тип zoned в нашем языке соответствует типу numeric в SQL (есть и остальные - packed соответствует decimal, поддерживаются date, time, varchar, timestamp).
Так вот, есть два варианта - "статический", как выше, где план запроса готовится на этапе компиляции и где-то там сохраняется, и "динамический", когда формируем строку запроса в коде, а потом
exec sql declare SqlStatement1 statement;
exec sql prepare SqlStatement1 from :sqlTxt1;
exec sql declare currACY1 cursor for SqlStatement1;
Недавно как раз столкнулся - динамический запрос выполнялся 15 минут, при этом данные начинали поступать только через 13 минут. Аналогичный статический запрос выполнялся 5 минут, данные начали поступать через 5-6 секунд.
Ну или вот сегодняшнее внедрение. Оптимизация sql - там как раз первод с динамики на статику и еще немного шаманства
![](https://webcf.waybackmachine.org/web/20240831110918/https://habrastorage.org/getpro/habr/upload_files/d53/dd8/ead/d53dd8eadf0e85300c079e67ac2cd505.png)
Последний столбец - время выполнения. Вчера была старая версия, на динамике, сегодня новая, на статике. Разница в три раза.
Это к вопросу о производительности (кстати, процессорных ресурсов оно тоже потребляет меньше).
Что же до типов данных - в языке уж точно найдётся куда эти типы отобразить, всё же это современный ЯП где можно вводить свои типы данных.
Во что обходится "введение своего типа и отображение в него"? Это ведь тоже не бесплатно.
Одно дело когда у нас в языке есть поддержка нужных типов - достаточно просто прочитать запись в байтовый буфер (структуру) и затем сразу работать с ее полями напрямую, и совсем другое, когда нужно что-то куда-то "отображать" с затратой на это дополнительного времени и ресурсов процессора. И, кстати, numeric и decimal, при внешней схожести, все-таки разные типы данных - в памяти они по разному предстваляются.
Собственно к чему все это... К тому, что в качестве workaraund для небольших приложений это сгодится, но не для высоконагруженных систем, плотно работающих с БД. Там стоит все-таки, поискать какой-то специализированный язык, ориентированный на эффективное выполнение именно такого рода задач.
То что описано выше для RUST есть и в других языках (SQLite для С, для Borland Delphi и C++ Builder были наборы компонент для работы с InterBase/FireBird...) и для небольших БД все это вполне удовлетворительно работало. Но как только речь заходит от терабайтных объемах где в таблице может быть несколько сотен миллионов записей а в выборку запросто может "влететь" несколько десятков миллионов записей и все это один из тысяч одновременно работающих на сервере процессов - тут уже нужно что-то посерьезнее.
Что значит "везде"? Я так понимаю, что подобные программы пишутся под конкретную БД и завязаны на структуру конкретной БД.
Они завязаны на структуру конкретной БД (точнее, на конкретную структуру БД), но не на саму БД.
Типичный современный процесс разработки подразумевает наличие как минимум двух окружений со своими БД одинаковой структуры: "стейджинг" и "прод". Также, скорее всего, будут свои БД на рабочем компе каждого разработчика, отдельная БД будет создаваться при каждой сборке для запуска автотестов, и отдельная БД будет в превью-окружении привязанном к Pull/Merge Request. В сложных проектах будут свои БД в интеграционных окружениях разного уровня.
И всё что выше относится к случаю, когда с СУБД "общается" бекенд. У десктопных и у мобильных приложений свой отдельный мир, в котором обычно никакой СУБД нет, но иногда и у них появляется встроенная СУБД. В таком случае отдельная БД появляется на каждом устройстве.
Построение плана запроса в рантайме кратно снижает производительность.
Нет никакой разницы строится ли план при компиляции, при установке или при первом появлении запроса - времени в любом случае будет потрачено одинаково.
Разница появляется лишь в том случае, когда план строится при каждом запросе. Это лечится кешированием планов запроса. Если ваша СУБД не умеет так делать - ну что же, грустно.
Ещё производительность может просесть если запрос формируется не один и тот же, а реально разный. Но тут и вовсе ничего поделать не получится, автоматически такое на статику не переписать.
Одно дело когда у нас в языке есть поддержка нужных типов - достаточно просто прочитать запись в байтовый буфер (структуру) и затем сразу работать с ее полями напрямую, и совсем другое, когда нужно что-то куда-то "отображать" с затратой на это дополнительного времени и ресурсов процессора.
На фоне обработки JSON или, уж тем более, XML, которая в программе наверняка будет, все эти затраты на раскладывание байт по полям - копейки.
Тем более что использование нативных типов данных порой само по себе даёт ускорение, которое с лихвой окупает две конвертации.
Типичный современный процесс разработки подразумевает наличие как минимум двух окружений со своими БД одинаковой структуры: "стейджинг" и "прод". Также, скорее всего, будут свои БД на рабочем компе каждого разработчика, отдельная БД будет создаваться при каждой сборке для запуска автотестов, и отдельная БД будет в превью-окружении привязанном к Pull/Merge Request. В сложных проектах будут свои БД в интеграционных окружениях разного уровня.
Не поверите, но я в курсе.
Нет никакой разницы строится ли план при компиляции, при установке или при первом появлении запроса - времени в любом случае будет потрачено одинаково.
Но в первом случае это время будет потрачено в процессе установки один раз, во втором - в процессе работы при каждом запуске.
Это лечится кешированием планов запроса.
Кеш хранится ограниченное время. У нас есть достаточно емкие по объему данных задачи, которые запускаются раз в день, раз в неделю-две, раз в месяц, раз в год. Будет ваша БД хранить кеш запросов в течении года? Особенно с учетом того, что запросов разных в ней 100500 штук (реально так).
Наша система много чего умеет. И не только запросы хранить, но и сохранять данные между запусками программы в рамках одного задания (точнее, в рамках жизни группы активации (activation group), явлюяющейся подмножеством задания (job)).
И, тем не менее, разница между временем выполнения статического и динамического sql видна даже простыми замерами времени (не говоря уже про специализированные инструменты типа PEX (Performance EXplorer).
На фоне обработки JSON или, уж тем более, XML, которая в программе наверняка будет, все эти затраты на раскладывание байт по полям - копейки.
Вот поэтому мы не используем ни то ни другое кроме тех случаев, когда без этого совсем никак. Но это обычно касается оправки каких-то данных во внешние системы через очередь. Если же данные возвращаются в вебсервис через ESB шину, то там проще вернуть ResultSet:
EXEC SQL declare Cursor2 cursor with return to client for
SELECT S01PS AS $$PS,
S01DSC AS $$DSC
FROM S01PF WHERE
S01PS = :iParms.PS AND
S01EXF = :iParms.EXF AND
TRIM(S01DSC) like TRIM(:iParms.DSC )
fetch first 9999 rows only;
EXEC SQL open Cursor2;
Все. Данные ушли тому, кто нас вызвал.
Тем более что использование нативных типов данных порой само по себе даёт ускорение, которое с лихвой окупает две конвертации.
О каких двойных конвертациях речь? Для нас типы данных zoned (numeric) и packed (decimal) - нативные. Равно как и остальные SQL типы данных.
Там нет никакой конвертации. Описали структуру (это просто статическая структура - там нет никаких конструкторов, просто буфер с размеченными в нем полями), прочитали в этот буфер запись (вот просто с диска, бат в байт) и все. Дальше работаем с полями. Если это чистовые поля - вся арифметика с ним работает (а для полей с фиксированной точкой еще и операции с округлением - присвоение, деление, умножение и т.п.).
Я понимаю, что это так сразу в голову не заходит, но оно вот так вот... Никаких "пакетов", никаких "зависимостей", никаких "отображений", "конвертаций"...
Не поверите, но я в курсе.
А если в курсе что могут быть несколько БД с одной структурой - почему задаёте глупые вопросы?
У нас есть достаточно емкие по объему данных задачи, которые запускаются раз в день, раз в неделю-две, раз в месяц, раз в год.
Чем более ёмкая задача по объёму данных, тем меньше влияние подготовительного этапа.
А уж пытаться избежать повторного построения плана запроса спустя год - это даже не экономия на спичках...
И, тем не менее, разница между временем выполнения статического и динамического sql видна даже простыми замерами времени
Это приколы вашей СУБД.
У той же Постгри планы запросов нужно периодически обновлять, потому что оптимизатор слишком сильно завязывается на текущую статистику по таблицам. Как следствие, статические запросы попросту не имеют смысла.
О каких двойных конвертациях речь? Для нас типы данных zoned (numeric) и packed (decimal) - нативные. Равно как и остальные SQL типы данных.
Да вот пусть даже из zoned в packed, а потом снова в zoned.
Zoned decimal - это настолько ужасный формат, что ему даже аппаратное ускорение что мёртвому припарки. От этой гадости надо избавляться при первой возможности.
Я понимаю, что это так сразу в голову не заходит, но оно вот так вот... Никаких "пакетов", никаких "зависимостей", никаких "отображений", "конвертаций"...
Вот это и плохо.
Чем более ёмкая задача по объёму данных, тем меньше влияние подготовительного этапа.
Увы, но это не совсем так. Если запрос достаточно сложен, он может отбирать, скажем, 50 записей из 50 000 000. И тогда "подготовительный этап" может оказаться доллше самой обработки.
Я уже писал - задача, запускаемая каждый день. Запрос
exec sql declare curRDKCHK1Clients cursor for
select RDKCUS,
RDKCLC,
GFP3R,
GFCTP,
RDKSER,
RDKNUM,
RDKSERNUM,
RDKOPN,
RDKEDT
from RDKPF RDK
join GFPF on (GFCUS, GFCLC) =
(RDK.RDKCUS, RDK.RDKCLC)
left join HDA1PF HDA1 on (HDA1CUS, HDA1CLC, HDA1TYP) =
(RDK.RDKCUS, RDK.RDKCLC, 'DOC')
where (RDKUCD, RDKSDL, RDKOSN) = ('001', 'Y', 'Y')
and (HDA1.HDA1DAT is null or HDA1.HDA1DAT < :minDA)
and exists (
select CAFCUS
from CAFPF
where (CAFCUS, CAFCLC, CAFATR1) = (RDK.RDKCUS, RDK.RDKCLC, 'Y')
)
and not exists
(
select RDKMCUS
from RDKMPF
where (RDKMCUS, RDKMCLC, RDKMUCD, RDKMSER, RDKMNUM, RDKMOPN, RDKMTP) =
(RDK.RDKCUS, RDK.RDKCLC, '001', RDK.RDKSER, RDK.RDKNUM, RDK.RDKOPN, '3')
);
выбирает порядка 1 200 записей из около 50 000 000. При динамическим подходе выполняется 15 минут из которых 13 уходит на "подготовительный этап". В статическом - 5 минут, на подготовку уходит несколько секунд.
И это далеко не самый сложный запрос из того, с чем псотоянно приходится работать.
А уж пытаться избежать повторного построения плана запроса спустя год - это даже не экономия на спичках...
А если речь идет о запросе (точнее, там 5 разных запросов), который выбирает 25 000 000 записей из 50 000 000, которые потом обрабатываются параллельно в 10 потоков и все это занимает 2+ часа?
У той же Постгри планы запросов нужно периодически обновлять, потому что оптимизатор слишком сильно завязывается на текущую статистику по таблицам. Как следствие, статические запросы попросту не имеют смысла.
Но, почему-то, они стабильно работают быстрее и потребляют меньше процессорных ресурсов.
Тут хочется задать вопрос - то, что вы разрабатывает проходит обязательное нагрузочное тестирование? Вам говорят "вот тут у вас идет излишнее обращение к БД, а тут много времени уходит на динамическое выделение/освобождение памяти"? Подозреваю, что нет... Смею полагать, что вам никогда не прилетает от сопровождение "дефект производительности":
Коллеги, сервис *** за последние 5 недель увеличил потребление процессорных ресурсов в 3 раза!!!
Он уже является 2-м по величине сервисом после *****.
В качестве альтернативы мы рассматриваем перенос запуска сервиса на резервный сервер, но там есть лаг по отставанию до 10 мин.
Заказчикам сервиса это может не понравиться :(
Да вот пусть даже из zoned в packed, а потом снова в zoned.
Зачем???
Я же писал:
dcl-ds t_dsSQLData qualified template;
CUS char(6) inz;
CLC char(6) inz;
Packet char(3) inz;
CTP char(2) inz;
SER char(10) inz;
NUM char(35) inz;
SERNUM char(45) inz;
OPN zoned(7: 0) inz;
EDT zoned(7: 0) inz;
end-ds;
Это описание структуры куда читается строка запроса
select RDKCUS,
RDKCLC,
GFP3R,
GFCTP,
RDKSER,
RDKNUM,
RDKSERNUM,
RDKOPN,
RDKEDT
Поля RDKOPN и RDKEDT в БД описаны как NUMERIC(7,0). У нас этому полностью соответствует нативный тип данных zoned(7:0). Никаких конвертаций, никаких преобразований. Просто двоичное чтение с диска в буфер. Дальше с этим полем работаем как с обычной числовой переменной - можем складывать, умножать, делить, вычитать...
Zoned decimal - это настолько ужасный формат, что ему даже аппаратное ускорение что мёртвому припарки.
Это потому что вам приходится его куда-то конвертировать прежде че с ним работать.
Вот это и плохо.
Что плохо? Что вам приходится следить на не поменялось ли что-то в зависимости? Фиксировать версии пакетов? Плохо, что вместо одной строки (например, прочистка работой таблицы от старых записей)
dcl-s now timestamp;
now = %timestamp(*sys);
exec sql delete from table where dte < :now;
вам придется тянуть зависимости и городить огород с sqlx::query и chrono::NaiveDateTime?
Мне не кажется что это плохо.
Плохо когда обработка выборки сводился к простому и понятному
dou lastBlock;
exec sql fetch curRDKCHK1Clients for :sqlRows rows into :dsSQLData;
lastBlock = sqlGetRows(rowsRead);
for row = 1 to rowsRead;
procData(dsSQLData(row));
endfor;
enddo;
где sqlGetRows возвращает количество реально причитанных записей и признак того, что это блок является последним в выборке:
dcl-proc sqlGetRows;
dcl-pi *n ind;
sqlRowsRead int(10);
end-pi;
dcl-s sqlRowCount packed(31 : 0) inz(*zero);
dcl-s sqlDB2LstRow int(10) inz(*zero);
exec sql GET DIAGNOSTICS
:sqlRowCount = ROW_COUNT,
:sqlDB2LstRow = DB2_LAST_ROW;
sqlRowsRead = sqlRowCount;
return (sqlDB2LstRow = 100);
end-proc;
Я вот не уверен что это так плохо...
13 минут на построение плана динамического запроса - это пиздец какой-то, не распространяйте этот опыт на другие СУБД. Такого просто не должно быть.
Дальше с этим полем работаем как с обычной числовой переменной - можем складывать, умножать, делить, вычитать...
И каждая из этих операций выполняется в 2 раза медленнее чем в packed формате.
Нет, если у вас 128-битная архитектура и полная аппаратная поддержка zoned формата, то конкретно 7-разрядное число в zoned формате будет работать нормально, но вот уже 64 битах упакованый формат будет выигрывать на математических операциях, просто потому что будет влезать в регистр процессора в то время когда zoned формат туда не влезает.
И даже на 128 битах упакованный формат станет выигрывать как только число разрядов увеличится до 9.
А если у вас архитектура процессора amd64, где на поддержку двоично-десятичных чисел давным-давно забили - то оба формата хрень и быстрее всего работают обычные i64.
Это потому что вам приходится его куда-то конвертировать прежде че с ним работать.
Нет, преобразование в более упакованный формат как раз ускоряет расчёты, если расчётов достаточно много, а преобразование сделано заранее (желательно - ещё до записи в БД).
Что плохо? Что вам приходится следить на не поменялось ли что-то в зависимости? Фиксировать версии пакетов?
Плохо то, что вы ограничены исключительно тем кодом, который вам "с барского плеча" предоставил производитель, и тем который вы написали сами.
Пакеты - это возможность, а не необходимость.
Вы понимаете, что вы сейчас пытаетесь поставить минус sqlx просто за то, что sqlx не предустановлен на ваше железо?
Плохо когда обработка выборки сводился к простому и понятному
Вот вообще не вижу ни одного отличия от sqlx в плане простоты и понятности.
13 минут на построение плана динамического запроса - это пиздец какой-то, не распространяйте этот опыт на другие СУБД. Такого просто не должно быть.
СУБД на самая плохая - DB2 for i
Но с динамическми скулем
![](https://webcf.waybackmachine.org/web/20240831110918/https://habrastorage.org/getpro/habr/upload_files/0eb/dce/0d7/0ebdce0d787f4d0e5e684a3c76544754.png)
95% утилизации - выполнение запроса SQL
Из 1015 сек выполнения программы - 975 сек - это выполнение SQL запроса
Переход на статику сразу в три раза уменьшил время выполнения запроса и потребление ресурсов. Это объективные цифры.
После перехода на статику
![](https://webcf.waybackmachine.org/web/20240831110918/https://habrastorage.org/getpro/habr/upload_files/813/a63/7e8/813a637e8cf4578b07424067fb9443e9.png)
И каждая из этих операций выполняется в 2 раза медленнее чем в packed формате.
Нет, если у вас 128-битная архитектура и полная аппаратная поддержка zoned формата, то конкретно 7-разрядное число в zoned формате будет работать нормально, но вот уже 64 битах упакованый формат будет выигрывать на математических операциях, просто потому что будет влезать в регистр процессора в то время когда zoned формат туда не влезает.
И даже на 128 битах упакованный формат станет выигрывать как только число разрядов увеличится до 9.
Вы понимаете, что все это работает только когда у вас язык нативно поддерживает эти типы? В вашем случае вы больше времени будет тратить на конвертацию из в обычный float и обратно
А если у вас архитектура процессора amd64
У нас архитектура процессора IBM Power9 Конкретно - Power E980 (120 процессорных ядер, каждое по 8 потоков)
Вы понимаете, что вы сейчас пытаетесь поставить минус sqlx просто за то, что sqlx не предустановлен на ваше железо?
Нет. Потому что это натягивание совы на глобус. Попытка прикрутить то, чего не предусмотрено на нативном уровне.
Вот вообще не вижу ни одного отличия от sqlx в плане простоты и понятности.
А теперь попробуйте написать программу, которая возвращает SQL resultset в вызывающую ее программу. Т.е. вас вызвали, вы что-то сделали и вернул резалтсет, который на той стороне воспринимается как обычная SQL выборка. И он может быть сформирован не обязательно как результат какого курсора, а, например, из таблицы в памяти. Т.е. что-то там посчитали, сложили в массив структур и вернули в виде SQL resultset вызывающему.
Тут вопрос в том, что все SQL типы данных совпадают с типами данных языка. Для них реализована вся арифметика. SQL поддерживается "насквозь", а не как ограниченный вызов через библиотечные функции которые что-то там внутри себя крутят.
Вот еще раз. Читаете запись. В ней есть поле в формате DATA. Просто набор байт. Что с ним произойдет прежде чем вы сможете с ним работать (ну, скажем, вычесть из нее заданное количество дней)? Ваш язык такой тип не поддерживает. Значит после чтения из БД эти байтики отправятся на какое-то преобразование, будет создан какой-то объект из них... Все это время и такты процессора.
В нашем случае эти байтики просто лягут в область памяти, объявленную как переменная соотв. типа. Безо всяких преобразований.
Вот предствьте себе "минималистический язык" в котором всего один тип данных - массив байт. И вас всегда надо сначала создать переменную типа byte[4], а потом, если вы хотите работа с ней как целым числом, обернуть ее в объект типа int32. И только после этого вы что-то с ней сможете делать. Вот о чем речь.
Плохо то, что вы ограничены исключительно тем кодом, который вам "с барского плеча" предоставил производитель, и тем который вы написали сами.
А вы чем ограничены? Каким-то кодом, неизвестно кем написанным и неизвестно как протестированым? Обнаружили в нем ошибку и что? Кто и в какие сроки вам ее исправит? И как вы будет оправдываться перед клиентами "ой, извините, у вас тут со счета 10 лямов куда-то ушло, но это не мы виноваты - это ошибка в пакете который мы из интернета скачали..." Серьезно?
Есть у нас на платформе С, есть С++. Но вся бизнес-логика намного проще и эффективнее реализуется на специально разработанном для этого языке. Это, опять же, результаты измерений.
Да, там есть не все что нужно для, например, низкоуровневых вещей. Но их мы пишем на С/С++. А вот работа с БД, коммерческие расчеты на нем объективно быстрее идут. У нас достаточное количество разработчиков, которые хорошо владеют как С/С++, так специальным языком. И, главное, имеют достаточно опыта для правильного выбора наиболее подходящего с точки зрения эффективности инструмента для решения той или иной задачи. Понимает, это как винты крестовые - есть PH, есть PZ и для каждого типа есть соответствующая бита. Можно заменить, но эффективность уже не та будет.
Вы понимаете, что все это работает только когда у вас язык нативно поддерживает эти типы? В вашем случае вы больше времени будет тратить на конвертацию из в обычный float и обратно
Работает что?
Для того что язык не поддерживает, в Rust на крайний случай есть ассемблерные вставки.
Нет. Потому что это натягивание совы на глобус. Попытка прикрутить то, чего не предусмотрено на нативном уровне.
Моя ваша не понимать.
Какое отношение пакеты имеют к архитектуре процессора?
А теперь попробуйте написать программу, которая возвращает SQL resultset в вызывающую ее программу. Т.е. вас вызвали, вы что-то сделали и вернул резалтсет, который на той стороне воспринимается как обычная SQL выборка. И он может быть сформирован не обязательно как результат какого курсора, а, например, из таблицы в памяти. Т.е. что-то там посчитали, сложили в массив структур и вернули в виде SQL resultset вызывающему.
Реализовать нужные трейты для вашей структуры данных, чтобы она сошла за SQL resultset - вообще не проблема, вопрос лишь в "нафига". Обычно решают прямо противоположную задачу.
Но вы куда-то отклонились от темы. Вы сначала говорили про простоту и понятность программы, а теперь пошли какие-то формирования resultset в памяти.
Вот еще раз. Читаете запись. В ней есть поле в формате DATA. Просто набор байт. Что с ним произойдет прежде чем вы сможете с ним работать (ну, скажем, вычесть из нее заданное количество дней)? Ваш язык такой тип не поддерживает.
А что в таком случае будете делать вы?
Сейчас вы, наверное, скажете что у вас-то язык всё поддерживает. Только вот у нас тоже язык всё что нужно поддерживает, так в чём же, блин, разница?
Вот предствьте себе "минималистический язык" в котором всего один тип данных - массив байт. И вас всегда надо сначала создать переменную типа byte[4], а потом, если вы хотите работа с ней как целым числом, обернуть ее в объект типа int32. И только после этого вы что-то с ней сможете делать. Вот о чем речь.
Ага, а потом выясняется что такая переменная обязана оставаться в памяти как часть массива, и не может быть помещена в регистр процессора целиком с целью оптимизации.
Ваша концепция не является чем-то новым, она стара, и не просто так от подобной концепции отказались.
А вы чем ограничены? Каким-то кодом, неизвестно кем написанным и неизвестно как протестированым? Обнаружили в нем ошибку и что? Кто и в какие сроки вам ее исправит?
Так я сам и исправлю, если будет необходимо - код-то открыт.
Да, это может быть долго, так ведь и без сторонних решений писать всё самому - тоже долго.
И как вы будет оправдываться перед клиентами "ой, извините, у вас тут со счета 10 лямов куда-то ушло, но это не мы виноваты - это ошибка в пакете который мы из интернета скачали..." Серьезно?
Вы, скорее всего, даже не смотрели код пакета sqlx, но уже обвиняете его в наличии ошибок которые могут списать со счёта 10 лямов.
А ещё вы не учитываете, что ошибка может быть как в коде IBM, так и в вашем собственном. За эти ошибки как оправдываться будете?
Реализовать нужные трейты для вашей структуры данных, чтобы она сошла за SQL resultset - вообще не проблема, вопрос лишь в "нафига". Обычно решают прямо противоположную задачу.
Самое частое - есть мобильное приложение. У него есть REST API, которое дергает вебсервис, который через ESB шину вызывает сервисмодуль на центральном сервере и ждет от него выборку в виде резалсета.
Это может быть и не мобильное приложение, а какая-то внешняя система или еще что-то. Но вот так оно работает. Сервера полностью изодлированы от мира, общать с яними можно только запросами через ESB или через очередь. Б - безопасность.
Можно накрутить трейтов и прочего. Но все это решается куда более простыми методами на самом деле.
Сервисмодлуль, кстати, может разом возвращать и 2 и 3 резалтсета. Одним ответом.
Выглядит это, например так - из окна интерактвного SQL вызываем единую сервисную процедуру с нужными параметрам, в ответ открывается три окошка с результатами выборок.
Ага, а потом выясняется что такая переменная обязана оставаться в памяти как часть массива, и не может быть помещена в регистр процессора целиком с целью оптимизации.
Вы говорите о "регистровой оптимизации" так, как будто ваш код уже оптимизирован на 146%. А на практике обычно бывает так, что просто изменеием алгоритма можно выморщить прирос в скорости и эффективности если не в разы, то на 15-20%
Вы, скорее всего, даже не смотрели код пакета sqlx
На С я начал писать где-то в 89-90-м году. И до 17-го года это был основной язык. И с БД поработал изрядно (начиная с Paradox через ParadoxEngine, позже BDE и до InterBase/FireBird с использование компонент очень похожих на sqlx идеологически). Даже немного пришлось с dbVista (которая нынче Raima DataManager - RDM). Так что у меня есть возможность сравнивать SQL с прямым доступом к БД (у нас он тоже есть в языке и в ряде случаев он быстрее и эффективнее чем SQL что тоже проверено PEX-статистиками), между работой через компоненты и библиотеки и возможностью использовать встроенные средства...
Так что все что пишу - результат многолетнего опыта, а сейчас еще и результаты множества нагрузочных тестов.
Специализированный инструмент для решения конкретный задач всегда будет лучше универсального. Раскроить листы фанеры на большие прямоугольники циркулярной пилой быстрее, ровнее и проще чем лобзиком. А пилить дрова лучше цепной.
А вот эти решения - они от того, что нет специального инструмента. И задачи не столько объемные - вряд ли вам нужно в рамках одного сервера реализовать десятки тысяч одновременно работающих бизнес-процессов которые прокручивают сотни миллионов бизнес-операций в день и совершают терабайты изменений БД в сутки. И все это не должно тормозить и, более того, у сервера всегда должен быть ощутимый запас производительности на случай периодов пиковых нагрузок (которые да, случаются достаточно регулярно и от нас никак не зависят).
Самое частое - есть мобильное приложение. У него есть REST API, которое дергает вебсервис, который через ESB шину вызывает сервисмодуль на центральном сервере и ждет от него выборку в виде резалсета.
Эта задача вообще не требует sql resultset.
Общение с ESB же через сеть идёт, верно? В таком случае всё что требуется на стороне сервисмодуля - это передать в сеть данные в таком формате, в каком ESB может их прочитать. Это решается сериализацией.
Поскольку типы данных тут вы выбираете сами, вы всегда можете выбрать их так, чтобы сериализация была бесплатной (учитывая что передачи данных в сеть всё равно не избежать, а значит её стоимость при сравнении разных решений можно не учитывать).
Но даже если я ошибся и ESB вызывает сервисмодуль как подпрограмму - вам всё равно нет необходимости возвращать какой-то абстрактный sql resultset. Вам нужно вернуть условный esb resultset, который совершенно не обязан бинарно совпадать с условным pgSQL resultset.
Можно накрутить трейтов и прочего. Но все это решается куда более простыми методами на самом деле.
Трейты - это и есть простое решение на самом деле.
Вы говорите о "регистровой оптимизации" так, как будто ваш код уже оптимизирован на 146%. А на практике обычно бывает так, что просто изменеием алгоритма можно выморщить прирос в скорости и эффективности если не в разы, то на 15-20%
Вот именно, алгоритм важен в первую очередь.
А регистровую оптимизацию я упомянул потому, что её делает компилятор, если ему не мешать лишними указаниями на то где и как должны храниться переменные.
Специализированный инструмент для решения конкретный задач всегда будет лучше универсального.
Но он не может заменить универсальный. А вы пришли в пост, рассказывающий про универсальный и бесплатный инструмент, и упорно рекламируете специализированный и дорогой.
Но даже если я ошибся и ESB вызывает сервисмодуль как подпрограмму - вам всё равно нет необходимости возвращать какой-то абстрактный sql resultset. Вам нужно вернуть условный esb resultset, который совершенно не обязан бинарно совпадать с условным pgSQL resultset.
Несколько не так. Ситуация таков - есть АБС (Автоматизированная Банковская Система), которая работает на центральном сервере и обеспечивает всю банковскую логику (а это очень много чего - десятки тысяч одновременно работающих бизнес-процессов, там все нереально сложно и необъятно разуму). Это mission-critical мастер-система.
Веб-сервис - один из способов для внешней системы получить какие-то данные (второй способ - очередь - у нас это IBM MQ на нашей стороне стыкующаяся с WBI на "той" стороне). Фактически вебсервис крайне тупая штука - это просто преобразователь интерфейсов и типов данных из/в внешних в/из наши и обратно. Есть два типа сервисов - геттеры (получающие ответ на конкретный запрос) и A/M/D (Add/Maintain/Delete) - изменение данных.
Поскольку у нас "своих забот хватает" - у нас и без этого много чего крутится постоянно, сервис-модуль не должен занимать много времени и ресурсов. Посему мы максимально быстро и с минимальными затратами ресурсов просто отдаем нужные данные а там уже пусть с ними сами разбираются. И максимально быстро и минимально затратно для нас - отдать sql resulset там где требуется выборка или просто одиночный результат.
Но наша основная задача - посильно снизить нагрузку на нашу систему. Поэтому все что можно делать не у нас - выносим. Оставляем только ту логику, которая обеспечивает собственно работу банка.
Но он не может заменить универсальный.
Не может. Как и универсальный проиграет в конкретной задаче специализированному. На самом деле, у нас и универсальны есть - С/С++ (и там тоже можно SQL в код встраивать и есть библиотека RECIO для прямой работы с БД, но т.к. там не поддержки SQL типов данных кроме decimal, работать с БД там мало смысла и много заморочек). Более того, у нас есть возможность написать кусок кода на С/С++, кусок на нашем специальном языке, а потом собрать это в одну программу :-)
А пришел я с одним вопросом - как это работает в Rust. Может там что-то реально интересное придумали. Выяснилось что нет - все то же самое, что было в С/С++ с незапамятных времен (начиная с библиотек ParadoxEngine и dbVista еще под DOS и далее до SQLite и борландовских компонент для Delphi/C++ Builder для InterBase/FireBird с которыми когда-то приходилось работать).
А пришел я с одним вопросом - как это работает в Rust. Может там что-то реально интересное придумали. Выяснилось что нет - все то же самое, что [...]
В клиент-серверных СУБД общего назначения никогда не будет ничего "интересного" с вашей точки зрения. Это напрямую следует из их архитектуры, в которой есть, ну, отдельные клиент и сервер.
Так зачем вы ходите по статьям, посвящённым работе с ними-то? С таким подходом вам нет смысла даже смотреть на клиентские библиотеки.
Вам надо искать прежде всего статьи посвящённые необычным СУБД. Например, посмотрите на Tarantool - там архитектура уже похожа на вашу. Только не упадите в обморок когда увидите что они используют интерпретируемый lua :-)
Так, проглядел середину комментария, отвечу отдельно.
А если речь идет о запросе (точнее, там 5 разных запросов), который выбирает 25 000 000 записей из 50 000 000, которые потом обрабатываются параллельно в 10 потоков и все это занимает 2+ часа?
Я не понял, они обрабатываются 2 часа в 10 потоков средствами запроса или уже после выборки?
Если первое - то да, тут план надо готовить аккуратнее.
Если второе - то на фоне этих 2 часов обработки в 10 потоков лишнее построение плана запроса вот вообще незаметно. Если меня попросят оптимизировать подобное - я начну не со спичек, а с поиска лучшего алгоритма, потом перейду к форматам данных и всяким локальностям в кешах.
Тут хочется задать вопрос - то, что вы разрабатывает проходит обязательное нагрузочное тестирование? Вам говорят "вот тут у вас идет излишнее обращение к БД, а тут много времени уходит на динамическое выделение/освобождение памяти"? Подозреваю, что нет... Смею полагать, что вам никогда не прилетает от сопровождение "дефект производительности":
Обязательное - нет (заказчик почему-то думает что оно ему не нужно и за него не платит). Но вот дефекты производительности я встречал и чинил. Причины у этих дефектов были самые разные, некоторые даже совсем "весёлые", только вот ни разу этой причиной не было слишком длительное формирование плана запроса.
Я не понял, они обрабатываются 2 часа в 10 потоков средствами запроса или уже после выборки?
Я бы сказал в процессе выборки. Стандартный паттерн параллельной обработки выглядит так: есть два программных объекта - головное задание и обработчик. Запускается головное задание, которое запускает (системной командой SBMJOB - Submit Job или функцией spawn) нужное количество обработчиков, каждый в своем фоновом задании (это более устойчивый, безопасный и сопровождаемый вариант нежели posix threads, которые у нас тоже есть, но используются реже).
Когда все "дочки" запустились, головное задание начинает выборку данных для обработки. Обычно мы используем пакетное чтение - читаем сразу 1000 (например) записей выборки. Прочитанные денные выкладываются на "конвейер" (это может быть пайп, но у нас на платформе есть более пригодные для этого системные объекты - очереди DataQueue и UserQueue). Обработчики подхватывают пакеты с конвейера и обрабатывают данные.
Т.е. голова еще читает, а дочки уже обрабатывают.
тут план надо готовить аккуратнее
Естественно. Каждый мало-мальски сложный запрос предварительно гоняется в интерактивном SQL, там можно посмотреть визуализацию плана (картинку выкладывал как это выглядит), посмотреть рекомендации оптимизатора, оценить время выполнения, объемы выборки и т.п. Тут все достаточно основательно делается.
Я просто к тому, что статический SQL всегда быстрее динамического работает.
Кстати, не удивлюсь, если он периодически контролирует план запроса. Система очень мощная - тут даже при переносе программного объекта на другое железо (скажем, купили сервер нового поколения) он будет пересобран при первом запуске под тот процессор, на котором работает (для этого кроме исполняемого кода в программном объекте хранится т.н. TIMI-код - код в Technology Independant Michine Instructions).
Когда-то (я не застал) работали на серверах Power7. Потом перешли на Power8 (это уже помню - 824 на тестовом сервере, 828 на боевом). Сейчас - Power9 (E960 на бою). И перенос системы (наших объектов) просто копированием. А пересборку под новый проц система уже сама делает при первом запуске программы. Это любителям "регистровой оптимизации".
Если второе - то на фоне этих 2 часов обработки в 10 потоков лишнее построение плана запроса вот вообще незаметно.
В данном случае да. Но все что может быть сделано более эффективно малыми затратами, должно делаться более эффективно. А использование статического скуля как раз та вещь, которая не требует никаких затрат, но дает некоторый прирост производительности и сокращений времени. Куда более ощутимое, чем "регистровая оптимизация".
Если меня попросят оптимизировать подобное - я начну не со спичек, а с поиска лучшего алгоритма, потом перейду к форматам данных и всяким локальностям в кешах.
Именно этим и занимаюсь в последнее время. Много старых модулей которые удовлетворительно работали пока было 30 000 000 клиентов, но вносят ощутимую задержку сейчас, когда их стало 50 000 000млн.
Благо у нас для кеширования очень много возможностей. Например, есть такая сущность внутри задания, как "группа активации". Так вот, если в одной ГА вызывать программу несколько раз, то все глобальные и статические переменные сохраняются с прошлого вызова. И этим можно активно пользоваться для кеширования - что-то сохраняем с предыдущих вызовов и проверяем на каждом новом - нужно ли пересчитывать (условно) или нет.
Обязательное - нет (заказчик почему-то думает что оно ему не нужно и за него не платит). Но вот дефекты производительности я встречал и чинил. Причины у этих дефектов были самые разные, некоторые даже совсем "весёлые", только вот ни разу этой причиной не было слишком длительное формирование плана запроса.
У нас НТ - обязательный этап. Сначала компонентное, потом бизнес, потом нагрузочное, интеграционное и техтест на прелайве (последние три на копии промсреды). Только после положительных заключений по всем этапам согласуется внедрение (и с обязательным планом отката).
Ибо заказчиком у нас бизнес-подразделения, но за то, чтобы все работало отвечаем мы (в первую очередь служба сопровождения и дежурная смена, но если что, то они только откатят или WA какой временно организуют, а претензии все к нам пойдут).
Действительно, дефекты производительности по очень многим причинам, главным образом алгоритмического характера, но, повторюсь, если есть возможность на 2-3% ускорится просто переходом на статический скуль - это будет сделано. Более того, есть "нефункциональные требования" где явно прописано, что использования динамического скуля допустимо только в исключительных случаях и с кучей оговорок (например, ручное формирование SQLDA - есть тут такая сущность - которое очень гиморно, но ощутимо ускоряет процесс и снижает нагрузку).
Дело в том, что в ситуациях когда запрос известен заранее и не изменяется от вызова к вызову, статически скуль не только быстрее, но и проще - вместо трех строк
exec sql declare SqlStatement1 statement;
exec sql prepare SqlStatement1 from :sqlTxt1;
exec sql declare currACY1 cursor for SqlStatement1;
пишется одна
exec sql declare curRDKCHK1Clients cursor for
select ...
которая на самом деле является декларативной, но не исполняемой строкой (т.е. не требует проверки sqlCode на предмет ошибки). Плюсом - корректность синтаксиса запроса будет проверяться на этапе компиляции (для кода, содержащего SQL компиляция идет в два этапа - сначала SQL-препроцессор, потом уже собственно компилятор).
Так что это и проще и быстрее.
Вообще тут SQL это как расширение языка и его часть. Есть возможность напрямую к таблицам обращаться (позиционирование по индексу, чтение записи по значению ключа в заданную структуру-буфер, добавление записи из буфера, удаление, обновление...), так и посредством SQL все тоже самое делать (скажем читать очередную запись или блок записей в структуру-буфер или массив структур).
Более того, прочитать одну (или небольшое количество) записей из одной таблицы проще и эффективнее прямым доступом чем SQL (тоже результаты наших исследований PEX-ом). Проверить наличие записи с заданным значением ключа тоже проще прямым доступом - просто смотрим есть ли такая запись в индексе без собственно чтения ее из таблицы.
Все это дает возможность писать максимально эффективный код малыми затратами сил. Но требует некоторого опыта и понимания как оно работает.
А использование статического скуля как раз та вещь, которая не требует никаких затрат, но дает некоторый прирост производительности и сокращений времени.
Это у вас использование статического скуля не требует никаких затрат. В других СУБД может быть совсем по-другому (включая отсутствие "статического скуля").
Это у вас использование статического скуля дает прирост производительности.
Дело в том, что в ситуациях когда запрос известен заранее и не изменяется от вызова к вызову, статически скуль не только быстрее, но и проще - вместо трех строк [...] пишется одна
Это у вас пишется вместо трёх строк одна. На других языках это может выглядеть совсем по-другому.
Все верно, все правильно. У нас.
Я говорил уже, что опыт работы с различными БД через разные библиотеки и компоненты на С/С++ у меня есть. И прямым доступом и посредством SQL.
И вижу, что специализированный язык с нативной поддержкой всех типов данных, имеющихся в БД и плотной интеграцией с SQL дает больше свободы, более удобен и эффективен чем все эти компоненты и библиотеки.
На прошлой работе БД была в качестве хранилища некой конфигурации системы. Десяток-полтора таблиц, максимум десяток тысяч записей на таблицу. Типичные объемы вуборки - сотня, ну тысяча записей. И программа работала фактически в монопольном режиме (все ресурсы машины мои). Там все эти компоненты вообще не парили.
Тут работа с БД - ключевой момент. Любой бизнес-процесс суть работа с потоком данных который начинается с формирования выборки (собственно потока) и дальше какая-то потоковая обработка результата выборки.
Таблиц десятки тысяч. Количество записей в таблице может запросто дойти до нескольких сотен миллионов. Объемы выборок могут доходить до десятков миллионов записей. И при этом параллельно работают десятки тысяч разных процессов. Т.е. нужно заботится не только о том, чтобы оно работало максимально быстро, но и при этом потребляло минимум ресурсов, прежде всего CPU - с памятью проблем нет - в основном используется одноуровневая модель памяти (SINGLE LEVEL, есть еще TERASPACE, но это для особых случаев когда нужно одним куском выделить большой, до 2Гб, объем в куче - одноуровневая позволяет не более 16Мб выделять) когда ты не знаешь где тебе физически выделили память - в ОЗУ (у нас ее 12Тб) или на диске (400Тб SSD массив). А вот процессор - да. 120 8-потоковых ядер на все задачи. В период пиковых нагрузок утилизация CPU может доходить до 90%, а это уже риски.
Посему (что для меня было некоторым "переходом на новый уровень") приходится всегда держать в голове что "ты тут не один" ну и соответственно подходить к решению задачи - не только быстро, но и с минимальными затратами ресурсов.
Возвращаясь в нашим баранам. Нашел вот одно старое заключение с НТ. Как раз по поводу динамического скуля:
Из PEX статистики работы PBC01U01R видно, что 33% времени и 36% ресурсов CPU тратится на выполнение QSQRPARS в программе STL#CHKN, т.е. парсинг статических выражений при подготовке SQL запроса,
Сократить данные русурсозатраты практически до нуля можно путем описания параметров sql запросов через SQL Descriptor Area (SQLDA).
Поскольку CU130 один из наиболее активно используемых сервис модулей, необоснованное повышенное ресурсопотребление является малодопустимым. Просьба инициировать доработку STL#CHKN.
В том случае проще всего оказалось не возиться руками с SQLDA (на редкость муторное занятие и по кодлу объемное), но вообще отказаться там от SQL в пользу прямой работы с БД т.к. там выборки были невелики (десяток-два записей), выполнялись по двум таблицам и полностью покрывались индексами.
В результате получилось чуть быстрее и существенно экономичнее по потреблению ресурсов
Текущая версия функционала (копия в пром среды от 25.01.2020), а - 7.98 м. сек
Обновленная версия, СМ - 6.71 м. сек
Общий тезис такой - если есть возможность, использование специализированного инструмента для решения специфических задач всегда даст больший эффект чем разного рода "регистровые оптимизации".
И это везде - если вы работает в промавтоматизации (или подобных областях) где требуется жесткий реалтайм, вы не будете использовать обычную ОС, но специализированную ОСРВ - QNX, WindRiver Linux, vxWorks и подобные.
Та платформа, IBM i, и специализированный язык RPG (более 80% кода на этой платформе пишется на нем), что используется у нас, достаточно распространены в мире там, где нужны коммерческие расчеты (банки, страховые и т.п.). Она создана именно для решения таких вот задач и в них они хороши. Это интенсивный путь развития.
Использование универсальных инструментов с обвешиванием их всевозможными "примочками" для решения специфических задач - экстенсивный путь развития, который приводит к необходимости постоянного наращивания ресурсов по мере увеличения нагрузки.
Т.е. наличие возможности работать с БД из ЯП общего назначения хорошо, но до определенного предела, до тех пор, пока работа с большими БД не становится основной задачей. С некоторого момента уже стоит думать в сторону перехода на специализированные системы и языки.
В документации sqlx существуют таблицы соответствий rust/sqlx типов с типами баз данных
использую sqlx
в своем приложении как основу для моей ORM, не сказал бы, что sqlx
идеальна из плюсов: позволила мне быстро написать мой ORM, поддержка async
, из минусов: были проблемы с использованием трейтов для соединений (пришлось отказаться, использую конкретные типы), проверка запросов при компиляции - звучит красиво, на практике больше похоже на "костыль на костыле" с кучей ограничений, не могу использовать WAL в sqlite (мой баг репорт https://github.com/launchbadge/sqlx/issues/2249)
в будущем буду переходить на rusqlite
SQLx: raw SQL в Rust