Make Zoekt storage buffer factor dynamic
# Make Zoekt storage buffer factor dynamic
## Problem
Zoekt's storage planner (`PlanningService`) uses a hardcoded `buffer_factor: 3` when
computing how much disk to reserve per replica:
```ruby
# ee/app/services/search/zoekt/planning_service.rb:248
def scaled_size(stats)
stats.repository_size * buffer_factor # always 3×
end
```
This `3×` reservation is stored in `zoekt_indices.reserved_storage_bytes` and directly
determines how many namespaces can fit on a node (via `unclaimed_storage_bytes`).
In practice, observed Zoekt disk usage on GitLab.com is closer to **`0.5×`** the source
repository Git size. The `3×` reservation is intentionally conservative to ensure headroom
during initial indexing — but once a namespace is indexed, the actual ratio is well-established
and the reservation is significantly over-sized. This means nodes are under-utilized compared
to their real capacity.
## Proposal
Replace the hardcoded `buffer_factor: 3` with a **data-driven per-namespace ratio**.
Once a namespace has been indexed, the actual expansion ratio is already tracked in
`zoekt_enabled_namespaces.metadata['last_used_storage_bytes']`. Use this observed ratio
(plus a safety margin) as the `buffer_factor` when re-planning, rather than the
conservative `3×` default.
```ruby
def dynamic_buffer_factor(enabled_namespace, safety_margin: 1.2)
observed = enabled_namespace.metadata['last_used_storage_bytes'].to_f
source = enabled_namespace.namespace.root_storage_statistics&.repository_size.to_f
return 3.0 if source.zero? || observed.zero? # fallback for new/unindexed namespaces
(observed / source) * safety_margin
end
```
New namespaces (no observed data yet) fall back to `3×` until real usage data is available.
Over time, reservations automatically converge toward actual usage patterns, improving node
utilization without sacrificing headroom for growth.
## Current data model
Key locations in the codebase:
| Purpose | File | Line |
|---|---|---|
| Hardcoded `buffer_factor: 3` | `ee/app/services/search/zoekt/planning_service.rb` | 70 |
| Formula: `repo_size × buffer_factor` | `ee/app/services/search/zoekt/planning_service.rb` | 248 |
| Orphaned `BUFFER_FACTOR = 3` | `ee/app/services/search/zoekt/scheduling_service.rb` | 125 |
| Caller (no buffer_factor passed) | `ee/app/services/search/zoekt/rollout_service.rb` | 39–43 |
| `reserved_storage_bytes` stored (3× value) | `ee/app/services/search/zoekt/provisioning_service.rb` | 123 |
| Actual usage: `used_storage_bytes` | `ee/app/models/search/zoekt/index.rb` | 163–166 |
| Actual usage: `last_used_storage_bytes` | `ee/app/models/search/zoekt/enabled_namespace.rb` | 86–93 |
## Expected outcomes
- Node utilization improves as the planner uses realistic reservations for already-indexed namespaces
- New namespaces still get a safe `3×` initial reservation until observed data is available
- Reservations automatically track actual usage patterns per namespace over time
- No new settings or admin UI required
## Documentation
Update `doc/integration/zoekt/_index.md` (the storage estimation section) to reflect the new
behavior:
- Explain that `3×` is the initial reservation for new namespaces
- Explain that GitLab automatically adjusts the reservation downward as actual usage data
becomes available
- Update the examples to reflect that steady-state storage will be lower than the initial `3×`
reservation
issue