订阅更新

出海业务覆盖10+国家,数据跨多个时区——时区问题怎么处理?

数仓项目手册 第 17 题 约 8 分钟读完

本文目录
  1. 先说结论
  2. 跨时区数据处理的完整方案
  3. 为什么分区键用当地日期?
  4. 夏令时处理
  5. 面试高频追问
  6. 实战踩坑
  7. 总结

先说结论

核心原则:存储用UTC,展示用当地时间。所有事件时间在DWD层统一转为UTC存储,在ADS层按业务需要转为当地时区展示。

分区键用当地日期,不用UTC日期。报表是按当地时间看的(美国运营看的是美国时间的'今天'),所以分区键应该是当地日期。

时区信息必须冗余存储:每条记录存三个时间字段——event_time_utc(UTC时间)、event_time_local(当地时间)、timezone(时区标识)。冗余但安全。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

时区问题是出海业务数仓的'隐形杀手'。看起来简单——不就是时间转换吗?但做过的人都知道,时区问题能衍生出无数个坑:日报按哪个时区算?跨时区的用户留存怎么定义?夏令时怎么处理?我在某短剧出海公司负责覆盖10+国家的数据体系,被时区问题折磨了整整一个月,最终总结出一套完整的跨时区处理方案。

跨时区数据处理的完整方案

以我在某短剧出海公司的实践为例,数据覆盖美国(UTC-5/-4)、巴西(UTC-3)、东南亚(UTC+7/+8)、中东(UTC+3)等10+个时区。完整处理流程如下:

-- ODS层:保留原始时间,不做任何转换
-- event_time可能是UTC、可能是当地时间,取决于客户端上报
 
-- DWD层:统一转为UTC + 计算当地时间
INSERT OVERWRITE TABLE dwd_user_event_di PARTITION(dt='${bizdate}')
SELECT
    event_id,
    user_id,
    event_type,
    -- 统一存UTC时间(核心!)
    CASE
        WHEN time_format = 'UTC' THEN event_time
        ELSE CONVERT_TZ(event_time, timezone, 'UTC')
    END AS event_time_utc,
 
    -- 同时存当地时间(方便报表直接用)
    CASE
        WHEN time_format = 'LOCAL' THEN event_time
        ELSE CONVERT_TZ(event_time, 'UTC', timezone)
    END AS event_time_local,
 
    -- 当地日期(用于分区和报表)
    DATE(CONVERT_TZ(
        CASE WHEN time_format='UTC' THEN event_time
             ELSE CONVERT_TZ(event_time, timezone, 'UTC') END,
        'UTC', timezone)) AS local_date,
 
    timezone,                -- 保留时区信息
    country_code
FROM ods_user_event_di
WHERE dt = '${bizdate}';

为什么分区键用当地日期?

这是一个很多人容易搞错的问题。看一个例子:

场景:美国用户在当地时间2024-03-15 23:30 付费
 
UTC时间:2024-03-16 04:30(UTC-5)
当地时间:2024-03-15 23:30
 
如果按UTC日期分区:dt = '2024-03-16'
  -> 美国运营查3月15日的报表,这笔付费不在里面!
  -> 因为UTC日期已经是3月16日了
 
如果按当地日期分区:local_dt = '2024-03-15'
  -> 美国运营查3月15日的报表,这笔付费在里面 ✅
  -> 因为当地时间确实是3月15日

所以报表相关的表(DWS/ADS)分区键必须用当地日期。但这引入了另一个问题:不同国家的'同一天'在UTC时间上是不同范围的。美国的3月15日对应UTC的3月15日05:00到3月16日05:00,日本的3月15日对应UTC的3月14日15:00到3月15日15:00。所以DWS汇总时要按local_date聚合,不能按UTC date聚合。

夏令时处理

夏令时是跨时区处理中最坑的问题。美国每年3月和11月调整时钟——春季前进1小时(UTC-5变UTC-4),秋季后退1小时。这意味着:

场景问题解决方案
春季切换日凌晨2点直接跳到3点,丢了1小时用UTC存储不受影响,当地时间自动跳过
秋季切换日凌晨2点倒回1点,多了1小时(重复)UTC存储不重复,但当地时间会有2条1:30的记录
跨天统计切换日当天只有23小时或25小时按UTC计算再转当地时间,不要直接按小时数算
定时任务调度时间按UTC设置避免跳过所有调度用UTC时间,不用当地时间
-- 夏令时安全的时区转换(推荐做法)
-- 使用时区名称(如'America/New_York')而不是偏移量(如'-05:00')
-- 因为时区名称会自动处理夏令时
 
