Вопрос

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

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

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

Существует ли способ или наилучшая практика непрерывного постепенного суммирования данных (по мере сбора данных) в требуемом разрешении?

Или есть лучший подход к такого рода проблемам?

PS.Что я нашел до сих пор, так это инструменты ETL, такие как Talend, которые могли бы облегчить жизнь.

Обновить:На данный момент я использую MySQL, но мне интересно узнать о лучших практиках независимо от базы данных, среды и т.д.

Это было полезно?

Решение

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

Большинство коммерческих и некоторые платформы СУБД с открытым исходным кодом (напримерPostgreSQL) может поддерживать секционированные таблицы, которые можно использовать для выполнения такого рода задач тем или иным способом.То, как вы заполняете базу данных из своих журналов, оставлено в качестве упражнения для читателя.

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

  • У вас есть таблица, разделенная на части по некоторому виду даты или значения дата-время, разделенная по часам, дням или чему угодно еще grain кажется подходящим.Журнал записи добавляются в эту таблицу.

  • Когда временное окно соскальзывает с раздела, периодическое задание индексирует или суммирует его и преобразует в его "замороженное" состояние.Например, задание в Oracle может создать bitmap индексы в этом разделе или обновить материализованное представление, включив в него сводку данные для этого раздела.

  • Позже вы можете удалить старые данные, обобщить их или объединить разделы вместе.

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

Точный характер этого процесса варьируется в зависимости от платформы СУБД.

Например, разбиение таблиц на SQL Server не так уж хорошо, но это можно сделать с помощью служб Analysis Services (OLAP-сервер, который Microsoft объединяет с SQL Server).Это делается путем настройки начального раздела как чистого ROLAP (OLAP-сервер просто отправляет запрос к базовой базе данных), а затем перестроения конечных разделов как MOLAP (OLAP-сервер создает свои собственные специализированные структуры данных, включая постоянные сводки, известные как "агрегации").Службы Analysis Services могут сделать это полностью прозрачно для пользователя.Он может перестроить раздел в фоновом режиме, в то время как старый раздел ROLAP все еще виден пользователю.Как только сборка завершена, она меняется местами в разделе;куб доступен пользователю все время без прерывания обслуживания.

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

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

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

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

  • Напишите скрипт, используя ваш любимый язык программирования, который считывает данные, анализирует соответствующие биты и вставляет их в базу данных.Это может выполняться довольно часто, но у вас должен быть какой-то способ отслеживать, где вы находитесь в файле.Будьте осторожны с запиранием, особенно на окнах.Семантика блокировки файлов по умолчанию в Unix / Linux позволяет вам сделать это (вот как tail -f работает), но поведение по умолчанию в Windows отличается;обе системы должны были бы быть написаны так, чтобы они хорошо сочетались друг с другом.

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

  • Напишите интерфейс ведения журнала для вашего приложения, которое напрямую заполняет базу данных, а не записывает файлы журнала.

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

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

  • Одним из вариантов было бы изменить регистратор, чтобы он начинал запись в другой файл каждый период (скажем, каждые несколько минут).Периодически запускайте программу чтения журналов и загружайте новые файлы, которые она еще не обработала.Прочтите старые файлы.Чтобы это сработало, схема именования файлов должна основываться на времени, чтобы читатель знал, какой файл выбрать.Работа с файлами, которые все еще используются приложением, более сложна (затем вам нужно будет отслеживать, сколько было прочитано), поэтому вы хотели бы читать файлы только до последнего периода.

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

Другие советы

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

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

Он широко используется для сбора статистики в таких системах, как Ганглии и Кактусы.

Когда дело доходит до нарезки и агрегирования данных (по времени или чему-то еще), звездообразная схема (Kimball star) является довольно простым, но мощным решением.Предположим, что для каждого клика мы сохраняем время (с точностью до второго разрешения), информацию о пользователе, идентификатор кнопки и местоположение пользователя.Чтобы упростить нарезку, я начну с предварительно загруженных таблиц поиска свойств объектов, которые редко меняются, - так называемых таблиц измерений в мире DW.

