Merge pull request #11 from openqj/workers-precision-fix

Fix workers time columns to DOUBLE PRECISION on Postgres
This commit is contained in:
openqj authored and GitHub committed 2026-10-26 05:07:16 +08:00
commit f1d42d04f3
2 files changed
+64 -3

No files matched your search

+21
View File
@@ -458,5 +458,26 @@ class ProjectTaskFieldTestCase(_ServerCase):
self.assertEqual(body["executor"], "docker")
class WorkerTablePrecisionTestCase(unittest.TestCase):
def test_workers_time_columns_are_double_precision(self):
# 回归:2026-10-26 主机实测,workers.last_seen 用 REAL 时,
# Postgres 的 float4 把 epoch 秒舍入到 128 秒一档,存活判断
# 误差可达 ±64 秒、甚至出现负 age。新表必须是 DOUBLE PRECISION。
import sqlite3 as _sqlite3
tmp = tempfile.TemporaryDirectory()
db_path = os.path.join(tmp.name, "w.db")
from cloud.workers import ensure_workers_table
ensure_workers_table(db_path)
conn = _sqlite3.connect(db_path)
try:
rows = conn.execute("PRAGMA table_info(workers)").fetchall()
finally:
conn.close()
tmp.cleanup()
by_name = {r[1]: r[2] for r in rows}
self.assertIn("DOUBLE", by_name["last_seen"])
self.assertIn("DOUBLE", by_name["created_at"])
if __name__ == "__main__":
unittest.main()
+43 -3
View File
@@ -42,7 +42,19 @@ def alive_threshold_seconds(override=None):
def ensure_workers_table(db_path):
"""建 workers 表(不存在时建,已存在时不动)。"""
"""建 workers 表(不存在时建,已存在时不动,老库补精度)。
注意:last_seen/created_at 必须是 DOUBLE PRECISION,不能用 REAL。
SQLite 的 REAL 是 8 字节双精度,没问题;但 Postgres 的 REAL 是
4 字节 float4,只能存约 7 位有效数字,epoch 秒(10 位)会被
舍入到 128 秒一档,导致存活判断误差可达 ±64 秒、甚至出现负 age
(2026-10-26 主机实测:last_seen 比 NOW() 还大 20 秒)。
新表直接用 DOUBLE PRECISION;老库(已是 REAL 列)用 ALTER
TYPE 升到 DOUBLE PRECISION(float4 -> float8 无损,已存的舍入值
无法恢复,但之后的心跳不再舍入)。SQLite 下 ALTER TYPE 语法
不支持,靠 try/except 跳过——SQLite 的 REAL 本来就是双精度,
不需要升。
"""
conn = _db_connect(str(db_path))
try:
conn.execute(
@@ -50,10 +62,38 @@ def ensure_workers_table(db_path):
"worker_id TEXT PRIMARY KEY, "
"executor TEXT, "
"capacity INTEGER, "
"last_seen REAL, "
"created_at REAL)"
"last_seen DOUBLE PRECISION, "
"created_at DOUBLE PRECISION)"
)
conn.commit()
# 老库升精度只在确实是 Postgres REAL 列时做一次:每次心跳都
# ALTER 会反复拿 ACCESS EXCLUSIVE 锁,没必要。先查
# information_schema(SQLite 下这张表不存在,查询失败即跳过,
# SQLite 的 REAL 本来就是双精度,不需要升)。
for col in ("last_seen", "created_at"):
try:
row = conn.execute(
"SELECT data_type FROM information_schema.columns "
"WHERE table_name = 'workers' AND column_name = ?",
(col,)).fetchone()
except Exception:
try:
conn.rollback()
except Exception:
pass
continue
if not row or row[0] != "real":
continue
try:
conn.execute(
"ALTER TABLE workers ALTER COLUMN %s "
"TYPE DOUBLE PRECISION" % col)
conn.commit()
except Exception:
try:
conn.rollback()
except Exception:
pass
finally:
conn.close()