Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(api): optimize token read/write during api call #9951

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions api/controllers/console/apikey.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from . import api
from .setup import setup_required
from .wraps import account_initialization_required
from ..service_api.wraps import get_api_token_from_db

api_key_fields = {
"id": fields.String,
Expand Down Expand Up @@ -137,6 +138,25 @@ def after_request(self, resp):
resp.headers["Access-Control-Allow-Credentials"] = "true"
return resp

def delete(self, resource_id, api_key_id):
crazywoola marked this conversation as resolved.
Show resolved Hide resolved
key = (
db.session.query(ApiToken)
.filter(
getattr(ApiToken, self.resource_id_field) == str(resource_id),
ApiToken.type == self.resource_type,
ApiToken.id == str(api_key_id),
)
.first()
)

response = super().delete(resource_id, api_key_id)

if response[1] == 204 and key:
from extensions.ext_redis import cache
cache.delete_memoized(get_api_token_from_db, key.token, "app")

return response

resource_type = "app"
resource_model = App
resource_id_field = "app_id"
Expand All @@ -160,6 +180,25 @@ def after_request(self, resp):
resp.headers["Access-Control-Allow-Credentials"] = "true"
return resp

def delete(self, resource_id, api_key_id):
key = (
db.session.query(ApiToken)
.filter(
getattr(ApiToken, self.resource_id_field) == str(resource_id),
ApiToken.type == self.resource_type,
ApiToken.id == str(api_key_id),
)
.first()
)

response = super().delete(resource_id, api_key_id)

if response[1] == 204 and key:
from extensions.ext_redis import cache
cache.delete_memoized(get_api_token_from_db, key.token, "dataset")

return response

resource_type = "dataset"
resource_model = Dataset
resource_id_field = "dataset_id"
Expand Down
34 changes: 24 additions & 10 deletions api/controllers/service_api/wraps.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
from functools import wraps
from typing import Optional

from celery import shared_task
from flask import current_app, request
from flask_login import user_logged_in
from flask_restful import Resource
from pydantic import BaseModel
from werkzeug.exceptions import Forbidden, Unauthorized

from extensions.ext_database import db
from extensions.ext_redis import cache
from libs.login import _get_user
from models.account import Account, Tenant, TenantAccountJoin, TenantStatus
from models.model import ApiToken, App, EndUser
Expand Down Expand Up @@ -172,6 +174,26 @@ def decorated(*args, **kwargs):
return decorator


@cache.memoize(timeout=3600)
def get_api_token_from_db(auth_token, scope):
return db.session.query(ApiToken).filter(
ApiToken.token == auth_token,
ApiToken.type == scope
).first()


@shared_task(queue="ops_trace", bind=True, max_retries=3)
def update_token_last_used_at(self, token_id, last_used_time):
try:
with db.session.begin():
token = db.session.query(ApiToken).get(token_id)
if token:
token.last_used_at = last_used_time
except Exception as e:
# 如果失败,最多重试3次
crazywoola marked this conversation as resolved.
Show resolved Hide resolved
self.retry(exc=e, countdown=60) # 1分钟后重试


def validate_and_get_api_token(scope=None):
"""
Validate and get API token.
Expand All @@ -186,20 +208,12 @@ def validate_and_get_api_token(scope=None):
if auth_scheme != "bearer":
raise Unauthorized("Authorization scheme must be 'Bearer'")

api_token = (
db.session.query(ApiToken)
.filter(
ApiToken.token == auth_token,
ApiToken.type == scope,
)
.first()
)
api_token = get_api_token_from_db(auth_token, scope)

if not api_token:
raise Unauthorized("Access token is invalid")

api_token.last_used_at = datetime.now(timezone.utc).replace(tzinfo=None)
db.session.commit()
update_token_last_used_at.delay(api_token.id, datetime.now(timezone.utc).replace(tzinfo=None))

return api_token

Expand Down
21 changes: 21 additions & 0 deletions api/extensions/ext_redis.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import redis
from redis.connection import Connection, SSLConnection
from redis.sentinel import Sentinel
from flask_caching import Cache

from configs import dify_config

Expand Down Expand Up @@ -40,6 +41,7 @@ def __getattr__(self, item):


redis_client = RedisClientWrapper()
cache = Cache()


def init_app(app):
Expand Down Expand Up @@ -71,6 +73,14 @@ def init_app(app):
)
master = sentinel.master_for(dify_config.REDIS_SENTINEL_SERVICE_NAME, **redis_params)
redis_client.initialize(master)

cache_config = {
'CACHE_TYPE': 'redis',
'CACHE_REDIS_SENTINELS': app.config.get('REDIS_SENTINELS'),
'CACHE_REDIS_SENTINEL_MASTER': app.config.get('REDIS_SENTINEL_SERVICE_NAME'),
'CACHE_REDIS_SENTINEL_PASSWORD': app.config.get('REDIS_SENTINEL_PASSWORD'),
'CACHE_DEFAULT_TIMEOUT': 3600
}
else:
redis_params.update(
{
Expand All @@ -82,4 +92,15 @@ def init_app(app):
pool = redis.ConnectionPool(**redis_params)
redis_client.initialize(redis.Redis(connection_pool=pool))

cache_config = {
'CACHE_TYPE': 'redis',
'CACHE_REDIS_HOST': app.config.get('REDIS_HOST'),
'CACHE_REDIS_PORT': app.config.get('REDIS_PORT'),
'CACHE_REDIS_PASSWORD': app.config.get('REDIS_PASSWORD'),
'CACHE_REDIS_DB': app.config.get('REDIS_DB'),
'CACHE_DEFAULT_TIMEOUT': 3600
}

cache.init_app(app, config=cache_config)

app.extensions["redis"] = redis_client
46 changes: 45 additions & 1 deletion api/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions api/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ zhipuai = "~2.1.5"
# Related transparent dependencies with pinned version
# required by main implementations
############################################################
flask-caching = "^2.3.0"
[tool.poetry.group.indirect.dependencies]
kaleido = "0.2.1"
rank-bm25 = "~0.2.2"
Expand Down
Loading