Лучший дизайн базы данных и таблицы для миллиардов строк данных [закрыто]

74

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

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

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

Что касается объема данных, которые необходимо сохранить, это приблизительное значение, но примерно так:
20 000+ местоположений, 720 записей в месяц (ежечасные измерения, приблизительно 720 часов в месяц), 120 месяцев (в течение 10 лет назад ) и много лет в будущее. Простые вычисления дают следующие результаты:

20 000 местоположений x 720 записей x 120 месяцев (10 лет назад) = 1 728 000 000 записей .

Это прошлые записи, новые записи будут импортироваться ежемесячно, то есть примерно 20 000 x 720 = 14 400 000 новых записей в месяц .

Общее количество мест будет неуклонно расти.

Над всеми этими данными должны быть выполнены следующие операции:

  1. Получить данные за определенную дату И период времени: все записи для определенного идентификатора местоположения в период с 01.01.2013 по 01.01.2017 и с 07:00 до 13:00.
  2. Простые математические операции для определенного диапазона даты и времени, например, MIN, MAX и AVG, температура и потребление электроэнергии для определенного идентификатора местоположения в течение 5 лет с 07:00 до 13:00.

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

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

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

Мои вопросы следующие:

  1. Должен ли я использовать базу данных NoSQL для таких больших объемов данных. Если нет, могу ли я придерживаться MySQL?
  2. Какую базу данных я должен использовать?
  3. Должен ли я хранить дату и время в отдельных, проиндексированных (если это возможно) столбцах, чтобы быстро извлекать и обрабатывать данные в течение определенных периодов времени и даты, или это можно сделать, храня метку времени в одном столбце?
  4. Подходит ли здесь подход к моделированию данных временных рядов, и если нет, то не могли бы вы дать мне подсказки для хорошего дизайна таблицы?

Спасибо.

Gecata
источник
29
2017. Хотя и не маленький, это не особо БОЛЬШОЙ объем данных для правильного оборудования. И мне неприятно вам это говорить, но пока то, что у вас есть, звучит как реляционные данные.
TomTom
6
Я сохранил многотабайтные таблицы с десятками миллиардов строк в MS SQL Server 2008-2014, используя хороший ключ (дату эпохи), сжатие, разбиение и гарантируя, что мои запросы / индексы выровнены по разделам. Мне пришлось перейти на NoSQL (Hadoop), когда я начал получать петабайты данных для анализа и индексации по-разному. У NoSQL должны быть другие соображения, и в этом случае он не подходит.
Али Разеги
3
@AliRazeghi Hadoop не имеет ничего общего с SQL или NoSQL - это просто механизм хранения. Существует множество интерфейсов SQL, поддерживаемых Hadoop.
mustaccio
3
Каковы ваши ограничения в отношении денег на программное обеспечение / лицензии?
user3067860
1
Когда у вас есть бесконечные деньги, я бы предложил купить устройство SAP HANA. Это отлично подходит для агрегации на больших наборах данных. Но у вас, вероятно, нет бесконечных денег.
Филипп

Ответы:

90

Это именно то, что я делаю каждый день, за исключением того, что вместо часовых данных я использую 5-минутные данные. Я загружаю около 200 миллионов записей каждый день, поэтому количество, о котором вы говорите, не является проблемой. 5-минутные данные имеют размер около 2 ТБ, и у меня есть данные о погоде за 50 лет на почасовом уровне в зависимости от местоположения. Итак, позвольте мне ответить на ваши вопросы, основываясь на моем опыте:

  1. Не используйте NoSQL для этого. Данные хорошо структурированы и идеально подходят для реляционной базы данных.
  2. Я лично использую SQL Server 2016, и у меня нет проблем с применением вычислений для этого объема данных. Первоначально он был на экземпляре PostgreSQL, когда я начал свою работу, и он не мог обрабатывать объем данных, как на небольшом экземпляре AWS.
  3. Я настоятельно рекомендую извлечь часовую часть даты и хранить ее отдельно от самой даты. Поверьте, учитесь на моих ошибках!
  4. Я храню большинство данных по списку (DATE, TIME, DATAPOINT_ID, VALUE), но это не то, как люди захотят интерпретировать данные. Будьте готовы к некоторым ужасным запросам к данным и огромным объемам разворота. Не бойтесь создавать ненормализованную таблицу для наборов результатов, которые слишком велики для вычисления на лету.

