Должны ли мы денормализовать базу данных для повышения производительности?

У нас есть требование хранить 500 измерений в секунду, поступающих с нескольких устройств. Каждое измерение состоит из метки времени, типа количества и нескольких векторных значений. Прямо сейчас на измерение 8 значений вектора, и мы можем считать это число постоянным для потребностей нашего прототипа. Мы используем HNibernate. Тесты выполняются в SQLite (файл диска db, а не в памяти), но производство, вероятно, будет MsSQL.

Наш класс объектов измерения - это тот, который содержит одно измерение и выглядит следующим образом:

public class Measurement
{
    public virtual Guid Id { get; private set; }
    public virtual Device Device { get; private set; }
    public virtual Timestamp Timestamp { get; private set; }
    public virtual IList<VectorValue> Vectors { get; private set; }
}

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

Мы сделали несколько вещей, чтобы гарантировать, что сгенерированный SQL (разумно) эффективный: мы используем Guid.Comb для генерации идентификаторов, мы собираем около 500 элементов в одной транзакции, размер партии ADO.Net установлен на 100 (я думаю, что SQLIte не поддерживает пакетные обновления, но может быть полезно позже).

Проблема

Прямо сейчас мы можем вставить 150-200 измерений в секунду (что не достаточно быстро, хотя это SQLite, о котором мы говорим). Посмотрев на сгенерированный SQL, мы видим, что в одной транзакции мы вставляем (как и ожидалось):

  • 1 временная метка
  • 1 измерение
  • 8 векторных значений

что означает, что мы на самом деле делаем 10-кратное число вставных таблиц: 1500-2000 в секунду.

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

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

[изменить]

С встроенным SQLite я получаю около 350 наименований/сек (3500 одиночных табличных вставок), которые, я считаю, примерно так же хороши, как и у NHibernate (взяв этот пост для справки: http://ayende.com/Blog/archive/2009/08/22/nhibernate-perf-tricks.aspx).

Но я мог бы переключиться на SQL-сервер и перестать принимать на себя все, не так ли? Я обновляю свой пост, как только проверю его.

[Обновление]

Я перешел на SQL-сервер и сплющил свою иерархию, я протестировал его, сохранив 3000 измерений/сек в течение нескольких часов и, похоже, работает нормально.

Ответ 1

Лично я бы сказал, для этого: denormalize, а затем создайте процесс ETL, чтобы привести эти данные в более нормализованный формат для анализа/регулярного использования.

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

Это не означает, что вам нужно отбросить сущности, созданные вами в вашей текущей структуре базы данных: просто вы должны также создать эти денормализованные таблицы и сделать ETL для их ввода. Вы можете использовать SSIS ( хотя он по-прежнему довольно глючный и раздражительный) периодически переносить данные в ваш нормализованный набор таблиц или даже приложение С# или другой процесс массовой загрузки.

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

Если некоторые люди, которые анализируют эти данные, требуют доступа в режиме реального времени, вы можете при необходимости создать набор инструментов против "голых металлических" денормализованных транзакционных данных: но довольно часто, когда вы действительно разбираетесь в требованиях, людям, выполняющим анализ, не нужно подлинного реального времени (и в некоторых случаях они предпочли бы иметь более статический набор данных для работы!): и в этом случае периодический ETL будет работать достаточно хорошо. Вам просто нужно собраться с целевыми пользователями и узнать, что им действительно нужно.

Ответ 2

Ну, это будет зависеть. Являются ли 8 векторных значений жестким и быстрым числом, которое никогда не изменится? Тогда денормализация в вашем случае может иметь смысл (но будет проверяться только тестирование на реальном оборудовании и базе данных, которые вы используете). Если на следующей неделе это может быть 9 измерений, не делайте этого.

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

Как только вы включили профайлер прогона. Вполне возможно, что nHibernate не создает лучшую SQl для вашей вставки.

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

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

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

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

Ответ 3

Сначала перейдите в целевую базу данных; производительность на основе SqlLite может не указывать на производительность на основе MsSql

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

Затем денормализовать, если необходимо, с процессом ETL, как было предложено выше.

Обработка потока событий имеет поговорку: "Если вы попали на диск, вы мертвы".; -)

Ответ 4

Считаете ли вы использование SqlBulkCopy? Он работает очень быстро. Я использовал его в производственной среде и добился 10.000+ вставок на одной таблице менее секунды с помощью сервера sql server 2005. Вам просто нужно подготовить DataTable (ы), чтобы они были добавлены в вашем приложении. Вот пример.

        public static void SQLBulkCopyInsert(DataTable dtInsertRows, string destinationTableName, string[] columnMappings)
    {
        using (SqlBulkCopy sbc = new SqlBulkCopy(DBHelper.Secim2009DB.ConnectionString, SqlBulkCopyOptions.UseInternalTransaction))
        {                
            sbc.DestinationTableName = destinationTableName;
            // Number of records to be processed in one go
            sbc.BatchSize = 30000;
            // Map the Source Column from DataTabel to the Destination Columns in SQL Server 2005 Person Table

            foreach (string columnMapping in columnMappings)
            {
                sbc.ColumnMappings.Add(columnMapping, columnMapping);
            }

            // Number of records after which client has to be notified about its status
            sbc.NotifyAfter = dtInsertRows.Rows.Count;
            // Event that gets fired when NotifyAfter number of records are processed.
            sbc.SqlRowsCopied += new SqlRowsCopiedEventHandler(sbc_SqlRowsCopied);
            // Finally write to server
            sbc.WriteToServer(dtInsertRows);
            sbc.Close();
        }
    }

    public static void sbc_SqlRowsCopied(object sender, SqlRowsCopiedEventArgs e)
    {            

    }

Ответ 5

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

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

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

Но не верьте мне на слово. Эксперимент. Анализ. Учить. Проспер.

Ответ 6

"У нас есть требование хранить 500 измерений в секунду, поступающих с нескольких устройств".

Не используйте СУБД для хранения таких данных.

Каковы причины, по которым люди используют СУБД?

(a) Они могут применять ограничения для вас на данных, которые вы пытаетесь зарегистрировать. Но у вас их нет. Данные измерений - это то, что они есть, и их необходимо принять. Нет ограничений.

(b) Они могут обеспечить согласованность и целостность ваших ценных бизнес-данных в случае (1) нарушений ограничений и (2) серьезных сбоев системы, таких как ошибки ввода-вывода на диске. Но поскольку у вас нет ограничений, (1) не применяется. Что же касается (2), что бы вы сделали с вашими измерениями, если ошибка ввода-вывода диска мешает ему записываться? Ваши измерения теряются независимо от того, что.

Итак, у вас нет причин, чтобы так использовать DBMS. Сбросьте нагрузку измерений в плоском файле и обработайте его по мере необходимости.

Ответ 7

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

Отличный ресурс для высокопроизводительной обработки (например, то, что вы пытаетесь сделать) находится в http://highscalability.com/

Одно из тематических исследований, которые они провели, - это хранить тысячи данных в базе данных. Решением было множество баз данных MYSQL и маршрутизация запроса на основе идентификатора устройства. В целом - сайт может обеспечить отличные тематические исследования. Может быть, вы можете найти там возможное решение.

TIMUR

Ответ 8

Используйте правильную СУБД и аппаратное обеспечение. Тестирование на другой платформе с различным оборудованием ничего не скажет о производительности.

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

Цифры, которые вы указали, не являются исключительными для потоковой передачи данных и вполне достижимы с использованием правильного оборудования, но я думаю, что nHibernate станет основным ограничивающим фактором для вас. Я думаю, что маловероятно, что nHib является разумным выбором для такого рода вещей.

Рассматривали ли вы использование некоторых технологий, которые обеспечивают особую поддержку для потоковых источников данных и CEP? Например: OSISoft PI, функция потокового потока Microsoft StreamInsight и SQL Server.

Ответ 9

Вы должны спросить себя: "Почему мы нормализуемся?"

Существуют три основные причины:

  • Согласование данных
  • Скорость обновления
  • Размер

Последовательность данных

Приятно иметь выпадающие списки и все строки, которые означают одно и то же, имея тот же FK, не так ли? Довольно очевидно. Это действительно важно для БД с несколькими "редакторами" данных. Но это так же хорошо, как и наши процессы. Позвольте сказать, что это база данных Flight, и есть запись для Национального аэропорта в Вашингтоне, округ Колумбия... и некоторые добавляют новую запись для Национального аэропорта Рейгана в Вашингтоне, округ Колумбия... FK будет там и будет использоваться в детском столе, но выиграл "Стоит много... Но это все еще хорошо сделать...

Скорость обновления

Что мы должны были сделать, это обновить строку для Национального аэропорта с новым именем. Поскольку существует только одна родительская строка, это делает очень простое изменение. Если бы в моей таблице полетов был текст, я бы обновил миллионы строк.

Размер

Если бы я сохранил "Национальный аэропорт Рейгана" на каждой записи, это заняло бы больше места, чем, скажем, FK. 19. Раньше был действительно большой сделкой, но SAN делает это довольно неуместным.


Conclussions

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

Хорошо, так Сколько раз вы думаете, что вы измените имя инструмента или точки данных? Я имею в виду, что растворенный O2 растворен O2, мутность - это мутность, правильно? Но если вам нужно сделать массовое обновление, я готов поспорить, что у вас будет время простоя между прогонами, чтобы сделать это. Так что это не проблема.

Хорошо, такой размер, конечно... что много измерений; но не делайте измерения "растворенный кислород", DO2 в порядке... насколько это больше, чем у некоторых FK, таких как "7". Проведите пространство, чтобы сэкономить время.

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

Ответ 10

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

public class Measurement 
{ 
    public Guid ID { get; private set; } 
    public Device Device { get; private set; }
    public Sample[] { get; private set; }

    public DateTime FirstTimestamp { get; private set; } 
    public DateTime LastTimestamp { get; private set; } 
} 

public class Sample
{ 
    public DateTime Timestamp { get; private set; } 
    public VectorValue[] Vectors { get; private set; } 
}

Существуют различные способы хранения сложных типов (например, списка списков в этом случае) в одной записи. столбцы XML и пользовательские типы CLR, являются два примера.