@@ -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,19 @@ interface ShardedDOTagCacheOptions {
6366 shardReplication ?: {
6467 numberOfSoftReplicas : number ;
6568 numberOfHardReplicas : number ;
69+
70+ /**
71+ * Enable regional replication for the shards.
72+ *
73+ * If not set, no regional replication will be performed and durable objects will be created without a location hint
74+ *
75+ * Can be used to reduce latency for users in different regions and to spread the load across multiple regions.
76+ *
77+ * This will increase the number of durable objects created, as each shard will be replicated in all regions.
78+ */
79+ regionalReplication ?: {
80+ defaultRegion : AllowedDurableObjectRegion ;
81+ } ;
6682 } ;
6783
6884 /**
@@ -78,23 +94,26 @@ interface DOIdOptions {
7894 numberOfReplicas : number ;
7995 shardType : "soft" | "hard" ;
8096 replicaId ?: number ;
97+ region ?: DurableObjectLocationHint ;
8198}
8299
83100export class DOId {
84101 shardId : string ;
85102 replicaId : number ;
103+ region ?: DurableObjectLocationHint ;
86104 constructor ( public options : DOIdOptions ) {
87- const { baseShardId, shardType, numberOfReplicas, replicaId } = options ;
105+ const { baseShardId, shardType, numberOfReplicas, replicaId, region } = options ;
88106 this . shardId = `tag-${ shardType } ;${ baseShardId } ` ;
89107 this . replicaId = replicaId ?? this . generateRandomNumberBetween ( 1 , numberOfReplicas ) ;
108+ this . region = region ;
90109 }
91110
92111 private generateRandomNumberBetween ( min : number , max : number ) {
93112 return Math . floor ( Math . random ( ) * ( max - min + 1 ) + min ) ;
94113 }
95114
96115 get key ( ) {
97- return `${ this . shardId } ;replica-${ this . replicaId } ` ;
116+ return `${ this . shardId } ;replica-${ this . replicaId } ${ this . region ? `;region- ${ this . region } ` : "" } ` ;
98117 }
99118}
100119
@@ -109,20 +128,28 @@ class ShardedDOTagCache implements NextModeTagCache {
109128 readonly numSoftReplicas : number ;
110129 readonly numHardReplicas : number ;
111130 readonly maxWriteRetries : number ;
131+ readonly enableRegionalReplication : boolean ;
132+ readonly defaultRegion : AllowedDurableObjectRegion ;
112133 localCache ?: Cache ;
113134
114135 constructor ( private opts : ShardedDOTagCacheOptions = { baseShardSize : DEFAULT_NUM_SHARDS } ) {
115136 this . numSoftReplicas = opts . shardReplication ?. numberOfSoftReplicas ?? 1 ;
116137 this . numHardReplicas = opts . shardReplication ?. numberOfHardReplicas ?? 1 ;
117138 this . maxWriteRetries = opts . maxWriteRetries ?? DEFAULT_WRITE_RETRIES ;
139+ this . enableRegionalReplication = Boolean ( opts . shardReplication ?. regionalReplication ) ;
140+ this . defaultRegion = opts . shardReplication ?. regionalReplication ?. defaultRegion ?? DEFAULT_REGION ;
118141 }
119142
120143 private getDurableObjectStub ( doId : DOId ) {
121144 const durableObject = getCloudflareContext ( ) . env . NEXT_TAG_CACHE_DO_SHARDED ;
122145 if ( ! durableObject ) throw new IgnorableError ( "No durable object binding for cache revalidation" ) ;
123146
124147 const id = durableObject . idFromName ( doId . key ) ;
125- return durableObject . get ( id ) ;
148+ debug ( "[shardedTagCache] - Accessing Durable Object : " , {
149+ key : doId . key ,
150+ region : doId . region ,
151+ } ) ;
152+ return durableObject . get ( id , { locationHint : doId . region } ) ;
126153 }
127154
128155 /**
@@ -143,10 +170,14 @@ class ShardedDOTagCache implements NextModeTagCache {
143170 } ) {
144171 let replicaIndexes : Array < number | undefined > = [ 1 ] ;
145172 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 ) => {
173+ let numReplicas = 1 ;
174+ if ( this . opts . shardReplication ) {
175+ numReplicas = isSoft ? this . numSoftReplicas : this . numHardReplicas ;
176+ replicaIndexes = generateAllReplicas
177+ ? Array . from ( { length : numReplicas } , ( _ , i ) => i + 1 )
178+ : [ undefined ] ;
179+ }
180+ const regionalReplicas = replicaIndexes . flatMap ( ( replicaId ) => {
150181 return tags
151182 . filter ( ( tag ) => ( isSoft ? tag . startsWith ( SOFT_TAG_PREFIX ) : ! tag . startsWith ( SOFT_TAG_PREFIX ) ) )
152183 . map ( ( tag ) => {
@@ -161,6 +192,51 @@ class ShardedDOTagCache implements NextModeTagCache {
161192 } ;
162193 } ) ;
163194 } ) ;
195+ if ( ! this . enableRegionalReplication ) return regionalReplicas ;
196+
197+ // If we have regional replication enabled, we need to further duplicate the shards in all the regions
198+ const regionalReplicasInAllRegions = generateAllReplicas
199+ ? regionalReplicas . flatMap ( ( { doId, tag } ) => {
200+ return AVAILABLE_REGIONS . map ( ( region ) => {
201+ return {
202+ doId : new DOId ( {
203+ baseShardId : doId . options . baseShardId ,
204+ numberOfReplicas : numReplicas ,
205+ shardType,
206+ replicaId : doId . replicaId ,
207+ region,
208+ } ) ,
209+ tag,
210+ } ;
211+ } ) ;
212+ } )
213+ : regionalReplicas . map ( ( { doId, tag } ) => {
214+ doId . region = this . getClosestRegion ( ) ;
215+ return { doId, tag } ;
216+ } ) ;
217+ return regionalReplicasInAllRegions ;
218+ }
219+
220+ getClosestRegion ( ) {
221+ const continent = getCloudflareContext ( ) . cf ?. continent ;
222+ if ( ! continent ) return this . defaultRegion ;
223+ debug ( "[shardedTagCache] - Continent : " , continent ) ;
224+ switch ( continent ) {
225+ case "AF" :
226+ return "afr" ;
227+ case "AS" :
228+ return "apac" ;
229+ case "EU" :
230+ return "weur" ;
231+ case "NA" :
232+ return "enam" ;
233+ case "OC" :
234+ return "oc" ;
235+ case "SA" :
236+ return "sam" ;
237+ default :
238+ return this . defaultRegion ;
239+ }
164240 }
165241
166242 /**
0 commit comments