Оценка требований к объему памяти для таблиц, оптимизированных для памяти

Применяется к:SQL ServerAzure SQL DatabaseAzure, управляемому экземпляру SQL Azure

Оптимизированные для памяти таблицы требуют наличия достаточного объема памяти для хранения всех строк и индексов в памяти. Так как память является конечным ресурсом, важно понимать использование памяти и управлять ими в системе. Темы в этом разделе описывают распространенные сценарии использования и управления памятью.

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

Если вы рассматриваете миграцию из дисковых таблиц в оптимизированные для памяти таблицы, прежде чем продолжить работу в этом разделе, ознакомьтесь с разделом "Определение того, следует ли перенести таблицу или хранимую процедуру в память OLTP " для получения рекомендаций по переносу таблиц, которые лучше всего перенести. Все разделы статьи Миграция в In-Memory OLTP содержат руководство по миграции дисковых таблиц в оптимизированные для памяти.

Основные инструкции по оценке требований к памяти

Начиная с SQL Server 2016 (13.x), нет ограничений на размер оптимизированных для памяти таблиц, хотя таблицы должны соответствовать памяти. В SQL Server 2014 (12.x) поддерживаемый размер данных составляет 256 ГБ для таблиц SCHEMA_AND_DATA.

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

Индексы в таблицах, оптимизированных для памяти, как правило, меньше, чем некластеризованные индексы в дисковых таблицах. Размер некластеризованных индексов равен порядка [primary key size] * [row count]. Размер хэш-индексов — [bucket count] * 8 bytes.

При активной рабочей нагрузке требуется дополнительная память для учета управления версиями строк и различных операций. Объем необходимой памяти на практике зависит от рабочей нагрузки, однако для надежности рекомендуется начинать с объема памяти, в два раза превышающего ожидаемый размер оптимизированных для памяти таблиц и индексов и наблюдать, каковы потребности в памяти на самом деле. Объем дополнительных затрат ресурсов на управление версиями строк всегда зависит от характеристик рабочей нагрузки. Так, длительные транзакции увеличивают нагрузку на память особенно сильно. Для большинства рабочих нагрузок, использующих более крупные базы данных (например, >100 ГБ), затраты, как правило, ограничены (25% или меньше).

Вычисление точных требований к памяти

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

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

CREATE TABLE t_hk
(  
  col1 int NOT NULL  PRIMARY KEY NONCLUSTERED,  

  col2 int NOT NULL  INDEX t1c2_index   
      HASH WITH (bucket_count = 5000000),  

  col3 int NOT NULL  INDEX t1c3_index   
      HASH WITH (bucket_count = 5000000),  

  col4 int NOT NULL  INDEX t1c4_index   
      HASH WITH (bucket_count = 5000000),  

  col5 int NOT NULL  INDEX t1c5_index NONCLUSTERED,  

  col6 char (50) NOT NULL,  
  col7 char (50) NOT NULL,   
  col8 char (30) NOT NULL,   
  col9 char (50) NOT NULL  

)   WITH (memory_optimized = on)  ;
GO  

Используя эту схему, мы определим минимальную память, необходимую для этой оптимизированной для памяти таблицы.

Память для таблицы

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

  • Метки времени
    Заголовок строки или метки времени = 24 байта.

  • Указатели индекса
    Для каждого хэш-индекса в таблице каждая строка содержит 8-байтный адресный указатель на следующую строку в индексе. Так как существует четыре индекса, каждая строка выделяет 32 байта для указателей индекса (8 байтов для каждого индекса).

  • Data
    Размер данных в строке определяется путем суммирования размера типа данных для каждого столбца данных. В нашей таблице имеется пять 4-байтных целых чисел, три 50-байтных символьных столбцов и один 30-байтный символьный столбец. Поэтому часть данных в каждой строке — это 4 + 4 + 4 + 4 + 4 + 50 + 50 + 30 + 50 или 200 байт.

Далее приведено вычисление размера для 5 миллионов строк в таблице, оптимизированной для памяти. Общий объем памяти, используемой строками данных, вычисляется следующим образом:

Память для строк таблицы

Из вышеуказанных вычислений, размер каждой строки в таблице, оптимизированной для памяти, будет 24 + 32 + 200 или 256 байт. Поскольку мы имеем 5 миллионов строк, таблица использует 5 000 000 * 256 байт или 1 280 000 000 байт — примерно 1,28 ГБ.

