--- title: "Database Formats and Multi-Contig Support" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Database Formats and Multi-Contig Support} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` ```{r setup} library(misha) gdb.init_examples() ``` # Overview Starting with misha 5.3.0, databases can be stored in two formats: - **Indexed format** (default, recommended): Single unified files for sequences and tracks - **Per-chromosome format** (legacy): Separate files for each chromosome The indexed format provides better performance and scalability, especially for genomes with many contigs (>50 chromosomes). ## Key Features - **Automatic format detection** - misha automatically detects which format your database uses - **Fully backward compatible** - existing databases continue to work without modification - **Transparent to users** - same API for both formats - **Migration tools** - convert databases when convenient - **Performance benefits** - 4-14% faster for large-scale analyses # Database Formats ## Indexed Format (Recommended) The indexed format uses unified files: **Sequence data:** - `seq/genome.seq` - All chromosome sequences concatenated - `seq/genome.idx` - Index mapping chromosome names to positions **Track data:** - `tracks/mytrack.track/track.dat` - All chromosome data concatenated - `tracks/mytrack.track/track.idx` - Index with offset/length per chromosome **Advantages:** - Fewer file descriptors (important for genomes with 100+ contigs) - Better performance for large workloads (14% faster) - Smaller disk footprint - Faster track creation and conversion ## Per-Chromosome Format (Legacy) The per-chromosome format uses separate files: **Sequence data:** - `seq/chr1.seq`, `seq/chr2.seq`, ... - One file per chromosome **Track data:** - `tracks/mytrack.track/chr1.track`, `chr2.track`, ... - One file per chromosome **When to use:** - Compatibility with older misha versions (<5.3.0) - Small genomes (<25 chromosomes) where performance difference is negligible # Creating Databases ## New Databases (Indexed Format) By default, new databases use the indexed format: ```{r, eval = FALSE} # eval = FALSE: needs a genome FASTA on disk, or a multi-GB download from UCSC. # Create database from FASTA file gdb.create("mydb", "/path/to/genome.fa") # Or download pre-built genome gdb.create_genome("hg38", path = "/path/to/install") ``` ## Force Legacy Format To create a database in legacy format (for compatibility with older misha): ```{r, eval = FALSE} # eval = FALSE: same as above, gdb.create() needs a genome FASTA. # Set option before creating database options(gmulticontig.indexed_format = FALSE) gdb.create("mydb", "/path/to/genome.fa") ``` # Checking Database Format Use `gdb.info()` to check your database format: ```{r} gdb.init_examples() # the bundled examples database info <- gdb.info() print(info$format) # "indexed" or "per-chromosome" ``` The full record: ```{r} gdb.info() ``` # Converting Databases ## Convert Entire Database Convert all tracks and sequences to indexed format: ```{r} gdb.convert_to_indexed() gdb.info()$format ``` This will: 1. Convert sequence files (`chr*.seq` → `genome.seq + genome.idx`) 2. Convert all tracks to indexed format 3. Validate conversions 4. Remove old files after successful conversion ## Convert Individual Tracks Convert specific tracks while keeping others in legacy format: ```{r} gdb.init_examples() # start again from a per-chromosome database # 1D tracks gtrack.convert_to_indexed("dense_track") # 2D tracks (rectangles and points) gtrack.2d.convert_to_indexed("rects_track") ``` ## Convert Intervals Convert interval sets to indexed format: ```{r} # Only "big" interval sets are stored per chromosome, so lower the threshold # to get big sets out of the small example database (default: 1,000,000). options(gbig.intervals.size = 10) gintervals.save("myintervals", gscreen("dense_track > 0.3")) gintervals.save("my2dintervals", gextract("rects_track", gintervals.2d.all())[, 1:6]) options(gbig.intervals.size = 1e6) # 1D intervals gintervals.convert_to_indexed("myintervals") # 2D intervals gintervals.2d.convert_to_indexed("my2dintervals") ``` # Migration Guide ## When to Migrate **High priority (significant benefits):** - Genomes with many contigs (>50 chromosomes) - Large-scale analyses (10M+ bp regions frequently) - 2D track workflows - File descriptor limit issues **Medium priority (moderate benefits):** - Repeated extraction workflows - Regular analyses on medium-sized regions (1-10M bp) **Low priority (minimal benefits):** - Small genomes (<25 chromosomes) - One-off analyses - Simple queries on small regions ## Migration Workflow **Step 1: Backup (optional but recommended)** ```{r, eval = FALSE} # eval = FALSE: copies a whole database outside the vignette's temp directory. # Create backup of important database system("cp -r /path/to/mydb /path/to/mydb.backup") ``` **Step 2: Check current format** ```{r} gdb.init_examples() info <- gdb.info() print(paste("Current format:", info$format)) ``` **Step 3: Convert** ```{r} gdb.convert_to_indexed() ``` **Step 4: Verify** ```{r} # Check format changed info <- gdb.info() print(paste("New format:", info$format)) # Test a few operations result <- gextract("dense_track", gintervals(1, 0, 1000)) print(head(result)) ``` **Step 5: Remove backup (after validation)** ```{r, eval = FALSE} # eval = FALSE: deletes a directory, and there is nothing here to delete. # After thorough testing system("rm -rf /path/to/mydb.backup") ``` # Copying Tracks Between Databases You can freely copy tracks between databases with different formats. ## Use `gtrack.copy()` `gtrack.copy(src, db = target_db)` is the idiom. It reads the source track directly, reconciles per-chromosome vs. indexed storage and any difference in the two databases' chromosome sets, and preserves the track's type. `src` may be a character vector, so a batch copy is a single call. ```{r} gdb.init_examples() source_db <- .misha$GROOT # A second database to copy into target_db <- file.path(tempdir(), "target_db") unlink(target_db, recursive = TRUE) gdb.create_linked(target_db, parent = source_db) # One track, or many gtrack.copy("dense_track", db = target_db) gtrack.copy(c("sparse_track", "array_track"), db = target_db) gsetroot(target_db) gtrack.ls() gtrack.info("dense_track")[c("type", "size.in.bytes")] ``` The destination does not have to be in the same format: converting it afterwards leaves the tracks readable. ```{r} gdb.convert_to_indexed() gdb.info()$format head(gextract("dense_track", gintervals(1, 0, 300))) ``` ## Round-tripping through a text file Exporting with `gextract(..., file = ...)` and re-importing with `gtrack.import()` also works, but it is lossy: `binsize = 0` builds a **sparse** track, so a dense source track comes back sparse and about 5x larger (20 bytes per value against 4 bytes per bin). Reach for it only when the destination is not a misha database, or when you actually want a different track type. ```{r} gsetroot(source_db) exported <- file.path(tempdir(), "dense_track.txt") gextract("dense_track", gintervals.all(), iterator = "dense_track", file = exported) gsetroot(target_db) gtrack.import("dense_track_roundtrip", "Round-tripped", exported, binsize = 0) gtrack.info("dense_track")[c("type", "size.in.bytes")] gtrack.info("dense_track_roundtrip")[c("type", "size.in.bytes")] gtrack.rm("dense_track_roundtrip", force = TRUE) ``` # Performance Comparison Based on comprehensive benchmarks comparing indexed vs legacy formats: ## Operations Faster with Indexed Format - **Very large workloads (10M+ bp)**: 14% faster - **2D track operations**: 2% faster (was 36% slower in legacy) - **Repeated extractions**: 14% faster - **Real workflows**: 8% faster on average ## Operations Similar Performance - Single chromosome extraction: Within 5% - Multi-chromosome (10-22 chr): Within 1% - PWM operations: Within 3% - Small workloads (<1M bp): Within 10% ## Summary - **64% of operations are faster** with indexed format - **93% within 5%** of legacy format (statistically equal) - **Average: 4% faster** across all benchmarks - **No regressions** for common use cases # Backward Compatibility ## Fully Compatible - ✅ Existing databases work without modification - ✅ Existing scripts work without changes - ✅ Same API for both formats - ✅ Automatic format detection - ✅ Can mix formats in same analysis ## Example: Mixed Environment ```{r} # Work with both formats in same session gsetroot(source_db) # per-chromosome data1 <- gextract("dense_track", gintervals(1, 0, 1000)) head(data1) gsetroot(target_db) # indexed data2 <- gextract("dense_track", gintervals(1, 0, 1000)) head(data2) ``` # Troubleshooting ## "File descriptor limit reached" This occurs with many-contig genomes in legacy format: **Solution:** Convert to indexed format ```{r} gdb.init_examples() gdb.convert_to_indexed() ``` ## "Track not found after copying files" After manually copying track directories: **Solution:** Reload database ```{r} gdb.reload() ``` ## "Conversion fails with disk space error" Conversion needs 2x track size temporarily: **Solution:** Free disk space or convert tracks individually ```{r} # Convert one track at a time gdb.init_examples() gtrack.convert_to_indexed("dense_track") gtrack.convert_to_indexed("sparse_track") # etc. ``` # Best Practices ## For New Projects 1. Use indexed format (default) for new databases 2. Use `gdb.create_genome()` for standard genomes 3. Use `gdb.create()` with multi-FASTA for custom genomes ## For Existing Projects 1. Check format with `gdb.info()` 2. Migrate if beneficial (many contigs, large analyses) 3. No rush - legacy format remains fully supported # Summary - **Indexed format is the default and recommended** for new databases - **Legacy format remains fully supported** - no forced migration - **Automatic format detection** - users don't need to think about it - **Significant performance benefits** for large-scale analyses - **Easy migration** with `gdb.convert_to_indexed()` - **Fully backward compatible** - existing code works unchanged