Skip to content

apigateway: ListRestApis scans every resource once per API (O(N x R)) #3305

Description

@allensanborn

Summary

ListRestApis resolves each API's root resource id by scanning the whole resource store, once per API, on an unpaginated endpoint. Listing N APIs therefore costs N full walks of every API Gateway resource in the emulator.

This was raised in review on #3269 and deferred there deliberately, because the clean fix is a storage change rather than a read-path tweak and did not belong in that PR.

Where it comes from

ApiGatewayService.getResources (ApiGatewayService.java:312-316):

public List<ApiGatewayResource> getResources(String region, String apiId) {
    getRestApi(region, apiId);
    String prefix = region + "::" + apiId + "::";
    return resourceStore.scan(k -> k.startsWith(prefix));
}

InMemoryStorage.scan (InMemoryStorage.java:35-41) walks every key in the store and filters:

public List<V> scan(Predicate<K> keyFilter) {
    List<V> result = new ArrayList<>();
    store.forEach((k, v) -> { if (keyFilter.test(k)) result.add(v); });

GET /restapis renders each API through toApiNode, which needs the root resource id, so serialising a list of N APIs performs N scans of all R resources: O(N x R). The endpoint has no pagination, so N is the full set.

The prefix filter does not help: it is applied per entry after the walk has already visited it, not used to seek.

Why it is not just theoretical

The root resource is the one member of GetRestApi that cannot be answered from the API record itself, so this cost is paid on the most common read path. It scales with total resource count across all APIs, not with the size of the API being rendered, so an account with one large API makes listing every other API slower too.

Suggested fix

Carry rootResourceId on the RestApi model and set it in createRestApi where the root resource is already being created, then read it directly instead of scanning.

That is a model field plus a persistence change, and it needs:

  • a fallback for APIs already stored without the field, so existing state keeps working
  • the same treatment on the importRestApi path, which also creates a root

An alternative that avoids the model change is a single scan that groups roots by API id, which fixes the N-times factor but still walks all resources once per list call.

I am happy to open a PR for the first option if that is the direction maintainers prefer.

Context

Found by review on #3269 (fix(apigateway): report rootResourceId and the API key defaults), which introduced the per-item root lookup that made this path hot. That PR fixes two correctness bugs in the same area and keeps the scan as-is on purpose.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    apigatewayAmazon API Gateway (REST/v1)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions