Вставка 1 миллиона строк

YugabyteDB оптимизирована для OLTP, при масштабировании кластера, и производительность массовой загрузки не была первоочередной задачей. Но массовая загрузка обычно является первым делом, и это может создать плохое впечатление о производительности, как в этом выпуске git по сравнению с PostgreSQL.

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

Я выполнил вставку одного миллиона строк со всеми версиями YugabyteDB, доступными в Docker Hub. Мой уродливый скрипт для этого находится в конце этого поста, вот результат:

Вы можете видеть, что 2 года назад, в версии 2.1.3, была проведена оптимизация, но затем ничего не изменилось до версии 2.13 для настроек по умолчанию (в Purple «default»)

yb_disable_transactional_writes

Оранжевый «нетранзакционный» — это то, что существовало некоторое время для улучшения COPY, и что теперь является общей настройкой на уровне PostgreSQL с 2.8.1 с 60% приростом производительности вставки. Транзакционное поведение по умолчанию — это то, что мы ожидаем от базы данных SQL: когда пакет строк вставляется в транзакцию, все или ни одной строки не фиксируется. Поскольку данные распределены по многим планшетам, это распределенная транзакция. В YugabyteDB для этого необходимо обновить табличку состояния транзакции и хранить предварительные записи в «Intents DB», прежде чем переместить их в «Regular DB». Это связано с определенными затратами, но это цена, которую приходится платить за ACID.

Однако для массовой загрузки, когда в случае ошибки вы усечете таблицу и загрузите ее снова, вы можете обойти это, используя «быстрый путь» записи непосредственно в обычную БД с однострочными транзакциями. Именно это и делает set yb_disable_transactional_writes = true. Вставка происходит быстрее, но вы должны знать о последствиях. Обратите внимание, что если у вас есть вторичные индексы или проверка внешних ключей, то даже вставка одной строки является распределенной транзакцией, и использование быстрого пути для нее не является последовательным. Таким образом, это быстро, но использовать его следует только для начальной загрузки данных, во время миграции, когда в случае сбоя все перезапускается.

В версии 2.13.2 появились и другие возможности, и вот обзор последней версии:

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

yb_enable_upsert_mode

В синем «Upsert» я установил yb_enable_upsert_mode=on. Это должно быть явно включено, потому что это изменяет поведение вставки.

Таблица имеет первичный ключ, и в SQL, если вы попытаетесь вставить строку с тем же ключом, что и существующая, вы получите исключение. Это хорошо для обнаружения ошибок в приложении до того, как данные будут испорчены. Но это и дополнительная работа: перед записью необходимо выполнить чтение. YugabyteDB хранит данные в LSM-деревьях, которые оптимизированы для записи, но эта операция чтения вредит производительности. Включение режима upsert не будет считывать предыдущее изображение, и новая версия строки просто заменит предыдущую, если она имеет то же значение первичного ключа. Это приемлемо, когда вы загружаете файл, например, в пустую таблицу, потому что вы знаете, что ваш файл не содержит дубликатов.

Упакованные столбцы

В Gray («Packed») я установил флаг tserver max_packed_row_columns=5. Это управляет новой функцией, которая по умолчанию отключена (max_packed_row_columns=-1) в этой версии. Таблицы и кортежи индексов хранятся в DocDB с одним поддокументом на колонку. Это имеет много преимуществ, например, не нужно копировать всю строку при обновлении одного столбца, что является хорошо известной проблемой PostgreSQL. Однако это дорого при массовой загрузке таблиц с большим количеством столбцов. Эта новая возможность позволяет упаковать всю строку в один документ. Будущие обновления по-прежнему будут происходить по столбцам, но первоначальная вставка будет оптимальной.

⚠ Это версия 2.13, настройки упакованных колонок изменятся в версии 2.15 (этот коммит), и я подробно расскажу об этом в одной из будущих статей блога.

Новый параметр: ysql_enable_packed_row.

Скрипт, который я использовал

Грядут и другие улучшения. Вот скрипт, который я использовал для запуска на всех версиях для этого эксперимента. (здесь все значения установлены по умолчанию, измените set и tserver_flags на те, которые вы хотите протестировать):

mkdir -p out
reg="registry.hub.docker.com"
img="yugabytedb/yugabyte"
for tag in $(
curl -Ls "https://${reg}/v2/repositories/${img}/tags?page_size=1000" |
 jq -r '."results"[]["name"]' |
 sort -rV                     |
 grep -v latest
)
do
 grep -H "rows=1000000" out/log_${tag}_5.txt || {
 docker pull ${img}:${tag}
 docker rm -f tmp_yb 2>/dev/null
 sleep 30
 timeout 60 docker run -d --rm --name tmp_yb  
 -p7000:7000 -p9000:9000 -p5433:5433 -p9042:9042 
 ${img}:${tag} 
 bin/yugabyted start --tserver_flags=max_packed_row_columns=-1 
 --daemon=false
 sleep 60
 for i in {1..5} ;  do
 {
 timeout 600 docker exec -i tmp_yb ./bin/ysqlsh -e <<'SQL'
drop table if exists demo;
create table demo (id int primary key, a int, b int, c int, d int);
set yb_disable_transactional_writes = off;
set yb_enable_upsert_mode = off;
set ysql_session_max_batch_size = 0;
set ysql_max_in_flight_ops = 10000;
explain (costs off, analyze) insert into demo select generate_series(1,1000000),0,0,0,0;
timing off
SQL
curl -qs http://localhost:9000/metrics |  jq --arg epoch $(date +%s) -r '
 .[]
 |(.attributes.namespace_name+"."+.attributes.table_name+"/"+.id) as $tablet
 |select(.type=="tablet" and .attributes.namespace_name!="system" and .attributes.table_name=="demo" )
 |.metrics[]
 |select(.value>0)
 |(.value|tostring)+"t"+$tablet+":"+.name
' | sort -rn
 } |
 tee "out/log_${tag}_${i}.txt"
 done
 docker rmi ${img}:${tag}
 awk '/Execution Time/{x=FILENAME;gsub("[-b_.]"," ",x);$0=$0" "x;printf"%-30s %10.2f %20dn",FILENAME,$3,$6*10000000000000+$7*10000000000+$8*10000000+$9*10000+$10}' out/*.txt | sort -nk3 > all.log
}
docker image prune --all --force
done
Войти в полноэкранный режим Выйти из полноэкранного режима

И как я получаю результат:

awk '/Execution Time/{x=FILENAME;gsub("[-b_.]"," ",x);$0=$0" "x;printf"%-30s %10.2f %20dn",FILENAME,$3,$6*10000000000000+$7*10000000000+$8*10000000+$9*10000+$10}' out/*.txt | sort -rnk3 | awk '{printf "%10.2f %sn",$2-l,$0;l=$2}' | nl | tee /dev/stderr | awk '{sub("out/log_","");sub("_..txt","")}{if(x[$5" "$3]==0){x[$5" "$3]=$4}else if(x[$5" "$3]<$4){x[$5" "$3]=$4}}END{for(i in x){print i,x[i]}}' | sort

Войти в полноэкранный режим Выйти из полноэкранного режима

Это также собирает некоторую статистику. Например, вы увидите, что без упакованных столбцов, на уровне DocDB, из-за 5 столбцов, существует 5000000 rows_inserted. С упакованными строками это 1000000 rows_inserted с одним поддокументом DocDB на строку.

В скрипте выше я установил значения по умолчанию для флага уровня кластера max_packed_row_columns и уровня сессии yb_enable_upsert_mode, yb_disable_transactional_writes, ysql_session_max_batch_size, ysql_max_in_flight_ops.

Вы можете поиграть с двумя последними, связанными с промывкой буфера записи, но значения по умолчанию должны быть в норме. Пусть вас не обманывает значение 0 размера пакета сессии. Это означает, что используется настройка уровня кластера ysql_session_max_batch_size, которая в этой версии равна 3072.

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

Оцените статью
Procodings.ru
Добавить комментарий