-
Notifications
You must be signed in to change notification settings - Fork 83
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: migrate set -> sorted set for existing nonce-recycled keys (#693)
* fix: migrate set -> sorted set for existing nonce-recycled keys * blocking poll * exit 0
- Loading branch information
Showing
6 changed files
with
122 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { logger } from "../utils/logger"; | ||
import { acquireLock, releaseLock, waitForLock } from "../utils/redis/lock"; | ||
import { redis } from "../utils/redis/redis"; | ||
|
||
const MIGRATION_LOCK_TTL_SECONDS = 60; | ||
|
||
const main = async () => { | ||
// Acquire a lock to allow only one host to run migrations. | ||
// Other hosts block until the migration is completed or lock times out. | ||
const acquiredLock = await acquireLock( | ||
"lock:apply-migrations", | ||
MIGRATION_LOCK_TTL_SECONDS, | ||
); | ||
if (!acquiredLock) { | ||
logger({ | ||
level: "info", | ||
message: "Migration in progress. Waiting for the lock to release...", | ||
service: "server", | ||
}); | ||
await waitForLock("lock:apply-migrations"); | ||
process.exit(0); | ||
} | ||
|
||
try { | ||
await migrateRecycledNonces(); | ||
|
||
logger({ | ||
level: "info", | ||
message: "Completed migrations without errors.", | ||
service: "server", | ||
}); | ||
} catch (e) { | ||
logger({ | ||
level: "error", | ||
message: `Failed to complete migrations: ${e}`, | ||
service: "server", | ||
}); | ||
process.exit(1); | ||
} finally { | ||
await releaseLock("lock:apply-migrations"); | ||
} | ||
|
||
process.exit(0); | ||
}; | ||
|
||
const migrateRecycledNonces = async () => { | ||
const keys = await redis.keys("nonce-recycled:*"); | ||
|
||
// For each `nonce-recycled:*` key that is a "set" in Redis, | ||
// migrate all members to a sorted set with score == nonce. | ||
for (const key of keys) { | ||
const type = await redis.type(key); | ||
if (type !== "set") { | ||
continue; | ||
} | ||
|
||
const members = await redis.smembers(key); | ||
await redis.del(key); | ||
if (members.length > 0) { | ||
const args = members.flatMap((member) => { | ||
const score = Number.parseInt(member); | ||
return Number.isNaN(score) ? [] : [score, member]; | ||
}); | ||
await redis.zadd(key, ...args); | ||
} | ||
} | ||
}; | ||
|
||
main(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { redis } from "./redis"; | ||
|
||
// Add more locks here. | ||
type LockType = "lock:apply-migrations"; | ||
|
||
/** | ||
* Acquire a lock to prevent duplicate runs of a workflow. | ||
* | ||
* @param key string The lock identifier. | ||
* @param ttlSeconds number The number of seconds before the lock is automatically released. | ||
* @returns true if the lock was acquired. Else false. | ||
*/ | ||
export const acquireLock = async ( | ||
key: LockType, | ||
ttlSeconds: number, | ||
): Promise<boolean> => { | ||
const result = await redis.set(key, Date.now(), "EX", ttlSeconds, "NX"); | ||
return result === "OK"; | ||
}; | ||
|
||
/** | ||
* Release a lock. | ||
* | ||
* @param key The lock identifier. | ||
* @returns true if the lock was active before releasing. | ||
*/ | ||
export const releaseLock = async (key: LockType) => { | ||
const result = await redis.del(key); | ||
return result > 0; | ||
}; | ||
|
||
/** | ||
* Blocking polls a lock every second until it's released. | ||
* | ||
* @param key The lock identifier. | ||
*/ | ||
export const waitForLock = async (key: LockType) => { | ||
while (await redis.get(key)) { | ||
await new Promise((resolve) => setTimeout(resolve, 1_000)); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters