Skip to content

Commit e5c1e6f

Browse files
authored
Merge pull request #81 from PostHog/jakob/honest-bytes
delivery: price the byte-cut from honest sampled bytes, not entry nbytes
2 parents ca97069 + c5faabd commit e5c1e6f

3 files changed

Lines changed: 911 additions & 12 deletions

File tree

tests/unit/test_delivery.py

Lines changed: 229 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def _recording_flush(mgr):
4242
in-flight guard contract (the real _flush's finally block)."""
4343
calls = []
4444

45-
def _fake(dest, tables, through, trigger):
45+
def _fake(dest, tables, through, trigger, est_bytes=None):
4646
calls.append((dest, tables, through, trigger))
4747
with mgr._lock:
4848
mgr._inflight.discard(dest)
@@ -179,7 +179,7 @@ def test_no_second_flush_while_in_flight_and_swap_keeps_buffering():
179179
started = threading.Event()
180180
seen = []
181181

182-
def slow_flush(dest, tables, through, trigger):
182+
def slow_flush(dest, tables, through, trigger, est_bytes=None):
183183
seen.append((sum(t.num_rows for t in tables), through))
184184
started.set()
185185
release.wait(5)
@@ -1870,3 +1870,230 @@ def _record(pool, d, b, **kw):
18701870
# Cursor: clamp floor wins (50 > entry cov 10); the entry completing
18711871
# must not regress it, and nothing may persist beyond it early.
18721872
assert mgr.status_snapshot()["d1"].flushed_snapshot == 50
1873+
1874+
1875+
# ---------------------------------------------------------------------------
1876+
# Honest-bytes estimation (the 2026-08-26 nbytes-inflation regression class)
1877+
# ---------------------------------------------------------------------------
1878+
1879+
1880+
def _offset_sliced_table(parent_rows: int, offset: int, n: int) -> pa.Table:
1881+
"""An entry-shaped table reproducing the PRODUCTION nbytes inflation:
1882+
a slice of a dictionary-encoded column counts the entire shared
1883+
dictionary (measured ~41KB/row against ~hundreds true), and
1884+
combine_chunks does not prune it. Plain flat-array slices do NOT
1885+
over-report on current pyarrow — the dictionary is the mechanism."""
1886+
dictionary = pa.array(["v" * 200 + str(i) for i in range(20_000)])
1887+
indices = pa.array([i % 10 for i in range(parent_rows)], type=pa.int32())
1888+
parent = pa.table(
1889+
{
1890+
"a": pa.array(range(parent_rows), type=pa.int64()),
1891+
"s": pa.DictionaryArray.from_arrays(indices, dictionary),
1892+
}
1893+
)
1894+
return parent.slice(offset, n)
1895+
1896+
1897+
class TestHonestBytes:
1898+
def test_offset_slice_nbytes_overreports(self):
1899+
# Fixture sanity: the pathology must exist for these tests to
1900+
# mean anything. A 100-row slice of a 100K-row parent reports
1901+
# the parent's buffers.
1902+
sliced = _offset_sliced_table(100_000, 5_000, 100)
1903+
from viaduck.delivery import _estimate_row_bytes
1904+
1905+
honest = _estimate_row_bytes(sliced) * sliced.num_rows
1906+
assert sliced.nbytes > honest * 50
1907+
# combine_chunks alone does NOT fix it (dictionary retained):
1908+
assert sliced.combine_chunks().nbytes > honest * 50
1909+
1910+
def test_estimate_row_bytes_matches_honest_copy(self):
1911+
from viaduck.delivery import _estimate_row_bytes
1912+
1913+
sliced = _offset_sliced_table(100_000, 5_000, 2_000)
1914+
decoded = sliced.combine_chunks()
1915+
decoded = pa.Table.from_arrays(
1916+
[decoded.column("a"), decoded.column("s").cast(pa.string())],
1917+
names=["a", "s"],
1918+
)
1919+
honest_per_row = decoded.nbytes / sliced.num_rows
1920+
est = _estimate_row_bytes(sliced)
1921+
assert honest_per_row * 0.5 <= est <= honest_per_row * 2
1922+
1923+
def test_estimate_row_bytes_empty_table(self):
1924+
from viaduck.delivery import _estimate_row_bytes
1925+
1926+
assert _estimate_row_bytes(_offset_sliced_table(10, 0, 0)) == 0
1927+
1928+
def test_split_sizes_from_honest_bytes_not_inflated(self):
1929+
# Adaptive byte-cut against an offset-sliced entry: the split must
1930+
# come out near target/honest_per_row. The inflated math produced
1931+
# target/parent_bytes-per-row — crumbs ~100x smaller.
1932+
from viaduck.delivery import _estimate_row_bytes
1933+
1934+
mgr, _, _ = _manager(flush_batch_max_rows=0, flush_interval_seconds=0.0, flush_adaptive=True)
1935+
entry = _offset_sliced_table(100_000, 5_000, 10_000)
1936+
per_row = _estimate_row_bytes(entry)
1937+
target = per_row * 1_000 # honest target: ~1,000 rows
1938+
with mgr._lock:
1939+
mgr._flush_target["d1"] = target
1940+
fake, calls = _recording_flush(mgr)
1941+
_, epoch = mgr.read_plan()["d1"]
1942+
mgr.buffer("d1", entry, through_snapshot=5, epoch=epoch)
1943+
with patch.object(mgr, "_flush", fake):
1944+
mgr.maybe_flush()
1945+
assert calls, "expected a flush"
1946+
rows = calls[0][1][0].num_rows
1947+
assert 500 <= rows <= 1_100, f"split of {rows} rows is not honest-sized (crumb regression)"
1948+
1949+
def test_flush_receives_honest_est_bytes(self):
1950+
# The adaptive controller's evidence must be in the same units as
1951+
# the cut. Capture the est_bytes handed to _flush and compare to
1952+
# the honest sample, not the inflated entry nbytes.
1953+
from viaduck.delivery import _estimate_row_bytes
1954+
1955+
mgr, _, _ = _manager(flush_batch_max_rows=0, flush_interval_seconds=0.0, flush_adaptive=True)
1956+
entry = _offset_sliced_table(100_000, 5_000, 500)
1957+
per_row = _estimate_row_bytes(entry)
1958+
with mgr._lock:
1959+
mgr._flush_target["d1"] = per_row * 10_000 # far above the entry: no split
1960+
received = []
1961+
1962+
def _fake(dest, tables, through, trigger, est_bytes=None):
1963+
received.append(est_bytes)
1964+
with mgr._lock:
1965+
mgr._inflight.discard(dest)
1966+
1967+
_, epoch = mgr.read_plan()["d1"]
1968+
mgr.buffer("d1", entry, through_snapshot=5, epoch=epoch)
1969+
with patch.object(mgr, "_flush", _fake):
1970+
mgr.maybe_flush(shutdown=True)
1971+
assert received and received[0] is not None
1972+
assert received[0] == 500 * per_row
1973+
assert received[0] < entry.nbytes / 10 # decisively not the inflated number
1974+
1975+
def test_adaptive_growth_gate_passes_with_honest_fill(self):
1976+
# Frozen-controller regression: a target-sized split flush must
1977+
# satisfy the fill gate when judged in honest units.
1978+
mgr, _, _ = _manager(
1979+
flush_batch_max_rows=0,
1980+
flush_interval_seconds=0.0,
1981+
flush_adaptive=True,
1982+
)
1983+
with mgr._lock:
1984+
cur = mgr._flush_target["d1"] = 1_000_000
1985+
honest_batch = 900_000 # fill 0.9 >= 0.7 gate
1986+
mgr._adapt_flush_target("d1", duration=0.01, batch_bytes=honest_batch)
1987+
with mgr._lock:
1988+
grown = mgr._flush_target["d1"]
1989+
assert grown > cur, "growth gate did not pass with honest fill"
1990+
1991+
1992+
class TestHonestBytesTotality:
1993+
"""Review HIGH-1/HIGH-2: the estimator must never raise (poll-cycle
1994+
escape handler exits the pod) and must price NESTED dictionaries
1995+
(which inflate identically but dodge a top-level-only check)."""
1996+
1997+
def _dict_col(self, parent_rows):
1998+
dictionary = pa.array(["v" * 200 + str(i) for i in range(20_000)])
1999+
indices = pa.array([i % 10 for i in range(parent_rows)], type=pa.int32())
2000+
return pa.DictionaryArray.from_arrays(indices, dictionary)
2001+
2002+
def test_struct_of_dict_priced_honestly(self):
2003+
from viaduck.delivery import _estimate_row_bytes
2004+
2005+
col = pa.StructArray.from_arrays([self._dict_col(50_000)], names=["inner"])
2006+
t = pa.table({"s": col}).slice(1_000, 500)
2007+
est = _estimate_row_bytes(t)
2008+
assert 0 < est < 2_000, f"nested dict unpriced or inflated: {est}"
2009+
2010+
def test_list_of_dict_priced_honestly(self):
2011+
from viaduck.delivery import _estimate_row_bytes
2012+
2013+
inner = self._dict_col(50_000)
2014+
offsets = pa.array(range(0, 50_001), type=pa.int32())
2015+
col = pa.ListArray.from_arrays(offsets, inner)
2016+
t = pa.table({"l": col}).slice(1_000, 500)
2017+
est = _estimate_row_bytes(t)
2018+
assert 0 < est < 2_000, f"list<dict> unpriced or inflated: {est}"
2019+
2020+
def test_dict_of_struct_never_raises(self):
2021+
# dict<struct> casts are unsupported in pyarrow — the estimator
2022+
# must degrade to 0 (byte-cut inert), never propagate.
2023+
from viaduck.delivery import _estimate_row_bytes
2024+
2025+
struct_vals = pa.StructArray.from_arrays([pa.array(["x" * 100] * 50)], names=["f"])
2026+
indices = pa.array([i % 50 for i in range(5_000)], type=pa.int32())
2027+
col = pa.DictionaryArray.from_arrays(indices, struct_vals)
2028+
t = pa.table({"d": col})
2029+
est = _estimate_row_bytes(t) # must not raise
2030+
assert est >= 0
2031+
2032+
def test_multichunk_dictionary_entry_priced_honestly(self):
2033+
from viaduck.delivery import _estimate_row_bytes
2034+
2035+
parent = pa.table({"s": self._dict_col(100_000)})
2036+
cat = pa.concat_tables([parent.slice(i * 100, 100) for i in range(50)])
2037+
assert cat.nbytes > 50_000_000 # per-chunk whole-dictionary inflation
2038+
est = _estimate_row_bytes(cat)
2039+
assert 0 < est < 2_000
2040+
2041+
def test_aimd_grows_after_real_split_flush(self):
2042+
# End-to-end wiring (review MEDIUM-2): through the REAL _flush,
2043+
# a fast target-sized split flush must grow the target. Reverting
2044+
# the est_bytes plumbing in _flush makes this fail.
2045+
from viaduck.delivery import _estimate_row_bytes
2046+
2047+
mgr, _, _ = _manager(flush_batch_max_rows=0, flush_interval_seconds=0.0, flush_adaptive=True)
2048+
entry = _offset_sliced_table(100_000, 5_000, 10_000)
2049+
per_row = _estimate_row_bytes(entry)
2050+
target = per_row * 2_000
2051+
with mgr._lock:
2052+
mgr._flush_target["d1"] = target
2053+
_, epoch = mgr.read_plan()["d1"]
2054+
mgr.buffer("d1", entry, through_snapshot=5, epoch=epoch)
2055+
# Mock the destination WRITE only — _flush's own est/adapt logic
2056+
# stays real, which is the wiring under test.
2057+
with patch("viaduck.delivery.append_only", return_value=2_000):
2058+
assert mgr.maybe_flush() == 1
2059+
mgr._executor.shutdown(wait=True)
2060+
with mgr._lock:
2061+
grown = mgr._flush_target["d1"]
2062+
assert grown > target, f"AIMD did not grow ({target} -> {grown}); est_bytes plumbing broken"
2063+
2064+
def test_aimd_no_spurious_growth_on_underfilled_flush(self):
2065+
# THE discriminator for the _flush est_bytes plumbing (review N2):
2066+
# a 500-row flush against a 2,000-row-equivalent target has honest
2067+
# fill 0.25 < 0.7 -> NO growth. The reverted (inflated-nbytes)
2068+
# wiring reads ~4MB >= 0.7*target and grows spuriously — this test
2069+
# fails under that revert; the growth test alone cannot (inflated
2070+
# >= honest always, so growth happens in both worlds).
2071+
from viaduck.delivery import _estimate_row_bytes
2072+
2073+
mgr, _, _ = _manager(flush_batch_max_rows=0, flush_interval_seconds=0.0, flush_adaptive=True)
2074+
entry = _offset_sliced_table(100_000, 5_000, 500)
2075+
per_row = _estimate_row_bytes(entry)
2076+
target = per_row * 2_000
2077+
with mgr._lock:
2078+
mgr._flush_target["d1"] = target
2079+
_, epoch = mgr.read_plan()["d1"]
2080+
mgr.buffer("d1", entry, through_snapshot=5, epoch=epoch)
2081+
with patch("viaduck.delivery.append_only", return_value=500):
2082+
assert mgr.maybe_flush(shutdown=True) == 1
2083+
mgr._executor.shutdown(wait=True)
2084+
with mgr._lock:
2085+
after = mgr._flush_target["d1"]
2086+
assert after == target, f"spurious growth {target} -> {after}: _flush fed inflated bytes to the fill gate"
2087+
2088+
def test_fixed_size_list_of_dict_priced_honestly(self):
2089+
# Review N1: fixed_size_list<dict> dodged the rewrite and kept
2090+
# ~19x inflation silently.
2091+
from viaduck.delivery import _estimate_row_bytes
2092+
2093+
dictionary = pa.array(["v" * 200 + str(i) for i in range(20_000)])
2094+
indices = pa.array([i % 10 for i in range(20_000)], type=pa.int32())
2095+
inner = pa.DictionaryArray.from_arrays(indices, dictionary)
2096+
col = pa.FixedSizeListArray.from_arrays(inner, 2)
2097+
t = pa.table({"fl": col}).slice(1_000, 500)
2098+
est = _estimate_row_bytes(t)
2099+
assert 0 < est < 3_000, f"fixed_size_list<dict> unpriced or inflated: {est}"

0 commit comments

Comments
 (0)