Память для индексов

Объем памяти для каждого хэш-индекса

Каждый хэш-индекс — это хэш-массив, состоящий из 8-байтных указателей адреса. Размер массива лучше всего определяется количеством уникальных значений индекса в нем, то есть количество уникальных значений Col2 будет хорошей отправной точкой для подсчета размера массива t1c2_index. Хэш-массив, слишком большой объем памяти. Слишком маленький хэш-массив снижает производительность, поскольку будет слишком много конфликтов индексных значений, которые хэшируются в один и тот же индекс.

В хэш-индексах скорость поиска совпадений очень высока:

SELECT * FROM t_hk  
   WHERE Col2 = 3;

Некластеризованные индексы будут быстрее при поиске диапазона, например:

SELECT * FROM t_hk  
   WHERE Col2 >= 3;

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

SELECT COUNT(DISTINCT [Col2])  
  FROM t_hk;

Если вы создаете новую таблицу, необходимо оценить размер массива или собрать данные из тестирования до развертывания.

Сведения о том, как хэш-индексы работают в оптимизированных для памяти таблицах OLTP, см. в разделе "Хэш-индексы".

Задание размера массива хэш-индекса

Размер хэш-массива задается (bucket_count= value) , где value — это целочисленное значение больше нуля. Если value это не мощность 2, фактический bucket_count округляется до следующей ближайшей власти 2. В нашем примере таблицы (bucket_count = 5000000), так как 5000 000 не является мощностью 2, фактическое число контейнеров округляет до 8 388 608 (2^23). Необходимо использовать это число, а не 5 000 000, при вычислении объема памяти, необходимого для хэш-массива.

Таким образом, в нашем примере для хэш-массива потребуется памяти:

8,388,608 * 8 = 2^23 * 8 = 2^23 * 2^3 = 2^26 = 67 108 864 или приблизительно 64 МБ.

Так как у нас есть три хэш-индекса, память, необходимая для хэш-индексов, составляет 3 * 64 МБ = 192 МБ.

Память для некластеризованных индексов

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

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

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

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

  • Память для конечных узлов
    Конечные узлы содержат по одной строке для каждого уникального ключа в таблице, и она указывает на строки данных с этим уникальным ключом. Если у вас есть несколько строк с одним ключом (т. е. у вас есть неукластеризованный индекс), в конечном узле индекса есть только одна строка, указывающая на одну из строк с другими строками, связанными друг с другом. Таким образом, общую память можно примерно вычислить следующим образом:

    • memoryForNonClusteredIndex = (pointerSize + sum(keyColumnDataTypeSizes)) * rowsWithUniqueKeys

Некластеризованные индексы лучше всего подходят для поиска по диапазону, как показано в следующем примере запроса:

SELECT * FROM t_hk  
   WHERE c2 > 5;  

Память для управления версиями строк

Чтобы избежать блокировок, модуль In-Memory OLTP использует оптимистический параллелизм при обновлении или удалении строк. Это означает, что при обновлении строки создается другая версия строки. Кроме того, операции удаления являются логическими — существующая строка помечается как удаленная, но не удаляется немедленно. Система хранит старые версии строк (включая удаленные строки), пока все транзакции, которые теоретически могут использовать одну из версий, не завершатся.

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

Количество дополнительных строк можно оценить путем вычисления максимального количества обновлений и удалений строк в секунду, а затем умножение на количество секунд, которое занимает самая длинная транзакция (минимум 1).

Затем это значение умножается на размер строки, что дает число байтов, необходимых для управления версиями строк.

rowVersions = durationOfLongestTransactionInSeconds * peakNumberOfRowUpdatesOrDeletesPerSecond

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

memoryForRowVersions = rowVersions * rowSize

Память для табличных переменных

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

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

Память под будущее увеличение

Указанные выше вычисления дают оценку потребного объема памяти для таблицы в том виде, в котором она существует в данный момент. В дополнение к этому объему памяти необходимо оценить увеличение размера таблицы и предоставить достаточно памяти для ее будущего роста. Например, если предполагается рост размера в 10 %, то следует умножить полученные ранее результаты на 1,1. Это и даст общую оценку объема памяти для таблицы.

См. также

Миграция в In-Memory OLTP