Общий совет: я храню большую часть данных между двумя базами данных, первая - это прямые данные временных рядов и нормализована. Моя вторая база данных очень ненормализована и содержит предварительно агрегированные данные. Как бы быстро ни работала моя система, я не закрываю глаза на тот факт, что пользователи даже не хотят ждать 30 секунд для загрузки отчета - даже если я лично считаю, что 30 секунд для обработки 2 ТБ данных чрезвычайно быстры.

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

  1. Способ, которым электрические данные представлены, является Часовым Окончанием- следовательно, 01:00 на самом деле является средним значением электроэнергии за предыдущий час, а 00:00 - это окончание часа 24. (Это важно, потому что вам действительно нужно искать две даты, чтобы включить значение 24 часа - день, когда вы ищем плюс первую отметку следующего дня.) Однако данные о погоде фактически представляются в прямом направлении (фактическое и прогноз на следующий час). По моему опыту с этими данными, потребители хотят проанализировать влияние погоды на цену / спрос на электроэнергию. Если бы вы использовали прямое сравнение дат, вы фактически сравнили бы среднюю цену за предыдущий час со средней температурой за следующий час, даже если отметки времени совпадают.DATETIME колонка.
  2. Представление. Я бы сказал, что, по крайней мере, 90% отчетов, которые я генерирую, представляют собой графики, которые обычно отображают цену в зависимости от часа либо для одной даты, либо для диапазона дат. Необходимость отделить время от даты может снизить скорость запроса, используемого для создания отчета, в зависимости от диапазона дат, который вы хотите увидеть. Потребители нередко хотят видеть одну дату в годовом исчислении за последние 30 лет (фактически для погоды это требуется для получения 30-летних нормалей) - это может быть медленным. Конечно, вы можете оптимизировать свой запрос и добавить индексы, и поверьте мне, у меня есть несколько безумных индексов, которых я бы не хотел, но это заставляет систему работать быстро.
  3. Производительность. Я ненавижу писать один и тот же кусок кода более одного раза. Раньше я сохранял дату и время в одном и том же столбце, пока мне не пришлось снова и снова писать один и тот же запрос, чтобы извлечь часть времени. Через некоторое время мне надоело это делать и я извлек его в свою колонку. Чем меньше кода вы должны написать, тем меньше вероятность ошибки. Кроме того, необходимость писать меньше кода означает, что вы можете получать свои отчеты быстрее, никто не хочет ждать отчетов весь день.
  4. Конечные пользователи. Не все конечные пользователи являются опытными пользователями (т.е. знают, как писать SQL). Хранение данных в формате, который они могут перенести в Excel (или другой подобный инструмент) с минимальными усилиями, сделает вас героем в офисе. Если пользователи не могут легко получить доступ к данным или манипулировать ими, они не будут использовать вашу систему. Поверьте мне, я разработал идеальную систему пару лет назад, и никто не использовал ее по этой причине. Проектирование базы данных - это не просто соблюдение заранее определенного набора правил / рекомендаций, а обеспечение работоспособности системы.

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

