From c8847c495e77e070d595194f7514fba816ac1dc3 Mon Sep 17 00:00:00 2001 From: Mirko Sekulic Date: Tue, 26 Nov 2024 14:21:16 +0100 Subject: [PATCH] feat: redis cache (#14158) Co-authored-by: davidovrelid.com --- README.md | 2 ++ backend/packagegroups/NuGet.props | 3 ++- .../Configuration/RedisCacheSettings.cs | 10 ++++++++ backend/src/Designer/Designer.csproj | 3 ++- .../20241111093515_DistributedCacheTable.cs | 6 ++--- .../01-setup-distributedcache-table.sql | 0 .../{Up => Create}/02-setup-grants.sql | 0 .../01-drop-distributedcache-table.sql | 0 backend/src/Designer/Program.cs | 24 ++++++++----------- backend/src/Designer/appsettings.json | 3 +++ charts/altinn-designer/values.yaml | 12 ++++++++++ compose.yaml | 23 ++++++++++++++++++ .../providers/PreviewConnectionContext.tsx | 7 ++++-- .../shared/src/websockets/WSConnector.test.ts | 1 + .../shared/src/websockets/WSConnector.ts | 7 ++++-- frontend/testing/setupTests.ts | 8 +++++-- 16 files changed, 84 insertions(+), 25 deletions(-) create mode 100644 backend/src/Designer/Configuration/RedisCacheSettings.cs rename backend/src/Designer/Migrations/SqlScripts/DistributedCache/{Up => Create}/01-setup-distributedcache-table.sql (100%) rename backend/src/Designer/Migrations/SqlScripts/DistributedCache/{Up => Create}/02-setup-grants.sql (100%) rename backend/src/Designer/Migrations/SqlScripts/DistributedCache/{Down => Drop}/01-drop-distributedcache-table.sql (100%) diff --git a/README.md b/README.md index 8af19271dfb..366fd92c22e 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,8 @@ The development environment consist of several services defined in [compose.yaml - `studio-repos` which is [gitea][14] with some custom config. More [here](gitea/README.md). - `studio-db` which is a postgres database used by both `studio-designer` and `studio-repos`. - `database_migrations` which is a one-time task container designed to perform and complete database migrations before exiting. +- `redis` which is a redis cache used by designer. +- `redis-commander` which is a ui for redis cache. Run all parts of the solution in containers (Make sure docker is running), with docker compose as follows: diff --git a/backend/packagegroups/NuGet.props b/backend/packagegroups/NuGet.props index 7aa6307c86f..f32cb55f724 100644 --- a/backend/packagegroups/NuGet.props +++ b/backend/packagegroups/NuGet.props @@ -24,7 +24,9 @@ + + @@ -40,7 +42,6 @@ - diff --git a/backend/src/Designer/Configuration/RedisCacheSettings.cs b/backend/src/Designer/Configuration/RedisCacheSettings.cs new file mode 100644 index 00000000000..8ce1de6abc5 --- /dev/null +++ b/backend/src/Designer/Configuration/RedisCacheSettings.cs @@ -0,0 +1,10 @@ +using Altinn.Studio.Designer.Configuration.Marker; + +namespace Altinn.Studio.Designer.Configuration; + +public class RedisCacheSettings : ISettingsMarker +{ + public bool UseRedisCache { get; set; } = false; + public string ConnectionString { get; set; } + public string InstanceName { get; set; } +} diff --git a/backend/src/Designer/Designer.csproj b/backend/src/Designer/Designer.csproj index c52469b77f6..37ff2b0dedf 100644 --- a/backend/src/Designer/Designer.csproj +++ b/backend/src/Designer/Designer.csproj @@ -22,7 +22,6 @@ - @@ -38,6 +37,7 @@ + @@ -46,6 +46,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all + diff --git a/backend/src/Designer/Migrations/20241111093515_DistributedCacheTable.cs b/backend/src/Designer/Migrations/20241111093515_DistributedCacheTable.cs index a6a7932bb27..5051cfbf588 100644 --- a/backend/src/Designer/Migrations/20241111093515_DistributedCacheTable.cs +++ b/backend/src/Designer/Migrations/20241111093515_DistributedCacheTable.cs @@ -11,14 +11,14 @@ public partial class DistributedCacheTable : Migration /// protected override void Up(MigrationBuilder migrationBuilder) { - migrationBuilder.Sql(SqlScriptsReadHelper.ReadSqlScript("DistributedCache/Up/01-setup-distributedcache-table.sql")); - migrationBuilder.Sql(SqlScriptsReadHelper.ReadSqlScript("DistributedCache/Up/02-setup-grants.sql")); + migrationBuilder.Sql(SqlScriptsReadHelper.ReadSqlScript("DistributedCache/Create/01-setup-distributedcache-table.sql")); + migrationBuilder.Sql(SqlScriptsReadHelper.ReadSqlScript("DistributedCache/Create/02-setup-grants.sql")); } /// protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.Sql(SqlScriptsReadHelper.ReadSqlScript("DistributedCache/Down/01-drop-distributedcache-table.sql")); + migrationBuilder.Sql(SqlScriptsReadHelper.ReadSqlScript("DistributedCache/Drop/01-drop-distributedcache-table.sql")); } } } diff --git a/backend/src/Designer/Migrations/SqlScripts/DistributedCache/Up/01-setup-distributedcache-table.sql b/backend/src/Designer/Migrations/SqlScripts/DistributedCache/Create/01-setup-distributedcache-table.sql similarity index 100% rename from backend/src/Designer/Migrations/SqlScripts/DistributedCache/Up/01-setup-distributedcache-table.sql rename to backend/src/Designer/Migrations/SqlScripts/DistributedCache/Create/01-setup-distributedcache-table.sql diff --git a/backend/src/Designer/Migrations/SqlScripts/DistributedCache/Up/02-setup-grants.sql b/backend/src/Designer/Migrations/SqlScripts/DistributedCache/Create/02-setup-grants.sql similarity index 100% rename from backend/src/Designer/Migrations/SqlScripts/DistributedCache/Up/02-setup-grants.sql rename to backend/src/Designer/Migrations/SqlScripts/DistributedCache/Create/02-setup-grants.sql diff --git a/backend/src/Designer/Migrations/SqlScripts/DistributedCache/Down/01-drop-distributedcache-table.sql b/backend/src/Designer/Migrations/SqlScripts/DistributedCache/Drop/01-drop-distributedcache-table.sql similarity index 100% rename from backend/src/Designer/Migrations/SqlScripts/DistributedCache/Down/01-drop-distributedcache-table.sql rename to backend/src/Designer/Migrations/SqlScripts/DistributedCache/Drop/01-drop-distributedcache-table.sql diff --git a/backend/src/Designer/Program.cs b/backend/src/Designer/Program.cs index 34b5ef65593..bae7c0a7425 100644 --- a/backend/src/Designer/Program.cs +++ b/backend/src/Designer/Program.cs @@ -19,7 +19,6 @@ using Altinn.Studio.Designer.Services.Interfaces; using Altinn.Studio.Designer.Tracing; using Altinn.Studio.Designer.TypedHttpClients; -using Community.Microsoft.Extensions.Caching.PostgreSql; using Microsoft.ApplicationInsights.Extensibility; using Microsoft.ApplicationInsights.Extensibility.EventCounterCollector; using Microsoft.AspNetCore.Builder; @@ -204,7 +203,6 @@ void ConfigureServices(IServiceCollection services, IConfiguration configuration services.AddMemoryCache(); services.AddResponseCompression(); services.AddHealthChecks().AddCheck("designer_health_check"); - services.AddSignalR(); CreateDirectory(configuration); @@ -253,19 +251,17 @@ void ConfigureServices(IServiceCollection services, IConfiguration configuration services.AddFeatureManagement(); services.RegisterSynchronizationServices(configuration); - // Override the default distributed cache with a PostgreSQL implementation - services.AddDistributedPostgreSqlCache(setup => + var signalRBuilder = services.AddSignalR(); + var redisSettings = configuration.GetSection(nameof(RedisCacheSettings)).Get(); + if (redisSettings.UseRedisCache) { - PostgreSQLSettings postgresSettings = - configuration.GetSection(nameof(PostgreSQLSettings)).Get(); - - setup.ConnectionString = postgresSettings.FormattedConnectionString(); - setup.SchemaName = "designer"; - setup.TableName = "distributedcache"; - setup.DisableRemoveExpired = false; - setup.CreateInfrastructure = false; - setup.ExpiredItemsDeletionInterval = TimeSpan.FromMinutes(30); - }); + services.AddStackExchangeRedisCache(options => + { + options.Configuration = redisSettings.ConnectionString; + options.InstanceName = redisSettings.InstanceName; + }); + signalRBuilder.AddStackExchangeRedis(redisSettings.ConnectionString); + } if (!env.IsDevelopment()) { diff --git a/backend/src/Designer/appsettings.json b/backend/src/Designer/appsettings.json index 53cd3b87fc1..21e00fad047 100644 --- a/backend/src/Designer/appsettings.json +++ b/backend/src/Designer/appsettings.json @@ -135,5 +135,8 @@ "SlackSettings": { "WebhookUrl": "https://hooks.slack.com/services/" } + }, + "RedisCacheSettings": { + "UseRedisCache": false } } diff --git a/charts/altinn-designer/values.yaml b/charts/altinn-designer/values.yaml index 5e57f21f2f7..47243834198 100644 --- a/charts/altinn-designer/values.yaml +++ b/charts/altinn-designer/values.yaml @@ -56,6 +56,10 @@ environmentVariables: value: "true" - name: FeatureManagement__EidLogging value: "true" + - name: RedisCacheSettings__UseRedisCache + value: "true" + - name: RedisCacheSettings__InstanceName + value: "designer" staging: - name: ASPNETCORE_ENVIRONMENT value: Staging @@ -91,6 +95,10 @@ environmentVariables: value: "false" - name: FeatureManagement__EidLogging value: "true" + - name: RedisCacheSettings__UseRedisCache + value: "true" + - name: RedisCacheSettings__InstanceName + value: "designer" prod: - name: ASPNETCORE_ENVIRONMENT value: Production @@ -124,6 +132,10 @@ environmentVariables: value: "false" - name: FeatureManagement__EidLogging value: "true" + - name: RedisCacheSettings__UseRedisCache + value: "true" + - name: RedisCacheSettings__InstanceName + value: "designer" dbMigrationsEnvironmentVariablesSecretName: altinn-designer-db-migrations-secret diff --git a/compose.yaml b/compose.yaml index e615b599890..21efd5c0bf5 100644 --- a/compose.yaml +++ b/compose.yaml @@ -7,6 +7,7 @@ volumes: gitea-attachments-data: keys: pgdata: + redisdata: services: studio_azure_mock: @@ -87,6 +88,9 @@ services: - FeatureManagement:Ansattporten=${FEATUREFLAGS_ANSATTPORTEN:-false} - AnsattPortenLoginSettings:ClientId=${ANSATTPORTEN_CLIENTID:-} - AnsattPortenLoginSettings:ClientSecret=${ANSATTPORTEN_CLIENTSECRET:-} + - RedisCacheSettings:UseRedisCache=true + - RedisCacheSettings:ConnectionString=redis:6379 + - RedisCacheSettings:InstanceName=designer ports: - '6000:6000' @@ -184,3 +188,22 @@ services: - PGUSER=designer_admin - PGPASSWORD=${POSTGRES_PASSWORD} - PGDATABASE=designerdb + + redis: + image: redis:alpine + container_name: redis + restart: always + command: redis-server --save 60 1 --loglevel warning + ports: + - "6379:6379" + volumes: + - redisdata:/data + + redis-commander: + container_name: redis-commander + image: rediscommander/redis-commander:latest + restart: always + environment: + - REDIS_HOSTS=redis + ports: + - "8081:8081" diff --git a/frontend/packages/shared/src/providers/PreviewConnectionContext.tsx b/frontend/packages/shared/src/providers/PreviewConnectionContext.tsx index 4bff1ccce87..45e4bbc1b4a 100644 --- a/frontend/packages/shared/src/providers/PreviewConnectionContext.tsx +++ b/frontend/packages/shared/src/providers/PreviewConnectionContext.tsx @@ -2,7 +2,7 @@ import type { ReactNode } from 'react'; import React, { createContext, useContext, useEffect, useState } from 'react'; import { previewSignalRHubSubPath } from 'app-shared/api/paths'; import type { HubConnection } from '@microsoft/signalr'; -import { HubConnectionBuilder, LogLevel } from '@microsoft/signalr'; +import { HttpTransportType, HubConnectionBuilder, LogLevel } from '@microsoft/signalr'; const PreviewConnectionContext = createContext(null); @@ -17,7 +17,10 @@ export const PreviewConnectionContextProvider = ({ useEffect(() => { const newConnection = new HubConnectionBuilder() - .withUrl(previewSignalRHubSubPath()) + .withUrl(previewSignalRHubSubPath(), { + skipNegotiation: true, + transport: HttpTransportType.WebSockets, + }) .configureLogging(LogLevel.Information) .build(); setConnection(newConnection); diff --git a/frontend/packages/shared/src/websockets/WSConnector.test.ts b/frontend/packages/shared/src/websockets/WSConnector.test.ts index 2327425c8c2..920711f3dcb 100644 --- a/frontend/packages/shared/src/websockets/WSConnector.test.ts +++ b/frontend/packages/shared/src/websockets/WSConnector.test.ts @@ -1,6 +1,7 @@ import { WSConnector } from 'app-shared/websockets/WSConnector'; jest.mock('@microsoft/signalr', () => ({ + ...jest.requireActual('@microsoft/signalr'), HubConnection: jest.fn().mockReturnValue({ start: jest.fn().mockResolvedValue('started'), }), diff --git a/frontend/packages/shared/src/websockets/WSConnector.ts b/frontend/packages/shared/src/websockets/WSConnector.ts index ae700fd0f49..38f5a469ff1 100644 --- a/frontend/packages/shared/src/websockets/WSConnector.ts +++ b/frontend/packages/shared/src/websockets/WSConnector.ts @@ -1,4 +1,4 @@ -import { type HubConnection, HubConnectionBuilder } from '@microsoft/signalr'; +import { HttpTransportType, type HubConnection, HubConnectionBuilder } from '@microsoft/signalr'; export class WSConnector { private connection: HubConnection; @@ -30,7 +30,10 @@ export class WSConnector { private createConnection(webSocketUrl: string): void { this.connection = new HubConnectionBuilder() - .withUrl(webSocketUrl) + .withUrl(webSocketUrl, { + skipNegotiation: true, + transport: HttpTransportType.WebSockets, + }) .withAutomaticReconnect() .build(); } diff --git a/frontend/testing/setupTests.ts b/frontend/testing/setupTests.ts index 828e355d313..cd00ba0cb63 100644 --- a/frontend/testing/setupTests.ts +++ b/frontend/testing/setupTests.ts @@ -38,6 +38,7 @@ class ResizeObserver { unobserve = jest.fn(); disconnect = jest.fn(); } + window.ResizeObserver = ResizeObserver; // document.getAnimations must be mocked because it is used by the design system, but it is not supported by React Testing Library. @@ -77,8 +78,11 @@ jest.mock('react-i18next', () => ({ }, })); -// SignalR PreviewHub mock to simulate setup of websockets. -jest.mock('@microsoft/signalr', () => SignalR); +// Mocked SignalR to be able to test in within the tests. +jest.mock('@microsoft/signalr', () => ({ + ...jest.requireActual('@microsoft/signalr'), + ...SignalR, +})); // Mock org and app params jest.mock('react-router-dom', () => ({