Проект хранилища данных для отчетности по данным для многих часовых поясов


10

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

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

Наши таблицы фактов станут очень широкими. Кроме того, нам нужно было бы решить проблему синтаксиса в SQL, указав, какие идентификаторы даты и времени будут использоваться для группировки при любом прогоне отчета. Возможно, очень большое CASE заявление?

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

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

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

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


В контексте моего ответа (и об обновлениях, которые я опубликую позже), как далеко ушли ваши данные? Будет ли ежемесячный отчет показывать 28-31 наборов 24-часовых блоков? Это всегда будет «календарный месяц» или это действительно какой-то диапазон? Что он должен показывать, когда одна из дат является датой перехода на летнее / откатное время DST для выбранного часового пояса? Кроме того, что именно вводится для отчета? Преобразуете ли вы местное время пользователя в UTC на основе его текущей локали, у них есть предпочтения, они выбирают вручную, или вы выводите каким-либо другим способом, или вы хотите, чтобы запрос выяснил это?
Аарон Бертран

Чтобы ответить на ваши вопросы: данные могут вернуться за 2 года. У нас есть некоторые отчеты, которые показывают только один набор 24-часовых фрагментов и другие отчеты, которые имеют 24-часовой блок каждый день в диапазоне дат отчета. Диапазон дат может быть действительно любым, что хочет пользователь. Пользователь выбирает начальную и конечную дату (и время), а затем выбирает нужный часовой пояс из выпадающего списка
Peter M

Ответы:


18

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

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

Есть несколько странных крайних случаев, которые вы должны рассмотреть:

  • 2014-11-02 05:30 UTC и 2014-11-02 06:30 UTC, например, конвертируются в 01:30 в восточном часовом поясе (например, первый раз в 01:30 был достигнут локально, а затем один во второй раз, когда часы откатились с 2:00 до 1:00, и прошло еще полчаса). Таким образом, вам нужно решить, как обрабатывать этот час отчетности - согласно UTC, вы должны увидеть удвоение трафика или объема того, что вы измеряете, после того, как эти два часа сопоставлены с одним часом в часовом поясе, в котором наблюдается DST. Это также может играть в забавные игры с последовательностью событий, поскольку что-то, что логически должно было произойти после того, как могло появиться что-то ещепроизойдет до того, как время будет установлено на один час вместо двух. Крайним примером является просмотр страницы, который произошел в 05:59 UTC, а затем щелчок, произошедший в 06:00 UTC. В UTC это происходило с интервалом в одну минуту, но при преобразовании в восточное время просмотр происходил в 1:59, а щелчок происходил на час раньше.

  • 2014-03-09 02:30 в США никогда не бывает Это потому, что в 2:00 утра мы переводим часы вперед на 3:00 утра. Скорее всего, вы захотите вызвать ошибку, если пользователь введет такое время и попросит вас преобразовать его в UTC или спроектировать форму так, чтобы пользователи не могли выбрать такое время.

Даже с учетом этих крайних случаев, я все еще думаю, что у вас есть правильный подход: хранить данные в UTC. Гораздо проще сопоставить данные с другими часовыми поясами из UTC, чем из некоторого часового пояса в другой часовой пояс, особенно когда разные часовые пояса начинают / заканчивают летнее время в разные даты, и даже один и тот же часовой пояс может переключаться с использованием разных правил в разные годы ( например, США изменили правила 6 лет назад или около того).

Вы захотите использовать таблицу календаря для всего этого, а не какое-то гигантское CASE выражение (не утверждение ). Я только что написал серию из трех частей для MSSQLTips.com по этому вопросу; Думаю, 3-я часть будет наиболее полезной для вас:

http://www.mssqltips.com/sqlservertip/3173/handle-conversion-between-time-zones-in-sql-server--part-1/

http://www.mssqltips.com/sqlservertip/3174/handle-conversion-between-time-zones-in-sql-server--part-2/

http://www.mssqltips.com/sqlservertip/3175/handle-conversion-between-time-zones-in-sql-server--part-3/


Настоящий живой пример, тем временем

Допустим, у вас есть очень простая таблица фактов. Единственный факт, который меня волнует в этом случае - это время события, но я добавлю бессмысленный GUID, чтобы таблица была достаточно широкой, чтобы о ней заботиться. Опять же, чтобы быть явным, таблица фактов хранит события только по времени UTC и UTC. Я даже добавил суффикс столбца, _UTCчтобы не было путаницы.

CREATE TABLE dbo.Fact
(
  EventTime_UTC DATETIME NOT NULL,
  Filler UNIQUEIDENTIFIER NOT NULL DEFAULT NEWSEQUENTIALID()
);
GO

CREATE CLUSTERED INDEX x ON dbo.Fact(EventTime_UTC);
GO

Теперь давайте загрузим нашу таблицу фактов с 10 000 000 строк, представляющих каждые 3 секунды (1200 строк в час) с 2013-12-30 в полночь по UTC до примерно после 5:00 UTC 2014-12-12. Это гарантирует, что данные пересекают границу года, а также DST вперед и назад для нескольких часовых поясов. Это выглядит действительно страшно, но в моей системе это заняло ~ 9 секунд. Таблица должна быть около 325 МБ.

;WITH x(c) AS 
(
  SELECT TOP (10000000) DATEADD(SECOND, 
    3*(ROW_NUMBER() OVER (ORDER BY s1.[object_id])-1),
    '20131230')
  FROM sys.all_columns AS s1
  CROSS JOIN sys.all_columns AS s2
  ORDER BY s1.[object_id]
)
INSERT dbo.Fact WITH (TABLOCKX) (EventTime_UTC) 
  SELECT c FROM x;

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

SELECT DATEADD(HOUR, DATEDIFF(HOUR, 0, EventTime_UTC), 0),
  COUNT(*)
FROM dbo.Fact 
WHERE EventTime_UTC >= '20140308'
AND EventTime_UTC < '20140311'
GROUP BY DATEADD(HOUR, DATEDIFF(HOUR, 0, EventTime_UTC), 0);

Я получаю этот план, и он возвращается через 25 миллисекунд *, выполняя 358 операций чтения, и возвращает 72 ежечасных результата:

введите описание изображения здесь

* Длительность измеряется нашим бесплатным SQL Sentry Plan Explorer , который отбрасывает результаты, так что сюда не входит время передачи данных по сети, рендеринг и т. Д. В качестве дополнительного отказа я работаю для SQL Sentry.

Очевидно, это займет немного больше времени, если я сделаю свой диапазон слишком большим - месяц данных занимает 258 мс, два месяца - более 500 мс и так далее. Параллелизм может вызвать:

введите описание изображения здесь

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

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

CREATE TABLE dbo.TimeZones
(
  TimeZoneID TINYINT    NOT NULL PRIMARY KEY,
  Name       VARCHAR(9) NOT NULL,
  Offset     SMALLINT   NOT NULL, -- minutes
  DSTName    VARCHAR(9) NOT NULL,
  DSTOffset  SMALLINT   NOT NULL  -- minutes
);

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

INSERT dbo.TimeZones VALUES
(1, 'UTC',     0, 'UTC',     0),
(2, 'GMT',     0, 'BST',    60), 
     -- London = UTC in winter, +1 in summer
(3, 'EST',  -300, 'EDT',  -240), 
     -- East coast US (-5 h in winter, -4 in summer)
(4, 'ACDT',  630, 'ACST',  570), 
     -- Adelaide (Australia) +10.5 h Oct - Apr, +9.5 Apr - Oct
(5, 'ACST',  570, 'ACST',  570); 
     -- Darwin (Australia) +9.5 h year round

Теперь, календарь таблицы, чтобы знать, когда TZ меняются. Я только собираюсь вставить интересующие строки (каждый часовой пояс выше, и только изменения летнего времени за 2014 год). Для простоты вычислений я сохраняю момент в UTC, где меняется часовой пояс, и один и тот же момент в местном времени. Для часовых поясов, которые не соблюдают летнее время, это стандартно в течение всего года, и летнее время «начинается» с 1 января.

CREATE TABLE dbo.Calendar
(
  TimeZoneID    TINYINT NOT NULL FOREIGN KEY
                REFERENCES dbo.TimeZones(TimeZoneID),
  [Year]        SMALLDATETIME NOT NULL,
  UTCDSTStart   SMALLDATETIME NOT NULL,
  UTCDSTEnd     SMALLDATETIME NOT NULL,
  LocalDSTStart SMALLDATETIME NOT NULL,
  LocalDSTEnd   SMALLDATETIME NOT NULL,
  PRIMARY KEY (TimeZoneID, [Year])
);

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

INSERT dbo.Calendar VALUES
(1, '20140101', '20140101 00:00','20150101 00:00','20140101 00:00','20150101 00:00'),
(2, '20140101', '20140330 01:00','20141026 00:00','20140330 02:00','20141026 01:00'),
(3, '20140101', '20140309 07:00','20141102 06:00','20140309 03:00','20141102 01:00'),
(4, '20140101', '20140405 16:30','20141004 16:30','20140406 03:00','20141005 02:00'),
(5, '20140101', '20140101 00:00','20150101 00:00','20140101 00:00','20150101 00:00');

Итак, у нас есть данные фактов и таблицы «измерений» (я съеживаюсь, когда говорю это), так в чем же логика? Что ж, я предполагаю, что вы будете предлагать пользователям выбирать свой часовой пояс и вводить диапазон дат для запроса. Я также предполагаю, что диапазон дат будет полными днями в их собственном часовом поясе; никаких неполных дней, не говоря уже о неполных часах. Таким образом, они передадут дату начала, дату окончания и TimeZoneID. Оттуда мы будем использовать скалярную функцию для преобразования даты начала / окончания из этого часового пояса в UTC, что позволит нам фильтровать данные на основе диапазона UTC. После того, как мы это сделали и выполнили наши агрегации, мы можем применить преобразование сгруппированных времен назад к часовому поясу источника, прежде чем отобразить его пользователю.