Mr.Brownstone
источник
Мне повезло, просто я использовал дату эпохи, но ваша рекомендация интересна для вашего случая использования. Спасибо, что поделился.
Али Разеги
4
Я не согласен со многими из этого. Ничто из этого не представляет реальной проблемы с современной базой данных, что демонстрируется здесь фактическими цифрами . Если пользователи данных слишком глупы, чтобы использовать sql, то вам нужно создать им интерфейс - вы не изменяете схему. Извлечение часа - плохая идея
Эван Кэрролл
1
Какое у вас оборудование?
Кенн
1
@kennes физический, 16 ядер, 256 ГБ ОЗУ, диск на 100 ГБ, локальный SSD на 500 ГБ с данными TempDB, гибридная сеть SAN с 8-ТБ кэш-памятью SSD и 40 ТБ дисков шпинделя, способных работать со скоростью 100 000 iops / sec. Реализация базы данных использует ColumnStore, сжатие, таблицы в памяти, секционирование и табличный экземпляр SSAS.
мистер Браунстоун
1
Это невероятное оборудование в зависимости от того, сколько пользователей вы обслуживаете. Поскольку это ответ псевдооптимизации, я думаю, что включение вашей технологии полезно. Я был в полном шоке, услышав, что вы можете сжать 2 ТБ за 30 секунд - это невероятно быстро. Помимо моего личного мнения, я думаю, что это будет полезно для будущих людей, которые хотят оптимизировать данные временных рядов!
Кенн
57

Индексы PostgreSQL и BRIN

Проверьте это сами. Это не проблема на 5-летнем ноутбуке с ssd.

EXPLAIN ANALYZE
CREATE TABLE electrothingy
AS
  SELECT
    x::int AS id,
    (x::int % 20000)::int AS locid,  -- fake location ids in the range of 1-20000
    now() AS tsin,                   -- static timestmap
    97.5::numeric(5,2) AS temp,      -- static temp
    x::int AS usage                  -- usage the same as id not sure what we want here.
  FROM generate_series(1,1728000000) -- for 1.7 billion rows
    AS gs(x);

                                                               QUERY PLAN                                                               
----------------------------------------------------------------------------------------------------------------------------------------
 Function Scan on generate_series gs  (cost=0.00..15.00 rows=1000 width=4) (actual time=173119.796..750391.668 rows=1728000000 loops=1)
 Planning time: 0.099 ms
 Execution time: 1343954.446 ms
(3 rows)

Итак, создание таблицы заняло 22 минуты. Во многом потому, что таблица скромная 97ГБ. Далее мы создаем индексы,

CREATE INDEX ON electrothingy USING brin (tsin);
CREATE INDEX ON electrothingy USING brin (id);    
VACUUM ANALYZE electrothingy;

Создание индексов также заняло много времени. Хотя, поскольку они BRIN, их всего 2-3 МБ, и они легко хранятся в оперативной памяти. Чтение 96 ГБ не происходит мгновенно, но это не проблема для моего ноутбука при вашей нагрузке.

Теперь мы запрашиваем это.

explain analyze
SELECT max(temp)
FROM electrothingy
WHERE id BETWEEN 1000000 AND 1001000;
                                                                 QUERY PLAN                                                                  
---------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=5245.22..5245.23 rows=1 width=7) (actual time=42.317..42.317 rows=1 loops=1)
   ->  Bitmap Heap Scan on electrothingy  (cost=1282.17..5242.73 rows=993 width=7) (actual time=40.619..42.158 rows=1001 loops=1)
         Recheck Cond: ((id >= 1000000) AND (id <= 1001000))
         Rows Removed by Index Recheck: 16407
         Heap Blocks: lossy=128
         ->  Bitmap Index Scan on electrothingy_id_idx  (cost=0.00..1281.93 rows=993 width=0) (actual time=39.769..39.769 rows=1280 loops=1)
               Index Cond: ((id >= 1000000) AND (id <= 1001000))
 Planning time: 0.238 ms
 Execution time: 42.373 ms
(9 rows)

Обновление с отметками времени

Здесь мы генерируем таблицу с разными временными метками, чтобы удовлетворить запрос на индексирование и поиск по столбцу временных меток, создание занимает немного больше времени, потому что to_timestamp(int)оно существенно медленнее, чем now()(которое кэшируется для транзакции)

EXPLAIN ANALYZE
CREATE TABLE electrothingy
AS
  SELECT
    x::int AS id,
    (x::int % 20000)::int AS locid,
    -- here we use to_timestamp rather than now(), we
    -- this calculates seconds since epoch using the gs(x) as the offset
    to_timestamp(x::int) AS tsin,
    97.5::numeric(5,2) AS temp,
    x::int AS usage
  FROM generate_series(1,1728000000)
    AS gs(x);

                                                               QUERY PLAN                                                                
-----------------------------------------------------------------------------------------------------------------------------------------
 Function Scan on generate_series gs  (cost=0.00..17.50 rows=1000 width=4) (actual time=176163.107..5891430.759 rows=1728000000 loops=1)
 Planning time: 0.607 ms
 Execution time: 7147449.908 ms
(3 rows)

Теперь мы можем вместо этого выполнить запрос к значению временной метки,

explain analyze
SELECT count(*), min(temp), max(temp)
FROM electrothingy WHERE tsin BETWEEN '1974-01-01' AND '1974-01-02';
                                                                        QUERY PLAN                                                                         
-----------------------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=296073.83..296073.84 rows=1 width=7) (actual time=83.243..83.243 rows=1 loops=1)
   ->  Bitmap Heap Scan on electrothingy  (cost=2460.86..295490.76 rows=77743 width=7) (actual time=41.466..59.442 rows=86401 loops=1)
         Recheck Cond: ((tsin >= '1974-01-01 00:00:00-06'::timestamp with time zone) AND (tsin <= '1974-01-02 00:00:00-06'::timestamp with time zone))
         Rows Removed by Index Recheck: 18047
         Heap Blocks: lossy=768
         ->  Bitmap Index Scan on electrothingy_tsin_idx  (cost=0.00..2441.43 rows=77743 width=0) (actual time=40.217..40.217 rows=7680 loops=1)
               Index Cond: ((tsin >= '1974-01-01 00:00:00-06'::timestamp with time zone) AND (tsin <= '1974-01-02 00:00:00-06'::timestamp with time zone))
 Planning time: 0.140 ms
 Execution time: 83.321 ms
(9 rows)

Результат:

 count |  min  |  max  
-------+-------+-------
 86401 | 97.50 | 97.50
(1 row)

Таким образом, за 83,321 мс мы можем объединить 86 401 запись в таблице с 1,7 млрд. Строк. Это должно быть разумно.

Часовое окончание

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

SELECT date_trunc('hour', tsin) + '1 hour' AS tsin,
  count(*),
  min(temp),
  max(temp)
FROM electrothingy
WHERE tsin >= '1974-01-01'
  AND tsin < '1974-01-02'
GROUP BY date_trunc('hour', tsin)
ORDER BY 1;
          tsin          | count |  min  |  max  
------------------------+-------+-------+-------
 1974-01-01 01:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 02:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 03:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 04:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 05:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 06:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 07:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 08:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 09:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 10:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 11:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 12:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 13:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 14:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 15:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 16:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 17:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 18:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 19:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 20:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 21:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 22:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 23:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-02 00:00:00-06 |  3600 | 97.50 | 97.50
(24 rows)

Time: 116.695 ms

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

Разметка

Другим важным моментом информации о PostgreSQL является то, что PG 10 обеспечивает разделение DDL . Так, например, вы можете легко создавать разделы для каждого года. Разбейте вашу скромную базу данных на второстепенные, крошечные. При этом вы должны иметь возможность использовать и поддерживать индексы btree, а не BRIN, что было бы еще быстрее.

CREATE TABLE electrothingy_y2016 PARTITION OF electrothingy
    FOR VALUES FROM ('2016-01-01') TO ('2017-01-01');

Или что угодно.

Эван Кэрролл
источник
13

Меня удивляет, что никто здесь не упомянул бенчмаркинг - пока @EvanCarroll не получил свой отличный вклад!

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

Мои собственные мысли:

Решения NoSQL могут работать очень хорошо для конкретных случаев использования, но часто негибки для специальных запросов. Для забавного взгляда на NoSQL Брайана Акера - бывшего главного архитектора MySQL, смотрите здесь !

Я согласен с @ Mr.Brownstone, что ваши данные в высшей степени подходят для реляционного решения (и это мнение подтвердил Эван Кэрролл )!

Если бы я взял на себя какие-либо расходы, это было бы с моей дисковой технологией! Я бы потратил любые деньги, которые были в моем распоряжении, на NAS или SAN или, возможно, на некоторые SSD-диски, чтобы хранить мои редко записанные совокупные данные!

Сначала я бы посмотрел на то, что у меня есть сейчас . Запустите несколько тестов и покажите результаты лицам, принимающим решения. У вас уже есть прокси в виде работы EC ! Но один или два быстрых теста на вашем оборудовании были бы более убедительными!

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

Моим личным первым портом захода для такого проекта был бы PostgreSQL. Это не значит, что я бы исключил частное решение, но законы физики и дисков одинаковы для всех! "Я могу поменять законы физики Джима" :-)

Verace
источник
6

Если вы еще этого не сделали, взгляните на СУБД временных рядов, поскольку она оптимизирована для хранения и запроса данных, где основное внимание уделяется типу даты / времени. Обычно базы данных временных рядов используются для записи данных в диапазонах минут / секунд / субсекунд, поэтому я не уверен, подходит ли он для часовых приращений. Тем не менее, этот тип СУБД, кажется, стоит изучить. В настоящее время InfluxDB является наиболее авторитетной и широко используемой базой данных временных рядов.

FloorDivision
источник
1
Что является примером СУБД временного ряда?
епископ
2
Посмотрите здесь .
Верас
4

Ясно, что это не проблема NoSQL, но я хотел бы предположить, что, хотя решение СУБД будет работать, я думаю, что подход OLAP подойдет намного лучше, и учитывая очень ограниченные диапазоны данных, я настоятельно рекомендую исследовать использование БД на основе столбцов. а не на основе строк. Подумайте об этом так: у вас может быть 1,7 миллиарда фрагментов данных, но вам по-прежнему нужно всего 5 бит, чтобы индексировать каждое возможное значение часа или дня месяца.

У меня есть опыт работы с аналогичной проблемной областью, в которой Sybase IQ (сейчас SAP IQ) используется для хранения до 300 миллионов счетчиков в час данных управления производительностью телекоммуникационного оборудования, но я сомневаюсь, что у вас есть бюджет для такого решения. На открытой арене MariaDB ColumnStore является очень многообещающим кандидатом, но я бы порекомендовал также изучить MonetDB.

Поскольку производительность запросов является для вас основным фактором, подумайте над тем, как будут формулироваться запросы. Именно здесь OLAP и RDBMS демонстрируют свои самые большие различия: - с OLAP вы нормализуете производительность запросов, а не уменьшаете повторение, уменьшаете объем хранилища или даже применяете согласованность. Поэтому в дополнение к исходной метке времени (вы не забыли захватить ее часовой пояс, я надеюсь?) Есть отдельное поле для метки времени UTC, другие для даты и времени, и еще больше для года, месяца, дня, часа, минуты и смещение UTC. Если у вас есть дополнительная информация о местоположениях, не стесняйтесь хранить ее в отдельной таблице местоположений, которую можно искать по требованию, и не стесняйтесь хранить ключ к этой таблице в основной записи, но сохраняйте полное имя местоположения в основной таблице как ну, в конце концов,

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

Пол Смит
источник
Вы могли бы также рассмотреть Greenplum как колонный магазин, если вы смотрите на них! В качестве «бонуса» - он основан на PostgreSQL!
Vérace
У меня был хороший опыт работы с HP Vertica. У нас была одна таблица с 9 столбцами, которая имела 130 миллиардов строк, без особых настроек. Это просто сработало.
ThatDataGuy