Skip to content

BE-273: HashQL: Profile and benchmark MIR interpreter#8251

Merged
indietyp merged 8 commits intomainfrom
bm/be-273-hashql-interpreter-benchmarks
Jan 30, 2026
Merged

BE-273: HashQL: Profile and benchmark MIR interpreter#8251
indietyp merged 8 commits intomainfrom
bm/be-273-hashql-interpreter-benchmarks

Conversation

@indietyp
Copy link
Member

🌟 What is the purpose of this PR?

Replace imbl with rpds for persistent data structures in the MIR interpreter, and optimize interpreter performance.

The interpreter is now only 20-40% slower instead of the previous 100%+.

🔍 What does this change?

  • Replaces imbl dependency with rpds for persistent data structures (List and Dict)
  • Adds inlining annotations to hot code paths in the interpreter
  • Optimizes SwitchTargets::target() with a fast path for common cases
  • Adds cold path annotations to error handling code
  • Optimizes value comparison in the interpreter
  • Adds a new benchmark for the interpreter (fibonacci recursive)
  • Improves parameter passing in the interpreter to avoid unnecessary copies
  • Optimizes local variable storage in the interpreter

Pre-Merge Checklist 🚀

🚢 Has this modified a publishable library?

This PR:

  • does not modify any publishable blocks or libraries, or modifications do not need publishing

📜 Does this require a change to the docs?

The changes in this PR:

  • are internal and do not require a docs change

🕸️ Does this require a change to the Turbo Graph?

The changes in this PR:

  • do not affect the execution graph

🛡 What tests cover this?

  • New benchmark for the interpreter (fibonacci recursive)
  • Existing tests continue to pass

❓ How to test this?

  1. Run the new benchmark to verify performance improvements
  2. Run existing tests to ensure functionality is preserved

@cursor
Copy link

cursor bot commented Jan 10, 2026

PR Summary

Medium Risk
Touches core MIR interpreter execution (locals storage, call/arg handling, switch dispatch and collections), so subtle semantic or performance regressions are possible despite being mostly internal changes and benchmark-focused.

Overview
Adds interpreter benchmarking and tunes MIR interpreter hot paths. Introduces a shared run_bencher harness for codspeed/criterion benches and adds a new interpret benchmark (recursive Fibonacci) alongside the existing transform benchmarks.

Performance-focused refactors in the interpreter: replaces persistent collections from imbl to rpds (List/Dict), reduces copying by switching many interpreter APIs to take references and requiring ExactSizeIterator for argument passing, and reworks locals storage to avoid Option overhead by filling missing locals with Unit.

Micro-optimizations and hints: adds #[inline] on hot methods, marks error paths with cold_path/likely_unlikely, and adds a fast-path in SwitchTargets::target() for small target sets; also updates workspace deps/lockfile accordingly and adds a small IdVec::reserve helper used by the runtime.

Written by Cursor Bugbot for commit 7476275. This will update automatically on new commits. Configure here.

