Files
user-system/internal/repository/repo_bench_test.go

306 lines
7.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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
}
}