docs
docs copied to clipboard
Issue with the "Session Affinity (a.k.a. Sticky Sessions)" doc
I found an issue with this document.
Title: Session Affinity (a.k.a. Sticky Sessions) Location: https://fly.io/docs/blueprints/sticky-sessions/ Source: https://github.com/superfly/docs/blob/main/blueprints/sticky-sessions.html.md
Describe the issue
The whole section about "Fly-Force-Instance_Id" makes little sense.
It talks about some @hotwired/stimulus, which I (as a reader) have no exposure to.
All I want is to drop a cookie on the client's device, and if a request comes in with that ID, it routes to the appropriate server, and if that server cannot be found, then it routes to a random server and sets a new cookie.
How do I do that?
Clients accessing Fly.io apps may set the fly-force-instance-id header to ensure that the request is sent to only a specific Machine.
If the Machine is deleted or not found, no other Machines will be tried.
https://fly.io/docs/networking/dynamic-request-routing/#the-fly-replay-response-header
The entire document has no mention of cookies, so it seems like that's not even possible.
Even if I somehow injected the header, the fact that "If the Machine is deleted or not found, no other Machines will be tried.", makes it useless.
I’ll start by saying that I agree that ideally this should be handled by the platform, and perhaps some day it will be. Meanwhile…
Consider a deployment with two VMs: VM1 and VM2.
A POST request comes in with some data. It gets routed to VM1. That VM writes the data to the filesystem. Before responding it gets FLY_MACHINE_ID from the ENV and adds it as a cookie.
Subsequently a GET request comes in requiring that same data. Unfortunately it gets routed to VM2. VM2 compares the cookie against FLY_MACHINE_ID and sees that it is different. So instead of proceeding, it responds with a Fly-Replay header specifying the desired machine. Fly.io replays the same get request, but this time on VM1, which successfully processes the request.
https://community.fly.io/t/sticky-sessions/19615/4
Not sure if this will work with SSE, but I will give it a try.
Hopefully this helps others. Here is a fastify solution:
if (config.FLY_MACHINE_ID) {
const { getMachines, stop } = createMachinePoller();
registerShutdownHandler({
name: 'fly-machine-poller',
run: async () => {
stop();
},
weight: 400,
});
app.addHook('onRequest', async (request, reply) => {
const machineCookie = request.cookies['glama-machine-id'];
if (!machineCookie) {
reply.setCookie('glama-machine-id', config.FLY_MACHINE_ID, {
maxAge: getDuration('1 day', 'milliseconds'),
});
return;
}
if (machineCookie !== config.FLY_MACHINE_ID) {
const startedMachineIds = getMachines()
.filter((machine) => machine.state === 'started')
.map((machine) => machine.id);
if (startedMachineIds.includes(machineCookie)) {
reply
.header('fly-replay', `instance=${machineCookie}`)
.code(307)
.send();
}
}
});
}
After several failed attempts, here is something that works.
// https://fly.io/docs/networking/dynamic-request-routing/
app.addHook('onRequest', async (request, reply) => {
// The current machine is terminating, so we need to find a new machine to replay.
if (shutdownHandler.getStatus() === 'terminating') {
const startedMachineIds = getMachines()
.filter((machine) => machine.state === 'started')
.filter((machine) => machine.id !== config.FLY_MACHINE_ID)
.map((machine) => machine.id);
const machineId = startedMachineIds[0];
if (machineId) {
reply
.header('fly-replay', `instance=${machineId};elsewhere=true`)
.code(307)
.send();
} else {
reply.code(503).send('Could not find a machine to replay');
}
return;
}
const machineCookie = request.cookies['glama-machine-id'];
// We are handling a request if the machine cookie is not set
// or if request is being replayed from a different machine.
if (!machineCookie || request.headers['fly-replay-src']) {
reply.setCookie('glama-machine-id', config.FLY_MACHINE_ID, {
maxAge: getDuration('1 day', 'milliseconds'),
});
return;
}
// We are asking another machine to replay the request
// if the machine cookie does not match the current machine.
if (machineCookie !== config.FLY_MACHINE_ID) {
const startedMachineIds = getMachines()
.filter((machine) => machine.state === 'started')
.map((machine) => machine.id);
if (startedMachineIds.includes(machineCookie)) {
reply
.header('fly-replay', `instance=${machineCookie}`)
.code(307)
.send();
}
}
});