两个冻结时点不同的时间函数。

NOW 与 clock_timestamp 的差别在冻结时机

PostgreSQL 提供多种“当前时间”。事务时间标记事务开始,语句时间标记客户端命令到达,墙钟读数则在表达式求值时读取当前时钟。选择不同函数,会改变审计字段、过期检查和长任务中间观测的含义。应先明确要记录哪个事件,再决定函数,不能因为名称都有时间含义就随意互换。 一个事务只有一个开始时刻 now()、transaction_timestamp() 与不带精度参数的 CURRENT_TIMESTAMP,返回同一个事务开始瞬间。在显式事务里,它们始终固定,即使后续语句已经晚了几秒或几分钟。CURRENT_TIMESTAMP 可以指定精度来舍入结果;小数位数的处理与采样时刻是两回事,不能因为显示精度不同就推断时钟来源改变。 这种稳定性适合让相关行共享同一个逻辑事务时间。它不记录每行真正插入的时刻,也不是最终提交时间。事务可能先等待锁或执行大量计算,随后才插入数据,最后还有可能回滚。把列命名为 created_at,并不能自动说明默认值到底代表哪个事件,需要在数据契约里写清楚。 普通自动提交模式下,连续语句通常各自处于不同事务,因此 now 看起来会随每条语句前进。但这种观察没有改变函数契约。后来为了原子性加上显式事务时,原本被误认为实时变化的时间,就可能暴露出早已固定的问题;验证时应覆盖真实事务边界,而不只在交互终端连续敲两条查询。 语句标记与实时读数 statement_timestamp() 返回服务器收到最近一条客户端命令消息的时间,在该命令执行期间保持稳定。事务中后续命令的语句时间可能与事务时间不同。同一协议消息里发送的多条 SQL 命令,也可能共享这个标记,所以它不一定代表等待结束后,每个内部操作真正开始的那一刻。 clock_timestamp() 在求值时读取当前墙钟,即使同一条语句内部也可能变化。由于时钟精度有限,两次调用并不保证得到不同值。它也不是单调计时器,时钟校正可能影响前后读数的差值。需要保证耗时测量不受墙钟调整影响时,应使用合适的单调时钟,而不是把数据库墙钟当成无条件递增的计数器。 这些值单独使用,都不能证明并发事务的提交顺序,也不能提供唯一事件身份。相关字段需要一致逻辑时间时选事务标记,观测命令到达时选语句标记,需要读取当前墙钟时才选实时观测。身份与顺序要求应另外明确,不能让时间戳自动承担它未承诺的语义。 坏掉的样子 长事务中的过期检查如果使用 now,就会持续与事务开始时刻比较。一个在事务开始时成立的条件,即使真实墙钟已经越过截止时间,也可能继续成立。反过来,把全部默认值换成 clock_timestamp,又可能让本该共享逻辑创建时间的相关行各不相同。修改函数前,应先确定列代表的是业务批次、命令到达还是实际观测。 列默认值中的特殊字面量 TIMESTAMPTZ 'now' 也是一个陷阱。PostgreSQL 在解析常量时就把它转换成时间,所以后续插入可能一直拿到建表时刻。DEFAULT now() 是函数调用,会在真正使用默认值时求值。两种写法看起来相似,并不代表求值生命周期相同;只检查默认表达式包含 now 这个词,会漏掉这个差别。 这些函数返回 timestamptz。切换会话时区会改变瞬间的显示形式,而不是改变已存储的瞬间本身。timestamptz 不保留最初传入的时区名称;应用需要记住原始时区时,应单独建模,不能从后来显示的偏移量反推原始地区。相同偏移量也不足以唯一确定某个时区名称。 快速验证 把下面内容保存为 timestamps.sql,在可丢弃的 PostgreSQL 18 数据库中执行 psql -X -v ON_ERROR_STOP=1 -f timestamps.sql。显式事务之外,应保持 psql 默认的自动提交行为;第二段需要让两次插入分别处在不同事务里。所有表都是临时表。 ...

一月 13, 2028 · 2 分钟 · XB Field Notes
服务激活、接受时间样本和时钟同步需要不同的观察证据。

分别检查 timesyncd 服务状态与时钟证据

systemd-timesyncd 是一个精简 SNTP 客户端,用配置的时间源同步本地系统时钟。它可以帮助证书检查与墙钟时间戳保持有意义,但不会无条件保证某个精度范围。守护进程能否运行、是否收到可接受样本,以及系统是否报告同步,是需要分别确认的事实。只看到安装了客户端,或者某条命令成功返回,都不足以推导时钟已经准确。 坏掉的样子 守护进程并不总是平滑校正。文档明确说明,大偏差会直接跳时,小偏差则逐渐调整。因此,开机时离正确时间很远的机器,可能发生时间不连续。经过时长测量应使用单调时钟,解释墙钟时间戳的应用则应处理校时,而不是假定时间永远平滑前进。不能把“精简客户端”误写成“绝不会让应用看见时间跳跃”。 FallbackNTP 不是显式服务器不可达时就一定会启用的通用应急列表,它在没有其他已知服务器信息时才适用。全局 NTP 条目和 networkd 提供的链路时间源,优先于这个后备设置。因此,应检查实际来源选择与配置覆盖文件,不能仅因为主来源连接失败,就断定某个写在 fallback 中的服务器必定已经被尝试。 未同步也不能证明 UDP 流量被阻止。服务可能缺失、未启用、被单元条件排除、没有调整时钟的权限,或者正在拒绝不合适样本。反过来,全局同步属性也不能单独说明哪个进程提供了时间。应区分管理器看到的服务状态、客户端观察到的样本,以及内核层同步指示,避免把来自不同层面的一个布尔值当成完整故障结论。 快速验证 这个失败模式实验在一次性 Debian 12 Docker 容器中以 root 运行,PID 1 为 systemd 252.39。初始化进程通过 /usr/bin/setpriv --bounding-set=-sys_time,-sys_module /sbin/init 启动,移除相关能力以防止调整 Docker 虚拟机共享时钟。脚本在请求任何服务启动前检查该前提,并要求安装包中的容器排除条件存在: from pathlib import Path import json,subprocess def run(*args): result=subprocess.run(args,text=True,capture_output=True,timeout=10) assert result.returncode==0,(args,result.stderr) return result.stdout.strip() def properties(unit): output=run('systemctl','show',unit,'-p','ActiveState','-p','SubState','-p','MainPID','-p','ConditionResult') return dict(line.split('=',1) for line in output.splitlines()) # This failure-mode exercise requires an isolated container that cannot set time. status=dict(line.split(':',1) for line in Path('/proc/1/status').read_text().splitlines() if ':' in line) assert int(status['CapBnd'].strip(),16)&(1<<25)==0,'PID 1 must lack CAP_SYS_TIME' assert run('systemd-detect-virt','--container')=='docker' unit='systemd-timesyncd.service';definition=run('systemctl','cat',unit) assert 'ConditionCapability=CAP_SYS_TIME' in definition assert 'ConditionVirtualization=!container' in definition assert properties(unit)['ActiveState']=='inactive' run('systemctl','start',unit) state=properties(unit) assert state=={'MainPID':'0','ActiveState':'inactive','SubState':'dead','ConditionResult':'no'},state clock=dict(line.split('=',1) for line in run('timedatectl','show','-p','CanNTP','-p','NTP','-p','NTPSynchronized').splitlines()) assert all(value in ('yes','no') for value in clock.values()) print('start command returned zero; unit stayed inactive with PID 0 and failed conditions') print('clock properties:',json.dumps(clock,sort_keys=True)) print('PID 1 lacks time-setting capability; Docker condition also excludes this packaged service') print('no clock-setting command was issued and no NTP synchronization was claimed') 启动命令返回零,但单元仍为非活动,主进程编号为零,ConditionResult=no。条件不满足而跳过激活,与守护进程成功运行,是不同结果。样本安装包要求调整时间的能力,并排除容器,而当前环境两项前提都不满足。因此在假设 NTP 报文被网络丢弃之前,就已经有具体证据解释服务为何没有工作。 ...

三月 11, 2027 · 1 分钟 · XB Field Notes
同一瞬间有不同偏移表示,重复的本地时刻则可能对应两个瞬间。

时间戳需要明确的时间语义

像 2026-11-01 01:30 这样的钟表标签,单独存在时不能确定唯一瞬间。解释它需要知道所在时区,在时钟回拨期间还可能需要说明是哪一次出现。反过来,已经明确纪元和单位的 Unix 时间戳,即使没有显示时区名称,也可以具有完整的瞬间语义。选择字符串格式之前,应先明确系统保存的究竟是哪一种时间值。 偏移不是完整时区规则 带数值偏移的时间戳说明本地字段如何对应 UTC。同一天的 12:00Z 和 20:00+08:00 可以表示同一瞬间。IANA 命名时区还包含历史及未来的民用时间规则,包括夏令时切换。安排下个月的洛杉矶本地约会时,固定的负七小时偏移不能替代 America/Los_Angeles,因为到时可能适用不同偏移。 对于已经发生的事件,可以保留无歧义瞬间,再按照读者需要的时区显示。但“每个工作日早上九点”是重复的民用时间意图,还需要保留本地日程及其适用时区。政策变化可能改变未来规则,需要可复现性时也应记录所用时区数据库版本。把今天的偏移固化成永远使用的日程,会悄悄改变原始业务含义。 UTC 格式并不证明时钟准确,也不证明因果顺序。两台机器都能打印 Z 后缀,但各自系统时钟仍可能有偏差;有限精度还会让多个事件得到相同时间戳。需要排序依据时,应保留事件标识及适当的因果上下文。本机经过时长则应选用合适的单调时钟,不能期待时区转换修复最初就测错的瞬间。 数值接口还必须注明采用秒还是毫秒,以及从哪个纪元开始计数。一个较大的整数本身不能完整说明单位;接收方把单位解释错了,之后再切换显示时区也无法恢复正确含义。单位应属于接口契约,而不是靠字段长度临时猜测。 坏掉的样子 任意时间戳字符串直接排序,可能得到与时间先后相反的结果。RFC 3339 对适合文字排序的条件有明确说明,包括统一的时区表示和小数精度。面对混合偏移,应先解析并归一化到瞬间,再进行比较。视觉上本地小时较晚的记录,换算到 UTC 之后可能反而更早,因此字符串字符比较不能替代时间语义解释。 秋季回拨可能让同一个本地小时出现两次,Python 的 ZoneInfo 通过 fold 区分这两个实例。春季前拨则可能让某个本地时刻根本不存在。仅仅把时区对象附到日期上,并不会自动拒绝缺失时间。接受本地约会输入的程序必须定义拒绝或解决歧义的策略,不能悄悄替用户猜测他想选择哪一个瞬间。 日期运算也要与目标一致。两个 datetime 共享同一个 tzinfo 时,相关运算可能使用钟表字段语义,忽略 fold 所表达的瞬间区别。如果需要比较实际经过时间,应先分别转换到 UTC。这个选择需要明确,因为跨越时钟切换时,本地表盘读数之差可能与真正经过的秒数不同,二者都不能无条件替代另一种含义。 快速验证 将下面程序保存为 timezone.py,使用 Python 3.11 或更新版本执行 python3 timezone.py,并确保环境中有 IANA 时区数据。实测环境是 Python 3.11.2 和 Debian tzdata 2026b。程序全部使用固定日期,不会修改机器的时钟,也不会更改系统时区;不同数据库版本需要重新检查相应规则。 ...

十一月 29, 2026 · 2 分钟 · XB Field Notes
四个时间戳估算偏移和延迟,路径不对称仍会留下误差。

时钟同步约束机器之间的时间偏差

时钟同步用于减少机器之间的民用时间读数差异,支撑证书有效期检查、事件时间解释以及许多过期策略,但不会建立完美的全局时钟。一台主机能够输出格式正确的 UTC,同时仍然测量了错误瞬间。应用需要区分经过时长、绝对截止时间和因果顺序,不能让所有语义都依赖未经解释的钟表数值比较。 四个时间戳估算两个量 一次 NTP 交换包含客户端发送时间 T1、服务端接收时间 T2、服务端发送时间 T3 和客户端接收时间 T4。常用偏移估计为 ((T2-T1)+(T3-T4))/2,正值表示服务端看起来比客户端更快。往返延迟估计为 (T4-T1)-(T3-T2),其中减去了服务端处理所花的时间。它们是根据观测推算的量,并没有直接测出每一段单向网络路径的时延。 当去程和回程延迟不同,偏移估计就会包含误差。较小的往返延迟有参考价值,却不能证明两个方向完全对称。同步守护进程会使用多次采样、来源选择、过滤和时钟模型,而不是把单个报文当作无条件可信的真相。来源可达性、估计不确定性和来源可信度都需要考虑,较低的 stratum 数字本身并不是精度证书。 时间戳的表示分辨率也不同于测量准确度。小数字段即使能编码非常细的步长,实际时钟或网络观测仍可能带有明显更大的不确定性。多打印几位小数不会改善证据质量,解释结果时应同时说明测量方法和它能够支持的结论。 时钟控制是后续的另一阶段。守护进程可以逐渐调整,也可以在配置允许的条件下直接跳变。因此不能保证 NTP 永远不会造成实时时钟的不连续,更不能承诺所有环境都达到亚毫秒精度。网络、振荡器、平台和配置共同决定实际效果。告警阈值应来自应用需求和测量条件,而不是把某个固定偏差值无条件推广到所有系统。 坏掉的样子 实时时钟可能向前或向后调整,直接相减得到的时长就可能失真甚至变成负数。本机持续时间和超时应选择适当的单调时钟,但其参考起点不能直接当成其他主机或下次启动仍可使用的时间戳。证书日期和业务截止时间仍需要民用时间语义,因此单调时钟并不是替代所有日期处理的通用答案。 格式不完整、与请求不对应、来源未同步或被策略拒绝的 NTP 响应,都不能当成普通样本使用。核对返回的 origin 时间戳是否匹配本次请求,有助于拒绝无关报文,但并不构成密码学认证。生产客户端还需要协议规定的更多校验和来源选择行为。时钟同步也不能自动让基于时间戳的租约成为安全分布式锁,更不能保证非常接近的事件按时间排序就等于因果顺序。 快速验证 使用全新的临时 Debian 12 Docker 容器,先执行 apt-get update,再执行 apt-get install -y python3 chrony 准备依赖。将代码保存为 ntp-exchange.py,以 root 运行 python3 ntp-exchange.py。实测版本是 chrony 4.3 和 Python 3.11.2。创建容器时应去掉 SYS_TIME 能力,并保持独立网络命名空间,不使用宿主网络;实验只需要容器内的回环通信。 ...

