跳转至

恢复保存的本地任务,复用已完成结果

创建一个持久化 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 会对相同的已保存定义去重。保持键不变却改变执行设置会产生冲突,不会覆盖之前的实验。需要新实验时,应使用新键。

检查恢复与任务身份

  1. 用同一个幂等键,提交两次完全相同的线路和选项,比较返回的任务 ID。
  2. 保持键不变,将采样次数从 8 改为 16,读取错误。
  3. 在没有配置存储的 Backend 上调用 resume(),此时缺少什么信息?

第一组提交指向同一任务。修改采样次数会报 JOB_IDEMPOTENCY_CONFLICT;未配置存储会报 LOCAL_BACKEND_STORE_REQUIRED。单凭任务 ID,不能定位任意外部数据库。

SQLite 协调同一主机上的本地尝试,不是分布式调度器,也不保证跨主机只执行一次。超时处理需要执行过程配合,不能强行打断原生数值代码。接口详情见重试、恢复与历史。下一课介绍接收任务前如何检查能力。

English version

SDK 1.0.8a · `8b227bff`