fix(data): 分区改多 part 追加写+批量提交,根治 day-1 O(n²) 劣化 [nas]
09-07 首跑实锤: append_parquet 每 unit 整读整写当日唯一 part-0, news 段 13万+行后 5.3s/股(应1.15s)且随行数恶化,10:30 墙钟必跑不完且逐日更糟。 修(两层): - DayBuffer: 当日新行内存缓冲→flush 落新 part-N.parquet(只写新行, 旧数据永不重写);id 去重职责收敛到调用方(账本+近窗双层,原就存在) - Ctx 批量提交: unit_done 延后 marker 入批,每 FLUSH_EVERY unit 一次 commit=数据→账本→marker 依次落盘(崩溃=整批重拉,绝不产生 marker-done-但数据缺的洞);run_lane 段首清缓冲/finally 兜底 commit - ③全文回补 known 检查补近窗兜底(账本 flush 崩溃窗防重) - readers 全改 part-* glob(RecentIndex/_iter_partitions/verify_corpus) - 测试 54:新增 多part语义/flush崩溃一致性/批量提交次序 三钉
This commit is contained in:
@@ -165,33 +165,59 @@ def test_recent_index_reads_partitions(root):
|
||||
|
||||
# ---------- parquet 追加(append-only + 原子) ----------
|
||||
|
||||
def test_append_parquet_dedup_and_atomic(root):
|
||||
def test_append_parquet_buffers_then_flush_batch(root):
|
||||
"""多 part 语义(09-07 首跑 O(n²) 修复): append 进内存缓冲, flush 落新 part
|
||||
文件只写新行; id 去重职责在调用方(账本+近窗)。"""
|
||||
import pandas as pd
|
||||
today = dt.date.today().isoformat()
|
||||
rows1 = [{"announcement_id": "a1", "title": "x"}, {"announcement_id": "a2", "title": "y"}]
|
||||
cd.append_parquet("ann_meta", rows1, id_col="announcement_id")
|
||||
part = root / "ann_meta" / f"dt={today}" / "part-0.parquet"
|
||||
df = pd.read_parquet(part)
|
||||
assert len(df) == 2
|
||||
# 同日追加含重复 id → 只进新 id
|
||||
rows2 = [{"announcement_id": "a2", "title": "y2"}, {"announcement_id": "a3", "title": "z"}]
|
||||
cd.append_parquet("ann_meta", rows2, id_col="announcement_id")
|
||||
df = pd.read_parquet(part)
|
||||
assert sorted(df["announcement_id"]) == ["a1", "a2", "a3"]
|
||||
assert not list((root / "ann_meta" / f"dt={today}").glob("*.tmp")) # 无残留 tmp
|
||||
cd._DAY_BUFFERS.clear()
|
||||
cd.append_parquet("ann_meta", [{"announcement_id": "a1"}, {"announcement_id": "a2"}])
|
||||
d = root / "ann_meta" / f"dt={today}"
|
||||
assert not list(d.glob("part-*.parquet")) # 未 flush 无文件
|
||||
cd.flush_domains()
|
||||
assert sorted(p.name for p in d.glob("part-*.parquet")) == ["part-0.parquet"]
|
||||
# 第二批 → 新 part-1(旧 part-0 永不重写)
|
||||
cd.append_parquet("ann_meta", [{"announcement_id": "a3"}])
|
||||
cd.flush_domains()
|
||||
df0 = pd.read_parquet(d / "part-0.parquet")
|
||||
df1 = pd.read_parquet(d / "part-1.parquet")
|
||||
assert sorted(df0["announcement_id"]) == ["a1", "a2"] and len(df1) == 1
|
||||
assert not list(d.glob("*.tmp"))
|
||||
|
||||
|
||||
def test_append_parquet_crash_leaves_no_partial(root):
|
||||
"""写中途抛错 → 不留 tmp/半截文件(下次重拉幂等前提)。"""
|
||||
"""flush 写中途抛错 → 不留 tmp/半截文件(下次重拉幂等前提)。"""
|
||||
import pandas as pd
|
||||
cd._DAY_BUFFERS.clear()
|
||||
cd.append_parquet("ann_meta", [{"announcement_id": "a1"}])
|
||||
with patch.object(pd.DataFrame, "to_parquet", side_effect=RuntimeError("disk")):
|
||||
with pytest.raises(RuntimeError):
|
||||
cd.append_parquet("ann_meta", [{"announcement_id": "a1"}],
|
||||
id_col="announcement_id")
|
||||
cd.flush_domains()
|
||||
today = dt.date.today().isoformat()
|
||||
assert not list((root / "ann_meta" / f"dt={today}").glob("*"))
|
||||
|
||||
|
||||
def test_commit_batches_data_before_markers(root, monkeypatch):
|
||||
"""批量提交次序=数据→账本→marker: unit_done 只入批,commit 才双双落盘
|
||||
(崩溃时宁可整批重拉,不产生 marker-done-但数据缺的洞)。"""
|
||||
import pandas as pd
|
||||
ctx = cd.Ctx(lane="daily")
|
||||
ledgers = {"ann_meta": cd.IdLedger("ann_meta")}
|
||||
ctx.set_stores(ledgers)
|
||||
cd._DAY_BUFFERS.clear()
|
||||
cd.append_parquet("ann_meta", [{"announcement_id": "m1", "title": "x"}])
|
||||
ledgers["ann_meta"].add(["m1"]) # 真实流程 _absorb_new 负责
|
||||
ctx.unit_done("ann", "000001")
|
||||
today = dt.date.today().isoformat()
|
||||
d = root / "ann_meta" / f"dt={today}"
|
||||
assert not d.exists() or not list(d.glob("part-*.parquet"))
|
||||
assert not cd.is_done("daily", "ann", "000001")
|
||||
ctx.commit()
|
||||
assert (d / "part-0.parquet").exists()
|
||||
assert cd.is_done("daily", "ann", "000001")
|
||||
assert cd.IdLedger("ann_meta").has_any(["m1"]) == [True]
|
||||
|
||||
|
||||
# ---------- unit marker ----------
|
||||
|
||||
def test_marker_done_and_skip(root):
|
||||
|
||||
Reference in New Issue
Block a user