Dify 集成-数据库与缓存

1. 支持的服务/产品

组件 服务 状态 说明
主数据库 PostgreSQL ✅ 支持 12+ 版本,推荐 15+
缓存 Redis ✅ 支持 6.0+,支持单机/哨兵/集群
ORM SQLAlchemy ✅ 支持 2.x 版本
迁移 Flask-Migrate/Alembic ✅ 支持 数据库版本管理

2. 架构与组件交互

Dify 采用 PostgreSQL + Redis + Celery 的经典架构。PostgreSQL 作为主数据库提供持久化,Redis 提供缓存与消息队列支持,Celery 负责异步任务处理。

2.1 核心交互图

2.2 组件职责与使用场景

组件 角色 在 Dify 中的核心场景 交互关系
PostgreSQL Source of Truth 配置存储 : App, Dataset, Document 等核心实体 • 业务记录 : Message, Conversation, Run Logs • 关联映射: 向量库索引 ID 的映射 供 API 和 Celery 读写,保证数据一致性。
Redis 高速枢纽 Celery Broker : 异步任务消息队列 • 分布式锁 : 索引构建、应用更新时的并发控制 • 租户队列 : 通过 TenantIsolatedTaskQueue 实现租户级任务限流 • Cache: 接口响应缓存、Session 存储 连接 API 与 Worker,协调并发。
Celery 异步执行器 RAG 流程 : 文件解析 (Parsing)、切片 (Chunking)、向量化 (Embedding) • Agent 执行 : 运行耗时较长的工作流节点 • 系统任务: 邮件发送、数据清理 消费 Redis 中的任务,处理后更新 PG 状态。

2.3 关键交互流程:文档索引

流程说明

  1. API 层 (步骤 1-2):

    • 将文档元数据写入 PostgreSQL,初始状态为 queuing
    • 通过 shared_task.delay() 将任务发布到 Redis (Celery Broker)。
  2. 任务调度 (步骤 3-5):

    • Worker 从 Redis 获取任务。
    • 检查 TenantIsolatedTaskQueue (Redis 中的 List + TTL Key),确保该租户的并发任务数未超过限制 (TENANT_ISOLATED_TASK_CONCURRENCY)。
  3. 文档处理 (步骤 6-16):

    • Worker 从 PostgreSQL 读取完整的文档上下文。
    • 依次执行 解析 (Parsing) → 切片 (Splitting) → 向量化 (Indexing)
    • 关键优化 : Embedding 结果直接缓存在 PostgreSQL 的 embeddings 表中(而非 Redis),避免重复调用 LLM API。
    • 最终将向量写入外部向量数据库,并更新 PostgreSQL 中的文档状态。

2.4 PostgreSQL 在 Dify 中的使用场景

📌 源代码引用 : api/models/, api/core/rag/embedding/cached_embedding.py

场景分类 具体表/模型 用途 关键特性
核心实体 accounts, tenants 用户与租户信息 提供身份认证与多租户隔离
datasets, documents, document_segments 知识库与文档数据 存储 RAG 流程的元数据与切片
apps, workflows 应用与工作流配置 存储 Agent 编排逻辑
运行记录 messages, conversations 对话历史 支持上下文管理与追溯
workflow_runs, workflow_node_executions 工作流执行日志 记录每个节点的输入输出
索引映射 dataset_keyword_tables 全文索引关联 关联向量数据库中的索引 ID
缓存优化 embeddings Embedding 缓存 缓存文本向量,避免重复调用 LLM

关键设计

  • Embedding 缓存使用 PostgreSQL 而非 Redis :因为向量数据体积较大,存储在数据库中便于持久化和定期清理(通过 Celery 定时任务 clean_embedding_cache_task)。
  • 事务保证 :通过 SQLAlchemy 的 db.session 确保文档状态更新的原子性。

2.5 Redis 在 Dify 中的使用场景

📌 源代码引用 : api/extensions/ext_redis.py, api/core/rag/pipeline/queue.py

场景分类 Redis 数据结构 用途 代码实现
Celery Broker String/List 存储异步任务消息 CELERY_BROKER_URL=redis://...
租户任务队列 List + Key 实现 TenantIsolatedTaskQueue,限制单租户并发索引数 redis_client.lpush() + redis_client.setex()
分布式锁 String (SET NX) 防止并发操作(如同时更新同一应用) redis_client.lock()
会话存储 Hash 存储用户 Session(如有配置) Flask-Session 集成
Pub/Sub Pub/Sub 实时消息推送(如工作流状态变更通知) redis_client.publish()

关键设计

  • 租户隔离机制 :每个租户有独立的队列 tenant_self_document_indexing_task_queue:{tenant_id},配合 TTL Key tenant_document_indexing_task:{tenant_id} 实现并发控制。
  • 支持多种部署模式 :单机、哨兵 (Sentinel)、集群 (Cluster),通过 RedisClientWrapper 统一封装。

2.6 Celery 在 Dify 中的使用场景

📌 源代码引用 : api/tasks/, api/extensions/ext_celery.py

