Problems using Railway's Redis Private Networking (Solved)

I have the following NestJs service:
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { Redis } from 'ioredis';
import { EnvService } from 'src/env/env.service';

@Injectable()
export class RedisService extends Redis implements OnModuleInit {
private readonly logger = new Logger(RedisService.name);
public readonly redisUrl = this.envService.get('REDIS_URL');
public readonly redisProtocol = this.envService.get('REDIS_PROTOCOL');

constructor(private readonly envService: EnvService) {
const redisUrl = envService.get('REDIS_URL');
const redisProtocol = envService.get('REDIS_PROTOCOL');

super(redisUrl, {
family: redisProtocol,
maxRetriesPerRequest: null,
connectTimeout: 30000, // 30 seconds
});

this.redisUrl = redisUrl;
this.redisProtocol = redisProtocol;

this.logger.log('creating redis client', redisUrl, redisProtocol);
}

async onModuleInit(): Promise<void> {
await new Promise<void>((resolve, reject) => {
this.logger.log('connecting to redis...');

this.on('connect', () => {
this.logger.log('connected to redis');
resolve();
});

this.on('error', (error) => {
this.logger.error('Error connecting to Redis:', error);
reject(error);
});
});
}
}
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { Redis } from 'ioredis';
import { EnvService } from 'src/env/env.service';

@Injectable()
export class RedisService extends Redis implements OnModuleInit {
private readonly logger = new Logger(RedisService.name);
public readonly redisUrl = this.envService.get('REDIS_URL');
public readonly redisProtocol = this.envService.get('REDIS_PROTOCOL');

constructor(private readonly envService: EnvService) {
const redisUrl = envService.get('REDIS_URL');
const redisProtocol = envService.get('REDIS_PROTOCOL');

super(redisUrl, {
family: redisProtocol,
maxRetriesPerRequest: null,
connectTimeout: 30000, // 30 seconds
});

this.redisUrl = redisUrl;
this.redisProtocol = redisProtocol;

this.logger.log('creating redis client', redisUrl, redisProtocol);
}

async onModuleInit(): Promise<void> {
await new Promise<void>((resolve, reject) => {
this.logger.log('connecting to redis...');

this.on('connect', () => {
this.logger.log('connected to redis');
resolve();
});

this.on('error', (error) => {
this.logger.error('Error connecting to Redis:', error);
reject(error);
});
});
}
}
In the Deploy Logs it prints the following values: [Nest] 25 - 02/27/2024, 2:52:04 PM LOG [RedisService] creating redis client [Nest] 25 - 02/27/2024, 2:52:04 PM LOG [RedisService] redis://default:BMdLME2HPo6igkB4bpgGHMMiA21MBiBI@redis-eblx.railway.internal:6379 [Nest] 25 - 02/27/2024, 2:52:04 PM LOG [RedisService] 6 [Nest] 25 - 02/27/2024, 2:52:09 PM ERROR [RedisService] Error connecting to Redis: [Nest] 25 - 02/27/2024, 2:52:09 PM ERROR [RedisService] Error: getaddrinfo ENOTFOUND redis-eblx.railway.internal Any idea on what I am doing wrong?
29 Replies
Percy
Percy•4mo ago
Project ID: 03b9d68c-2d80-4600-a0a4-317a53176c97
!0xRafael 🔨👷
03b9d68c-2d80-4600-a0a4-317a53176c97
!0xRafael 🔨👷
I see this in the docs
ioredis is a Redis client for node.js, commonly used for connecting to Redis from a node application. When initializing a Redis client using ioredis, you must specify family=0 in the connection string to support connecting to both IPv6 and IPv4 connections: import Redis from 'ioredis'; const redis = new Redis(process.env.REDIS_PRIVATE_URL + '?family=0'); const ping = await redis.ping();
When I update my constructor to the following it unfortunately still doesn't work.
constructor(private readonly envService: EnvService) {
const redisProtocol = envService.get('REDIS_PROTOCOL');
const redisUrl = `${envService.get('REDIS_URL')}?family=${redisProtocol}`;

super(redisUrl, {
maxRetriesPerRequest: null,
connectTimeout: 30000, // 30 seconds
});

this.redisUrl = redisUrl;
this.redisProtocol = redisProtocol;

this.logger.log('creating redis client', redisUrl, redisProtocol);
}
constructor(private readonly envService: EnvService) {
const redisProtocol = envService.get('REDIS_PROTOCOL');
const redisUrl = `${envService.get('REDIS_URL')}?family=${redisProtocol}`;

super(redisUrl, {
maxRetriesPerRequest: null,
connectTimeout: 30000, // 30 seconds
});

this.redisUrl = redisUrl;
this.redisProtocol = redisProtocol;

this.logger.log('creating redis client', redisUrl, redisProtocol);
}
Logs: [Nest] 26 - 02/27/2024, 3:34:13 PM LOG [RedisService] creating redis client [Nest] 26 - 02/27/2024, 3:34:13 PM LOG [RedisService] redis://default:BMdLME2HPo6igkB4bpgGHMMiA21MBiBI@redis-eblx.railway.internal:6379?family=0 [Nest] 26 - 02/27/2024, 3:34:13 PM LOG [RedisService] 0 [Nest] 25 - 02/27/2024, 3:34:41 PM ERROR [RedisService] Error connecting to Redis: [Nest] 25 - 02/27/2024, 3:34:41 PM ERROR [RedisService] Error: getaddrinfo ENOTFOUND redis-eblx.railway.internal With the public URL it works no problem Thanks for taking the time to respond.
Brody
Brody•4mo ago
looks like you are complicating things quite a bit, just try setting family to 0 in the options object please are you deploying with nixpacks?
!0xRafael 🔨👷
Looks like it
No description
Brody
Brody•4mo ago
okay cool, please try my suggestion
!0xRafael 🔨👷
When I do "const redisUrl = envService.get('REDIS_URL') + '?family=0';" it works, you right. Not sure why, it logs the same
Brody
Brody•4mo ago
awsome!
!0xRafael 🔨👷
Any idea why?
Brody
Brody•4mo ago
typo?
!0xRafael 🔨👷
When I use a text compare tool it is the same. No idea, I don't really have the time to look into it at the moment. Thanks a lot!
Brody
Brody•4mo ago
no problem!
!0xRafael 🔨👷
@Brody, nvm, seems like this one time was luck
Brody
Brody•4mo ago
okay can you add a 3 second sleep to your start script as to delay the starting of your app
!0xRafael 🔨👷
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { EnvService } from './env/env.service';

async function bootstrap() {
const app = await NestFactory.create(AppModule);
const envService = app.get(EnvService);
app.enableCors();
await app.listen(envService.get('PORT'));
}
bootstrap();
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { EnvService } from './env/env.service';

async function bootstrap() {
const app = await NestFactory.create(AppModule);
const envService = app.get(EnvService);
app.enableCors();
await app.listen(envService.get('PORT'));
}
bootstrap();
Before NestFactory.create or after?
Brody
Brody•4mo ago
please add the sleep in the start script, theres really no need to overcomplicate the solutions im trying to provide 🫤
!0xRafael 🔨👷
Ah, I think you mean here like this "start:prod": "sleep 3 && node dist/main" I am not over-complicating it on purpose. I am unsure what a "start script" is from your perspective. The package.json script, the code initializing the application, etc.
Brody
Brody•4mo ago
thats correct
!0xRafael 🔨👷
Seems like it is helping. Why?
No description
Brody
Brody•4mo ago
the private network's dns resolver takes about 3 seconds to start answering lookup requests, though its still only ipv6, so you do need family: 0 still
!0xRafael 🔨👷
Ok, thanks you, interesting. Even after multiple restarts it is working. I will increase it to 5 seconds just to be sure
Brody
Brody•4mo ago
i dont think theres a need for 5 seconds, over a 7 day period the slowest resolver ready time was only ~2.3 seconds
No description
!0xRafael 🔨👷
Interesting stats, very good to know. Now the private networking is solid. Thanks for the patience, I highly appreciate it :salute:
Brody
Brody•4mo ago
no problem! and for the record the team is working to remove any and all startup delays
!0xRafael 🔨👷
Great, can't wait, less unexpected stuff to worry about!
Brody
Brody•4mo ago
less help threads for me too, though it wont solve the need for the family 0 config thats something ioredis really should change, since it currently defaults to ipv4 only (family 4)
!0xRafael 🔨👷
Yeah, I had it on family 6, which did work once, but then after another redeploy it didn't work and I thought I had introduced a bug, but seems like the sleep was the only thing missing. Ipv6 generally still feels like a earlier adopter thing or like an afterthought, which is ridiculous
Duchess
Duchess•2mo ago
New reply sent from Help Station thread:
Just came across this same issue with ioredis and solved by setting family=0, thanks! Just for clarification to anyone else who finds this, the family option can take one of the following values: * 4: Use IPv4 protocol family. (default) * 6: Use IPv6 protocol family. * 0: Let the operating system decide which protocol family to use.
You're seeing this because this thread has been automatically linked to the Help Station thread.