pagevisit2_model_02

В dimDate таблица содержит одну строку для каждого дня с количеством атрибутов (полей), описывающих конкретный день.Таблица может быть предварительно загружена на годы вперед и должна обновляться один раз в день, если она содержит такие поля, как DaysAgo, WeeksAgo, MonthsAgo, YearsAgo;в противном случае это может быть “загрузи и забудь”.В dimDate позволяет легко нарезать атрибуты для каждой даты, такие как

WHERE [YEAR] = 2009 AND DayOfWeek = 'Sunday'

За десять лет данных в таблице всего ~ 3650 строк.

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

WHERE Continent = 'South America'

После загрузки он редко меняется.

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

WHERE PageURL = 'http://…/somepage.php'

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

Для записи нажатий на кнопки я добавлю factClick таблица.

pagevisit2_model_01

В factClick таблица содержит одну строку для каждого нажатия кнопки конкретным пользователем в определенный момент времени.Я использовал TimeStamp (вторая резолюция), ButtonKey и UserKey в составном первичном ключе для отфильтровывания кликов быстрее, чем один в секунду от конкретного пользователя.Обратите внимание на Hour поле, оно содержит часовую часть TimeStamp, целое число в диапазоне 0-23, позволяющее легко нарезать в час, например

WHERE [HOUR] BETWEEN 7 AND 9

Итак, теперь мы должны рассмотреть:

  • Как загрузить таблицу?Периодически - возможно, каждый час или каждые несколько минут - из блога с помощью инструмента ETL или решения с низкой задержкой, использующего какой-либо процесс потоковой передачи событий.
  • Как долго хранить информацию в таблице?

Независимо от того, хранится ли в таблице информация только за день или за несколько лет - она должна быть разделена; Обеспокоенный Софтбриджев объяснил разделение в своем ответе, поэтому я пропущу это здесь.

Теперь несколько примеров нарезки в соответствии с различными атрибутами (включая день и час)

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

/* To simplify queries flatten the model */ 
CREATE VIEW vClicks 
AS 
SELECT * 
FROM factClick AS f 
JOIN dimDate AS d ON d.DateKey = f.DateKey 
JOIN dimButton AS b ON b.ButtonKey = f.ButtonKey 
JOIN dimUser AS u ON u.UserKey = f.UserKey 
JOIN dimGeography AS g ON g.GeographyKey = f.GeographyKey

Пример запроса

/* 
Count number of times specific users clicked any button  
today between 7 and 9 AM (7:00 - 9:59)
*/ 
SELECT  [Email] 
       ,COUNT(*) AS [Counter] 
FROM    vClicks 
WHERE   [DaysAgo] = 0 
        AND [Hour] BETWEEN 7 AND 9 
        AND [Email] IN ('dude45@somemail.com', 'bob46@bobmail.com') 
GROUP BY [Email] 
ORDER BY [Email]

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

/* 
Because dimUser can be large table it is good 
to have a view without it, to speed-up queries 
when user info is not required 
*/ 
CREATE VIEW vClicksNoUsr 
AS 
SELECT * 
FROM factClick AS f 
JOIN dimDate AS d ON d.DateKey = f.DateKey 
JOIN dimButton AS b ON b.ButtonKey = f.ButtonKey 
JOIN dimGeography AS g ON g.GeographyKey = f.GeographyKey

Пример запроса

/* 
Count number of times a button was clicked on a specific page 
today and yesterday, for each hour. 
*/ 
SELECT  [FullDate] 
       ,[Hour] 
       ,COUNT(*) AS [Counter] 
FROM    vClicksNoUsr 
WHERE   [DaysAgo] IN ( 0, 1 ) 
        AND PageURL = 'http://...MyPage' 
GROUP BY [FullDate], [Hour] 
ORDER BY [FullDate] DESC, [Hour] DESC



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

pagevisit2_model_03

В factClickAgg таблица может загружаться ежечасно или даже в конце каждого дня - в зависимости от требований к отчетности и аналитике.Например, предположим, что таблица загружается в конце каждого дня (после полуночи), я могу использовать что-то вроде:

/* At the end of each day (after midnight) aggregate data. */ 
INSERT  INTO factClickAgg 
        SELECT  DateKey 
               ,[Hour] 
               ,ButtonKey 
               ,GeographyKey 
               ,COUNT(*) AS [ClickCount] 
        FROM    vClicksNoUsr 
        WHERE   [DaysAgo] = 1 
        GROUP BY DateKey 
               ,[Hour] 
               ,ButtonKey 
               ,GeographyKey

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

/* To simplify queries for aggregated data */ 
CREATE VIEW vClicksAggregate 
AS 
SELECT * 
FROM factClickAgg AS f 
JOIN dimDate AS d ON d.DateKey = f.DateKey 
JOIN dimButton AS b ON b.ButtonKey = f.ButtonKey 
JOIN dimGeography AS g ON g.GeographyKey = f.GeographyKey

Теперь я могу запрашивать агрегированные данные, например, по дням :

/* 
Number of times a specific buttons was clicked 
in year 2009, by day 
*/ 
SELECT  FullDate 
       ,SUM(ClickCount) AS [Counter] 
FROM    vClicksAggregate 
WHERE   ButtonName = 'MyBtn_1' 
        AND [Year] = 2009 
GROUP BY FullDate 
ORDER BY FullDate

Или с еще несколькими вариантами

/* 
Number of times specific buttons were clicked 
in year 2008, on Saturdays, between 9:00 and 11:59 AM 
by users from Africa 
*/ 

SELECT  SUM(ClickCount) AS [Counter] 
FROM    vClicksAggregate 
WHERE   [Year] = 2008 
        AND [DayOfWeek] = 'Saturday' 
        AND [Hour] BETWEEN 9 AND 11 
        AND Continent = 'Africa' 
        AND ButtonName IN ( 'MyBtn_1', 'MyBtn_2', 'MyBtn_3' )

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

Быстрые и грязные предложения.

[Предполагая, что вы не можете изменить базовые таблицы, что в этих таблицах уже записаны добавленные строки времени / даты и что у вас есть разрешение на создание объектов в БД].

  1. Создайте ПРЕДСТАВЛЕНИЕ (или пару представлений), в котором есть логическое поле, которое генерирует уникальный "номер слота" путем дробления даты в таблицах.Что - то вроде:

СОЗДАТЬ ПРЕДСТАВЛЕНИЕ В ВИДЕ ВЫБЕРИТЕ a, b, c, SUBSTR (date_field,x, y) slot_number От ТАБЛИЦА;

Приведенный выше пример упрощен, вы, вероятно, захотите добавить больше элементов из date + time.

[например, допустим, дата '2010-01-01 10:20:23,111', возможно, вы могли бы сгенерировать ключ как '2010-01-01 10:00':таким образом, ваше разрешение составляет один час].

  1. Необязательно:используйте ПРЕДСТАВЛЕНИЕ для создания реальной таблицы, например:

    СОЗДАТЬ ТАБЛИЦУ frozen_data КАК ВЫБЕРИТЕ * ИЗ ПРЕДСТАВЛЕНИЯ ГДЕ slot_number='xxx;

Зачем беспокоиться о шаге 1?На самом деле тебе не обязательно:простое использование представления может немного упростить задачу (с точки зрения SQL).

Зачем беспокоиться о шаге 2?Просто способ (возможно) снизить нагрузку на и без того занятые таблицы:если вы можете динамически генерировать DDL, то вы могли бы создавать отдельные таблицы с копиями "слотов" данных:с которым вы затем можете работать.

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

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

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

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

CouchDB собирает неструктурированные данные (JSON & c);цитирую технический обзор с веб-сайта,

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

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

Исходя из ваших требований, я могу сказать, что вам нужно

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

Лично я бы сделал что-то вроде:

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

Последний пункт - это всего лишь пример.Я понятия не имею, что ты планируешь с этим делать.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top