Временные таблицы и SELECT FASTTRUNCATE

29.10.24

База данных - HighLoad оптимизация

В данной статье мы рассмотрим, как работает механизм временных таблиц на postgres на платформе 8.3.23 и что изменилось в нем при добавлении новых возможностей в платформе 8.3.25. А также на примере покажу, как понимание работы платформы позволяет оптимизировать СУБД для работы с 1С.

 

Временные таблицы в 8.3.23

Давайте рассмотрим механизм работы платформы 1С с временными таблицами.

Выполним запрос создания временной таблицы:

ВЫБРАТЬ
    Контрагенты.Ссылка КАК Ссылка,
    Контрагенты.Код КАК Код,
    Контрагенты.Наименование КАК Наименование
ПОМЕСТИТЬ ВТ_Контрагенты
ИЗ
    Справочник.Контрагенты КАК Контрагенты
 
ИНДЕКСИРОВАТЬ ПО
    Ссылка

 

Платформа выполнит следующие запросы к БД:

// 1. создаем ВТ. Если ВТ с таким именем уже существует, то сначала уничтожим ее.
drop table if exists tt1 cascade;create temporary table tt3 (_Q_001_F_000RRef bytea, _Q_001_F_001 mvarchar(9), _Q_001_F_002 mvarchar(25) ) without oids
// 2. Запишем либо прочитаем информацию о созданной ВТ. Данная функция выполняется на стороне сервера 1С.
Func=lookupTmpTable
// 3. Удалим индекс tmpind_0, если он существует
drop index if exists tmpind_0
// 4. Создадим индекс tmpind_0 на созданной ВТ
create index tmpind_0 on pg_temp.tt3(_Q_001_F_000RRef)
// 5. Вставим данные в ВТ
INSERT INTO pg_temp.tt3 (_Q_001_F_000RRef, _Q_001_F_001, _Q_001_F_002) SELECT
T1._IDRRef,
T1._Code,
T1._Description
FROM _Reference54 T1
// 6. Рассчитаем статистику на ВТ
ANALYZE pg_temp.tt3
// 7. Удаляем индекс
drop index if exists TMPIND_0
// 8. Очищаем ВТ
SELECT FASTTRUNCATE ('pg_temp.tt3')

 

Если еще раз выполнить из того же сеанса этот же запрос, то будут выполнены все те же самые запросы к БД кроме п.1 - создания временной таблицы. Почему так происходит?

При первом создании временной таблицы на стороне сервера приложений сохраняется примерно следующая информация (это не описание с ИТС, а предположение, исходя из имеющихся фактов):

 

 База  Состав колонок

 Номер

 соединения 

 СУБД

 Имя

 временной

 таблицы

 erp_prod  _Q_001_F_000RRef bytea, _Q_001_F_001 mvarchar(9), _Q_001_F_002 mvarchar(25)  224789  tt3

 

Поэтому при повторном выполнении платформа 1С понимает, что временная таблица с таким составом и типом полей существует, если обращение к базе данных идет по номеру соединения СУБД, в рамках которого она была создана, и переиспользует ее вместо создания новой. Это позволяет экономить время на создание новой таблицы и удалении существующей (вместо удаления оставляем ее, чтобы использовать снова).

Создадим еще одну временную таблицу, но с другим составом полей, уберем выбор поля "Наименование":

ВЫБРАТЬ
    Контрагенты.Ссылка КАК Ссылка,
    Контрагенты.Код КАК Код
ПОМЕСТИТЬ ВТ_Контрагенты
ИЗ
    Справочник.Контрагенты КАК Контрагенты
 
ИНДЕКСИРОВАТЬ ПО
    Ссылка

 

Т.к. структура колонок отличается, то в этом случае будет создана новая временная таблица, а на стороне сервера приложений будет храниться примерно следующая информация:

 База  Состав колонок

 Номер

 соединения 

 СУБД

 Имя

 временной

 таблицы

 erp_prod   _Q_001_F_000RRef bytea, _Q_001_F_001 mvarchar(9), _Q_001_F_002 mvarchar(25)  224789  tt3
 erp_prod  _Q_001_F_000RRef bytea, _Q_001_F_001 mvarchar(9)      224789  tt4

 

То, что обе временные таблицы продолжают существовать, можно убедиться из данных СУБД.

Для этого сначала определим схему, в которой созданы наши 2 временные таблицы:

SELECT n.nspname AS schema_name, c.relname AS table_name
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relname in ('tt3', 'tt4') AND n.nspname LIKE 'pg_temp%';

 

Результат запроса:

 schema_name | table_name
-------------+------------
 pg_temp_27  | tt3
 pg_temp_27  | tt4
(2 rows)

Посмотрим на состав этих таблиц:

SELECT a.attrelid::regclass, a.attname, t.typname AS data_type
FROM pg_attribute a
JOIN pg_type t ON a.atttypid = t.oid
WHERE a.attrelid in ('pg_temp_27.tt3'::regclass, 'pg_temp_27.tt4'::regclass) AND attnum > 0;

 

Результат запроса:

    attrelid    |     attname      | data_type
----------------+------------------+-----------
 pg_temp_27.tt3 | _q_001_f_000rref | bytea
 pg_temp_27.tt3 | _q_001_f_001     | mvarchar
 pg_temp_27.tt3 | _q_001_f_002     | mvarchar
 pg_temp_27.tt4 | _q_001_f_000rref | bytea
 pg_temp_27.tt4 | _q_001_f_001     | mvarchar
(5 rows)

Для того чтобы посмотреть, какие данные хранятся во временной таблице, необходимо выполнить запрос "select * from pg_temp_27.tt3" - результат будет пустой, т.к. наши временные таблицы после выполнения функции SELECT FASTTRUNCATE стали пустыми.

Когда временная таблица будет удалена на стороне СУБД? Она будет удалена, когда будет закрыто соединение к СУБД, в рамках которого она была создана.

Конструкция "Уничтожить"

Что делает конструкция "Уничтожить" в запросе? Выполним запрос:

ВЫБРАТЬ
    Контрагенты.Ссылка КАК Ссылка,
    Контрагенты.Код КАК Код,
    Контрагенты.Наименование КАК Наименование
ПОМЕСТИТЬ ВТ_Контрагенты
ИЗ
    Справочник.Контрагенты КАК Контрагенты
 
ИНДЕКСИРОВАТЬ ПО
    Ссылка
;
 
////////////////////////////////////////////////////////////////////////////////
УНИЧТОЖИТЬ ВТ_Контрагенты

 

Здесь поведение будет точно таким 1 в 1, как и выше. Это объясняется тем, что функция "Уничтожить"-  это есть не что иное, как вызов функции SELECT FASTTRUNCATE. На примерах выше это происходило автоматически, т.к. мы не используем МенеджерВременныхТаблиц, и платформа 1С вызывает функцию SELECT FASTTRUNCATE при окончании выполнения запроса.

