207 lines
5.3 KiB
Go
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)
|
|
}
|