Repository navigation
Description
Summary
The ListAttributes and GetAttribute RPCs return all attribute values for each attribute with no pagination or limit. If an attribute definition accumulates a large number of values, this can cause excessive memory usage, large response payloads, and high latency.
Details
Root Cause
Both ListAttributes and GetAttribute use the same hydrateAttribute path, which deserializes the full valuesJSON blob from the database query. The underlying SQL (listAttributesDetail, getAttribute) aggregates values via JSON_AGG with no limit:
-- service/policy/db/attributes.sql.go (listAttributesDetail)
JSON_AGG(
JSON_BUILD_OBJECT(
'id', avt.id,
'value', avt.value,
'active', avt.active,
'fqn', CONCAT(fqns.fqn, '/value/', avt.value)
) ORDER BY ARRAY_POSITION(ad.values_order, avt.id)
) FILTER (WHERE avt.id IS NOT NULL) AS valuesThe LEFT JOIN on attribute_values (lines 787–795 in attributes.sql.go) has no filter or limit on the number of values joined per attribute definition.
Affected RPCs
| RPC | Impact |
|---|---|
ListAttributes |
Every attribute in the paginated response carries all of its values. Pagination only controls the number of attribute definitions returned, not the values within each. |
GetAttribute |
Returns a single attribute with all of its values. |
ListAttributesByFqns |
Same hydrateAttribute path — all values included. |
No Alternative Endpoint
The ListAttributeValues RPC was deprecated and removed — it returns Unimplemented:
// service/policy/attributes/attributes.go:309
func (s *AttributesService) ListAttributeValues(_ context.Context, _ *connect.Request[attributes.ListAttributeValuesRequest]) (*connect.Response[attributes.ListAttributeValuesResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, ErrDeprecatedListAttributeValues)
}This means there is currently no paginated way to retrieve attribute values for a given attribute definition.
Potential Impact
- Memory pressure on Postgres:
JSON_AGGbuilds the entire JSON array in memory server-side before returning. - Large gRPC responses: An attribute with hundreds of thousands of values produces a response payload in the tens or hundreds of MB.
- Latency: Aggregation, serialization (protojson), and network transfer all scale linearly with value count.
- Amplification on
ListAttributes: The cost is multiplied by every attribute in the page — a page of 25 attributes each with large value sets compounds the problem.
Suggested Approaches
- Add a limit on values within the
JSON_AGG: Cap the number of values returned per attribute in list/get queries (e.g., return the first N values and ahas_moreindicator). - Re-introduce a paginated
ListAttributeValuesRPC: Provide a dedicated endpoint for fetching values for a specific attribute definition with proper pagination support. - Add an option to exclude values from list responses: Allow callers to opt out of values in
ListAttributeswhen they only need attribute metadata, reducing payload size for common use cases.
Activity
eugenioenko commentedon Jul 10, 2026
Implementation Pattern
Per direction to match existing proto patterns, the best template to follow is
ListRegisteredResourceValuesin the registered resources service — it's a clean paginated sub-resource listing with a parent ID filter.Proto Pattern
From
service/policy/registeredresources/registered_resources.proto(lines 284-299):SQL Pattern
From
service/policy/db/queries/registered_resources.sql(lines 242-289), uses aWITH countedCTE +LIMIT/OFFSET:Go DB Layer Pattern
From
service/policy/db/registered_resources.go(lines 467-532):getRequestedLimitOffset()to extract pagination params with defaultsc.listCfg.limitMaxgetNextOffset()to compute next page offsetPageResponseGo Service Handler Pattern
From
service/policy/registeredresources/registered_resources.go(lines 281-292) — thin wrapper:Notes
ListAttributeValuesalready followed this exact pattern before it was deprecated in PR fix(policy): deprecate ListAttributeValues in favor of existing GetAttribute #3108. The fix is essentially re-introducing it with current conventions.PageRequest/PageResponse) are defined inservice/policy/selectors.proto.getRequestedLimitOffset,getNextOffset) are inservice/policy/db/utils.go.eugenioenko commentedon Jul 13, 2026
ADR with proposed fix
#3747
eugenioenko commentedon Aug 6, 2026
The current API doesn't return RANK either and attribute values cannot be sorted by rank. This might be a separate issue but V2 implementation needs to include RANK and ability to sort by rank. This way if there is 1M attribute values and you want to get the one from lowers rank instead of highest.