"""
Ch6 配套代码 3 / 3 —— appendfsync 三档刷盘策略性能对比

演示：
  对同样一批写入（默认 10 万 SET），分别在 always / everysec / no 三种刷盘
  策略下用 pipeline 跑，统计耗时与 QPS。直观感受：always 多慢、everysec
  与 no 之间差距其实不大。

⚠️ 运行前请确认：
  - 当前实例不是生产实例
  - 已开启 AOF（脚本会临时切换 appendfsync，结束后还原）
"""

import time
import redis

r = redis.Redis(host="127.0.0.1", port=6379, decode_responses=True)

TOTAL_OPS    = 100_000     # 默认写入条数
PIPELINE_BAT = 200         # 每个 pipeline 的批大小
KEY_PREFIX   = "perf:fsync"


def section(title: str) -> None:
    print("\n" + "=" * 60)
    print(title)
    print("=" * 60)


def ensure_aof() -> None:
    if r.config_get("appendonly").get("appendonly") != "yes":
        print("  ⚠️ 临时开启 appendonly = yes")
        r.config_set("appendonly", "yes")
        time.sleep(0.3)


def cleanup() -> None:
    cur = b"0"
    cur, keys = r.scan(cursor=0, match=f"{KEY_PREFIX}:*", count=2000)
    while keys:
        if keys:
            r.delete(*keys)
        if cur in (0, "0", b"0"):
            break
        cur, keys = r.scan(cursor=cur, match=f"{KEY_PREFIX}:*", count=2000)


def run_one(strategy: str, total: int) -> tuple:
    r.config_set("appendfsync", strategy)
    cleanup()

    start = time.perf_counter()
    pipe = r.pipeline(transaction=False)
    for i in range(total):
        pipe.set(f"{KEY_PREFIX}:{strategy}:{i}", f"v-{i}")
        if (i + 1) % PIPELINE_BAT == 0:
            pipe.execute()
    pipe.execute()
    elapsed = time.perf_counter() - start

    qps = total / elapsed if elapsed > 0 else 0
    info = r.info("persistence")
    delayed = info.get("aof_delayed_fsync", 0)
    return elapsed, qps, delayed


def main() -> None:
    section("appendfsync 三档刷盘策略 · 吞吐对比")
    print(f"  写入量：{TOTAL_OPS:,} SET  ·  pipeline 批大小：{PIPELINE_BAT}")
    print(f"  ⚠️ always 在 SSD 上仍可能比 everysec 慢 5~20 倍\n")

    original = r.config_get("appendfsync").get("appendfsync", "everysec")
    results = []
    try:
        ensure_aof()
        for strat in ("always", "everysec", "no"):
            print(f"  → 跑 {strat} ...")
            elapsed, qps, delayed = run_one(strat, TOTAL_OPS)
            results.append((strat, elapsed, qps, delayed))
            print(f"    耗时 {elapsed:7.2f} s  ·  QPS {qps:>10,.0f}"
                  f"  ·  aof_delayed_fsync = {delayed}")
    finally:
        r.config_set("appendfsync", original)
        cleanup()

    section("结果汇总（以 everysec 为基准）")
    base = next((q for s, _, q, _ in results if s == "everysec"), 1) or 1
    print(f"  {'策略':<12} {'耗时(s)':<12} {'QPS':<14} {'相对 everysec':<14}")
    print(f"  {'-'*12} {'-'*12} {'-'*14} {'-'*14}")
    for strat, elapsed, qps, _ in results:
        ratio = qps / base
        print(f"  {strat:<12} {elapsed:<12.2f} {qps:<14,.0f} {ratio:<14.2%}")

    print("""
  💡 解读：
    · always   每条命令同步 fsync()，磁盘 IO 是瓶颈；HDD 更明显
    · everysec 主线程只 write 进 page cache，BIO 异步 fsync，性能/安全最佳折中
    · no       完全交给 OS，性能略好于 everysec，但宕机最多丢 30s+
    · aof_delayed_fsync 持续上涨 → 磁盘扛不住，需要换 SSD 或换策略
    """)


if __name__ == "__main__":
    try:
        main()
    except redis.ConnectionError as e:
        print(f"❌ Redis 连接失败: {e}")