任务类别 典型任务 队列名称 触发方式
RAG 索引 normal_document_indexing_task dataset API 调用 .delay()
priority_document_indexing_task priority_dataset 付费用户优先处理
工作流执行 async_workflow_tasks workflow 异步工作流节点
邮件通知 send_invite_member_mail_task mail 用户邀请、密码重置等
定时清理 clean_embedding_cache_task dataset Celery Beat 定时 (每月 2 日凌晨 2 点)
clean_unused_datasets_task dataset 清理未使用的知识库

队列设计

  • 优先级队列priority_dataset 队列用于付费用户,Worker 优先消费该队列。
  • 隔离策略mail 队列独立运行,避免大量邮件任务阻塞核心业务。

定时任务 (Celery Beat)

  • 通过 crontab 配置周期性任务,如每月清理过期的 Embedding 缓存。
  • 所有定时任务的开关在 dify_config 中控制(如 ENABLE_CLEAN_EMBEDDING_CACHE_TASK)。

3. PostgreSQL 集成

3.1 配置

⚠️ 配置示例: 以下为环境变量配置示例

bash 复制代码
# 数据库连接配置
DB_USERNAME=postgres
DB_PASSWORD=your_password
DB_HOST=db
DB_PORT=5432
DB_DATABASE=dify

# 连接池配置
SQLALCHEMY_POOL_SIZE=30
SQLALCHEMY_POOL_RECYCLE=3600
SQLALCHEMY_ECHO=false

3.2 连接管理

📌 源代码引用:

python 复制代码
# api/models/engine.py - 源代码引用
from sqlalchemy.orm import DeclarativeBase, MappedAsDataclass

class Base(MappedAsDataclass, DeclarativeBase):
    pass

db = SQLAlchemy(model_class=Base)

# api/extensions/ext_database.py - 概念性示例
def init_app(app):
    db.init_app(app)
    
    # 配置连接池
    app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {
        'pool_size': 30,
        'pool_recycle': 3600,
        'pool_pre_ping': True,
    }

3.3 模型定义

⚠️ 概念性示例: 以下代码为演示 SQLAlchemy 模型定义的概念性示例

📌 实际项目模型可参考: api/models/

python 复制代码
from models.engine import db

class Account(db.Model):
    __tablename__ = 'accounts'
    
    id = db.Column(db.String(255), primary_key=True)
    name = db.Column(db.String(255), nullable=False)
    email = db.Column(db.String(255), unique=True, nullable=False)
    created_at = db.Column(db.DateTime, default=db.func.now())

3.4 查询操作

⚠️ 概念性示例: 以下代码为演示 SQLAlchemy 查询操作的概念性示例

python 复制代码
# 查询
account = db.session.query(Account).filter_by(email=email).first()

# 插入
new_account = Account(id=id, name=name, email=email)
db.session.add(new_account)
db.session.commit()

# 更新
account.name = new_name
db.session.commit()

# 删除
db.session.delete(account)
db.session.commit()

4. Redis 集成

4.1 配置

⚠️ 配置示例: 以下为环境变量配置示例

bash 复制代码
# Redis 单机模式
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_DB=0
REDIS_PASSWORD=your_password
REDIS_USE_SSL=false

# Redis 哨兵模式
REDIS_USE_SENTINEL=true
REDIS_SENTINELS=sentinel1:26379,sentinel2:26379
REDIS_SENTINEL_SERVICE_NAME=mymaster
REDIS_SENTINEL_USERNAME=sentinel_user
REDIS_SENTINEL_PASSWORD=sentinel_password

# Redis 集群模式
REDIS_USE_CLUSTER=true
REDIS_CLUSTER_NODES=node1:6379,node2:6379,node3:6379

4.2 连接管理

📌 源代码引用 : api/extensions/ext_redis.py

python 复制代码
# api/extensions/ext_redis.py - 源代码引用(简化版)
import redis
from redis.sentinel import Sentinel
from redis.cluster import RedisCluster

class RedisClientWrapper:
    """Redis client wrapper supporting multiple connection modes"""
    
    def __init__(self) -> None:
        self._client = None
    
    def initialize(self, client: Union[redis.Redis, RedisCluster]) -> None:
        """Initialize the Redis client"""
        if self._client is not None:
            raise RuntimeError("Redis client has already been initialized")
        self._client = client
    
    def __getattr__(self, item):
        if self._client is None:
            raise RuntimeError("Redis client is not initialized")
        return getattr(self._client, item)

redis_client = RedisClientWrapper()

4.3 使用示例

⚠️ 概念性示例: 以下代码为演示 Redis 操作的概念性示例

python 复制代码
from extensions.ext_redis import redis_client

# 字符串操作
redis_client.set('key', 'value')
value = redis_client.get('key')

# 设置过期时间
redis_client.setex('key', 3600, 'value')  # 1小时过期

# Hash 操作
redis_client.hset('user:123', 'name', 'John')
redis_client.hget('user:123', 'name')

