Skip to content

Commit f22c24e

Browse files
mwiewiorclaude
andauthored
feat: optimize annotation context loading (#44)
* feat: optimize annotation context loading with column projection and miss worklist - Fix profiling bug: replace .any() short-circuit with full iteration over all batches for accurate cache hit/miss counts - Add MissWorklist: collects cache-miss variant positions, coalesces nearby intervals, generates interval-aware SQL predicates - Column projection: replace SELECT * with explicit column lists in all context loaders. The load_translations change alone saves ~13s by skipping 132 MB of sift/polyphen data the main loader discards. - Interval predicates: regulatory/motif/mirna/structural loaders use position-overlap predicates instead of chrom-only IN clauses - Rust-side transcript_id filter: after loading transcripts, filter exons and translations by HashSet in Rust (microseconds) - Support split translation layout: translations_sift_table option directs sift window loading to a dedicated sift parquet file Measured impact (chr1, 319K variants, no --everything): context_tables_total: 13.5s → 0.47s (-97%) total pipeline: 60.0s → 49.6s (-17%) Refs: #43 Refs: biodatageeks/datafusion-bio-formats#131 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: support split translation layout in golden bench and profile examples - annotate_vep_golden_bench: discover translation_core and translation_sift parquet files from context_dir, register translations_sift_table for sift window loading - profile_annotation: same split layout support with fallback to unsplit translation file - Add benchmark.md with golden benchmark invocation and results: 80/80 CSQ fields at 100% accuracy, context_tables 13.5s → 0.5s Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * perf: filter sift loading by translation transcript_ids and add split timing - Add restrict_to parameter to load_sift_window to skip transcripts not in the loaded translations set - Add separate VEP_PROFILE timing for sift loading vs annotation (7a. sift_lazy_load_only, 7b. annotate_batches_only) Golden benchmark: 80/80 fields at 100% accuracy, no regression. For the distributed chr1 workload (all translations loaded), the filter has minimal effect. The two-pass approach (filter by actual missense transcript_ids) is needed for significant sift savings. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Revert "perf: filter sift loading by translation transcript_ids and add split timing" This reverts commit 5340e15. * perf: add split profiling for sift loading vs annotation Adds separate VEP_PROFILE timing lines: - 7a. sift_lazy_load_only: time spent in sift window SQL queries - 7b. annotate_batches_only: time spent in transcript consequence engine This makes it clear that sift I/O dominates (48s) vs annotation (8s) in --everything mode. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * perf: compact sift/polyphen predictions — eliminate String allocations Replace HashMap<(i32, String), (String, f32)> with sorted Vec<CompactPrediction> where amino acid and prediction type are encoded as u8 indices instead of heap-allocated Strings. - Amino acid: single char → u8 (A=0..Y=24) - Prediction: ~5 unique strings → u8 enum - Lookup: binary search on sorted Vec vs HashMap get - Eliminates ~256M String allocations across 51 sift windows Measured impact (chr1 --everything, 319K variants, golden bench): sift_lazy_load: 48.0s → 35.8s (-25%) total pipeline: 104.6s → 91.7s (-12%) correctness: 80/80 CSQ fields at 100% accuracy Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: update benchmark.md with compact prediction timings - Add 7a/7b split profiling (sift_lazy_load: 35.8s, annotate: 8.8s) - Update --everything before/after table: 107.7s → 91.7s (-15%) - List all optimizations applied Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * perf: zero-copy Arrow parsing for sift predictions Add read_compact_predictions() that reads directly from Arrow arrays into CompactPrediction without intermediate ProteinPrediction structs or String allocations. Amino acid and prediction &str are read from Arrow buffers (zero-copy) and encoded to u8 in-place. Eliminates ~133M String allocations (2.6M per window × 51 windows) that were created in read_protein_predictions() and immediately discarded after encoding to u8. Measured impact (chr1 --everything, 319K variants, golden bench): sift_lazy_load: 35.8s → 26.5s (-26%) total pipeline: 91.7s → 83.2s (-9%) correctness: 80/80 CSQ fields at 100% accuracy Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add --output=<path> VCF sink to profile_annotation example Writes annotated output as minimal VCF (CHROM, POS, ID, REF, ALT, QUAL, FILTER, INFO with CSQ field) with timing. Useful for comparing annotation output across code changes. Usage: cargo run --release --example profile_annotation -- \ input.vcf.gz cache_dir 1000 --output=/tmp/output.vcf Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: update benchmark.md with latest profile_annotation timings - Full chr1 --everything with VCF output: 82.3s (was 107.7s baseline) - sift_lazy_load: 26.1s (was 48.0s, -46%) - context_tables: 0.5s (was 13.6s, -96%) - Total savings: 25.4s (-24%) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * perf: direct parquet-rs sift reader with cached ArrowReaderMetadata Bypass DataFusion session.sql() for sift/polyphen window loading. Read parquet footer once, pre-compute RG position ranges, then reuse cached metadata for all 51 window queries. Falls back to session.sql() when the sift parquet file path cannot be resolved. - Move parquet crate from dev-dependencies to dependencies - Add SiftDirectReader struct with cached metadata + projection + RG ranges - Resolve sift file path from cache_source parent directory - Each window: open file (fd only), reuse metadata, select matching RGs Measured impact (chr1 --everything, 319K variants, profile_annotation): sift_lazy_load: 26.1s → 23.7s (-9%) total pipeline: 82.3s → 78.5s (-5%) correctness: 80/80 CSQ fields at 100% accuracy (golden bench) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: update benchmark.md with direct parquet-rs sift reader results - sift_lazy_load: 48.0s → 23.7s (-51%) - total pipeline: 107.7s → 78.5s (-27%) - Add cumulative optimization impact table - Add remaining bottlenecks breakdown Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: reuse CSQ string buffer across rows instead of Vec<String> + join Replace per-row `Vec<String>` + `format!()` + `join(",")` pattern with a single reusable `csq_buf: String` and `write!()`. Also reuse `terms_buf` for the per-CSQ-entry terms.join("&"). Eliminates ~3M intermediate String allocations (one per CSQ entry) and ~321K Vec<String> allocations (one per row). Unmeasurable wall-clock impact (~90ms theoretical) but reduces allocator pressure. Golden benchmark: 80/80 CSQ fields at 100% accuracy. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: correct VCF output positions and add id/qual/filter columns - Fix int64_at to handle UInt32 (VCF provider's start column type) - Write correct 1-based VCF POS (start column is already 1-based) - Include id, qual, filter columns from the annotation output - ID field is empty string when not available (VCF provider behavior) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: pass through original VCF INFO fields in annotation output - Register VCF with None for info_fields/format_fields (include all) instead of Some(vec![]) (include none) - VCF writer merges original INFO fields with CSQ annotation - Fix int64_at to handle UInt32/UInt64 column types The annotation UDTF output Arrow table now contains all original VCF columns (INFO fields, FORMAT/sample fields) alongside annotation columns (csq, most_severe_consequence, cache columns). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: proper VCF output with INFO, FORMAT, and sample columns Use VCF field metadata (bio.vcf.field.field_type) from the original VCF input schema to properly classify columns as INFO vs FORMAT. The annotation pipeline output loses field metadata, so we capture the input schema before registration and use it during VCF writing. - INFO columns: written as key=value pairs in INFO field - FORMAT columns: written as colon-separated values per sample - Sample names: read from bio.vcf.samples schema metadata - CSQ annotation: appended to INFO field Output format: #CHROM POS ID REF ALT QUAL FILTER INFO FORMAT SAMPLE Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: add VCF output round-trip tests with noodles-vcf Add noodles-vcf as dev-dependency and write 4 round-trip tests that: 1. Generate a VCF from annotation output Arrow batches 2. Read it back with noodles-vcf parser 3. Verify the file is valid VCF (header, records parse without error) 4. Verify positions, CSQ in INFO, FORMAT/sample columns, empty batches Tests: - test_vcf_roundtrip_noodles_can_parse: header + 3 records parse OK - test_vcf_roundtrip_positions_correct: CHROM, POS, REF, ALT values - test_vcf_roundtrip_csq_preserved: CSQ annotation in INFO field - test_vcf_roundtrip_empty_batches: empty input produces valid VCF Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: update Cargo.lock for noodles-vcf dev-dependency Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: apply cargo fmt formatting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: resolve clippy warnings for CI (--all-targets --all-features) - Remove unused mut on records Vec in roundtrip test - Replace redundant closure with PathBuf::from - Collapse nested if-let statements - Use rsplit().next() instead of split().last() on DoubleEndedIterator Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address PR review feedback from Claude and Codex - Remove dead code: resolve_parquet_path (always returned None), ProteinPrediction struct, read_protein_predictions (replaced by read_compact_predictions) - Fix {sift_load_ms:.1} format specifier: .1 has no effect on u128 - Extract MAX_INTERVAL_CLAUSES constant (50) replacing magic number - Add doc comment on MissWorklist::chroms explaining bare-name invariant Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address second round of PR review feedback - Remove dead AnnotateProvider::chrom_filter_clause (callers migrated to worklist.chrom_filter_clause()) - Remove dead CompactPrediction::decode_amino_acid (never called) - Write minimal VCF header for empty batch results instead of empty file Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent b11017d commit f22c24e

10 files changed

Lines changed: 1488 additions & 240 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

datafusion/bio-function-vep/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,14 @@ coitrees = "0.4.0"
2525
serde_json = "1"
2626
noodles-core = "0.16"
2727
noodles-fasta = "0.49"
28+
parquet = { version = "56", features = ["arrow"] }
2829

2930
[dev-dependencies]
3031
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
3132
datafusion-bio-format-vcf = { git = "https://github.com/biodatageeks/datafusion-bio-formats.git", rev = "45562f3beb230c23008b19bfe6c172bd1c5923fa" }
33+
noodles-vcf = { git = "https://github.com/biodatageeks/noodles.git", rev = "9b7b2c5b6531373918302d4c07410e583f1b5b5c" }
3234
env_logger = "0.11"
3335
tempfile = "3"
34-
parquet = { version = "56", features = ["arrow"] }
3536

3637
[[example]]
3738
name = "vcf_batch_stats"
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
# Benchmark Results: Annotation Context Loading Optimization
2+
3+
## Environment
4+
5+
- Machine: macOS Darwin 24.6.0
6+
- Rust: edition 2024, toolchain 1.88.0
7+
- DataFusion: 52.1.0
8+
- Dataset: HG002 chr1 (319,349 variants → 323,430 after multi-allelic decomposition)
9+
- Cache: chr1-vep split layout (translation_core + translation_sift, 90 RGs × 256 rows)
10+
11+
## Golden Benchmark Invocation
12+
13+
```bash
14+
mkdir -p /tmp/annotate_vep_everything_chr1vep
15+
cp vep-benchmark/data/output/everything/HG002_chr1_0_vep115_golden.vcf \
16+
/tmp/annotate_vep_everything_chr1vep/HG002_chr1_0_vep115_golden.vcf
17+
18+
VEP_PROFILE=1 cargo run --release -p datafusion-bio-function-vep \
19+
--example annotate_vep_golden_bench -- \
20+
--everything \
21+
--extended-probes \
22+
--reference-fasta-path=/Users/mwiewior/research/data/vep/Homo_sapiens.GRCh38.dna.primary_assembly.fa \
23+
vep-benchmark/data/HG002_chr1.vcf.gz \
24+
/Users/mwiewior/research/data/vep/chr1-vep/115_GRCh38_variation_1_vep.parquet \
25+
parquet \
26+
0 \
27+
/Users/mwiewior/research/data/vep/homo_sapiens/115_GRCh38 \
28+
vep-benchmark/data/HG002_chr1.vcf.gz \
29+
/tmp/annotate_vep_everything_chr1vep \
30+
/Users/mwiewior/research/data/vep/chr1-vep \
31+
--steps=ensembl,datafusion
32+
```
33+
34+
## Correctness: 80/80 CSQ Fields at 100% Accuracy
35+
36+
```
37+
golden_rows=323430
38+
ours_rows=323430
39+
intersection_rows=323430
40+
missing_in_ours=0
41+
extra_in_ours=0
42+
golden_with_csq=323428
43+
ours_with_csq=323428
44+
most_severe_accuracy=100.0%
45+
term_set_accuracy=100.0%
46+
47+
Perfect (0 mismatches): 80/80 fields
48+
```
49+
50+
## Timing Breakdown (VEP_PROFILE, --everything mode, profile_annotation, 321K VCF rows)
51+
52+
```
53+
[VEP_PROFILE] 1. variation_lookup (scan+collect)................ 38642.3ms 321713 VCF rows, 10840 batches
54+
[VEP_PROFILE] 2. collect_variant_intervals...................... 94.0ms
55+
[VEP_PROFILE] 3. colocated_map_build............................ 365.7ms 314926 entries
56+
[VEP_PROFILE] cache hits: 0, misses: 321713, hit rate: 0.0%
57+
[VEP_PROFILE] 4a. load_transcripts.............................. 301.2ms 47849 transcripts
58+
[VEP_PROFILE] 4b. load_exons.................................... 49.2ms 355800 exons
59+
[VEP_PROFILE] 4c. load_translations............................. 93.6ms 22832 translations
60+
[VEP_PROFILE] 4d. load_regulatory............................... 10.9ms 35815 features
61+
[VEP_PROFILE] 4e. load_motif.................................... 1.1ms
62+
[VEP_PROFILE] 4f. load_mirna.................................... 0.0ms
63+
[VEP_PROFILE] 4g. load_structural............................... 0.0ms
64+
[VEP_PROFILE] 4. context_tables_total........................... 461.1ms
65+
[VEP_PROFILE] 5a. hydrate_refseq_cds............................ 20.6ms 0 hydrated
66+
[VEP_PROFILE] 5b. hydrate_transcript_cdna....................... 895.9ms
67+
[VEP_PROFILE] 6. prepared_context_build......................... 25.5ms 1 tx_trees chroms
68+
[VEP_PROFILE] sift_direct_reader: enabled (bypassing DataFusion SQL)
69+
[VEP_PROFILE] 7. sift_polyphen_cache_init....................... 1.7ms
70+
[VEP_PROFILE] 7a. sift_lazy_load_only........................... 23725.0ms
71+
[VEP_PROFILE] 7b. annotate_batches_only......................... 8395.0ms
72+
[VEP_PROFILE] 7+8. sift_lazy_load + annotate_batches............ 36995.0ms 10840 batches, 321713 total rows, 51 sift windows loaded
73+
[VEP_PROFILE] 9. projection + memtable.......................... 34.4ms
74+
[VEP_PROFILE] TOTAL scan_with_transcript_engine................. 77589.1ms 321713 VCF rows
75+
Total time: 78525.4ms (78.53s)
76+
Throughput: 4097 variants/sec
77+
VCF output: 884.1ms
78+
```
79+
80+
### profile_annotation invocation
81+
82+
```bash
83+
VEP_PROFILE=1 cargo run -p datafusion-bio-function-vep --release --example profile_annotation -- \
84+
vep-benchmark/data/HG002_chr1.vcf.gz \
85+
/Users/mwiewior/research/data/vep/chr1-vep \
86+
320000 \
87+
--everything \
88+
--reference-fasta-path=/Users/mwiewior/research/data/vep/Homo_sapiens.GRCh38.dna.primary_assembly.fa \
89+
--output=/tmp/HG002_chr1_annotated.vcf
90+
```
91+
92+
## Context Loading: Before vs After
93+
94+
Measured with `profile_annotation` example on same chr1-vep caches (non-everything mode, 319K variants):
95+
96+
| Stage | Before (master, unsplit cache) | After (this PR, split cache) |
97+
|-------|------|-------|
98+
| load_translations | 13.1s | **0.09s** |
99+
| load_transcripts | 0.30s | 0.30s |
100+
| load_exons | 0.12s | 0.05s |
101+
| load_regulatory | 0.07s | 0.01s |
102+
| **context_tables_total** | **13.5s** | **0.46s** |
103+
104+
The 13s savings comes from column projection on `load_translations`: the old `SELECT *` loaded 132 MB of sift/polyphen data that the main loader discarded. The new code projects only the 8 core columns (~5 MB).
105+
106+
## --everything Mode: Before vs After
107+
108+
| Stage | Baseline (master, unsplit) | After (this PR, split cache) | Savings |
109+
|-------|------|-------|---------|
110+
| variation_lookup | 33.9s | 38.6s | (run-to-run variance) |
111+
| context_tables_total | 13.6s | **0.5s** | **-13.1s** |
112+
| sift_lazy_load | 48.0s | **23.7s** | **-24.3s** |
113+
| annotate_batches | 8.7s | 8.4s ||
114+
| hydrate_transcript_cdna | 1.1s | 0.9s | -0.2s |
115+
| VCF sink || 0.9s | (new) |
116+
| other | 2.4s | 0.5s | -1.9s |
117+
| **TOTAL** | **107.7s** | **78.5s** | **-29.2s (-27%)** |
118+
119+
### Cumulative optimization impact
120+
121+
| Optimization | Sift load | Context load | Total pipeline |
122+
|---|---|---|---|
123+
| **Baseline (master)** | 48.0s | 13.6s | **107.7s** |
124+
| + Column projection | 48.0s | **0.5s** | ~94s |
125+
| + Compact predictions (u8 enum + sorted Vec) | 35.8s | 0.5s | ~92s |
126+
| + Zero-copy Arrow parsing | 26.1s | 0.5s | ~82s |
127+
| + Direct parquet-rs reader (cached metadata) | **23.7s** | 0.5s | **78.5s** |
128+
129+
### Optimizations applied
130+
131+
1. **Column projection** on `load_translations`: skip 132 MB sift/polyphen data → 13.1s → 0.1s
132+
2. **Compact sift predictions**: replace `HashMap<(i32, String), (String, f32)>` with sorted `Vec<CompactPrediction>` using u8-encoded amino acids and prediction types. Eliminates ~256M String allocations
133+
3. **Zero-copy Arrow parsing** for sift: read `&str` directly from Arrow buffers and encode to u8 in-place, bypassing intermediate `ProteinPrediction` structs with heap-allocated Strings. Combined with (2): 48.0s → 26.1s
134+
4. **Direct parquet-rs sift reader** with cached `ArrowReaderMetadata`: bypass DataFusion SQL planning for all 51 window queries. Read footer once, reuse metadata. 26.1s → 23.7s
135+
5. **MissWorklist** with interval predicates for regulatory/motif/mirna/structural
136+
6. **Rust-side transcript_id filter** for exons and translations
137+
7. **Split translation layout** support (translation_core + translation_sift)
138+
139+
### Remaining bottlenecks
140+
141+
| Stage | Time | % of total | Addressable by |
142+
|---|---|---|---|
143+
| variation_lookup | 38.6s | 49% | fjall KV cache (separate PR) |
144+
| sift I/O (parquet decompression) | 23.7s | 30% | ~21.5s is irreducible parquet I/O; single-pass bulk load could save ~2-3s more |
145+
| annotate_batches | 8.4s | 11% | already efficient (~26μs/row) |
146+
| VCF sink | 0.9s | 1% ||
147+
| context + hydrate + other | 1.9s | 2% ||
148+
149+
## Cache Layout (chr1-vep)
150+
151+
```
152+
115_GRCh38_exon_1_vep.parquet 17.5 MB 355,800 rows 8 RGs sort=(transcript_id, start)
153+
115_GRCh38_motif_1_vep.parquet 0.0 MB 0 rows 0 RGs
154+
115_GRCh38_regulatory_1_vep.parquet 6.8 MB 35,930 rows 4 RGs sort=(chrom, start)
155+
115_GRCh38_transcript_1_vep.parquet 31.8 MB 47,849 rows 6 RGs sort=(chrom, start)
156+
115_GRCh38_translation_core_1_vep.parquet 13.0 MB 22,832 rows 4 RGs sort=(transcript_id)
157+
115_GRCh38_translation_sift_1_vep.parquet 222.0 MB 22,832 rows 90 RGs sort=(chrom, start)
158+
115_GRCh38_variation_1_vep.parquet 2.8 GB 88,153,966 rows 882 RGs sort=(chrom, start)
159+
```

datafusion/bio-function-vep/examples/annotate_vep_golden_bench.rs

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -441,22 +441,35 @@ fn build_options_json(args: &Args) -> Result<Option<String>> {
441441
.unwrap_or("");
442442

443443
// Build table name -> parquet path mapping for known context tables.
444-
let table_specs: Vec<(&str, &str)> = vec![
445-
("transcripts_table", "transcript"),
446-
("exons_table", "exon"),
447-
("translations_table", "translation"),
448-
("regulatory_table", "regulatory"),
449-
("motif_table", "motif"),
444+
// For each key, try the primary stem first, then fallback stems.
445+
let table_specs: Vec<(&str, &[&str])> = vec![
446+
("transcripts_table", &["transcript"] as &[&str]),
447+
("exons_table", &["exon"]),
448+
("translations_table", &["translation_core", "translation"]),
449+
("regulatory_table", &["regulatory"]),
450+
("motif_table", &["motif"]),
450451
];
451452

452453
let mut entries = Vec::new();
453-
for (json_key, file_stem) in &table_specs {
454-
let parquet_name = format!("{base}_{file_stem}{suffix}.parquet");
455-
let parquet_path = context_dir.join(&parquet_name);
456-
if parquet_path.exists() {
457-
// Table name used for DataFusion registration (no .parquet extension).
458-
let table_name = format!("{base}_{file_stem}{suffix}");
459-
entries.push(format!("\"{json_key}\":\"{table_name}\""));
454+
for (json_key, file_stems) in &table_specs {
455+
for file_stem in *file_stems {
456+
let parquet_name = format!("{base}_{file_stem}{suffix}.parquet");
457+
let parquet_path = context_dir.join(&parquet_name);
458+
if parquet_path.exists() {
459+
let table_name = format!("{base}_{file_stem}{suffix}");
460+
entries.push(format!("\"{json_key}\":\"{table_name}\""));
461+
break;
462+
}
463+
}
464+
}
465+
466+
// Discover split translation_sift for sift/polyphen window loading.
467+
{
468+
let sift_name = format!("{base}_translation_sift{suffix}.parquet");
469+
let sift_path = context_dir.join(&sift_name);
470+
if sift_path.exists() {
471+
let table_name = format!("{base}_translation_sift{suffix}");
472+
entries.push(format!("\"translations_sift_table\":\"{table_name}\""));
460473
}
461474
}
462475

@@ -532,6 +545,7 @@ async fn register_context_tables(
532545
"transcripts_table",
533546
"exons_table",
534547
"translations_table",
548+
"translations_sift_table",
535549
"regulatory_table",
536550
"motif_table",
537551
];

0 commit comments

Comments
 (0)