您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

clean_workflow_runlogs_precise.py 5.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import datetime
  2. import logging
  3. import time
  4. import click
  5. import app
  6. from configs import dify_config
  7. from extensions.ext_database import db
  8. from models.model import (
  9. AppAnnotationHitHistory,
  10. Conversation,
  11. Message,
  12. MessageAgentThought,
  13. MessageAnnotation,
  14. MessageChain,
  15. MessageFeedback,
  16. MessageFile,
  17. )
  18. from models.workflow import ConversationVariable, WorkflowAppLog, WorkflowNodeExecutionModel, WorkflowRun
  19. logger = logging.getLogger(__name__)
  20. MAX_RETRIES = 3
  21. BATCH_SIZE = dify_config.WORKFLOW_LOG_CLEANUP_BATCH_SIZE
  22. @app.celery.task(queue="dataset")
  23. def clean_workflow_runlogs_precise():
  24. """Clean expired workflow run logs with retry mechanism and complete message cascade"""
  25. click.echo(click.style("Start clean workflow run logs (precise mode with complete cascade).", fg="green"))
  26. start_at = time.perf_counter()
  27. retention_days = dify_config.WORKFLOW_LOG_RETENTION_DAYS
  28. cutoff_date = datetime.datetime.now() - datetime.timedelta(days=retention_days)
  29. try:
  30. total_workflow_runs = db.session.query(WorkflowRun).where(WorkflowRun.created_at < cutoff_date).count()
  31. if total_workflow_runs == 0:
  32. logger.info("No expired workflow run logs found")
  33. return
  34. logger.info("Found %s expired workflow run logs to clean", total_workflow_runs)
  35. total_deleted = 0
  36. failed_batches = 0
  37. batch_count = 0
  38. while True:
  39. workflow_runs = (
  40. db.session.query(WorkflowRun.id).where(WorkflowRun.created_at < cutoff_date).limit(BATCH_SIZE).all()
  41. )
  42. if not workflow_runs:
  43. break
  44. workflow_run_ids = [run.id for run in workflow_runs]
  45. batch_count += 1
  46. success = _delete_batch_with_retry(workflow_run_ids, failed_batches)
  47. if success:
  48. total_deleted += len(workflow_run_ids)
  49. failed_batches = 0
  50. else:
  51. failed_batches += 1
  52. if failed_batches >= MAX_RETRIES:
  53. logger.error("Failed to delete batch after %s retries, aborting cleanup for today", MAX_RETRIES)
  54. break
  55. else:
  56. # Calculate incremental delay times: 5, 10, 15 minutes
  57. retry_delay_minutes = failed_batches * 5
  58. logger.warning("Batch deletion failed, retrying in %s minutes...", retry_delay_minutes)
  59. time.sleep(retry_delay_minutes * 60)
  60. continue
  61. logger.info("Cleanup completed: %s expired workflow run logs deleted", total_deleted)
  62. except Exception:
  63. db.session.rollback()
  64. logger.exception("Unexpected error in workflow log cleanup")
  65. raise
  66. end_at = time.perf_counter()
  67. execution_time = end_at - start_at
  68. click.echo(click.style(f"Cleaned workflow run logs from db success latency: {execution_time:.2f}s", fg="green"))
  69. def _delete_batch_with_retry(workflow_run_ids: list[str], attempt_count: int) -> bool:
  70. """Delete a single batch with a retry mechanism and complete cascading deletion"""
  71. try:
  72. with db.session.begin_nested():
  73. message_data = (
  74. db.session.query(Message.id, Message.conversation_id)
  75. .where(Message.workflow_run_id.in_(workflow_run_ids))
  76. .all()
  77. )
  78. message_id_list = [msg.id for msg in message_data]
  79. conversation_id_list = list({msg.conversation_id for msg in message_data if msg.conversation_id})
  80. if message_id_list:
  81. db.session.query(AppAnnotationHitHistory).where(
  82. AppAnnotationHitHistory.message_id.in_(message_id_list)
  83. ).delete(synchronize_session=False)
  84. db.session.query(MessageAgentThought).where(MessageAgentThought.message_id.in_(message_id_list)).delete(
  85. synchronize_session=False
  86. )
  87. db.session.query(MessageChain).where(MessageChain.message_id.in_(message_id_list)).delete(
  88. synchronize_session=False
  89. )
  90. db.session.query(MessageFile).where(MessageFile.message_id.in_(message_id_list)).delete(
  91. synchronize_session=False
  92. )
  93. db.session.query(MessageAnnotation).where(MessageAnnotation.message_id.in_(message_id_list)).delete(
  94. synchronize_session=False
  95. )
  96. db.session.query(MessageFeedback).where(MessageFeedback.message_id.in_(message_id_list)).delete(
  97. synchronize_session=False
  98. )
  99. db.session.query(Message).where(Message.workflow_run_id.in_(workflow_run_ids)).delete(
  100. synchronize_session=False
  101. )
  102. db.session.query(WorkflowAppLog).where(WorkflowAppLog.workflow_run_id.in_(workflow_run_ids)).delete(
  103. synchronize_session=False
  104. )
  105. db.session.query(WorkflowNodeExecutionModel).where(
  106. WorkflowNodeExecutionModel.workflow_run_id.in_(workflow_run_ids)
  107. ).delete(synchronize_session=False)
  108. if conversation_id_list:
  109. db.session.query(ConversationVariable).where(
  110. ConversationVariable.conversation_id.in_(conversation_id_list)
  111. ).delete(synchronize_session=False)
  112. db.session.query(Conversation).where(Conversation.id.in_(conversation_id_list)).delete(
  113. synchronize_session=False
  114. )
  115. db.session.query(WorkflowRun).where(WorkflowRun.id.in_(workflow_run_ids)).delete(synchronize_session=False)
  116. db.session.commit()
  117. return True
  118. except Exception:
  119. db.session.rollback()
  120. logger.exception("Batch deletion failed (attempt %s)", attempt_count + 1)
  121. return False