А если выполнять запрос с использованием МенеджераВременныхТаблиц и без явного уничтожения временной таблицы в тексте запроса, то функция SELECT FASTTRUNCATE будет вызвана в тот момент, когда мы закроем МенеджерВременныхТаблиц явно, либо он перестанет существовать.

Из этого всего можно сделать вывод, что функция SELECT FASTTRUNCATE нужна для очистки временной таблицы с целью ее дальнейшего переиспользования.

В ходе миграции и сопровождения информационных систем на postgres эта функция часто мне встречалась и показывала себя не с лучшей стороны.

 

Оптимизация select fasttruncate

 

Первая встреча

Самый первый раз с долгим выполнением функции SELECT FASTTRUNCATE я столкнулся около 3-х лет назад при переводе базы УХ на postgres.

В базе пользователи не работали, и мы ее использовали только для подготовки отчетности по контролируемым сделкам. Весь тест перехода на postgres как раз и состоял в том, чтобы протестировать функцию загрузки данных из экселя и создание операций по контролируемым сделкам.

Первый тест на postgres показал ухудшение по сравнению с mssql на 70% и по логам технологического журнала львиную долю времени как раз и занимала функция SELECT FASTTRUNCATE. 

Долгое выполнение было связано с тем, что по каждой контролируемой сделке шла проверка, которая выполняла запрос и создавала пустую временную таблицу. "Уничтожение" этой пустой таблицы функцией SELECT FASTTRUNCATE и было этим узким местом, занимавшем большую часть выполнения тестируемой операции. В ходе разбора логики я выяснил, что можно заранее определить будет в запросе создаваться пустая временная таблица или нет. Если пустая, то запрос выполнять смысла не было - таким образом и была достигнута оптимизация: если не выполняется запрос с созданием временной таблицы, то и не вызывается функция SELECT FASTTRUNCATE.

Затем я написал кейс на корп поддержку 1С, но у коллег проблема не воспроизвелась - на том история была закончена.

 

Вторая встреча

Компания "Тантор Лабс" находится в контуре "Группы Астра", в которую входит в том числе одна производственная компания. Свой учет она ведет в базе ERP размером 700 Гб, которая и была предметом нашего исследования.

На продуктивном контуре для мониторинга развернута платформа Tantor, в ходе изучения которой я опять встретился с SELECT FASTTRUNCATE. 

Дашборд "Top 5 total query time" показывает топ-5 запросов на инстансе, и наш запрос в рабочее время стабильно входил в этот топ:

 

 

Модуль "Профайлер запросов" уже дает более расширенную статистику по конкретным запросам.

Смотрим ее по нашему запросу за последние 24 часа:

 

 

 Как видим, эта функция занимает 12.9% процессорного времени.

Давай разберем ее исходный код, чтобы подробнее понять, что же она делает:

 
Исходный код функции fasttruncate

 

Далее я поясню основные ее моменты:

 

text    *name=PG_GETARG_TEXT_P(0);

Присваивает имя временной таблицы, переданной в качестве параметра функции, в переменную name.

 

relname = palloc( VARSIZE(name) + 1);
memcpy(relname, VARDATA(name), VARSIZE(name)-VARHDRSZ);
relname[ VARSIZE(name)-VARHDRSZ ] = '\0';

Извлекает имя таблицы из текстового объекта name и выделяет память для его хранения в переменной relname.

 

relname_list = stringToQualifiedNameList(relname);
relvar = makeRangeVarFromNameList(relname_list);
relOid = RangeVarGetRelid(relvar, AccessExclusiveLock, false);

По relname получает внутренний идентификатор таблицы в базе данных (OID) в переменную reloid и накладывает эксклюзивную блокировку на эту временную таблицу, чтобы никто не мог ее менять.

 

if ( get_rel_relkind(relOid) != RELKIND_RELATION )
    elog(ERROR,"Relation isn't a ordinary table");
 
rel = table_open(relOid, NoLock);
 
if ( !isTempNamespace(get_rel_namespace(relOid)) )
    elog(ERROR,"Relation isn't a temporary table");

Сначала выполняется проверка, что таблица является ординарной, затем временная таблица "открывается" для дальнейшего изменения (можно провести аналогию с ПолучитьОбъект() в 1С) и затем проверка, что таблица является временной. Обе проверки выглядят избыточными, т.к. функция написана именно под приложение 1С.

 

heap_truncate(list_make1_oid(relOid));

Удаляются все строки из временной таблицы (то, ради чего платформа 1С и вызывает эту функцию). 

 

if ( rel->rd_rel->relpages > 0 || rel->rd_rel->reltuples > 0 )
        makeanalyze = true;

Если исходя из статистики (именно из статистики, а не фактического количества строк) данной временной таблицы в ней до усечения было строк более 0, то присвоим переменной makeanalyze значение ИСТИНА.

 

table_close(rel, AccessExclusiveLock);

"Закрываем" таблицу и снимаем наложенную на нее блокировку, теперь она может быть использована другими сеансами.

 

if (makeanalyze) {
    VacuumParams params;
    VacuumRelation *rel;
     
    params.options = VACOPT_ANALYZE;
    ...
     
    rel = makeNode(VacuumRelation);
    rel->relation = relvar;
    rel->oid = relOid;
    rel->va_cols = NULL;
     
    vacuum(list_make1(rel), params,
           GetAccessStrategy(BAS_VACUUM), false);
}

Если переменная makeanalyze = ИСТИНА, то пересчитаем статистику по таблице, т.к. мы удалили из нее все записи, и статистика стала неактуальной.

 

Если перевести на язык 1С и откинуть все неважное, то можно представить это в виде следующего кода:

Функция SelectFasttruncate(ВременнаяТаблица)  
     
    ТребуетсяПересчитатьСтатистикуПоТаблице = Ложь;
     
    ВременнаяТаблица.Очистить();
 
    Если КоличествоСтрокВоВременнойТаблицеСогласноСтатистики > 0 Тогда  
        ТребуетсяПересчитатьСтатистикуПоТаблице = Истина; 
    КонецЕсли;
     
    Если ТребуетсяПересчитатьСтатистикуПоТаблице Тогда
        ПересчитатьСтатистикуПоТаблице(ВременнаяТаблица);
    КонецЕсли;
     
КонецФункции

 

Мы исследовали данную функцию с помощью perf'а, чтобы посмотреть, какой ее код выполняется дольше всего:

 

 

Несложно было догадаться, что основная часть времени уходит на очистку временной таблицы - это рассмотренный выше код "heap_truncate(list_make1_oid(relOid));".

Далее мы решили провести анализ, чтобы определить, как часто функция select fasttruncate вызывается для пустой таблицы.

 

Как часто создаются пустые временные таблицы?

Для ответа на этот вопрос мы провели исследование на продуктивной базе ЕРП, упомянутой выше. 

