1+ /*
2+ Refs:
3+ https://upstash.com/docs/redis/sdks/ts/pipelining/pipeline-transaction
4+ https://upstash.com/docs/redis/sdks/ts/pipelining/auto-pipeline
5+ */
16import Crypto from 'node:crypto' ;
2- import { createClient } from '@vercel/kv ' ;
7+ import { Redis } from '@upstash/redis ' ;
38
49const secret = process . env . SECRET ;
510const sigLen = parseInt ( process . env . SIG_LEN ) ;
@@ -13,14 +18,18 @@ const dbKeyPrefix = {
1318 cache : "cache:"
1419 }
1520// Redis client for user database
16- const redisData = createClient ( {
21+ const redisData = new Redis ( {
1722 url : process . env . UPSTASH_REDIS_REST_URL ,
1823 token : process . env . UPSTASH_REDIS_REST_TOKEN ,
24+ latencyLogging : false ,
25+ enableAutoPipelining : true
1926} )
2027// Redis client for ratelimiter database
21- const redisRateLimit = createClient ( {
28+ const redisRateLimit = new Redis ( {
2229 url : process . env . KV_REST_API_URL ,
2330 token : process . env . KV_REST_API_TOKEN ,
31+ latencyLogging : false ,
32+ enableAutoPipelining : true
2433} )
2534
2635function hash ( str ) {
@@ -61,7 +70,13 @@ export function genPublicKey(privateOrPublicKey){
6170export function cacheSet ( privateKey , obj ) {
6271 const publicKey = genPublicKey ( privateKey ) ;
6372 const dbKey = dbKeyPrefix . cache + publicKey ;
64- return redisRateLimit . hset ( dbKey , obj ) . then ( redisRateLimit . expire ( dbKey , cacheTtl ) ) ;
73+ // Promise.all below enables both commands to be executed in a single http request (using same pipeline)
74+ // As Redis is single-threaded, the commands are executed in order
75+ // See https://upstash.com/docs/redis/sdks/ts/pipelining/auto-pipeline
76+ return Promise . all ( [
77+ redisRateLimit . hset ( dbKey , obj ) ,
78+ redisRateLimit . expire ( dbKey , cacheTtl )
79+ ] )
6580}
6681
6782export function cacheGet ( publicKey , key ) {
@@ -84,15 +99,20 @@ export function genKeyPair(seed = Crypto.randomUUID()){
8499
85100export async function publicProduce ( publicKey , data ) {
86101 const dbKey = dbKeyPrefix . manyToOne + publicKey ;
87- return redisData . rpush ( dbKey , data ) . then ( redisData . expire ( dbKey , ttl ) ) ;
102+ return Promise . all ( [
103+ redisData . rpush ( dbKey , data ) ,
104+ redisData . expire ( dbKey , ttl )
105+ ] )
88106}
89107
90108export async function privateConsume ( privateKey ) {
91109 const publicKey = genPublicKey ( privateKey ) ;
92110 const dbKey = dbKeyPrefix . manyToOne + publicKey ;
93- const llen = await redisData . llen ( dbKey ) ;
94- if ( ! llen ) return [ ] ;
95- return redisData . lpop ( dbKey , llen ) ;
111+ const atomicTransaction = redisData . multi ( ) ;
112+ atomicTransaction . lrange ( dbKey , 0 , - 1 ) ;
113+ atomicTransaction . del ( dbKey ) ;
114+ return atomicTransaction . exec ( )
115+ . then ( ( values ) => values [ 0 ] ) ;
96116}
97117
98118export async function privateProduce ( privateKey , data ) {
@@ -117,9 +137,11 @@ export async function privateStats(privateKey){
117137 const publicKey = genPublicKey ( privateKey ) ;
118138 const dbKeyConsume = dbKeyPrefix . manyToOne + publicKey ;
119139 const dbKeyPublish = dbKeyPrefix . oneToMany + publicKey ;
120- const countConsume = await redisData . llen ( dbKeyConsume ) ;
121- const ttlConsume = await redisData . ttl ( dbKeyConsume ) ;
122- const ttlPublish = await redisData . ttl ( dbKeyPublish ) ;
140+ const [ countConsume , ttlConsume , ttlPublish ] = await Promise . all ( [
141+ redisData . llen ( dbKeyConsume ) ,
142+ redisData . ttl ( dbKeyConsume ) ,
143+ redisData . ttl ( dbKeyPublish )
144+ ] )
123145 return {
124146 consume : {
125147 count : countConsume ,
@@ -141,23 +163,29 @@ export async function oneToOneProduce(privateKey, key, data){
141163 const dbKey = dbKeyPrefix . oneToOne + publicKey ;
142164 let field = { } ;
143165 field [ key ] = data ;
144- return redisData . hset ( dbKey , field ) . then ( redisData . expire ( dbKey , ttl ) ) ;
166+ return Promise . all ( [
167+ redisData . hset ( dbKey , field ) ,
168+ redisData . expire ( dbKey , ttl )
169+ ] )
145170}
146171
147172export async function oneToOneConsume ( publicKey , key ) {
148173 const dbKey = dbKeyPrefix . oneToOne + publicKey ;
149174 const field = key ;
150- return redisData . hget ( dbKey , field ) . then ( redisData . hdel ( dbKey , field ) ) ;
175+ const atomicTransaction = redisData . multi ( ) ;
176+ atomicTransaction . hget ( dbKey , field ) ;
177+ atomicTransaction . hdel ( dbKey , field ) ;
178+ return atomicTransaction . exec ( )
179+ . then ( ( values ) => values [ 0 ] ) ;
151180}
152181
153182export async function oneToOneTTL ( privateKey , key ) {
154183 const publicKey = genPublicKey ( privateKey ) ;
155184 const dbKey = dbKeyPrefix . oneToOne + publicKey ;
156185 const field = key ;
157- const bool = await redisData . hexists ( dbKey , field ) ;
158- if ( bool ) {
159- return { ttl : await redisData . ttl ( dbKey ) } ;
160- } else {
161- return { ttl : 0 } ;
162- }
186+ const [ bool , ttl ] = await Promise . all ( [
187+ redisData . hexists ( dbKey , field ) ,
188+ redisData . ttl ( dbKey )
189+ ] )
190+ return { ttl : bool ? ttl : 0 } ;
163191}
0 commit comments