Skip to main content
Glama
orneryd

M.I.M.I.R - Multi-agent Intelligent Memory & Insight Repository

by orneryd
performance_test.go7.59 kB
package bolt import ( "context" "fmt" "net" "testing" "time" "github.com/orneryd/nornicdb/pkg/cypher" "github.com/orneryd/nornicdb/pkg/storage" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // performanceQueryExecutor wraps the Cypher executor for performance tests. type performanceQueryExecutor struct { executor *cypher.StorageExecutor } func (p *performanceQueryExecutor) Execute(ctx context.Context, query string, params map[string]any) (*QueryResult, error) { result, err := p.executor.Execute(ctx, query, params) if err != nil { return nil, err } return &QueryResult{ Columns: result.Columns, Rows: result.Rows, }, nil } // startPerfTestServer starts a server for performance testing func startPerfTestServer(t *testing.T) (*Server, int) { store := storage.NewMemoryEngine() cypherExec := cypher.NewStorageExecutor(store) executor := &performanceQueryExecutor{executor: cypherExec} config := &Config{ Port: 0, MaxConnections: 10, ReadBufferSize: 8192, WriteBufferSize: 8192, } server := New(config, executor) go func() { if err := server.ListenAndServe(); err != nil { t.Logf("Server error: %v", err) } }() // Wait for server to start time.Sleep(50 * time.Millisecond) port := server.listener.Addr().(*net.TCPAddr).Port t.Logf("Bolt server listening on bolt://localhost:%d", port) return server, port } // TestPerformance_RelationshipVsNode tests that relationship operations // should be within 10x of node operations (they're the same complexity). // FAILING: Currently relationship is 36x slower than node on JS driver. func TestPerformance_RelationshipVsNode(t *testing.T) { server, port := startPerfTestServer(t) defer server.Close() conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", port)) require.NoError(t, err) defer conn.Close() // Handshake and HELLO performHandshake(t, conn) sendHello(t, conn) readSuccess(t, conn) // Setup: Create nodes for relationship test for i := 0; i < 10; i++ { sendRun(t, conn, fmt.Sprintf("CREATE (a:Actor {name: 'Actor_%d'})", i), nil) readSuccess(t, conn) sendPull(t, conn) readSuccess(t, conn) } for i := 0; i < 10; i++ { sendRun(t, conn, fmt.Sprintf("CREATE (m:Movie {title: 'Movie_%d'})", i), nil) readSuccess(t, conn) sendPull(t, conn) readSuccess(t, conn) } iterations := 50 // Test 1: Node create/delete (two queries) start := time.Now() for i := 0; i < iterations; i++ { sendRun(t, conn, "CREATE (n:TestNode {id: 1}) RETURN n", nil) readSuccess(t, conn) sendPull(t, conn) readSuccess(t, conn) sendRun(t, conn, "MATCH (n:TestNode) DELETE n", nil) readSuccess(t, conn) sendPull(t, conn) readSuccess(t, conn) } nodeTime := time.Since(start) nodeOpsPerSec := float64(iterations) / nodeTime.Seconds() // Test 2: Relationship create/delete (compound query) start = time.Now() for i := 0; i < iterations; i++ { sendRun(t, conn, `MATCH (a:Actor), (m:Movie) WITH a, m LIMIT 1 CREATE (a)-[r:TEMP_REL]->(m) DELETE r`, nil) readSuccess(t, conn) sendPull(t, conn) readSuccess(t, conn) } relTime := time.Since(start) relOpsPerSec := float64(iterations) / relTime.Seconds() t.Logf("Node operations: %.0f ops/sec (%.2f ms/op)", nodeOpsPerSec, float64(nodeTime.Milliseconds())/float64(iterations)) t.Logf("Relationship operations: %.0f ops/sec (%.2f ms/op)", relOpsPerSec, float64(relTime.Milliseconds())/float64(iterations)) // ASSERTION: Relationship should be within 10x of node performance // Both are simple operations - the gap should not be huge ratio := nodeOpsPerSec / relOpsPerSec t.Logf("Performance ratio (node/rel): %.1fx", ratio) assert.Less(t, ratio, 10.0, "Relationship operations should not be more than 10x slower than node operations. "+ "Current ratio: %.1fx. This indicates a performance bug in compound query handling.", ratio) } // TestPerformance_CompoundQueryShouldMatchSeparateQueries tests that // a compound MATCH...CREATE...DELETE should be similar speed to separate queries. func TestPerformance_CompoundQueryShouldMatchSeparateQueries(t *testing.T) { server, port := startPerfTestServer(t) defer server.Close() conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", port)) require.NoError(t, err) defer conn.Close() performHandshake(t, conn) sendHello(t, conn) readSuccess(t, conn) // Setup for i := 0; i < 10; i++ { sendRun(t, conn, fmt.Sprintf("CREATE (a:Actor {name: 'Actor_%d'})", i), nil) readSuccess(t, conn) sendPull(t, conn) readSuccess(t, conn) } for i := 0; i < 10; i++ { sendRun(t, conn, fmt.Sprintf("CREATE (m:Movie {title: 'Movie_%d'})", i), nil) readSuccess(t, conn) sendPull(t, conn) readSuccess(t, conn) } iterations := 50 // Test 1: Compound query (one roundtrip) start := time.Now() for i := 0; i < iterations; i++ { sendRun(t, conn, `MATCH (a:Actor), (m:Movie) WITH a, m LIMIT 1 CREATE (a)-[r:TEMP_REL]->(m) DELETE r`, nil) readSuccess(t, conn) sendPull(t, conn) readSuccess(t, conn) } compoundTime := time.Since(start) compoundOps := float64(iterations) / compoundTime.Seconds() // Test 2: Two separate queries (two roundtrips) start = time.Now() for i := 0; i < iterations; i++ { sendRun(t, conn, `MATCH (a:Actor), (m:Movie) WITH a, m LIMIT 1 CREATE (a)-[r:TEMP_REL]->(m) RETURN r`, nil) readSuccess(t, conn) sendPull(t, conn) readSuccess(t, conn) sendRun(t, conn, "MATCH ()-[r:TEMP_REL]->() DELETE r", nil) readSuccess(t, conn) sendPull(t, conn) readSuccess(t, conn) } separateTime := time.Since(start) separateOps := float64(iterations) / separateTime.Seconds() t.Logf("Compound query: %.0f ops/sec", compoundOps) t.Logf("Separate queries: %.0f ops/sec", separateOps) // ASSERTION: Compound query (1 roundtrip) should be FASTER than separate (2 roundtrips) // If compound is slower, there's a bug in our handling assert.Greater(t, compoundOps, separateOps*0.5, "Compound query should be at least half as fast as separate queries (1 vs 2 roundtrips)") } // TestPerformance_MinimumThroughput tests that we achieve minimum acceptable throughput // for relationship operations over Bolt. func TestPerformance_MinimumThroughput(t *testing.T) { server, port := startPerfTestServer(t) defer server.Close() conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", port)) require.NoError(t, err) defer conn.Close() performHandshake(t, conn) sendHello(t, conn) readSuccess(t, conn) // Setup for i := 0; i < 100; i++ { sendRun(t, conn, fmt.Sprintf("CREATE (a:Actor {name: 'Actor_%d'})", i), nil) readSuccess(t, conn) sendPull(t, conn) readSuccess(t, conn) } for i := 0; i < 150; i++ { sendRun(t, conn, fmt.Sprintf("CREATE (m:Movie {title: 'Movie_%d'})", i), nil) readSuccess(t, conn) sendPull(t, conn) readSuccess(t, conn) } iterations := 100 query := `MATCH (a:Actor), (m:Movie) WITH a, m LIMIT 1 CREATE (a)-[r:TEMP_REL]->(m) DELETE r` start := time.Now() for i := 0; i < iterations; i++ { sendRun(t, conn, query, nil) readSuccess(t, conn) sendPull(t, conn) readSuccess(t, conn) } elapsed := time.Since(start) opsPerSec := float64(iterations) / elapsed.Seconds() t.Logf("Relationship create/delete: %.0f ops/sec", opsPerSec) // MINIMUM: Should achieve at least 1000 ops/sec for simple relationship operations // Neo4j achieves ~1800 ops/sec, so 1000 is a reasonable minimum assert.Greater(t, opsPerSec, 1000.0, "Should achieve at least 1000 ops/sec for relationship create/delete. "+ "Current: %.0f ops/sec. This is a performance regression.", opsPerSec) }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/orneryd/Mimir'

If you have feedback or need assistance with the MCP directory API, please join our Discord server