Был собран простейший ТЖ:

<log location="ПутьСбораЛогов" history="72">
    <event>
        <eq property="Name" value="DBPOSTGRS"/>
        <eq property="p:processname" value="ИмяБазы"/>
        <like property="Sql" value="INSERT%pg_temp%"/>
    </event>
    <property name="RowsAffected"/>
</log>

 

Пример полученного лога:

55:20.112002-999,DBPOSTGRS,5,RowsAffected=0
55:20.112012-11,DBPOSTGRS,6,RowsAffected=0
55:20.113007-996,DBPOSTGRS,5,RowsAffected=0
55:20.115000-995,DBPOSTGRS,5,RowsAffected=0
55:23.323000-992,DBPOSTGRS,5,RowsAffected=0
55:23.386002-5000,DBPOSTGRS,5,RowsAffected=0
55:23.483003-2999,DBPOSTGRS,5,RowsAffected=0
55:23.506000-1992,DBPOSTGRS,5,RowsAffected=5
55:23.511005-1002,DBPOSTGRS,5,RowsAffected=10
55:23.514000-1994,DBPOSTGRS,5,RowsAffected=53
55:23.517002-2997,DBPOSTGRS,5,RowsAffected=714
55:23.525002-7995,DBPOSTGRS,5,RowsAffected=1892
55:23.540000-13997,DBPOSTGRS,5,RowsAffected=1317

 

Поле RowsAffected содержит количество строк, которые вставлялись во временную таблицу. Оставалось только агрегировать полученные данные и построить диаграмму для наглядности:

 

 

Это данные за день работы продуктивной базы ЕРП, где на диаграмме первое число - это количество строк во временной таблице, а после запятой - доля таких временных таблиц.

Получается почти 5 млн созданий временных таблиц, из них 32% были пустыми.

Исходя из этого напрашивается доработка функции SELECT FASTTRUNCATE: по пустой временной таблице нет смысла делать очистку строк, ведь она и так пустая, при этом функция очистки строк все равно выполняется существенное время. А также мы завязались не на оценку строк из статистики, а на реальное количество строк во временной таблице, что на наш взгляд более правильно.

В итоге оптимизированную функцию можно представить следующим кодом 1С:

Функция SelectFasttruncate(ВременнаяТаблица)  
     
    ТребуетсяПересчитатьСтатистикуПоТаблице = Ложь;
     
    Если ВременнаяТаблица.Количество() > 0 Тогда
        ВременнаяТаблица.Очистить();
        ТребуетсяПересчитатьСтатистикуПоТаблице = Истина; 
    КонецЕсли;
     
    Если ТребуетсяПересчитатьСтатистикуПоТаблице Тогда
        ПересчитатьСтатистикуПоТаблице(ВременнаяТаблица);
    КонецЕсли;
     
КонецФункции

 

Результат оптимизации

Эффект от оптимизации оценивался тремя способами.

Первый. Платформа Tantor позволяет получить статистические показатели по запросу за выбранный промежуток времени. Я взял данные за неделю до оптимизации:

 

 

И неделю после:

 

 

Среднее время выполнения функции SELECT FASTTRUNCATE ускорилось на 44%.

Второй. Также был собран технологический журнал следующего вида:

<log location="ПутьСбораЛогов" history="72">
    <event>
        <eq property="Name" value="DBPOSTGRS"/>
        <eq property="p:processName" value="ИмяБазы"/>
        <like property="Sql" value="%FASTTRUNCATE%"/>
    </event>
</log>

 

Список собираемых полей не указан специально, потому что они нам не нужны. Нам нужна только длительность такого запроса, которая и будет собираться - получаем логи следующего вида:

47:12.114004-3995,DBPOSTGRS,4
47:12.115026-4018,DBPOSTGRS,4
47:12.117009-3996,DBPOSTGRS,4
47:12.119011-3986,DBPOSTGRS,4
47:12.121007-5004,DBPOSTGRS,4
47:12.121013-4991,DBPOSTGRS,5

 

Данные были собраны за 2 дня до и после оптимизации, и мы получили следующий результат

:

 Замер  Количество операций

 Суммарное время выполнения (мс)

 Среднее время выполнения (мс)

 До  14572327  47400477  3.253
 После  10983028  22435790  2.043 ↓59%

 

Среднее время выполнения функции SELECT FASTTRUNCATE ускорилось на 59% (среднее время больше чем по данным платформы Tantor, т.к. событие DBPOSTGRS включает в себя издержки на сетевое взаимодействие между СП и СУБД на отправку запроса на выполнение и получение ответа от СУБД о выполнении запроса).

Третий. Был проведен синтетический тест, в котором каждый поток создавал 10 000 раз пустую временную таблицу.

 

 

 

По оси Y указано среднее время выполнения функции SELECT FASTTRUNCATE в мс, по оси X - количество потоков. 

Первый график показал ускорение в среднем на 74%, второй график (во временной таблице создавался индекс) - на 115%.

 

Неожиданный эффект от оптимизации

Перед выпуском каждого релиза мы тестируем сборку на нагрузочных тестах, чтобы убедиться, что внедряемые задачи дают оптимизацию на каких-то ключевых операциях, и нет ключевых операций, ставших хуже.

Нагрузочный тест ERP показал, что ключевая операция проведения документа "Поступление безналичных денежных средств" стала выполняться быстрее. Я нашел запрос, который ускорился, и получилась довольно интересная ситуация.

Первый раз выполнение запроса было одинаково долгим как до, так и после оптимизации - https://explain.tensor.ru/archive/explain/29db90bdcf487abe6e8a5002d31f4476:0:2024-08-05

А вот второе выполнение отличалось. До оптимизации запрос выполнялся 1.7 сек - https://explain.tensor.ru/archive/explain/93d3e65d99949d65797b9a4c6844a8a0:0:2024-08-05, а после оптимизации менее 1 мс - https://explain.tensor.ru/archive/explain/e51134f3e5239038c6741f8563db5979:0:2024-10-07.

Причина ускорения была довольно нетривиальной, попробуйте сами понять почему.

