Показаны сообщения с ярлыком производительность. Показать все сообщения
Показаны сообщения с ярлыком производительность. Показать все сообщения

26 нояб. 2021 г.

Конфигурация небольшого сервера для базы данных Гедымина

На днях один клиент обратился с просьбой порекомендовать конфигурацию сервера под базу данных размером в районе 50 Гб и количеством одновременных подключений 25+. Думаю такая рекомендация может быть интересна и другим нашим клиентам, а также как исторический материал, который через некоторое время позволит сравнить цены и оценить скорость развития вычислительной техники.

Курс доллара на момент написания где-то в районе 2.5 руб за 1 USD.

  1. Память. Ставьте 256 ГБ самой быстрой DDR4, которую будет поддерживать материнская плата. Это на вырост. Если 256 Гб дорого, то никак не меньше 128 Гб. Планка на 32 Гб стоит сейчас где-то в районе 400 руб.
  2. Процессор AMD Ryzen 9 5900X (12 ядер, ~1500 руб) или 5950X (16 ядер, ~2200 руб).
  3. 4 планки NVME по 1 Tb. Что-нибудь вроде SSD Samsung 980 Pro 1TB (по 550 руб за штуку). Из них собрать два зеркала RAID 1. Одно использовать для размещения операционной системы, темп каталога и папки с архивами бд, а второе -- для размещения рабочего файла базы данных.
  4. Материнская плата с поддержкой всего вышеперечисленного (в т.ч. PCI Express 4.0). Сеть желательно 10 Gbit Ethernet до центрального свитча. Видео встроенное в материнскую плату или самое простое. Монитор можно б/у-шный тоже самый простой.
  5. Корпус можно самый обычный брать. Блока питания на 600 W хватит вполне.
  6. Надежный бесперебойник.
На вскидку, суммарная стоимость будет:

8 * 400 + 1500 + 550 * 4 + 600 + 300 + 400 = 8200, округленно -- 9000 руб.

Этой техники хватит без проблем, по производительности, на 5 ближайших лет.

9 мая 2016 г.

Прямой доступ к DBF файлам

Устав бороться с "тормозами" и ошибками стандартных ODBC драйверов для доступа к DBF файлам мы включили компонент TDBF в исходный код Гедымина. Пример использования можно посмотреть тут.

Точных замеров и сравнений не проводили, но "на глаз" переход на встроенный компонент ускорил у одного из наших клиентов обработку больших массивов данных раза в три.

23 окт. 2014 г.

Код добавления нулей слева к числу:

S = Right("00000000000" & V, 12)

на 5% быстрее чем:

S = String(12 - Len(V), "0") & V

Очевидно из-за меньшего количества вызова функций.

14 мая 2014 г.

CURRENT OF vs поиск по ключу

Какое же утро без хорошего эксперимента. Берем встроенный сервер Firebird 2.5.3 и чистую БД:
Page size               8192 
ODS version             11.2 
Page buffers            75 
Database dialect        3 
Attributes              force write 
Создаем две идентичных по структуре таблицы:
create table t1 (i integer not null)
create table t2 (i integer not null)
Одинаково заполняем каждую из них миллионом случайных значений:
execute block
as
  declare variable i integer = 0;
  declare variable v integer = 0;
begin
  while (i < 1000000) do
  begin
    v = round(rand() * 2000000000);
    insert into t1 values (:v);
    insert into t2 values (:v);
    i = :i + 1;
  end
end
Для таблицы t1 создаем индекс:
create index t1x on t1 (i)
Делаем коммит транзакции и переподключение к базе данных. Организуем скан таблицы t1 и случайным образом удаляем четверть записей через поиск по индексированному полю:
execute block
as
  declare variable i integer;
begin
  for select i from t1 into :i
  do
  begin
    if (rand() < 0.25) then
      delete from t1 where i = :i;
  end
end
Статистика выполнения:
Execute       : 10 717.00 ms
Read          : 239 855
Writes        : 6 250
Fetches       : 4 264 681
Marks         : 250 286
IR            : 250 286
NIR           : 999 934
Deletes       : 250 286
Не совсем понятно почему NIR не миллион. Все остальное вполне логично. Коммит транзакции и еще раз удаляем четверть, теперь от оставшихся записей:
Execute       : 582 243.00 ms
Read          : 414 519
Writes        : 246 390
Fetches       : 5 712 020
Marks         : 944 206
IR            : 187 214
NIR           : 749 693 
Deletes       : 187 214
Expunges      : 250 286
Почти 10 минут! Обратите внимание на сумасшедшее количество Writes и Marks. Сборка мусора и перестроение индекса?

Далее, берем таблицу t2 и удаляем из нее четверть записей, но с помощью курсора и конструкции CURRENT OF:

execute block
as
  declare variable c cursor for
    (select i from t2);
  declare variable i integer;
begin
  open c;
  while (1=1) do
  begin
    fetch c into :i;
    if (row_count = 0) then
      leave;
    if (rand() < 0.25) then
      delete from t2 where current of c;
  end
  close c;
end
Статистика:
Execute       : 8 362.00 ms
Read          : 6 138
Writes        : 6 064
Fetches       : 2 762 582
Marks         : 250 100
NIR           : 1 000 000
Deletes       : 250 100
Обратите внимание, насколько меньше чтений по сравнению с удалением по индексированному полю. Выполнение на 20% быстрее за счет того, что не надо дергать индекс.

Коммит транзакции и еще раз удаляем четверть записей:

Execute       : 8 252.00 ms
Read          : 6 137
Writes        : 6 067
Fetches       : 3 587 315
Marks         : 693 789
NIR           : 749 900
Deletes       : 187 455
Expunges      : 250 100
Выполнился даже быстрее чем первый запрос, несмотря на сборку мусора, и в 70 (!!!) раз быстрее чем запрос с удалением по индексированному полю. Непонятно почему так велико значение Marks, ведь удаляется всего 187 455 записей.

Подсчитаем количество записей в первой таблице:

select count(*) from t1
Статистика:
Execute       : 493 697.00 ms
Read          : 181 339
Writes        : 181 084
Fetches       : 3 009 620
Marks         : 561 642
NIR           : 562 500
Expunges      : 187 214
Все ясно, сборка мусора напоролась на индекс. 8 минут на кофе. Теперь считаем во второй:
select count(*) from t2
Статистика:
Execute       : 6 443.00 ms
Read          : 6 137
Writes        : 6 064
Fetches       : 2 261 901
Marks         : 374 910
NIR           : 562 445
Expunges      : 187 455
Упс! Мы ожидали увидеть сравнимые цифры по времени выполнения для первой и второй таблицы, но разница составила 76 (!!!) раз. Все из-за наличия в первой таблице индекса, который при подсчете количества никак не используется, но катастрофически тормозит сборку мусора.

Попытаемся выяснить как влияет наличие индекса на выполнение операции CURRENT OF. Возвращаемся к исходной базе данных. Будем удалять записи из таблицы t1 с помощью конструкции CURRENT OF:

Execute       : 8 050.00 ms
Read          : 6 138
Writes        : 6 064
Fetches       : 2 765 717
Marks         : 251 145
NIR           : 1 000 000
Deletes       : 251 145
В одно время с удалением из таблицы, по которой нет индекса.

Второй цикл удаления:

Execute       : 688 480.00 ms
Read          : 240 628
Writes        : 240 341
Fetches       : 4 592 695
Marks         : 945 817
NIR           : 748 855
Deletes       : 186 248
Expunges      : 251 145
Самое большое время, которое нам пришлось наблюдать сегодня. Сборка мусора и индекс явно не дружат друг с другом.

Для оценки влияния сборки мусора повторим все операции с самого начала на исходной базе данных с флагом подключения no_garbage_collect. Первое удаление из таблицы t1 (поиск по индексированному полю):

Execute       : 12 418.00 ms
Read          : 239 735
Writes        : 6 231
Fetches       : 4 260 434
Marks         : 249 809
NIR           : 999 939
IR            : 249 809
Deletes       : 249 809
Странно, но NIR снова не миллион. Куда деваются около шестидесяти чтений непонятно.

Второе удаление:

Execute       : 9 563.00 ms
Read          : 181 842
Writes        : 6 189
Fetches       : 3 702 914
Marks         : 187 849
NIR           : 750 162
IR            : 187 849
Deletes       : 187 849
Записей стало меньше и время сканирования и удаления уменьшилось соответственно.

Для второй таблицы удаление через курсор. Первый проход:

Execute       : 8 112.00 ms
Read          : 6 138
Writes        : 6 064
Fetches       : 2 764 184
Marks         : 250 634
NIR           : 1 000 000
Deletes       : 250 634
Второй:
Execute       : 8 034.00 ms
Read          : 6 137
Writes        : 6 064
Fetches       : 2 574 131
Marks         : 187 283
NIR           : 749 366
Deletes       : 187 283
Обратите внимание, что цифры идентичны тем, которые мы имели при включенной сборке мусора. Т.е. нет индекса и сборка мусора не проблема.

Получаем количество записей для первой таблицы:

Execute       : 546.00 ms
Read          : 6 137
Writes        : 2
Fetches       : 2 012 281
Marks         : 0
NIR           : 562 342
Для второй:
Execute       : 531.00 ms
Read          : 6 137
Writes        : 2
Fetches       : 2 012 281
Marks         : 0
NIR           : 562 083
Выводы:

  • Нижесказанное применимо к частным случаям массовой обработки данных.
  • Если по таблице нет индексов, то включение/выключение сборки мусора практически не влияет на производительность.
  • Если по таблице созданы индексы, то сборка мусора способна радикально, в десятки и сотни раз, замедлить ход процесса.
  • В нашем примере CURRENT OF был на 20-25% быстрее чем поиск по индексированному полю.
  • CURRENT OF может применяться на таблицах, где индексов нет вообще. Совершенно очевидно, что удаление с поиском по неиндесированному полю выполнялось бы в нашем случае часами, если не сутками.
Так и осталось загадкой, почему количество неиндексированных чтений при скане таблицы с индексом дважды получилось меньше правильного по теории миллиона.

14 янв. 2014 г.

Попытка №2

Упростим ситуацию и будем рассматривать только удаление из базы данных документов указанных типов, даты меньше заданной (D). Под документом в широком смысле мы понимаем:
  • Cовокупность записей в таблице GD_DOCUMENT, шапка и позиции.
  • Присоединенные к ним записи 1-к-1 (включая записи в специфических таблицах документов, связанные через поле documentkey).
  • Связанные с ними таблицы с уточняющей информацией (например, USR$INV_ADDINFO). По сути это 1-к-1, но через отдельное поле внешний ключ. Из структуры БД мы не можем знать, что некоторая таблица несет уточняющую информацию, поэтому определим список таких таблиц через константу в программе.
  • Бухгалтерские проводки (AC_RECORD, AC_ENTRY).
  • Складское движение (INV_CARD, INV_MOVEMENT).
  • Ко всему вышеперечисленному связанные записи в кросс-таблицах множеств.
Типы указываем, так как некоторые документы (например, зарплатные или по ОС) удалять нельзя за весь период.

Процесс "обрезания" базы сводится к следующему:

  1. Вычисляем бухгалтерские и складские остатки на заданную дату. Сохраняем их в базе.
  2. Формируем массив М из ИД записей, которые останутся в результирующей базе:
    1. Сканируем все таблицы, не относящиеся к документам. Добавляем в М ИД записи (если он есть и целочисленный) и идентификаторы всех ссылок на таблицы документов. Прочие ссылки нас не интересуют, так как все записи из прочих таблиц в любом случае будут сохранены.
    2. Выбираем из таблиц документов указанных типов записи с датой больше либо равно D. Сканируем их аналогичным образом, добавляя ИД в М.
    3. Сканируем аналогичным образом таблицы документов остальных типов.
  3. Запоминаем информацию о структуре БД в части обрабатываемых таблиц.
  4. Отключаем ключи, индексы, триггеры, ограничения на таблицах, из которых будем удалять записи.
  5. Удаляем записи, отсутствующие в М.
  6. Удаляем из таблицы GD_RUID записи для удаленных ИД.
  7. Восстанавливаем структуру БД.
Подготовка исходной базы данных:
  1. Пользователю рекомендуется провести бэкап-разбэкап исходной базы перед началом процесса.
  2. Кэш не должен превышать 500 Мб.
  3. Режим принудительной записи должен быть отключен
  4. Если задействован наш механизм замены внешних ключей, то отключенные ключи должны быть включены.
  5. Аудит средствами платформы Гедымин должен быть отключен.
Для оценки эффективности процесса будем использовать количество записей до и после "обрезания" в затрагиваемых процессом таблицах.

4 сент. 2009 г.

Профилируем Гедымин

Случайно нашел ссылку на Eric Grange's Sampling Profiler — бесплатный профайлер с поддержкой Delphi 5. Перекомпилировал Гедымин с TD32, запустил под профайлером и поставил загрузку на эталон пакета "Банк и Касса". По завершении процесса собранная статистика отображается в таком виде:
Достаточно информативно и сразу видны узкие места в коде. Правда, 77.5% времени занимает вызов функций WinAPI, так что даже двукратное ускорение всего оставшегося кода даст нам немногим более 10-ти процентов выйгрыша в общей производительности. Пока не было времени определить куда относится время выполнения запросов на сервере. Такое впечатление, что они включены в эти самые 77.5%

Теперь собрать бы статистику у клиентов, в полевых условиях.