Skip to content

Conversation

@strantalis
Copy link
Member

@strantalis strantalis commented Dec 4, 2025

Proposed Changes

Add performance optimizations to subject mapping evaluation hot paths:

Flattening: O(1) selector lookups via index map

  • GetFromFlattened_Large: 43x faster (228ns → 5.3ns), zero allocs
  • GetFromFlattened_MultipleLookups: 18x faster (509ns → 28ns), zero allocs
  • Index only built for structures ≥8 items; smaller structures use fast linear scan

EvaluateCondition: Optimized IN/NOT_IN evaluation

  • EvaluateCondition_IN_Large: 57% faster (2.1µs → 0.9µs)
  • EvaluateCondition_IN_Hit: 79% faster (433ns → 93ns), zero allocs
  • Small value sets (≤4) use linear search to avoid map allocation

Per-entity caches for SubjectSet/ConditionGroup evaluation

  • RepeatedSubjectSets: 48% faster, 45% fewer allocs
  • Cached variants used only in batch evaluation; single-call exports avoid cache overhead

Action deduplication using per-valueFQN accumulator

  • WithActions_LargeAttrCount: 22% faster, 57% fewer allocations

Overall impact

  • -38% average improvement in subject mapping evaluation time
  • -66% average improvement in flattening operations
  • Significant allocation reductions across all complex benchmarks

Trade-offs

  • Flatten_NestedEntity: +28% slower for large structures (index building cost)
  • This cost is amortized over multiple GetFromFlattened calls per entity (break-even: ~3 lookups)
  • Small structures have zero overhead due to index threshold
goos: darwin
goarch: arm64
pkg: github.com/opentdf/platform/service/internal/subjectmappingbuiltin
cpu: Apple M4 Max
                                                     │  sm-old.txt  │             sm-new.txt              │
                                                     │    sec/op    │   sec/op     vs base                │
EvaluateCondition_IN_Large-16                          2132.5n ± 4%   917.5n ± 0%  -56.98% (p=0.000 n=20)
EvaluateCondition_IN_Hit-16                            433.25n ± 2%   92.76n ± 2%  -78.59% (p=0.000 n=20)
EvaluateSubjectMappings_LargeAttrCount-16               14.54µ ± 1%   11.27µ ± 1%  -22.49% (p=0.000 n=20)
EvaluateSubjectMappings_RepeatedSubjectSets-16         11.049µ ± 0%   5.784µ ± 1%  -47.66% (p=0.000 n=20)
EvaluateSubjectMappingsWithActions_LargeAttrCount-16    38.43µ ± 1%   30.06µ ± 1%  -21.78% (p=0.000 n=20)
EvaluateSubjectMappingMultipleEntitiesWithActions-16    162.4µ ± 2%   143.2µ ± 1%  -11.83% (p=0.000 n=20)
EvaluateSubjectSet-16                                   65.28n ± 1%   55.27n ± 1%  -15.33% (p=0.000 n=20)
EvaluateSubjectMappings-16                              1.111µ ± 1%   1.061µ ± 1%   -4.50% (p=0.000 n=20)
geomean                                                 4.013µ        2.473µ       -38.36%

                                                     │   sm-old.txt   │                sm-new.txt                 │
                                                     │      B/op      │     B/op      vs base                     │
EvaluateCondition_IN_Large-16                            960.0 ± 0%      1832.0 ± 0%   +90.83% (p=0.000 n=20)
EvaluateCondition_IN_Hit-16                              960.0 ± 0%         0.0 ± 0%  -100.00% (p=0.000 n=20)
EvaluateSubjectMappings_LargeAttrCount-16              35.70Ki ± 0%     18.85Ki ± 0%   -47.19% (p=0.000 n=20)
EvaluateSubjectMappings_RepeatedSubjectSets-16         28.42Ki ± 0%     12.36Ki ± 0%   -56.49% (p=0.000 n=20)
EvaluateSubjectMappingsWithActions_LargeAttrCount-16   62.20Ki ± 0%     38.55Ki ± 0%   -38.03% (p=0.000 n=20)
EvaluateSubjectMappingMultipleEntitiesWithActions-16   253.1Ki ± 0%     217.1Ki ± 0%   -14.22% (p=0.000 n=20)
EvaluateSubjectSet-16                                    0.000 ± 0%       0.000 ± 0%         ~ (p=1.000 n=20) ¹
EvaluateSubjectMappings-16                             1.634Ki ± 0%     1.634Ki ± 0%         ~ (p=1.000 n=20) ¹
geomean                                                             ²                 ?                       ² ³
¹ all samples are equal
² summaries must be >0 to compute geomean
³ ratios must be >0 to compute geomean

                                                     │  sm-old.txt   │                sm-new.txt                │
                                                     │   allocs/op   │  allocs/op   vs base                     │