Дам несколько подсказок, которые в дополнении к информации из этой статьи дадут возможность понять причины:

  1. Перед первым выполнением данного запроса, которое было долгим в обоих случаях, для временной таблицы tt94 не была рассчитана статистика (почему так может происходить, описано в статье //infostart.ru/1c/articles/2142833/
  2. Временная таблица tt94 существует между первым и вторым выполнениями, перед вторым выполнением ее "очистка" осуществляется функцией SELECT FASTTRUNCATE.
  3. Во временную таблицу tt94 вставляется одна запись.

Жду ваших ответов в комментариях!

Данную оптимизацию мы внедрили в нашу СУБД Tantor Special Edition 1C, начиная с релиза 15.6.0. 

 

Временные таблицы в 8.3.25

3 года назад отправлял пожелание по добавлению новых возможностей по работе с временными таблицами в 1С и вот в 25й платформе появились долгожданные новые возможности. Давайте разберем их техническую сторону.

 

Добавление строк в существующую временную таблицу

Выполним запрос, который создаст временную таблицу со 100К строк, а вторым запросом добавим еще 1 строку.

ВЫБРАТЬ ПЕРВЫЕ 100000
    СебестоимостьТоваров.Период КАК Период,
    СебестоимостьТоваров.Регистратор КАК Регистратор,
    СебестоимостьТоваров.НомерСтроки КАК НомерСтроки
ПОМЕСТИТЬ ВременнаяТаблица
ИЗ
    РегистрНакопления.СебестоимостьТоваров КАК СебестоимостьТоваров
;
 
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ ПЕРВЫЕ 1
    СебестоимостьТоваров.Период КАК Период,
    СебестоимостьТоваров.Регистратор КАК Регистратор,
    СебестоимостьТоваров.НомерСтроки КАК НомерСтроки
ДОБАВИТЬ ВременнаяТаблица
ИЗ
    РегистрНакопления.СебестоимостьТоваров КАК СебестоимостьТоваров

 

Платформа выполнит следующие запросы к БД:

// 1. создаем ВТ. Если ВТ с таким именем уже существует, то сначала уничтожим ее.
drop table if exists tt1 cascade;create temporary table tt1 (_Q_001_F_000 timestamp, _Q_001_F_001TRef bytea, _Q_001_F_001RRef bytea, _Q_001_F_002 numeric(9, 0) ) without oids
// 2. Запишем либо прочитаем информацию о созданной ВТ. Данная функция выполняется на стороне сервера 1С.
Func=lookupTmpTable
// 3. Вставим 100К строк в ВТ
INSERT INTO pg_temp.tt1 (_Q_001_F_000, _Q_001_F_001TRef, _Q_001_F_001RRef, _Q_001_F_002) SELECT
T1._Period,
T1._RecorderTRef,
T1._RecorderRRef,
T1._LineNo
FROM _AccumRg50748 T1
WHERE (T1._Fld2488 = CAST(0 AS NUMERIC)) LIMIT 100000
// 4. Рассчитаем статистику на ВТ
ANALYZE pg_temp.tt1
// 5. Вставим еще одну строку в ВТ
INSERT INTO pg_temp.tt1 (_Q_001_F_000, _Q_001_F_001TRef, _Q_001_F_001RRef, _Q_001_F_002) SELECT
T1._Period,
T1._RecorderTRef,
T1._RecorderRRef,
T1._LineNo
FROM _AccumRg50748 T1
WHERE (T1._Fld2488 = CAST(0 AS NUMERIC)) LIMIT 1
// 6. Очищаем ВТ
52:06.452000-4999,DBPOSTGRS,5,Sql="SELECT FASTTRUNCATE ('pg_temp.tt1')"

 

Видно, что при использовании конструкции "Добавить" по таблице не рассчитывается статистика. Это может приводить к неверной оценке строк по временной таблице. Воспроизведем данную ситуацию.

Изменим пример. Мы будем смотреть по плану запроса сколько строк по оценке планировщика будет во временной таблице: 

ВЫБРАТЬ ПЕРВЫЕ 100000
    СебестоимостьТоваров.Период КАК Период,
    СебестоимостьТоваров.Регистратор КАК Регистратор,
    СебестоимостьТоваров.НомерСтроки КАК НомерСтроки
ПОМЕСТИТЬ ВременнаяТаблица
ИЗ
    РегистрНакопления.СебестоимостьТоваров КАК СебестоимостьТоваров
 
УПОРЯДОЧИТЬ ПО
    Период,
    Регистратор,
    НомерСтроки
;
 
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    КОЛИЧЕСТВО(1) КАК Поле1
ИЗ
    ВременнаяТаблица КАК ВременнаяТаблица
ГДЕ
    ВременнаяТаблица.НомерСтроки = 2

 

План запроса:

Query Text: SELECT
   COUNT(CAST(1 AS NUMERIC))
   FROM pg_temp.tt1 T1
   WHERE (T1._Q_001_F_002 = CAST(2 AS NUMERIC))
   Aggregate  (cost=1942.92..1942.93 rows=1 width=8) (actual time=11.698..11.699 rows=1 loops=1)
     Output: count('1'::numeric)
     Buffers: local hit=841
     ->  Seq Scan on pg_temp.tt1 t1  (cost=0.00..1941.00 rows=1923 width=0) (actual time=0.011..11.599 rows=1865 loops=1)
           Output: _q_001_f_000, _q_001_f_001tref, _q_001_f_001rref, _q_001_f_002
           Filter: (t1._q_001_f_002 = '2'::numeric)
           Rows Removed by Filter: 98135
           Buffers: local hit=841

 

По оценке планировщика 1923 строки, а фактически - 1865. Тут все хорошо, т.к. при первой вставке во временную таблицу статистика по ней рассчитывается.

Теперь выполним запрос, чтобы подтвердить, что планировщик ошибется при отсутствии статистики:

ВЫБРАТЬ ПЕРВЫЕ 0
    СебестоимостьТоваров.Период КАК Период,
    СебестоимостьТоваров.Регистратор КАК Регистратор,
    СебестоимостьТоваров.НомерСтроки КАК НомерСтроки
ПОМЕСТИТЬ ВременнаяТаблица
ИЗ
    РегистрНакопления.СебестоимостьТоваров КАК СебестоимостьТоваров
 
УПОРЯДОЧИТЬ ПО
    Период,
    Регистратор,
    НомерСтроки
;
 
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ ПЕРВЫЕ 100000
    СебестоимостьТоваров.Период КАК Период,
    СебестоимостьТоваров.Регистратор КАК Регистратор,
    СебестоимостьТоваров.НомерСтроки КАК НомерСтроки
ДОБАВИТЬ ВременнаяТаблица
ИЗ
    РегистрНакопления.СебестоимостьТоваров КАК СебестоимостьТоваров
 
УПОРЯДОЧИТЬ ПО
    Период,
    Регистратор,
    НомерСтроки
;
 
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    КОЛИЧЕСТВО(1) КАК Поле1
ИЗ
    ВременнаяТаблица КАК ВременнаяТаблица
ГДЕ
    ВременнаяТаблица.НомерСтроки = 2

 

Первым запросом мы просто создаем временную таблицу, вторым добавляем в нее 100К строк. План:

Query Text: SELECT
COUNT(CAST(1 AS NUMERIC))
FROM pg_temp.tt1 T1
WHERE (T1._Q_001_F_002 = CAST(2 AS NUMERIC))
Aggregate  (cost=1498.12..1498.13 rows=1 width=8) (actual time=12.345..12.346 rows=1 loops=1)
  Output: count('1'::numeric)
  Buffers: local hit=841
  ->  Seq Scan on pg_temp.tt1 t1  (cost=0.00..1497.82 rows=299 width=0) (actual time=0.015..12.244 rows=1865 loops=1)
        Output: _q_001_f_000, _q_001_f_001tref, _q_001_f_001rref, _q_001_f_002
        Filter: (t1._q_001_f_002 = '2'::numeric)
        Rows Removed by Filter: 98135
        Buffers: local hit=841
Query Identifier: 209524141104999352

 

Планировщик сильно ошибается, думая что будет 299 строк вместо 1865 фактических.

Тут нам на помощь может прийти уже забытый плагин online_analyze. Включим его и выполним прошлый запрос еще раз. Получаем план:

Query Text: SELECT
COUNT(CAST(1 AS NUMERIC))
FROM pg_temp.tt1 T1
WHERE (T1._Q_001_F_002 = CAST(2 AS NUMERIC))
Aggregate  (cost=1942.83..1942.84 rows=1 width=8) (actual time=12.583..12.584 rows=1 loops=1)
  Output: count('1'::numeric)
  Buffers: local hit=841
  ->  Seq Scan on pg_temp.tt1 t1  (cost=0.00..1941.00 rows=1827 width=0) (actual time=0.013..12.459 rows=1865 loops=1)
        Output: _q_001_f_000, _q_001_f_001tref, _q_001_f_001rref, _q_001_f_002
        Filter: (t1._q_001_f_002 = '2'::numeric)
        Rows Removed by Filter: 98135
        Buffers: local hit=841
Query Identifier: -6082868411932076458

 

Планировщик снова угадывает количество строк - 1827 при 1865 фактических.

Исходя из этого можно сделать следующий вывод: если ваша информационная система работает на СУБД семейства postgres, то при добавлении строк в существующую временную таблицу учитывайте, что статистика по ней пересчитана не будет. Это может привести к выбору неоптимального плана запроса. Если вы с таким столкнулись, то решением здесь может быть изменение логики запроса, либо включение плагина online_analyze.

 

Несколько индексов во временной таблице

Также была добавлена возможность создавать несколько индексов по одной временной таблице. Давайте посмотрим, как это работает, выполнив запрос:

ВЫБРАТЬ ПЕРВЫЕ 100000
    СебестоимостьТоваров.Период КАК Период,
    СебестоимостьТоваров.Регистратор КАК Регистратор,
    СебестоимостьТоваров.НомерСтроки КАК НомерСтроки
ПОМЕСТИТЬ ВременнаяТаблица
ИЗ
    РегистрНакопления.СебестоимостьТоваров КАК СебестоимостьТоваров
 
УПОРЯДОЧИТЬ ПО
    Период,
    Регистратор,
    НомерСтроки
 
ИНДЕКСИРОВАТЬ ПО НАБОРАМ
(
    (Период,
    Регистратор,
    НомерСтроки) УНИКАЛЬНО,
    (Регистратор),
    (НомерСтроки)
)
;
 
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    КОЛИЧЕСТВО(1) КАК Поле1
ИЗ
    ВременнаяТаблица КАК ВременнаяТаблица
ГДЕ
    ВременнаяТаблица.НомерСтроки = 2
;
 
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    КОЛИЧЕСТВО(1) КАК Поле1
ИЗ
    ВременнаяТаблица КАК ВременнаяТаблица
ГДЕ
    ВременнаяТаблица.Регистратор = ЗНАЧЕНИЕ(Документ.РасчетСебестоимостиТоваров.ПустаяСсылка)

 

Создаем на временной таблице один уникальный индекс и один неуникальный. Уникальный индекс означает, что в нашей временной таблице не должно быть 2х записей с одинаковыми значениями полей Период, Регистратор, НомерСтроки.

Платформа выполнит следующие запросы к БД:

// 1. Создаем ВТ. Если ВТ с таким именем уже существует, то сначала уничтожим ее.
drop table if exists tt3 cascade;create temporary table tt3 (_Q_001_F_000 timestamp, _Q_001_F_001TRef bytea, _Q_001_F_001RRef bytea, _Q_001_F_002 numeric(9, 0) ) without oids
// 2. Запишем либо прочитаем информацию о созданной ВТ. Данная функция выполняется на стороне сервера 1С.
Func=lookupTmpTable
// 3. Удалим индекс tmpind_0, если он существует
drop index if exists tmpind_0
// 4. Создадим уникальный индекс tmpind_0 на созданной ВТ
create unique index tmpind_0 on pg_temp.tt3(_Q_001_F_000, _Q_001_F_001TRef, _Q_001_F_001RRef, _Q_001_F_002)
// 5. Удалим и создадим еще 2 индекса: tmpind_1 и tmpind_2
drop index if exists tmpind_1
create index tmpind_1 on pg_temp.tt3(_Q_001_F_001TRef, _Q_001_F_001RRef)
drop index if exists tmpind_2
create index tmpind_2 on pg_temp.tt3(_Q_001_F_002)
// 6. Вставим данные в ВТ
INSERT INTO pg_temp.tt3 (_Q_001_F_000, _Q_001_F_001TRef, _Q_001_F_001RRef, _Q_001_F_002) SELECT
T1._Period,
T1._RecorderTRef,
T1._RecorderRRef,
T1._LineNo
FROM _AccumRg50748 T1
WHERE (T1._Fld2488 = CAST(0 AS NUMERIC))
ORDER BY (T1._Period), (T1._RecorderTRef), (T1._RecorderRRef), (T1._LineNo) LIMIT 100000
// 7. Рассчитаем статистику на ВТ
ANALYZE pg_temp.tt3
// 8. Выполним запрос вычисления строк с отбором по НомерСтроки
SELECT
COUNT(CAST(1 AS NUMERIC))
FROM pg_temp.tt3 T1
WHERE (T1._Q_001_F_002 = CAST(2 AS NUMERIC))
// 9. Выполним запрос вычисления строк с отбором по Регистратор
SELECT
COUNT(CAST(1 AS NUMERIC))
FROM pg_temp.tt3 T1
WHERE (T1._Q_001_F_001TRef = '\\000\\000\\004\\270'::bytea AND T1._Q_001_F_001RRef = '\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000'::bytea)'
// 10. Удаляем 3 созданных индекса
drop index if exists TMPIND_0
drop index if exists TMPIND_1
drop index if exists TMPIND_2
// 11. Очищаем ВТ
SELECT FASTTRUNCATE ('pg_temp.tt3')
 

В запросах платформы к БД нет ничего необычного, видим, что создаются все 3 индекса.

Посмотрим планы запросов, чтобы убедиться, что индексы используются, мало ли. План запроса отбора по полю Номер Строки:

Query Text: SELECT
COUNT(CAST(1 AS NUMERIC))
FROM pg_temp.tt3 T1
WHERE (T1._Q_001_F_002 = CAST(2 AS NUMERIC))
Aggregate  (cost=880.63..880.64 rows=1 width=8) (actual time=1.264..1.265 rows=1 loops=1)
  Output: count('1'::numeric)
  Buffers: local hit=492
  ->  Bitmap Heap Scan on pg_temp.tt3 t1  (cost=17.23..878.90 rows=1723 width=0) (actual time=0.220..1.173 rows=1865 loops=1)
        Recheck Cond: (t1._q_001_f_002 = '2'::numeric)
        Heap Blocks: exact=486
        Buffers: local hit=492
        ->  Bitmap Index Scan on tmpind_2  (cost=0.00..17.06 rows=1723 width=0) (actual time=0.155..0.155 rows=1865 loops=1)
              Index Cond: (t1._q_001_f_002 = '2'::numeric)
              Buffers: local hit=6

 

А это план запроса с отбором по полю Регистратор:

Query Text: SELECT
COUNT(CAST(1 AS NUMERIC))
FROM pg_temp.tt3 T1
WHERE (T1._Q_001_F_001TRef = '\\000\\000\\004\\270'::bytea AND T1._Q_001_F_001RRef = '\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000'::bytea)
Aggregate  (cost=2.33..2.34 rows=1 width=8) (actual time=0.009..0.009 rows=1 loops=1)
  Output: count('1'::numeric)
  Buffers: local hit=2
  ->  Index Only Scan using tmpind_1 on pg_temp.tt3 t1  (cost=0.12..2.33 rows=1 width=0) (actual time=0.008..0.008 rows=0 loops=1)
        Output: _q_001_f_001tref, _q_001_f_001rref
        Index Cond: ((t1._q_001_f_001tref = '\\x000004b8'::bytea) AND (t1._q_001_f_001rref = '\\x00000000000000000000000000000000'::bytea))
        Heap Fetches: 0
        Buffers: local hit=2

 

 

В обоих случаях используются созданные нами индексы, все работает верно.

 

Заключение

Мы рассмотрели техническую часть работы платформы 1С с временными таблицами. В платформе 8.3.25 появились новые возможности, которые позволят в определенных сценариях ускорить выполнения запросов при использовании нескольких индексов на одной временной таблице.

Стоит учитывать, что при использовании конструкции "ДОБАВИТЬ" по временной таблице не будет пересчитана статистика - здесь вам может помочь плагин online_analyze.

Хорошее знание и понимание работы механизмов платформы позволяют ускорить работу 1С - мы рассмотрели это на примере функции SELECT FASTTRUNCATE.

На текущий момент у нас в работе находятся и другие задачи по дальнейшему улучшению производительности СУБД Tantor  SE 1C при массовой работе с временными таблицами. Мы расскажем о них по факту их реализации и обкатки.

Чтобы своевременно узнавать о выходе новых статей, подпишитесь на наш профиль Инфостарта.

Временные таблицы postgres Tantor fasttruncate платформа Tantor 8.3.25 8.3.23 online_analyze оптимизация

См. также

Механизмы платформы 1С Программист Стажер Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

Эта небольшая статья - некоторого рода шпаргалка по файловым потокам: как и зачем с ними работать, какие преимущества это дает.

23.06.2024    7905    bayselonarrend    20    

156

Механизмы платформы 1С Программист Бесплатно (free)

Язык программирования 1С содержит много нюансов и особенностей, которые могут приводить к неожиданным для разработчика результатам. Сталкиваясь с ними, программист начинает лучше понимать логику платформы, а значит, быстрее выявлять ошибки и видеть потенциальные узкие места своего кода там, где позже можно было бы ещё долго медитировать с отладчиком в поисках источника проблемы. Мы рассмотрим разные примеры поведения кода 1С. Разберём результаты выполнения и ответим на вопросы «Почему?», «Как же так?» и «Зачем нам это знать?». 

06.10.2023    24031    SeiOkami    48    

135

WEB-интеграция Универсальные функции Механизмы платформы 1С Программист Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

При работе с интеграциями рано или поздно придется столкнуться с получением JSON файлов. И, конечно же, жизнь заставит проверять файлы перед тем, как записывать данные в БД.

28.08.2023    15087    YA_418728146    7    

169

Механизмы платформы 1С Программист Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

Мало кто знает, что поле "Глобального поиска" в 1С можно доработать. Добавить свои варианты поиска, кнопочки в результатах и даже целые пользовательские меню.

27.03.2023    8703    SeiOkami    10    

143

Механизмы платформы 1С Программист Платформа 1С v8.3 Бесплатно (free)

Давайте разберемся в механизме «История данных» и поэкспериментируем для наглядности. Сравним «Версионирование объектов» и «Историю данных».

06.03.2023    32634    dsdred    74    

216

Механизмы платформы 1С Запросы Программист Платформа 1С v8.3 Запросы Конфигурации 1cv8 Бесплатно (free)

В предлагаемой статье решил привести примеры применения новых возможностей языка запросов 1С, начиная с версии платформы 8.3.20.

21.11.2022    28657    quazare    36    

127
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. denmax 436 29.10.24 15:00 Сейчас в теме
А вы уверены, что поступили правильно, завязав пересчет статистики в вашей оптимизации на реальное число строк, вместо оценки по статистике (как было в оригинале)? Логически выглядит более правильным выполнение пересчета только если по статистике было ненулевое число строк. Например, если в таблице были строки, но статистика выдавала нулевое число, то достаточно очистить строки, а статистику не пересчитывать.
3. Tantor 89 29.10.24 21:12 Сейчас в теме
(1) Да, т.к. случаев, когда во временной таблице нет статистики, очень мало, примерно менее 0.01%.
Также мы же завязываемся (речь про оптимизированный вариант функции) на реальное количество строк в таблице не только для того, чтобы пересчитать статистику, но и для того, чтобы очистить временную таблицу. Если в этом случае завязываться на количество строк из статистики, то можно получить проблему, т.к. если по статистике строк 0, а по факту более 0, то временная таблица не будет очищена.
6. denmax 436 30.10.24 00:35 Сейчас в теме
(3) я только про статистику говорил, очищать понятно нужно только по наличию реальных строк
2. paulwist 29.10.24 15:20 Сейчас в теме
Причина ускорения была довольно нетривиальной, попробуйте сами понять почему.


Начнём с того, что быстрый и медленный запрос - это не тождественные запросы :)

В медленном предикат:
(((T1._Fld77707RRef IN ('\\2041\\030f\\332\\261R\\333\\021\\356\\2244x^\\307\\262'::bytea))))) 


В быстром предикат:
(((T1._Fld77707RRef IN ('\\215\\200\\030f\\332\\261R\\333\\021\\356\\225\\235\\276\\\\\\211~'::bytea)))))


Во временную таблицу tt94 вставляется одна запись.


И второе.

В быстром временная табличка tt94 имеет 43 записи.

В медленном, ПГ считает, что там 430.

Почему вы пишите про 1 запись - непонятно.

Скрытый текст


Собственно, поэтому планы разные :)
5. Tantor 89 29.10.24 22:57 Сейчас в теме
(2) Указанное вами условие не особо влияет, т.к. там идет сравнение на не равно, что в данном случае особой роли не имеет. Но спасибо за то, что нашли несоответствие, я сейчас выполнил запросы еще раз:

Первый раз выполнение запроса было одинаково долгим как до, так и после оптимизации - https://explain.tensor.ru/archive/explain/0f93ac7a8ff7177407e75c0593b2b09f:0:2024-10-29

А вот второе выполнение отличалось. До оптимизации запрос выполнялся 1.8 сек - https://explain.tensor.ru/archive/explain/cfcce9b4ff853211498685498a19a5e9:0:2024-10-29, а после оптимизации менее 1 мс - https://explain.tensor.ru/archive/explain/2d2766e1885b3862ac0f7b60afa93454:0:2024-10-29.


