java-topology/defects/elasticsearch/unit/test_elasticsearch_scale.py
russell@unturf.com b878442549 test: add Elasticsearch stacked + scale benchmarks
test_elasticsearch_scale.py: single-patch scaling (10K to 1M docs, k=20/100/500)
test_elasticsearch_stacked.py: all 3 patches combined with multi-node projections

Results: 42-node, 1B docs, k=500 SIEM: 940 days -> 18 days (53x, 922 days saved)
2026-04-16 11:17:18 -04:00

146 lines
5.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Elasticsearch CWE-407 scaling benchmark — projected wall-clock savings.
Simulates bulk ingest with append dedup (elasticsearch-0001) at production scale.
Each document has an array field with k existing values and receives N new values
with ~50% overlap (realistic for tag/label append pipelines).
The cost per document:
Before: O(N×k) for the list.contains() calls (k grows as values append)
After: O(N) for the set.add() calls (O(1) each)
Total cost for D documents:
Before: D × O(N×k) ≈ D × N × k/2 comparisons (average)
After: D × O(N) ≈ D × N hash lookups
"""
import time
import sys
def append_values_before(existing, new_values):
"""Original: list.contains O(k) per value."""
result = list(existing)
for val in new_values:
if val not in result:
result.append(val)
return result
def append_values_after(existing, new_values):
"""Fixed: HashSet shadow O(1) per value."""
result = list(existing)
seen = set(result)
for val in new_values:
if val not in seen:
result.append(val)
seen.add(val)
return result
def benchmark_bulk_ingest(num_docs, field_size, new_per_doc, overlap_pct=50):
"""
Simulate bulk ingest of num_docs documents.
Each document has an array field with field_size existing values.
Each append adds new_per_doc values with overlap_pct% already present.
"""
overlap = int(new_per_doc * overlap_pct / 100)
unique_new = new_per_doc - overlap
# Build representative document field and new values
existing = list(range(field_size))
new_values = list(range(field_size - overlap, field_size - overlap + new_per_doc))
# Time BEFORE (list.contains)
t0 = time.perf_counter()
for _ in range(num_docs):
append_values_before(existing, new_values)
t_before = time.perf_counter() - t0
# Time AFTER (set shadow)
t0 = time.perf_counter()
for _ in range(num_docs):
append_values_after(existing, new_values)
t_after = time.perf_counter() - t0
speedup = t_before / t_after if t_after > 1e-9 else float('inf')
return t_before, t_after, speedup
def format_time(seconds):
if seconds < 0.001:
return f"{seconds*1_000_000:.0f}us"
elif seconds < 1:
return f"{seconds*1000:.1f}ms"
elif seconds < 60:
return f"{seconds:.2f}s"
elif seconds < 3600:
return f"{seconds/60:.1f}min"
else:
return f"{seconds/3600:.1f}hr"
def main():
print("=" * 90)
print("Elasticsearch CWE-407 Scaling Benchmark — IngestDocument.appendValues()")
print("=" * 90)
print()
# Scenario 1: Small array fields (k=20, N=10) — tags, labels, categories
print("Scenario 1: Tag/label append (k=20 existing, N=10 new, 50% overlap)")
print(f"{'Documents':>15} {'Before':>12} {'After':>12} {'Speedup':>10} {'Saved':>12}")
print("-" * 65)
for num_docs in [10_000, 100_000, 1_000_000]:
t_b, t_a, spd = benchmark_bulk_ingest(num_docs, 20, 10)
saved = t_b - t_a
print(f"{num_docs:>15,} {format_time(t_b):>12} {format_time(t_a):>12} {spd:>9.0f}x {format_time(saved):>12}")
print()
# Scenario 2: Medium array fields (k=100, N=50) — enrichment pipelines
print("Scenario 2: Enrichment pipeline (k=100 existing, N=50 new, 50% overlap)")
print(f"{'Documents':>15} {'Before':>12} {'After':>12} {'Speedup':>10} {'Saved':>12}")
print("-" * 65)
for num_docs in [10_000, 100_000, 1_000_000]:
t_b, t_a, spd = benchmark_bulk_ingest(num_docs, 100, 50)
saved = t_b - t_a
print(f"{num_docs:>15,} {format_time(t_b):>12} {format_time(t_a):>12} {spd:>9.0f}x {format_time(saved):>12}")
print()
# Scenario 3: Large array fields (k=500, N=200) — IoT sensor tags, log enrichment
print("Scenario 3: Large field append (k=500 existing, N=200 new, 50% overlap)")
print(f"{'Documents':>15} {'Before':>12} {'After':>12} {'Speedup':>10} {'Saved':>12}")
print("-" * 65)
for num_docs in [10_000, 100_000, 1_000_000]:
t_b, t_a, spd = benchmark_bulk_ingest(num_docs, 500, 200)
saved = t_b - t_a
print(f"{num_docs:>15,} {format_time(t_b):>12} {format_time(t_a):>12} {spd:>9.0f}x {format_time(saved):>12}")
print()
# Scenario 4: Projected at 10M/100M scale (extrapolate from 10K measurement)
print("Scenario 4: Projected savings at production scale (k=100, N=50)")
print("(Extrapolated linearly from measured 10K document benchmark)")
print("-" * 65)
t_b_10k, t_a_10k, spd = benchmark_bulk_ingest(10_000, 100, 50)
for label, multiplier in [("10K", 1), ("100K", 10), ("1M", 100),
("10M", 1_000), ("100M", 10_000)]:
proj_before = t_b_10k * multiplier
proj_after = t_a_10k * multiplier
proj_saved = proj_before - proj_after
print(f" {label:>6} docs: before={format_time(proj_before):>10} "
f"after={format_time(proj_after):>10} "
f"saved={format_time(proj_saved):>10} ({spd:.0f}x)")
print()
print("=" * 90)
print(f"Conclusion: at k=100, N=50 (typical enrichment pipeline),")
print(f" our patch saves {format_time(t_b_10k - t_a_10k)} per 10K documents ({spd:.0f}x faster).")
print(f" At 100M documents: projected savings of {format_time((t_b_10k - t_a_10k) * 10_000)}.")
print("=" * 90)
if __name__ == "__main__":
main()