Merge pull request #11 from openqj/workers-precision-fix
Fix workers time columns to DOUBLE PRECISION on Postgres
This commit is contained in:
2 files changed
+64
-3
No files matched your search
@@ -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
@@ -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()
|
||||
|
||||
|
||||
Reference in new issue
Block a user