-- 正确写法(自动处理夏令时)
CONVERT_TZ(event_time_utc, 'UTC', 'America/New_York')
-- 冬天自动用UTC-5,夏天自动用UTC-4
 
-- 错误写法(不处理夏令时!)
DATE_ADD(event_time_utc, INTERVAL -5 HOUR)
-- 永远减5小时,夏天就错了
 
-- 常用时区名称映射
-- 美东:America/New_York(UTC-5 / UTC-4夏令时)
-- 美西:America/Los_Angeles(UTC-8 / UTC-7夏令时)
-- 巴西:America/Sao_Paulo(UTC-3 / UTC-2夏令时)
-- 日本:Asia/Tokyo(UTC+9,无夏令时)
-- 印尼:Asia/Jakarta(UTC+7,无夏令时)

面试高频追问

追问一:全球报表怎么做?不同国家的'昨天'是不同的UTC范围啊。

全球报表有两种做法:① 按UTC统一——全球看同一个UTC日期的汇总,适合管理层看全局趋势;② 按当地时间分国家汇总——每个国家按自己的当地日期统计,然后加总,适合运营分国家看数据。我们两种都做了:管理驾驶舱用UTC统一视角,各国运营看自己当地时间的报表。ADS层建了两套表——ads_global_summary_utc和ads_country_summary_local,分别服务两种需求。

追问二:用户跨时区怎么处理?比如用户从美国飞到日本。

看业务定义。我们的做法是'用户归属国家以注册时的国家为准'——用户在美国注册,即使飞到日本,他的行为数据仍然按美东时间统计(因为他的付费、渠道归因都在美国)。当然这个规则不是唯一的,有些业务按IP实时判断用户当前位置。关键是要跟业务方对齐规则并记录在指标口径文档里。

追问三:server_time和event_time时区不一致怎么办?

server_time是服务端接收时间,一般是UTC(服务器都配UTC时区)。event_time是客户端上报的时间,可能是UTC也可能是当地时间,取决于SDK实现。我们的处理规则是:以event_time为准(更准确反映用户行为发生时间),用server_time做兜底——如果event_time与server_time差超过24小时,认为event_time异常,用server_time替代。

实战踩坑

我在某短剧出海公司遇到过一次严重的时区事故。上线巴西市场后,发现巴西的日报数据每天都比其他国家晚3个小时才出。排查发现原因是:我们的调度任务是UTC时间0点触发的,计算'昨天'的数据。但巴西是UTC-3——UTC时间0点时,巴西当地时间是前一天晚上9点,巴西的'昨天'还没过完!所以跑出来的巴西数据总是少了最后3个小时。

修复方案:把巴西相关的调度任务延迟到UTC时间3点触发(对应巴西当地时间0点),确保巴西的'昨天'完整结束后再跑。更通用的方案是:按国家分组设置调度时间——每个国家的调度任务在该国当地时间0:30触发(留30分钟缓冲),用CONVERT_TZ动态计算'当地的昨天'。

-- 踩坑:调度时间没考虑时区差异
 
-- 错误做法:UTC 0点统一跑所有国家的'昨天'数据
-- 巴西(UTC-3):UTC 0点 = 巴西21:00,'昨天'还没过完!
 
-- 修复方案:按时区分组调度
-- 东亚(UTC+7~+9):UTC 16:00触发(当地23:00~01:00)
-- 欧洲(UTC+0~+2):UTC 23:00触发(当地23:00~01:00)
-- 美洲(UTC-3~-8):UTC 06:00触发(当地22:00~03:00)
 
-- 通用SQL:动态计算当地的'昨天'
SELECT *
FROM dwd_user_event_di
WHERE local_date = DATE(CONVERT_TZ(
    DATE_SUB(NOW(), INTERVAL 1 DAY),
    'UTC', timezone));

总结

跨时区处理的核心是'存储用UTC,展示用当地时间,分区用当地日期'。三个必须冗余存储的时间字段:event_time_utc、event_time_local、timezone。夏令时用时区名称(如America/New_York)而不是偏移量(如-05:00)。调度时间要按国家分组,不能UTC 0点一刀切。面试时能讲清楚这些细节,面试官就知道你是真做过出海业务的。

时区问题搞定了,下一篇聊另一个数据治理的重要话题——埋点事件1000+,数据质量一塌糊涂,埋点治理怎么做?

你们出海业务覆盖多少个时区?有没有踩过夏令时的坑?评论区聊聊~

关注「Wood聊大数据」| 9年大数据老兵,聊技术、聊职场、聊成长

刷题进度保存在当前浏览器