@@ -12,6 +12,9 @@ export const DEFAULT_NUM_SHARDS = 4;
1212export const NAME = "do-sharded-tag-cache" ;
1313
1414const SOFT_TAG_PREFIX = "_N_T_/" ;
15+ export const DEFAULT_REGION = "enam" as const ;
16+ export const AVAILABLE_REGIONS = [ "enam" , "weur" , "apac" , "sam" , "afr" , "oc" ] as const ;
17+ type AllowedDurableObjectRegion = ( typeof AVAILABLE_REGIONS ) [ number ] ;
1518
1619interface ShardedDOTagCacheOptions {
1720 /**
@@ -63,6 +66,21 @@ interface ShardedDOTagCacheOptions {
6366 shardReplication ?: {
6467 numberOfSoftReplicas : number ;
6568 numberOfHardReplicas : number ;
69+
70+ /**
71+ * Whether to enable regional replication
72+ * Regional replication will duplicate each shards and their associated replicas into every regions
73+ * This will reduce the latency for the read operations
74+ * On write, the write will be sent to all the shards and all the replicas in all the regions
75+ * @default false
76+ */
77+ enableRegionalReplication ?: boolean ;
78+
79+ /**
80+ * Default region to use for the regional replication when the region cannot be determined
81+ * @default "enam"
82+ */
83+ defaultRegion ?: AllowedDurableObjectRegion ;
6684 } ;
6785
6886 /**
@@ -78,23 +96,26 @@ interface DOIdOptions {
7896 numberOfReplicas : number ;
7997 shardType : "soft" | "hard" ;
8098 replicaId ?: number ;
99+ region ?: DurableObjectLocationHint ;
81100}
82101
83102export class DOId {
84103 shardId : string ;
85104 replicaId : number ;
105+ region ?: DurableObjectLocationHint ;
86106 constructor ( public options : DOIdOptions ) {
87- const { baseShardId, shardType, numberOfReplicas, replicaId } = options ;
107+ const { baseShardId, shardType, numberOfReplicas, replicaId, region } = options ;
88108 this . shardId = `tag-${ shardType } ;${ baseShardId } ` ;
89109 this . replicaId = replicaId ?? this . generateRandomNumberBetween ( 1 , numberOfReplicas ) ;
110+ this . region = region ;
90111 }
91112
92113 private generateRandomNumberBetween ( min : number , max : number ) {
93114 return Math . floor ( Math . random ( ) * ( max - min + 1 ) + min ) ;
94115 }
95116
96117 get key ( ) {
97- return `${ this . shardId } ;replica-${ this . replicaId } ` ;
118+ return `${ this . shardId } ;replica-${ this . replicaId } ${ this . region ? `;region- ${ this . region } ` : "" } ` ;
98119 }
99120}
100121
@@ -109,20 +130,28 @@ class ShardedDOTagCache implements NextModeTagCache {
109130 readonly numSoftReplicas : number ;
110131 readonly numHardReplicas : number ;
111132 readonly maxWriteRetries : number ;
133+ readonly enableRegionalReplication : boolean ;
134+ readonly defaultRegion : AllowedDurableObjectRegion ;
112135 localCache ?: Cache ;
113136
114137 constructor ( private opts : ShardedDOTagCacheOptions = { baseShardSize : DEFAULT_NUM_SHARDS } ) {
115138 this . numSoftReplicas = opts . shardReplication ?. numberOfSoftReplicas ?? 1 ;
116139 this . numHardReplicas = opts . shardReplication ?. numberOfHardReplicas ?? 1 ;
117140 this . maxWriteRetries = opts . maxWriteRetries ?? DEFAULT_WRITE_RETRIES ;
141+ this . enableRegionalReplication = opts . shardReplication ?. enableRegionalReplication ?? false ;
142+ this . defaultRegion = opts . shardReplication ?. defaultRegion ?? DEFAULT_REGION ;
118143 }
119144
120145 private getDurableObjectStub ( doId : DOId ) {
121146 const durableObject = getCloudflareContext ( ) . env . NEXT_TAG_CACHE_DO_SHARDED ;
122147 if ( ! durableObject ) throw new IgnorableError ( "No durable object binding for cache revalidation" ) ;
123148
124149 const id = durableObject . idFromName ( doId . key ) ;
125- return durableObject . get ( id ) ;
150+ debug ( "[shardedTagCache] - Accessing Durable Object : " , {
151+ key : doId . key ,
152+ region : doId . region ,
153+ } ) ;
154+ return durableObject . get ( id , { locationHint : doId . region } ) ;
126155 }
127156
128157 /**
@@ -143,10 +172,14 @@ class ShardedDOTagCache implements NextModeTagCache {
143172 } ) {
144173 let replicaIndexes : Array < number | undefined > = [ 1 ] ;
145174 const isSoft = shardType === "soft" ;
146- const numReplicas = isSoft ? this . numSoftReplicas : this . numHardReplicas ;
147- replicaIndexes = generateAllReplicas ? Array . from ( { length : numReplicas } , ( _ , i ) => i + 1 ) : [ undefined ] ;
148-
149- return replicaIndexes . flatMap ( ( replicaId ) => {
175+ let numReplicas = 1 ;
176+ if ( this . opts . shardReplication ) {
177+ numReplicas = isSoft ? this . numSoftReplicas : this . numHardReplicas ;
178+ replicaIndexes = generateAllReplicas
179+ ? Array . from ( { length : numReplicas } , ( _ , i ) => i + 1 )
180+ : [ undefined ] ;
181+ }
182+ const regionalReplicas = replicaIndexes . flatMap ( ( replicaId ) => {
150183 return tags
151184 . filter ( ( tag ) => ( isSoft ? tag . startsWith ( SOFT_TAG_PREFIX ) : ! tag . startsWith ( SOFT_TAG_PREFIX ) ) )
152185 . map ( ( tag ) => {
@@ -161,6 +194,51 @@ class ShardedDOTagCache implements NextModeTagCache {
161194 } ;
162195 } ) ;
163196 } ) ;
197+ if ( ! this . enableRegionalReplication ) return regionalReplicas ;
198+
199+ // If we have regional replication enabled, we need to further duplicate the shards in all the regions
200+ const regionalReplicasInAllRegions = generateAllReplicas
201+ ? regionalReplicas . flatMap ( ( { doId, tag } ) => {
202+ return AVAILABLE_REGIONS . map ( ( region ) => {
203+ return {
204+ doId : new DOId ( {
205+ baseShardId : doId . options . baseShardId ,
206+ numberOfReplicas : numReplicas ,
207+ shardType,
208+ replicaId : doId . replicaId ,
209+ region,
210+ } ) ,
211+ tag,
212+ } ;
213+ } ) ;
214+ } )
215+ : regionalReplicas . map ( ( { doId, tag } ) => {
216+ doId . region = this . getClosestRegion ( ) ;
217+ return { doId, tag } ;
218+ } ) ;
219+ return regionalReplicasInAllRegions ;
220+ }
221+
222+ getClosestRegion ( ) {
223+ const continent = getCloudflareContext ( ) . cf ?. continent ;
224+ if ( ! continent ) return this . defaultRegion ;
225+ debug ( "[shardedTagCache] - Continent : " , continent ) ;
226+ switch ( continent ) {
227+ case "AF" :
228+ return "afr" ;
229+ case "AS" :
230+ return "apac" ;
231+ case "EU" :
232+ return "weur" ;
233+ case "NA" :
234+ return "enam" ;
235+ case "OC" :
236+ return "oc" ;
237+ case "SA" :
238+ return "sam" ;
239+ default :
240+ return this . defaultRegion ;
241+ }
164242 }
165243
166244 /**
0 commit comments