优化数据库读写
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { api } from '../src/client/api.mjs';
|
||||
|
||||
const originalFetch = globalThis.fetch;
|
||||
let calls = 0;
|
||||
let release;
|
||||
globalThis.fetch = async () => {
|
||||
calls += 1;
|
||||
await new Promise(resolve => { release = resolve; });
|
||||
return new Response(JSON.stringify({ ok: true, calls }), { status: 200, headers: { 'Content-Type': 'application/json' } });
|
||||
};
|
||||
|
||||
try {
|
||||
const first = api('/api/large-ledger');
|
||||
const second = api('/api/large-ledger');
|
||||
await Promise.resolve();
|
||||
assert.equal(calls, 1, '并发的相同 GET 请求应只发送一次');
|
||||
release();
|
||||
assert.deepEqual(await first, { ok: true, calls: 1 });
|
||||
assert.deepEqual(await second, { ok: true, calls: 1 });
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
|
||||
console.log('API read deduplication tests passed');
|
||||
+23
-3
@@ -83,16 +83,35 @@ const silentLogger = { warn() {} };
|
||||
await cache.invalidate('results');
|
||||
assert.deepEqual(await cache.remember('results', 'candidate:1', loadResults), { version: 3 });
|
||||
assert.equal(resultLoads, 3, '后台手动刷新后应重新生成成绩缓存');
|
||||
|
||||
client.isReady = false;
|
||||
let fallbackLoads = 0;
|
||||
assert.equal(await cache.remember('public', 'runtime-fallback', async () => ++fallbackLoads), 1);
|
||||
assert.equal(await cache.remember('public', 'runtime-fallback', async () => ++fallbackLoads), 1);
|
||||
assert.equal(fallbackLoads, 1, 'Redis 运行中断开后,相同热点读取应由本机缓存合并');
|
||||
client.isReady = true;
|
||||
await cache.close();
|
||||
}
|
||||
|
||||
{
|
||||
const cache = await createRedisCache({ env: {}, logger: silentLogger });
|
||||
let loads = 0;
|
||||
await cache.remember('public', 'home', async () => ++loads);
|
||||
await cache.remember('public', 'home', async () => ++loads);
|
||||
assert.equal(loads, 2, '未配置 Redis 时应始终回源');
|
||||
assert.equal(await cache.remember('public', 'home', async () => ++loads), 1);
|
||||
assert.equal(await cache.remember('public', 'home', async () => ++loads), 1);
|
||||
assert.equal(loads, 1, '未配置 Redis 时应使用有界本机缓存,避免重复回源');
|
||||
await cache.invalidate('public');
|
||||
assert.equal(await cache.remember('public', 'home', async () => ++loads), 2);
|
||||
assert.equal(loads, 2, '本机缓存应在数据库写入后立即失效');
|
||||
assert.equal(cache.status, 'disabled');
|
||||
|
||||
let releaseOld;
|
||||
const oldLoad = cache.remember('public', 'race', () => new Promise(resolve => { releaseOld = () => resolve('old'); }));
|
||||
await cache.invalidate('public');
|
||||
const newLoad = cache.remember('public', 'race', async () => 'new');
|
||||
releaseOld();
|
||||
assert.equal(await oldLoad, 'old');
|
||||
assert.equal(await newLoad, 'new', '失效后不得等待失效前仍在运行的加载');
|
||||
assert.equal(await cache.remember('public', 'race', async () => 'unexpected'), 'new', '旧加载完成后不得覆盖新缓存');
|
||||
}
|
||||
|
||||
{
|
||||
@@ -104,6 +123,7 @@ const silentLogger = { warn() {} };
|
||||
});
|
||||
assert.equal(cache.status, 'unavailable');
|
||||
assert.equal(await cache.remember('public', 'home', async () => 'database'), 'database');
|
||||
assert.equal(await cache.remember('public', 'home', async () => 'unexpected'), 'database', 'Redis 故障时本机缓存应继续承接重复读取');
|
||||
}
|
||||
|
||||
console.log('Redis 缓存测试通过');
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { createStateCache } from '../src/database/state-cache.mjs';
|
||||
|
||||
let loads = 0;
|
||||
let release;
|
||||
const gate = new Promise(resolve => { release = resolve; });
|
||||
const coalesced = createStateCache({
|
||||
maxAgeMs: 30000,
|
||||
async load() {
|
||||
loads += 1;
|
||||
await gate;
|
||||
return { load: loads };
|
||||
}
|
||||
});
|
||||
const firstPending = coalesced.read();
|
||||
const secondPending = coalesced.read();
|
||||
release();
|
||||
const [first, second] = await Promise.all([firstPending, secondPending]);
|
||||
assert.equal(loads, 1, '并发冷读取应合并为一次全量加载');
|
||||
assert.strictEqual(first, second, '并发读取应共享同一份快照');
|
||||
assert.strictEqual(await coalesced.read(), first, '有效期内应直接复用内存快照');
|
||||
|
||||
coalesced.invalidate();
|
||||
const afterInvalidation = await coalesced.read();
|
||||
assert.equal(loads, 2, '应用写入失效后应重新加载');
|
||||
assert.notStrictEqual(afterInvalidation, first, '失效后不得继续返回旧快照');
|
||||
|
||||
let version = 1;
|
||||
let versionLoads = 0;
|
||||
const versioned = createStateCache({
|
||||
version: () => version,
|
||||
load: () => ({ load: ++versionLoads })
|
||||
});
|
||||
const versionOne = await versioned.read();
|
||||
assert.strictEqual(await versioned.read(), versionOne, '数据库版本未变化时应复用快照');
|
||||
version += 1;
|
||||
const versionTwo = await versioned.read();
|
||||
assert.equal(versionLoads, 2, '外部数据库版本变化后应重新加载');
|
||||
assert.notStrictEqual(versionTwo, versionOne, '外部写入后不得返回旧快照');
|
||||
|
||||
console.log('State cache tests passed');
|
||||
Reference in New Issue
Block a user