Skip to content

[query] Cache the last contig index for canonical loci - #15881

Merged
hail-ci-robot merged 6 commits into
mainfrom
cv/query/stack2/locus-contig-index-cache
Oct 29, 2026
Merged

hail-ci-robot merged 6 commits into
mainfrom
cv/query/stack2/locus-contig-index-cache

Conversation

@chrisvittal

Copy link
Copy Markdown
Collaborator

Converting a canonical locus to its contig index loaded the contig as a String and looked it up in the reference genome's contig map every time. Loci are usually processed in key order, so consecutive lookups almost always hit the same contig.

ContigIndexCache keeps a copy of the last contig's bytes and its index. A hit compares bytes in place and creates no String. A miss falls back to ReferenceGenome.getContigIndex, so results are unchanged. Generated code holds one instance per reference genome as a lazy field of the generated class, keeping the cache confined to one thread and off the shared ReferenceGenome.

Security Assessment

  • This change cannot impact the Hail Batch instance as deployed by Broad Institute in GCP

Stack created with GitHub Stacks CLI • Give Feedback 💬

@chrisvittal
chrisvittal added this pull request to stack #15876 October 16, 2026 23:28
@coderabbitai

coderabbitai Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: ef44d619-d05a-4403-bdf7-7457ba7391f2
Commits

Reviewing files that changed from the base of the PR and between 7f0f929 and e1eaa2d.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review.

ℹ️ Recent review info
⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 85aa656a-9374-4d7b-b3ef-4ddffc794523
📥 Commits

Reviewing files that changed from the base of the PR and between eb68ae8 and 848bc4e.

📒 Files selected for processing: 1
  • hail/hail/src/is/hail/types/physical/stypes/concrete/SCanonicalLocusPointer.scala

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 2 remain after this review.

Walkthrough

Canonical locus contig-index lookups now use a byte-based cache. Missing contigs produce a reference-specific error. Tests cover cache behavior and use explicit stable-encoding contexts for stable baselines.

Changes: Contig Index Lookup

Layer / File(s) Summary
Cached contig-index lookup
hail/hail/src/is/hail/variant/ReferenceGenome.scala, hail/hail/src/is/hail/variant/ContigIndexCache.scala, hail/hail/src/is/hail/types/physical/stypes/concrete/SCanonicalLocusPointer.scala, hail/hail/src/is/hail/expr/ir/functions/LocusFunctions.scala
ContigIndexCache reuses an index when the contig bytes match its cached entry. Otherwise, it decodes the bytes and calls ReferenceGenome.getContigIndex. Canonical locus lookup uses the cache, and missing contigs produce a reference-specific error.
Contig lookup coverage
hail/hail/test/src/is/hail/variant/ContigIndexCacheSuite.scala, hail/hail/test/src/is/hail/expr/ir/LocusFunctionsSuite.scala, hail/hail/test/src/is/hail/variant/ReferenceGenomeSuite.scala
Tests cover built-in and custom reference genomes, cache behavior with reused region memory, generated-code lookups, contig-index results, and missing-contig errors.
Stable encoding test contexts
hail/hail/test/src/is/hail/TestUtils.scala, hail/hail/test/src/is/hail/expr/ir/TableIRSuite.scala, hail/hail/test/src/is/hail/io/bgen/BgenIndexSuite.scala, hail/hail/test/src/is/hail/types/encoded/ETypeSuite.scala
withStableEncodings runs callbacks with unstable encodings disabled. Stable baseline setup in table, BGEN index, and encoded-type tests now uses explicit stable-encoding contexts.

Priority: Low

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Refactor

Sequence Diagram(s)

sequenceDiagram
  participant SLocusValue
  participant ContigIndexCache
  participant ReferenceGenome
  SLocusValue->>ContigIndexCache: Pass contig byte address and length
  ContigIndexCache->>ContigIndexCache: Compare bytes with cached contig
  ContigIndexCache->>ReferenceGenome: Look up decoded contig on cache miss
  ReferenceGenome-->>ContigIndexCache: Return contig index or report missing contig
  ContigIndexCache-->>SLocusValue: Return contig index
Loading

Suggested reviewers: ehigham

Merge Risk: Minimal · previous review 848bc

The cache preserves canonical locus contig-index results and missing-contig errors without introducing merge-blocking risk.

Security architecture risk: Minimal · up to 96c8e

The inspected cache preserves reference-genome validation, owns a copy of the contig bytes, and remains local to generated execution instances. No material security risk introduced or worsened by this change was identified.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The demonstrated propagation is through Hail query contig ordinals, including contig_idx and packed-locus consumers. Cache corruption could affect query data correctness, but the inspected ownership path does not introduce shared cache state across concurrent partitions.

Trust Boundaries and Controls

  • inferred — Input contig bytes cannot select a new ordinal merely by matching a prefix or length: every byte must match a previously resolved entry for the cache's reference genome. Other inputs return to the authoritative lookup, including its missing-contig rejection. No validation bypass was identified in this path.
Pre-merge checks: Passed 4
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main change: caching the last contig index for canonical loci.
Description check Passed The description explains the change and justification, and it includes the required security assessment conclusion. The impact-rating, impact-description, and Appsec Review sections may be omitted bec…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description explains the change and justification, and it includes the required security assessment conclusion. The impact-rating, impact-description, and Appsec Review sections may be omitted because the change is stated not to affect the Hail Batch instance.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@hail-ci-robot
hail-ci-robot force-pushed the cv/query/stack2/locus-contig-index-cache branch from 96c8e94 to 8b5ee71 Compare October 21, 2026 23:32
// Compares the contig's bytes in place against the last contig seen, so runs of loci on one
// contig load no String.
override def contigIdx(cb: EmitCodeBuilder): Value[Int] = {
val cache = cb.emb.getOrDefineLazyField[ContigIndexCache](

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.

Does this need to be defined on the module to survive method splitting?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

No. Method splitting, as a pass, fixes up references if they're moved around. Furthermore, I don't think that method splitting makes new classes. The fields stay on the same class, as to the methods. IIRC, we package locals into a field holding a container object, then when the split method is called, those locals are packed, then unpacked on each side of the caller.

override def contigIdx(cb: EmitCodeBuilder): Value[Int] = {
val cache = cb.emb.getOrDefineLazyField[ContigIndexCache](
Code.newInstance[ContigIndexCache, ReferenceGenome](cb.emb.getReferenceGenome(st.rg)),
("contig_index_cache", st.rg),

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.

I forget how fields are named and whether or not there's some amount of scrambling, but what is it possible a query operates on more than one reference? shouldn't the name include the rg to avoid collisions?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I think you're right. This is easy to fix.

@ehigham ehigham 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.

see comments. Looking good otherwise

@hail-ci-robot
hail-ci-robot force-pushed the cv/query/stack2/locus-contig-index-cache branch from 8b5ee71 to eb68ae8 Compare October 22, 2026 16:54
@chrisvittal
chrisvittal force-pushed the cv/query/stack2/locus-contig-index-cache branch from eb68ae8 to 848bc4e Compare October 22, 2026 17:19
@chrisvittal
chrisvittal requested a review from ehigham October 22, 2026 17:19
@hail-ci-robot
hail-ci-robot force-pushed the cv/query/stack2/locus-contig-index-cache branch 6 times, most recently from 3f71b69 to 3810194 Compare October 23, 2026 00:33
@hail-ci-robot
hail-ci-robot force-pushed the cv/query/stack2/locus-contig-index-cache branch 2 times, most recently from b3f4f66 to 510b8fd Compare October 23, 2026 14:20
@chrisvittal
chrisvittal force-pushed the cv/query/stack2/locus-contig-index-cache branch from 510b8fd to 61b0000 Compare October 23, 2026 17:30
@chrisvittal
chrisvittal force-pushed the cv/query/stack2/locus-contig-index-cache branch from 61b0000 to 9d93832 Compare October 26, 2026 14:30
@hail-ci-robot
hail-ci-robot force-pushed the cv/query/stack2/locus-contig-index-cache branch 2 times, most recently from 81af4d8 to 05624ac Compare October 26, 2026 21:13
@chrisvittal
chrisvittal force-pushed the cv/query/stack2/locus-contig-index-cache branch 2 times, most recently from fdf00c7 to 6566137 Compare October 27, 2026 16:57
@chrisvittal
chrisvittal force-pushed the cv/query/stack2/locus-contig-index-cache branch from 6566137 to ad0d9d6 Compare October 27, 2026 18:27
@hail-ci-robot
hail-ci-robot force-pushed the cv/query/stack2/locus-contig-index-cache branch from ad0d9d6 to ee7f13a Compare October 28, 2026 15:32
@chrisvittal
chrisvittal force-pushed the cv/query/stack2/locus-contig-index-cache branch from ee7f13a to 7f0f929 Compare October 29, 2026 16:33
Base automatically changed from cv/query/stack2/packed-locus-encode-decode to main October 29, 2026 23:03
Converting a canonical locus to its contig index loaded the contig as a
String and looked it up in the reference genome's contig map every time.
Loci are usually processed in key order, so consecutive lookups almost
always hit the same contig.

ContigIndexCache keeps a copy of the last contig's bytes and its index.
A hit compares bytes in place and creates no String. A miss falls back
to ReferenceGenome.getContigIndex, so results are unchanged. Generated
code holds one instance per reference genome as a lazy field of the
generated class, keeping the cache confined to one thread and off the
shared ReferenceGenome.
getContigIndex unboxed the contig map's null for an unknown contig to 0,
so an unknown contig silently became the reference genome's first
contig. It now raises the same error as checkContig.

Locus producers validate contigs, so well-formed data never reaches this
path, but contig_idx, canonical contigIdx / packed, and unstaged packing
of loci all go through it.
contig_idx loaded the contig as a String and looked it up in the
reference genome, bypassing SLocusValue.contigIdx. Canonical loci now
get the cached contig index lookup, and packed loci read the index
directly from their integer.
Tests that check unstable encodings against stable ones took the
session's flags as the stable baseline. With use_unstable_encodings set
for the whole run, two of them failed, and others compared the unstable
encoding with itself. Add withStableEncodings and use it for each
stable half.
The cache's own suite checked the helper directly. These tests drive it
through the canonical locus's generated contigIdx, where a cache could
go wrong in ways a direct call does not show:

- Region reuse: after the region is cleared, a different contig of the
  same length lands at the same address. A cache keyed on the address,
  or on a view of the region, returns the old index.
- Unknown contigs: a failed lookup, repeated, leaves the cache giving
  correct indices for the previously cached contig and a new one.
- Concurrency: many small partitions on the local backend, with
  contigs interleaved within each partition, write canonical loci under
  unstable encodings and read them back. Expected values come from
  ReferenceGenome.getContigIndex, the uncached lookup.
@hail-ci-robot
hail-ci-robot force-pushed the cv/query/stack2/locus-contig-index-cache branch from 7f0f929 to e1eaa2d Compare October 29, 2026 23:03
@hail-ci-robot
hail-ci-robot merged commit 55fbdd7 into main Oct 29, 2026
13 checks passed
@hail-ci-robot
hail-ci-robot deleted the cv/query/stack2/locus-contig-index-cache branch October 29, 2026 23:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants