refactor(knowledge): migrate deleted-item cleanup from polling scheduler to event-driven worker
Replaces the scheduler-polled KnowledgeDeletedCleanupWorker with an event-driven KnowledgeDeletedCleanupEventWorker in tenant-api; cleanup events are now enqueued transactionally at delete time instead of swept periodically. Also fixes statement_timeout parameterization in MonitoringRetentionWorker and propagates errors from cleanupDeletedKnowledgeItem. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -30,7 +30,6 @@ import (
|
||||
|
||||
const (
|
||||
defaultKnowledgeQueueSize = 128
|
||||
defaultKnowledgeCleanupPoll = 15 * time.Second
|
||||
defaultKnowledgeChildGroupName = "默认目录"
|
||||
maxKnowledgeSnippetsPerItem = 2
|
||||
maxKnowledgeTextNameRunes = 20
|
||||
@@ -59,11 +58,6 @@ type knowledgeParseJob struct {
|
||||
ParseTaskID int64
|
||||
}
|
||||
|
||||
type knowledgeCleanupJob struct {
|
||||
TenantID int64
|
||||
ItemID int64
|
||||
}
|
||||
|
||||
type knowledgeItemRecord struct {
|
||||
ID int64
|
||||
TenantID int64
|
||||
@@ -459,12 +453,12 @@ func (s *KnowledgeService) DeleteGroup(ctx context.Context, groupID int64) error
|
||||
return response.ErrInternal(50054, "knowledge_group_delete_failed", "failed to delete knowledge groups")
|
||||
}
|
||||
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return response.ErrInternal(50054, "knowledge_group_delete_failed", "failed to commit delete transaction")
|
||||
if err := s.enqueueDeletedKnowledgeCleanupEvents(ctx, tx, actor.TenantID, itemIDs); err != nil {
|
||||
return response.ErrInternal(50054, "knowledge_group_delete_failed", "failed to enqueue knowledge cleanup")
|
||||
}
|
||||
|
||||
for _, itemID := range itemIDs {
|
||||
go s.cleanupDeletedKnowledgeItemAsync(actor.TenantID, itemID)
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return response.ErrInternal(50054, "knowledge_group_delete_failed", "failed to commit delete transaction")
|
||||
}
|
||||
|
||||
if s.logger != nil {
|
||||
@@ -720,6 +714,10 @@ func (s *KnowledgeService) DeleteItem(ctx context.Context, itemID int64) error {
|
||||
return response.ErrInternal(50054, "knowledge_item_delete_failed", "failed to mark knowledge chunks for deletion")
|
||||
}
|
||||
|
||||
if err := s.enqueueDeletedKnowledgeCleanupEvents(ctx, tx, actor.TenantID, []int64{itemID}); err != nil {
|
||||
return response.ErrInternal(50054, "knowledge_item_delete_failed", "failed to enqueue knowledge cleanup")
|
||||
}
|
||||
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return response.ErrInternal(50054, "knowledge_item_delete_failed", "failed to commit delete transaction")
|
||||
}
|
||||
@@ -730,7 +728,6 @@ func (s *KnowledgeService) DeleteItem(ctx context.Context, itemID int64) error {
|
||||
zap.Int64("knowledge_item_id", itemID),
|
||||
)
|
||||
}
|
||||
go s.cleanupDeletedKnowledgeItemAsync(actor.TenantID, itemID)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2558,72 +2555,6 @@ func (s *KnowledgeService) filterActiveKnowledgeSnippets(
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
func (s *KnowledgeService) cleanupDeletedKnowledgeItemAsync(tenantID, itemID int64) {
|
||||
if !s.startCleanupJob(tenantID, itemID) {
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer s.finishCleanupJob(tenantID, itemID)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
defer cancel()
|
||||
s.cleanupDeletedKnowledgeItem(ctx, tenantID, itemID)
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *KnowledgeService) scheduleDeletedKnowledgeCleanup() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
jobs, err := s.listKnowledgeCleanupCandidates(ctx, 20)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, job := range jobs {
|
||||
s.cleanupDeletedKnowledgeItemAsync(job.TenantID, job.ItemID)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *KnowledgeService) RunDeletedCleanupSweep() {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
s.scheduleDeletedKnowledgeCleanup()
|
||||
}
|
||||
|
||||
func (s *KnowledgeService) listKnowledgeCleanupCandidates(
|
||||
ctx context.Context,
|
||||
limit int,
|
||||
) ([]knowledgeCleanupJob, error) {
|
||||
if limit <= 0 {
|
||||
limit = 20
|
||||
}
|
||||
|
||||
rows, err := s.pool.Query(ctx, `
|
||||
SELECT ki.tenant_id, ki.id
|
||||
FROM knowledge_items ki
|
||||
WHERE ki.deleted_at IS NOT NULL
|
||||
ORDER BY ki.updated_at ASC, ki.id ASC
|
||||
LIMIT $1
|
||||
`, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
jobs := make([]knowledgeCleanupJob, 0, limit)
|
||||
for rows.Next() {
|
||||
var job knowledgeCleanupJob
|
||||
if err := rows.Scan(&job.TenantID, &job.ItemID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
jobs = append(jobs, job)
|
||||
}
|
||||
return jobs, nil
|
||||
}
|
||||
|
||||
func (s *KnowledgeService) startCleanupJob(tenantID, itemID int64) bool {
|
||||
key := fmt.Sprintf("%d:%d", tenantID, itemID)
|
||||
|
||||
@@ -2645,10 +2576,10 @@ func (s *KnowledgeService) finishCleanupJob(tenantID, itemID int64) {
|
||||
s.cleanupMu.Unlock()
|
||||
}
|
||||
|
||||
func (s *KnowledgeService) cleanupDeletedKnowledgeItem(ctx context.Context, tenantID, itemID int64) {
|
||||
func (s *KnowledgeService) cleanupDeletedKnowledgeItem(ctx context.Context, tenantID, itemID int64) error {
|
||||
item, err := s.loadKnowledgeItemForCleanup(ctx, tenantID, itemID)
|
||||
if err != nil || item == nil {
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
errs := make([]string, 0, 2)
|
||||
@@ -2681,7 +2612,7 @@ func (s *KnowledgeService) cleanupDeletedKnowledgeItem(ctx context.Context, tena
|
||||
)
|
||||
}
|
||||
_ = s.setKnowledgeCleanupError(ctx, tenantID, itemID, strings.Join(errs, "; "))
|
||||
return
|
||||
return fmt.Errorf("knowledge cleanup failed: %s", strings.Join(errs, "; "))
|
||||
}
|
||||
|
||||
if err := s.deleteKnowledgeItemPermanently(ctx, tenantID, itemID); err != nil {
|
||||
@@ -2693,7 +2624,7 @@ func (s *KnowledgeService) cleanupDeletedKnowledgeItem(ctx context.Context, tena
|
||||
)
|
||||
}
|
||||
_ = s.setKnowledgeCleanupError(ctx, tenantID, itemID, err.Error())
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
if s.logger != nil {
|
||||
@@ -2703,6 +2634,7 @@ func (s *KnowledgeService) cleanupDeletedKnowledgeItem(ctx context.Context, tena
|
||||
)
|
||||
}
|
||||
_ = s.clearKnowledgeCleanupError(ctx, tenantID, itemID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *KnowledgeService) loadKnowledgeItemForCleanup(
|
||||
@@ -2732,7 +2664,10 @@ func (s *KnowledgeService) loadKnowledgeItemForCleanup(
|
||||
&record.ItemVersion,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
record.ContentText = contentText
|
||||
record.Markdown = markdownContent
|
||||
|
||||
Reference in New Issue
Block a user