# List 操作
redis_client.lpush('queue', 'task1')
task = redis_client.rpop('queue')

# Set 操作
redis_client.sadd('tags', 'python', 'flask')
members = redis_client.smembers('tags')

# 分布式锁
lock = redis_client.lock('my_lock', timeout=10)
if lock.acquire(blocking=False):
    try:
        # 执行需要加锁的操作
        pass
    finally:
        lock.release()

5. Celery 集成

5.1 配置

⚠️ 配置示例: 以下为环境变量配置示例

bash 复制代码
# Celery Broker (Redis)
CELERY_BROKER_URL=redis://redis:6379/1

# Celery Backend (可选)
CELERY_RESULT_BACKEND=redis://redis:6379/2

5.2 任务定义

📌 源代码引用 : api/extensions/ext_celery.py - Celery 实例定义

⚠️ 概念性示例 : 以下任务定义为概念性示例,实际任务定义见各模块的 tasks.py 文件

python 复制代码
# api/extensions/ext_celery.py - 源代码引用
from celery import Celery

celery = Celery(__name__)

# 概念性示例:异步任务定义
@celery.task
def process_document(document_id):
    """异步处理文档"""
    # 处理逻辑
    pass

# 调用任务
process_document.delay(document_id='doc_123')

6. 数据迁移

📌 实际命令: 以下为项目中实际使用的迁移命令

6.1 创建迁移

bash 复制代码
# 创建新迁移
uv run --project api flask db migrate -m "Add new column"

# 应用迁移
uv run --project api flask db upgrade

# 回滚迁移
uv run --project api flask db downgrade

6.2 迁移文件

⚠️ 概念性示例: 以下代码为演示迁移文件结构的概念性示例

📌 实际迁移文件见: api/migrations/versions/

python 复制代码
# api/migrations/versions/xxx_add_column.py
def upgrade():
    op.add_column('accounts', 
        sa.Column('phone', sa.String(20), nullable=True)
    )

def downgrade():
    op.drop_column('accounts', 'phone')

7. 性能优化

⚠️ 概念性示例: 本节所有代码为演示性能优化模式的概念性示例

7.1 数据库优化

python 复制代码
# 使用索引
class Account(db.Model):
    email = db.Column(db.String(255), index=True)
    created_at = db.Column(db.DateTime, index=True)

# 批量查询
accounts = db.session.query(Account).filter(
    Account.id.in_(account_ids)
).all()

# 预加载关联对象
from sqlalchemy.orm import joinedload
accounts = db.session.query(Account).options(
    joinedload(Account.tenant)
).all()

7.2 Redis 缓存模式

python 复制代码
def get_user_with_cache(user_id):
    """带缓存的用户查询"""
    cache_key = f"user:{user_id}"
    
    # 尝试从缓存获取
    cached = redis_client.get(cache_key)
    if cached:
        return json.loads(cached)
    
    # 从数据库查询
    user = db.session.query(User).get(user_id)
    
    # 写入缓存
    redis_client.setex(cache_key, 3600, json.dumps(user.to_dict()))
    
    return user

8. 监控与日志

⚠️ 概念性示例: 以下代码为演示监控与日志模式的概念性示例

python 复制代码
# 数据库慢查询日志
import logging
from sqlalchemy import event
from sqlalchemy.engine import Engine

logger = logging.getLogger(__name__)

@event.listens_for(Engine, "before_cursor_execute")
def before_cursor_execute(conn, cursor, statement, parameters, context, executemany):
    conn.info.setdefault('query_start_time', []).append(time.time())

@event.listens_for(Engine, "after_cursor_execute")
def after_cursor_execute(conn, cursor, statement, parameters, context, executemany):
    total = time.time() - conn.info['query_start_time'].pop(-1)
    if total > 1.0:  # 慢查询阈值 1 秒
        logger.warning(f"Slow query ({total:.2f}s): {statement}")
相关推荐
栗子叶2 小时前
深入理解 MySQL 半同步复制:AFTER_SYNC 为何能避免主从同步数据丢失?
数据库·mysql·adb·高可用·主从同步
我科绝伦(Huanhuan Zhou)2 小时前
MySQL主主复制管理器(MMM):技术原理与实践架构解析
数据库·mysql·架构
步步为营DotNet2 小时前
深度解析.NET 中IAsyncEnumerable:异步迭代的高效实现与应用】
服务器·数据库·.net
mpHH2 小时前
postgresql 执行器中readme的翻译
数据库·学习·postgresql
萧曵 丶2 小时前
覆盖索引与回表(MySQL 索引核心概念,性能优化关键)
数据库·mysql·性能优化·索引·聚簇索引
霖霖总总2 小时前
[小技巧24]MySQL 命令行提示符(Prompt)自定义:从入门到精通
数据库·mysql
石像鬼₧魂石2 小时前
3306 端口(MySQL 数据库)渗透测试全流程学习总结
数据库·学习·mysql
程序媛哪有这么可爱!2 小时前
【删除远程服务器vscode缓存】
服务器·人工智能·vscode·缓存·边缘计算