elastic / elastic/observability-migration-platform

[Blocked] Benchmark/migration perf: parallelize compile_all (dominant cost) and the two benchmark legs

Open
#228 1 comment 0 reactions 0 assignees View on GitHub
enhancement program:trust status:blocked workstream:stability
Dominant language
Python
Stars
6
Forks
8
Avg merge
2d 22h
Merged PRs (30d)
23

Description

## Summary

The dashboard benchmark (and real bulk migrations) spend most of their wall-clock in the **`--compile` step**, which runs `kb-dashboard-cli` **once per dashboard, serially**. Profiling shows compile — not translation and not the co-occurrence probes — is the dominant cost, in *both* the Grafana and Datadog legs. There are two independent levers worth ~2–3× total speedup; both leave migration *output* unchanged.

Not urgent (filed post-investigation for July, after the June 29 release). Captured here with full data so it can be picked up cold.

## Baseline (G:500 D:671, against the local lab)

| Component | Time | % of total |
|---|---|---|
| **Total** | **2060.8s (34.3 min)** | 100% |
| Grafana leg | 1108.2s | 53.8% |
| Datadog leg | 952.5s | 46.2% |
| └ co-occurrence probes (grafana) | 43.2s | 2.1% |

- 1171 dashboards / 32,551 panels.
- Grafana per-dashboard translate: median **0.03s**, p95 0.59s, p99 2.01s, max 3.17s → translation is *negligible*.
- The two legs run **serially and additively** (1108 + 953 ≈ 2061).
- (This run included one-time downloads of uncached dashboards; compare cached-to-cached when measuring fixes.)

## Where the time goes (G:50 D:50 phase profile, timestamped stream)

Total 202.9s:

| Phase | Time | % of total |
|---|---|---|
| Datadog leg | 75.7s | 37% |
| **Grafana — compile** (`kb-dashboard-cli` / `uvx`, per-dashboard) | **68.8s** | **34%** |
| Grafana — migrate/translate (50 dashboards) | 57.6s | 28% |
| Grafana — schema discovery | 0.7s | <1% |
| └ of which co-occurrence probes | 5.0s | 2.5% |

Per-dashboard translate is tiny; the grafana leg is dominated by **compile**. The Datadog leg also runs `--compile`, so it is compile-heavy too.

## Root cause

`compile_all` compiles each YAML serially, one `kb-dashboard-cli` subprocess per dashboard:

```python
# observability_migration/targets/kibana/compile.py:48
def compile_all(yaml_dir, compiled_dir):
Path(compiled_dir).mkdir(parents=True, exist_ok=True)
results = []
for yaml_file in sorted(Path(yaml_dir).glob("*.yaml")): # <-- serial
out_dir = Path(compiled_dir) / yaml_file.stem
out_dir.mkdir(parents=True, exist_ok=True)
success, output = compile_yaml(yaml_file, out_dir) # <-- 1 subprocess each
results.append((yaml_file.name, success, output))
return results
```

For G:500 D:671 that's **1171 sequential subprocess starts** (each `kb-dashboard-cli compile --input-file `). It's embarrassingly parallel — each file is independent and results are collected by name.

## Proposed work (ranked)

**Lever A — parallelize `compile_all` (engine, `compile.py`).** Thread-pool the per-file compiles (bounded worker pool, e.g. `min(os.cpu_count(), 8)`), preserve deterministic result collection by name. Expected: compile 69s → ~10–15s on a multi-core box (~25–30% of total). **Low risk** (independent subprocesses; identical output), and it speeds up *real* migrations too, not just the benchmark. **Recommended first.**

**Lever B — run the two benchmark legs in parallel (harness, tools repo `server.py`).** The Grafana and Datadog legs are independent but serial. Running them concurrently → `max(1108, 953)` ≈ 1108s ≈ **~46% off**. Caveat: the legs currently share one `INPUT_DIR`/`OUTPUT_DIR`, so this needs per-leg dirs threaded through `_build_migrate_cmd` / `_parse_report`. Medium effort/risk; benchmark-only win.

**Lever C — parallelize per-dashboard migrate/translate (engine).** 28% of the grafana leg at G:50. Bigger engine change; lower priority.

Levers A and B compound but both consume cores, so combined < additive. On a core-starved CI box the parallel speedup is smaller.

## Secondary finding — wasted co-occurrence probes

Probes are only ~2% of wall-clock **on the local lab** (~6ms each), but the *count* is very wasteful: at G:500, **6766 probe queries, 6202 errored (91.7%), 1527 error-fallbacks** (batched probe 400s → per-candidate re-probe fan-out, issue #182/#188). On a **remote/Serverless target** each probe is network-bound (10s timeout on errors), so this could flip from negligible to dominant. Worth a follow-up: skip/avoid probing fields that will 400, and/or cache negative results more aggressively. Tracked-adjacent to #182/#188.

## Tooling already available (to support this work)

Instrumentation built during the investigation (on branches, not yet merged/pushed):

- **Engine** branch `perf/188-probe-instrumentation`:
- `SchemaResolver.probe_stats()` + a `[probe-stats] {json}` line gated on `OBS_MIGRATE_PROBE_STATS=1` (queries/seconds/errors/fallbacks/candidates).
- `scripts/bench_probe_ab.py` — interleaved A/B of `migrate --es-url` across two commits, with a request-layer probe counter that works on un-instrumented commits.
- **Tools** branch `feat/probe-stats-instrumentation` (elastic/observability-migration-platform-tools):
- Captures `probe_stats` per run into `benchmark_history.json` + renders it (Insights → Speed card, Duration tooltip).
- `OBS_BENCH_ES_URL` override so the benchmark can target a non-Cloud ES (e.g. the open local lab) creds-free.

## How to reproduce

```bash
# Local lab (open ES, security off):
bash scripts/full_local_demo.sh --recreate-lab # ES :19200, Kibana :15601, seeded metrics-*

# Headless phase profile (timestamped stream → compile vs migrate split):
OBS_MIGRATE_PROBE_STATS=1 obs-migrate migrate --source grafana --input-mode files \
--input-dir infra/grafana/dashboards --output-dir /tmp/out --compile \
--es-url http://localhost:19200 --esql-index 'metrics-*'

# Full benchmark via the harness (tools repo):
OBS_MIG_DIR= OBS_BENCH_ES_URL=http://localhost:19200 python3 server.py
# → Dashboards Benchmark, Schema discovery on
```

## Measurement plan for a fix

1. Headless G:50/D:50 with timestamped stream before/after → isolate the **compile** delta (baseline compile = 68.8s).
2. One cached G:500/D:671 end-to-end before/after for the total (baseline = 34.3 min, but re-baseline **cached** to remove download noise).

## Notes / caveats

- All numbers above are against the **local lab**, where probes are cheap (~6ms). The manager's benchmark historically ran against a remote/Serverless cluster, where probe latency would reshuffle the ranking (see secondary finding).
- These optimizations change *speed only*, not migration output (compile produces the same per-dashboard NDJSON; legs are independent).

Contributor guide

Open the contributing guide

Research direction

Start in observability_migration/targets/kibana/compile.py at compile_all and inspect the tools repo server.py entry points _build_migrate_cmd and _parse_report. Run the headless G:50/D:50 profile, then compare cached before/after timings. Done means bounded parallel work preserves deterministic results and migration output, with benchmark legs using isolated directories if Lever B is attempted.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
performance, tooling
Issue type
Refactor
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.