В быстром временная табличка tt94 имеет 43 записи.

В медленном, ПГ считает, что там 430.

Почему вы пишите про 1 запись - непонятно.

43 и 430 это оценки планировщика, а фактическое количество строк - 1.

А так, в быстром попали в индекс считали 1 блок, в медленном сканируем табличку,
как результат вынимается 1.6Г :)

Верно мыслите, но почему в одном случае пошел в индекс, а в другом в скан?)
7. paulwist 30.10.24 09:32 Сейчас в теме
(5)
почему в одном случае пошел в индекс, а в другом в скан?


Продолжим.

Оптимизатор выбирает не самый "быстрый" план, а самый лучший за отведённое время.

Из "общих" соображений, не вдаваясь в подробности.

Видим, для медленного запроса время выбора плана составило 1.118 мс, для быстрого 1.178, те поиск плана во втором случае был дольше на 0.06 мс, соответственно во втором случае планировщик нашёл более быстрый план, поскольку перебрал больше вариантов. Почему оптимизатор в быстром случае нашел индексное решение - надо смотреть на "дерево" решений, из которого будет понятно, какие условия изменились между запросами.

PS из очевидных - статистика на временной табличке tt94 поменялась. (видимо основной вопрос: почему статистика поменялась) :)
11. Tantor 89 30.10.24 10:52 Сейчас в теме
(7)
PS из очевидных - статистика на временной табличке tt94 поменялась. (видимо основной вопрос: почему статистика поменялась) :)

Да, это основной вопрос почему она изменилась)
15. paulwist 30.10.24 15:12 Сейчас в теме
(11)
Да, это основной вопрос почему она изменилась)


Ой блин, пришлось перечитать статью :)


Временная таблица tt94 существует между первым и вторым выполнениями, перед вторым выполнением ее "очистка" осуществляется функцией SELECT FASTTRUNCATE.


Если исходя из статистики (именно из статистики, а не фактического количества строк) данной временной таблицы в ней до усечения было строк более 0, то присвоим переменной makeanalyze значение ИСТИНА.
...
Если переменная makeanalyze = ИСТИНА, то пересчитаем статистику по таблице, т.к. мы удалили из нее все записи, и статистика стала неактуальной.


PS я думал какая-то фича движка ПГ, ан нет Этот модуль требуется для поддержки системы 1С:Предприятие.
:) :) :)
21. Tantor 89 30.10.24 19:31 Сейчас в теме
4. aximo 2099 29.10.24 22:07 Сейчас в теме
Я вот лет 20 занимаюсь 1с, сталкивался с разными базами, поэтому считаю, что для erp - 700 гб - не такая уж и большая и вероятно, «запущенная»…

А из статьи, я понял, что появились некие новшества - «ДОБАВИТЬ», которые лично для меня совершенно бесполезны- я предпочел бы ОБЪЕДИНИТЬ….

И на сколько ваши изыскания по оптимизации оправданы для бизнеса? Откуда столько времени на подобные работы?

Вообще, картина представилась так - есть некая запущенная база, за которой никто не следил, она начала тормозить в определенный момент - ребята решили что-то «оптимизировать» непонятными инструментами….
9. Tantor 89 30.10.24 09:50 Сейчас в теме
(4) Мы делаем данные изыскания и оптимизации не ради одной какой-то базы, а ради всех клиентов, которые используют нашу СУБД Tantor. И конечно все зависит от количества пользователей: для 500 пользователей эта оптимизация может быть незаметна, а вот уже на нескольких тысячах оптимизированная функция будет меньше нагружать CPU сервера СУБД, т.к. каждый сеанс 1С работает с временными таблицами.
10. aximo 2099 30.10.24 10:07 Сейчас в теме
(9) у вас все написано красиво в статье, но думаю, что количество одновременных сеансов в вашей базе - на пике 200, а по-факту не превышает и 150.

В статье резануло еще то, что вы пишите “документ прихода безнала» стал проводиться быстрее)))) - это вообще «полупустой» документ по проводкам…. Если это приводится как аргумент для ваших изысканий - это весьма странно для профессионалов учета….

Лайфхак от типовых - чтобы каждый сеанс меньше работал с вр.таб - можно включить отложенное проведение
12. Tantor 89 30.10.24 10:57 Сейчас в теме
(10)
В статье резануло еще то, что вы пишите “документ прихода безнала» стал проводиться быстрее)))) - это вообще «полупустой» документ по проводкам…. Если это приводится как аргумент для ваших изысканий - это весьма странно для профессионалов учета….

Это не аргумент, это просто было обнаружено после оптимизации функции SELECT FASTTRUNCATE. А аргументы для оптимизации этой функции были другие. Кстати, на недавнем партнерском форуме 1С, который проходил в сентябре, фирма 1С тоже рассказывала, что она в ходе своих нагрузочных тестов пришла к такому же выводу: более 30% создаваемых временных таблиц в приложениях 1С являются пустыми.
13. TMV 14 30.10.24 15:05 Сейчас в теме
(12)
30%
зная это, что можно сказать о качестве продуктов 1с?
19. Tantor 89 30.10.24 17:54 Сейчас в теме
(13) Я не измеряю качество продуктов 1С по количеству пустых временных таблиц, создаваемых приложениями.
По факту же приложение пишется универсально и при разных вводных данных создается разное количество записей во временных таблицах.
Мы же пишем в коде проверки типа "ТаблицаЗначений.Количество() > 0"? Если таблица значений может содержать 0 записей, то почему временная таблица не может содержать 0 записей?)
JohnyDeath; grumagargler; +2 Ответить
31. TMV 14 01.11.24 13:19 Сейчас в теме
34. Tantor 89 01.11.24 14:26 Сейчас в теме
(31) так написан типовой код. Он априори не может учитывать индивидуальных особенностей каждой организации
36. TMV 14 02.11.24 06:58 Сейчас в теме
(34) 30% ВСЕХ вт - не очень похоже на индивидуальные особенности
14. TMV 14 30.10.24 15:08 Сейчас в теме
(10)
Лайфхак от типовых - чтобы каждый сеанс меньше работал с вр.таб - можно включить отложенное проведение
это как? Перекладывание проведения на фз никак на это не влияет.
16. a.doroshkevich 1512 30.10.24 15:51 Сейчас в теме
Добрый день!

Получается что запросить количество строк дешевле чем почистить пустую?
Не измеряли через perf этот момент?

Кажется что select count() в общем случае будет дороже truncate
18. Tantor 89 30.10.24 17:50 Сейчас в теме
(16) Добрый день!
Мы не запрашиваем информацию о количестве строк через SQL-запрос "SELECT COUNT()". Я привел пример "Если ВременнаяТаблица.Количество() > 0 Тогда" образно, чтобы упростить понимание оптимизации.
По факту информация о размере временной таблицы хранится во внутренних механизмах постгреса, оттуда мы ее и запрашиваем.
Через perf я после оптимизации не измерял, а только тестами 1С, описанными в статье.
20. a.doroshkevich 1512 30.10.24 18:26 Сейчас в теме
(18) Если не секрет, что за механизм?)
Очень интересно как получить количество не из статистики и не через подсчёт запросом?
В любом случае, спасибо за проделанную работу и прикладные тесты!
lemonline; +1 Ответить
26. dobpilot 31.10.24 19:47 Сейчас в теме
(20)
Очень интересно как получить количество не из статистики и не через подсчёт запросом?


механизм отладки ядра perf, https://wiki.postgresql.org/wiki/Profiling_with_perf

Мы протестировали на PostgresPro Ent 15, https://github.com/dobpilot/fasttrun, получили ускорение в наших сценариях загрузки документов в 4-ре раза
28. Tantor 89 01.11.24 12:10 Сейчас в теме
(26) В 4 раза ускорилась загрузка документов или выполнение функции fasttrun?
30. dobpilot 01.11.24 12:55 Сейчас в теме
(28)
В нашем сценарии ~600 потоков загрузки, стали погружать в 4 раза больше документов.
32. Tantor 89 01.11.24 13:56 Сейчас в теме
(30) Интересно измерить по логам ТЖ сколько времени ДО и ПОСЛЕ у вас занимало выполнение запроса "SELECT FASTTRUNCATE". Такое ощущение, что у вас там очень много пустых временных таблиц создавалось также как у меня в кейсе с УХ из статьи.
33. dobpilot 01.11.24 14:10 Сейчас в теме
(32)

Так и есть, почти типовая ERP 2.5.12, используется только подсистема продаж, много пустых ВТ.

Вот что замерили под нагрузкой (>500 active pg connections)

1000 итераций:

Пакет с ВТ с УНИЧТОЖИТЬ : (В которой происходит DROP временной таблицы)
Время с сервера: 16.136 сек
Время с сервера: 17.845 сек
Время с сервера: 17.278 сек
Время с сервера: 20.514 сек
Время с сервера: 17.673 сек

Пакет ВТ (честный вызов FASTTRUNCATE с не пустой таблицей):
Время с сервера: 19.126 сек
Время с сервера: 18.570 сек
Время с сервера: 18.586 сек
Время с сервера: 20.717 сек
Время с сервера: 20.917 сек

Пустая ВТ : (вызов FASTTRUNCATE с пустой таблицей)
Время с сервера: 5.431 сек
Время с сервера: 5.173 сек
Время с сервера: 5.318 сек
Время с сервера: 5.143 сек
Время с сервера: 4.649 сек

Если подитожить, остается вопрос почему вызов DROPа быстрее чем heap_truncate
17. aximo 2099 30.10.24 15:53 Сейчас в теме
Пример из собственной практики - в базе одновременно работает 60-70 человек - все они с разной скоростью вносят документы поступления товаров…

Наступает момент, когда через полгода количество документов переходит в критическую массу и любое переоткрытие - проведение документов создает блокировку.

Что я сделал - первым шагом - я отрубил создание движений в регистрах, которые мне не понадобяться… когда и это перешло в критическую ситуацию - я сделал так … запретил пользователям делать проводки этим типом документа, кроме условного админа - создал регламентное задание ночью по последовательному проведению документов

Блокировки победил
user1015414; Tantor; +2 Ответить
22. lemonline 31.10.24 06:54 Сейчас в теме
(17) Зачем такие сложности?? Достаточно было заблокировать информационную базу и все. Всем известно, что в не рабочей базе блокировок не бывает.
JohnyDeath; starik-2005; d4rkmesa; +3 Ответить
27. d4rkmesa 01.11.24 10:27 Сейчас в теме
(17) Это в 1С ERP? Какая-то история из времен УПП. Современные конфы легко в 200 пользователей работают, без блокировок.
29. dobpilot 01.11.24 12:53 Сейчас в теме
(27)

В PostgreSQL есть ахиллесова пята в виде LockManager (подробности в докладе Александра Короткова https://pgconf.ru/media/2020/02/06/Korotkov_pgconfru_2020_Bottlenecks_2.pdf).

Суть в том что, чем больше вызывается в секунду метод LWLockAccquire, тем больше задержки и производительность всей системы снижается.

Так вот, данный патч снижает количество вызовов к менеджеру блокировок (LWLock) тем самым улучшая производительность всей системы.
35. Tantor 89 01.11.24 14:28 Сейчас в теме
(29) В ванильном постгрес, начиная с версии 15.6, также была решена проблема lwlock при большом количестве соединений, работающих с временными таблицами
23. lemonline 31.10.24 07:05 Сейчас в теме
Вопрос про ДОБАВИТЬ.
Да, видно, что планировщик ошибся с количеством строк. Но оператор в любом случае был Seq Scan и количество прочитанных буферов одинаково. Как это отразилось на скорости? Вы не привели Execution Time
24. Tantor 89 31.10.24 15:33 Сейчас в теме
(23) Тут дело было не в скорости, а в факте ошибки с количество строк.
А к чему такая ошибка может привести описано в статье https://infostart.ru/1c/articles/2142833/
25. dobpilot 31.10.24 15:58 Сейчас в теме
Для тех кому интересно попробовать данную оптимизацию, по мотивам статьи накатал патч https://github.com/dobpilot/fasttrun
37. user2099845 11.11.24 09:39 Сейчас в теме
Как протестировать данную оптимизацию?
38. kan4045 11.11.24 12:25 Сейчас в теме
(37)
Как протестировать данную оптимизацию?


Собрать https://github.com/dobpilot/fasttrun, заменить fasttrun.so, запустить ваш нагрузочный тест.
39. Tantor 89 11.11.24 12:42 Сейчас в теме
(37) Перейти на сайт https://tantorlabs.ru/ и нажать на кнопку "Связаться" в правом верхнем углу
Оставьте свое сообщение