九月 9, 2026 · 3 分钟 · XB Field Notes
未同步但仍运行的客户端选择本地来源,全程不调整系统时钟。

服务器时间校准需要可观察的依据

时间守护进程只有在实际状态满足应用要求时才真正有用。进程正在运行,仍可能没有可用来源;已经选择某个来源,也可能尚未充分测量,或者来源本身就不正确。时间服务健康应当是一条可观察的链路:来源配置、成功采样、选择状态、不确定性以及实际允许的时钟控制。进程显示绿色,只能覆盖链路最前面的一部分。 读取守护进程实际报告的内容 Chrony 的 tracking 输出分别给出参考来源、层级、最近更新时间、尚未消除的系统时间修正量、估计频率、频率偏斜不确定性、根延迟和根离散度。它们回答不同问题:offset 关注时间差,frequency 关注时钟走速,skew 反映频率估计的不确定性。根延迟与离散度则参与描述参考路径带来的时间不确定性。只挑一个很小的偏移数值,不能概括整个时钟质量。 sources 输出用于观察候选来源及实际选择。常见显示中的 ^* 表示选中的 NTP 服务器。Reach 是八进制历史位图,377 表示最近八次轮询机会都有成功响应,并不是百分比,也不保证下一次仍然可达。LastRx 有助于查看最近收到的样本已经过去多久。应把来源状态与 tracking 一起读,而不是把进程持续运行时长误认为同步证据。 上游短暂中断后,守护进程可能继续依据已有模型估计时间,但不确定性会随条件变化而扩大。需要根据应用容忍度监控更新时间和误差估计,区分短期维持与无限期可信。多个服务器名称也不必然意味着独立参考,如果它们共享上游或基础设施,仍然可能一起出错。来源选择降低部分故障风险,却不能证明所有上游声明都正确。 坏掉的样子 容器通常共享宿主的内核时钟,在容器内运行时间服务并不会自然得到一块独立可调的民用时钟。Chronyd 的 -x 模式明确关闭系统时钟控制,但仍保留估计和提供时间服务的能力。因此,客户端可以报告已经同步,系统时钟却并没有被这个进程修正。排查时必须确认实际运行模式,以及哪一层负责宿主时间。 local 指令也容易制造错误信心。它允许 chronyd 在没有外部参考的情况下表现为已同步,可以用于有明确设计的隔离网络安排。但如果只是为了消除告警而启用,就会把缺失参考这一问题隐藏起来。解释 Normal 之前,要核对参考标识以及预期来源政策,不能把某种协议状态直接解释成 UTC 已经准确。 运行检查还需要有意义的限制。Chronyc 的 waitsync 可以等待同步,并可选地限制剩余时间修正量和频率估计偏斜;相应限制为零时,额外约束会被关闭。两个限制都关闭的成功结果,比执行应用专属容忍边界的检查弱得多。即使配置了边界,它也不能替任意上游时钟证明真实性,来源信任仍是另一项要求。 快速验证 使用新的临时 Debian 12 Docker 容器,运行 apt-get update 和 apt-get install -y python3 chrony 安装依赖,将下面代码保存为 chrony-status.py,以 root 执行 python3 chrony-status.py。创建容器时保持独立网络,不使用宿主网络,并移除 SYS_TIME 能力。实测版本为 chrony 4.3 与 Python 3.11.2,代码只在回环地址通信。 ...

九月 2, 2026 · 2 分钟 · XB Field Notes