Skip to content

Commit d6d780a

Browse files
committed
sticky router: more sensible defaults
1 parent 78deaad commit d6d780a

File tree

2 files changed

+39
-8
lines changed

2 files changed

+39
-8
lines changed

src/packages/conat/core/server.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -767,16 +767,23 @@ export class ConatServer extends EventEmitter {
767767
if (targets.size == 0) {
768768
return undefined;
769769
}
770+
if (targets.size == 1) {
771+
// currently there is a canonical choice
772+
for (const target of targets) {
773+
return target;
774+
}
775+
}
770776
try {
771-
const target = await stickyChoice({
772-
client: this.stickyClient,
777+
return await stickyChoice({
773778
pattern,
774779
subject,
775780
targets,
781+
// client -- so can call the remote sticky router if necessary
782+
client: this.stickyClient,
783+
// updateSticky & getStickyTarget -- so can consule/update the cache
776784
updateSticky: this.updateSticky,
777785
getStickyTarget: this.getStickyTarget,
778786
});
779-
return target;
780787
} catch (err) {
781788
this.log("WARNING: sticky router is not working", err);
782789
// not possible to make assignment, e.g., not able

src/packages/conat/core/sticky.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,15 @@ export function consistentHashingChoice(
3030
return hr.get(hash_string(resource));
3131
}
3232

33+
// the subject that is used for the sticky router service
3334
const SUBJECT = "sticky.one";
34-
const DEFAULT_TTL = 15_000;
35+
36+
const DEFAULT_CHOICE_TTL = 60_000 * 60 * 24 * 30; // 30 days
37+
38+
const DEFAULT_CLIENT_TTL = 15_000; // 15 seconds
39+
40+
// NOTE: there are no assumptions here about clocks being synchronized. These
41+
// are just ttl's.
3542

3643
export function stickyKey({ pattern, subject }) {
3744
return pattern + " " + subject;
@@ -60,7 +67,24 @@ export function getStickyTarget({
6067
return undefined;
6168
}
6269

63-
export async function createStickyRouter({ client }: { client: Client }) {
70+
export async function createStickyRouter({
71+
client,
72+
// when the stick router service makes a choice, it keeps it this
73+
// long, or until the choice it made is no longer valid (i.e., the target
74+
// vanishes). This may as well be infinite, but it is nice to have the
75+
// option to discard choices from memory to avoid leaks.
76+
choiceTtl = DEFAULT_CHOICE_TTL,
77+
// The client trusts a choice returned from the router for this long,
78+
// or until the target is no longer available. Thus if the target
79+
// is randomly vanishing and coming back and a reassignment gets made,
80+
// this client would definitely find out if necessary within this amount of time.
81+
// Basically this is roughly how long failover may take.
82+
clientTtl = DEFAULT_CLIENT_TTL,
83+
}: {
84+
client: Client;
85+
choiceTtl?: number;
86+
clientTtl?: number;
87+
}) {
6488
const sub = await client.subscribe(SUBJECT);
6589
const stickyCache: { [key: string]: { target: string; expire: number } } = {};
6690

@@ -76,9 +100,9 @@ export async function createStickyRouter({ client }: { client: Client }) {
76100
if (target == null || !targets.includes(target)) {
77101
// make a new choice
78102
target = consistentHashingChoice(targets, subject);
79-
stickyCache[key] = { target, expire: Date.now() + DEFAULT_TTL };
103+
stickyCache[key] = { target, expire: Date.now() + choiceTtl };
80104
}
81-
await mesg.respond({ target, ttl: DEFAULT_TTL });
105+
await mesg.respond({ target, ttl: clientTtl });
82106
} catch (err) {
83107
logger.debug("WARNING: unable to handle routing message", err);
84108
}
@@ -143,7 +167,7 @@ export async function stickyChoice({
143167
subject,
144168
targets: Array.from(targets),
145169
});
146-
const { target, ttl = DEFAULT_TTL } = resp.data;
170+
const { target, ttl = DEFAULT_CLIENT_TTL } = resp.data;
147171
updateSticky({ pattern, subject, target, ttl });
148172
return target;
149173
}

0 commit comments

Comments
 (0)