Должны ли мы денормализовать базу данных для повышения производительности? - PullRequest
11 голосов
/ 03 мая 2010

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

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

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-сервер и перестать предполагать, верно? Я обновлю свой пост, как только протестирую его.

[Update]

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

Ответы [ 10 ]

10 голосов
/ 03 мая 2010

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

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

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

РЕДАКТИРОВАТЬ: Это предполагает, конечно, что ваш анализ не нужно делать в режиме реального времени: только сбор данных. Довольно часто люди не нуждаются (а иногда даже предпочитают не иметь) обновления данных анализа в реальном времени. Это одна из тех вещей, которые звучат хорошо на бумаге, но на практике это не нужно.

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

4 голосов
/ 03 мая 2010

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

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

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

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

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

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

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

3 голосов
/ 03 мая 2010

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

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

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

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

2 голосов
/ 05 мая 2010

Рассматривали ли вы использование SqlBulkCopy? Это работает очень быстро. Я использовал его в производственной среде и достиг 10 000+ вставок в одну таблицу меньше, чем за секунду на машине с сервером SQL Server 2005. Вам просто нужно подготовить DataTable (s) для массовой вставки в ваше приложение. Вот образец.

        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)
    {            

    }
1 голос
/ 04 мая 2010

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

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

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

Согласованность данных

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

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

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

размер

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


Conclussions

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

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

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

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

1 голос
/ 04 мая 2010

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

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

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

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

1 голос
/ 04 мая 2010

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

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

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

Timur

1 голос
/ 04 мая 2010

«У нас есть требование хранить 500 измерений в секунду с нескольких устройств».

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

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

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

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

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

1 голос
/ 03 мая 2010

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

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

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

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

0 голосов
/ 03 мая 2010

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

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 , являются двумя примерами.

...