Page the stale-index GC's existence check instead of loading the full entity-ID set
MIR-1389 shipped the background stale-index GC (#917). Each sweep builds a full in-memory set of live entity IDs via ListAllEntityIDs (pkg/entity/cleanup.go) so it can confirm the common "entity still exists" case with a map lookup instead of a per-entry linearizable read. That keeps steady-state point reads near zero, but it holds O(total entities) in memory per sweep, and the manual miren debug reindex path lists entities twice. Evan, CodeRabbit, and the biscuit auto-reviewer all flagged this as a non-blocking "should be paged eventually" nit; at our current entity counts (~10^5) it's single-digit MB, so it isn't urgent.
The optimization: drop the full valid-set and instead stream the collection scan (already page-by-page via scanPagedFunc) and resolve each page's candidate entity IDs with a batched GetEntities call, treating nil results as orphaned. Memory then stays bounded by one page rather than the whole entity set.
The catch, and why this wants a deliberate decision rather than a reflexive swap: GetEntities issues ~2 reads per entity (primary key + session prefix) inside a txn, so per-sweep etcd read ops go up relative to the current single bulk prefix scan. It's a memory-vs-read-volume trade, not a free win, and worth measuring. GetEntities also logs a Warn per missing entity, which would be noisy during convergence, so we'd probably want a quieter batched-existence helper.
Also noted from the same review: controllers/indexgc has no test for the GCController scheduling itself (Start/Stop, ticker loop, context cancellation). Cheap to add and would confirm the background wiring doesn't leak goroutines.
Not urgent; revisit if entity counts grow toward where the ID-set memory actually matters. Refs PR #917.