gomog/RELOAD_FIX.md

7.5 KiB
Raw Blame History

服务器重启数据加载修复说明

问题描述

服务器重启后没有正确载入底层数据库中的数据,导致内存存储为空。

根本原因

在之前的实现中,服务器启动时只创建了空的 MemoryStore,但没有从数据库中加载已有的数据到内存中。这导致:

  1. 服务器重启后,内存中的数据丢失
  2. 查询操作无法找到已有数据
  3. 插入操作可能产生重复数据

修复方案

1. 添加 ListCollections 方法到数据库适配器

为所有数据库适配器实现了 ListCollections 方法,用于获取数据库中所有现有的表(集合):

文件修改:

  • internal/database/base.go - 添加基础方法声明
  • internal/database/sqlite/adapter.go - SQLite 实现
  • internal/database/postgres/adapter.go - PostgreSQL 实现
  • internal/database/dm8/adapter.go - DM8 实现

SQLite 示例代码:

// ListCollections 获取所有集合(表)列表
func (a *SQLiteAdapter) ListCollections(ctx context.Context) ([]string, error) {
	query := `SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name`
	rows, err := a.GetDB().QueryContext(ctx, query)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var tables []string
	for rows.Next() {
		var table string
		if err := rows.Scan(&table); err != nil {
			return nil, err
		}
		tables = append(tables, table)
	}

	return tables, rows.Err()
}

2. 添加 Initialize 方法到 MemoryStore

internal/engine/memory_store.go 中添加了 Initialize 方法:

// Initialize 从数据库加载所有现有集合到内存
func (ms *MemoryStore) Initialize(ctx context.Context) error {
	if ms.adapter == nil {
		log.Println("[INFO] No database adapter, skipping initialization")
		return nil
	}

	// 获取所有现有集合
	tables, err := ms.adapter.ListCollections(ctx)
	if err != nil {
		// 如果 ListCollections 未实现,返回 nil不加载
		if err.Error() == "not implemented" {
			log.Println("[WARN] ListCollections not implemented, skipping initialization")
			return nil
		}
		return fmt.Errorf("failed to list collections: %w", err)
	}

	log.Printf("[INFO] Found %d collections in database", len(tables))

	// 逐个加载集合
	loadedCount := 0
	for _, tableName := range tables {
		// 从数据库加载所有文档
		docs, err := ms.adapter.FindAll(ctx, tableName)
		if err != nil {
			log.Printf("[WARN] Failed to load collection %s: %v", tableName, err)
			continue
		}

		// 创建集合并加载文档
		ms.mu.Lock()
		coll := &Collection{
			name:      tableName,
			documents: make(map[string]types.Document),
		}
		for _, doc := range docs {
			coll.documents[doc.ID] = doc
		}
		ms.collections[tableName] = coll
		ms.mu.Unlock()

		loadedCount++
		log.Printf("[DEBUG] Loaded collection %s with %d documents", tableName, len(docs))
	}

	log.Printf("[INFO] Successfully loaded %d collections from database", loadedCount)
	return nil
}

3. 在服务器启动时调用初始化

修改 cmd/server/main.go,在创建内存存储后立即调用初始化:

// 创建内存存储
store := engine.NewMemoryStore(adapter)

// 从数据库加载现有数据到内存
log.Println("[INFO] Initializing memory store from database...")
if err := store.Initialize(ctx); err != nil {
	log.Printf("[WARN] Failed to initialize memory store: %v", err)
	// 不阻止启动,继续运行
}

// 创建 CRUD 处理器
crud := engine.NewCRUDHandler(store, adapter)

工作流程

服务器启动流程:
1. 连接数据库
   ↓
2. 创建 MemoryStore
   ↓
3. 【新增】调用 Initialize() 从数据库加载数据
   ↓
4. 创建 CRUDHandler
   ↓
5. 启动 HTTP/TCP 服务器

测试方法

快速测试(推荐)

cd /home/kingecg/code/gomog
./test_quick.sh

预期输出:

✅ 成功!服务器重启后正确加载了数据库中的数据
=== 测试结果SUCCESS ===

详细测试

./test_reload_simple.sh

手动测试

  1. 启动服务器并插入数据
./bin/gomog -config config.yaml

# 在另一个终端插入数据
curl -X POST http://localhost:8080/api/v1/testdb/users/insert \
  -H "Content-Type: application/json" \
  -d '{"documents": [{"name": "Alice", "age": 30}]}'
  1. 验证数据已存入数据库
sqlite3 gomog.db "SELECT * FROM users;"
  1. 停止并重启服务器
# Ctrl+C 停止服务器
./bin/gomog -config config.yaml
  1. 查询数据(验证是否加载)
curl -X POST http://localhost:8080/api/v1/testdb/users/find \
  -H "Content-Type: application/json" \
  -d '{"filter": {}}'

应该能看到之前插入的数据。

日志输出示例

成功的初始化日志:

2026/03/14 22:00:00 [INFO] Connected to sqlite database
2026/03/14 22:00:00 [INFO] Initializing memory store from database...
2026/03/14 22:00:00 [INFO] Found 1 collections in database
2026/03/14 22:00:00 [DEBUG] Loaded collection users with 2 documents
2026/03/14 22:00:00 [INFO] Successfully loaded 1 collections from database
2026/03/14 22:00:00 Starting HTTP server on :8080
2026/03/14 22:00:00 Gomog server started successfully

关键技术细节

集合名称映射

由于 HTTP API 使用 dbName.collection 格式(如 testdb.users),而 SQLite 数据库中表名不带前缀(如 users),实现了智能名称映射:

1. Initialize 方法加载数据

// 从数据库加载时使用纯表名例如users
ms.collections[tableName] = coll

2. GetCollection 方法支持两种查找方式

// 首先尝试完整名称例如testdb.users
coll, exists := ms.collections[name]
if exists {
    return coll, nil
}

// 如果找不到尝试去掉数据库前缀例如users
if idx := strings.Index(name, "."); idx > 0 {
    tableName := name[idx+1:]
    coll, exists = ms.collections[tableName]
    if exists {
        return coll, nil
    }
}

这样确保了:

  • 新插入的数据可以使用 testdb.users 格式
  • 重启后加载的数据也能通过 testdb.users 查询到
  • 向后兼容,不影响现有功能

容错处理

修复实现了多层容错机制:

  1. 无数据库适配器:如果未配置数据库,跳过初始化
  2. ListCollections 未实现:如果数据库不支持列出表,记录警告但不阻止启动
  3. 单个集合加载失败:记录错误但继续加载其他集合
  4. 初始化失败:记录错误但服务器继续运行(不会崩溃)

影响范围

  • 服务器重启后数据自动恢复
  • HTTP API 和 TCP 协议行为一致
  • 支持 SQLite、PostgreSQL、DM8 三种数据库
  • 向后兼容,不影响现有功能
  • 优雅降级,初始化失败不影响服务器启动

相关文件清单

修改的文件

  • internal/engine/memory_store.go - 添加 Initialize 方法
  • internal/database/base.go - 添加 ListCollections 基础方法
  • internal/database/sqlite/adapter.go - SQLite 实现
  • internal/database/postgres/adapter.go - PostgreSQL 实现
  • internal/database/dm8/adapter.go - DM8 实现
  • cmd/server/main.go - 启动时调用初始化

新增的文件

  • test_reload.sh - 自动化测试脚本
  • RELOAD_FIX.md - 本文档

后续优化建议

  1. 增量加载:对于大数据量场景,可以考虑分页加载
  2. 懒加载:只在第一次访问集合时才从数据库加载
  3. 并发加载:并行加载多个集合以提高启动速度
  4. 加载进度监控:添加更详细的进度日志和指标