Skip to content

BE-644: Run subgraph reads in a single READ ONLY, REPEATABLE READ transaction#8996

Open
claude[bot] wants to merge 13 commits into
mainfrom
claude/be-644-snapshot-consistent-subgraph-reads
Open

BE-644: Run subgraph reads in a single READ ONLY, REPEATABLE READ transaction#8996
claude[bot] wants to merge 13 commits into
mainfrom
claude/be-644-snapshot-consistent-subgraph-reads

Conversation

@claude

@claude claude Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Requested by Tim Diekmann · Slack thread

🌟 What is the purpose of this PR?

Before: a subgraph read (query_entity_subgraph) executes as several separate autocommit statements under READ COMMITTED — the roots query, one recursive CTE per traversal path, the permission filtering of the traversed edges, and the final vertex read — each with its own MVCC snapshot. A concurrent write committing between those statements (e.g. patch_entity on a link's endpoint) retroactively evicts the endpoint's edition at the reader's pinned timestamp, so the returned subgraph contains the link vertex but silently drops its has-left-entity/has-right-entity edge. This crashed the frontend (FE-1172, flaky Playwright run with a captured payload showing a membership link with no HAS_LEFT_ENTITY edge while an updateEntity was in flight).

After: the whole subgraph read runs inside one READ ONLY, REPEATABLE READ transaction, so every statement of the read shares a single MVCC snapshot and the anomaly cannot occur.

🔗 Related links

🔍 What does this change?

  • hash-graph-migrations: Context::transaction() is now a synchronous method returning a builder that implements IntoFuture. The Transaction associated type moves from Context to the new TransactionBuilder trait (isolation_level, read_only, deferrable), and the ContextTransaction alias keeps Migration signatures readable. Existing context.transaction().await call sites keep compiling.
  • The Postgres implementations delegate to tokio-postgres' Client::build_transaction(), so the options compose into the single BEGIN statement (START TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ ONLYSTART TRANSACTION is Postgres' spelling of BEGIN with options).
  • hash-graph-postgres-store: PostgresStore::transaction() returns a PostgresStoreTransactionBuilder; PostgresStore implements Context, the builder implements TransactionBuilder, and PostgresStore<tokio_postgres::Transaction> implements Transaction. Options are routed through a new AsClient::begin_transaction; beginning a "transaction" on an already-running transaction creates a savepoint and ignores the options (savepoints inherit the enclosing transaction's characteristics — used by the integration-test harness).
  • query_entity_subgraph begins a READ ONLY, REPEATABLE READ transaction and runs the roots query, traversal CTEs, permission filters, and vertex reads on that transaction's client. EntityStore::query_entity_subgraph takes &mut self so the implementation can begin the transaction; the REST handler, type fetcher, tests, and benches are adjusted.
  • hash-graph-migrations is promoted from a dev-dependency to a public dependency of hash-graph-postgres-store (dependency diagrams and package.json updated).
  • New DB-backed tests assert the composed BEGIN options take effect from the first statement (transaction_isolation = 'repeatable read', transaction_read_only = 'on') and that the savepoint fallback inherits the enclosing transaction's characteristics.

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

⚠️ Known issues

  • The statistical concurrency repro (hot-loop patch_entity vs. looping queryEntitySubgraph) is described in BE-644 and intentionally not added here: the integration-test harness runs each test inside a single wrapping transaction on one connection, which cannot express multi-connection concurrency.

🐾 Next steps

  • Defense in depth (tracked in BE-644): stop re-applying the pinned temporal axes to CTE-captured edition ids in the permission-filter/vertex-read statements; source pinned timestamps from the database rather than the host clock (BE-2).

🛡 What tests cover this?

  • New: tests/graph/integration/postgres/transaction.rs — asserts the builder composes REPEATABLE READ, READ ONLY into the BEGIN statement, the default builder keeps session characteristics, and the savepoint fallback ignores options.
  • Existing: the whole postgres integration suite exercises query_entity_subgraph through the new path (98 passed locally; 3 pre-existing clustering failures are environmental — sandbox pgvector < 0.7 lacks subvector()).
  • Existing: hash-graph-postgres-store unit tests (142 passed).

❓ How to test this?

  1. Run the postgres integration suite: cargo test -p hash-graph-integration --test postgres
  2. For the anomaly itself: run the FE-1172 Playwright spec (entity-editing / "user can update values on property table") in a loop, or follow the repro sketch in BE-644.

claude added 3 commits July 9, 2026 16:54
…ansaction builder

Context::transaction() is now a synchronous method returning a
TransactionBuilder which begins the transaction when awaited, so call
sites can configure the transaction before starting it:

    context.transaction().isolation_level(IsolationLevel::RepeatableRead).read_only().await

The Transaction associated type moves from Context to the new
TransactionBuilder trait (bounded by IntoFuture); the ContextTransaction
alias keeps Migration signatures readable. The Postgres implementation
delegates to tokio-postgres' Client::build_transaction() so all options
compose into the single BEGIN statement, e.g.
`START TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ ONLY`.

Groundwork for BE-644.
PostgresStore::transaction() is now synchronous and returns a
PostgresStoreTransactionBuilder which begins the transaction when
awaited; existing `store.transaction().await` call sites are
unchanged. The builder implements the hash-graph-migrations
TransactionBuilder trait (and PostgresStore implements Context,
PostgresStore<Transaction> implements Transaction), so isolation level,
read-only, and deferrable options can be configured before starting.

For a Client-backed store the options are delegated through the new
AsClient::begin_transaction to tokio-postgres' build_transaction(), so
they compose into the single BEGIN statement. Beginning a transaction
on an already-running transaction creates a savepoint; savepoints
inherit the enclosing transaction's characteristics, so the options are
ignored there (used by the integration-test harness).

hash-graph-migrations is promoted from a dev-dependency to a public
dependency of hash-graph-postgres-store; dependency diagrams updated
accordingly.

Part of BE-644.
…saction (BE-644)

The subgraph read executes multiple statements: the roots query, one
recursive CTE per traversal path, the permission filtering of the
traversed edges, and the final vertex read. Under READ COMMITTED each
statement gets its own MVCC snapshot, so a concurrent write (e.g.
patch_entity on a link's endpoint) committing mid-read retroactively
evicts the endpoint's edition at the reader's pinned timestamp: the
subgraph then contains the link vertex but drops its
has-left-entity/has-right-entity edge, which crashed the frontend
(FE-1172).

query_entity_subgraph now begins a READ ONLY, REPEATABLE READ
transaction (a single BEGIN via the new transaction builder) and runs
every statement of the read on that transaction's client, giving the
whole read one shared snapshot. EntityStore::query_entity_subgraph
takes &mut self so the implementation can begin the transaction;
callers (REST handler, type fetcher, tests, benches) are adjusted.

DB-backed tests assert the composed BEGIN options take effect from the
first statement and that the savepoint fallback inherits the enclosing
transaction's characteristics.
@vercel

vercel Bot commented Jul 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hash Ready Ready Preview, Comment Jul 10, 2026 12:06pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
hashdotdesign-tokens Ignored Ignored Preview Jul 10, 2026 12:06pm
petrinaut Skipped Skipped Comment Jul 10, 2026 12:06pm

@github-actions github-actions Bot added area/apps > hash* Affects HASH (a `hash-*` app) area/libs Relates to first-party libraries/crates/packages (area) type/eng > backend Owned by the @backend team area/tests New or updated tests area/apps area/apps > hash-graph labels Jul 9, 2026
@codecov

codecov Bot commented Jul 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 322 lines in your changes missing coverage. Please review.
✅ Project coverage is 59.70%. Comparing base (232f15c) to head (000a7c8).
⚠️ Report is 7 commits behind head on main.

Files with missing lines Patch % Lines
...s-store/src/store/postgres/knowledge/entity/mod.rs 0.00% 190 Missing ⚠️
...cal/graph/postgres-store/src/store/postgres/mod.rs 0.00% 78 Missing ⚠️
.../graph/postgres-store/src/snapshot/entity/batch.rs 0.00% 7 Missing ⚠️
.../graph/postgres-store/src/snapshot/action/batch.rs 0.00% 5 Missing ⚠️
...res-store/src/snapshot/ontology/data_type/batch.rs 0.00% 5 Missing ⚠️
...s-store/src/snapshot/ontology/entity_type/batch.rs 0.00% 5 Missing ⚠️
...gres-store/src/snapshot/ontology/metadata/batch.rs 0.00% 5 Missing ⚠️
...store/src/snapshot/ontology/property_type/batch.rs 0.00% 5 Missing ⚠️
.../graph/postgres-store/src/snapshot/policy/batch.rs 0.00% 5 Missing ⚠️
...aph/postgres-store/src/snapshot/principal/batch.rs 0.00% 5 Missing ⚠️
... and 6 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8996      +/-   ##
==========================================
- Coverage   59.74%   59.70%   -0.05%     
==========================================
  Files        1370     1370              
  Lines      135170   135264      +94     
  Branches     6067     6067              
==========================================
  Hits        80760    80760              
- Misses      53477    53571      +94     
  Partials      933      933              
Flag Coverage Δ
apps.hash-ai-worker-ts 1.39% <ø> (ø)
apps.hash-api 6.39% <ø> (ø)
local.hash-backend-utils 1.90% <ø> (ø)
local.hash-graph-sdk 10.02% <ø> (ø)
local.hash-isomorphic-utils 0.18% <ø> (ø)
rust.hash-graph-api 7.41% <0.00%> (ø)
rust.hash-graph-postgres-store 29.33% <0.00%> (-0.15%) ⬇️
rust.hashql-compiletest 28.39% <ø> (ø)
rust.hashql-eval 79.47% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@codspeed-hq

codspeed-hq Bot commented Jul 9, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 15.38%

❌ 2 regressed benchmarks
✅ 96 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
bit_matrix/dense/iter_row[64] 140.8 ns 170 ns -17.16%
bit_matrix/dense/iter_row[200] 185.8 ns 215 ns -13.57%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing claude/be-644-snapshot-consistent-subgraph-reads (000a7c8) with main (6736174)

Open in CodSpeed

The RefCell wrappers introduced for the `&mut self` change on
`EntityStore::query_entity_subgraph` trip `await_holding_refcell_ref`
(and `future_not_send` on `run_benchmark`). Criterion drives one
benchmark future at a time to completion on a single thread via
`block_on`, so the borrows are never contended and the futures never
cross threads; mark the lints as expected with that reasoning.
@vercel vercel Bot temporarily deployed to Preview – petrinaut July 9, 2026 17:09 Inactive

@TimDiekmann TimDiekmann left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good so far, a few comments.

Comment thread libs/@local/graph/migrations/src/context.rs Outdated
Comment thread libs/@local/graph/migrations/src/context.rs
Comment thread libs/@local/graph/migrations/src/postgres.rs
Comment thread libs/@local/graph/postgres-store/src/store/postgres/mod.rs
@vercel vercel Bot temporarily deployed to Preview – petrinaut July 9, 2026 17:28 Inactive
@TimDiekmann TimDiekmann marked this pull request as ready for review July 9, 2026 18:02
@TimDiekmann TimDiekmann requested a review from a team as a code owner July 9, 2026 18:02
Copilot AI review requested due to automatic review settings July 9, 2026 18:02
@cursor

cursor Bot commented Jul 9, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes core graph read semantics and threads transactions through a wide PostgresStore refactor; incorrect isolation or savepoint behavior could affect consistency or tests, but the change is targeted at fixing a known production anomaly.

Overview
Fixes snapshot inconsistency where multi-statement entity and subgraph reads could return link vertices without their edges when a concurrent write committed between autocommit statements under READ COMMITTED.

hash-graph-migrations replaces immediate Context::transaction().await with a TransactionBuilder (isolation_level, read_only, deferrable) that composes into one Postgres BEGIN. Migrations and the Postgres client adapter use the new ContextTransaction alias.

hash-graph-postgres-store adds a second type parameter TransactionState (NoTransaction / InTransaction) on PostgresStore, begin_transaction for writes/nested savepoints, and BeginReadOnlyTransaction to start REPEATABLE READ, READ ONLY for reads. query_entities and query_entity_subgraph (and their REST entrypoints) now take &mut self, run the full read on one transaction via *_impl on PostgresStore<_, InTransaction>, then commit. Writes and snapshot restore paths switch from transaction() to begin_transaction(); snapshot WriteBatch hooks require an in-transaction store. hash-graph-migrations becomes a normal dependency of the postgres store crate.

Dependency diagrams and lockfile reflect the new postgres-store ↔ migrations edge.

Reviewed by Cursor Bugbot for commit 000a7c8. Bugbot is set up for automated code reviews on this repo. Configure here.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 fixes a snapshot-consistency anomaly (BE-644) where a subgraph read (query_entity_subgraph) executed as several separate autocommit statements under READ COMMITTED, each with its own MVCC snapshot. A concurrent write committing mid-read could retroactively evict an edge endpoint's edition at the reader's pinned timestamp, producing a subgraph containing a link vertex without its has-left-entity/has-right-entity edge (the root cause of the FE-1172 frontend crash loop). The fix runs the entire subgraph read inside a single READ ONLY, REPEATABLE READ transaction so all statements share one snapshot.

Changes:

  • Reworked hash-graph-migrations Context::transaction() into a synchronous builder (TransactionBuilder trait with isolation_level/read_only/deferrable) that implements IntoFuture, plus a new IsolationLevel enum and ContextTransaction alias; existing context.transaction().await call sites keep compiling.
  • Added AsClient::begin_transaction and PostgresStoreTransactionBuilder in hash-graph-postgres-store, implemented Context/Transaction/TransactionBuilder, and split query_entity_subgraph into a transaction-wrapping method plus query_entity_subgraph_impl; the trait method now takes &mut self, cascading to the REST handler, type fetcher, tests, and benches.
  • Promoted hash-graph-migrations to a public dependency, added DB-backed transaction tests, and regenerated dependency wiring/diagrams.

Reviewed changes

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

Show a summary per file
File Description
libs/@local/graph/migrations/src/context.rs Adds IsolationLevel, TransactionBuilder, ContextTransaction; converts Context::transaction to a synchronous builder-returning method.
libs/@local/graph/migrations/src/postgres.rs Implements PostgresTransactionBuilder delegating to Client::build_transaction; maps IsolationLevel to tokio-postgres.
libs/@local/graph/migrations/src/{lib,migration}.rs Re-exports new items; migration signatures use ContextTransaction.
libs/@local/graph/migrations/graph-migrations/v001..v010/mod.rs Mechanical switch to ContextTransaction<'_, Self::Context>.
libs/@local/graph/postgres-store/src/store/postgres/pool.rs Adds TransactionOptions and AsClient::begin_transaction (composed BEGIN for clients, savepoint fallback for transactions).
libs/@local/graph/postgres-store/src/store/postgres/mod.rs Adds PostgresStoreTransactionBuilder and Context/Transaction/TransactionBuilder impls; re-exports migration types.
libs/@local/graph/postgres-store/src/store/postgres/knowledge/entity/mod.rs Extracts query_entity_subgraph_impl; wraps the read in a READ ONLY, REPEATABLE READ transaction.
libs/@local/graph/postgres-store/src/store/mod.rs Re-exports the new transaction types.
libs/@local/graph/postgres-store/{Cargo.toml,package.json,docs/dependency-diagram.mmd} Promotes hash-graph-migrations to a public dependency.
libs/@local/graph/store/src/entity/store.rs EntityStore::query_entity_subgraph now &mut self, with snapshot-consistency doc.
libs/@local/graph/type-fetcher/src/store.rs Delegating impl updated to &mut self.
libs/@local/graph/api/src/rest/entity/query/mod.rs Acquires mut store for the &mut self call.
tests/graph/integration/postgres/{lib,sorting}.rs, transaction.rs Test harness/tests updated for &mut; new tests assert composed BEGIN options and savepoint inheritance.
tests/graph/benches/** Wrap stores in RefCell to supply the mutable borrow inside benchmark closures.
Various **/docs/dependency-diagram.mmd Regenerated dependency graphs.

…BE-644)

An entity query executes multiple statements: the entity read itself,
the optional entity-type resolution, and the permission checks on the
returned entities. Under READ COMMITTED each statement gets its own
MVCC snapshot, so a write committing mid-read can yield entities,
entity types, and permissions reflecting different states of the store.

query_entities now begins a READ ONLY, REPEATABLE READ transaction via
the transaction builder and runs the whole read on that transaction's
client, mirroring the query_entity_subgraph structure: the previous
trait-method body moves to an inherent query_entities_impl on the
store, and the shared low-level read helper (previously
query_entities_impl) is renamed to read_entities_impl.
EntityStore::query_entities takes &mut self so the implementation can
begin the transaction; search_entities inherits the snapshot through
query_entities and takes &mut self as well. Callers (REST handlers,
type fetcher, tests, benches) are adjusted with the same patterns as
for query_entity_subgraph.
Comment thread libs/@local/graph/postgres-store/src/store/postgres/mod.rs Outdated
Comment thread libs/@local/graph/postgres-store/src/store/postgres/pool.rs Outdated
claude added 2 commits July 10, 2026 09:20
PostgresStore had inherent transaction(), commit(), and rollback()
methods that only delegated to (or mirrored) the hash-graph-migrations
Context and Transaction trait implementations, each carrying an
#[expect(clippy::same_name_method)]. The trait implementations are now
the single canonical API: the Context impl constructs the transaction
builder directly, and the inherent commit/rollback wrappers are gone.

Call sites import the traits; Context and Transaction are re-exported
from hash_graph_postgres_store::store alongside TransactionBuilder for
external users.
Beginning a transaction with TransactionOptions on a store that was
already inside a transaction silently degraded to a savepoint and
dropped the options. Prevent this at compile time with a typestate:

PostgresStore<C, S: TransactionState = NoTransaction> carries a sealed,
zero-sized marker (NoTransaction or InTransaction). The options-bearing
transaction builder (Context::transaction) only exists in the
NoTransaction state and yields the store in the InTransaction state;
nesting is only possible through the options-free begin_transaction(),
which issues a plain BEGIN at the root and a savepoint inside a
transaction, so there is nothing left to silently ignore.
AsClient::begin_transaction and its savepoint fallback are removed
entirely; AsClient is back to pure client access and store functions
remain callable from both states.

The REPEATABLE READ, READ ONLY scope used by query_entities and
query_entity_subgraph moves into the sealed BeginReadOnlyTransaction
trait, which is dispatched per state: a configured top-level
transaction in the NoTransaction state, a savepoint within the
enclosing transaction in the InTransaction state.

The snapshot-restore write batches and the test harnesses now name the
InTransaction state explicitly.
@vercel vercel Bot temporarily deployed to Preview – petrinaut July 10, 2026 09:44 Inactive
Comment thread libs/@local/graph/postgres-store/src/store/postgres/knowledge/entity/mod.rs Outdated
Comment thread libs/@local/graph/postgres-store/src/store/postgres/mod.rs
query_entities_impl and query_entity_subgraph_impl only ever run on the
store returned by BeginReadOnlyTransaction::begin_read_only_transaction,
but lived in the state-generic impl block and carried a
BeginReadOnlyTransaction bound they never used. Move them into a
PostgresStore<C, InTransaction> impl block so the typestate itself
states they execute inside the read transaction.

Also deduplicate the BEGIN option composition: the NoTransaction
BeginReadOnlyTransaction impl now delegates to the canonical
Context::transaction() builder instead of hand-rolling
build_transaction(), leaving a single code path which compiles
TransactionOptions into the BEGIN statement.
In production every snapshot-consistent read begins its own REPEATABLE
READ, READ ONLY transaction: the API only ever calls query_entities /
query_entity_subgraph / search_entities on NoTransaction stores. The
InTransaction impl of BeginReadOnlyTransaction — a savepoint that
inherits the enclosing transaction's isolation, i.e. no single-snapshot
guarantee — exists solely for the rollback-isolation test harnesses,
which wrap each test in a transaction. Hide it behind a new test-utils
cargo feature so the production surface cannot silently take the
savepoint path; the harness crates (the graph integration tests, the
crate's own suites via a self dev-dependency, and the benches' seeding
helpers) opt back in. TODO(BE-688) on the trait tracks removing the
impl once the harness uses per-test databases.

reindex_entity_cache and has_permission_for_entities move to an
inherent impl on the state-generic PostgresStore<C, S> (the EntityStore
methods delegate): snapshot restore, entity-type reindexing, and the
read _impl helpers call them on InTransaction stores and must keep
compiling without the gated impl, since the blanket EntityStore impl is
bounded on BeginReadOnlyTransaction as a whole.
- Format the tests/graph manifests with taplo (the inline comments split
  the key alignment groups)
- Ignore the self dev-dependency in sync-turborepo metadata so the
  generated package.json keeps a Turborepo-valid dependency graph
- Add the resulting self-loop dev edge to the dependency diagrams that
  include hash-graph-postgres-store
@vercel vercel Bot temporarily deployed to Preview – petrinaut July 10, 2026 11:40 Inactive

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit dc931b0. Configure here.

Comment thread libs/@local/graph/postgres-store/src/store/postgres/knowledge/entity/mod.rs Outdated
The inherent `reindex_entity_cache` and `has_permission_for_entities`
shared their names with the `EntityStore` methods that delegate to them,
relying on inherent-method precedence (guarded only by
`#[expect(clippy::same_name_method)]`). Renaming or removing the inherent
method would silently turn each delegation into infinite recursion.

Follow the file's existing `*_impl` naming (`query_entities_impl`,
`read_entities_impl`, ...) so the delegations are unambiguous, and drop
the now-unneeded lint expectations.
@github-actions

