Skip to content

Commit 4217350

Browse files
committed
Optimalisering: Prefetcher alle kategorier ved startup
1 parent 08b4f8a commit 4217350

7 files changed

+199
-94
lines changed

common/cms-documents/category.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ export type CmsCategoryDocument = {
2020
xmlAsString: string;
2121
};
2222

23-
export type CmsCategoryListItem = Pick<CmsCategoryDocument, 'key' | 'title' | 'categories'> & {
23+
export type CmsCategoryListItem = Pick<
24+
CmsCategoryDocument,
25+
'key' | 'title' | 'categories' | 'superKey'
26+
> & {
2427
contentCount: number;
2528
path: CmsCategoryPath;
2629
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import { CmsArchiveOpenSearchClient, SearchResult } from '../opensearch/CmsArchiveOpenSearchClient';
2+
import { CmsArchiveSiteConfig } from './CmsArchiveSite';
3+
import { CmsCategoryPath } from '../../../common/cms-documents/_common';
4+
import { CmsCategoryListItem } from '../../../common/cms-documents/category';
5+
import { CmsCategoryDocument } from '../opensearch/types';
6+
7+
type ConstructorProps = {
8+
config: CmsArchiveSiteConfig;
9+
client: CmsArchiveOpenSearchClient;
10+
};
11+
12+
export class CmsArchiveCategoriesService {
13+
private readonly client: CmsArchiveOpenSearchClient;
14+
private readonly config: CmsArchiveSiteConfig;
15+
16+
private readonly index: string;
17+
18+
private readonly categoriesMap: Map<string, CmsCategoryListItem> = new Map();
19+
private readonly rootCategories: CmsCategoryListItem[] = [];
20+
21+
constructor({ client, config }: ConstructorProps) {
22+
this.client = client;
23+
this.config = config;
24+
this.index = `${config.indexPrefix}_categories`;
25+
}
26+
27+
async init() {
28+
const allCategories = await this.getAllCategories();
29+
if (!allCategories) {
30+
console.error(`Could not retrieve categories for ${this.config.name}`);
31+
return false;
32+
}
33+
34+
console.log(`Found ${allCategories.length} categories for ${this.config.name}`);
35+
36+
this.populateCategoriesMap(allCategories);
37+
38+
console.log(
39+
`Initialized categories service for ${this.config.name} with ${this.categoriesMap.size} categories and ${this.rootCategories.length} root categories`
40+
);
41+
}
42+
43+
async getCategoryWithXml(categoryKey: string): Promise<CmsCategoryDocument | null> {
44+
return this.client.getDocument<CmsCategoryDocument>({
45+
index: this.index,
46+
id: categoryKey,
47+
});
48+
}
49+
50+
getRootCategories(): CmsCategoryListItem[] {
51+
return this.rootCategories;
52+
}
53+
54+
getCategory(categoryKey: string): CmsCategoryListItem | undefined {
55+
return this.categoriesMap.get(categoryKey);
56+
}
57+
58+
getCategories(categoryKeys: string[]): CmsCategoryListItem[] {
59+
return categoryKeys.reduce<CmsCategoryListItem[]>((acc, key) => {
60+
const category = this.categoriesMap.get(key);
61+
if (category) {
62+
acc.push(category);
63+
}
64+
65+
return acc;
66+
}, []);
67+
}
68+
69+
private populateCategoriesMap(categories: CmsCategoryListItem[]) {
70+
this.categoriesMap.clear();
71+
this.rootCategories.length = 0;
72+
73+
categories.forEach((category) => {
74+
this.categoriesMap.set(category.key, category);
75+
if (!category.superKey) {
76+
this.rootCategories.push(category);
77+
}
78+
});
79+
80+
this.categoriesMap.forEach((category) => {
81+
if (category.superKey) {
82+
category.path = this.resolveCategoryPath(category.superKey);
83+
}
84+
});
85+
}
86+
87+
resolveCategoryPath(categoryKey: string, path: CmsCategoryPath = []): CmsCategoryPath {
88+
const category = this.categoriesMap.get(categoryKey);
89+
if (!category) {
90+
console.error(`No category found for key ${categoryKey}`);
91+
return path;
92+
}
93+
94+
const { key, superKey, title } = category;
95+
96+
path.push({
97+
key,
98+
name: title,
99+
});
100+
101+
if (!superKey) {
102+
return path;
103+
}
104+
105+
return this.resolveCategoryPath(superKey, path);
106+
}
107+
108+
private async getAllCategories(): Promise<CmsCategoryListItem[] | null> {
109+
return this.client
110+
.search<CmsCategoryDocument>({
111+
index: this.index,
112+
_source_excludes: ['xmlAsString'],
113+
size: 10000,
114+
body: {
115+
query: {
116+
match_all: {},
117+
},
118+
},
119+
})
120+
.then((result) => {
121+
if (result && result.total > 10000) {
122+
console.error(
123+
`Found more than 10000 categories in ${this.index} - expected 3127 (sbs) or 3866 (fss)`
124+
);
125+
}
126+
127+
return this.transformResult(result);
128+
});
129+
}
130+
131+
private transformResult(
132+
result: SearchResult<CmsCategoryDocument> | null
133+
): CmsCategoryListItem[] | null {
134+
if (!result) {
135+
return null;
136+
}
137+
138+
return result.hits.map((hit) => this.transformToListItem(hit));
139+
}
140+
141+
private transformToListItem(document: CmsCategoryDocument): CmsCategoryListItem {
142+
const { key, title, categories, contents, superKey } = document;
143+
144+
return {
145+
key,
146+
superKey,
147+
title,
148+
categories,
149+
contentCount: contents.length,
150+
path: [],
151+
};
152+
}
153+
}

server/src/cms/CmsArchiveService.ts server/src/cms/CmsArchiveContentService.ts

+15-78
Original file line numberDiff line numberDiff line change
@@ -9,74 +9,35 @@ import { transformToCategoriesList } from './utils/transformToCategoriesList';
99
import { CmsCategoryPath } from '../../../common/cms-documents/_common';
1010
import { ContentSearchParams, ContentSearchResult } from '../../../common/contentSearch';
1111
import { buildContentSearchParams } from '../opensearch/queries/contentSearch';
12+
import { CmsArchiveCategoriesService } from './CmsArchiveCategoriesService';
1213

1314
type ConstructorProps = {
1415
client: CmsArchiveOpenSearchClient;
1516
siteConfig: CmsArchiveSiteConfig;
17+
categoriesService: CmsArchiveCategoriesService;
1618
};
1719

18-
export class CmsArchiveService {
20+
export class CmsArchiveContentService {
1921
private readonly client: CmsArchiveOpenSearchClient;
2022
private readonly siteConfig: CmsArchiveSiteConfig;
23+
private readonly categoriesService: CmsArchiveCategoriesService;
2124

22-
private readonly categoriesIndex: string;
2325
private readonly contentsIndex: string;
2426
private readonly binariesIndex: string;
2527
private readonly staticAssetsIndex: string;
2628

27-
constructor({ client, siteConfig }: ConstructorProps) {
29+
constructor({ client, siteConfig, categoriesService }: ConstructorProps) {
2830
const { indexPrefix } = siteConfig;
2931

3032
this.client = client;
3133
this.siteConfig = siteConfig;
34+
this.categoriesService = categoriesService;
3235

33-
this.categoriesIndex = `${indexPrefix}_categories`;
3436
this.contentsIndex = `${indexPrefix}_content`;
3537
this.binariesIndex = `${indexPrefix}_binaries`;
3638
this.staticAssetsIndex = `${indexPrefix}_assets`;
3739
}
3840

39-
public async getRootCategories(): Promise<CmsCategoryListItem[] | null> {
40-
return this.client
41-
.search<CmsCategoryDocument>({
42-
index: this.categoriesIndex,
43-
_source_excludes: ['xmlAsString'],
44-
size: 100,
45-
body: {
46-
query: {
47-
bool: {
48-
must_not: {
49-
exists: {
50-
field: 'superKey',
51-
},
52-
},
53-
},
54-
},
55-
},
56-
})
57-
.then((result) => (result ? transformToCategoriesList(result.hits, this) : result));
58-
}
59-
60-
public async getCategory(categoryKey: string): Promise<CmsCategoryDocument | null> {
61-
return this.client.getDocument<CmsCategoryDocument>({
62-
index: this.categoriesIndex,
63-
_source_excludes: ['xmlAsString'],
64-
id: categoryKey,
65-
});
66-
}
67-
68-
public async getCategories(categoryKeys: string[]): Promise<CmsCategoryListItem[] | null> {
69-
return this.client
70-
.getDocuments<CmsCategoryDocument>({
71-
index: this.categoriesIndex,
72-
_source_excludes: ['xmlAsString'],
73-
body: {
74-
ids: categoryKeys,
75-
},
76-
})
77-
.then((res) => transformToCategoriesList(res, this));
78-
}
79-
8041
public async contentSearch(params: ContentSearchParams): Promise<ContentSearchResult> {
8142
const result = await this.client.search<CmsContentDocument>({
8243
...buildContentSearchParams(params),
@@ -92,15 +53,13 @@ export class CmsArchiveService {
9253
};
9354
}
9455

95-
const hits = await Promise.all(
96-
result.hits.map(async (hit) => ({
97-
contentKey: hit.contentKey,
98-
versionKey: hit.versionKey,
99-
displayName: hit.displayName,
100-
score: hit._score || 0,
101-
path: await this.resolveCategoryPath(hit.category.key),
102-
}))
103-
);
56+
const hits = result.hits.map((hit) => ({
57+
contentKey: hit.contentKey,
58+
versionKey: hit.versionKey,
59+
displayName: hit.displayName,
60+
score: hit._score || 0,
61+
path: this.categoriesService.resolveCategoryPath(hit.category.key),
62+
}));
10463

10564
return {
10665
params,
@@ -190,29 +149,7 @@ export class CmsArchiveService {
190149
return result.hits[0];
191150
}
192151

193-
async resolveCategoryPath(categoryKey: string): Promise<CmsCategoryPath> {
194-
const category = await this.getCategory(categoryKey);
195-
if (!category) {
196-
return [];
197-
}
198-
199-
const { key, superKey, title } = category;
200-
201-
const item = {
202-
key,
203-
name: title,
204-
};
205-
206-
if (!superKey) {
207-
return [item];
208-
}
209-
210-
return [...(await this.resolveCategoryPath(superKey)), item];
211-
}
212-
213-
private async fixContent(
214-
contentDocument: CmsContentDocument | null
215-
): Promise<CmsContent | null> {
152+
private fixContent(contentDocument: CmsContentDocument | null): CmsContent | null {
216153
if (!contentDocument) {
217154
return null;
218155
}
@@ -221,7 +158,7 @@ export class CmsArchiveService {
221158
...contentDocument,
222159
versions: sortVersions(contentDocument),
223160
html: this.fixHtml(contentDocument.html),
224-
path: await this.resolveCategoryPath(contentDocument.category.key),
161+
path: this.categoriesService.resolveCategoryPath(contentDocument.category.key),
225162
};
226163
}
227164

server/src/cms/CmsArchiveSite.ts

+20-9
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { CmsArchiveOpenSearchClient } from '../opensearch/CmsArchiveOpenSearchClient';
22
import express, { Express, Response, Router } from 'express';
3-
import { CmsArchiveService } from './CmsArchiveService';
3+
import { CmsArchiveContentService } from './CmsArchiveContentService';
44
import { parseQueryParamsList } from '../utils/queryParams';
55
import mime from 'mime';
66
import { HtmlRenderer } from '../site/ssr/htmlRenderer';
77
import { transformQueryToContentSearchParams } from '../opensearch/queries/contentSearch';
8+
import { CmsArchiveCategoriesService } from './CmsArchiveCategoriesService';
89

910
export type CmsArchiveSiteConfig = {
1011
name: string;
@@ -21,13 +22,23 @@ type ContructorProps = {
2122

2223
export class CmsArchiveSite {
2324
private readonly config: CmsArchiveSiteConfig;
24-
private readonly cmsArchiveService: CmsArchiveService;
25+
private readonly cmsArchiveService: CmsArchiveContentService;
26+
private readonly cmsArchiveCategoriesService: CmsArchiveCategoriesService;
2527

2628
constructor({ config, expressApp, dbClient, htmlRenderer }: ContructorProps) {
2729
this.config = config;
28-
this.cmsArchiveService = new CmsArchiveService({
30+
31+
this.cmsArchiveCategoriesService = new CmsArchiveCategoriesService({
32+
config: config,
33+
client: dbClient,
34+
});
35+
36+
this.cmsArchiveCategoriesService.init();
37+
38+
this.cmsArchiveService = new CmsArchiveContentService({
2939
client: dbClient,
3040
siteConfig: config,
41+
categoriesService: this.cmsArchiveCategoriesService,
3142
});
3243

3344
const siteRouter = express.Router();
@@ -42,8 +53,8 @@ export class CmsArchiveSite {
4253
}
4354

4455
private setupApiRoutes(router: Router) {
45-
router.get('/root-categories', async (req, res) => {
46-
const rootCategories = await this.cmsArchiveService.getRootCategories();
56+
router.get('/root-categories', (req, res) => {
57+
const rootCategories = this.cmsArchiveCategoriesService.getRootCategories();
4758
return res.send(rootCategories);
4859
});
4960

@@ -53,7 +64,7 @@ export class CmsArchiveSite {
5364
return res.status(400).send('Required parameter "keys" is not valid');
5465
}
5566

56-
const category = await this.cmsArchiveService.getCategories(keys);
67+
const category = await this.cmsArchiveCategoriesService.getCategories(keys);
5768
return res.send(category);
5869
});
5970

@@ -91,9 +102,9 @@ export class CmsArchiveSite {
91102
});
92103
}
93104

94-
private async setupSiteRoutes(router: Router, htmlRenderer: HtmlRenderer) {
105+
private setupSiteRoutes(router: Router, htmlRenderer: HtmlRenderer) {
95106
router.get('/:versionKey?', async (req, res) => {
96-
const rootCategories = (await this.cmsArchiveService.getRootCategories()) || [];
107+
const rootCategories = this.cmsArchiveCategoriesService.getRootCategories();
97108

98109
const appContext = {
99110
rootCategories,
@@ -108,7 +119,7 @@ export class CmsArchiveSite {
108119
});
109120
}
110121

111-
private async setupFileRoutes(router: Router) {
122+
private setupFileRoutes(router: Router) {
112123
router.get('/binary/file/:binaryKey', async (req, res) => {
113124
const { binaryKey } = req.params;
114125

0 commit comments

Comments
 (0)