Данные в разном разрешении

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

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

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

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

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

PS. На данный момент я обнаружил, что инструменты ETL, такие как Talend, могут облегчить жизнь.

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


person nimcap    schedule 07.01.2010    source источник
comment
Что вы сейчас используете для хранения этих таблиц? Если вы не сообщите нам об этом, мы рискуем дать рекомендации, которые не подходят для вашей текущей деятельности.   -  person High Performance Mark    schedule 14.01.2010


Ответы (6)


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

person ConcernedOfTunbridgeWells    schedule 14.01.2010
comment
Очень интересный материал и хорошо объясненный. +1 - person monojohnny; 15.01.2010
comment
Информация, которую вы предоставили, весьма полезна, большое вам спасибо. Это было то, в чем я не знал, что мне нужно. Но мой первоначальный вопрос был о заполнении этих секционированных таблиц. А вы оставили это как упражнение :) Есть указания, как загрузить таблицу? - person nimcap; 17.01.2010
comment
Я добавил кое-что выше, но, не имея более подробной информации об архитектуре системы, я не могу рекомендовать конкретный подход. Однако редактирование может дать вам некоторые идеи. - person ConcernedOfTunbridgeWells; 18.01.2010

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

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

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

person Robert Christie    schedule 07.01.2010
comment
Вероятно, вы не захотите, чтобы rrdb был начальным хранилищем данных. Я не думаю, что он может обрабатывать одновременный ввод в одну таблицу. Вероятно, лучше всего использовать обычную базу данных для обработки вставок. Но использование rrdb в качестве местоположения сводной информации - отличный вариант. И для этого вам не нужны никакие инструменты etl; просто вставьте в базу данных, как вы уже есть. Пример потока: 1. Запись в таблицу db (из приложения) 2. rrd извлекает данные в свое хранилище данных - необязательно, обрезать таблицу db после 2 Done. Затем rrdtool сгенерирует для вас изображения. - person coffeepac; 11.01.2010
comment
@coffeepac: проблема одновременного доступа легко решается с помощью очереди. Я знаю, что ганглии развернуты в средах с тысячами узлов, все передающие данные обратно на один узел ганглиев, и управление одновременными обновлениями не является проблемой. - person Robert Christie; 12.01.2010

Когда дело доходит до нарезки и агрегирования данных (по времени или чему-то еще), звездная схема (звезда Кимбалла) является довольно простым, но мощным решением. Предположим, что для каждого клика мы сохраняем время (с точностью до секунды), информацию о пользователе, идентификатор кнопки и местоположение пользователя. Чтобы упростить нарезку и нарезку кубиками, я начну с предварительно загруженных таблиц поиска для свойств объектов, которые редко меняются, - так называемых таблиц измерений в мире 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 или решения с низкой задержкой с использованием какого-либо процесса потоковой передачи событий.
  • Как долго хранить информацию в таблице?

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

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

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

/* 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 ('[email protected]', '[email protected]') 
GROUP BY [Email] 
ORDER BY [Email]

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

/* 
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' )
person Damir Sudarevic    schedule 16.01.2010

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

person Lance Roberts    schedule 14.01.2010

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

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

  1. Создайте ВИД (или пару ВИДОВ), в котором есть логическое поле, которое генерирует уникальный «номер слота» путем дробления даты в таблицах. Что-то типа:

СОЗДАТЬ ПРОСМОТР представление КАК ВЫБРАТЬ a, b, c, SUBSTR (поле_даты, x, y) slot_number ИЗ ТАБЛИЦЫ;

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

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

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

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

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

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

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

Ежедневно вам придется сбрасывать эти таблицы: если вы не можете сгенерировать таблицы в своем триггере в своей БД. [вряд ли я думаю].

person monojohnny    schedule 14.01.2010

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

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

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

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

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

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

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

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

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

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

person lorenzog    schedule 15.01.2010