-
Notifications
You must be signed in to change notification settings - Fork 3.4k
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
nv-2054-implement-redis-cluster-service #3177
Conversation
NV-2054 Implement Redis cluster service
Definition of done:
|
@@ -0,0 +1,15 @@ | |||
name: Setup Novu Redis Cluster |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Action to create a Redis Cluster to be used by the CI/CD in the tests.
@@ -32,6 +32,7 @@ jobs: | |||
steps: | |||
- uses: actions/checkout@v3 | |||
- uses: ./.github/actions/setup-project | |||
- uses: ./.github/actions/setup-redis-cluster |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Precondition for the tests.
858b51a
to
cc53e0b
Compare
apps/api/src/.example.env
Outdated
REDIS_CACHE_SERVICE_HOST=localhost | ||
REDIS_CACHE_SERVICE_PORT=6379 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would prefer if by default cache will be disabled locally, wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we have that mentioned in the documentation I do agree.
It is:
Caching is turned off by default, but can easily be activated by setting the following environment variables:
@@ -10,6 +10,8 @@ import { DistributedLockService } from './distributed-lock.service'; | |||
|
|||
const originalRedisCacheServiceHost = process.env.REDIS_CACHE_SERVICE_HOST; | |||
const originalRedisCacheServicePort = process.env.REDIS_CACHE_SERVICE_PORT; | |||
const originalRedisClusterServiceHost = process.env.REDIS_CLUSTER_SERVICE_HOST; | |||
const originalRedisClusterServicePorts = process.env.REDIS_CLUSTER_SERVICE_PORTS; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be?
const originalRedisClusterServicePorts = process.env.REDIS_CLUSTER_SERVICE_PORTS; | |
const originalRedisClusterServicePorts = process.env.REDIS_CLUSTER_SERVICE_PORT; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, the name is right as this is for the case where we provide the Redis Cluster with the ports nodes into an array. Nothing to do with the Elasticache cluster set up. This is the fallback.
}); | ||
|
||
inMemoryProviderClient.on('ready', () => { | ||
Logger.warn('In-memory cluster ready', LOG_CONTEXT); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Logger.warn('In-memory cluster ready', LOG_CONTEXT); | |
Logger.log('In-memory cluster ready', LOG_CONTEXT); |
I think better suited as it's a success one
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I set it up higher as the readiness was being a problem because my infra problems. I can revert it back. 👍🏻
const { host, ttl } = getConfig(); | ||
|
||
if (!host) { | ||
Logger.log('Missing host for in-memory cluster provider', LOG_CONTEXT); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Logger.log('Missing host for in-memory cluster provider', LOG_CONTEXT); | |
Logger.warn('Missing host for in-memory cluster provider', LOG_CONTEXT); |
cc53e0b
to
6cbb287
Compare
apps/api/src/.env.test
Outdated
@@ -22,6 +22,19 @@ REDIS_CACHE_KEEP_ALIVE= | |||
REDIS_CACHE_FAMILY= | |||
REDIS_CACHE_KEY_PREFIX= | |||
|
|||
IN_MEMORY_CLUSTER_MODE_ENABLED=true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For testing we will use the Redis Cluster instance we are adding to the CI/CD. For running locally it would be needed to run one for tests to pass unless IN_MEMORY_CLUSTER_MODE_ENABLED
is set to false and the tests for the InMemoryService are skipped.
const stream = client.scanStream({ | ||
match: pattern, | ||
}); | ||
const stream = this.inMemoryProviderService.inMemoryScan(pattern); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@djabarovgeorge here is where I had to do this proxy to solve the lack of availability of this method in the Redis Cluster instances.
const DEFAULT_TTL_SECONDS = 60 * 60 * 2; | ||
const DEFAULT_CONNECT_TIMEOUT = 50000; | ||
const DEFAULT_KEEP_ALIVE = 30000; | ||
const DEFAULT_FAMILY = 4; | ||
const DEFAULT_KEY_PREFIX = ''; | ||
const TTL_VARIANT_PERCENTAGE = 0.1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Carrying away the existing Redis instance configurations. Worth to be reviewed.
dnsLookup: (address, callback) => callback(null, address), | ||
redisOptions: { | ||
tls: {}, | ||
}, | ||
/* | ||
* Disabled in Prod as affects performance | ||
*/ | ||
showFriendlyErrorStack: process.env.NODE_ENV !== 'prod', | ||
slotsRefreshTimeout: 2000, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Specific Elasticache configuration but showFriendlyErrorStack
.
} | ||
} | ||
|
||
public getOptions(): RedisOptions | undefined { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Downstream the Redis Cluster can have Redis Options set up and configured just like a single instance, but they live in a different place.
}, | ||
}; | ||
|
||
return this.isElasticacheEnabled() ? clusterProviders.elasticache : clusterProviders.redis; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If Elasticache configuration is set in the environment we use that provider and if not we fall back to the Redis Cluster provider that needs automated setting in the CI/CD and in your local environment when running the tests. If the Cluster mode is enabled in the configuration through IN_MEMORY_CLUSTER_MODE_ENABLED
.
REDIS_CLUSTER_SERVICE_HOST: str({ | ||
default: '', | ||
}), | ||
REDIS_CLUSTER_SERVICE_PORTS: str({ | ||
default: '', | ||
}), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if we should validate the Elasticache variables as they are optional and depending on the environment deployed.
@@ -36,16 +36,18 @@ import { UserService } from './user.service'; | |||
/** | |||
* TODO: move this to a reusable area | |||
*/ | |||
const connection = { | |||
db: Number(process.env.REDIS_DB_INDEX || '1'), | |||
port: Number(process.env.REDIS_PORT || 6379), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This and other hardcodes regarding the port need to be addressed in another PR.
3cf76e3
to
43c642e
Compare
REDIS_CLUSTER_SERVICE_HOST=localhost | ||
REDIS_CLUSTER_SERVICE_PORTS=[7000,7001,7002,7003,7004,7005] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@p-fernandez I think we should remove this from the .env.test, so that tests are running against single node on our machines, but in ci we can override those env variables in runtime
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can do that just by setting: IN_MEMORY_CLUSTER_MODE_ENABLED
to false
. Just want to highlight that in-memory-provider.service.spec.ts
won't pass.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, we can do that
}; | ||
|
||
Logger.log(`Initializing Elasticache Cluster with ${instances?.length} instances`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not going to count the real instances as the set up for Elasticache is different. Not sure if through the URL or any API call we would know the exact number.
@@ -86,6 +87,8 @@ export const getRedisCluster = (): Cluster | undefined => { | |||
slotsRefreshTimeout: 2000, | |||
}; | |||
|
|||
Logger.log(`Initializing Redis Cluster Provider with ${instances?.length} instances`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is ok. 👍🏻
- name: Run a test | ||
- name: Run E2E tests | ||
env: | ||
IN_MEMORY_CLUSTER_MODE_ENABLED: true | ||
run: | | ||
cd apps/api && pnpm test:e2e | ||
pnpm test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this line will run unit tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ooops forgot to remove it
What change does this PR introduce?
Adds two in memory cluster providers (Redis based configuration and Elasticache provider; both using Redis Cluster downstream) and connects it to the API, enabling Cache Service and Distributed Lock service to take advantage of a Redis Cluster instance for improving the performance.
It also adds the tests and the CI/CD changes needed for those to run.
Why was this change needed?
One of the performance improvements we are executing.
Other information (Screenshots)
Several environment variables need to be added and set depending on the different environments configuration.
For DEV environment we would like to set:
IN_MEMORY_CLUSTER_MODE_ENABLED=true
ELASTICACHE_CLUSTER_SERVICE_HOST=<ElastiCache_URL>
ELASTICACHE_CLUSTER_SERVICE_PORT=<ElastiCache_PORT>
For CI/CD and tests environments we would like to set:
IN_MEMORY_CLUSTER_MODE_ENABLED=true
REDIS_CLUSTER_SERVICE_HOST=localhost
REDIS_CLUSTER_SERVICE_PORTS=[7000,7001,7002,7003,7004,7005]