Огромные данные и производительность в SQL Server


20

Я написал приложение с бэкэндом SQL Server, которое собирает и хранит огромное количество записей. Я подсчитал, что на пике среднее количество записей составляет где-то на проспекте 3-4 миллиарда в день (20 часов работы).

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

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

Мое приложение будет получать данные, разбивать их на пакеты по ~ 100 тыс. Записей и массово вставлять в промежуточную таблицу. После ~ 100 тыс. Записей приложение на лету создаст другую промежуточную таблицу с той же схемой, что и раньше, и начнет вставлять в эту таблицу. Это создаст запись в таблице заданий с именем таблицы, содержащей 100 тыс. Записей, а хранимая процедура на стороне SQL Server переместит данные из промежуточной таблицы в готовую для клиента рабочую таблицу, а затем отбросит таблица временная таблица, созданная моим приложением.

Обе базы данных имеют одинаковый набор из 5 таблиц с одинаковой схемой, за исключением промежуточной базы данных, в которой есть таблица заданий. Промежуточная база данных не имеет ограничений целостности, ключа, индексов и т. Д. В таблице, в которой будет находиться основная часть записей. Ниже показано имя таблицы SignalValues_staging. Цель состояла в том, чтобы мое приложение как можно быстрее сбрасывало данные в SQL Server. Рабочий процесс создания таблиц на лету, чтобы их можно было легко перенести, работает довольно хорошо.

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

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

-- Signalvalues jobs table.
SELECT *
      ,ROW_NUMBER() OVER (ORDER BY JobId) AS 'RowIndex'
INTO #JobsToProcess
FROM 
(
    SELECT JobId 
           ,ProcessingComplete  
           ,SignalValueStagingTableName AS 'TableName'
           ,(DATEDIFF(SECOND, (SELECT last_user_update
                              FROM sys.dm_db_index_usage_stats
                              WHERE database_id = DB_ID(DB_NAME())
                                AND OBJECT_ID = OBJECT_ID(SignalValueStagingTableName))
                     ,GETUTCDATE())) SecondsSinceLastUpdate
    FROM SignalValueJobs
) cte
WHERE cte.ProcessingComplete = 1
   OR cte.SecondsSinceLastUpdate >= 120

DECLARE @i INT = (SELECT COUNT(*) FROM #JobsToProcess)

DECLARE @jobParam UNIQUEIDENTIFIER
DECLARE @currentTable NVARCHAR(128) 
DECLARE @processingParam BIT
DECLARE @sqlStatement NVARCHAR(2048)
DECLARE @paramDefinitions NVARCHAR(500) = N'@currentJob UNIQUEIDENTIFIER, @processingComplete BIT'
DECLARE @qualifiedTableName NVARCHAR(128)

WHILE @i > 0
BEGIN

    SELECT @jobParam = JobId, @currentTable = TableName, @processingParam = ProcessingComplete
    FROM #JobsToProcess 
    WHERE RowIndex = @i 

    SET @qualifiedTableName = '[Database_Staging].[dbo].['+@currentTable+']'

    SET @sqlStatement = N'

        --Signal values staging table.
        SELECT svs.* INTO #sValues
        FROM '+ @qualifiedTableName +' svs
        INNER JOIN SignalMetaData smd
            ON smd.SignalId = svs.SignalId  


        INSERT INTO SignalValues SELECT * FROM #sValues

        SELECT DISTINCT SignalId INTO #uniqueIdentifiers FROM #sValues

        DELETE c FROM '+ @qualifiedTableName +' c INNER JOIN #uniqueIdentifiers u ON c.SignalId = u.SignalId

        DROP TABLE #sValues
        DROP TABLE #uniqueIdentifiers

        IF NOT EXISTS (SELECT TOP 1 1 FROM '+ @qualifiedTableName +') --table is empty
        BEGIN
            -- processing is completed so drop the table and remvoe the entry
            IF @processingComplete = 1 
            BEGIN 
                DELETE FROM SignalValueJobs WHERE JobId = @currentJob

                IF '''+@currentTable+''' <> ''SignalValues_staging''
                BEGIN
                    DROP TABLE '+ @qualifiedTableName +'
                END
            END
        END 
    '

    EXEC sp_executesql @sqlStatement, @paramDefinitions, @currentJob = @jobParam, @processingComplete = @processingParam;

    SET @i = @i - 1
END

DROP TABLE #JobsToProcess

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

Эта хранимая процедура выполняется каждые 2 секунды, используя трюк, который я узнал из этого поста на dba.stackexchange.com .

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

Я попытался разделить таблицу, используя в [Timestamp]качестве столбца разделения, но безрезультатно. Любая форма индексации замедляет вставки настолько, что они не успевают за ней. Кроме того, мне нужно было бы создать тысячи разделов (по одному каждую минуту? Час?) За годы. Я не мог понять, как создать их на лету

Я попытался создать разбиение, добавив вычисляемый столбец в таблице под названием TimestampMinute, значение которого было, по INSERT, DATEPART(MINUTE, GETUTCDATE()). Все еще слишком медленно.

Я попытался сделать это оптимизированной для памяти таблицей в соответствии с этой статьей Microsoft . Может быть, я не понимаю, как это сделать, но MOT сделал вставки как-то медленнее.

Я проверил план выполнения хранимой процедуры и обнаружил, что (я думаю?) Самая интенсивная операция

SELECT svs.* INTO #sValues
FROM '+ @qualifiedTableName +' svs
INNER JOIN SignalMetaData smd
    ON smd.SignalId = svs.SignalId

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

С точки зрения регистрации времени, этот конкретный оператор выше выполняется за ~ 300 мс на 100 тыс. Записей.

Заявление

INSERT INTO SignalValues SELECT * FROM #sValues

выполняется за 2500-3000мс на 100к записей. Удаление из таблицы затронутых записей, за:

DELETE c FROM '+ @qualifiedTableName +' c INNER JOIN #uniqueIdentifiers u ON c.SignalId = u.SignalId

занимает еще 300 мс.

Как я могу сделать это быстрее? Может ли SQL Server обрабатывать миллиарды записей в день?

Если это уместно, это SQL Server 2014 Enterprise x64.

Конфигурация оборудования:

Я забыл включить оборудование в первый проход этого вопроса. Виноват.

Я предваряю это следующими утверждениями: я знаю, что теряю некоторую производительность из-за конфигурации моего оборудования. Я пробовал много раз, но из-за бюджета, C-Level, выравнивания планет и т. Д., К сожалению, я ничего не могу сделать, чтобы получить лучшую настройку. Сервер работает на виртуальной машине, и я даже не могу увеличить память, потому что у нас просто больше нет.

Вот моя системная информация:

Системная информация

Хранилище подключено к серверу VM через интерфейс iSCSI к блоку NAS (это приведет к снижению производительности). Блок NAS имеет 4 диска в конфигурации RAID 10. Это вращающиеся дисководы WD WD4000FYYZ объемом 4 ТБ с интерфейсом SATA 6 ГБ / с. На сервере настроено только одно хранилище данных, поэтому база данных tempdb и моя база данных находятся в одном хранилище данных.

Макс DOP равен нулю. Должен ли я изменить это на постоянное значение или просто позволить SQL Server обрабатывать это? Я прочитал о RCSI: правильно ли я считаю, что единственная выгода от RCSI - это обновление строк? Там никогда не будет обновлений для любой из этих конкретных записей, они будут INSERTредактироваться и SELECTредактироваться. RCSI все еще принесет пользу мне?

У меня tempdb 8мб. Основываясь на ответе ниже от jyao, я изменил #sValues ​​на обычную таблицу, чтобы вообще избежать tempdb. Производительность была примерно такой же, хотя. Я постараюсь увеличить размер и рост базы данных tempdb, но, учитывая, что размер #sValues ​​будет более или менее всегда одинаковым, я не ожидаю большого выигрыша.

Я взял план выполнения, который я приложил ниже. Этот план выполнения представляет собой одну итерацию промежуточной таблицы - 100 тыс. Записей. Выполнение запроса было довольно быстрым, около 2 секунд, но имейте в виду, что в нем нет индексов для SignalValuesтаблицы, а в SignalValuesтаблице, являющейся целью INSERT, в ней нет записей.

План выполнения


3
Вы уже экспериментировали с отсроченной прочностью?
Мартин Смит

2
Какие индексы были на месте с медленными производственными вставками?
Папараццо

Пока что я не думаю, что здесь достаточно данных, чтобы выяснить, что на самом деле отнимает столько времени. Это процессор? Это IO? Поскольку вы, кажется, получаете 30 тыс. Строк в секунду, это не выглядит как IO для меня. Правильно ли я понимаю, что вы достаточно близки к достижению цели? Вам нужно 50 тыс. Строк в секунду, поэтому достаточно одного 100-килобайтного пакета каждые 2 секунды. Сейчас кажется, что одна партия занимает 3 секунды. Разместите фактический план выполнения одного репрезентативного прогона. Любое предложение, которое не затрагивает самые трудоемкие операции, является спорным.
USR

Я опубликовал план выполнения.
Брэндон

Ответы:


7

Я подсчитал, что на пике среднее количество записей составляет где-то на проспекте 3-4 миллиарда в день (20 часов работы).

На скриншоте у вас ТОЛЬКО ОЗУ объемом 8 ГБ и 6 ГБ выделено для SQL Server. Это слишком низко для того, чего вы пытаетесь достичь.

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

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

Также обратитесь к руководству по производительности загрузки данных - оно описывает умные способы эффективной загрузки данных.

У меня tempdb 8мб.

Основываясь на ваших изменениях ... вы должны иметь разумную базу данных tempdb - предпочтительно несколько файлов данных tempdb одинакового размера с шириной экземпляра с поддержкой TF 1117 и 1118.

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

Настоятельно рекомендую

  1. Увеличьте характеристики вашего сервера.

  2. Попросите специалиста * проверить работоспособность вашего экземпляра сервера базы данных и следуйте рекомендациям.

  3. Один раз. и б. готово, затем погрузитесь в настройку запросов и другие оптимизации, такие как просмотр статистики ожидания, планов запросов и т. д.

Примечание: я профессиональный эксперт по SQL Server на hackhands.com - многопрофильной компании, но ни в коем случае не предлагаю вам нанять меня за помощью. Я просто предлагаю вам воспользоваться профессиональной помощью только на основании ваших правок.

НТН.


Я пытаюсь собрать предложение (читай: просить) больше оборудования для этого. Имея это в виду и ваш ответ здесь, нет ничего другого с точки зрения конфигурации SQL Server или оптимизации запросов, что бы вы предложили сделать это быстрее?
Брэндон

1

Общий совет для таких проблем с большими данными, когда лицом к стене ничего не работает:

Одно яйцо готовится около 5 минут. 10 яиц будут приготовлены одновременно, если будет достаточно электричества и воды.

Или, другими словами:

Во-первых, посмотрите на оборудование; во-вторых, отделите логику процесса (ремоделирование данных) и сделайте это параллельно.

Вполне возможно создать собственное вертикальное разбиение динамически и автоматически для каждого количества таблиц и для каждого размера таблицы; Если у меня есть Quarter_1_2017, Quarter_2_2017, Quarter_3_2017, Quarter_4_2017, Quarter_1_2018 ... и я не знаю, где находятся мои записи и сколько у меня разделов, запустите один и тот же запрос для всех пользовательских разделов в одно и то же время, отдельных сеансов и сборок результат будет обработан для моей логики.


Кажется, что проблема ОП заключается в обработке вставки и доступе к вновь введенным данным, а не в обработке данных за недели или месяцы назад. ОП упоминает разбиение данных по минутам на его отметке времени (таким образом, 60 разбиений, разбивая текущие данные на отдельные сегменты); разделение на четверть вряд ли поможет. Ваша точка зрения в целом хорошо принята, но вряд ли поможет кому-то в этой конкретной ситуации.
RDFozz

-1

Я сделаю следующую проверку / оптимизацию:

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

  2. Не используй

    select * into [dest table] from [source table];

    но вместо этого предварительно определите [таблица таблиц]. Кроме того, вместо удаления таблицы [dest] и ее воссоздания, я урежу таблицу. Таким образом, при необходимости, вместо использования временной таблицы, я бы использовал обычную таблицу. (Я также могу создать индекс для таблицы [dest], чтобы повысить производительность запроса на соединение)

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

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

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

Удачи и люблю ваш вопрос.


Спасибо за ответ. Я установил размер базы данных на 1 ГБ и увеличился на 1 ГБ, ожидая, что операции по расширению займут некоторое время, что изначально помогло с быстродействием. Я постараюсь реализовать предварительный рост сегодня. Я реализовал таблицу [dest] как обычную таблицу, но не увидел большого прироста производительности. У меня не было много времени в последние несколько дней, но я попытаюсь добраться до других сегодня.
Брэндон
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.