Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate from Redis to Valkey #933

Merged
merged 1 commit into from
Feb 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions nais/nais.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ spec:
- application: kabal-e2e-tests
outbound:
rules:
- application: klage-redis-fe
- application: kabal-api
- application: kabal-smart-editor-api
- application: kabal-json-to-pdf
Expand All @@ -62,7 +61,7 @@ spec:
envFrom:
- secret: kabal-session-key
- secret: slack-url
redis:
valkey:
- instance: obo-cache
access: readwrite
- instance: hocuspocus
Expand Down
30 changes: 0 additions & 30 deletions nais/redis.yaml

This file was deleted.

2 changes: 0 additions & 2 deletions server/.env

This file was deleted.

14 changes: 7 additions & 7 deletions server/src/auth/cache/cache-gauge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@ import { Counter, Gauge, Histogram } from 'prom-client';

const labelNames = ['hit'] as const;

export const redisCacheGauge = new Counter({
name: 'obo_redis_cache',
help: 'Number of requests to the Redis OBO cache. "hit" is the type of hit: "miss", "invalid", "hit" or "expired".',
export const persistentCacheGauge = new Counter({
name: 'obo_persistent_cache',
help: 'Number of requests to the persistent OBO cache. "hit" is the type of hit: "miss", "invalid", "hit" or "expired".',
labelNames,
registers: [proxyRegister],
});

export const redisCacheSizeGauge = new Gauge({
name: 'obo_redis_cache_size',
help: 'Number of OBO tokens in the Redis cache.',
export const persistentCacheSizeGauge = new Gauge({
name: 'obo_persistent_cache_size',
help: 'Number of OBO tokens in the persistent cache.',
registers: [proxyRegister],
});

export const memoryCacheGauge = new Counter({
name: 'obo_cache',
help: 'Number of requests to the OBO cache. "hit" is the type of hit: "miss", "redis", "hit", or "expired".',
help: 'Number of requests to the OBO cache. "hit" is the type of hit: "miss", "persistent", "hit", or "expired".',
labelNames,
registers: [proxyRegister],
});
Expand Down
38 changes: 20 additions & 18 deletions server/src/auth/cache/cache.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
import { OboMemoryCache } from '@app/auth/cache/memory-cache';
import { OboRedisCache } from '@app/auth/cache/redis-cache';
import { OboPersistentCache } from '@app/auth/cache/persistent-cache';
import { optionalEnvString } from '@app/config/env-var';

const REDIS_URI = optionalEnvString('REDIS_URI_OBO_CACHE');
const REDIS_USERNAME = optionalEnvString('REDIS_USERNAME_OBO_CACHE');
const REDIS_PASSWORD = optionalEnvString('REDIS_PASSWORD_OBO_CACHE');
const VALKEY_URI = optionalEnvString('VALKEY_URI_OBO_CACHE');
const VALKEY_USERNAME = optionalEnvString('VALKEY_USERNAME_OBO_CACHE');
const VALKEY_PASSWORD = optionalEnvString('VALKEY_PASSWORD_OBO_CACHE');

class OboTieredCache {
#oboRedisCache: OboRedisCache;
#oboPersistentCache: OboPersistentCache;
#oboMemoryCache: OboMemoryCache | null = null;
#isReady = false;

constructor(redisUri: string, redisUsername: string, redisPassword: string) {
this.#oboRedisCache = new OboRedisCache(redisUri, redisUsername, redisPassword);
constructor(valkeyUri: string, valkeyUsername: string, valkeyPassword: string) {
this.#oboPersistentCache = new OboPersistentCache(valkeyUri, valkeyUsername, valkeyPassword);
this.#init();
}

async #init() {
await this.#oboRedisCache.init();
const allTokenMessages = await this.#oboRedisCache.getAll();
await this.#oboPersistentCache.init();
const allTokenMessages = await this.#oboPersistentCache.getAll();
const oboMemoryCache = new OboMemoryCache(allTokenMessages);
this.#oboMemoryCache = oboMemoryCache;
this.#oboRedisCache.addTokenListener(({ key, token, expiresAt }) => oboMemoryCache.set(key, token, expiresAt));
this.#oboPersistentCache.addTokenListener(({ key, token, expiresAt }) => oboMemoryCache.set(key, token, expiresAt));
this.#isReady = true;
}

Expand All @@ -36,12 +36,12 @@ class OboTieredCache {
return memoryHit.token;
}

const redisHit = await this.#oboRedisCache.get(key);
const persistentHit = await this.#oboPersistentCache.get(key);

if (redisHit !== null) {
this.#oboMemoryCache.set(key, redisHit.token, redisHit.expiresAt);
if (persistentHit !== null) {
this.#oboMemoryCache.set(key, persistentHit.token, persistentHit.expiresAt);

return redisHit.token;
return persistentHit.token;
}

return null;
Expand All @@ -59,11 +59,11 @@ class OboTieredCache {

public async set(key: string, token: string, expiresAt: number): Promise<void> {
this.#oboMemoryCache?.set(key, token, expiresAt);
await this.#oboRedisCache.set(key, token, expiresAt);
await this.#oboPersistentCache.set(key, token, expiresAt);
}

public get isReady(): boolean {
return this.#isReady && this.#oboRedisCache.isReady;
return this.#isReady && this.#oboPersistentCache.isReady;
}
}

Expand All @@ -89,8 +89,10 @@ class OboSimpleCache {
}
}

const hasRedis = REDIS_URI !== undefined && REDIS_USERNAME !== undefined && REDIS_PASSWORD !== undefined;
const hasValkey = VALKEY_URI !== undefined && VALKEY_USERNAME !== undefined && VALKEY_PASSWORD !== undefined;

export const oboCache = hasRedis ? new OboTieredCache(REDIS_URI, REDIS_USERNAME, REDIS_PASSWORD) : new OboSimpleCache();
export const oboCache = hasValkey
? new OboTieredCache(VALKEY_URI, VALKEY_USERNAME, VALKEY_PASSWORD)
: new OboSimpleCache();

export const getCacheKey = (navIdent: string, appName: string) => `${navIdent}-${appName}`;
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
import { memoryCacheGauge, redisCacheGauge, redisCacheSizeGauge } from '@app/auth/cache/cache-gauge';
import { memoryCacheGauge, persistentCacheGauge, persistentCacheSizeGauge } from '@app/auth/cache/cache-gauge';
import type { TokenMessage } from '@app/auth/cache/types';
import { getLogger } from '@app/logger';
import { type RedisClientType, createClient } from 'redis';
import { type RedisClientType as ValkeyClientType, createClient } from 'redis';

const log = getLogger('obo-redis-cache');
const log = getLogger('obo-persistent-cache');

export type TokenListener = (message: TokenMessage) => void;

const TOKEN_CHANNEL = 'obo-token';

export class OboRedisCache {
#client: RedisClientType;
#subscribeClient: RedisClientType;
export class OboPersistentCache {
#client: ValkeyClientType;
#subscribeClient: ValkeyClientType;

#listeners: TokenListener[] = [];

constructor(url: string, username: string, password: string) {
this.#client = createClient({ url, username, password, pingInterval: 3_000 });
this.#client.on('error', (error) => log.error({ msg: 'Redis Data Client Error', error }));
this.#client.on('error', (error) => log.error({ msg: 'Valkey Data Client Error', error }));

this.#subscribeClient = this.#client.duplicate();
this.#subscribeClient.on('error', (error) => log.error({ msg: 'Redis Subscribe Client Error', error }));
this.#subscribeClient.on('error', (error) => log.error({ msg: 'Valkey Subscribe Client Error', error }));
}

public async init() {
Expand Down Expand Up @@ -51,7 +51,7 @@ export class OboRedisCache {

#refreshCacheSizeMetric = async () => {
const count = await this.#client.dbSize();
redisCacheSizeGauge.set(count);
persistentCacheSizeGauge.set(count);
};

public async getAll(): Promise<TokenMessage[]> {
Expand Down Expand Up @@ -91,32 +91,32 @@ export class OboRedisCache {
* ttl() gets remaining time to live in seconds.
* Returns -2 if the key does not exist.
* Returns -1 if the key exists but has no associated expire.
* @see https://redis.io/docs/latest/commands/ttl/
* @see https://valkey.io/commands/ttl/
*/
const [token, ttl] = await Promise.all([this.#client.get(key), this.#client.ttl(key)]);

if (token === null || ttl === -2) {
redisCacheGauge.inc({ hit: 'miss' });
persistentCacheGauge.inc({ hit: 'miss' });

return null;
}

if (ttl === -1) {
redisCacheGauge.inc({ hit: 'invalid' });
persistentCacheGauge.inc({ hit: 'invalid' });
this.#client.del(key);
this.#refreshCacheSizeMetric();

return null;
}

if (ttl === 0) {
redisCacheGauge.inc({ hit: 'expired' });
persistentCacheGauge.inc({ hit: 'expired' });

return null;
}

redisCacheGauge.inc({ hit: 'hit' });
memoryCacheGauge.inc({ hit: 'redis' });
persistentCacheGauge.inc({ hit: 'hit' });
memoryCacheGauge.inc({ hit: 'persistent' });

return { token, expiresAt: now() + ttl };
}
Expand Down
4 changes: 2 additions & 2 deletions server/src/plugins/crdt/collaboration-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { getDocument } from '@app/plugins/crdt/api/get-document';
import { setDocument } from '@app/plugins/crdt/api/set-document';
import { type ConnectionContext, isConnectionContext } from '@app/plugins/crdt/context';
import { hasOwn, isObject } from '@app/plugins/crdt/functions';
import { getRedisExtension } from '@app/plugins/crdt/redis';
import { getValkeyExtension } from '@app/plugins/crdt/valkey';
import type { CloseEvent } from '@hocuspocus/common';
import { Server } from '@hocuspocus/server';
import { applyUpdateV2 } from 'yjs';
Expand Down Expand Up @@ -231,7 +231,7 @@ export const collaborationServer = Server.configure({
logContext('Saved document to database', context, 'debug');
},

extensions: isDeployed ? [getRedisExtension()].filter(isNotNull) : [],
extensions: isDeployed ? [getValkeyExtension()].filter(isNotNull) : [],
});

const getCloseEvent = (reason: string, code: number): CloseEvent => ({ reason, code });
Expand Down
26 changes: 0 additions & 26 deletions server/src/plugins/crdt/redis.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
export interface RedisOptions {
export interface ValkeyOptions {
/**
* The URL of the Redis server.
* The URL of the Valkey server.
* @example 'redis://localhost:6379'
*/
readonly url: string;
/**
* The username for the Redis server.
* The username for the Valkey server.
* @example 'username'
*/
readonly username?: string;
/**
* The password for the Redis server.
* The password for the Valkey server.
* @example 'password'
*/
readonly password?: string;
Expand Down
Loading