@@ -11,6 +11,9 @@ export const DEFAULT_SOFT_REPLICAS = 4;
11
11
export const DEFAULT_HARD_REPLICAS = 2 ;
12
12
export const DEFAULT_WRITE_RETRIES = 3 ;
13
13
export const DEFAULT_NUM_SHARDS = 4 ;
14
+ export const DEFAULT_REGION = "enam" as const ;
15
+ export const AVAILABLE_REGIONS = [ "enam" , "weur" , "apac" , "sam" , "afr" , "oc" ] as const ;
16
+ type AllowedDurableObjectRegion = ( typeof AVAILABLE_REGIONS ) [ number ] ;
14
17
15
18
interface ShardedDOTagCacheOptions {
16
19
/**
@@ -55,6 +58,21 @@ interface ShardedDOTagCacheOptions {
55
58
shardReplicationOptions ?: {
56
59
numberOfSoftReplicas : number ;
57
60
numberOfHardReplicas : number ;
61
+
62
+ /**
63
+ * Whether to enable regional replication
64
+ * Regional replication will duplicate each shards and their associated replicas into every regions
65
+ * This will reduce the latency for the read operations
66
+ * On write, the write will be sent to all the shards and all the replicas in all the regions
67
+ * @default false
68
+ */
69
+ enableRegionalReplication ?: boolean ;
70
+
71
+ /**
72
+ * Default region to use for the regional replication when the region cannot be determined
73
+ * @default "enam"
74
+ */
75
+ defaultRegion ?: AllowedDurableObjectRegion ;
58
76
} ;
59
77
60
78
/**
@@ -69,22 +87,25 @@ interface TagCacheDOIdOptions {
69
87
numberOfReplicas : number ;
70
88
shardType : "soft" | "hard" ;
71
89
replicaId ?: number ;
90
+ region ?: DurableObjectLocationHint ;
72
91
}
73
92
export class TagCacheDOId {
74
93
shardId : string ;
75
94
replicaId : number ;
95
+ region ?: DurableObjectLocationHint ;
76
96
constructor ( public options : TagCacheDOIdOptions ) {
77
- const { baseShardId, shardType, numberOfReplicas, replicaId } = options ;
97
+ const { baseShardId, shardType, numberOfReplicas, replicaId, region } = options ;
78
98
this . shardId = `tag-${ shardType } ;${ baseShardId } ` ;
79
99
this . replicaId = replicaId ?? this . generateRandomNumberBetween ( 1 , numberOfReplicas ) ;
100
+ this . region = region ;
80
101
}
81
102
82
103
private generateRandomNumberBetween ( min : number , max : number ) {
83
104
return Math . floor ( Math . random ( ) * ( max - min + 1 ) + min ) ;
84
105
}
85
106
86
107
get key ( ) {
87
- return `${ this . shardId } ;replica-${ this . replicaId } ` ;
108
+ return `${ this . shardId } ;replica-${ this . replicaId } ${ this . region ? `;region- ${ this . region } ` : "" } ` ;
88
109
}
89
110
}
90
111
class ShardedDOTagCache implements NextModeTagCache {
@@ -93,20 +114,28 @@ class ShardedDOTagCache implements NextModeTagCache {
93
114
readonly numSoftReplicas : number ;
94
115
readonly numHardReplicas : number ;
95
116
readonly maxWriteRetries : number ;
117
+ readonly enableRegionalReplication : boolean ;
118
+ readonly defaultRegion : AllowedDurableObjectRegion ;
96
119
localCache ?: Cache ;
97
120
98
121
constructor ( private opts : ShardedDOTagCacheOptions = { baseShardSize : DEFAULT_NUM_SHARDS } ) {
99
122
this . numSoftReplicas = opts . shardReplicationOptions ?. numberOfSoftReplicas ?? DEFAULT_SOFT_REPLICAS ;
100
123
this . numHardReplicas = opts . shardReplicationOptions ?. numberOfHardReplicas ?? DEFAULT_HARD_REPLICAS ;
101
124
this . maxWriteRetries = opts . maxWriteRetries ?? DEFAULT_WRITE_RETRIES ;
125
+ this . enableRegionalReplication = opts . shardReplicationOptions ?. enableRegionalReplication ?? false ;
126
+ this . defaultRegion = opts . shardReplicationOptions ?. defaultRegion ?? DEFAULT_REGION ;
102
127
}
103
128
104
129
private getDurableObjectStub ( doId : TagCacheDOId ) {
105
130
const durableObject = getCloudflareContext ( ) . env . NEXT_TAG_CACHE_DO_SHARDED ;
106
131
if ( ! durableObject ) throw new IgnorableError ( "No durable object binding for cache revalidation" ) ;
107
132
108
133
const id = durableObject . idFromName ( doId . key ) ;
109
- return durableObject . get ( id ) ;
134
+ debug ( "[shardedTagCache] - Accessing Durable Object : " , {
135
+ key : doId . key ,
136
+ region : doId . region ,
137
+ } ) ;
138
+ return durableObject . get ( id , { locationHint : doId . region } ) ;
110
139
}
111
140
112
141
/**
@@ -134,7 +163,7 @@ class ShardedDOTagCache implements NextModeTagCache {
134
163
? Array . from ( { length : numReplicas } , ( _ , i ) => i + 1 )
135
164
: [ undefined ] ;
136
165
}
137
- return replicaIndexes . flatMap ( ( replicaId ) => {
166
+ const regionalReplicas = replicaIndexes . flatMap ( ( replicaId ) => {
138
167
return tags
139
168
. filter ( ( tag ) => ( isSoft ? tag . startsWith ( SOFT_TAG_PREFIX ) : ! tag . startsWith ( SOFT_TAG_PREFIX ) ) )
140
169
. map ( ( tag ) => {
@@ -149,6 +178,51 @@ class ShardedDOTagCache implements NextModeTagCache {
149
178
} ;
150
179
} ) ;
151
180
} ) ;
181
+ if ( ! this . enableRegionalReplication ) return regionalReplicas ;
182
+
183
+ // If we have regional replication enabled, we need to further duplicate the shards in all the regions
184
+ const regionalReplicasInAllRegions = generateAllReplicas
185
+ ? regionalReplicas . flatMap ( ( { doId, tag } ) => {
186
+ return AVAILABLE_REGIONS . map ( ( region ) => {
187
+ return {
188
+ doId : new TagCacheDOId ( {
189
+ baseShardId : doId . options . baseShardId ,
190
+ numberOfReplicas : numReplicas ,
191
+ shardType,
192
+ replicaId : doId . replicaId ,
193
+ region,
194
+ } ) ,
195
+ tag,
196
+ } ;
197
+ } ) ;
198
+ } )
199
+ : regionalReplicas . map ( ( { doId, tag } ) => {
200
+ doId . region = this . getClosestRegion ( ) ;
201
+ return { doId, tag } ;
202
+ } ) ;
203
+ return regionalReplicasInAllRegions ;
204
+ }
205
+
206
+ getClosestRegion ( ) {
207
+ const continent = getCloudflareContext ( ) . cf ?. continent ;
208
+ if ( ! continent ) return this . defaultRegion ;
209
+ debug ( "[shardedTagCache] - Continent : " , continent ) ;
210
+ switch ( continent ) {
211
+ case "AF" :
212
+ return "afr" ;
213
+ case "AS" :
214
+ return "apac" ;
215
+ case "EU" :
216
+ return "weur" ;
217
+ case "NA" :
218
+ return "enam" ;
219
+ case "OC" :
220
+ return "oc" ;
221
+ case "SA" :
222
+ return "sam" ;
223
+ default :
224
+ return this . defaultRegion ;
225
+ }
152
226
}
153
227
154
228
/**
0 commit comments