From 61fba6e50b31ee4559344ca53abaee3addd2b23a Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Thu, 2 Jun 2022 16:25:27 +0200 Subject: [PATCH] Limit concurrent local TechDocs builds. Signed-off-by: Eric Peterson --- .changeset/techdocs-everybody-to-the-limit.md | 5 +++ .../src/service/DocsSynchronizer.test.ts | 31 +++++++++++++++++++ .../src/service/DocsSynchronizer.ts | 7 ++++- 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 .changeset/techdocs-everybody-to-the-limit.md diff --git a/.changeset/techdocs-everybody-to-the-limit.md b/.changeset/techdocs-everybody-to-the-limit.md new file mode 100644 index 0000000000..9167838d03 --- /dev/null +++ b/.changeset/techdocs-everybody-to-the-limit.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-backend': patch +--- + +In order to ensure a good, stable TechDocs user experience when running TechDocs with `techdocs.builder` set to `local`, the number of concurrent builds has been limited to 10. Any additional builds requested concurrently will be queued and handled as prior builds complete. In the unlikely event that you need to handle more concurrent builds, consider scaling out your TechDocs backend deployment or using the `external` option for `techdocs.builder`. diff --git a/plugins/techdocs-backend/src/service/DocsSynchronizer.test.ts b/plugins/techdocs-backend/src/service/DocsSynchronizer.test.ts index fff35bedc0..0ab124c26d 100644 --- a/plugins/techdocs-backend/src/service/DocsSynchronizer.test.ts +++ b/plugins/techdocs-backend/src/service/DocsSynchronizer.test.ts @@ -159,6 +159,37 @@ describe('DocsSynchronizer', () => { expect(DocsBuilder.prototype.build).toBeCalledTimes(1); }); + it('should limit concurrent updates', async () => { + // Given a build implementation that runs long... + MockedDocsBuilder.prototype.build.mockImplementation( + () => new Promise(() => {}), + ); + (shouldCheckForUpdate as jest.Mock).mockReturnValue(true); + const entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + uid: '0', + name: 'test', + namespace: 'default', + }, + }; + + // When more than 10 syncs are attempted... + for (let i = 0; i < 12; i++) { + docsSynchronizer.doSync({ + responseHandler: mockResponseHandler, + entity, + preparers, + generators, + }); + } + + // Then still only 10 builds should have been triggered. + await new Promise(resolve => resolve()); + expect(DocsBuilder.prototype.build).toHaveBeenCalledTimes(10); + }); + it('should not check for an update too often', async () => { (shouldCheckForUpdate as jest.Mock).mockReturnValue(false); diff --git a/plugins/techdocs-backend/src/service/DocsSynchronizer.ts b/plugins/techdocs-backend/src/service/DocsSynchronizer.ts index 6c377381e0..647447a612 100644 --- a/plugins/techdocs-backend/src/service/DocsSynchronizer.ts +++ b/plugins/techdocs-backend/src/service/DocsSynchronizer.ts @@ -25,6 +25,7 @@ import { PublisherBase, } from '@backstage/plugin-techdocs-node'; import fetch from 'node-fetch'; +import pLimit, { Limit } from 'p-limit'; import { PassThrough } from 'stream'; import * as winston from 'winston'; import { TechDocsCache } from '../cache'; @@ -47,6 +48,7 @@ export class DocsSynchronizer { private readonly config: Config; private readonly scmIntegrations: ScmIntegrationRegistry; private readonly cache: TechDocsCache | undefined; + private readonly buildLimiter: Limit; constructor({ publisher, @@ -69,6 +71,9 @@ export class DocsSynchronizer { this.publisher = publisher; this.scmIntegrations = scmIntegrations; this.cache = cache; + + // Single host/process: limit concurrent builds up to 10 at a time. + this.buildLimiter = pLimit(10); } async doSync({ @@ -123,7 +128,7 @@ export class DocsSynchronizer { cache: this.cache, }); - const updated = await docsBuilder.build(); + const updated = await this.buildLimiter(() => docsBuilder.build()); if (!updated) { finish({ updated: false });