@github-actions github-actions bot added area/deps Relates to third-party dependencies (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > backend Owned by the @backend team labels Jan 10, 2026
This was referenced Jan 10, 2026
Copy link
Member Author

indietyp commented Jan 10, 2026

This stack of pull requests is managed by Graphite. Learn more about stacking.

TimDiekmann
TimDiekmann previously approved these changes Jan 30, 2026
Copy link
Member

Choose a reason for hiding this comment

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

You probably guessed that I will comment on this file 😅

I'm not thrilled about the raw pointer approach, but it looks sound. PerIteration is indeed critical for soundness to prevent use-after-free and RefCell does not work here.

I have a few concerns (if any of these apply, this will become unsound)

  • If DiagnosticIssues ever gains a lifetime parameter
  • If criterion/codspeed ever change iter_batched_ref semantics (unlikely, but I don't like relying on implementation details)
  • As mentioned above: if BatchSize is changed

Can you assert, that MirDiiagnosticIssues: 'static so we don't accidentally capture the heap-lifetime?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, me too, but this is the only way I could make this work. I tried multiple other ways, usually you would do this via iter_custom, but that isn't supported by codspeed. So run_bencher was born. This is also why the function is practically only safety comments. FWIW although not explicitely stated, PerIteration docs suggest very strongly that any value will only be held once at a time (even giving the file lock example: https://docs.rs/criterion/latest/criterion/enum.BatchSize.html#variant.PerIteration)

Copy link
Member Author

Choose a reason for hiding this comment

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

I have added a static lifetime assertion inside of 7476275

Copy link
Member

Choose a reason for hiding this comment

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

Great, thank you!

@indietyp indietyp added this pull request to the merge queue Jan 30, 2026
@indietyp indietyp removed this pull request from the merge queue due to a manual request Jan 30, 2026
@vercel vercel bot temporarily deployed to Preview – petrinaut January 30, 2026 10:36 Inactive
@github-actions github-actions bot dismissed TimDiekmann’s stale review January 30, 2026 10:36

Your organization requires reapproval when changes are made, so Graphite has dismissed approvals. See the output of git range-diff at https://github.com/hashintel/hash/actions/runs/21512891851

Copy link
Member

Choose a reason for hiding this comment

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

Great, thank you!

@indietyp indietyp added this pull request to the merge queue Jan 30, 2026
Merged via the queue into main with commit 0be3f12 Jan 30, 2026
173 of 174 checks passed
@indietyp indietyp deleted the bm/be-273-hashql-interpreter-benchmarks branch January 30, 2026 11:05
@github-actions
Copy link
Contributor

Benchmark results

@rust/hash-graph-benches – Integrations

policy_resolution_large

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 2002 $$27.3 \mathrm{ms} \pm 195 \mathrm{μs}\left({\color{red}5.46 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$3.23 \mathrm{ms} \pm 14.4 \mathrm{μs}\left({\color{gray}1.06 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 1001 $$12.1 \mathrm{ms} \pm 97.8 \mathrm{μs}\left({\color{gray}4.01 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 3314 $$43.0 \mathrm{ms} \pm 400 \mathrm{μs}\left({\color{gray}3.94 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$15.6 \mathrm{ms} \pm 98.7 \mathrm{μs}\left({\color{red}15.6 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 1526 $$24.8 \mathrm{ms} \pm 203 \mathrm{μs}\left({\color{red}10.5 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 2078 $$43.0 \mathrm{ms} \pm 318 \mathrm{μs}\left({\color{gray}2.27 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$20.1 \mathrm{ms} \pm 148 \mathrm{μs}\left({\color{gray}2.82 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 1033 $$30.8 \mathrm{ms} \pm 251 \mathrm{μs}\left({\color{red}13.7 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_medium

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 102 $$3.80 \mathrm{ms} \pm 22.0 \mathrm{μs}\left({\color{red}6.24 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$2.92 \mathrm{ms} \pm 15.1 \mathrm{μs}\left({\color{gray}4.32 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 51 $$3.34 \mathrm{ms} \pm 13.6 \mathrm{μs}\left({\color{red}5.65 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 269 $$5.08 \mathrm{ms} \pm 31.4 \mathrm{μs}\left({\color{gray}3.50 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$3.41 \mathrm{ms} \pm 15.0 \mathrm{μs}\left({\color{gray}2.40 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 107 $$3.99 \mathrm{ms} \pm 18.7 \mathrm{μs}\left({\color{gray}2.88 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 133 $$4.57 \mathrm{ms} \pm 37.0 \mathrm{μs}\left({\color{red}9.62 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$3.45 \mathrm{ms} \pm 13.6 \mathrm{μs}\left({\color{red}7.49 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 63 $$4.21 \mathrm{ms} \pm 26.5 \mathrm{μs}\left({\color{red}9.61 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_none

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 2 $$2.36 \mathrm{ms} \pm 10.0 \mathrm{μs}\left({\color{gray}-0.788 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$2.32 \mathrm{ms} \pm 17.0 \mathrm{μs}\left({\color{gray}0.806 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 1 $$2.42 \mathrm{ms} \pm 13.6 \mathrm{μs}\left({\color{gray}0.126 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 8 $$2.62 \mathrm{ms} \pm 11.6 \mathrm{μs}\left({\color{gray}0.219 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$2.48 \mathrm{ms} \pm 10.4 \mathrm{μs}\left({\color{gray}-0.070 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 3 $$2.72 \mathrm{ms} \pm 17.3 \mathrm{μs}\left({\color{gray}2.00 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_small

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 52 $$2.75 \mathrm{ms} \pm 15.4 \mathrm{μs}\left({\color{gray}-0.835 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$2.38 \mathrm{ms} \pm 10.7 \mathrm{μs}\left({\color{gray}-1.273 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 25 $$2.59 \mathrm{ms} \pm 13.3 \mathrm{μs}\left({\color{gray}0.265 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 94 $$3.08 \mathrm{ms} \pm 15.3 \mathrm{μs}\left({\color{gray}0.702 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$2.64 \mathrm{ms} \pm 13.2 \mathrm{μs}\left({\color{gray}-0.327 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 26 $$2.87 \mathrm{ms} \pm 11.8 \mathrm{μs}\left({\color{gray}0.545 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 66 $$3.00 \mathrm{ms} \pm 19.7 \mathrm{μs}\left({\color{gray}0.976 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$2.61 \mathrm{ms} \pm 15.8 \mathrm{μs}\left({\color{gray}-0.156 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 29 $$2.85 \mathrm{ms} \pm 14.1 \mathrm{μs}\left({\color{gray}0.564 \mathrm{\%}}\right) $$ Flame Graph

read_scaling_complete

Function Value Mean Flame graphs
entity_by_id;one_depth 1 entities $$39.2 \mathrm{ms} \pm 161 \mathrm{μs}\left({\color{gray}0.540 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 10 entities $$76.8 \mathrm{ms} \pm 447 \mathrm{μs}\left({\color{gray}1.35 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 25 entities $$44.2 \mathrm{ms} \pm 198 \mathrm{μs}\left({\color{gray}3.02 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 5 entities $$46.0 \mathrm{ms} \pm 180 \mathrm{μs}\left({\color{gray}-0.110 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 50 entities $$53.4 \mathrm{ms} \pm 232 \mathrm{μs}\left({\color{gray}-0.588 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 1 entities $$41.1 \mathrm{ms} \pm 185 \mathrm{μs}\left({\color{gray}-0.673 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 10 entities $$415 \mathrm{ms} \pm 1.03 \mathrm{ms}\left({\color{gray}0.037 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 25 entities $$93.6 \mathrm{ms} \pm 401 \mathrm{μs}\left({\color{gray}1.28 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 5 entities $$84.8 \mathrm{ms} \pm 385 \mathrm{μs}\left({\color{gray}-1.807 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 50 entities $$306 \mathrm{ms} \pm 879 \mathrm{μs}\left({\color{gray}-3.922 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 1 entities $$14.4 \mathrm{ms} \pm 68.1 \mathrm{μs}\left({\color{lightgreen}-5.116 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 10 entities $$14.7 \mathrm{ms} \pm 78.5 \mathrm{μs}\left({\color{lightgreen}-5.034 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 25 entities $$15.3 \mathrm{ms} \pm 81.8 \mathrm{μs}\left({\color{gray}-2.826 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 5 entities $$14.4 \mathrm{ms} \pm 78.6 \mathrm{μs}\left({\color{lightgreen}-5.975 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 50 entities $$17.7 \mathrm{ms} \pm 96.7 \mathrm{μs}\left({\color{gray}-1.027 \mathrm{\%}}\right) $$ Flame Graph

read_scaling_linkless

Function Value Mean Flame graphs
entity_by_id 1 entities $$14.3 \mathrm{ms} \pm 74.1 \mathrm{μs}\left({\color{lightgreen}-6.792 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 10 entities $$14.5 \mathrm{ms} \pm 89.1 \mathrm{μs}\left({\color{lightgreen}-5.729 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 100 entities $$14.4 \mathrm{ms} \pm 68.7 \mathrm{μs}\left({\color{lightgreen}-6.517 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 1000 entities $$15.1 \mathrm{ms} \pm 93.4 \mathrm{μs}\left({\color{lightgreen}-7.372 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 10000 entities $$22.3 \mathrm{ms} \pm 152 \mathrm{μs}\left({\color{lightgreen}-7.324 \mathrm{\%}}\right) $$ Flame Graph

representative_read_entity

Function Value Mean Flame graphs
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/block/v/1 $$29.7 \mathrm{ms} \pm 283 \mathrm{μs}\left({\color{gray}0.715 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/book/v/1 $$30.2 \mathrm{ms} \pm 317 \mathrm{μs}\left({\color{gray}-2.645 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/building/v/1 $$29.2 \mathrm{ms} \pm 306 \mathrm{μs}\left({\color{gray}0.151 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/organization/v/1 $$29.0 \mathrm{ms} \pm 285 \mathrm{μs}\left({\color{gray}-3.453 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/page/v/2 $$30.1 \mathrm{ms} \pm 342 \mathrm{μs}\left({\color{gray}2.37 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/person/v/1 $$29.1 \mathrm{ms} \pm 284 \mathrm{μs}\left({\color{gray}-3.437 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/playlist/v/1 $$30.2 \mathrm{ms} \pm 289 \mathrm{μs}\left({\color{gray}0.894 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/song/v/1 $$29.6 \mathrm{ms} \pm 337 \mathrm{μs}\left({\color{gray}2.32 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/uk-address/v/1 $$30.6 \mathrm{ms} \pm 256 \mathrm{μs}\left({\color{gray}1.75 \mathrm{\%}}\right) $$ Flame Graph

representative_read_entity_type

Function Value Mean Flame graphs
get_entity_type_by_id Account ID: bf5a9ef5-dc3b-43cf-a291-6210c0321eba $$7.98 \mathrm{ms} \pm 37.3 \mathrm{μs}\left({\color{gray}-0.240 \mathrm{\%}}\right) $$ Flame Graph

representative_read_multiple_entities

Function Value Mean Flame graphs
entity_by_property traversal_paths=0 0 $$45.4 \mathrm{ms} \pm 242 \mathrm{μs}\left({\color{gray}-0.815 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=255 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true $$93.0 \mathrm{ms} \pm 450 \mathrm{μs}\left({\color{gray}-0.065 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false $$51.3 \mathrm{ms} \pm 353 \mathrm{μs}\left({\color{gray}-0.210 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true $$59.1 \mathrm{ms} \pm 332 \mathrm{μs}\left({\color{gray}-0.357 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true $$67.4 \mathrm{ms} \pm 320 \mathrm{μs}\left({\color{gray}-0.592 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true $$73.9 \mathrm{ms} \pm 399 \mathrm{μs}\left({\color{gray}-0.476 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=0 0 $$49.5 \mathrm{ms} \pm 288 \mathrm{μs}\left({\color{gray}1.30 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=255 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true $$77.2 \mathrm{ms} \pm 375 \mathrm{μs}\left({\color{gray}2.05 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false $$56.3 \mathrm{ms} \pm 386 \mathrm{μs}\left({\color{gray}0.949 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true $$63.3 \mathrm{ms} \pm 337 \mathrm{μs}\left({\color{gray}-0.341 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true $$65.5 \mathrm{ms} \pm 377 \mathrm{μs}\left({\color{gray}-0.107 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true $$65.4 \mathrm{ms} \pm 355 \mathrm{μs}\left({\color{gray}-0.302 \mathrm{\%}}\right) $$

scenarios

Function Value Mean Flame graphs
full_test query-limited $$135 \mathrm{ms} \pm 578 \mathrm{μs}\left({\color{gray}-1.131 \mathrm{\%}}\right) $$ Flame Graph
full_test query-unlimited $$133 \mathrm{ms} \pm 584 \mathrm{μs}\left({\color{gray}-3.256 \mathrm{\%}}\right) $$ Flame Graph
linked_queries query-limited $$106 \mathrm{ms} \pm 492 \mathrm{μs}\left({\color{red}8.27 \mathrm{\%}}\right) $$ Flame Graph
linked_queries query-unlimited $$621 \mathrm{ms} \pm 3.99 \mathrm{ms}\left({\color{red}11.0 \mathrm{\%}}\right) $$ Flame Graph

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

Labels

area/deps Relates to third-party dependencies (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > backend Owned by the @backend team

Development

Successfully merging this pull request may close these issues.

2 participants