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")
|
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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
+43
-3
@@ -42,7 +42,19 @@ def alive_threshold_seconds(override=None):
|
|||||||
|
|
||||||
|
|
||||||
def ensure_workers_table(db_path):
|
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))
|
conn = _db_connect(str(db_path))
|
||||||
try:
|
try:
|
||||||
conn.execute(
|
conn.execute(
|
||||||
@@ -50,10 +62,38 @@ def ensure_workers_table(db_path):
|
|||||||
"worker_id TEXT PRIMARY KEY, "
|
"worker_id TEXT PRIMARY KEY, "
|
||||||
"executor TEXT, "
|
"executor TEXT, "
|
||||||
"capacity INTEGER, "
|
"capacity INTEGER, "
|
||||||
"last_seen REAL, "
|
"last_seen DOUBLE PRECISION, "
|
||||||
"created_at REAL)"
|
"created_at DOUBLE PRECISION)"
|
||||||
)
|
)
|
||||||
conn.commit()
|
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:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user