Moving from Netlify + Railway to Render

The other day I was getting a 502 error when doing a complex query to my database. I knew the problem, my query was running longer than 10 seconds. With Netlify’s functions being a wrapper around aws lambda calls that’s a limitation of using Netlify.

To solve this I decided I’d move from Netlify + Railway to Render.

Moving to Render from Netlify + Railway

I wanted to write this down so others may find it useful. A few thing about my site.

Lets get into it.

Table of Contents

  1. Create a branch and setup the render deploy
  2. Deploy to render
  3. Connect the Web Side to the API Side
  4. Moving the data
    1. Download DBGate
    2. Connect to your existing database
    3. Connect to your new database on Render
    4. Move the data via Import Wizard
    5. Fixing your autoincrement columns
  5. Setting your environment variables
  6. Logging in
  7. Configuring your custom domain
  8. Adding Custom Headers
  9. Addressing any custom routes
  10. Adding Redis to the mix
    1. Update render.yaml
    2. Add envelop packages
    3. Configure graphql.js
    4. Create redis.js
    5. Deploy and set the environment variables

Create a branch

First I created a branch on my repo called “move-to-render” but this will let you make the changes without breaking the current site.
git checkout -b move-to-render

Then we need to setup the render deploy
yarn rw setup deploy render

This creates a render.yaml file which we will need to make some changes to after we have render make our services initially.

Add the changes to your new branch and push them up.
git add .
git commit -m "added render.yaml to deploy to render"
git push

Deploy to Render.com

Now we’re going to follow these steps (I’m summarizing below): Deploy RedwoodJS | Render · Cloud Hosting for Developers

  1. create/login to your account on render.com
  2. From your dashboard, click New, then blueprint, It’ll ask for your repository, select it, then choose the branch you defined above, “deploy-to-render”
  3. Take a 5 minute break to allow it all to build.

Connect the Web Side to the API Side

Now we need to wire up the front end to the web services, to do this we need to change the render.yaml file. First we need the URL for the API services.

  1. Goto your dashboard
  2. find the web service you just deployed, click it
  3. Note the URL for the service on that page, copy it.
  4. Open render.yaml and find the source: /.redwood/function/*
  5. Update the next line (should be destination) value to your api endpoint followed by /*
  6. It should look like
    destination: https://whateverYourApiUrlIs.onrender.com/*
  7. Save that. and push it up.
    git add .
    git commit -m "updated rewrite rules"
    git push

Moving the data

This is a more complex step so I’m going to break it down into steps.

Download DBGate

I use a tool called dbgate to connect, extract and moving my data. You can use any tool you’d like but I found this worked for me.

Once downloaded lets open it up.

Connect to your existing database

We’re going to add a connection here.

  1. Click Add a new connection.
  2. Choose Postgres
  3. Choose use Database URL
  4. Login to netlify and copy your DATABASE_URL value
  5. Name this “Railway Database”

Connect to your new database on Render

  1. Add a new connection to your new database
  2. Choose Postgres
  3. Choose use databse url
  4. log in to render.com and goto your the database
  5. copy the External Database URL.
  6. Paste that in the database url
  7. Append ?ssl=true to the database url.
  8. Name it something like “Render Database”

Move the data via Import Wizard

Now that you have both databases lets do the import. Before we get started you may need to move your tables in a hierarchy order. I mean move the tables with no dependencies and add the ones with dependencies after they are made.

  1. Right click on the render database in the top left and click Import wizard
    image
  2. This loads up a new interface.
  3. On the left
    1. Storage Type: Database
    2. Server: Netlify Database
    3. Database: postgres
    4. Tables, select all the tables you name in your schema.prisma file and _prisma_migrations
  4. On the right
    1. Storage Type: Database
    2. Server: Render Database
    3. Database: postgres
  5. Now your data may have dependencies, and in those cases you’ll need to import in stages. e.g. if you have a users table and a group member table and a group table. You can probably import users and groups but you’ll need to import group member AFTER users and groups is moved. With that caveat it took me some time to get this to move over with out errors.
  6. Start running these imports until you get them all.

Fixing your autoincrement columns

My autoincrement fell out of sync for two of my tables. If you’re getting an error later when you’re creating records I had to run the following sql against each table.
SELECT setval(pg_get_serial_sequence('"ItemView"', 'id'), coalesce(max(id)+1, 1), false) FROM "ItemView";
You’ll need to replace the table (ItemView) and id field appropriately but that fixed me all up.

Setting your environment variables

Let’s set up our environment variables. With DBAuth you need to set a session secret, you can reuse your session secret from your past host, OR you can generate a new one. I reused my old one but generating a new one would mean also resetting your password to authenticate. To set the environment variable on render goto the API service, on the left is a environment link. Add a new one wiht the name SESSION_SECRET and the value from your terminal or your previous host.
yarn rw g secret

Logging in

At this point you should be able to either log in or reset your password. Try it.
If you’re using a new secret, you’ll need to get your reset token and reset it.
You can connect locally by connecting to prisma;
yarn rw prisma studio
navigate to your user table, find your account, copy the reset token and goto your sites reset password link: /reset-password?resetToken=PASTEYOURTOKENHERE

Configuring your custom domain

I am using my own domain. I assume you’ll want to too.
In Render this was really simple.

  1. Goto the Static Site and click settings.
  2. scroll down until you get to the custom domains
  3. add your domain
  4. follow instructions and update your dns to match the new domain
    In my case I was taking the old domain. So I had to delete the ipv4 and ipv6 versions on netlify’s domain management. This will likely be similiar for you but may be different depending how you manage your DNS entries.

Adding Custom Headers

Let’s add on those headers missing from before.
Back in the render.yaml file find the below the routes you midified earlier is a big comment block, I removed this here. Than I added a new section called headers at the same level;
image

You’ll need to add one for each header you want.

Addressing any custom routes

I had a custom function to build a external RSS feed I forgot about. To add that so /index.xml rendered the equivalent of /.redwood/functions/rss I just needed to add another route to my render.yaml file.

routes:
  - type: rewrite
    source: /index.xml
    destination: https://news-tskr-io-api.onrender.com/rss
  

Congratulations

Edit 1 2022-11-10 Adding Redis to the mix!

So I also had Redis set up at one point and I wanted to set this back up. I’m still learning how to deal with caching but I wanted to share how to set this up on Render in 4 Steps.

Step one, update render.yaml

In your render.yaml file you’ll want to add this as another service;

- type: redis
  name: cache
  #ipAllowList: [] # only allow internal connections
  plan: free # optional (defaults to starter)
  maxmemoryPolicy: allkeys-lfu # optional (defaults to allkeys-lru). Rails recommends allkeys-lfu as a default.
  ipAllowList:
  - source: 0.0.0.0/0 #I wasn't able to get this work using just the internal connection
    description: public

That will add another item to your dashboard’s blueprint for your service.

Step two, add the envelop packages

Next you’ll need to add the packages for redis;
yarn workspace api add @envelop/response-cache
yarn workspace api add @envelop/response-cache-redis

Step three, configure graphql.js

Next you need to configure graphql to use it. Open ./api/src/functions/graphql.js and make these changes

// Add these two lines below
import { useResponseCache } from '@envelop/response-cache'
import { createRedisCache } from '@envelop/response-cache-redis'

import { createGraphQLHandler } from '@redwoodjs/graphql-server'
import directives from 'src/directives/**/*.{js,ts}'
import sdls from 'src/graphql/**/*.sdl.{js,ts}'
import services from 'src/services/**/*.{js,ts}'
import { getCurrentUser } from 'src/lib/auth'
import { db } from 'src/lib/db'
import { logger } from 'src/lib/logger'
import { redis } from 'src/lib/redis' // this is needed still but we'll do this next

export const handler = createGraphQLHandler({
  loggerConfig: { logger, options: {} },
  getCurrentUser,
  directives,
  sdls,
  services,
  extraPlugins: [
  // this here needs to be added for the redis cache
    useResponseCache({
      cache: createRedisCache({ redis }),
      ttl: 0, // 1 minute
      includeExtensionMetadata: true,
      ttlPerSchemaCoordinate: {
  // there's more ways to control this but here's my first stab at it
        'Query.PublicFeeds': 1000 * 60 * 5, // 5 minutes in ms
        'Query.searchPublicItems': 1000 * 60 * 5, // 5 minute in ms
      },
    }),
  ],
  onException: () => {
    // Disconnect from your database with an unhandled exception.
    db.$disconnect()
  },
})

Step four, create redis.js

You’ll need to create a ./api/src/lib/redis.js file. I had this using the redis url before but I couldn’t get that to work on Render. Here’s my file’s content;

import Redis from 'ioredis'

//export const redis = new Redis(process.env.REDIS)
export const redis = new Redis({
  username: process.env.REDIS_SERVICE_NAME, // Render Redis name, red-xxxxxxxxxxxxxxxxxxxx
  host: process.env.REDIS_HOST, // Render Redis hostname, REGION-redis.render.com
  password: process.env.REDIS_PASSWORD, // Provided password
  port: process.env.REDIS_PORT || 6379, // Connection port
  tls: true, // TLS required when externally connecting to Render Redis
})

Step five, deploy to render, and set the environment variables

Now you’ll need to do a do a push to have Render build you’re redis cache, but when you do you will be able to capture the service, host, password from the External Redis URL.


Click on the External Redis URL and we’ll tear it down.
rediss://serviceName:password@redisHost.render.com:6379
So if this were my string I’d make environment variables like such (locally) and also on render’s API service;

REDIS_HOST=redisHost.render.com
REDIS_PASSWORD=password
REDIS_SERVICE_NAME=serviceName

You may need to do another deploy here to trigger the builds with your variables.

2 Likes

Jace, good to see you around and thanks for taking the time to write this up and post it! It is duplicated though. At the end of the first “Add those headers” section, the entire thing is pasted again. After all your work putting this together, I would be happy to fix it - if you want to be hands off :smiley:

Much appreciated - Barrett

1 Like

Thanks! I did an edit and must have somehow duplicated the content.

Excellent Write-up.

For anyone on netlify experiencing the issue above you can request and increase to the serverless function timeout so you don’t error out. Of course, the experience for you user who has to wait around for a long time will be a little :poop:

Excited to see them fix up the issue in this post. Render will be an amazing solution for Redwood users

I would propose that would audit and profile your query as there is likely an optimization to be made:

  • n+1 that’s in need of eager loading
  • too much data returned
  • lack of indexes for query
  • database in different region for extra latency

A 10 second response for a query with user interactivity isn’t what anyone wants so even if you move to a non lambda function deploy that’s just kicking the problem down the road in my opinion.

2 Likes

Yea, it could be some of those things, but the page load time now for taht same query in a co-located or at least same host was > 10s, and is now 2.5-3s. I know this is a mess of a thing but some of it is just me learning how to do things, with that being said I have it hosted on both still so I can compare.

I was getting into the paid tiers at at Railway and was wondering how comparable Render was to that as well.