Skip to content

Optimize VaultPendingSwapOuts query to use the ByVault index instead of a full collection scan #227

Description

@nullpointer0x00

Summary

VaultPendingSwapOuts (keeper/query_server.go:301-315) retrieves a single vault's pending swap-outs by scanning the entire PendingSwapOutQueue.IndexedMap and filtering in memory, making the query O(N) in the total number of pending swap-outs across all vaults instead of O(k) in the requested vault's entries. A ByVault secondary index already exists for exactly this access pattern, so the cost grows with global queue size even when the target vault has few or zero pending swap-outs.

Details

The handler uses query.CollectionFilteredPaginate over the full primary map with a per-entry address comparison:

swapOuts, pageRes, err := query.CollectionFilteredPaginate(
    ctx,
    k.PendingSwapOutQueue.IndexedMap,
    req.Pagination,
    func(key collections.Triple[int64, uint64, sdk.AccAddress], _ types.PendingSwapOut) (include bool, err error) {
        return vault.Address == key.K3().String(), nil   // in-memory filter over every entry
    },
    ...
)

The efficient path is already implemented in queue/pending_swap_out.go:

  • ByVault *indexes.Multi[...] (line 18) — the secondary index keyed by vault address.
  • WalkByVault (line 171) — IndexedMap.Indexes.ByVault.MatchExact(ctx, vaultAddr), a range scan returning only the requested vault's entries.

The sibling PendingSwapOuts ("list all") endpoint correctly uses query.CollectionPaginate on the full map; only the vault-scoped endpoint uses the wrong traversal.

Constraint: preserve pagination

QueryVaultPendingSwapOutsRequest has a pagination field and the response returns pageRes, so the endpoint's pagination contract must be kept. The straightforward WalkByVault rewrite loads all of a vault's entries into a slice and drops pagination, which would change the API and risk unbounded memory for a vault with many pending swap-outs. The fix should paginate over the ByVault index iterator (e.g. via MatchExact + SDK iterator pagination), not collect everything in memory.

Proposed change

  • Rewrite VaultPendingSwapOuts to traverse the ByVault index (MatchExact / the WalkByVault access pattern) rather than CollectionFilteredPaginate over the full IndexedMap.
  • Preserve the existing pagination request/response behavior by paginating over the index-scoped iterator.

Tasks

  • Rewrite VaultPendingSwapOuts to use the ByVault index path.
  • Keep request/response pagination intact (do not load all entries into memory).
  • Add a test asserting only the target vault's entries are returned (and entries for other vaults are excluded), including the empty-result case for a vault with no pending swap-outs.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions