From dda070442ed3b01f2a89153752f6466bb86eb77c Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 21 May 2021 17:23:54 +0200 Subject: [PATCH 1/7] Making note of cache stores in documentation. Signed-off-by: Eric Peterson --- .github/styles/vocab.txt | 1 + docs/overview/architecture-overview.md | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index c367fa0a4c..b8f028a2ae 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -113,6 +113,7 @@ json jsonnet jsx Kaewkasi +Keyv Knex kubectl kubernetes diff --git a/docs/overview/architecture-overview.md b/docs/overview/architecture-overview.md index 316f066614..1098a3f5f9 100644 --- a/docs/overview/architecture-overview.md +++ b/docs/overview/architecture-overview.md @@ -182,6 +182,21 @@ work but [aren't tested as fully](https://github.com/backstage/backstage/issues/2460) yet. +## Cache + +The Backstage backend and its builtin plugins are also able to leverage cache +stores as a means of improving performance or reliability. Similar to how +databases are supported, plugins receive logically separated cache connections, +which are powered by [Keyv](https://github.com/lukechilds/keyv) under the hood. + +At this time of writing, Backstage can be configured to use one of two cache +stores: memory, which is mainly used for local testing, and memcache, which is +is a cache store better suited for production deployment. The right cache store +for your Backstage instance will depend on your own run-time constraints and +those specified by the plugins you're running. + +Contributions supporting other cache stores are welcome! + ## Containerization The example Backstage architecture shown above would Dockerize into three From 5bc7b25184e61f045e1dfa79ea509362394a811f Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 21 May 2021 17:24:33 +0200 Subject: [PATCH 2/7] Update default example config to use memory cache store. Signed-off-by: Eric Peterson --- app-config.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app-config.yaml b/app-config.yaml index b1c915f85a..9874ce37ac 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -29,6 +29,8 @@ backend: database: client: sqlite3 connection: ':memory:' + cache: + store: memory cors: origin: http://localhost:3000 methods: [GET, POST, PUT, DELETE] From 70c345df29690c605158a26d60ab2a6bbcfc57c4 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 21 May 2021 17:25:26 +0200 Subject: [PATCH 3/7] Updating create-app so that cache store can be selected/configured. Signed-off-by: Eric Peterson --- packages/create-app/src/createApp.ts | 9 +++++++++ .../create-app/templates/default-app/app-config.yaml.hbs | 9 +++++++++ .../templates/default-app/packages/backend/src/index.ts | 5 ++++- .../templates/default-app/packages/backend/src/types.ts | 2 ++ 4 files changed, 24 insertions(+), 1 deletion(-) diff --git a/packages/create-app/src/createApp.ts b/packages/create-app/src/createApp.ts index 8a269416a3..e5bf323399 100644 --- a/packages/create-app/src/createApp.ts +++ b/packages/create-app/src/createApp.ts @@ -112,10 +112,19 @@ export default async (cmd: Command): Promise => { // @ts-ignore choices: ['SQLite', 'PostgreSQL'], }, + { + type: 'list', + name: 'cacheType', + message: chalk.blue('Select cache store for the backend [required]'), + // @ts-ignore + choices: ['in-memory', 'memcache'], + }, ]; const answers: Answers = await inquirer.prompt(questions); answers.dbTypePG = answers.dbType === 'PostgreSQL'; answers.dbTypeSqlite = answers.dbType === 'SQLite'; + answers.cacheTypeMemory = answers.cacheType === 'in-memory'; + answers.cacheTypeMemcache = answers.cacheType === 'memcache'; const templateDir = paths.resolveOwn('templates/default-app'); const tempDir = resolvePath(os.tmpdir(), answers.name); diff --git a/packages/create-app/templates/default-app/app-config.yaml.hbs b/packages/create-app/templates/default-app/app-config.yaml.hbs index 275b3865f9..7a8eb8f13c 100644 --- a/packages/create-app/templates/default-app/app-config.yaml.hbs +++ b/packages/create-app/templates/default-app/app-config.yaml.hbs @@ -34,6 +34,15 @@ backend: #ca: # if you have a CA file and want to verify it you can uncomment this section # $file: /ca/server.crt {{/if}} + {{#if cacheTypeMemory}} + cache: + store: memory + {{/if}} + {{#if cacheTypeMemcache}} + cache: + store: memcache + connection: ${MEMCACHE_USER}:${MEMCACHE_PASSWORD}@${MEMCACHE_HOST}:$MEMCACHE_PORT} + {{/if}} # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir integrations: diff --git a/packages/create-app/templates/default-app/packages/backend/src/index.ts b/packages/create-app/templates/default-app/packages/backend/src/index.ts index 80dc6230a6..aebd034aae 100644 --- a/packages/create-app/templates/default-app/packages/backend/src/index.ts +++ b/packages/create-app/templates/default-app/packages/backend/src/index.ts @@ -13,6 +13,7 @@ import { getRootLogger, useHotMemoize, notFoundHandler, + CacheManager, SingleConnectionDatabaseManager, SingleHostDiscovery, UrlReaders, @@ -34,11 +35,13 @@ function makeCreateEnv(config: Config) { root.info(`Created UrlReader ${reader}`); const databaseManager = SingleConnectionDatabaseManager.fromConfig(config); + const cacheManager = CacheManager.fromConfig(config); return (plugin: string): PluginEnvironment => { const logger = root.child({ type: 'plugin', plugin }); const database = databaseManager.forPlugin(plugin); - return { logger, database, config, reader, discovery }; + const cache = cacheManager.forPlugin(plugin); + return { logger, database, cache, config, reader, discovery }; }; } diff --git a/packages/create-app/templates/default-app/packages/backend/src/types.ts b/packages/create-app/templates/default-app/packages/backend/src/types.ts index 757a0e5acf..e5bfff2e1f 100644 --- a/packages/create-app/templates/default-app/packages/backend/src/types.ts +++ b/packages/create-app/templates/default-app/packages/backend/src/types.ts @@ -1,6 +1,7 @@ import { Logger } from 'winston'; import { Config } from '@backstage/config'; import { + PluginCacheManager, PluginDatabaseManager, PluginEndpointDiscovery, UrlReader, @@ -9,6 +10,7 @@ import { export type PluginEnvironment = { logger: Logger; database: PluginDatabaseManager; + cache: PluginCacheManager; config: Config; reader: UrlReader discovery: PluginEndpointDiscovery; From 6c4bd674c0ececcb6b8d8356a82ec9e1a8700441 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 21 May 2021 17:38:17 +0200 Subject: [PATCH 4/7] Changeset. Signed-off-by: Eric Peterson --- .changeset/cache-me-if-you-can.md | 82 +++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 .changeset/cache-me-if-you-can.md diff --git a/.changeset/cache-me-if-you-can.md b/.changeset/cache-me-if-you-can.md new file mode 100644 index 0000000000..4322d1158f --- /dev/null +++ b/.changeset/cache-me-if-you-can.md @@ -0,0 +1,82 @@ +--- +'@backstage/create-app': patch +--- + +Cache management has been added to the Backstage backend. + +To apply this change to an existing app, make the following changes: + +```diff +// packages/backend/src/types.ts + +import { Logger } from 'winston'; +import { Config } from '@backstage/config'; +import { ++ PluginCacheManager, + PluginDatabaseManager, + PluginEndpointDiscovery, + UrlReader, +} from '@backstage/backend-common'; + +export type PluginEnvironment = { + logger: Logger; + database: PluginDatabaseManager; ++ cache: PluginCacheManager; + config: Config; + reader: UrlReader + discovery: PluginEndpointDiscovery; +}; +``` + +```diff +// packages/backend/src/index.ts + +import Router from 'express-promise-router'; +import { + createServiceBuilder, + loadBackendConfig, + getRootLogger, + useHotMemoize, + notFoundHandler, ++ CacheManager, + SingleConnectionDatabaseManager, + SingleHostDiscovery, + UrlReaders, +} from '@backstage/backend-common'; +import { Config } from '@backstage/config'; + +function makeCreateEnv(config: Config) { + const root = getRootLogger(); + const reader = UrlReaders.default({ logger: root, config }); + const discovery = SingleHostDiscovery.fromConfig(config); + + root.info(`Created UrlReader ${reader}`); + + const databaseManager = SingleConnectionDatabaseManager.fromConfig(config); ++ const cacheManager = CacheManager.fromConfig(config); + + return (plugin: string): PluginEnvironment => { + const logger = root.child({ type: 'plugin', plugin }); + const database = databaseManager.forPlugin(plugin); +- return { logger, database, config, reader, discovery }; ++ const cache = cacheManager.forPlugin(plugin); ++ return { logger, database, cache, config, reader, discovery }; + }; +} +``` + +To configure a cache store, add a `backend.cache` key to your app-config.yaml. + +```diff +// app-config.yaml + +backend: + baseUrl: http://localhost:7000 + listen: + port: 7000 + database: + client: sqlite3 + connection: ':memory:' ++ cache: ++ store: memory +``` From 85d1398d1cdc25db2735d5101c0883bba71fc075 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Sat, 22 May 2021 20:31:03 +0200 Subject: [PATCH 5/7] Update CLI E2E test Signed-off-by: Eric Peterson --- packages/e2e-test/src/commands/run.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/e2e-test/src/commands/run.ts b/packages/e2e-test/src/commands/run.ts index ebf4e6c835..885b0bbb0e 100644 --- a/packages/e2e-test/src/commands/run.ts +++ b/packages/e2e-test/src/commands/run.ts @@ -209,6 +209,9 @@ async function createApp( } child.stdin?.write(`\n`); + await waitFor(() => stdout.includes('Select cache store for the backend')); + child.stdin?.write(`\n`); + print('Waiting for app create script to be done'); await waitForExit(child); From e6ce2ef9a1f6c162d7e20a4410492e3e0af78128 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Mon, 24 May 2021 18:13:48 +0200 Subject: [PATCH 6/7] Grammar, clarity, etc. Signed-off-by: Eric Peterson --- docs/overview/architecture-overview.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/overview/architecture-overview.md b/docs/overview/architecture-overview.md index 1098a3f5f9..af46a8c830 100644 --- a/docs/overview/architecture-overview.md +++ b/docs/overview/architecture-overview.md @@ -190,10 +190,10 @@ databases are supported, plugins receive logically separated cache connections, which are powered by [Keyv](https://github.com/lukechilds/keyv) under the hood. At this time of writing, Backstage can be configured to use one of two cache -stores: memory, which is mainly used for local testing, and memcache, which is -is a cache store better suited for production deployment. The right cache store -for your Backstage instance will depend on your own run-time constraints and -those specified by the plugins you're running. +stores: memory, which is mainly used for local testing, and memcache, which is a +cache store better suited for production deployment. The right cache store for +your Backstage instance will depend on your own run-time constraints and those +required of the plugins you're running. Contributions supporting other cache stores are welcome! From e2780c7e10ce215f9a0d677e37a94dbda357cad3 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Mon, 24 May 2021 21:43:20 +0200 Subject: [PATCH 7/7] Just default to in-memory instead. Signed-off-by: Eric Peterson --- packages/create-app/src/createApp.ts | 9 --------- .../create-app/templates/default-app/app-config.yaml.hbs | 7 ------- packages/e2e-test/src/commands/run.ts | 3 --- 3 files changed, 19 deletions(-) diff --git a/packages/create-app/src/createApp.ts b/packages/create-app/src/createApp.ts index e5bf323399..8a269416a3 100644 --- a/packages/create-app/src/createApp.ts +++ b/packages/create-app/src/createApp.ts @@ -112,19 +112,10 @@ export default async (cmd: Command): Promise => { // @ts-ignore choices: ['SQLite', 'PostgreSQL'], }, - { - type: 'list', - name: 'cacheType', - message: chalk.blue('Select cache store for the backend [required]'), - // @ts-ignore - choices: ['in-memory', 'memcache'], - }, ]; const answers: Answers = await inquirer.prompt(questions); answers.dbTypePG = answers.dbType === 'PostgreSQL'; answers.dbTypeSqlite = answers.dbType === 'SQLite'; - answers.cacheTypeMemory = answers.cacheType === 'in-memory'; - answers.cacheTypeMemcache = answers.cacheType === 'memcache'; const templateDir = paths.resolveOwn('templates/default-app'); const tempDir = resolvePath(os.tmpdir(), answers.name); diff --git a/packages/create-app/templates/default-app/app-config.yaml.hbs b/packages/create-app/templates/default-app/app-config.yaml.hbs index 7a8eb8f13c..59cfd09753 100644 --- a/packages/create-app/templates/default-app/app-config.yaml.hbs +++ b/packages/create-app/templates/default-app/app-config.yaml.hbs @@ -34,15 +34,8 @@ backend: #ca: # if you have a CA file and want to verify it you can uncomment this section # $file: /ca/server.crt {{/if}} - {{#if cacheTypeMemory}} cache: store: memory - {{/if}} - {{#if cacheTypeMemcache}} - cache: - store: memcache - connection: ${MEMCACHE_USER}:${MEMCACHE_PASSWORD}@${MEMCACHE_HOST}:$MEMCACHE_PORT} - {{/if}} # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir integrations: diff --git a/packages/e2e-test/src/commands/run.ts b/packages/e2e-test/src/commands/run.ts index 885b0bbb0e..ebf4e6c835 100644 --- a/packages/e2e-test/src/commands/run.ts +++ b/packages/e2e-test/src/commands/run.ts @@ -209,9 +209,6 @@ async function createApp( } child.stdin?.write(`\n`); - await waitFor(() => stdout.includes('Select cache store for the backend')); - child.stdin?.write(`\n`); - print('Waiting for app create script to be done'); await waitForExit(child);