gomog/RELOAD_FIX.md

279 lines
7.5 KiB
Markdown
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.

# 服务器重启数据加载修复说明
## 问题描述
服务器重启后没有正确载入底层数据库中的数据,导致内存存储为空。
## 根本原因
在之前的实现中,服务器启动时只创建了空的 `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 示例代码:**
```go
// 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` 方法:
```go
// 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`,在创建内存存储后立即调用初始化:
```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 服务器
```
## 测试方法
### 快速测试(推荐)
```bash
cd /home/kingecg/code/gomog
./test_quick.sh
```
**预期输出:**
```
✅ 成功!服务器重启后正确加载了数据库中的数据
=== 测试结果SUCCESS ===
```
### 详细测试
```bash
./test_reload_simple.sh
```
### 手动测试
1. **启动服务器并插入数据**
```bash
./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}]}'
```
2. **验证数据已存入数据库**
```bash
sqlite3 gomog.db "SELECT * FROM users;"
```
3. **停止并重启服务器**
```bash
# Ctrl+C 停止服务器
./bin/gomog -config config.yaml
```
4. **查询数据(验证是否加载)**
```bash
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 方法加载数据**
```go
// 从数据库加载时使用纯表名例如users
ms.collections[tableName] = coll
```
**2. GetCollection 方法支持两种查找方式**
```go
// 首先尝试完整名称例如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. **加载进度监控**:添加更详细的进度日志和指标