Back to Datahub

WAR Extraction to tmpfs Optimization

docs/advanced/jar-extraction-tmpfs-optimization.md

1.5.0.38.9 KB
Original Source

WAR Extraction to tmpfs Optimization

Overview

This optimization improves DataHub GMS startup performance by 15-30% through two key techniques:

  1. Extracting the WAR to a tmpfs (RAM disk) - Eliminates nested JAR overhead
  2. Using Spring Boot's classpath.idx for deterministic class loading - Ensures classes load in consistent order

How It Works

1. WAR Extraction to tmpfs

Instead of running Java from the packaged WAR directly:

Traditional: java -jar war.war
  → Java decompresses nested JARs on first class load
  → Slow filesystem I/O
  → Filesystem order randomness affects startup

This optimization extracts to RAM disk first:

Optimized: Extract WAR → /tmp/gms/extraction (tmpfs)
  → Read classes from RAM
  → No decompression overhead
  → 2-3× faster class loading

2. Deterministic Class Loading with classpath.idx

Spring Boot 3.2+ includes BOOT-INF/classpath.idx - a pre-computed ordered list of all JARs and their dependencies.

Without optimization:

  • Classpath built from filesystem directory listing
  • Filesystem order varies between boots (some filesystems randomize)
  • May affect class resolution order if there are duplicate classes
  • Variable startup times depending on filesystem state

With optimization:

  • Uses Spring's pre-computed classpath.idx ordering
  • Same class loading order every boot
  • Consistent performance
  • Deterministic behavior for reproducible environments

How it works:

bash
# classpath.idx format:
- "BOOT-INF/lib/spring-core-6.0.jar"
- "BOOT-INF/lib/spring-context-6.0.jar"
- "BOOT-INF/lib/spring-data-commons-3.0.jar"
... (100+ entries in dependency order)

# Extracted to absolute paths:
/tmp/gms-work/BOOT-INF/classes          (application classes first)
/tmp/gms-work/BOOT-INF/lib/spring-core-6.0.jar
/tmp/gms-work/BOOT-INF/lib/spring-context-6.0.jar
/tmp/gms-work/BOOT-INF/lib/spring-data-commons-3.0.jar
... (all as single colon-separated classpath)

Performance Impact

MetricImprovement
Startup Time15-30% faster
Class Loading2-3× faster (RAM vs filesystem)
ConsistencyDeterministic ordering, no random variations
Memory Overhead+150-300MB temporary (freed after startup completes)

Example Timing

Without optimization (filesystem WAR):
  - WAR decompression: 8-15s
  - Class discovery: 5-10s
  - Total startup: ~20-30s

With optimization (tmpfs extraction):
  - WAR extraction to RAM: 2-3s
  - Class discovery (from classpath.idx): 1-2s
  - Total startup: ~15-20s

Net gain: 5-15 seconds faster

Configuration

Enable in Helm

Add to your values.yaml:

yaml
# Enable WAR extraction to tmpfs for faster startup
extractJarEnabled: true

Or via command line:

bash
helm install datahub ... --set extractJarEnabled=true

What Gets Created

When extractJarEnabled: true:

  1. tmpfs emptyDir volume (1Gi, Memory-backed)

    • Mounted at /tmp/gms-work in the container
    • Automatically cleaned up after pod termination
  2. EXTRACT_JAR_ENABLED environment variable

    • Tells startup script to use extraction
    • Triggers classpath.idx loading
  3. Startup logging

    • WAR size logged for validation
    • Available RAM checked for safety
    • Extraction time measured

Requirements

RequirementMinimumRecommendedNotes
Available RAM500MB2GB+Per pod; extraction is temporary
WAR File SizeN/A< 500MBExceeding 1Gi tmpfs limit will fail
Spring Boot Version3.2+LatestRequires classpath.idx support
Kubernetes1.20+1.24+For reliable emptyDir medium: Memory

Pre-Flight Checks

The startup script performs these checks:

bash
[STARTUP] JAR extraction enabled. WAR size: 250MB, Available RAM: 7200MB
[STARTUP] Extracting WAR to tmpfs: /tmp/gms-work
[STARTUP] Generating deterministic classpath from BOOT-INF/classpath.idx
[STARTUP] WAR extracted in 2843ms

Warning Conditions

⚠️ WAR Size Warning (> 1Gi):

