perf(kv-client): use EVALSHA for rate limit scripts (#2130)

This commit is contained in:
Hampus authored and GitHub committed 2026-08-30 22:15:32 +02:00
1 parent e06436d6f5
commit 9d1733bdb3
5 files changed
+279 -2

No files matched your search

+32 -1
View File
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {createHash} from 'node:crypto';
import type {IKVPipeline, IKVProvider, IKVSubscription, KVRateLimitResult} from '@pkgs/kv_client/src/IKVProvider';
import {
type IKVLogger,
@@ -232,6 +233,8 @@ redis.call('DEL', KEYS[2])
return 1
`;
const SCRIPT_SHA_CACHE = new Map<string, string>();
interface ScriptPurgeBatchResult {
urls: Array<string>;
tokens: number;
@@ -699,7 +702,18 @@ export class KVClient implements IKVProvider {
keyCount: number,
...args: Array<string | number>
): Promise<unknown> {
return await this.execute(command, async () => this.client.eval(script, keyCount, ...args));
return await this.execute(command, async () => this.evalCachedScript(script, keyCount, args));
}
private async evalCachedScript(script: string, keyCount: number, args: Array<string | number>): Promise<unknown> {
try {
return await this.client.evalsha(getScriptSha(script), keyCount, ...args);
} catch (error) {
if (!isNoScriptError(error)) {
throw error;
}
return await this.client.eval(script, keyCount, ...args);
}
}
private async executeJsonScript<T>(
@@ -795,6 +809,23 @@ function normalizeRateLimitResult(result: KVRateLimitResult): KVRateLimitResult
};
}
function getScriptSha(script: string): string {
const cached = SCRIPT_SHA_CACHE.get(script);
if (cached !== undefined) {
return cached;
}
const sha = createHash('sha1').update(script).digest('hex');
SCRIPT_SHA_CACHE.set(script, sha);
return sha;
}
function isNoScriptError(error: unknown): boolean {
if (!(error instanceof Error)) {
return false;
}
return error.message.includes('NOSCRIPT');
}
function isTimeoutError(error: unknown): boolean {
if (!(error instanceof Error)) {
return false;