Copy link
Copy Markdown
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.9 \mathrm{ms} \pm 216 \mathrm{μs}\left({\color{gray}1.30 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$3.47 \mathrm{ms} \pm 30.5 \mathrm{μs}\left({\color{gray}-0.125 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 1002 $$12.6 \mathrm{ms} \pm 127 \mathrm{μs}\left({\color{gray}-1.482 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 3314 $$44.3 \mathrm{ms} \pm 420 \mathrm{μs}\left({\color{gray}0.780 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$14.3 \mathrm{ms} \pm 167 \mathrm{μs}\left({\color{gray}0.668 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 1527 $$24.6 \mathrm{ms} \pm 189 \mathrm{μs}\left({\color{gray}-3.090 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 2078 $$28.8 \mathrm{ms} \pm 188 \mathrm{μs}\left({\color{gray}-0.069 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$3.77 \mathrm{ms} \pm 25.1 \mathrm{μs}\left({\color{gray}0.008 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 1033 $$14.2 \mathrm{ms} \pm 148 \mathrm{μs}\left({\color{gray}0.788 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_medium

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 102 $$3.82 \mathrm{ms} \pm 21.9 \mathrm{μs}\left({\color{gray}-0.180 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$3.01 \mathrm{ms} \pm 17.6 \mathrm{μs}\left({\color{gray}-1.492 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 52 $$3.44 \mathrm{ms} \pm 32.8 \mathrm{μs}\left({\color{gray}1.52 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 269 $$5.19 \mathrm{ms} \pm 28.3 \mathrm{μs}\left({\color{gray}-1.546 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$3.59 \mathrm{ms} \pm 25.2 \mathrm{μs}\left({\color{gray}-0.431 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 108 $$4.14 \mathrm{ms} \pm 23.3 \mathrm{μs}\left({\color{gray}-0.633 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 133 $$4.43 \mathrm{ms} \pm 29.8 \mathrm{μs}\left({\color{gray}0.069 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$3.46 \mathrm{ms} \pm 22.0 \mathrm{μs}\left({\color{gray}-0.256 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 63 $$4.15 \mathrm{ms} \pm 35.9 \mathrm{μs}\left({\color{gray}0.667 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_none

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 2 $$2.69 \mathrm{ms} \pm 15.3 \mathrm{μs}\left({\color{gray}0.210 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$2.52 \mathrm{ms} \pm 13.2 \mathrm{μs}\left({\color{gray}-0.769 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 2 $$2.66 \mathrm{ms} \pm 18.4 \mathrm{μs}\left({\color{gray}-0.139 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 8 $$2.98 \mathrm{ms} \pm 18.8 \mathrm{μs}\left({\color{gray}0.842 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$2.75 \mathrm{ms} \pm 16.5 \mathrm{μs}\left({\color{gray}0.733 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 3 $$2.99 \mathrm{ms} \pm 26.2 \mathrm{μs}\left({\color{gray}2.19 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_small

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 52 $$3.09 \mathrm{ms} \pm 23.9 \mathrm{μs}\left({\color{gray}0.618 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$2.80 \mathrm{ms} \pm 17.9 \mathrm{μs}\left({\color{gray}1.59 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 26 $$3.01 \mathrm{ms} \pm 17.5 \mathrm{μs}\left({\color{gray}0.790 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 94 $$3.45 \mathrm{ms} \pm 21.7 \mathrm{μs}\left({\color{gray}-0.773 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$3.03 \mathrm{ms} \pm 19.0 \mathrm{μs}\left({\color{gray}1.51 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 27 $$3.35 \mathrm{ms} \pm 21.1 \mathrm{μs}\left({\color{gray}0.985 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 66 $$3.42 \mathrm{ms} \pm 21.7 \mathrm{μs}\left({\color{gray}-0.937 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$2.97 \mathrm{ms} \pm 17.5 \mathrm{μs}\left({\color{gray}-1.501 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 29 $$3.35 \mathrm{ms} \pm 18.4 \mathrm{μs}\left({\color{gray}-1.091 \mathrm{\%}}\right) $$ Flame Graph

read_scaling_complete

Function Value Mean Flame graphs
entity_by_id;one_depth 1 entities $$42.8 \mathrm{ms} \pm 212 \mathrm{μs}\left({\color{gray}2.92 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 10 entities $$33.5 \mathrm{ms} \pm 185 \mathrm{μs}\left({\color{gray}2.21 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 25 entities $$36.1 \mathrm{ms} \pm 174 \mathrm{μs}\left({\color{gray}1.43 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 5 entities $$32.5 \mathrm{ms} \pm 214 \mathrm{μs}\left({\color{gray}1.95 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 50 entities $$42.5 \mathrm{ms} \pm 256 \mathrm{μs}\left({\color{gray}2.09 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 1 entities $$50.3 \mathrm{ms} \pm 341 \mathrm{μs}\left({\color{gray}2.72 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 10 entities $$40.9 \mathrm{ms} \pm 237 \mathrm{μs}\left({\color{gray}1.48 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 25 entities $$89.4 \mathrm{ms} \pm 598 \mathrm{μs}\left({\color{gray}-2.309 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 5 entities $$34.5 \mathrm{ms} \pm 226 \mathrm{μs}\left({\color{gray}3.54 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 50 entities $$313 \mathrm{ms} \pm 1.12 \mathrm{ms}\left({\color{gray}2.52 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 1 entities $$11.1 \mathrm{ms} \pm 73.3 \mathrm{μs}\left({\color{gray}4.72 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 10 entities $$11.3 \mathrm{ms} \pm 87.5 \mathrm{μs}\left({\color{red}6.33 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 25 entities $$11.4 \mathrm{ms} \pm 109 \mathrm{μs}\left({\color{red}5.95 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 5 entities $$11.2 \mathrm{ms} \pm 64.7 \mathrm{μs}\left({\color{red}5.22 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 50 entities $$11.2 \mathrm{ms} \pm 75.6 \mathrm{μs}\left({\color{gray}4.29 \mathrm{\%}}\right) $$ Flame Graph

read_scaling_linkless

Function Value Mean Flame graphs
entity_by_id 1 entities $$11.0 \mathrm{ms} \pm 59.9 \mathrm{μs}\left({\color{gray}1.82 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 10 entities $$11.2 \mathrm{ms} \pm 78.7 \mathrm{μs}\left({\color{gray}4.10 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 100 entities $$11.2 \mathrm{ms} \pm 81.7 \mathrm{μs}\left({\color{gray}4.89 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 1000 entities $$11.2 \mathrm{ms} \pm 95.3 \mathrm{μs}\left({\color{gray}4.38 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 10000 entities $$11.3 \mathrm{ms} \pm 83.7 \mathrm{μs}\left({\color{gray}3.57 \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 $$11.5 \mathrm{ms} \pm 63.5 \mathrm{μs}\left({\color{gray}3.30 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/book/v/1 $$11.6 \mathrm{ms} \pm 75.6 \mathrm{μs}\left({\color{gray}2.96 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/building/v/1 $$11.5 \mathrm{ms} \pm 69.9 \mathrm{μs}\left({\color{gray}2.38 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/organization/v/1 $$11.7 \mathrm{ms} \pm 75.6 \mathrm{μs}\left({\color{red}5.01 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/page/v/2 $$11.8 \mathrm{ms} \pm 87.8 \mathrm{μs}\left({\color{gray}2.53 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/person/v/1 $$11.8 \mathrm{ms} \pm 72.1 \mathrm{μs}\left({\color{red}5.23 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/playlist/v/1 $$11.6 \mathrm{ms} \pm 84.9 \mathrm{μs}\left({\color{gray}3.31 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/song/v/1 $$11.8 \mathrm{ms} \pm 89.9 \mathrm{μs}\left({\color{red}6.37 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/uk-address/v/1 $$11.6 \mathrm{ms} \pm 76.8 \mathrm{μs}\left({\color{gray}1.79 \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 $$8.65 \mathrm{ms} \pm 47.5 \mathrm{μs}\left({\color{gray}-1.902 \mathrm{\%}}\right) $$ Flame Graph

representative_read_multiple_entities

Function Value Mean Flame graphs
entity_by_property traversal_paths=0 0 $$61.3 \mathrm{ms} \pm 488 \mathrm{μs}\left({\color{gray}-2.626 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=255 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true $$115 \mathrm{ms} \pm 704 \mathrm{μs}\left({\color{gray}-1.297 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false $$67.8 \mathrm{ms} \pm 488 \mathrm{μs}\left({\color{gray}-2.652 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true $$77.3 \mathrm{ms} \pm 524 \mathrm{μs}\left({\color{gray}-2.909 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true $$87.1 \mathrm{ms} \pm 490 \mathrm{μs}\left({\color{gray}-1.057 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true $$93.7 \mathrm{ms} \pm 498 \mathrm{μs}\left({\color{gray}-3.996 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=0 0 $$44.9 \mathrm{ms} \pm 232 \mathrm{μs}\left({\color{gray}-2.876 \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 $$75.5 \mathrm{ms} \pm 439 \mathrm{μs}\left({\color{gray}-1.519 \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 $$52.0 \mathrm{ms} \pm 480 \mathrm{μs}\left({\color{gray}0.418 \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 $$61.0 \mathrm{ms} \pm 437 \mathrm{μs}\left({\color{gray}-2.206 \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 $$64.0 \mathrm{ms} \pm 509 \mathrm{μs}\left({\color{gray}-0.823 \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 $$62.8 \mathrm{ms} \pm 394 \mathrm{μs}\left({\color{gray}-3.223 \mathrm{\%}}\right) $$

scenarios

Function Value Mean Flame graphs
full_test query-limited $$124 \mathrm{ms} \pm 615 \mathrm{μs}\left({\color{gray}-0.727 \mathrm{\%}}\right) $$ Flame Graph
full_test query-unlimited $$136 \mathrm{ms} \pm 660 \mathrm{μs}\left({\color{gray}1.71 \mathrm{\%}}\right) $$ Flame Graph
linked_queries query-limited $$20.1 \mathrm{ms} \pm 191 \mathrm{μs}\left({\color{gray}3.54 \mathrm{\%}}\right) $$ Flame Graph
linked_queries query-unlimited $$540 \mathrm{ms} \pm 1.15 \mathrm{ms}\left({\color{gray}-4.896 \mathrm{\%}}\right) $$ Flame Graph

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

Labels

area/apps > hash* Affects HASH (a `hash-*` app) area/apps > hash-graph area/apps area/deps Relates to third-party dependencies (area) area/libs Relates to first-party libraries/crates/packages (area) area/tests New or updated tests type/eng > backend Owned by the @backend team

Development

Successfully merging this pull request may close these issues.

3 participants