refactor(messaging): shift sequence generation responsibility to database transactions
Move the responsibility of sequence (`seq`) allocation from the application/cache layer to the database layer to ensure absolute consistency. Previously, sequences were pre-allocated via Redis/Lua, which introduced complexity in managing synchronization between the cache and the database. Key changes: - Implement `CreateMessageWithSeq` in `message_repo.go` using `SELECT FOR UPDATE` to lock the conversation row and atomically increment the `last_seq` within a single transaction. - Simplify `SeqBufferManager` by removing the complex local buffering and Lua-based pre-allocation logic, reverting to a simpler model. - Update `chat_service.go`, `group_service.go`, and `message_service.go` to remove manual sequence retrieval, relying instead on the repository's transactional allocation. - Introduce `SyncConvSeq` in `conversation_cache.go` to perform "write-through" updates to Redis, ensuring the cache remains synchronized with the database's source of truth. - Improve cold-start handling in `conversation_cache.go` with a new `syncSeqLua` script to prevent stale sequence reads.
This commit is contained in:
@@ -431,40 +431,43 @@ func (r *messageRepository) GetNextSeq(conversationID string) (int64, error) {
|
||||
return nextSeq, err
|
||||
}
|
||||
|
||||
// CreateMessageWithSeq 创建消息并更新seq(事务操作)
|
||||
// msg.Seq 应由调用方通过 Redis INCR 预分配
|
||||
// CreateMessageWithSeq 创建消息并在事务内原子分配 seq
|
||||
// DB 是 seq 的唯一真相来源:SELECT FOR UPDATE 锁行 → last_seq+1 → 写入消息 → 更新 last_seq
|
||||
// msg.Seq 会在事务内被设置,调用方无需预先分配
|
||||
func (r *messageRepository) CreateMessageWithSeq(msg *model.Message) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
// 创建消息(seq 已由调用方预分配)
|
||||
// 1. 锁定会话行,读取当前 last_seq
|
||||
var conv model.Conversation
|
||||
query := tx.Where("id = ?", msg.ConversationID)
|
||||
if tx.Dialector.Name() == "postgres" {
|
||||
query = query.Clauses(clause.Locking{Strength: "UPDATE"})
|
||||
}
|
||||
if err := query.First(&conv).Error; err != nil {
|
||||
return fmt.Errorf("lock conversation for seq: %w", err)
|
||||
}
|
||||
|
||||
// 2. 原子分配 seq
|
||||
msg.Seq = conv.LastSeq + 1
|
||||
|
||||
// 3. 创建消息
|
||||
if err := tx.Create(msg).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 更新会话last_seq:仅当新 seq 更大时才更新,防止并发写入导致 last_seq 回退
|
||||
result := tx.Model(&model.Conversation{}).
|
||||
Where("id = ? AND last_seq < ?", msg.ConversationID, msg.Seq).
|
||||
// 4. 更新会话 last_seq 和 last_msg_time
|
||||
if err := tx.Model(&model.Conversation{}).
|
||||
Where("id = ?", msg.ConversationID).
|
||||
Updates(map[string]any{
|
||||
"last_seq": msg.Seq,
|
||||
"last_msg_time": gorm.Expr("CURRENT_TIMESTAMP"),
|
||||
})
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
// 当 last_seq >= msg.Seq 时(并发写入已更新),仍需刷新 last_msg_time
|
||||
if err := tx.Model(&model.Conversation{}).
|
||||
Where("id = ? AND last_seq >= ?", msg.ConversationID, msg.Seq).
|
||||
Update("last_msg_time", gorm.Expr("CURRENT_TIMESTAMP")).Error; err != nil {
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 新消息到达后,自动恢复被"仅自己删除"的会话
|
||||
if err := tx.Model(&model.ConversationParticipant{}).
|
||||
// 5. 新消息到达后,自动恢复被"仅自己删除"的会话
|
||||
return tx.Model(&model.ConversationParticipant{}).
|
||||
Where("conversation_id = ?", msg.ConversationID).
|
||||
Update("hidden_at", nil).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
Update("hidden_at", nil).Error
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user