EvaluateCondition_IN_Large-16                           4.000 ± 0%      3.000 ± 0%   -25.00% (p=0.000 n=20)
EvaluateCondition_IN_Hit-16                             4.000 ± 0%      0.000 ± 0%  -100.00% (p=0.000 n=20)
EvaluateSubjectMappings_LargeAttrCount-16               266.0 ± 0%      145.0 ± 0%   -45.49% (p=0.000 n=20)
EvaluateSubjectMappings_RepeatedSubjectSets-16          233.0 ± 0%      129.0 ± 0%   -44.64% (p=0.000 n=20)
EvaluateSubjectMappingsWithActions_LargeAttrCount-16    709.0 ± 0%      302.0 ± 0%   -57.40% (p=0.000 n=20)
EvaluateSubjectMappingMultipleEntitiesWithActions-16   3.269k ± 0%     2.010k ± 0%   -38.51% (p=0.000 n=20)
EvaluateSubjectSet-16                                   0.000 ± 0%      0.000 ± 0%         ~ (p=1.000 n=20) ¹
EvaluateSubjectMappings-16                              30.00 ± 0%      30.00 ± 0%         ~ (p=1.000 n=20) ¹
geomean                                                            ²                ?                       ² ³
¹ all samples are equal
² summaries must be >0 to compute geomean
³ ratios must be >0 to compute geomean
goos: darwin
goarch: arm64
pkg: github.com/opentdf/platform/lib/flattening
cpu: Apple M4 Max
                                    │ flatten-old.txt │           flatten-new.txt           │
                                    │     sec/op      │   sec/op     vs base                │
GetFromFlattened_Large-16               228.500n ± 2%   5.331n ± 1%  -97.67% (p=0.000 n=20)
GetFromFlattened_MultipleLookups-16      508.80n ± 2%   27.90n ± 5%  -94.52% (p=0.000 n=20)
Flatten_NestedEntity-16                   2.269µ ± 2%   2.917µ ± 1%  +28.54% (p=0.000 n=20)
FlattenMap-16                             224.0n ± 1%   221.0n ± 3%        ~ (p=0.351 n=20)
FlattenMapWithinMap-16                    374.0n ± 1%   374.9n ± 1%        ~ (p=0.372 n=20)
GetFromFlattened-16                       13.68n ± 2%   13.87n ± 1%        ~ (p=0.163 n=20)
geomean                                   259.1n        89.04n       -65.63%

                                    │ flatten-old.txt │              flatten-new.txt              │
                                    │      B/op       │     B/op      vs base                     │
GetFromFlattened_Large-16                  16.00 ± 0%      0.00 ± 0%  -100.00% (p=0.000 n=20)
GetFromFlattened_MultipleLookups-16        80.00 ± 0%      0.00 ± 0%  -100.00% (p=0.000 n=20)
Flatten_NestedEntity-16                  5.861Ki ± 0%   7.714Ki ± 0%   +31.61% (p=0.000 n=20)
FlattenMap-16                              360.0 ± 0%     360.0 ± 0%         ~ (p=1.000 n=20) ¹
FlattenMapWithinMap-16                     624.0 ± 0%     624.0 ± 0%         ~ (p=1.000 n=20) ¹
GetFromFlattened-16                        16.00 ± 0%     16.00 ± 0%         ~ (p=1.000 n=20) ¹
geomean                                    173.9                      ?                       ² ³
¹ all samples are equal
² summaries must be >0 to compute geomean
³ ratios must be >0 to compute geomean

                                    │ flatten-old.txt │             flatten-new.txt              │
                                    │    allocs/op    │  allocs/op   vs base                     │
GetFromFlattened_Large-16                  1.000 ± 0%    0.000 ± 0%  -100.00% (p=0.000 n=20)
GetFromFlattened_MultipleLookups-16        5.000 ± 0%    0.000 ± 0%  -100.00% (p=0.000 n=20)
Flatten_NestedEntity-16                    94.00 ± 0%   117.00 ± 0%   +24.47% (p=0.000 n=20)
FlattenMap-16                              11.00 ± 0%    11.00 ± 0%         ~ (p=1.000 n=20) ¹
FlattenMapWithinMap-16                     17.00 ± 0%    17.00 ± 0%         ~ (p=1.000 n=20) ¹
GetFromFlattened-16                        1.000 ± 0%    1.000 ± 0%         ~ (p=1.000 n=20) ¹
geomean                                    6.668                     ?                       ² ³
¹ all samples are equal
² summaries must be >0 to compute geomean
³ ratios must be >0 to compute geomean

sm-old.txt
flatten-old.txt
flatten-new.txt
sm-new.txt

Checklist

  • I have added or updated unit tests
  • I have added or updated integration tests (if appropriate)
  • I have added or updated documentation

Testing Instructions

Add performance optimizations to subject mapping evaluation hot paths:

- Flattening: Add internal index map for O(1) selector lookups
  - GetFromFlattened_Large: 39x faster (217ns → 5.5ns), zero allocs
  - GetFromFlattened_MultipleLookups: 17x faster, zero allocs

- EvaluateCondition: Set-based IN/NOT_IN with early returns
  - EvaluateCondition_IN_Large: 2.3x faster
  - EvaluateCondition_IN_Hit: 4.6x faster, zero allocs

- Per-entity caches for SubjectSet/ConditionGroup evaluation
  - RepeatedSubjectSets: 21% faster, 37% fewer allocs

- Action deduplication using per-valueFQN accumulator
  - WithActions: 6% faster, 51% fewer allocations

- HierarchyRule: O(h) definition scan vs O(e) entitlements scan

Trade-off: Flatten() is ~2x slower due to index building, but this
cost is amortized over many GetFromFlattened calls per entity.
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @strantalis, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a suite of performance optimizations across the authorization subject mapping evaluation logic. The primary goal is to significantly reduce the computational overhead and improve the response times of critical authorization paths. This is achieved through strategic use of data structures for faster lookups, caching mechanisms to avoid redundant calculations, and algorithmic improvements in core evaluation functions. The changes aim to make the authorization system more scalable and efficient, particularly under heavy load or with complex policy definitions.

Highlights

  • Flattening Optimization: Implemented an internal index map within the Flattened structure for O(1) selector lookups, drastically improving GetFromFlattened performance (e.g., 39x faster for large datasets). This comes with a trade-off of Flatten() being ~2x slower due to index building.
  • Condition Evaluation Speedup: Enhanced EvaluateCondition for IN and NOT_IN operators by using set-based lookups with early returns, leading to significant speed improvements (e.g., 4.6x faster for IN_Hit cases).
  • Per-Entity Caching: Introduced per-entity caches for SubjectSet and ConditionGroup evaluations within EvaluateSubjectMappings and EvaluateSubjectMappingsWithActions, reducing redundant computations and improving performance (e.g., 21% faster with 37% fewer allocations for repeated subject sets).
  • Action Deduplication: Optimized action accumulation in EvaluateSubjectMappingsWithActions by using a per-valueFQN accumulator that deduplicates actions by name, resulting in faster processing and fewer allocations (e.g., 6% faster with 51% fewer allocations).
  • Hierarchy Rule Efficiency: Refactored the hierarchyRule to scan only the relevant FQNs in the definition (O(h)) instead of all entitlements (O(e)), improving efficiency when the number of entitlements is much larger than the hierarchy depth.
  • New Benchmarks: Added comprehensive benchmark tests for flattening, condition evaluation, and subject mapping evaluation to validate and measure the performance improvements.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.


Code runs slow, then fast, Maps and caches, quick to find, Authz now takes flight.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a series of significant performance optimizations to the authorization and subject mapping evaluation paths. The changes are well-structured and demonstrate a deep understanding of the performance characteristics of the code.

Key improvements include:

  • O(1) lookups in flattening: By pre-building an index, GetFromFlattened is now significantly faster, with benchmarks showing zero allocations on cache hits.
  • Efficient condition evaluation: The IN and NOT_IN operators in EvaluateCondition now use hash sets, reducing the complexity from O(N*M) to O(N+M).
  • Caching: Per-entity caching for SubjectSet and ConditionGroup evaluations avoids redundant computations for repeated policy structures.
  • Correct action deduplication: The logic in EvaluateSubjectMappingsWithActions now correctly deduplicates actions across multiple subject mappings for the same FQN, which is both a performance win and a correctness fix.
  • Optimized hierarchy rule: The hierarchyRule now scans a much smaller set of definitions, improving its efficiency.

The addition of comprehensive benchmarks is excellent, as it validates the performance gains claimed.

I have one suggestion to improve consistency in the flattening package. Overall, this is an excellent contribution.

Fixed bug where subjectPropertySet was checked but never updated,
causing duplicate selectors to be added.
@strantalis strantalis force-pushed the fix/sm-perf-improvements branch 3 times, most recently from 2dcf1c9 to 8c10f4e Compare December 5, 2025 03:12
Add new benchmark commands that exercise subject mapping evaluation
with multiple attributes per resource and multiple resources per request:

- benchmark-decision-complex: v2 API with configurable resources/attrs
- benchmark-decision-v1-complex: v1 API with same capabilities

These benchmarks show where optimization improvements shine by testing
with 500 resources × 5 attributes = 2,500 attribute checks per request.

Also updates CI workflow to run and report these benchmarks.
@strantalis strantalis force-pushed the fix/sm-perf-improvements branch from 8c10f4e to c5ec090 Compare December 5, 2025 03:17
The complex benchmarks were not providing useful metrics as all
decisions were being denied. Removing them to simplify the CI.
- Flatten: separate index building into a linear pass, skip index for
  structures with fewer than 8 items (linear scan is faster)
- EvaluateSubjectSet/ConditionGroup: split into cached and non-cached
  versions, use non-cached for single evaluations to avoid map overhead
- IN/NOT_IN conditions: use linear search for small value sets (<=4)
  to avoid map allocation

Benchmarks show -38% geomean improvement in subject mapping evaluation
and -66% geomean improvement in flattening lookups.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes subject mapping evaluation performance through targeted improvements in hot paths. The optimizations include flattening index maps for O(1) lookups, set-based IN/NOT_IN evaluation with early returns, per-entity caching for SubjectSet/ConditionGroup evaluations, streamlined action deduplication, and a more efficient hierarchy rule scan. The changes maintain backward compatibility while delivering significant performance gains (up to 97% faster for large dataset lookups) with reduced memory allocations.

Key Changes

  • Flattening optimization with internal index map provides O(1) selector lookups (39x faster for large datasets)
  • Set-based IN/NOT_IN operators with threshold-based algorithm selection (2-5x faster)
  • Per-entity caching for repeated SubjectSet/ConditionGroup evaluations (21-47% faster, 37-57% fewer allocations)

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.

Show a summary per file
File Description
lib/flattening/flatten.go Adds internal index map for O(1) selector lookups when structure size exceeds threshold (8 items)
lib/flattening/flatten_bench_large_test.go New benchmarks demonstrating flattening lookup performance improvements
service/internal/subjectmappingbuiltin/subject_mapping_builtin.go Implements per-entity caching, optimized IN/NOT_IN operators with threshold-based algorithm selection, and fixes spelling in comments
service/internal/subjectmappingbuiltin/subject_mapping_builtin_actions.go Refactors action deduplication to use per-valueFQN accumulator pattern, reducing allocations
service/internal/subjectmappingbuiltin/subject_mapping_bench_large_test.go Comprehensive benchmarks covering large attribute counts, repeated subject sets, and multiple entities
service/internal/access/v2/evaluate.go Optimizes hierarchy rule to scan O(h) definition entries instead of O(e) entitlements
service/internal/access/v2/just_in_time_pdp.go Fixes bug where subject properties could be duplicated by properly tracking added keys

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@strantalis
Copy link
Member Author

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a series of significant performance optimizations to the subject mapping evaluation paths, backed by comprehensive benchmarks demonstrating impressive gains. The changes include introducing an index for O(1) lookups in flattened structures, using set-based logic for IN/NOT_IN operations, caching evaluation results, and improving action deduplication logic. The code is well-structured and the optimizations are clearly beneficial.

My review includes a couple of suggestions to improve robustness: one to maintain backward compatibility in a modified library function's return value, and another to ensure deterministic output by sorting results from map iteration. Overall, this is an excellent set of performance improvements.

flattened.Items = items
return flattened, nil

// Build index in a separate pass, only for larger structures
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like we could improve this further by updating flattenInterface to build the index lookup as well so the passes drop from 2n to n.

Add TestHierarchyRule_DefinitionMissingValues to exercise the defensive
code path that handles data inconsistency where resourceValueFQNs
contains values not present in attrDefinition.GetValues().

This guards against panics if the policy service returns incomplete
attribute definitions.
@strantalis strantalis marked this pull request as ready for review December 8, 2025 18:36
@strantalis strantalis requested review from a team as code owners December 8, 2025 18:36
@strantalis strantalis changed the title perf(authz): optimize subject mapping evaluation performance fix(authz): optimize subject mapping evaluation performance Dec 8, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants