Current section: Multi-Region Data and Deployment 6 exercises

Set Up a Proxy Server for Multi-Region Database Support

Our application is still running in a single instance because we haven't actually set up multiple instances yet.

There's a bit more setup we have to do.

We need to make sure that when a request comes that will lead to a database write that the instance will be able to handle it.

With LiteFS we ca

Loading lesson


Instructor: 0:00 Our app is actually not yet running in multiple regions, and we are not quite ready to do that. We're almost there. What we need to do is make sure that when a request comes in to make a write request, like I hit the increment button, or the decrement button here, that's going to write to the database.

0:17 We need to make sure that request gets routed to the instance that is going to actually be able to handle that write request. It's pretty much all non-get requests we can assume are going to write to the database. For the most part, we can assume that get request will not.

0:33 This is actually a built-in feature to litefs. Basically, what litefs will do, as we can set up a proxy server that sits between our user and our actual app. That proxy server when it receives a request, we'll do a couple of things to make sure that we're OK to proceed to the actual instance.

0:54 If the instance is not the primary node, and we're about to do a write request, then it will actually replay that request into the primary note. It's actually a really nice setup. To get this setup, we update our litefs config to add a proxy.

1:12 This configures the proxy that litefs is going to create for us. The couple of configuration options that we need is adder. For the address, we need this to be the internal port that is used in our fly configuration. If we look at our fly tunnel, this is the internal port.

1:31 I'm actually going to set this up inside of my docker as an environment variable to communicate that is important, that it is the same. In fact, we're going to rename this port to internal port, and then we're going to have a new port for our application to be listening on. This is going to be 8081.

1:50 Now our application's going to start up on port 8081, but then the proxy is going to be the port 8080 that is accessible outside of our virtual machine. The proxy takes traffic from the outside world and then proxy forwards traffic to our own server that's running on our virtual machine.

2:11 The adder here is going to be Cohen internal port. This is the publicly accessible address. The target is going to be localhost:${Port}. This is going to be the locally running instance of our application at that port.

2:29 Then the database, db, is going to need to point to the file that our database is running in. If we look at our Docker file, that's going to be at LITEFS_DIR/sqlite.db. I'm actually going to make another environment variable, ENV DATABASE_FILENAME, and that will equal that. Then we can simply say DATABASE_FILENAME for our database URL.

2:56 With that now, we can take that DATABASE_FILENAME environment variable and interpolate it right here. Now our proxy server is set up to proxy any request that are coming in to our target server and make sure that any write requests don't actually hit non-primary nodes.

3:13 The other thing that we need to do while we're here in the litefs EMO, we need to specify whether something can be a candidate or not. We don't want everything to be a candidate. We put a node in Amsterdam and one in San Jose and we put another in Hong Kong.

3:28 We don't want them all to just say, "Hey, I can be a candidate." Because we want to keep our candidate in a region that most of our users are in. For us, most of our users are going to be around San Jose. I'm going to specify this is ${FLY_REGION = = sjc}, so San Jose.

3:50 Now if you're an Amsterdam region, you're going to say, "No, I can't be a candidate." When you're talking to Consul, it's going to say, "I can't be a candidate who is the primary." That way we keep the primary in sjc.

4:01 Now we are going to deploy two regions to sjc to make sure that we can have one fall over and the other takeover. This is one way that we can make sure that the primary region is in a part of the world that we're OK with it being in.

4:18 The last thing that we need to do here is make sure that our start script is going to work. Because here we're running the Prisma migrations and this will not work if we're not on the primary instance. We need to have some way to say if (isPrimaryInstance), then we can run the migrations, otherwise we don't.

4:36 We need to know some way whether we're currently running in the primary instance. There's a file that you can read that litefs sets up for you to make this really easy. To make it even easier, there's actually a library we can install and we'll save this as a regular dependency called litefs-js.

4:55 The reason this needs to be saved as a regular dependency is because this is going to be running in production. We're going to get the { getInstanceInfo } = require('litefs-js'). This get instance info is going to give us exactly what we want {currentIsPrimary} = is await getInstanceInfo ().

5:19 If the currentIsPrimary then we'll go ahead and run the migrations, otherwise we'll simply start the app. We'll skip that part because those migrations should have already been run by the primary instance. We can add a bunch of logs here too, which I am going to copy paste because I don't think anybody wants to watch me write out a bunch of logs, so we'll just stick all of those there.

5:38 We get the currentInstance, the currentIsPrimary, and the primaryInstance, and we can use those in our logs where you say that this is in the primary region deploying migrations, or this is saying it's not in the primary region, so we're skipping migrations.

5:51 That should be enough for us to go ahead and deploy this. We are still not technically running in multiple regions, but let's commit this multi region. We'll push this up and we'll make sure that those logs appear the way that we expect them to.

6:07 Coming over to our actions, now we can go to multi-region and we get our deploy going right here. Then we can take a look at our logs and we'll see the logs that happen as this new version of our app is deployed.

6:23 Our application is now shutting down and starting back up with our new configuration, and we can see some of these logs coming in. Right here it's disconnecting from the primary retrying and it eventually it reconnects. No problem there. It manages.

6:38 That's the benefit of consoles. It makes sure that you always have a good primary. Don't worry about those logs right there. Then we'll notice here that instance, ebb yada, yada. You'll see that in the logs right there as well in sjc, that's the fly region, is primary deploying migrations.

6:56 There weren't any actual migrations to deploy. You can see the logs right here that we did actually execute fly to deploy those migrations. Now you'll notice also server listening at port 8081. We have our proxy server listening at port 8080.

7:12 Now all traffic is going to go through that proxy server and it's going to make sure that we never actually execute a write against the non-primary region when we're doing like a post, put, delete or anything like that.

7:25 Get requests can go to the region that it's going to, but the proxy will make sure that we don't write to any region that it's not supposed to go to. Now, there's actually a lot to this that the proxy is doing for us.

7:37 If you want to look at the README for litefs-js, then you can take a look at the challenge of Reed replicas and consistency to understand why this proxy and why litefs-js are so useful for us. This is all pretty much handled for us and it's really nice.

7:55 There are some caveats. If you ever try to write to the database inside of a get request, you do need to use a couple other utilities from litefs-js. For the most part, 99 percent of the time you should not have to actually change any other of your application code for this to work. Your application should work swimmingly in multiple regions with this setup.

8:16 In review, the things that we changed here was we updated our docker file to have a couple different environment variables. One, we specified the internal port, which equals the internal port that we have specified here.

8:28 We're using that internal port for the adder of our proxy so it knows what port it should be listening at. Then we updated the port for our actual application to be port 8081. In our litefs, we say our target is going to be at that port.

8:44 We also update our database URL to use a database file name environment variable so that we could use that same database file name in our litefs configuration here, so litefs knows which database it should be handling transactional consistency.

9:00 Because you actually can have multiple databases in a single litefs instance, but only one proxy for this support to ensure we're writing to the right instance. Then we also updated our start script to use litefs-js to make sure that we're only running the migrations on the primary instance, and our application is now running in a way that we're ready to actually deploy to multiple regions.

9:29 The last thing we updated, this is actually optional, but the last thing we updated was we set the candidate to be sjc. It can only be this region. What we're going to do is what we have right here is actually basically static leasing because we're saying only one region can be a candidate.

9:49 What we'll do is we'll actually deploy to two regions, or two instances in the sjc region so that if one of those falls over, the other can pick it up. That's what we did to get our application ready to go multi-region, and we're going to go multi-region next.