Скаляр UDF:

CREATE FUNCTION dbo.ConvertToUTC
(
  @Source   SMALLDATETIME,
  @SourceTZ TINYINT
)
RETURNS SMALLDATETIME
WITH SCHEMABINDING
AS
BEGIN
  RETURN 
  (
    SELECT DATEADD(MINUTE, -CASE 
        WHEN @Source >= src.LocalDSTStart 
         AND @Source < src.LocalDSTEnd THEN t.DSTOffset 
        WHEN @Source >= DATEADD(HOUR,-1,src.LocalDSTStart) 
         AND @Source < src.LocalDSTStart THEN NULL
        ELSE t.Offset END, @Source)
    FROM dbo.Calendar AS src
    INNER JOIN dbo.TimeZones AS t 
    ON src.TimeZoneID = t.TimeZoneID
    WHERE src.TimeZoneID = @SourceTZ 
      AND t.TimeZoneID = @SourceTZ
      AND DATEADD(MINUTE,t.Offset,@Source) >= src.[Year]
      AND DATEADD(MINUTE,t.Offset,@Source) < DATEADD(YEAR, 1, src.[Year])
  );
END
GO

И табличная функция:

CREATE FUNCTION dbo.ConvertFromUTC
(
  @Source   SMALLDATETIME,
  @SourceTZ TINYINT
)
RETURNS TABLE
WITH SCHEMABINDING
AS
 RETURN 
 (
  SELECT 
     [Target] = DATEADD(MINUTE, CASE 
       WHEN @Source >= trg.UTCDSTStart 
        AND @Source < trg.UTCDSTEnd THEN tz.DSTOffset 
       ELSE tz.Offset END, @Source)
  FROM dbo.Calendar AS trg
  INNER JOIN dbo.TimeZones AS tz
  ON trg.TimeZoneID = tz.TimeZoneID
  WHERE trg.TimeZoneID = @SourceTZ 
  AND tz.TimeZoneID = @SourceTZ
  AND @Source >= trg.[Year] 
  AND @Source < DATEADD(YEAR, 1, trg.[Year])
);

И процедура, которая его использует ( edit : updated для обработки 30-минутной группировки смещений):

CREATE PROCEDURE dbo.ReportOnDateRange
  @Start      SMALLDATETIME, -- whole dates only please! 
  @End        SMALLDATETIME, -- whole dates only please!
  @TimeZoneID TINYINT
AS 
BEGIN
  SET NOCOUNT ON;

  SELECT @Start = dbo.ConvertToUTC(@Start, @TimeZoneID),
         @End   = dbo.ConvertToUTC(@End,   @TimeZoneID);

  ;WITH x(t,c) AS
  (
    SELECT DATEDIFF(MINUTE, @Start, EventTime_UTC)/60, 
      COUNT(*) 
    FROM dbo.Fact 
    WHERE EventTime_UTC >= @Start
      AND EventTime_UTC <  DATEADD(DAY, 1, @End)
    GROUP BY DATEDIFF(MINUTE, @Start, EventTime_UTC)/60
  )
  SELECT 
    UTC = DATEADD(MINUTE, x.t*60, @Start), 
    [Local] = y.[Target], 
    [RowCount] = x.c 
  FROM x OUTER APPLY 
    dbo.ConvertFromUTC(DATEADD(MINUTE, x.t*60, @Start), @TimeZoneID) AS y
  ORDER BY UTC;
END
GO

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

Образец звонка:

EXEC dbo.ReportOnDateRange 
  @Start      = '20140308', 
  @End        = '20140311', 
  @TimeZoneID = 3;

Возвращает в 41мс *, и генерирует этот план:

введите описание изображения здесь

* Опять же, с отброшенными результатами.

В течение 2 месяцев он возвращается через 507 мс, и план идентичен, кроме количества строк:

введите описание изображения здесь

Несмотря на то, что он немного более сложный и немного увеличивает время выполнения, я довольно уверен, что этот тип подхода сработает намного, намного лучше, чем подход с промежуточным столом. И это случайный пример ответа dba.se; Я уверен, что моя логика и эффективность могут быть улучшены людьми намного умнее меня.

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

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


0

Можете ли вы сделать преобразование в сохраненном процессе или параметризованном представлении вместо уровня представления? Другой вариант - создать куб и выполнить вычисления в кубе.

Объяснение из комментариев:

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


Итак, представление, в котором есть более 100 дополнительных столбцов, где в каждой строке указано исходное время в формате UTC для всех более 100 часовых поясов? Я даже не могу понять, как будет написана такая точка зрения. Также обратите внимание, что в SQL Server нет «параметризованного представления» ...
Аарон Бертран

хм .. так вот что ты думаешь. и это не то, что я имел в виду.
KNI

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

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

Как это может работать, если данные агрегированы? Если часовой пояс смещен на 30 минут, данные попадают в другую группу. Вы не можете просто изменить метки, отображаемые в слое презентации.
Colin 't Hart
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.