恢复保存的本地任务,复用已完成结果¶
创建一个持久化 Job,换一个 Backend 实例,通过同一 SQLite 存储恢复它。请先完成扫描结果,使用安装好的环境。
线路在单比特上施加 H,采样八次。理想概率各占一半,但本课重点预测的是执行行为:恢复排队中的任务后执行一次,再读取已完成结果时不会新增尝试。
恢复已经保存的任务定义¶
"""持久化、恢复并查询一个本地 Job。
SQLite 保存不可变 Job definition、attempt、状态 transition 和带 checksum 的 ResultRef。
新的 Backend 实例可以在同一主机恢复任务;本例不提供分布式调度或跨主机 exactly-once。
"""
from __future__ import annotations
import json
from pathlib import Path
from tempfile import TemporaryDirectory
from cascaqit import Circuit, LocalBackend, ResultIR, RetryPolicy
def main() -> None:
"""运行一个 durable Job,并检查恢复后的 history。"""
with TemporaryDirectory(prefix="cascaqit-track-history-") as directory:
store = Path(directory) / "runs.sqlite"
retry = RetryPolicy(max_attempts=3, backoff_strategy="none")
backend = LocalBackend(store=store, seed=404)
job = backend.run(
Circuit(1, program_id="lesson.platform.durable").h(0),
shots=8,
retry=retry,
idempotency_key="lesson-platform-durable",
)
queued_state = job.status().state
restored = LocalBackend(store=store).resume(job.job_id)
result = restored.result()
assert isinstance(result, ResultIR)
completed_again = LocalBackend(store=store, seed=999).resume(job.job_id)
saved_result = completed_again.result()
history = backend.history(limit=10, statuses=("completed",))
facts = {
"queued_state": queued_state,
"completed_state": restored.status().state,
"attempt_count": restored.status().to_dict()["execution_count"],
"completed_read_matches": saved_result.stable_hash()
== result.stable_hash(),
"attempts_after_completed_read": completed_again.status().to_dict()[
"execution_count"
],
"counts_total": sum(result.counts.values()),
"history_entries": len(history.entries),
"result_ref_present": history.entries[0].result_ref is not None,
"retry_max_attempts": retry.max_attempts,
}
payload = {
"track": "sdk_platform_engineer",
"level": "advanced",
"lesson": "retry_resume_history",
"facts": facts,
"boundaries": {
"hardware_execution": False,
"cloud_execution": False,
"network_accessed": False,
"credentials_loaded": False,
},
}
print(json.dumps(payload, sort_keys=True))
if __name__ == "__main__":
main()
python3 examples/user/tracks/sdk_platform_engineer/04_advanced_retry_resume_history_zh.py
store=... 启用持久化。第一个 Backend 在任务排队时保存程序与执行设置;第二个根据 job_id 恢复定义,在调用 result() 时执行。最后一个 Backend 将默认种子设为 999,但读取的是已经完成的结果,不会覆盖保存的种子,也不会重新采样。
{
"boundaries": {
"cloud_execution": false,
"credentials_loaded": false,
"hardware_execution": false,
"network_accessed": false
},
"facts": {
"attempt_count": 1,
"attempts_after_completed_read": 1,
"completed_read_matches": true,
"completed_state": "completed",
"counts_total": 8,
"history_entries": 1,
"queued_state": "queued",
"result_ref_present": true,
"retry_max_attempts": 3
},
"lesson": "retry_resume_history",
"level": "advanced",
"track": "sdk_platform_engineer"
}
两次观察到的尝试次数都应为一,completed_read_matches 应为 true。history_entries=1 统计保存的任务,不统计 Backend 对象个数。result_ref_present 表示历史条目含有结果引用,历史列表本身不包含完整结果正文。
示例使用 TemporaryDirectory,退出代码块时会删除存储及其产物文件。因此它展示的是目录仍存在时,在不同句柄之间恢复任务。若要在脚本结束后或新进程中恢复,应选择长期保存的路径,同时保留 SQLite 文件和对应的 .artifacts 目录。结果文件会经过校验和检查,只备份数据库文件并不完整。
重试策略是上限,不等于实际发生了重试¶
max_attempts=3 表示在策略允许的条件下最多尝试三次。本例一次成功,没有模拟临时故障。被识别为可重试的错误可能触发下一次尝试;校验、编译、参数错误、存储损坏和未分类的 Python 异常默认不重试。重复提交无效输入并不能修复它。
相同的显式 idempotency_key 会对相同的已保存定义去重。保持键不变却改变执行设置会产生冲突,不会覆盖之前的实验。需要新实验时,应使用新键。
检查恢复与任务身份¶
- 用同一个幂等键,提交两次完全相同的线路和选项,比较返回的任务 ID。
- 保持键不变,将采样次数从 8 改为 16,读取错误。
- 在没有配置存储的 Backend 上调用
resume(),此时缺少什么信息?
第一组提交指向同一任务。修改采样次数会报 JOB_IDEMPOTENCY_CONFLICT;未配置存储会报 LOCAL_BACKEND_STORE_REQUIRED。单凭任务 ID,不能定位任意外部数据库。
SQLite 协调同一主机上的本地尝试,不是分布式调度器,也不保证跨主机只执行一次。超时处理需要执行过程配合,不能强行打断原生数值代码。接口详情见重试、恢复与历史。下一课介绍接收任务前如何检查能力。