[WARN] WAR size (1200MB) exceeds tmpfs limit (1Gi). Extraction may fail

Action: Increase tmpfs sizeLimit in values.yaml or reduce WAR size

⚠️ Low RAM Warning (< 500MB):

[WARN] Low available RAM (256MB). Extraction may fail or trigger swap

Action: Allocate more resources to the pod or disable optimization

Architecture Details

Classpath.idx Processing

The startup script processes classpath.idx in 4 steps:

Step 1: Convert to absolute paths

bash
- "BOOT-INF/lib/spring-core.jar"
↓
/tmp/gms-work/BOOT-INF/lib/spring-core.jar

Step 2: Prepend application classes

bash
/tmp/gms-work/BOOT-INF/classes (application code - loaded first)
/tmp/gms-work/BOOT-INF/lib/... (library JARs - in dependency order)

Step 3: Join into single classpath

bash
/tmp/gms-work/BOOT-INF/classes:/tmp/gms-work/BOOT-INF/lib/jar1.jar:/tmp/gms-work/BOOT-INF/lib/jar2.jar:...

Step 4: Create Java argfile

bash
# Avoids shell variable size limits (32KB-256KB depending on system)
cat > java.args <<EOF
-cp
/tmp/gms-work/BOOT-INF/classes:/tmp/gms-work/BOOT-INF/lib/...
com.linkedin.gms.GMSApplication
EOF

java @java.args  # Load from file instead of command line

Troubleshooting

Startup Fails: "Missing BOOT-INF/classpath.idx"

Cause: WAR is not a Spring Boot executable archive or Spring Boot < 3.2

Solution:

  • Verify Spring Boot version ≥ 3.2
  • Disable optimization: extractJarEnabled: false
  • Check WAR packaging in build pipeline

Extraction Hangs or Times Out

Cause: Not enough RAM or disk space

Solution:

yaml
# Increase pod resources
resources:
  requests:
    memory: 4Gi
  limits:
    memory: 6Gi

tmpfs Mount Permission Denied

Cause: Security context doesn't allow tmpfs mounting

Solution:

yaml
podSecurityContext:
  fsGroup: 1000

securityContext:
  runAsNonRoot: true
  runAsUser: 1000

High Memory Usage After Startup

Expected: Temporary spike during extraction (freed after startup completes)

Monitor: Watch for sustained high memory after startup settles. If sustained, check:

  • Heap size configuration (may need tuning)
  • Garbage collection logs
  • Application memory leaks

Disabling the Optimization

If you need to disable for debugging or compatibility:

yaml
extractJarEnabled: false # Default

The container will run normally without extraction (standard startup path).

Performance Comparison

Cold Start (Pod Creation)

ConfigurationTimeWAR SizeRAM Used
Standard (no extraction)25-35s250MB1.2GB baseline
With tmpfs extraction18-25s250MB1.2GB + 250MB (temporary)
Improvement+25-30%

Warm Start (Existing Pod)

Both configurations are similar once JVM is loaded. Main improvement is initial startup only.

Kubernetes Best Practices

Resource Requests/Limits

Ensure adequate resources for extraction:

yaml
resources:
  requests:
    memory: 2Gi
    cpu: 500m
  limits:
    memory: 4Gi
    cpu: 1000m

Rationale:

  • Extraction uses 150-300MB additional memory (temporary)
  • Class loading is CPU-bound (needs CPU during extraction)
  • Total memory = baseline (1.2GB) + extraction overhead (250-300MB)

Node Affinity

For consistent performance, run on nodes with:

  • Sufficient free RAM (at least 4GB when extractJarEnabled=true)
  • Fast disk (for initial container image pull)

Metrics & Monitoring

The startup script logs extraction metrics:

[STARTUP] JAR extraction enabled. WAR size: 250MB, Available RAM: 7200MB
[STARTUP] Extracting WAR to tmpfs: /tmp/gms/extraction
[STARTUP] WAR extracted in 2843ms
[STARTUP] Generating deterministic classpath from BOOT-INF/classpath.idx

Parse these logs to:

  • Track startup performance over time
  • Detect extraction failures early
  • Monitor RAM availability trends

Future Enhancements

  • Lazy extraction (only if WAR > threshold)
  • Parallel class loading for multi-core systems
  • Extraction pre-warming in init containers
  • Compression of classpath.idx for smaller WARs

References