// Пример комплексного 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) }