пятница, 5 апреля 2013 г.

Техническое. Мимоходом.

Если у вас в проекте oracle && .net && куча данных для сохранения, то следующий абзац (два и более, как получится), возможно, будет вам интересен.

Там получилось, что нам надо сохранять довольно много ячеек, порядка одного/двух миллионов. Естественно, хочется быстрее. Ячейки из собственного OLAP, по измерениям отличаются, разных типов штук 10-15, то есть раскидать по таблицам на каждый тип.

В итоге мы плюем на нормализуемость таблиц, тут производительность важнее. Хранится ключ по разрезам, понятно отдельно Id, а потом идет пару сотен столбцов/показателей. У себя внутри мы это называем "развернутые таблицы".

Кстати, кто помним все нормальные формы. В мое время их было ровно 3. Посмотрел, сейчас они тоже есть, потом есть усиленная третья, и еще четвертая, пятая и шестая, как усиление усиленной третьей. Сложно сказать, иногда надо забивать на теорию и выжимать скорость. Но чтобы забивать на теорию, надо сначала знать эту теорию. Иначе это как-то иначе называется.

Ранее все хранилось как у всех обычных людей, для каждой ячейки отдельная запись. Философски это правильно. А как развернули, база с десятка Гб ужалась в 1Гб, со всеми индексами.

Теперь собственно про запись. Рекомендуется посмотреть на ODP.net (Oracle Data Provider). Там есть фишечки и тюнинг. В нем же есть ArrayBinding, это когда вместо сохранения большого массива записей вы кидаете один запрос, а все параметры биндите на массивы, где каждый массив представляет один параметр. Хоть это и круто, но пока это не все.

Очень важно, чтобы запрос был все время одинаковым. Иначе oracle будет все время его компилять/парсить, как следствие - потери. Для нашего случая мы определяем наиболее общий набор показателей (столбцов) для сохранения. То есть сохранение в просто "развернутой" таблице - это что попало из прямоугольной матрицы, а становится - объединение набора столбцов. Да, это немного лишнее, получается что мы заново сохраняем некоторые ячейки которые уже есть в базе. Но зато так sql-запрос становится универсальным для всей таблицы (конкретного набора для сохранения). А сейчас если воткнуть arrayBinding из предыдущего абзаца, то скорость сохранения увеличится на десятичный порядок.

То есть у нас было несколько итераций.
1) Просто таблица
1.1) Просто таблица + arrayBinding
2) Развернутая таблица, сохраняем одним запросом кучу ячеек.
3) Развернутая таблица, объединеный запрос на все сохранение, биндим параметры на каждый ключ отдельно
4) Развернутая таблица, объединенный запрос, биндим массивы как параметры.

Переход от 3 к 4 - это реально десятикатный прирост. От 1 к 1.1, от 1 к 2 и от 2 к 3 - где-то от полутора до трех раз (в зависимости от данных и направления солнечного ветра).

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

Еще важно, чтобы запрос был как можно проще. У нас в итоге после последнего рефкторинга самая медленная таблица оказалась единственная с nullable полем в ключе. И запрос был с NVL/decode. Естественно, решили проверить. И да, бинго. Разнесли данные, и оно относительно себя ускорилось в 2 раза, относительно всех остальных не стало выделяться.

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

Комментариев нет: