feat(get): consolidate GET performance optimization (#3972)

* feat(get): consolidate GET performance optimization

Consolidated implementation of all GET performance optimizations into
a single, well-organized commit replacing the previous patch-on-patch
approach.

## Changes

### Configuration (set_disk/mod.rs)
- Consolidated all GET optimization flags into a single organized section
- Enabled by default: codec streaming, metadata early-stop, page cache reclaim
- Added codec streaming multipart flag (default: disabled)
- Added version-aware early-stop flag (default: disabled)
- Added adaptive duplex buffer sizing based on object size
- All flags use OnceLock caching with rollout percentage support

### Metadata Early-Stop (set_disk/read.rs)
- Delete marker early-stop when quorum agrees
- Version-aware early-stop for versioned GET requests
- MetadataQuorumAccumulator enhanced with:
  - delete_marker_votes tracking
  - requested_version_id and matching_version_votes tracking
  - version_early_stop_decision() method
- 6 new tests for version early-stop scenarios

### Codec Streaming (erasure/coding/decode_reader.rs)
- DualInFlight (2-stripe lookahead) enabled by default

### Decode Pipeline (erasure/coding/decode.rs)
- Stripe prefetch count configuration
- Bitrot-decode overlap configuration

### Disk Layer (disk/local.rs)
- O_DIRECT read configuration constants (preparation)

### Metrics (io-metrics/lib.rs)
- BytesPool acquisition/return metrics
- Metadata phase duration with early-stop label
- Total duration with reader_path label

### Diagnostics (diagnostics/)
- Early-stop reason constants
- Pool tier/outcome label constants

### Observability (.docker/observability/)
- 3 Grafana dashboards for GET optimization monitoring
- Prometheus alert rules (6 alerts: 3 critical, 3 warning)
- Updated README.md and README_ZH.md with usage docs

### Config (config/src/constants/runtime.rs)
- Page cache reclaim read enabled by default

## Environment Variables

| Variable | Default | Description |
|----------|---------|-------------|
| RUSTFS_GET_CODEC_STREAMING_ENABLE | true | Codec streaming base flag |
| RUSTFS_GET_CODEC_STREAMING_ROLLOUT_PCT | 100 | Codec streaming rollout % |
| RUSTFS_GET_CODEC_STREAMING_MULTIPART_ENABLE | false | Multipart codec streaming |
| RUSTFS_GET_METADATA_EARLY_STOP_ENABLE | true | Early-stop base flag |
| RUSTFS_GET_METADATA_EARLY_STOP_ROLLOUT_PCT | 100 | Early-stop rollout % |
| RUSTFS_GET_METADATA_VERSION_EARLY_STOP_ENABLE | false | Version-aware early-stop |
| RUSTFS_OBJECT_FILE_CACHE_RECLAIM_READ_ENABLE | true | Page cache reclaim |
| RUSTFS_OBJECT_DIRECT_IO_READ_ENABLE | false | O_DIRECT (preparation) |
| RUSTFS_GET_DECODE_STRIPE_PREFETCH_COUNT | 1 | Stripe prefetch |
| RUSTFS_GET_BITROT_DECODE_OVERLAP_ENABLE | false | Bitrot-decode overlap |
| RUSTFS_GET_CODEC_STREAMING_MAX_INFLIGHT | 2 | DualInFlight stripes |

## Rollback

All optimizations can be disabled via environment variables:
RUSTFS_GET_CODEC_STREAMING_ENABLE=false
RUSTFS_GET_METADATA_EARLY_STOP_ENABLE=false
RUSTFS_OBJECT_FILE_CACHE_RECLAIM_READ_ENABLE=false

Co-Authored-By: heihutu <heihutu@gmail.com>

* test(get): add stress test scripts for GET optimization validation

- quick-validate-get-optimization.sh: Quick 5-minute validation
- stress-test-get-optimization.sh: Full 30+ minute stress test
- README-stress-test.md: Usage documentation

Co-Authored-By: heihutu <heihutu@gmail.com>

* test(ecstore): align file cache reclaim defaults

* chore(deps): update redis and erasure codec

* test(ecstore): align decode fill policy default

* test(ecstore): align metadata early-stop default

* fix(ecstore): keep metadata early stop opt-in

---------

Co-authored-by: heihutu <heihutu@gmail.com>
This commit is contained in:
houseme
2026-06-28 07:14:07 +08:00
committed by GitHub
parent 512418cda9
commit 27468ebfa9
22 changed files with 2886 additions and 71 deletions
+119
View File
@@ -0,0 +1,119 @@
# GET Optimization Stress Test Scripts
## Quick Start
### Prerequisites
- `warp` installed (https://github.com/minio/warp)
- `mc` configured with access to the RustFS server
- RustFS server running with GET optimizations enabled
### Quick Validation (5 minutes)
```bash
./scripts/quick-validate-get-optimization.sh localhost:9000
```
This runs basic functional tests:
- Data integrity verification
- Concurrent GET stability
- Early-stop behavior validation
### Full Stress Test (30+ minutes)
```bash
./scripts/stress-test-get-optimization.sh localhost:9000 ./stress-results
```
This runs comprehensive tests:
- Data correctness validation (1KB, 1MB, 10MB objects)
- Concurrent GET stress test (16, 64, 256 concurrency)
- Mixed read/write workload
- Early-stop behavior under load
## Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `WARP_ACCESS_KEY` | rustfsadmin | S3 access key |
| `WARP_SECRET_KEY` | rustfsadmin | S3 secret key |
| `MC_ALIAS` | rustfs | mc alias for the server |
| `TEST_BUCKET` | auto-generated | Test bucket name |
| `TEST_DURATION` | 300s | Duration for stress tests |
| `CONCURRENCY` | 64 | Default concurrency level |
## Test Scenarios
### 1. Data Correctness Validation
Verifies that GET returns correct data with early-stop enabled:
- Uploads random data
- Downloads and compares MD5 hash
- Tests different object sizes (1KB, 1MB, 10MB)
### 2. Concurrent GET Stress Test
Tests performance under high concurrency:
- Object sizes: 1KiB, 1MiB, 4MiB, 10MiB
- Concurrency levels: 16, 64, 256
- Duration: configurable (default 300s)
### 3. Mixed Read/Write Stress Test
Tests stability under concurrent read/write:
- 50% reads, 50% writes
- 1MB objects
- 64 concurrent operations
### 4. Early-Stop Behavior Validation
Verifies early-stop works correctly:
- 100 sequential reads of the same object
- Verifies data size matches expected
- Checks for any download failures
## Expected Results
### Success Criteria
- **Data Correctness**: 100% pass rate (no data corruption)
- **Concurrent GET**: No errors, consistent latency
- **Mixed Workload**: No deadlocks or data corruption
- **Early-Stop**: All reads return correct data size
### Performance Baselines
| Object Size | Expected Throughput | Expected p95 Latency |
|-------------|--------------------|--------------------|
| 1KiB | > 5 MiB/s | < 10ms |
| 1MiB | > 500 MiB/s | < 20ms |
| 4MiB | > 1000 MiB/s | < 30ms |
| 10MiB | > 2000 MiB/s | < 50ms |
## Troubleshooting
### Common Issues
1. **warp not found**: Install with `go install github.com/minio/warp@latest`
2. **mc not configured**: Run `mc alias set rustfs http://localhost:9000 admin password`
3. **Connection refused**: Verify RustFS is running and accessible
4. **Permission denied**: Check S3 credentials
### Debug Mode
Enable debug logging:
```bash
RUST_LOG=rustfs_ecstore::bucket::lifecycle=debug ./scripts/stress-test-get-optimization.sh
```
## Output Files
| File | Description |
|------|-------------|
| `test-config.txt` | Test configuration |
| `correctness-results.txt` | Data correctness results |
| `early-stop-results.txt` | Early-stop validation results |
| `get-*.json` | Concurrent GET performance (warp JSON) |
| `mixed-*.json` | Mixed workload performance (warp JSON) |
| `correctness-errors.log` | Data correctness errors (if any) |
| `early-stop-errors.log` | Early-stop errors (if any) |
+122
View File
@@ -0,0 +1,122 @@
#!/usr/bin/env bash
# Quick Validation Script for GET Optimization
# Usage: ./scripts/quick-validate-get-optimization.sh [TARGET_HOST]
#
# Runs quick functional tests to validate GET optimization changes:
# 1. Basic GET correctness
# 2. Concurrent GET stability
# 3. Early-stop behavior
set -euo pipefail
TARGET_HOST="${1:-localhost:9000}"
MC_ALIAS="${MC_ALIAS:-rustfs}"
TEST_BUCKET="quick-validate-$(date +%s)"
echo "=========================================="
echo "Quick GET Optimization Validation"
echo "=========================================="
echo "Target: $TARGET_HOST"
echo ""
# Create test bucket
mc mb "${MC_ALIAS}/${TEST_BUCKET}" 2>/dev/null || true
# ============================================================================
# Test 1: Basic GET Correctness
# ============================================================================
echo "[1/3] Basic GET Correctness"
# Upload test file
dd if=/dev/urandom of=/tmp/quick-test.bin bs=1048576 count=1 2>/dev/null
original_hash=$(md5 -q /tmp/quick-test.bin 2>/dev/null || md5sum /tmp/quick-test.bin | awk '{print $1}')
mc cp /tmp/quick-test.bin "${MC_ALIAS}/${TEST_BUCKET}/test.bin" >/dev/null 2>&1
# Download and verify
mc cp "${MC_ALIAS}/${TEST_BUCKET}/test.bin" /tmp/quick-test-download.bin >/dev/null 2>&1
download_hash=$(md5 -q /tmp/quick-test-download.bin 2>/dev/null || md5sum /tmp/quick-test-download.bin | awk '{print $1}')
if [ "$original_hash" = "$download_hash" ]; then
echo " PASS: Data integrity verified"
else
echo " FAIL: Data mismatch (original=$original_hash, download=$download_hash)"
fi
rm -f /tmp/quick-test.bin /tmp/quick-test-download.bin
# ============================================================================
# Test 2: Concurrent GET Stability
# ============================================================================
echo "[2/3] Concurrent GET Stability"
# Upload multiple test files
for i in $(seq 1 10); do
dd if=/dev/urandom of="/tmp/quick-test-${i}.bin" bs=1048576 count=1 2>/dev/null
mc cp "/tmp/quick-test-${i}.bin" "${MC_ALIAS}/${TEST_BUCKET}/test-${i}.bin" >/dev/null 2>&1
rm -f "/tmp/quick-test-${i}.bin"
done
# Concurrent download
passed=0
failed=0
for i in $(seq 1 10); do
if mc cp "${MC_ALIAS}/${TEST_BUCKET}/test-${i}.bin" "/tmp/quick-download-${i}.bin" >/dev/null 2>&1; then
size=$(stat -f%z "/tmp/quick-download-${i}.bin" 2>/dev/null || stat -c%s "/tmp/quick-download-${i}.bin" 2>/dev/null)
if [ "$size" = "1048576" ]; then
passed=$((passed + 1))
else
failed=$((failed + 1))
fi
else
failed=$((failed + 1))
fi
rm -f "/tmp/quick-download-${i}.bin"
done
echo " Result: $passed passed, $failed failed"
# ============================================================================
# Test 3: Early-Stop Behavior
# ============================================================================
echo "[3/3] Early-Stop Behavior"
# Upload a larger test file
dd if=/dev/urandom of=/tmp/quick-test-large.bin bs=1048576 count=10 2>/dev/null
mc cp /tmp/quick-test-large.bin "${MC_ALIAS}/${TEST_BUCKET}/test-large.bin" >/dev/null 2>&1
# Multiple reads to trigger early-stop
passed=0
failed=0
for i in $(seq 1 20); do
if mc cp "${MC_ALIAS}/${TEST_BUCKET}/test-large.bin" "/tmp/quick-large-${i}.bin" >/dev/null 2>&1; then
size=$(stat -f%z "/tmp/quick-large-${i}.bin" 2>/dev/null || stat -c%s "/tmp/quick-large-${i}.bin" 2>/dev/null)
if [ "$size" = "10485760" ]; then
passed=$((passed + 1))
else
failed=$((failed + 1))
fi
else
failed=$((failed + 1))
fi
rm -f "/tmp/quick-large-${i}.bin"
done
echo " Result: $passed passed, $failed failed"
rm -f /tmp/quick-test-large.bin
# ============================================================================
# Cleanup
# ============================================================================
echo ""
echo "Cleaning up..."
mc rm --recursive --force "${MC_ALIAS}/${TEST_BUCKET}" >/dev/null 2>&1 || true
mc rb "${MC_ALIAS}/${TEST_BUCKET}" >/dev/null 2>&1 || true
echo ""
echo "=========================================="
echo "Quick Validation Complete"
echo "=========================================="
+242
View File
@@ -0,0 +1,242 @@
#!/usr/bin/env bash
# GET Optimization Stress Test Script
# Usage: ./scripts/stress-test-get-optimization.sh [TARGET_HOST] [OUTPUT_DIR]
#
# Validates:
# 1. Data correctness with early-stop enabled
# 2. Concurrent GET performance
# 3. Mixed read/write stability
# 4. Early-stop behavior under slow disk conditions
set -euo pipefail
TARGET_HOST="${1:-localhost:9000}"
OUTPUT_DIR="${2:-./stress-test-results/$(date +%Y%m%d-%H%M%S)}"
MC_ALIAS="${MC_ALIAS:-rustfs}"
TEST_BUCKET="${TEST_BUCKET:-stress-test-$(date +%s)}"
TEST_DURATION="${TEST_DURATION:-300s}"
CONCURRENCY="${CONCURRENCY:-64}"
# S3 credentials
export WARP_ACCESS_KEY="${WARP_ACCESS_KEY:-rustfsadmin}"
export WARP_SECRET_KEY="${WARP_SECRET_KEY:-rustfsadmin}"
mkdir -p "$OUTPUT_DIR"
echo "=========================================="
echo "GET Optimization Stress Test"
echo "=========================================="
echo "Target: $TARGET_HOST"
echo "Output: $OUTPUT_DIR"
echo "Bucket: $TEST_BUCKET"
echo "Duration: $TEST_DURATION"
echo "Concurrency: $CONCURRENCY"
echo "=========================================="
echo ""
# Save test configuration
cat > "$OUTPUT_DIR/test-config.txt" <<EOF
Date: $(date -u +"%Y-%m-%dT%H:%M:%SZ")
Target: $TARGET_HOST
Bucket: $TEST_BUCKET
Duration: $TEST_DURATION
Concurrency: $CONCURRENCY
Environment Variables:
RUSTFS_GET_METADATA_EARLY_STOP_ENABLE=${RUSTFS_GET_METADATA_EARLY_STOP_ENABLE:-true}
RUSTFS_GET_CODEC_STREAMING_ENABLE=${RUSTFS_GET_CODEC_STREAMING_ENABLE:-false}
RUSTFS_OBJECT_FILE_CACHE_RECLAIM_READ_ENABLE=${RUSTFS_OBJECT_FILE_CACHE_RECLAIM_READ_ENABLE:-true}
EOF
# ============================================================================
# Test 1: Data Correctness Validation
# ============================================================================
echo "[1/4] Data Correctness Validation"
echo "=================================="
test_data_correctness() {
local size=$1
local count=$2
local passed=0
local failed=0
echo " Testing $count objects of size $size..."
for i in $(seq 1 $count); do
# Generate test file
local testfile="/tmp/stress-test-${size}-${i}.bin"
if [ "$size" = "1KB" ]; then
dd if=/dev/urandom of="$testfile" bs=1024 count=1 2>/dev/null
elif [ "$size" = "1MB" ]; then
dd if=/dev/urandom of="$testfile" bs=1048576 count=1 2>/dev/null
elif [ "$size" = "10MB" ]; then
dd if=/dev/urandom of="$testfile" bs=1048576 count=10 2>/dev/null
fi
# Calculate original hash
local original_hash
original_hash=$(md5 -q "$testfile" 2>/dev/null || md5sum "$testfile" | awk '{print $1}')
# Upload
mc cp "$testfile" "${MC_ALIAS}/${TEST_BUCKET}/correctness-${size}-${i}.bin" >/dev/null 2>&1
# Download and verify
local downloadfile="/tmp/stress-test-${size}-${i}-download.bin"
mc cp "${MC_ALIAS}/${TEST_BUCKET}/correctness-${size}-${i}.bin" "$downloadfile" >/dev/null 2>&1
local download_hash
download_hash=$(md5 -q "$downloadfile" 2>/dev/null || md5sum "$downloadfile" | awk '{print $1}')
if [ "$original_hash" = "$download_hash" ]; then
passed=$((passed + 1))
else
failed=$((failed + 1))
echo " MISMATCH: correctness-${size}-${i}.bin (original=$original_hash, download=$download_hash)" >> "$OUTPUT_DIR/correctness-errors.log"
fi
# Cleanup
rm -f "$testfile" "$downloadfile"
done
echo " Result: $passed passed, $failed failed"
echo "correctness_${size}: passed=$passed failed=$failed" >> "$OUTPUT_DIR/correctness-results.txt"
}
# Test different object sizes
mc mb "${MC_ALIAS}/${TEST_BUCKET}" 2>/dev/null || true
test_data_correctness "1KB" 100
test_data_correctness "1MB" 50
test_data_correctness "10MB" 10
echo ""
# ============================================================================
# Test 2: Concurrent GET Stress Test
# ============================================================================
echo "[2/4] Concurrent GET Stress Test"
echo "================================="
# Prepare test objects
echo " Preparing test objects..."
for size in 1KiB 1MiB 4MiB 10MiB; do
warp put --obj.size="$size" --num.objects=100 --host="$TARGET_HOST" --bucket="$TEST_BUCKET" --concurrent=16 >/dev/null 2>&1
done
echo " Running concurrent GET tests..."
for size in 1KiB 1MiB 4MiB 10MiB; do
for conc in 16 64 256; do
echo " GET size=$size concurrency=$conc duration=$TEST_DURATION"
output_file="$OUTPUT_DIR/get-${size}-c${conc}.json"
if warp get \
--host="$TARGET_HOST" \
--obj.size="$size" \
--concurrent="$conc" \
--duration="$TEST_DURATION" \
--bucket="$TEST_BUCKET" \
--json \
> "$output_file" 2>/dev/null; then
echo " OK"
else
echo " FAILED (see $output_file)"
fi
done
done
echo ""
# ============================================================================
# Test 3: Mixed Read/Write Stress Test
# ============================================================================
echo "[3/4] Mixed Read/Write Stress Test"
echo "==================================="
echo " Running mixed workload for $TEST_DURATION..."
warp mixed \
--host="$TARGET_HOST" \
--obj.size=1MiB \
--concurrent="$CONCURRENCY" \
--duration="$TEST_DURATION" \
--bucket="$TEST_BUCKET" \
--json \
> "$OUTPUT_DIR/mixed-1MiB-c${CONCURRENCY}.json" 2>/dev/null || echo " MIXED TEST FAILED"
echo " Mixed test complete"
echo ""
# ============================================================================
# Test 4: Early-Stop Behavior Validation
# ============================================================================
echo "[4/4] Early-Stop Behavior Validation"
echo "====================================="
# This test verifies that early-stop works correctly by checking:
# 1. All GET requests return correct data
# 2. No timeouts or errors under normal conditions
# 3. Performance is consistent
echo " Running early-stop validation with concurrent reads..."
# Create a test object
dd if=/dev/urandom of=/tmp/early-stop-test.bin bs=1048576 count=10 2>/dev/null
mc cp /tmp/early-stop-test.bin "${MC_ALIAS}/${TEST_BUCKET}/early-stop-test.bin" >/dev/null 2>&1
# Concurrent read test
local_passed=0
local_failed=0
for i in $(seq 1 100); do
downloadfile="/tmp/early-stop-download-${i}.bin"
if mc cp "${MC_ALIAS}/${TEST_BUCKET}/early-stop-test.bin" "$downloadfile" >/dev/null 2>&1; then
# Verify file size
local_size=$(stat -f%z "$downloadfile" 2>/dev/null || stat -c%s "$downloadfile" 2>/dev/null)
if [ "$local_size" = "10485760" ]; then
local_passed=$((local_passed + 1))
else
local_failed=$((local_failed + 1))
echo " SIZE MISMATCH: expected 10485760, got $local_size" >> "$OUTPUT_DIR/early-stop-errors.log"
fi
else
local_failed=$((local_failed + 1))
echo " DOWNLOAD FAILED: iteration $i" >> "$OUTPUT_DIR/early-stop-errors.log"
fi
rm -f "$downloadfile"
done
echo " Early-stop validation: $local_passed passed, $local_failed failed"
echo "early_stop_validation: passed=$local_passed failed=$local_failed" >> "$OUTPUT_DIR/early-stop-results.txt"
# Cleanup
rm -f /tmp/early-stop-test.bin
mc rm "${MC_ALIAS}/${TEST_BUCKET}/early-stop-test.bin" >/dev/null 2>&1 || true
echo ""
# ============================================================================
# Summary
# ============================================================================
echo "=========================================="
echo "Stress Test Summary"
echo "=========================================="
echo ""
echo "Results saved to: $OUTPUT_DIR"
echo ""
echo "Files:"
ls -la "$OUTPUT_DIR"
echo ""
echo "Review:"
echo " - correctness-results.txt: Data correctness validation"
echo " - early-stop-results.txt: Early-stop behavior validation"
echo " - get-*.json: Concurrent GET performance results"
echo " - mixed-*.json: Mixed workload results"
echo ""
# Cleanup test bucket
echo "Cleaning up test bucket..."
mc rm --recursive --force "${MC_ALIAS}/${TEST_BUCKET}" >/dev/null 2>&1 || true
mc rb "${MC_ALIAS}/${TEST_BUCKET}" >/dev/null 2>&1 || true
echo "Done!"