For low-latency/high-performance use-cases, e.g. microservice API authorization, policy evaluation has a budget on the order of 1 millisecond. Not all use cases require that kind of performance, and OPA is powerful enough that you can write expressive policies that take longer than 1 millisecond to evaluate. But for high-performance use cases, there is a fragment of the policy language that has been engineered to evaluate quickly. Even as the size of the policies grow, the performance for this fragment can be nearly constant-time.

    The linear fragment of the language is all of those policies where evaluation amounts to walking over the policy once. This means there is no search required to make a policy decision. Any variables you use can be assigned at most one value.

    For example, the following rule has one local variable user, and that variable can only be assigned one value. Intuitively, evaluating this rule requires checking each of the conditions in the body, and if there were N of these rules, evaluation would only require walking over each of them as well.

    One common mistake people make is using arrays when they could use objects. For example, below is an array of ID/first-name/last-names where ID is unique, and you’re looking up the first-name/last-name given the ID.

    1. # DO NOT DO THIS.
    2. # Array of objects where each object has a unique identifier
    3. d = [{"id": "a123", "first": "alice", "last": "smith"},
    4. {"id": "a456", "first": "bob", "last": "jones"},
    5. {"id": "a789", "first": "clarice", "last": "johnson"}
    6. ]
    7. # search through all elements of the array to find the ID
    8. d[i].id == "a789"
    9. d[i].first ...

    Instead, use a dictionary where the key is the ID and the value is the first-name/last-name. Given the ID, you can lookup the name information directly.

    1. # DO THIS INSTEAD OF THE ABOVE
    2. # Use object whose keys are the IDs for the objects.
    3. # Looking up an object given its ID requires NO search
    4. d = {"a123": {"first": "alice", "last": "smith"},
    5. "a456": {"first": "bob", "last": "jones"},
    6. "a789": {"first": "clarice", "last": "johnson"}
    7. }
    8. # no search required
    9. d["a789"].first ...

    The linear-time fragment ensures that the cost of evaluation is no larger than the size of the policy. OPA lets you write non-linear policies, because sometimes you need to, and because sometimes it’s convenient. The blog on describes one mechanism for converting non-linear policies into linear policies.

    But as the size of the policy grows, the cost of evaluation grows with it. Sometimes the policy can grow large enough that even the linear-fragment fails to meet the performance budget.

    Here is an example policy from the rule-indexing blog giving the details for these algorithms. See the rest of this section for details on indexed statements.

    1. package indexed
    2. default allow = false
    3. allow {
    4. some user
    5. input.method = "GET"
    6. input.path = ["accounts", user]
    7. input.user = user
    8. }
    9. allow {
    10. input.method = "GET"
    11. input.path = ["accounts", "report"]
    12. roles[input.user][_] = "admin"
    13. }
    14. allow {
    15. input.method = "POST"
    16. input.path = ["accounts"]
    17. roles[input.user][_] = "admin"
    18. }
    19. roles = {
    20. "bob": ["admin", "hr"],
    21. "alice": ["procurement"],
    22. }
      1. true

      Equality statements

      For simple equality statements (= and ==) to be indexed one side must be a non-nested reference that does not contain any variables and the other side must be a variable, scalar, or array (which may contain scalars and variables). For example:

      Glob statements

      For glob.match(pattern, delimiter, match) statements to be indexed the pattern must be recognized by the indexer and the match be a non-nested reference that does not contain any variables. The indexer recognizes patterns containing the normal glob (*) operator but not the super glob (**) or character pattern matching operators.

      You can also profile your policies using opa eval. The profiler is useful if you need to understand why policy evaluation is slow.

      The opa eval command provides the following profiler options:

      Sort criteria for the profile results

      • total_time_ns - Results are displayed is decreasing order of expression evaluation time
      • num_eval - Results are displayed is decreasing order of number of times an expression is evaluated
      • num_redo - Results are displayed is decreasing order of number of times an expression is re-evaluated(redo)
      • file - Results are sorted in reverse alphabetical order based on the rego source filename
      • line - Results are displayed is decreasing order of expression line number in the source file

      When the sort criteria is not provided total_time_ns has the highest priority while line has the lowest.

      Example Policy

      The different profiling examples shown later on this page use the below sample policy.

      1. package rbac
      2. # Example input request
      3. input = {
      4. "subject": "bob",
      5. "resource": "foo123",
      6. "action": "write",
      7. }
      8. # Example RBAC configuration.
      9. bindings = [
      10. {
      11. "user": "alice",
      12. "roles": ["dev", "test"],
      13. },
      14. {
      15. "user": "bob",
      16. "roles": ["test"],
      17. },
      18. ]
      19. roles = [
      20. {
      21. "name": "dev",
      22. "permissions": [
      23. {"resource": "foo123", "action": "write"},
      24. {"resource": "foo123", "action": "read"},
      25. ],
      26. },
      27. {
      28. "name": "test",
      29. "permissions": [{"resource": "foo123", "action": "read"}],
      30. },
      31. ]
      32. # Example RBAC policy implementation.
      33. default allow = false
      34. allow {
      35. some role_name
      36. user_has_role[role_name]
      37. role_has_permission[role_name]
      38. }
      39. user_has_role[role_name] {
      40. binding := bindings[_]
      41. binding.user == input.subject
      42. role_name := binding.roles[_]
      43. role := roles[_]
      44. role_name := role.name
      45. perm := role.permissions[_]
      46. perm.resource == input.resource
      47. perm.action == input.action
      48. }

      Example: Display ALL profile results with default ordering criteria

      1. opa eval --data rbac.rego --profile --format=pretty 'data.rbac.allow'
      1. false
      2. +----------+----------+----------+-----------------+
      3. | TIME | NUM EVAL | NUM REDO | LOCATION |
      4. +----------+----------+----------+-----------------+
      5. | 47.148µs | 1 | 1 | data.rbac.allow |
      6. | 28.965µs | 1 | 1 | rbac.rego:11 |
      7. | 24.384µs | 1 | 1 | rbac.rego:41 |
      8. | 23.064µs | 2 | 1 | rbac.rego:47 |
      9. | 15.525µs | 1 | 1 | rbac.rego:38 |
      10. | 14.137µs | 1 | 2 | rbac.rego:46 |
      11. | 13.927µs | 1 | 0 | rbac.rego:42 |
      12. | 13.568µs | 1 | 1 | rbac.rego:55 |
      13. | 12.982µs | 1 | 0 | rbac.rego:56 |
      14. | 12.763µs | 1 | 2 | rbac.rego:52 |
      15. +----------+----------+----------+-----------------+
      16. +------------------------------+----------+
      17. | METRIC | VALUE |
      18. +------------------------------+----------+
      19. | timer_rego_module_compile_ns | 1871613 |
      20. | timer_rego_query_compile_ns | 82290 |
      21. | timer_rego_query_eval_ns | 257952 |
      22. | timer_rego_query_parse_ns | 12337169 |
      23. +------------------------------+----------+

      As seen from the above table, all results are displayed. The profile results are sorted on the default sort criteria.

      Example: Display top 5 profile results

      Sample Output

      1. +----------+----------+----------+-----------------+
      2. | TIME | NUM EVAL | NUM REDO | LOCATION |
      3. +----------+----------+----------+-----------------+
      4. | 46.329µs | 1 | 1 | data.rbac.allow |
      5. | 26.656µs | 1 | 1 | rbac.rego:11 |
      6. | 24.206µs | 2 | 1 | rbac.rego:47 |
      7. | 23.235µs | 1 | 1 | rbac.rego:41 |
      8. | 18.242µs | 1 | 1 | rbac.rego:38 |
      9. +----------+----------+----------+-----------------+

      The profile results are sorted on the default sort criteria. Also --profile option is implied and does not need to be provided.

      Example: Display top 5 profile results based on the number of times an expression is evaluated
      1. opa eval --data rbac.rego --profile-limit 5 --profile-sort num_eval --format=pretty 'data.rbac.allow'

      Sample Profile Output

      1. +----------+----------+----------+-----------------+
      2. | TIME | NUM EVAL | NUM REDO | LOCATION |
      3. +----------+----------+----------+-----------------+
      4. | 26.675µs | 2 | 1 | rbac.rego:47 |
      5. | 9.274µs | 2 | 1 | rbac.rego:53 |
      6. | 43.356µs | 1 | 1 | data.rbac.allow |
      7. | 22.467µs | 1 | 1 | rbac.rego:41 |
      8. | 22.425µs | 1 | 1 | rbac.rego:11 |
      9. +----------+----------+----------+-----------------+

      As seen from the above table, the results are arranged first in decreasing order of number of evaluations and if two expressions have been evaluated the same number of times, the default criteria is used since no other sort criteria is provided. In this case, total_time_ns => num_redo => file => line. Also --profile option is implied and does not need to be provided.

      Example: Display top 5 profile results based on the number of times an expression is evaluated and number of times an expression is re-evaluated
      1. opa eval --data rbac.rego --profile-limit 5 --profile-sort num_eval,num_redo --format=pretty 'data.rbac.allow'

      Sample Profile Output

      As seen from the above table, result are first arranged based on number of evaluations, then number of re-evaluations and finally the default criteria is used. In this case, total_time_ns => file => line. The --profile-sort options accepts repeated or comma-separated values for the criteria. The order of the criteria on the command line determine their priority.

      1. opa eval --data rbac.rego --profile-limit 5 --profile-sort num_eval --profile-sort num_redo --format=pretty 'data.rbac.allow'

      For high-performance use cases:

      • Write your policies to minimize iteration and search.
        • Use objects instead of arrays when you have a unique identifier for the elements of the array.
        • Consider to compile non-linear policies to linear policies.
      • Write your policies with indexed statements so that rule-indexing is effective.
      • Use the profiler to help identify portions of the policy that would benefit the most from improved performance.