Files
elowdb-go/examples/perf/main.go
2026-03-04 16:29:24 +06:00

207 lines
5.3 KiB
Go

// Пример комплексного performance-теста LineDb.
// Запуск:
//
// go run ./examples/perf/main.go
//
// Сценарий:
// 1. Вставка 5000 записей, каждая ~1800 символов.
// 2. 10 случайных обновлений, замер среднего времени.
// 3. 100 случайных удалений, замер среднего времени.
// 4. Замеры памяти процесса через runtime.MemStats.
package main
import (
"fmt"
"log"
"math/rand"
"os"
"runtime"
"strings"
"time"
"linedb/pkg/linedb"
)
const (
recordsCount = 5000
payloadSize = 1800
updateOps = 100
deleteOps = 100
collectionName = "perf_items"
dbDir = "./data/perf-benchmark"
allocSizeEstimate = 2048
)
func main() {
rand.Seed(time.Now().UnixNano())
if err := os.MkdirAll(dbDir, 0755); err != nil {
log.Fatalf("mkdir: %v", err)
}
initOptions := &linedb.LineDbInitOptions{
CacheSize: 1000,
CacheTTL: time.Minute,
DBFolder: dbDir,
Collections: []linedb.JSONLFileOptions{
{
CollectionName: collectionName,
AllocSize: allocSizeEstimate,
},
},
}
db := linedb.NewLineDb(nil)
if err := db.Init(true, initOptions); err != nil {
log.Fatalf("Init failed: %v", err)
}
defer db.Close()
printMem("Before insert")
fmt.Printf("1) Insert %d records (payload ~%d chars)...\n", recordsCount, payloadSize)
start := time.Now()
if err := insertRecords(db); err != nil {
log.Fatalf("InsertRecords failed: %v", err)
}
elapsedInsert := time.Since(start)
fmt.Printf(" Total insert time: %v, per record: %v\n",
elapsedInsert, elapsedInsert/time.Duration(recordsCount))
printMem("After insert")
all, err := db.Read(collectionName, linedb.LineDbAdapterOptions{})
if err != nil {
log.Fatalf("Read after insert failed: %v", err)
}
fmt.Printf(" Records in collection: %d\n", len(all))
ids := collectIDs(all)
if len(ids) == 0 {
log.Fatalf("No IDs collected, cannot continue")
}
fmt.Printf("\n2) Random update of %d records...\n", updateOps)
avgUpdate := benchmarkUpdates(db, ids, updateOps)
fmt.Printf(" Average update time: %v\n", avgUpdate)
printMem("After updates")
fmt.Printf("\n3) Random delete of %d records...\n", deleteOps)
avgDelete := benchmarkDeletes(db, ids, deleteOps)
fmt.Printf(" Average delete time: %v\n", avgDelete)
printMem("After deletes")
final, err := db.Read(collectionName, linedb.LineDbAdapterOptions{})
if err != nil {
log.Fatalf("Final read failed: %v", err)
}
fmt.Printf("\nFinal records in collection: %d\n", len(final))
fmt.Printf("Data directory: %s\n", dbDir)
}
func insertRecords(db *linedb.LineDb) error {
// Сгенерируем базовый payload
base := strings.Repeat("X", payloadSize)
batch := make([]any, 0, 100)
for i := 0; i < recordsCount; i++ {
rec := map[string]any{
"index": i,
"payload": base,
"created": time.Now().Format(time.RFC3339Nano),
}
batch = append(batch, rec)
if len(batch) >= cap(batch) {
if err := db.Insert(batch, collectionName, linedb.LineDbAdapterOptions{}); err != nil {
return err
}
batch = batch[:0]
}
}
if len(batch) > 0 {
if err := db.Insert(batch, collectionName, linedb.LineDbAdapterOptions{}); err != nil {
return err
}
}
return nil
}
func collectIDs(all []any) []any {
ids := make([]any, 0, len(all))
for _, r := range all {
if m, ok := r.(map[string]any); ok {
if id, ok := m["id"]; ok {
ids = append(ids, id)
}
}
}
return ids
}
func benchmarkUpdates(db *linedb.LineDb, ids []any, ops int) time.Duration {
if len(ids) == 0 {
return 0
}
var total time.Duration
for i := 0; i < ops; i++ {
id := ids[rand.Intn(len(ids))]
update := map[string]any{
"updatedAt": time.Now().Format(time.RFC3339Nano),
}
start := time.Now()
_, err := db.Update(update, collectionName, map[string]any{"id": id}, linedb.LineDbAdapterOptions{})
dur := time.Since(start)
if err != nil {
log.Printf("Update error (id=%v): %v", id, err)
continue
}
total += dur
}
if ops == 0 {
return 0
}
return total / time.Duration(ops)
}
func benchmarkDeletes(db *linedb.LineDb, ids []any, ops int) time.Duration {
if len(ids) == 0 {
return 0
}
var total time.Duration
used := make(map[int]struct{})
for i := 0; i < ops; i++ {
// чтобы не удалять по одному и тому же id постоянно, постараемся брать разные индексы
var idx int
for tries := 0; tries < 10; tries++ {
idx = rand.Intn(len(ids))
if _, ok := used[idx]; !ok {
break
}
}
used[idx] = struct{}{}
id := ids[idx]
start := time.Now()
_, err := db.Delete(map[string]any{"id": id}, collectionName, linedb.LineDbAdapterOptions{})
dur := time.Since(start)
if err != nil {
log.Printf("Delete error (id=%v): %v", id, err)
continue
}
total += dur
}
if ops == 0 {
return 0
}
return total / time.Duration(ops)
}
func printMem(label string) {
var m runtime.MemStats
runtime.ReadMemStats(&m)
fmt.Printf("\n=== %s ===\n", label)
fmt.Printf(" Alloc: %.2f MB\n", float64(m.Alloc)/1024.0/1024.0)
fmt.Printf(" TotalAlloc: %.2f MB\n", float64(m.TotalAlloc)/1024.0/1024.0)
fmt.Printf(" Sys: %.2f MB\n", float64(m.Sys)/1024.0/1024.0)
fmt.Printf(" NumGC: %d\n", m.NumGC)
}