mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-09-07 17:58:55 +08:00
270 lines
7.5 KiB
Go
270 lines
7.5 KiB
Go
// Package pregel provides performance benchmarks for the Pregel engine.
|
|
//
|
|
// Benchmarks cover: throughput (ops/sec), latency distribution (P50/P99),
|
|
// memory allocation, large state handling, and scalability with
|
|
// increasing node counts.
|
|
package pregel
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"ragflow/internal/harness/graph/channels"
|
|
"ragflow/internal/harness/graph/checkpoint"
|
|
"ragflow/internal/harness/graph/constants"
|
|
graphPkg "ragflow/internal/harness/graph/graph"
|
|
"ragflow/internal/harness/graph/types"
|
|
)
|
|
|
|
// ============================================================
|
|
// P0: Throughput benchmarks
|
|
// ============================================================
|
|
|
|
// benchmarkSimpleGraph creates a simple 3-node graph for benchmarking.
|
|
// mirrors newSimpleGraph in engine_test.go
|
|
func benchmarkSimpleGraph() types.StateGraph {
|
|
sg := graphPkg.NewStateGraph(map[string]any{"value": ""})
|
|
sg.AddChannel("value", channels.NewLastValue(""))
|
|
|
|
sg.AddNode("node_a", func(ctx context.Context, state any) (any, error) {
|
|
// Return a fresh map copy to avoid sharing mutable state across runs.
|
|
return map[string]any{"value": "a"}, nil
|
|
})
|
|
sg.AddNode("node_b", func(ctx context.Context, state any) (any, error) {
|
|
return map[string]any{"value": "b"}, nil
|
|
})
|
|
_ = sg.AddEdge(constants.Start, "node_a")
|
|
_ = sg.AddEdge("node_a", "node_b")
|
|
_ = sg.AddEdge("node_b", constants.End)
|
|
return sg
|
|
}
|
|
|
|
// BenchmarkEngine_SimpleChain measures throughput for a 3-node chain.
|
|
func BenchmarkEngine_SimpleChain(b *testing.B) {
|
|
g := benchmarkSimpleGraph()
|
|
engine := NewEngine(g, WithRecursionLimit(10))
|
|
ctx := context.Background()
|
|
input := map[string]any{"value": "bench"}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := engine.RunSync(ctx, input)
|
|
if err != nil {
|
|
b.Fatalf("RunSync: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// BenchmarkEngine_LongChain measures throughput for a 100-node chain.
|
|
func BenchmarkEngine_LongChain(b *testing.B) {
|
|
type State struct {
|
|
Count int
|
|
}
|
|
sg := graphPkg.NewStateGraph(State{})
|
|
sg.AddChannel("count", channels.NewLastValue(0))
|
|
|
|
prev := constants.Start
|
|
for i := 0; i < 100; i++ {
|
|
name := fmt.Sprintf("node_%d", i)
|
|
sg.AddNode(name, func(ctx context.Context, state any) (any, error) {
|
|
s := state.(State)
|
|
s.Count++
|
|
return s, nil
|
|
})
|
|
sg.AddEdge(prev, name)
|
|
prev = name
|
|
}
|
|
sg.AddEdge(prev, constants.End)
|
|
|
|
engine := NewEngine(sg, WithRecursionLimit(200))
|
|
ctx := context.Background()
|
|
input := State{}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := engine.RunSync(ctx, input)
|
|
if err != nil {
|
|
b.Fatalf("RunSync: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// P0: Latency benchmarks
|
|
// ============================================================
|
|
|
|
// BenchmarkEngine_WithCheckpointer measures latency when checkpoints are
|
|
// persisted to MemorySaver.
|
|
func BenchmarkEngine_WithCheckpointer(b *testing.B) {
|
|
g := benchmarkSimpleGraph()
|
|
ms := checkpoint.NewMemorySaver()
|
|
engine := NewEngine(g, WithRecursionLimit(10), WithCheckpointer(ms))
|
|
ctx := context.Background()
|
|
input := map[string]any{"value": "bench-cp"}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := engine.RunSync(ctx, input)
|
|
if err != nil {
|
|
b.Fatalf("RunSync with CP: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// P1: Increasing node count scaling
|
|
// ============================================================
|
|
|
|
// BenchmarkEngine_Scaling_Nodes measures how throughput scales with
|
|
// increasing node counts (10, 50, 100 nodes).
|
|
func BenchmarkEngine_Scaling_Nodes(b *testing.B) {
|
|
for _, n := range []int{10, 50, 100} {
|
|
b.Run(fmt.Sprintf("%d_nodes", n), func(b *testing.B) {
|
|
type State struct{ Count int }
|
|
sg := graphPkg.NewStateGraph(State{})
|
|
sg.AddChannel("count", channels.NewLastValue(0))
|
|
|
|
prev := constants.Start
|
|
for i := 0; i < n; i++ {
|
|
name := fmt.Sprintf("n_%d", i)
|
|
sg.AddNode(name, func(ctx context.Context, state any) (any, error) {
|
|
s := state.(State)
|
|
s.Count++
|
|
return s, nil
|
|
})
|
|
sg.AddEdge(prev, name)
|
|
prev = name
|
|
}
|
|
sg.AddEdge(prev, constants.End)
|
|
|
|
engine := NewEngine(sg, WithRecursionLimit(n*2))
|
|
ctx := context.Background()
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := engine.RunSync(ctx, State{})
|
|
if err != nil {
|
|
b.Fatalf("RunSync: %v", err)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// P1: Allocation benchmarks
|
|
// ============================================================
|
|
|
|
// BenchmarkEngine_Allocation measures per-call memory allocation overhead.
|
|
func BenchmarkEngine_Allocation(b *testing.B) {
|
|
g := benchmarkSimpleGraph()
|
|
engine := NewEngine(g, WithRecursionLimit(10))
|
|
ctx := context.Background()
|
|
input := map[string]any{"value": "bench-alloc"}
|
|
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := engine.RunSync(ctx, input)
|
|
if err != nil {
|
|
b.Fatalf("RunSync: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// P2: Large state benchmarks
|
|
// ============================================================
|
|
|
|
// BenchmarkEngine_LargeState measures performance when the state contains
|
|
// a large map (10K entries).
|
|
func BenchmarkEngine_LargeState(b *testing.B) {
|
|
type State struct{ Data map[string]string }
|
|
|
|
sg := graphPkg.NewStateGraph(State{})
|
|
sg.AddNode("load", func(ctx context.Context, state any) (any, error) {
|
|
s := state.(State)
|
|
if s.Data == nil {
|
|
s.Data = make(map[string]string)
|
|
}
|
|
for i := 0; i < 10000; i++ {
|
|
s.Data[fmt.Sprintf("k_%d", i)] = fmt.Sprintf("v_%d", i)
|
|
}
|
|
return s, nil
|
|
})
|
|
sg.AddEdge(constants.Start, "load")
|
|
sg.AddEdge("load", constants.End)
|
|
|
|
engine := NewEngine(sg, WithRecursionLimit(10))
|
|
ctx := context.Background()
|
|
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := engine.RunSync(ctx, State{})
|
|
if err != nil {
|
|
b.Fatalf("RunSync: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// P2: Concurrency scaling benchmarks
|
|
// ============================================================
|
|
|
|
// BenchmarkEngine_ConcurrentCalls measures throughput under concurrent load.
|
|
func BenchmarkEngine_ConcurrentCalls(b *testing.B) {
|
|
g := benchmarkSimpleGraph()
|
|
engine := NewEngine(g, WithRecursionLimit(10))
|
|
ctx := context.Background()
|
|
input := map[string]any{"value": "bench-conc"}
|
|
|
|
b.ResetTimer()
|
|
b.RunParallel(func(pb *testing.PB) {
|
|
for pb.Next() {
|
|
_, err := engine.RunSync(ctx, input)
|
|
if err != nil {
|
|
b.Errorf("RunSync: %v", err)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// ============================================================
|
|
// P2: Checkpoint with large state benchmarks
|
|
// ============================================================
|
|
|
|
// BenchmarkEngine_Checkpoint_LargeState measures the cost of checkpointing
|
|
// a large state (10K entries map).
|
|
func BenchmarkEngine_Checkpoint_LargeState(b *testing.B) {
|
|
type State struct{ Data map[string]string }
|
|
|
|
sg := graphPkg.NewStateGraph(State{})
|
|
sg.AddNode("load", func(ctx context.Context, state any) (any, error) {
|
|
s := state.(State)
|
|
if s.Data == nil {
|
|
s.Data = make(map[string]string)
|
|
}
|
|
for i := 0; i < 10000; i++ {
|
|
s.Data[fmt.Sprintf("k_%d", i)] = fmt.Sprintf("v_%d", i)
|
|
}
|
|
return s, nil
|
|
})
|
|
sg.AddEdge(constants.Start, "load")
|
|
sg.AddEdge("load", constants.End)
|
|
|
|
ms := checkpoint.NewMemorySaver()
|
|
engine := NewEngine(sg, WithRecursionLimit(10), WithCheckpointer(ms))
|
|
ctx := context.Background()
|
|
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := engine.RunSync(ctx, State{})
|
|
if err != nil {
|
|
b.Fatalf("RunSync: %v", err)
|
|
}
|
|
}
|
|
}
|