Skip to content

Commit b9a1462

Browse files
committed
Fixed kvCache for DocLoaders reading from kv store
Fedify has several context documents built in and stored in-memory. By default the DocumentLoader will read from memory, but when it is wrapped with the kvCache, we end up reading from the kv store rather than memory which generates a lot of overhead for stores backed by an external db like Redis or MySQL.
1 parent 638b9ca commit b9a1462

File tree

3 files changed

+102
-0
lines changed

3 files changed

+102
-0
lines changed

CHANGES.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@
33
Fedify changelog
44
================
55

6+
Version 1.7.8
7+
-------------
8+
9+
Released on August 5, 2025.
10+
11+
- Updated `kvCache` wrapper to read from preloaded contexts rather than
12+
from the KvStore. This saves network and disk overheads when parsing
13+
activities and objects using the JSON-LD library.
14+
15+
616
Version 1.7.7
717
-------------
818

fedify/runtime/docloader.test.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { mockDocumentLoader } from "../testing/docloader.ts";
88
import { test } from "../testing/mod.ts";
99
import preloadedContexts from "./contexts.ts";
1010
import {
11+
type DocumentLoader,
1112
FetchError,
1213
getDocumentLoader,
1314
getUserAgent,
@@ -516,6 +517,89 @@ test("kvCache()", async (t) => {
516517
},
517518
});
518519
});
520+
521+
await t.step("preloaded contexts bypass cache", async () => {
522+
const kv = new MemoryKvStore();
523+
let loaderCalled = false;
524+
const mockLoader: DocumentLoader = (url: string) => {
525+
loaderCalled = true;
526+
return Promise.resolve({
527+
contextUrl: null,
528+
documentUrl: url,
529+
document: { "mock": "document" },
530+
});
531+
};
532+
533+
const loader = kvCache({
534+
kv,
535+
loader: mockLoader,
536+
prefix: ["_test", "preloaded"],
537+
});
538+
539+
// Test that preloaded context URLs return preloaded data without calling the wrapped loader
540+
const activityStreamsUrl = "https://www.w3.org/ns/activitystreams";
541+
loaderCalled = false;
542+
const result = await loader(activityStreamsUrl);
543+
544+
assertEquals(result, {
545+
contextUrl: null,
546+
documentUrl: activityStreamsUrl,
547+
document: preloadedContexts[activityStreamsUrl],
548+
});
549+
assertEquals(
550+
loaderCalled,
551+
false,
552+
"Loader should not be called for preloaded contexts",
553+
);
554+
555+
// Verify that the preloaded context was not cached in KV store
556+
const cachedValue = await kv.get([
557+
"_test",
558+
"preloaded",
559+
activityStreamsUrl,
560+
]);
561+
assertEquals(
562+
cachedValue,
563+
undefined,
564+
"Preloaded contexts should not be cached in KV store",
565+
);
566+
567+
// Test another preloaded context
568+
const securityUrl = "https://w3id.org/security/v1";
569+
loaderCalled = false;
570+
const result2 = await loader(securityUrl);
571+
572+
assertEquals(result2, {
573+
contextUrl: null,
574+
documentUrl: securityUrl,
575+
document: preloadedContexts[securityUrl],
576+
});
577+
assertEquals(
578+
loaderCalled,
579+
false,
580+
"Loader should not be called for preloaded contexts",
581+
);
582+
583+
// Test that non-preloaded URLs still use the cache normally
584+
const nonPreloadedUrl = "https://example.com/not-preloaded";
585+
loaderCalled = false;
586+
const result3 = await loader(nonPreloadedUrl);
587+
588+
assertEquals(result3, {
589+
contextUrl: null,
590+
documentUrl: nonPreloadedUrl,
591+
document: { "mock": "document" },
592+
});
593+
assertEquals(
594+
loaderCalled,
595+
true,
596+
"Loader should be called for non-preloaded URLs",
597+
);
598+
599+
// Verify that non-preloaded URL was cached
600+
const cachedValue2 = await kv.get(["_test", "preloaded", nonPreloadedUrl]);
601+
assertEquals(cachedValue2, result3, "Non-preloaded URLs should be cached");
602+
});
519603
});
520604

521605
test("getUserAgent()", () => {

fedify/runtime/docloader.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,14 @@ export function kvCache(
456456
}
457457

458458
return async (url: string): Promise<RemoteDocument> => {
459+
if (url in preloadedContexts) {
460+
logger.debug("Using preloaded context: {url}.", { url });
461+
return {
462+
contextUrl: null,
463+
document: preloadedContexts[url],
464+
documentUrl: url,
465+
};
466+
}
459467
const match = matchRule(url);
460468
if (match == null) return await loader(url);
461469
const key: KvKey = [...keyPrefix, url];

0 commit comments

Comments
 (0)