Skip to content

Performance: Establish performance benchmarks and SLAs #71

@sfloess

Description

@sfloess

Overview

Document expected performance characteristics, establish Service Level Agreements, and create performance regression tests.

Current State

  • ❌ No documented performance targets
  • ❌ No performance SLAs
  • ❌ No baseline metrics
  • ✅ Subjective "feels fast" feedback

Performance Score Impact

Current: Performance B+ (88/100)
With #53 + #62 + #64 + this: Performance A+ (98/100)

Performance Benchmarks

1. Operation Latency Targets

List Operations (cached):

- 100 components:   <50ms   (p50), <100ms  (p95), <200ms  (p99)
- 1,000 components: <100ms  (p50), <250ms  (p95), <500ms  (p99)
- 10,000 components: <500ms (p50), <1000ms (p95), <2000ms (p99)

List Operations (uncached - includes HTTP):

- 100 components:   <500ms  (p50), <1000ms (p95), <2000ms (p99)
- 1,000 components: <2s     (p50), <5s     (p95), <10s    (p99)
- 10,000 components: <15s   (p50), <30s    (p95), <60s    (p99)

Delete Operations:

- Single component:  <500ms  (p50), <1000ms (p95)
- 100 components:    <30s    (p50), <60s    (p95)
- 1,000 components:  <5min   (p50), <10min  (p95)

Statistics Calculation:

- 100 components:    <100ms
- 1,000 components:  <500ms
- 10,000 components: <2s

Cache Performance:

- Cache hit overhead:  <1ms
- Cache check:         <0.1ms
- Cache invalidation:  <10ms

2. Resource Limits

Memory:

- Idle (GUI):           50-100 MB
- List 1K components:   100-150 MB
- List 10K components:  150-300 MB
- List 100K components: 300-500 MB
- Maximum heap:         1 GB (should never exceed)

Network:

- Connections per second:     5-10
- Concurrent connections:     4-8
- Retry attempts:             3 max
- Timeout per request:        30s (configurable)

Disk:

- JAR size:              <10 MB
- Cache per repository:  <10 MB
- Log file growth:       <1 MB/day

3. Scalability Targets

Linear Scaling (up to):

  • 10,000 components per repository
  • 100 repositories
  • 100 concurrent operations (desktop)

Graceful Degradation (beyond):

  • 100,000 components: slower but functional
  • 1,000 repositories: may require cache tuning
  • Show progress feedback for long operations

Performance Test Suite

JMH Benchmarks

Create :

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 5, time = 1)
@Fork(1)
@State(Scope.Benchmark)
public class NexusClientBenchmark {
    
    private NexusClient client;
    private NexusService service;
    
    @Setup
    public void setup() {
        // Initialize with mock data
    }
    
    @Benchmark
    public void listComponents_100_cached() {
        client.listComponents("test-repo", false);
    }
    
    @Benchmark
    public void listComponents_1000_cached() {
        client.listComponents("large-repo", false);
    }
    
    @Benchmark
    public void calculateStatistics_1000() {
        service.calculateStatistics("test-repo");
    }
    
    @Benchmark
    public void regexFiltering_complex() {
        service.searchComponents(
            SearchCriteria.builder()
                .repository("test-repo")
                .regexFilter(".*\\.(jar|war|ear)")
                .build()
        );
    }
}

Run benchmarks:

mvn test -Pbenchmark

Load Testing

Create :

@Test
void testConcurrent100ListOperations() {
    ExecutorService executor = Executors.newFixedThreadPool(10);
    List<Future<?>> futures = new ArrayList<>();
    
    long start = System.currentTimeMillis();
    
    for (int i = 0; i < 100; i++) {
        futures.add(executor.submit(() -> {
            client.listComponents("test-repo", false);
        }));
    }
    
    for (Future<?> future : futures) {
        future.get();
    }
    
    long duration = System.currentTimeMillis() - start;
    
    // Should complete in <10 seconds (100ms avg * 100 / 10 threads)
    assertTrue(duration < 10000, "100 concurrent operations took " + duration + "ms");
}

@Test
void testMemoryUsageWith10000Components() {
    Runtime runtime = Runtime.getRuntime();
    long beforeMemory = runtime.totalMemory() - runtime.freeMemory();
    
    List<RepoRecord> records = client.listComponents("huge-repo", false);
    assertEquals(10000, records.size());
    
    long afterMemory = runtime.totalMemory() - runtime.freeMemory();
    long memoryUsed = afterMemory - beforeMemory;
    
    // Should use <300MB for 10K components
    assertTrue(memoryUsed < 300 * 1024 * 1024, 
        "Memory usage: " + (memoryUsed / 1024 / 1024) + "MB");
}

Performance Regression Detection

Add to CI:

name: Performance Tests

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  benchmark:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up JDK 21
        uses: actions/setup-java@v4
        with:
          distribution: 'temurin'
          java-version: '21'
      
      - name: Run Benchmarks
        run: mvn test -Pbenchmark
      
      - name: Store Benchmark Results
        uses: benchmark-action/github-action-benchmark@v1
        with:
          tool: 'jmh'
          output-file-path: target/jmh-result.json
          github-token: ${{ secrets.GITHUB_TOKEN }}
          auto-push: true
          alert-threshold: '150%'  # Alert if 50% slower
          comment-on-alert: true
          fail-on-alert: false  # Don't fail build, just warn

Performance Documentation

Create :

# JNexus Performance Characteristics

## Benchmarks

Tested on: Intel Core i7-9700K, 16GB RAM, SSD, Ubuntu 22.04

### Operation Latency

| Operation | Dataset | p50 | p95 | p99 |
|-----------|---------|-----|-----|-----|
| List (cached) | 100 | 45ms | 80ms | 150ms |
| List (cached) | 1,000 | 90ms | 200ms | 400ms |
| List (uncached) | 100 | 450ms | 900ms | 1800ms |
| Delete | 1 | 400ms | 800ms | 1500ms |
| Statistics | 1,000 | 400ms | 600ms | 800ms |

### Resource Usage

| Metric | Idle | 1K components | 10K components |
|--------|------|---------------|----------------|
| Memory | 80 MB | 120 MB | 250 MB |
| CPU | <1% | 5-10% | 10-20% |

## Optimization Tips

### For Large Repositories (10K+ components)

1. **Use caching aggressively**
   - Default 5-minute TTL works well
   - Increase if data doesn't change often
   
2. **Filter server-side if possible**
   - Use Nexus search API (future enhancement)
   - Current client-side filtering is fast but downloads all data

3. **Increase HTTP timeout**
   - nexus.http.timeout.seconds=60 for slow networks

### For Bulk Deletes

1. **Use dry-run first** to validate
2. **Monitor progress** via status updates
3. **Consider batching** - delete 100 at a time, verify, repeat

### For Statistics

1. **Cache statistics results** if running repeatedly
2. **Use JSON format** for faster parsing in scripts
3. **Filter data first** to reduce dataset before stats

## Troubleshooting Slow Performance

**Symptom: List operation takes >10s**
- Check network latency to Nexus server
- Verify repository size (<10K components expected)
- Check HTTP timeout settings
- Try forceRefresh to bypass potentially corrupt cache

**Symptom: High memory usage**
- Check repository size (>100K components?)
- Clear cache: jnexus.clearAllCache()
- Reduce cache TTL to free memory faster
- Increase JVM heap: java -Xmx2g -jar jnexus.jar

**Symptom: Timeouts**
- Increase nexus.http.timeout.seconds
- Check server health (Nexus CPU/memory)
- Verify network connectivity
- Check firewall/proxy settings

## Performance Roadmap

See GitHub issues for planned improvements:
- #53: Executor inefficiency
- #62: Performance testing framework
- #64: HTTP connection pooling
- #71: Performance benchmarks (this issue)

Acceptance Criteria

  • JMH benchmarks created (10+ benchmarks)
  • Load tests created (5+ tests)
  • Performance CI workflow added
  • Baseline metrics documented
  • PERFORMANCE.md created
  • SLA targets defined
  • Regression detection configured
  • Alert thresholds set

Priority

Medium - Establishes quality bar and prevents regressions

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions