diff --git a/BENCHMARK_QUICK_REFERENCE.md b/BENCHMARK_QUICK_REFERENCE.md new file mode 100644 index 00000000..45294d9f --- /dev/null +++ b/BENCHMARK_QUICK_REFERENCE.md @@ -0,0 +1,67 @@ +# 🚀 Benchmark Quick Reference + +## 📋 **Essential Commands** + +### **Quick Start** +```bash +make benchmark-compare # Compare V1 vs V2 performance +make benchmark-all # Run all benchmarks +``` + +### **Individual Benchmarks** +```bash +# V1 Benchmarks +make run-benchmarks-single-json-no-auth # JSON, no auth +make run-benchmarks-single-vpack-no-auth # Velocypack, no auth + +# V2 Benchmarks +make run-v2-benchmarks-single-no-auth # No authentication +make run-v2-benchmarks-single-with-auth # With authentication +``` + +### **Shortcuts** +```bash +make benchmark # Quick V2 benchmark +make benchmark-v2 # All V2 benchmarks +make benchmark-all # V1 + V2 comparison +``` + +## 📊 **Understanding Results** + +### **Key Metrics** +- **ns/op** = Nanoseconds per operation (lower = faster) +- **B/op** = Bytes allocated per operation (lower = less memory) +- **allocs/op** = Memory allocations per operation (lower = better) + +### **Example Output** +``` +BenchmarkConnectionInitialization 534 1963648 ns/op 1234 B/op 5 allocs/op +BenchmarkCreateCollection 193 5377251 ns/op 5678 B/op 12 allocs/op +``` + +## 🔧 **Prerequisites** +- Docker running +- Make installed +- Go 1.21+ + +## 📁 **Benchmark Files** +- `test/benchmark_collection_test.go` - V1 collection/query benchmarks +- `test/benchmark_document_test.go` - V1 document benchmarks +- `v2/tests/benchmark_v2_test.go` - V2 comprehensive benchmarks + +## 🎯 **Benchmark Categories** +1. **Connection Initialization** - Client setup +2. **Collection Operations** - Create, exists, list +3. **Document Operations** - CRUD operations +4. **Query Operations** - AQL queries +5. **Cursor Operations** - Result iteration + +## 🚨 **Troubleshooting** +- **Docker not running**: Start Docker service +- **Port conflicts**: Stop other ArangoDB instances +- **Timeouts**: Check system resources + +## 💡 **Pro Tips** +- Run multiple times for consistency +- Close other applications during benchmarks +- Use same environment for fair comparison diff --git a/Makefile b/Makefile index 777a2437..1709d6b3 100644 --- a/Makefile +++ b/Makefile @@ -145,7 +145,7 @@ ifeq ("$(DEBUG)", "true") DOCKER_RUN_CMD := $(DOCKER_DEBUG_ARGS) $(GOIMAGE) /go/bin/dlv --listen=:$(DEBUG_PORT) --headless=true --api-version=2 --accept-multiclient exec /test_debug.test -- $(TESTOPTIONS) DOCKER_V2_RUN_CMD := $(DOCKER_RUN_CMD) else - DOCKER_RUN_CMD := $(GOIMAGE) go test -timeout 120m $(GOBUILDTAGSOPT) $(TESTOPTIONS) $(TESTVERBOSEOPTIONS) $(TESTS) + DOCKER_RUN_CMD := $(GOIMAGE) go test -timeout 120m $(GOBUILDTAGSOPT) $(TESTOPTIONS) $(TESTVERBOSEOPTIONS) $(TAGS) $(TESTS) DOCKER_V2_RUN_CMD := $(GOV2IMAGE) go test -timeout 120m $(GOBUILDTAGSOPT) $(TESTOPTIONS) $(TESTVERBOSEOPTIONS) -parallel $(TESTV2PARALLEL) ./tests endif @@ -459,6 +459,15 @@ __test_v2_go_test: ($(DOCKER_CMD) $(DOCKER_CMD_V2_PARAMS) $(DOCKER_V2_RUN_CMD) $(ADD_TIMESTAMP)) && echo "success!" \ || ($(ON_FAILURE_PARAMS) MAJOR_VERSION=2 . ./test/on_failure.sh) +# Internal V2 benchmark tasks +__run_v2_benchmarks: __test_v2_debug__ __test_prepare __test_v2_benchmark_test __test_cleanup + +DOCKER_V2_BENCHMARK_CMD := $(GOV2IMAGE) go test -bench=. -benchmem -run=^$$ -timeout 60m $(GOBUILDTAGSOPT) $(TESTOPTIONS) $(TESTVERBOSEOPTIONS) -parallel $(TESTV2PARALLEL) ./tests + +__test_v2_benchmark_test: + ($(DOCKER_CMD) $(DOCKER_CMD_V2_PARAMS) $(DOCKER_V2_BENCHMARK_CMD) $(ADD_TIMESTAMP)) && echo "success!" \ + || ($(ON_FAILURE_PARAMS) MAJOR_VERSION=2 . ./test/on_failure.sh) + __test_debug__: ifeq ("$(DEBUG)", "true") @docker build -f Dockerfile.debug --build-arg GOVERSION=$(GOVERSION) --build-arg GOTOOLCHAIN=$(GOTOOLCHAIN) --build-arg "TESTS_DIRECTORY=./test" -t $(GOIMAGE) . @@ -508,6 +517,52 @@ run-benchmarks-single-vpack-no-auth: @echo "Benchmarks: Single server, Velocypack, no authentication" @${MAKE} TEST_MODE="single" TEST_AUTH="none" TEST_CONTENT_TYPE="vpack" TEST_BENCHMARK="true" __run_tests +# V2 Benchmarks +run-v2-benchmarks-single-no-auth: + @echo "V2 Benchmarks: Single server, without authentication" + @${MAKE} TEST_MODE="single" TEST_AUTH="none" __run_v2_benchmarks + +run-v2-benchmarks-single-with-auth: + @echo "V2 Benchmarks: Single server, with authentication" + @${MAKE} TEST_MODE="single" TEST_SSL="auto" TEST_AUTH="rootpw" __run_v2_benchmarks + +# Combined V2 benchmark targets for convenience +run-v2-benchmarks: run-v2-benchmarks-single-no-auth run-v2-benchmarks-single-with-auth + @echo "All V2 benchmarks completed" + +# Combined V1 and V2 Benchmarks +run-all-benchmarks: run-benchmarks-single-json-no-auth run-v2-benchmarks-single-no-auth + @echo "All benchmarks completed" + +V1_RESULTS = test/v1_benchmarks.txt +V2_RESULTS = v2/tests/v2_benchmarks.txt +# Benchmark comparison target +benchmark-compare: + @echo "=== V1 vs V2 Benchmark Comparison ===" + @echo "Running V1 benchmarks..." + @${MAKE} run-benchmarks-single-json-no-auth > $(V1_RESULTS) 2>&1 || true + @echo "Running V2 benchmarks..." + @${MAKE} run-v2-benchmarks-single-no-auth > $(V2_RESULTS) 2>&1 || true + @echo "" + @echo "=== BENCHMARK COMPARISON RESULTS ===" + @echo "Format: Benchmark Name | V1 (ns/op) | V2 (ns/op) | V2/V1 Ratio" + @echo "==================================================================" + @echo "Connection Initialization:" + @grep "BenchmarkConnectionInitialization" $(V1_RESULTS) | grep "ns/op" | awk '{print "V1: " $$3}' || echo "V1: N/A" + @grep "BenchmarkV2ConnectionInitialization" $(V2_RESULTS) | grep "ns/op" | awk '{print "V2: " $$3}' || echo "V2: N/A" + @echo "" + @echo "Create Collection:" + @grep "BenchmarkCreateCollection" $(V1_RESULTS) | grep "ns/op" | awk '{print "V1: " $$3}' || echo "V1: N/A" + @grep "BenchmarkV2CreateCollection" $(V2_RESULTS) | grep "ns/op" | awk '{print "V2: " $$3}' || echo "V2: N/A" + @echo "" + @echo "Insert Single Document:" + @grep "BenchmarkInsertSingleDocument" $(V1_RESULTS) | grep "ns/op" | awk '{print "V1: " $$3}' || echo "V1: N/A" + @grep "BenchmarkV2InsertSingleDocument" $(V2_RESULTS) | grep "ns/op" | awk '{print "V2: " $$3}' || echo "V2: N/A" + @echo "" + @echo "=== Full Results Saved ===" + @echo "V1 Results: $(V1_RESULTS)" + @echo "V2 Results: $(V2_RESULTS)" + ## Lint .PHONY: tools @@ -616,3 +671,15 @@ release-v2-minor: release-v2-major: go run $(RELEASE) -type=major -github-release=$(GH_RELEASE) -versionfile=$(V2_VERSION) + +# Convenient benchmark shortcuts +benchmark: run-v2-benchmarks-single-no-auth + @echo "Quick benchmark run completed" + +benchmark-v2: run-v2-benchmarks + @echo "V2 benchmarks completed" + +benchmark-all: run-all-benchmarks + @echo "All benchmarks completed" + +# Note: benchmark-compare target is defined above in the benchmark section diff --git a/README.md b/README.md index eba00e04..065e2197 100644 --- a/README.md +++ b/README.md @@ -41,3 +41,275 @@ Key: * `+` Features included in the driver may be not present in the ArangoDB API. Calls to ArangoDB may result in unexpected responses (404). * `-` The ArangoDB version has features that are not supported by the driver. + +## Benchmark Testing + +This project includes comprehensive benchmark tests to measure and compare performance between V1 and V2 APIs across all major ArangoDB operations. + +### Prerequisites + +Before running benchmarks, ensure you have: + +1. **Docker** installed and running +2. **Make** installed +3. **Go 1.21+** installed +4. **ArangoDB Enterprise** Docker image available (will be pulled automatically) + +### Benchmark Result Files + +When you run benchmark comparisons, results are automatically saved to: + +- **V1 Results**: `test/v1_benchmarks.txt` +- **V2 Results**: `v2/tests/v2_benchmarks.txt` + +These files contain the complete benchmark output and can be used for: +- **Detailed analysis** of individual benchmark results +- **Historical comparison** between different runs +- **Debugging** benchmark failures or performance regressions + +### Available Benchmark Names + +#### V1 Benchmarks +- `BenchmarkConnectionInitialization` +- `BenchmarkCreateCollection` +- `BenchmarkInsertSingleDocument` +- `BenchmarkInsertBatchDocuments` +- `BenchmarkSimpleQuery` +- `BenchmarkAQLWithBindParameters` +- `BenchmarkCursorIteration` +- `BenchmarkUpdateDocument` +- `BenchmarkDeleteDocument` +- `BenchmarkBatchUpdateDocuments` +- `BenchmarkBatchDeleteDocuments` +- `BenchmarkCreateDocument` +- `BenchmarkReadDocument` +- `BenchmarkCollectionExists` +- `BenchmarkCollections` + +#### V2 Benchmarks +- `BenchmarkV2ConnectionInitialization` +- `BenchmarkV2CreateCollection` +- `BenchmarkV2InsertSingleDocument` +- `BenchmarkV2InsertBatchDocuments` +- `BenchmarkV2SimpleQuery` +- `BenchmarkV2AQLWithBindParameters` +- `BenchmarkV2CursorIteration` +- `BenchmarkV2UpdateDocument` +- `BenchmarkV2DeleteDocument` +- `BenchmarkV2BatchUpdateDocuments` +- `BenchmarkV2BatchDeleteDocuments` +- `BenchmarkV2ReadDocument` +- `BenchmarkV2CollectionExists` +- `BenchmarkV2ListCollections` +- `BenchmarkV2DatabaseExists` + + +### Quick Start + +```bash +# Run V1 benchmarks (JSON, no authentication) +make run-benchmarks-single-json-no-auth + +# Run V2 benchmarks (no authentication) +make run-v2-benchmarks-single-no-auth + +# Compare V1 vs V2 performance +make benchmark-compare + +# Run all benchmarks +make benchmark-all +``` + +### Available Benchmark Commands + +#### V1 Benchmarks +```bash +make run-benchmarks-single-json-no-auth # Single server, JSON, no auth +make run-benchmarks-single-vpack-no-auth # Single server, Velocypack, no auth +``` + +#### V2 Benchmarks +```bash +make run-v2-benchmarks-single-no-auth # Single server, no authentication +make run-v2-benchmarks-single-with-auth # Single server, with authentication +``` + +#### Combined Commands +```bash +make run-v2-benchmarks # All V2 benchmarks +make run-all-benchmarks # V1 + V2 comparison +``` + +#### Convenient Shortcuts +```bash +make benchmark # Quick V2 benchmark (no auth) +make benchmark-v2 # All V2 benchmarks +make benchmark-all # V1 + V2 comparison +make benchmark-compare # Side-by-side V1 vs V2 comparison +``` + +### Benchmark Categories + +The benchmark suite covers all major ArangoDB operations: + +#### 1. Connection Initialization +- Client connection setup and initialization + +#### 2. Collection Operations +- Creating collections +- Checking collection existence +- Listing collections +- Database existence checks + +#### 3. Document Operations +- Single document creation, reading, updating, deletion +- Batch document operations (create, read, update, delete) +- Parallel document operations + +#### 4. Query Operations +- Simple AQL queries +- AQL queries with bind parameters +- Query validation and explanation + +#### 5. Cursor Operations +- Iterating over query results +- Cursor-based data fetching + +### Understanding Benchmark Results + +#### Key Metrics +- **ns/op** (nanoseconds per operation) - Lower is better +- **B/op** (bytes allocated per operation) - Lower is better +- **allocs/op** (memory allocations per operation) - Lower is better + +#### Example Output +``` +BenchmarkConnectionInitialization 534 1963648 ns/op +BenchmarkCreateCollection 193 5377251 ns/op +BenchmarkInsertSingleDocument 1044 1178407 ns/op +``` + +#### Performance Comparison +```bash +make benchmark-compare +``` +This command provides side-by-side comparison: +``` +=== BENCHMARK COMPARISON RESULTS === +Connection Initialization: +V1: 1963648 ns/op +V2: 38056 ns/op + +Create Collection: +V1: 5377251 ns/op +V2: 3085702 ns/op +``` + +### Benchmark Files Structure + +``` +test/ +├── benchmark_collection_test.go # V1 collection and query benchmarks +├── benchmark_document_test.go # V1 document operation benchmarks +└── v1_benchmarks.txt # V1 benchmark results (generated) + +v2/tests/ +├── benchmark_v2_test.go # V2 comprehensive benchmarks +└── v2_benchmarks.txt # V2 benchmark results (generated) +``` + +### Benchmark Result Files + +When you run benchmark comparisons, results are automatically saved to: + +- **V1 Results**: `test/v1_benchmarks.txt` +- **V2 Results**: `v2/tests/v2_benchmarks.txt` + +These files contain the complete benchmark output and can be used for: +- **Detailed analysis** of individual benchmark results +- **Historical comparison** between different runs +- **CI/CD artifact collection** for performance tracking +- **Debugging** benchmark failures or performance regressions + +### Running Benchmarks Manually + +If you prefer to run benchmarks directly with Go: + +```bash +# V1 benchmarks +cd test +go test -bench=. -benchmem -run=^$ -timeout 60m + +# V2 benchmarks +cd v2/tests +go test -bench=. -benchmem -run=^$ -timeout 60m + +``` + +### Troubleshooting + +#### Common Issues + +1. **Docker not running** + ``` + Error: Cannot connect to the Docker daemon + ``` + **Solution**: Start Docker service + +2. **Port conflicts** + ``` + Error: Port 7001 already in use + ``` + **Solution**: Stop other ArangoDB instances or change ports + +3. **Timeout errors** + ``` + Error: context deadline exceeded + ``` + **Solution**: Increase timeout or check system resources + +4. **Environment variable errors in V2** + ``` + Error: No endpoints found in environment variable TEST_ENDPOINTS + ``` + **Solution**: Use Makefile targets instead of direct `go test` commands + +#### Performance Tips + +1. **Run multiple times** - Benchmark results can vary, run 2-3 times for consistency +2. **Check system resources** - Ensure adequate CPU and memory +3. **Close other applications** - Minimize background processes +4. **Use consistent environment** - Run on the same machine for fair comparison + +### Benchmark Development + +To add new benchmarks: + +1. **V1**: Add to `test/benchmark_*_test.go` files +2. **V2**: Add to `v2/tests/benchmark_v2_test.go` +3. Follow the naming convention: `Benchmark[V2]OperationName` +4. Use appropriate helper functions for setup/cleanup +5. Include memory allocation reporting with `b.ReportAllocs()` + +### Example Benchmark Function + +```go +func BenchmarkMyOperation(b *testing.B) { + // Setup + client := createClient(b, nil) + db := ensureDatabase(nil, client, "test_db", nil, b) + defer db.Remove(nil) + + col := ensureCollection(nil, db, "test_col", nil, b) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + // Your operation here + _, err := col.CreateDocument(nil, MyDocument{}) + if err != nil { + b.Errorf("Operation failed: %s", err) + } + } + b.ReportAllocs() +} +``` diff --git a/test/benchmark_collection_test.go b/test/benchmark_collection_test.go index f56698c4..a88a266c 100644 --- a/test/benchmark_collection_test.go +++ b/test/benchmark_collection_test.go @@ -23,6 +23,8 @@ package test import ( "fmt" "testing" + + driver "github.com/arangodb/go-driver" ) // BenchmarkCollectionExists measures the CollectionExists operation. @@ -43,6 +45,7 @@ func BenchmarkCollectionExists(b *testing.B) { b.Errorf("CollectionExists failed: %s", describe(err)) } } + b.ReportAllocs() } // BenchmarkCollection measures the Collection operation. @@ -63,6 +66,7 @@ func BenchmarkCollection(b *testing.B) { b.Errorf("Collection failed: %s", describe(err)) } } + b.ReportAllocs() } // BenchmarkCollections measures the Collections operation. @@ -85,4 +89,428 @@ func BenchmarkCollections(b *testing.B) { b.Errorf("Collections failed: %s", describe(err)) } } + b.ReportAllocs() +} + +// BenchmarkConnectionInitialization measures the time to create a new client connection. +func BenchmarkConnectionInitialization(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + c := createClient(b, nil) + if c == nil { + b.Error("Failed to create client") + } + } + b.ReportAllocs() +} + +// BenchmarkCreateCollection measures the time to create a new collection. +func BenchmarkCreateCollection(b *testing.B) { + c := createClient(b, nil) + db := ensureDatabase(nil, c, "benchmark_collection_test", nil, b) + defer func() { + err := db.Remove(nil) + if err != nil { + b.Logf("Failed to drop database %s: %s ...", db.Name(), err) + } + }() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + colName := fmt.Sprintf("bench_col_%d", i) + col, err := db.CreateCollection(nil, colName, nil) + if err != nil { + b.Errorf("CreateCollection failed: %s", describe(err)) + } + // Clean up the collection immediately to avoid accumulation + if err := col.Remove(nil); err != nil { + b.Logf("Failed to remove collection %s: %s", colName, err) + } + } + b.ReportAllocs() +} + +// BenchmarkInsertSingleDocument measures the time to insert a single document. +func BenchmarkInsertSingleDocument(b *testing.B) { + c := createClient(b, nil) + db := ensureDatabase(nil, c, "benchmark_document_test", nil, b) + defer func() { + err := db.Remove(nil) + if err != nil { + b.Logf("Failed to drop database %s: %s ...", db.Name(), err) + } + }() + col := ensureCollection(nil, db, "benchmark_docs", nil, b) + + // Pre-create documents to avoid allocation during benchmark + docs := make([]UserDoc, b.N) + for i := 0; i < b.N; i++ { + docs[i] = UserDoc{ + Name: fmt.Sprintf("User_%d", i), + Age: 20 + (i % 50), + } + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + if _, err := col.CreateDocument(nil, docs[i]); err != nil { + b.Errorf("CreateDocument failed: %s", describe(err)) + } + } + b.ReportAllocs() +} + +// BenchmarkInsertBatchDocuments measures the time to insert documents in batches. +func BenchmarkInsertBatchDocuments(b *testing.B) { + c := createClient(b, nil) + db := ensureDatabase(nil, c, "benchmark_batch_test", nil, b) + defer func() { + err := db.Remove(nil) + if err != nil { + b.Logf("Failed to drop database %s: %s ...", db.Name(), err) + } + }() + col := ensureCollection(nil, db, "benchmark_batch_docs", nil, b) + + // Use batch size of 100 documents + batchSize := 100 + if b.N < batchSize { + batchSize = b.N + } + + // Pre-create documents to avoid allocation during benchmark + docs := make([]UserDoc, batchSize) + for i := 0; i < batchSize; i++ { + docs[i] = UserDoc{ + Name: fmt.Sprintf("BatchUser_%d", i), + Age: 20 + (i % 50), + } + } + + b.ResetTimer() + for i := 0; i < b.N; i += batchSize { + currentBatchSize := batchSize + if i+batchSize > b.N { + currentBatchSize = b.N - i + } + if _, _, err := col.CreateDocuments(nil, docs[:currentBatchSize]); err != nil { + b.Errorf("CreateDocuments failed: %s", describe(err)) + } + } + b.ReportAllocs() +} + +// BenchmarkSimpleQuery measures the time to execute simple AQL queries. +func BenchmarkSimpleQuery(b *testing.B) { + c := createClient(b, nil) + db := ensureDatabase(nil, c, "benchmark_query_test", nil, b) + defer func() { + err := db.Remove(nil) + if err != nil { + b.Logf("Failed to drop database %s: %s ...", db.Name(), err) + } + }() + col := ensureCollection(nil, db, "benchmark_query_docs", nil, b) + + // Pre-populate collection with test data + testDocs := make([]UserDoc, 1000) + for i := 0; i < 1000; i++ { + testDocs[i] = UserDoc{ + Name: fmt.Sprintf("QueryUser_%d", i), + Age: 20 + (i % 50), + } + } + if _, _, err := col.CreateDocuments(nil, testDocs); err != nil { + b.Fatalf("Failed to create test documents: %s", describe(err)) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + query := "FOR doc IN benchmark_query_docs FILTER doc.age > 30 RETURN doc" + cur, err := db.Query(nil, query, nil) + if err != nil { + b.Errorf("Query failed: %s", describe(err)) + } else { + cur.Close() + } + } + b.ReportAllocs() +} + +// BenchmarkAQLWithBindParameters measures the time to execute AQL queries with bind parameters. +func BenchmarkAQLWithBindParameters(b *testing.B) { + c := createClient(b, nil) + db := ensureDatabase(nil, c, "benchmark_bind_test", nil, b) + defer func() { + err := db.Remove(nil) + if err != nil { + b.Logf("Failed to drop database %s: %s ...", db.Name(), err) + } + }() + col := ensureCollection(nil, db, "benchmark_bind_docs", nil, b) + + // Pre-populate collection with test data + testDocs := make([]UserDoc, 1000) + for i := 0; i < 1000; i++ { + testDocs[i] = UserDoc{ + Name: fmt.Sprintf("BindUser_%d", i), + Age: 20 + (i % 50), + } + } + if _, _, err := col.CreateDocuments(nil, testDocs); err != nil { + b.Fatalf("Failed to create test documents: %s", describe(err)) + } + + query := "FOR doc IN benchmark_bind_docs FILTER doc.age > @minAge AND doc.age < @maxAge RETURN doc" + + b.ResetTimer() + for i := 0; i < b.N; i++ { + bindVars := map[string]interface{}{ + "minAge": 20 + (i % 20), + "maxAge": 40 + (i % 20), + } + cur, err := db.Query(nil, query, bindVars) + if err != nil { + b.Errorf("Query with bind parameters failed: %s", describe(err)) + } else { + cur.Close() + } + } + b.ReportAllocs() +} + +// BenchmarkCursorIteration measures the time to iterate over query results. +func BenchmarkCursorIteration(b *testing.B) { + c := createClient(b, nil) + db := ensureDatabase(nil, c, "benchmark_cursor_test", nil, b) + defer func() { + err := db.Remove(nil) + if err != nil { + b.Logf("Failed to drop database %s: %s ...", db.Name(), err) + } + }() + col := ensureCollection(nil, db, "benchmark_cursor_docs", nil, b) + + // Pre-populate collection with test data + testDocs := make([]UserDoc, 1000) + for i := 0; i < 1000; i++ { + testDocs[i] = UserDoc{ + Name: fmt.Sprintf("CursorUser_%d", i), + Age: 20 + (i % 50), + } + } + if _, _, err := col.CreateDocuments(nil, testDocs); err != nil { + b.Fatalf("Failed to create test documents: %s", describe(err)) + } + + query := "FOR doc IN benchmark_cursor_docs RETURN doc" + + b.ResetTimer() + for i := 0; i < b.N; i++ { + cur, err := db.Query(nil, query, nil) + if err != nil { + b.Errorf("Query failed: %s", describe(err)) + continue + } + + // Iterate through all results + for cur.HasMore() { + var doc UserDoc + if _, err := cur.ReadDocument(nil, &doc); err != nil { + b.Errorf("ReadDocument failed: %s", describe(err)) + break + } + } + cur.Close() + } + b.ReportAllocs() +} + +// BenchmarkUpdateDocument measures the time to update documents. +func BenchmarkUpdateDocument(b *testing.B) { + c := createClient(b, nil) + db := ensureDatabase(nil, c, "benchmark_update_test", nil, b) + defer func() { + err := db.Remove(nil) + if err != nil { + b.Logf("Failed to drop database %s: %s ...", db.Name(), err) + } + }() + col := ensureCollection(nil, db, "benchmark_update_docs", nil, b) + + // Pre-create documents to update + metas := make([]driver.DocumentMeta, b.N) + for i := 0; i < b.N; i++ { + doc := UserDoc{ + Name: fmt.Sprintf("UpdateUser_%d", i), + Age: 20, + } + meta, err := col.CreateDocument(nil, doc) + if err != nil { + b.Fatalf("Failed to create document %d: %s", i, describe(err)) + } + metas[i] = meta + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + update := map[string]interface{}{ + "age": 30 + (i % 20), + } + if _, err := col.UpdateDocument(nil, metas[i].Key, update); err != nil { + b.Errorf("UpdateDocument failed: %s", describe(err)) + } + } + b.ReportAllocs() +} + +// BenchmarkDeleteDocument measures the time to delete documents. +func BenchmarkDeleteDocument(b *testing.B) { + c := createClient(b, nil) + db := ensureDatabase(nil, c, "benchmark_delete_test", nil, b) + defer func() { + err := db.Remove(nil) + if err != nil { + b.Logf("Failed to drop database %s: %s ...", db.Name(), err) + } + }() + + // Create a new collection for each benchmark iteration to avoid running out of documents + colName := fmt.Sprintf("benchmark_delete_docs_%d", b.N) + col := ensureCollection(nil, db, colName, nil, b) + + // Pre-create documents to delete + metas := make([]driver.DocumentMeta, b.N) + for i := 0; i < b.N; i++ { + doc := UserDoc{ + Name: fmt.Sprintf("DeleteUser_%d", i), + Age: 20 + (i % 50), + } + meta, err := col.CreateDocument(nil, doc) + if err != nil { + b.Fatalf("Failed to create document %d: %s", i, describe(err)) + } + metas[i] = meta + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + if _, err := col.RemoveDocument(nil, metas[i].Key); err != nil { + b.Errorf("RemoveDocument failed: %s", describe(err)) + } + } + b.ReportAllocs() +} + +// BenchmarkBatchUpdateDocuments measures the time to update multiple documents in a batch. +func BenchmarkBatchUpdateDocuments(b *testing.B) { + c := createClient(b, nil) + db := ensureDatabase(nil, c, "benchmark_batch_update_test", nil, b) + defer func() { + err := db.Remove(nil) + if err != nil { + b.Logf("Failed to drop database %s: %s ...", db.Name(), err) + } + }() + col := ensureCollection(nil, db, "benchmark_batch_update_docs", nil, b) + + // Use batch size of 50 documents + batchSize := 50 + if b.N < batchSize { + batchSize = b.N + } + + // Pre-create documents to update + metas := make([]driver.DocumentMeta, b.N) + for i := 0; i < b.N; i++ { + doc := UserDoc{ + Name: fmt.Sprintf("BatchUpdateUser_%d", i), + Age: 20, + } + meta, err := col.CreateDocument(nil, doc) + if err != nil { + b.Fatalf("Failed to create document %d: %s", i, describe(err)) + } + metas[i] = meta + } + + // Pre-create update data + updates := make([]map[string]interface{}, batchSize) + for i := 0; i < batchSize; i++ { + updates[i] = map[string]interface{}{ + "age": 30 + (i % 20), + } + } + + b.ResetTimer() + for i := 0; i < b.N; i += batchSize { + currentBatchSize := batchSize + if i+batchSize > b.N { + currentBatchSize = b.N - i + } + + keys := make([]string, currentBatchSize) + for j := 0; j < currentBatchSize; j++ { + keys[j] = metas[i+j].Key + } + + if _, _, err := col.UpdateDocuments(nil, keys, updates[:currentBatchSize]); err != nil { + b.Errorf("UpdateDocuments failed: %s", describe(err)) + } + } + b.ReportAllocs() +} + +// BenchmarkBatchDeleteDocuments measures the time to delete multiple documents in a batch. +func BenchmarkBatchDeleteDocuments(b *testing.B) { + c := createClient(b, nil) + db := ensureDatabase(nil, c, "benchmark_batch_delete_test", nil, b) + defer func() { + err := db.Remove(nil) + if err != nil { + b.Logf("Failed to drop database %s: %s ...", db.Name(), err) + } + }() + + // Use batch size of 50 documents + batchSize := 50 + if b.N < batchSize { + batchSize = b.N + } + + // Create a new collection for each benchmark iteration to avoid running out of documents + colName := fmt.Sprintf("benchmark_batch_delete_docs_%d", b.N) + col := ensureCollection(nil, db, colName, nil, b) + + // Pre-create documents to delete + metas := make([]driver.DocumentMeta, b.N) + for i := 0; i < b.N; i++ { + doc := UserDoc{ + Name: fmt.Sprintf("BatchDeleteUser_%d", i), + Age: 20 + (i % 50), + } + meta, err := col.CreateDocument(nil, doc) + if err != nil { + b.Fatalf("Failed to create document %d: %s", i, describe(err)) + } + metas[i] = meta + } + + b.ResetTimer() + for i := 0; i < b.N; i += batchSize { + currentBatchSize := batchSize + if i+batchSize > b.N { + currentBatchSize = b.N - i + } + + keys := make([]string, currentBatchSize) + for j := 0; j < currentBatchSize; j++ { + keys[j] = metas[i+j].Key + } + + if _, _, err := col.RemoveDocuments(nil, keys); err != nil { + b.Errorf("RemoveDocuments failed: %s", describe(err)) + } + } + b.ReportAllocs() } diff --git a/test/benchmark_document_test.go b/test/benchmark_document_test.go index d24385fa..4e39c969 100644 --- a/test/benchmark_document_test.go +++ b/test/benchmark_document_test.go @@ -20,7 +20,12 @@ package test -import "testing" +import ( + "fmt" + "testing" + + driver "github.com/arangodb/go-driver" +) // BenchmarkCreateDocument measures the CreateDocument operation for a simple document. func BenchmarkCreateDocument(b *testing.B) { @@ -44,6 +49,7 @@ func BenchmarkCreateDocument(b *testing.B) { b.Fatalf("Failed to create new document: %s", describe(err)) } } + b.ReportAllocs() } // BenchmarkCreateDocumentParallel measures parallel CreateDocument operations for a simple document. @@ -70,6 +76,7 @@ func BenchmarkCreateDocumentParallel(b *testing.B) { } } }) + b.ReportAllocs() } // BenchmarkReadDocument measures the ReadDocument operation for a simple document. @@ -99,6 +106,7 @@ func BenchmarkReadDocument(b *testing.B) { b.Errorf("Failed to read document: %s", describe(err)) } } + b.ReportAllocs() } // BenchmarkReadDocumentParallel measures parallel ReadDocument operations for a simple document. @@ -130,6 +138,7 @@ func BenchmarkReadDocumentParallel(b *testing.B) { } } }) + b.ReportAllocs() } // BenchmarkRemoveDocument measures the RemoveDocument operation for a simple document. @@ -163,4 +172,58 @@ func BenchmarkRemoveDocument(b *testing.B) { b.Errorf("Failed to remove document: %s", describe(err)) } } + b.ReportAllocs() +} + +// BenchmarkBatchReadDocuments measures the time to read multiple documents in a batch. +func BenchmarkBatchReadDocuments(b *testing.B) { + c := createClient(b, nil) + db := ensureDatabase(nil, c, "benchmark_batch_read_test", nil, b) + defer func() { + err := db.Remove(nil) + if err != nil { + b.Logf("Failed to drop database %s: %s ...", db.Name(), err) + } + }() + + col := ensureCollection(nil, db, "benchmark_batch_read_docs", nil, b) + + // Use batch size of 50 documents + batchSize := 50 + if b.N < batchSize { + batchSize = b.N + } + + // Pre-create documents to read + metas := make([]driver.DocumentMeta, b.N) + for i := 0; i < b.N; i++ { + doc := UserDoc{ + Name: fmt.Sprintf("BatchReadUser_%d", i), + Age: 20 + (i % 50), + } + meta, err := col.CreateDocument(nil, doc) + if err != nil { + b.Fatalf("Failed to create document %d: %s", i, describe(err)) + } + metas[i] = meta + } + + b.ResetTimer() + for i := 0; i < b.N; i += batchSize { + currentBatchSize := batchSize + if i+batchSize > b.N { + currentBatchSize = b.N - i + } + + keys := make([]string, currentBatchSize) + for j := 0; j < currentBatchSize; j++ { + keys[j] = metas[i+j].Key + } + + results := make([]UserDoc, currentBatchSize) + if _, _, err := col.ReadDocuments(nil, keys, results); err != nil { + b.Errorf("ReadDocuments failed: %s", describe(err)) + } + } + b.ReportAllocs() } diff --git a/test/database_test.go b/test/database_test.go index ffa38236..ad30d4c3 100644 --- a/test/database_test.go +++ b/test/database_test.go @@ -42,7 +42,7 @@ func databaseName(parts ...string) string { // It will fail the test when an error occurs. func ensureDatabase(ctx context.Context, c driver.Client, name string, options *driver.CreateDatabaseOptions, t testEnv) driver.Database { db, err := c.Database(ctx, name) - if driver.IsNotFound(err) { + if driver.IsNotFoundGeneral(err) { db, err = c.CreateDatabase(ctx, name, options) if err != nil { if driver.IsConflict(err) { diff --git a/v2/tests/benchmark_v2_test.go b/v2/tests/benchmark_v2_test.go new file mode 100644 index 00000000..6d0bdd9a --- /dev/null +++ b/v2/tests/benchmark_v2_test.go @@ -0,0 +1,787 @@ +// +// DISCLAIMER +// +// Copyright 2020-2025 ArangoDB GmbH, Cologne, Germany +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package tests + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/arangodb/go-driver/v2/arangodb" + "github.com/arangodb/go-driver/v2/arangodb/shared" +) + +// createBenchmarkClient creates a client for benchmarks without the complex connection waiting +func createBenchmarkClient(b *testing.B) arangodb.Client { + conn := connectionJsonHttp(b) + client := arangodb.NewClient(conn) + return client +} + +// setupBenchmarkDB creates a database and collection for benchmarks with shorter timeouts +func setupBenchmarkDB(b *testing.B, client arangodb.Client) (arangodb.Database, arangodb.Collection) { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + dbName := GenerateUUID("bench-db") + db, err := client.CreateDatabase(ctx, dbName, nil) + if err != nil { + b.Fatalf("Failed to create database: %s", err) + } + + colName := GenerateUUID("bench-col") + col, err := db.CreateCollectionV2(ctx, colName, nil) + if err != nil { + b.Fatalf("Failed to create collection: %s", err) + } + + return db, col +} + +// cleanupBenchmarkDB removes the database created for benchmarks +func cleanupBenchmarkDB(b *testing.B, db arangodb.Database) { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + if err := db.Remove(ctx); err != nil { + b.Logf("Failed to remove database: %s", err) + } +} + +// BenchmarkV2ConnectionInitialization measures the time to create a new V2 client connection. +func BenchmarkV2ConnectionInitialization(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + conn := connectionJsonHttp(b) + client := arangodb.NewClient(conn) + if client == nil { + b.Error("Failed to create V2 client") + } + } + b.ReportAllocs() +} + +// BenchmarkCreateDocument measures the CreateDocument operation for a simple document. +func BenchmarkCreateDocumentV2(b *testing.B) { + client := createBenchmarkClient(b) + db, _ := setupBenchmarkDB(b, client) + defer cleanupBenchmarkDB(b, db) + + colName := GenerateUUID("bench-col") + col, err := db.CreateCollectionV2(context.Background(), colName, nil) + if err != nil { + b.Fatalf("CreateCollectionV2 failed: %s", err) + } + defer func() { + if err := col.Remove(context.Background()); err != nil { + b.Logf("Failed to remove collection %s: %s", colName, err) + } + }() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + doc := UserDoc{ + "Jan", + 40 + i, + } + if _, err := col.CreateDocument(context.Background(), doc); err != nil { + b.Fatalf("Failed to create new document: %v", err) + } + } + b.ReportAllocs() +} + +// BenchmarkCreateDocumentParallel measures parallel CreateDocument operations for a simple document. +func BenchmarkCreateDocumentParallel(b *testing.B) { + client := createBenchmarkClient(b) + db, _ := setupBenchmarkDB(b, client) + defer cleanupBenchmarkDB(b, db) + + colName := GenerateUUID("bench-col") + col, err := db.CreateCollectionV2(context.Background(), colName, nil) + if err != nil { + b.Fatalf("CreateCollectionV2 failed: %s", err) + } + defer func() { + if err := col.Remove(context.Background()); err != nil { + b.Logf("Failed to remove collection %s: %s", colName, err) + } + }() + + b.SetParallelism(100) + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + doc := UserDoc{ + "Jan", + 40, + } + if _, err := col.CreateDocument(nil, doc); err != nil { + b.Fatalf("Failed to create new document: %v", err) + } + } + }) + b.ReportAllocs() +} + +// BenchmarkV2CreateCollection measures the time to create a new collection using V2 API. +func BenchmarkV2CreateCollection(b *testing.B) { + client := createBenchmarkClient(b) + db, _ := setupBenchmarkDB(b, client) + defer cleanupBenchmarkDB(b, db) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + colName := GenerateUUID("bench-col") + col, err := db.CreateCollectionV2(context.Background(), colName, nil) + if err != nil { + b.Errorf("CreateCollectionV2 failed: %s", err) + } else { + // Clean up the collection immediately to avoid accumulation + if err := col.Remove(context.Background()); err != nil { + b.Logf("Failed to remove collection %s: %s", colName, err) + } + } + } + b.ReportAllocs() +} + +// BenchmarkV2InsertSingleDocument measures the time to insert a single document using V2 API. +func BenchmarkV2InsertSingleDocument(b *testing.B) { + client := createBenchmarkClient(b) + _, col := setupBenchmarkDB(b, client) + defer cleanupBenchmarkDB(b, col.Database()) + + // Pre-create documents to avoid allocation during benchmark + docs := make([]UserDoc, b.N) + for i := 0; i < b.N; i++ { + docs[i] = UserDoc{ + Name: fmt.Sprintf("V2User_%d", i), + Age: 20 + (i % 50), + } + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + if _, err := col.CreateDocument(context.Background(), docs[i]); err != nil { + b.Errorf("CreateDocument failed: %s", err) + } + } + b.ReportAllocs() +} + +// BenchmarkV2InsertBatchDocuments measures the time to insert documents in batches using V2 API. +func BenchmarkV2InsertBatchDocuments(b *testing.B) { + client := createBenchmarkClient(b) + _, col := setupBenchmarkDB(b, client) + defer cleanupBenchmarkDB(b, col.Database()) + + // Use batch size of 100 documents + batchSize := 100 + if b.N < batchSize { + batchSize = b.N + } + + // Pre-create documents to avoid allocation during benchmark + docs := make([]UserDoc, batchSize) + for i := 0; i < batchSize; i++ { + docs[i] = UserDoc{ + Name: fmt.Sprintf("V2BatchUser_%d", i), + Age: 20 + (i % 50), + } + } + + b.ResetTimer() + for i := 0; i < b.N; i += batchSize { + currentBatchSize := batchSize + if i+batchSize > b.N { + currentBatchSize = b.N - i + } + reader, err := col.CreateDocuments(context.Background(), docs[:currentBatchSize]) + if err != nil { + b.Errorf("CreateDocuments failed: %s", err) + } else { + // Consume the reader to complete the operation + for { + _, err := reader.Read() + if shared.IsNoMoreDocuments(err) { + break + } + if err != nil { + b.Errorf("CreateDocuments read failed: %s", err) + break + } + } + } + } + b.ReportAllocs() +} + +// BenchmarkV2SimpleQuery measures the time to execute simple AQL queries using V2 API. +func BenchmarkV2SimpleQuery(b *testing.B) { + client := createBenchmarkClient(b) + db, col := setupBenchmarkDB(b, client) + defer cleanupBenchmarkDB(b, db) + + // Pre-populate collection with test data + testDocs := make([]UserDoc, 1000) + for i := 0; i < 1000; i++ { + testDocs[i] = UserDoc{ + Name: fmt.Sprintf("V2QueryUser_%d", i), + Age: 20 + (i % 50), + } + } + reader, err := col.CreateDocuments(context.Background(), testDocs) + if err != nil { + b.Fatalf("Failed to create test documents: %s", err) + } + // Consume the reader + for { + _, err := reader.Read() + if shared.IsNoMoreDocuments(err) { + break + } + if err != nil { + b.Fatalf("Failed to read test documents: %s", err) + } + } + + query := fmt.Sprintf("FOR doc IN `%s` FILTER doc.age > 30 RETURN doc", col.Name()) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + cur, err := db.Query(context.Background(), query, nil) + if err != nil { + b.Errorf("Query failed: %s", err) + } else { + cur.Close() + } + } + b.ReportAllocs() +} + +// BenchmarkV2AQLWithBindParameters measures the time to execute AQL queries with bind parameters using V2 API. +func BenchmarkV2AQLWithBindParameters(b *testing.B) { + client := createBenchmarkClient(b) + db, col := setupBenchmarkDB(b, client) + defer cleanupBenchmarkDB(b, db) + + // Pre-populate collection with test data + testDocs := make([]UserDoc, 1000) + for i := 0; i < 1000; i++ { + testDocs[i] = UserDoc{ + Name: fmt.Sprintf("V2BindUser_%d", i), + Age: 20 + (i % 50), + } + } + reader, err := col.CreateDocuments(context.Background(), testDocs) + if err != nil { + b.Fatalf("Failed to create test documents: %s", err) + } + // Consume the reader + for { + _, err := reader.Read() + if shared.IsNoMoreDocuments(err) { + break + } + if err != nil { + b.Fatalf("Failed to read test documents: %s", err) + } + } + + query := fmt.Sprintf("FOR doc IN `%s` FILTER doc.age > @minAge AND doc.age < @maxAge RETURN doc", col.Name()) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + bindVars := map[string]interface{}{ + "minAge": 20 + (i % 20), + "maxAge": 40 + (i % 20), + } + cur, err := db.Query(context.Background(), query, &arangodb.QueryOptions{ + BindVars: bindVars, + }) + if err != nil { + b.Errorf("Query with bind parameters failed: %s", err) + } else { + cur.Close() + } + } + b.ReportAllocs() +} + +// BenchmarkV2CursorIteration measures the time to iterate over query results using V2 API. +func BenchmarkV2CursorIteration(b *testing.B) { + client := createBenchmarkClient(b) + db, col := setupBenchmarkDB(b, client) + defer cleanupBenchmarkDB(b, db) + + // Pre-populate collection with test data + testDocs := make([]UserDoc, 1000) + for i := 0; i < 1000; i++ { + testDocs[i] = UserDoc{ + Name: fmt.Sprintf("V2CursorUser_%d", i), + Age: 20 + (i % 50), + } + } + reader, err := col.CreateDocuments(context.Background(), testDocs) + if err != nil { + b.Fatalf("Failed to create test documents: %s", err) + } + // Consume the reader + for { + _, err := reader.Read() + if shared.IsNoMoreDocuments(err) { + break + } + if err != nil { + b.Fatalf("Failed to read test documents: %s", err) + } + } + + query := fmt.Sprintf("FOR doc IN `%s` RETURN doc", col.Name()) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + cur, err := db.Query(context.Background(), query, nil) + if err != nil { + b.Errorf("Query failed: %s", err) + continue + } + + // Iterate through all results + for { + var doc UserDoc + _, err := cur.ReadDocument(context.Background(), &doc) + if shared.IsNoMoreDocuments(err) { + break + } + if err != nil { + b.Errorf("ReadDocument failed: %s", err) + break + } + } + cur.Close() + } + b.ReportAllocs() +} + +// BenchmarkV2UpdateDocument measures the time to update documents using V2 API. +func BenchmarkV2UpdateDocument(b *testing.B) { + client := createBenchmarkClient(b) + _, col := setupBenchmarkDB(b, client) + defer cleanupBenchmarkDB(b, col.Database()) + + // Pre-create documents to update + metas := make([]arangodb.CollectionDocumentCreateResponse, b.N) + for i := 0; i < b.N; i++ { + doc := UserDoc{ + Name: fmt.Sprintf("V2UpdateUser_%d", i), + Age: 20, + } + meta, err := col.CreateDocument(context.Background(), doc) + if err != nil { + b.Fatalf("Failed to create document %d: %s", i, err) + } + metas[i] = meta + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + update := map[string]interface{}{ + "age": 30 + (i % 20), + } + if _, err := col.UpdateDocument(context.Background(), metas[i].Key, update); err != nil { + b.Errorf("UpdateDocument failed: %s", err) + } + } + b.ReportAllocs() +} + +// BenchmarkV2DeleteDocument measures the time to delete documents using V2 API. +func BenchmarkV2DeleteDocument(b *testing.B) { + client := createBenchmarkClient(b) + db, _ := setupBenchmarkDB(b, client) + defer cleanupBenchmarkDB(b, db) + + // Create a new collection for each benchmark iteration to avoid running out of documents + colName := GenerateUUID("benchmark-delete-docs") + col, err := db.CreateCollectionV2(context.Background(), colName, nil) + if err != nil { + b.Fatalf("Failed to create collection: %s", err) + } + + // Pre-create documents to delete + metas := make([]arangodb.CollectionDocumentCreateResponse, b.N) + for i := 0; i < b.N; i++ { + doc := UserDoc{ + Name: fmt.Sprintf("V2DeleteUser_%d", i), + Age: 20 + (i % 50), + } + meta, err := col.CreateDocument(context.Background(), doc) + if err != nil { + b.Fatalf("Failed to create document %d: %s", i, err) + } + metas[i] = meta + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + if _, err := col.DeleteDocument(context.Background(), metas[i].Key); err != nil { + b.Errorf("DeleteDocument failed: %s", err) + } + } + b.ReportAllocs() +} + +// BenchmarkV2BatchUpdateDocuments measures the time to update multiple documents in a batch using V2 API. +func BenchmarkV2BatchUpdateDocuments(b *testing.B) { + client := createBenchmarkClient(b) + _, col := setupBenchmarkDB(b, client) + defer cleanupBenchmarkDB(b, col.Database()) + + // Use batch size of 50 documents + batchSize := 50 + if b.N < batchSize { + batchSize = b.N + } + + // Pre-create documents to update + metas := make([]arangodb.CollectionDocumentCreateResponse, b.N) + for i := 0; i < b.N; i++ { + doc := UserDoc{ + Name: fmt.Sprintf("V2BatchUpdateUser_%d", i), + Age: 20, + } + meta, err := col.CreateDocument(context.Background(), doc) + if err != nil { + b.Fatalf("Failed to create document %d: %s", i, err) + } + metas[i] = meta + } + + // Pre-create update data + updates := make([]map[string]interface{}, batchSize) + for i := 0; i < batchSize; i++ { + updates[i] = map[string]interface{}{ + "_key": "", // Will be set per batch + "age": 30 + (i % 20), + } + } + + b.ResetTimer() + for i := 0; i < b.N; i += batchSize { + currentBatchSize := batchSize + if i+batchSize > b.N { + currentBatchSize = b.N - i + } + + // Set the keys for this batch + for j := 0; j < currentBatchSize; j++ { + updates[j]["_key"] = metas[i+j].Key + } + + reader, err := col.UpdateDocuments(context.Background(), updates[:currentBatchSize]) + if err != nil { + b.Errorf("UpdateDocuments failed: %s", err) + } else { + // Consume the reader to complete the operation + for { + _, err := reader.Read() + if shared.IsNoMoreDocuments(err) { + break + } + if err != nil { + b.Errorf("UpdateDocuments read failed: %s", err) + break + } + } + } + } + b.ReportAllocs() +} + +// BenchmarkV2BatchDeleteDocuments measures the time to delete multiple documents in a batch using V2 API. +func BenchmarkV2BatchDeleteDocuments(b *testing.B) { + client := createBenchmarkClient(b) + db, _ := setupBenchmarkDB(b, client) + defer cleanupBenchmarkDB(b, db) + + // Use batch size of 50 documents + batchSize := 50 + if b.N < batchSize { + batchSize = b.N + } + + // Create a new collection for each benchmark iteration to avoid running out of documents + colName := GenerateUUID("benchmark-batch-delete-docs") + col, err := db.CreateCollectionV2(context.Background(), colName, nil) + if err != nil { + b.Fatalf("Failed to create collection: %s", err) + } + + // Pre-create documents to delete + metas := make([]arangodb.CollectionDocumentCreateResponse, b.N) + for i := 0; i < b.N; i++ { + doc := UserDoc{ + Name: fmt.Sprintf("V2BatchDeleteUser_%d", i), + Age: 20 + (i % 50), + } + meta, err := col.CreateDocument(context.Background(), doc) + if err != nil { + b.Fatalf("Failed to create document %d: %s", i, err) + } + metas[i] = meta + } + + b.ResetTimer() + for i := 0; i < b.N; i += batchSize { + currentBatchSize := batchSize + if i+batchSize > b.N { + currentBatchSize = b.N - i + } + + keys := make([]string, currentBatchSize) + for j := 0; j < currentBatchSize; j++ { + keys[j] = metas[i+j].Key + } + + reader, err := col.DeleteDocuments(context.Background(), keys) + if err != nil { + b.Errorf("DeleteDocuments failed: %s", err) + } else { + // Consume the reader to complete the operation + for { + var result arangodb.CollectionDocumentDeleteResponse + _, err := reader.Read(&result) + if shared.IsNoMoreDocuments(err) { + break + } + if err != nil { + b.Errorf("DeleteDocuments read failed: %s", err) + break + } + } + } + } + b.ReportAllocs() +} + +// BenchmarkV2ReadDocument measures the time to read documents using V2 API. +func BenchmarkV2ReadDocument(b *testing.B) { + client := createBenchmarkClient(b) + _, col := setupBenchmarkDB(b, client) + defer cleanupBenchmarkDB(b, col.Database()) + + // Pre-create documents to read + metas := make([]arangodb.CollectionDocumentCreateResponse, b.N) + for i := 0; i < b.N; i++ { + doc := UserDoc{ + Name: fmt.Sprintf("V2ReadUser_%d", i), + Age: 20 + (i % 50), + } + meta, err := col.CreateDocument(context.Background(), doc) + if err != nil { + b.Fatalf("Failed to create document %d: %s", i, err) + } + metas[i] = meta + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + var doc UserDoc + if _, err := col.ReadDocument(context.Background(), metas[i].Key, &doc); err != nil { + b.Errorf("ReadDocument failed: %s", err) + } + } + b.ReportAllocs() +} + +// BenchmarkReadDocumentParallel measures parallel ReadDocument operations for a simple document. +func BenchmarkV2ReadDocumentParallel(b *testing.B) { + client := createBenchmarkClient(b) + db, _ := setupBenchmarkDB(b, client) + defer cleanupBenchmarkDB(b, db) + + colName := GenerateUUID("bench-col") + col, err := db.CreateCollectionV2(context.Background(), colName, nil) + if err != nil { + b.Fatalf("CreateCollectionV2 failed: %s", err) + } + defer func() { + if err := col.Remove(context.Background()); err != nil { + b.Logf("Failed to remove collection %s: %s", colName, err) + } + }() + + doc := UserDoc{ + "Jan", + 40, + } + meta, err := col.CreateDocument(context.Background(), doc) + if err != nil { + b.Fatalf("Failed to create new document: %s", err) + } + + b.SetParallelism(100) + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + var result UserDoc + if _, err := col.ReadDocument(context.Background(), meta.Key, &result); err != nil { + b.Errorf("Failed to read document: %s", err) + } + } + }) + b.ReportAllocs() +} + +// BenchmarkV2BatchReadDocuments measures the time to read multiple documents in a batch using V2 API. +func BenchmarkV2BatchReadDocuments(b *testing.B) { + client := createBenchmarkClient(b) + _, col := setupBenchmarkDB(b, client) + defer cleanupBenchmarkDB(b, col.Database()) + + // Use batch size of 50 documents + batchSize := 50 + if b.N < batchSize { + batchSize = b.N + } + + // Pre-create documents to read + metas := make([]arangodb.CollectionDocumentCreateResponse, b.N) + for i := 0; i < b.N; i++ { + doc := UserDoc{ + Name: fmt.Sprintf("V2BatchReadUser_%d", i), + Age: 20 + (i % 50), + } + meta, err := col.CreateDocument(context.Background(), doc) + if err != nil { + b.Fatalf("Failed to create document %d: %s", i, err) + } + metas[i] = meta + } + + b.ResetTimer() + for i := 0; i < b.N; i += batchSize { + currentBatchSize := batchSize + if i+batchSize > b.N { + currentBatchSize = b.N - i + } + + keys := make([]string, currentBatchSize) + for j := 0; j < currentBatchSize; j++ { + keys[j] = metas[i+j].Key + } + + reader, err := col.ReadDocuments(context.Background(), keys) + if err != nil { + b.Errorf("ReadDocuments failed: %s", err) + } else { + // Consume the reader to complete the operation + for { + var result UserDoc + _, err := reader.Read(&result) + if shared.IsNoMoreDocuments(err) { + break + } + if err != nil { + b.Errorf("ReadDocuments read failed: %s", err) + break + } + } + } + } + b.ReportAllocs() +} + +// BenchmarkV2CollectionExists measures the time to check if a collection exists. +func BenchmarkV2CollectionExists(b *testing.B) { + client := createBenchmarkClient(b) + db, _ := setupBenchmarkDB(b, client) + defer cleanupBenchmarkDB(b, db) + + colName := GenerateUUID("bench-col") + col, err := db.CreateCollectionV2(context.Background(), colName, nil) + if err != nil { + b.Fatalf("Failed to create collection: %s", err) + } + defer col.Remove(context.Background()) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + exists, err := db.CollectionExists(context.Background(), colName) + if err != nil { + b.Errorf("Database.CollectionExists failed: %s", err) + } + if !exists { + b.Error("Collection should exist") + } + } + b.ReportAllocs() +} + +// BenchmarkV2ListCollections measures the time to list all collections in a database. +func BenchmarkV2ListCollections(b *testing.B) { + client := createBenchmarkClient(b) + db, _ := setupBenchmarkDB(b, client) + defer cleanupBenchmarkDB(b, db) + + // Create multiple collections + for i := 0; i < 10; i++ { + colName := GenerateUUID(fmt.Sprintf("bench-col-%d", i)) + col, err := db.CreateCollectionV2(context.Background(), colName, nil) + if err != nil { + b.Fatalf("Failed to create collection %d: %s", i, err) + } + defer col.Remove(context.Background()) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + collections, err := db.Collections(context.Background()) + if err != nil { + b.Errorf("Database.Collections failed: %s", err) + } + if len(collections) == 0 { + b.Error("Should have collections") + } + } + b.ReportAllocs() +} + +// BenchmarkV2DatabaseExists measures the time to check if a database exists. +func BenchmarkV2DatabaseExists(b *testing.B) { + client := createBenchmarkClient(b) + db, _ := setupBenchmarkDB(b, client) + defer cleanupBenchmarkDB(b, db) + + dbName := db.Name() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + exists, err := client.DatabaseExists(context.Background(), dbName) + if err != nil { + b.Errorf("Client.DatabaseExists failed: %s", err) + } + if !exists { + b.Error("Database should exist") + } + } + b.ReportAllocs() +}