Эффективная схема хранения для миллионов значений различных типов - PullRequest
0 голосов
/ 11 февраля 2019

Я собираюсь создать базу данных SQL, которая будет содержать результаты статистических вычислений для сотен тысяч объектов.Планируется использовать Postgres, но вопрос в равной степени относится и к MySQL.

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

  • call_duration: в секундах (с плавающей запятой)
  • setup_time: в секундах (с плавающей запятой)
  • dropouts: периоды, в которые было обнаружено пропадание звука (массив), например [5.23, 40.92]
  • hung_up_unexpectedly: истина или ложь (логическое значение)

Это просто простые примеры;на самом деле статистика более сложная.С каждой статистикой связан номер версии.

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

Вариант 1 - длинный формат в одном столбце

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

  • statistic_id (PK)
  • phone_call_id (FK)
  • statistic_name (строка)
  • statistic_value (текст, сериализация)
  • statistic_version (целое число)
  • created_at (дата / время)

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

Но мне кажется, что (де) сериализация значений делает ее довольно неэффективной с точки зрения обработки большого количества данных.Кроме того, я не могу выполнять вычисления на уровне SQL;Я всегда должен загружать и десериализовать данные.Или поддержка JSON в Postgres настолько хороша, что я все еще могу выбрать этот шаблон?

Вариант 2 - статистика как атрибуты основного объекта

Я мог бы также подумать о сборе всех типов статистических имени добавление их в качестве новых столбцов к объекту телефонного вызова, например:

  • id (PK)
  • call_duration
  • setup_time
  • dropouts
  • hung_up_unexpectedly
  • ...

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

Вариант 3 - статистика в виде разных столбцов

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

  • statistic_id (PK)
  • phone_call_id (FK)
  • statistic_name (строка)
  • statistic_value_bool (логическое значение)
  • statistic_value_string (строка)
  • statistic_value_float (число с плавающей запятой)
  • statistic_value_complex (сериализованный или сложный тип данных)
  • statistic_value_type (строка, указывающая bool, string и т. Д.)
  • statistic_version (целое число)
  • created_at (datetime)

Это будет означать, что таблица будет очень разреженной, так как будет заполнен только один из столбцов statistic_value_.Может ли это привести к проблемам с производительностью?

Вариант 4 - нормализованная форма

Пытаясь нормализовать вариант 3, я бы создал две таблицы:

  • statistics
    • id (PK)
    • version
    • created_at
  • statistic_mapping
    • phone_call_id (ФК)
    • statistic_id (ФК)
  • statistic_type_mapping
    • statistic_id (ФК)
    • type (строка, указывает bool, string и т. Д.)
  • statistic_values_boolean
    • statistic_id (FK)
    • value (bool)

Но это никуда не денется, так как я не могу динамически присоединиться к другому имени таблицы, не так ли?Или я должен в любом случае просто присоединиться ко всем statistic_values_* таблицам на основе статистического идентификатора?Мое приложение должно было бы убедиться, что дублирующих записей не существует.

Подводя итог, учитывая данный вариант использования, какой будет наиболее эффективный подход для хранения миллионов статистических значений в реляционной БД (например, Postgres),когда требуется, чтобы типы статистики можно было добавлять или изменять, и чтобы одновременно существовало несколько версий, и чтобы запрос значений был несколько эффективным?

1 Ответ

0 голосов
/ 26 февраля 2019

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

Словарь типов статистики

Очень простая таблица - просто имя и описание статистики.тип:

create table stat_types (
  type        text not null constraint stat_types_pkey primary key,
  description text  
);

(Вы можете заменить его на enum, если у вас конечное число элементов)

Таблица статистики для каждого типа объектов в проекте

Этосодержит FK для объекта, FK для стат.введите (или просто перечислите) и, что важно, поле jsonb с произвольным значением .Данные относятся к его типу.Например, такая таблица для телефонных звонков :

create table phone_calls_statistics ( 
  phone_call_id uuid  not null references phone_calls,
  stat_type     text  not null references stat_types,
  data          jsonb,
  constraint phone_calls_statistics_pkey primary key (phone_call_id, stat_type)  
);

Здесь я предполагаю, что таблица phone_calls имеет тип ПК uuid:

create table phone_calls (
  id uuid not null constraint phone_calls_pkey primary key
-- ...
);

Поле data имеет другую структуру, которая зависит от его стат.тип.Пример для продолжительности вызова :

{
   "call_duration": 120.0
}

или для выпадений :

{
   "dropouts": [5.23, 40.92]
}

Давайте поиграем с данными:

insert into phone_calls_statistics values 
  ('9fc1f6c3-a9d3-4828-93ee-cf5045e93c4c', 'CALL_DURATION', '{"call_duration": 100.0}'),
  ('86d1a2a6-f477-4ed6-a031-b82584b1bc7e', 'CALL_DURATION', '{"call_duration": 110.0}'),
  ('cfd4b301-bdb9-4cfd-95db-3844e4c0625c', 'CALL_DURATION', '{"call_duration": 120.0}'),
  ('39465c2f-2321-499e-a156-c56a3363206a', 'CALL_DURATION', '{"call_duration": 130.0}'),
  ('9fc1f6c3-a9d3-4828-93ee-cf5045e93c4c', 'UNEXPECTED_HANGUP', '{"unexpected_hungup": true}'),
  ('86d1a2a6-f477-4ed6-a031-b82584b1bc7e', 'UNEXPECTED_HANGUP', '{"unexpected_hungup": true}'),
  ('cfd4b301-bdb9-4cfd-95db-3844e4c0625c', 'UNEXPECTED_HANGUP', '{"unexpected_hungup": false}'),
  ('39465c2f-2321-499e-a156-c56a3363206a', 'UNEXPECTED_HANGUP', '{"unexpected_hungup": false}');

Получите среднее, минимальное и максимальное время разговора:

select 
  avg((pcs.data ->> 'call_duration')::float) as avg,
  min((pcs.data ->> 'call_duration')::float) as min,
  max((pcs.data ->> 'call_duration')::float) as max
from 
  phone_calls_statistics pcs 
where 
  pcs.stat_type = 'CALL_DURATION';

Получите количество неожиданных зависаний:

select 
  sum(case when (pcs.data ->> 'unexpected_hungup')::boolean is true then 1 else 0 end) as hungups  
from 
  phone_calls_statistics pcs 
where 
  pcs.stat_type = 'UNEXPECTED_HANGUP'; 

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

Живой пример: https://www.db -fiddle.com / f / auATgkRKrAuN3jHjeYzfux / 0

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...