Files

306 lines
7.3 KiB
Go
Raw Permalink Normal View History

// repo_bench_test.go — repository 层性能基准测试
// 覆盖:批量写入、并发只读查询、分页列表、更新状态、软删除
package repository
import (
"context"
"fmt"
"sync"
"sync/atomic"
"testing"
_ "modernc.org/sqlite"
gormsqlite "gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"github.com/user-management-system/internal/domain"
)
var repoBenchCounter int64
// openBenchDB 为 Benchmark 打开独立内存 DB不依赖 *testing.T
func openBenchDB(b *testing.B) *gorm.DB {
b.Helper()
id := atomic.AddInt64(&repoBenchCounter, 1)
dsn := fmt.Sprintf("file:repobenchdb%d?mode=memory&cache=private", id)
db, err := gorm.Open(gormsqlite.New(gormsqlite.Config{
DriverName: "sqlite",
DSN: dsn,
}), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
if err != nil {
b.Fatalf("openBenchDB: %v", err)
}
if err := db.AutoMigrate(
&domain.User{},
&domain.Role{},
&domain.Permission{},
&domain.UserRole{},
&domain.RolePermission{},
); err != nil {
b.Fatalf("AutoMigrate: %v", err)
}
return db
}
// seedUsers 往 DB 插入 n 条用户
func seedUsers(b *testing.B, repo *UserRepository, n int) {
b.Helper()
ctx := context.Background()
for i := 0; i < n; i++ {
if err := repo.Create(ctx, &domain.User{
Username: fmt.Sprintf("benchuser%06d", i),
Email: domain.StrPtr(fmt.Sprintf("bench%06d@example.com", i)),
Phone: domain.StrPtr(fmt.Sprintf("1380000%04d", i%10000)),
Password: "hashed_placeholder",
Status: domain.UserStatusActive,
}); err != nil {
b.Fatalf("seedUsers i=%d: %v", i, err)
}
}
}
// ---------- BenchmarkRepo_Create — 单条写入吞吐 ----------
func BenchmarkRepo_Create(b *testing.B) {
db := openBenchDB(b)
repo := NewUserRepository(db)
ctx := context.Background()
b.ResetTimer()
for i := 0; i < b.N; i++ {
repo.Create(ctx, &domain.User{ //nolint:errcheck
Username: fmt.Sprintf("cr_%d_%d", b.N, i),
Email: domain.StrPtr(fmt.Sprintf("cr_%d_%d@bench.com", b.N, i)),
Password: "hash",
Status: domain.UserStatusActive,
})
}
}
// ---------- BenchmarkRepo_BulkCreate — 批量写入(串行) ----------
func BenchmarkRepo_BulkCreate(b *testing.B) {
sizes := []int{10, 100, 500}
for _, size := range sizes {
size := size
b.Run(fmt.Sprintf("batch=%d", size), func(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
db := openBenchDB(b)
repo := NewUserRepository(db)
ctx := context.Background()
users := make([]*domain.User, size)
for j := 0; j < size; j++ {
users[j] = &domain.User{
Username: fmt.Sprintf("bulk_%d_%d_%d", i, j, size),
Password: "hash",
Status: domain.UserStatusActive,
}
}
b.StartTimer()
for _, u := range users {
repo.Create(ctx, u) //nolint:errcheck
}
}
})
}
}
// ---------- BenchmarkRepo_GetByID — 主键查询 ----------
func BenchmarkRepo_GetByID(b *testing.B) {
db := openBenchDB(b)
repo := NewUserRepository(db)
seedUsers(b, repo, 1000)
ctx := context.Background()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
id := int64(1)
for pb.Next() {
repo.GetByID(ctx, id) //nolint:errcheck
id++
if id > 1000 {
id = 1
}
}
})
}
// ---------- BenchmarkRepo_GetByUsername — 索引查询 ----------
func BenchmarkRepo_GetByUsername(b *testing.B) {
db := openBenchDB(b)
repo := NewUserRepository(db)
seedUsers(b, repo, 500)
ctx := context.Background()
b.ResetTimer()
for i := 0; i < b.N; i++ {
repo.GetByUsername(ctx, fmt.Sprintf("benchuser%06d", i%500)) //nolint:errcheck
}
}
// ---------- BenchmarkRepo_GetByEmail — 索引查询 ----------
func BenchmarkRepo_GetByEmail(b *testing.B) {
db := openBenchDB(b)
repo := NewUserRepository(db)
seedUsers(b, repo, 500)
ctx := context.Background()
b.ResetTimer()
for i := 0; i < b.N; i++ {
repo.GetByEmail(ctx, fmt.Sprintf("bench%06d@example.com", i%500)) //nolint:errcheck
}
}
// ---------- BenchmarkRepo_List — 分页列表 ----------
func BenchmarkRepo_List(b *testing.B) {
pageSizes := []int{10, 50, 200}
for _, ps := range pageSizes {
ps := ps
b.Run(fmt.Sprintf("pageSize=%d", ps), func(b *testing.B) {
db := openBenchDB(b)
repo := NewUserRepository(db)
seedUsers(b, repo, 1000)
ctx := context.Background()
b.ResetTimer()
for i := 0; i < b.N; i++ {
repo.List(ctx, 0, ps) //nolint:errcheck
}
})
}
}
// ---------- BenchmarkRepo_ListByStatus ----------
func BenchmarkRepo_ListByStatus(b *testing.B) {
db := openBenchDB(b)
repo := NewUserRepository(db)
seedUsers(b, repo, 1000)
ctx := context.Background()
b.ResetTimer()
for i := 0; i < b.N; i++ {
repo.ListByStatus(ctx, domain.UserStatusActive, 0, 20) //nolint:errcheck
}
}
// ---------- BenchmarkRepo_UpdateStatus ----------
func BenchmarkRepo_UpdateStatus(b *testing.B) {
db := openBenchDB(b)
repo := NewUserRepository(db)
seedUsers(b, repo, 200)
ctx := context.Background()
b.ResetTimer()
for i := 0; i < b.N; i++ {
id := int64(i%200) + 1
repo.UpdateStatus(ctx, id, domain.UserStatusActive) //nolint:errcheck
}
}
// ---------- BenchmarkRepo_Update — 全字段更新 ----------
func BenchmarkRepo_Update(b *testing.B) {
db := openBenchDB(b)
repo := NewUserRepository(db)
seedUsers(b, repo, 100)
ctx := context.Background()
b.ResetTimer()
for i := 0; i < b.N; i++ {
id := int64(i%100) + 1
u, err := repo.GetByID(ctx, id)
if err != nil {
continue
}
u.Nickname = fmt.Sprintf("nick%d", i)
repo.Update(ctx, u) //nolint:errcheck
}
}
// ---------- BenchmarkRepo_Delete — 软删除 ----------
func BenchmarkRepo_Delete(b *testing.B) {
ctx := context.Background()
b.ResetTimer()
for i := 0; i < b.N; i++ {
b.StopTimer()
db := openBenchDB(b)
repo := NewUserRepository(db)
repo.Create(ctx, &domain.User{Username: "victim", Password: "hash", Status: domain.UserStatusActive}) //nolint:errcheck
b.StartTimer()
repo.Delete(ctx, 1) //nolint:errcheck
}
}
// ---------- BenchmarkRepo_ExistsByUsername ----------
func BenchmarkRepo_ExistsByUsername(b *testing.B) {
db := openBenchDB(b)
repo := NewUserRepository(db)
seedUsers(b, repo, 500)
ctx := context.Background()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
i := 0
for pb.Next() {
repo.ExistsByUsername(ctx, fmt.Sprintf("benchuser%06d", i%500)) //nolint:errcheck
i++
}
})
}
// ---------- BenchmarkRepo_ConcurrentReadWrite — 高并发读写混合 ----------
func BenchmarkRepo_ConcurrentReadWrite(b *testing.B) {
db := openBenchDB(b)
repo := NewUserRepository(db)
seedUsers(b, repo, 200)
ctx := context.Background()
var mu sync.Mutex // SQLite 不支持多写并发,需要序列化写入
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
i := int64(1)
for pb.Next() {
if i%10 == 0 {
// 10% 写操作
mu.Lock()
repo.UpdateLastLogin(ctx, i%200+1, "10.0.0.1") //nolint:errcheck
mu.Unlock()
} else {
// 90% 读操作
repo.GetByID(ctx, i%200+1) //nolint:errcheck
}
i++
}
})
}
// ---------- BenchmarkRepo_Search — 模糊搜索 ----------
func BenchmarkRepo_Search(b *testing.B) {
db := openBenchDB(b)
repo := NewUserRepository(db)
seedUsers(b, repo, 2000)
ctx := context.Background()
b.ResetTimer()
keywords := []string{"benchuser000", "bench0001", "benchuser05"}
for i := 0; i < b.N; i++ {
repo.Search(ctx, keywords[i%len(keywords)], 0, 20) //nolint:errcheck
}
}