@@ -21,16 +21,19 @@ import (
2121)
2222
2323const (
24- indexNameFormat = "sg_%s_%s%d" // Name, xattrs, version. e.g. "sg_channels_x1"
25- syncRelativeToken = "$relativesync" // Relative sync token (no keyspace), used to swap between xattr/non-xattr handling in n1ql statements
26- syncToken = "$sync" // Sync token, used to swap between xattr/non-xattr handling in n1ql statements
27- indexToken = "$idx" // Index token, used to hint which index should be used for the query
24+ indexNameFormat = "sg_%s_%s%d" // Name, xattrs, version. e.g. "sg_channels_x1"
25+ partitionableIndexNameFormat = "%s_p%d" // indexName, numPartitions
26+ syncRelativeToken = "$relativesync" // Relative sync token (no keyspace), used to swap between xattr/non-xattr handling in n1ql statements
27+ syncToken = "$sync" // Sync token, used to swap between xattr/non-xattr handling in n1ql statements
28+ indexToken = "$idx" // Index token, used to hint which index should be used for the query
2829
2930 // N1ql-encoded wildcard expression matching the '_sync:' prefix used for all sync gateway's system documents.
3031 // Need to escape the underscore in '_sync' to prevent it being treated as a N1QL wildcard
3132 SyncDocWildcard = `\\_sync:%`
3233 SyncUserWildcard = `\\_sync:user:%`
3334 SyncRoleWildcard = `\\_sync:role:%`
35+
36+ DefaultNumIndexPartitions uint32 = 1
3437)
3538
3639// Index and query definitions use syncToken ($sync) to represent the location of sync gateway's metadata.
@@ -175,6 +178,10 @@ var (
175178 "ORDER BY [op.name, LEAST($sync.`sequence`, op.val.seq),IFMISSING(op.val.rev,null),IFMISSING(op.val.del,null)] " +
176179 "LIMIT 1" ,
177180 }
181+ partitionableIndexes = map [SGIndexType ]bool {
182+ IndexAllDocs : true ,
183+ IndexChannels : true ,
184+ }
178185)
179186
180187var sgIndexes map [SGIndexType ]SGIndex
@@ -191,6 +198,7 @@ func init() {
191198 filterExpression : indexFilterExpressions [i ],
192199 flags : indexFlags [i ],
193200 creationMode : indexCreationModes [i ],
201+ partitionable : partitionableIndexes [i ],
194202 }
195203 // If a readiness query is specified for this index, mark the index as required and add to SGIndex
196204 readinessQuery , ok := readinessQueries [i ]
@@ -214,18 +222,23 @@ type SGIndex struct {
214222 readinessQuery string // Query used to determine view readiness
215223 flags SGIndexFlags // Additional index options
216224 creationMode indexCreationMode // Signal when to create indexes
225+ partitionable bool // Whether the index is partitionable
217226}
218227
219- func (i * SGIndex ) fullIndexName (useXattrs bool ) string {
220- return i .indexNameForVersion (i .version , useXattrs )
228+ func (i * SGIndex ) fullIndexName (useXattrs bool , numPartitions uint32 ) string {
229+ return i .indexNameForVersion (i .version , useXattrs , numPartitions )
221230}
222231
223- func (i * SGIndex ) indexNameForVersion (version int , useXattrs bool ) string {
232+ func (i * SGIndex ) indexNameForVersion (version int , useXattrs bool , numPartitions uint32 ) string {
224233 xattrsToken := ""
225234 if useXattrs {
226235 xattrsToken = "x"
227236 }
228- return fmt .Sprintf (indexNameFormat , i .simpleName , xattrsToken , version )
237+ indexName := fmt .Sprintf (indexNameFormat , i .simpleName , xattrsToken , version )
238+ if i .partitionable && numPartitions > 1 {
239+ indexName = fmt .Sprintf (partitionableIndexNameFormat , indexName , numPartitions )
240+ }
241+ return indexName
229242}
230243
231244// Tombstone indexing is required for indexes that need to index the _sync xattrs even when the document
@@ -281,8 +294,10 @@ func (i *SGIndex) shouldCreate(options InitializeIndexOptions) bool {
281294// Creates index associated with specified SGIndex if not already present. Always defers build - a subsequent BUILD INDEX
282295// will need to be invoked for any created indexes.
283296func (i * SGIndex ) createIfNeeded (ctx context.Context , bucket base.N1QLStore , options InitializeIndexOptions ) error {
284-
285- indexName := i .fullIndexName (options .UseXattrs )
297+ if options .NumPartitions < 1 {
298+ return fmt .Errorf ("Invalid number of partitions specified for index %s: %d, needs to be greater than 0" , i .simpleName , options .NumPartitions )
299+ }
300+ indexName := i .fullIndexName (options .UseXattrs , options .NumPartitions )
286301
287302 // Create index
288303 base .InfofCtx (ctx , base .KeyQuery , "Creating index %s if it doesn't already exist..." , indexName )
@@ -294,6 +309,9 @@ func (i *SGIndex) createIfNeeded(ctx context.Context, bucket base.N1QLStore, opt
294309 NumReplica : options .NumReplicas ,
295310 IndexTombstones : i .shouldIndexTombstones (options .UseXattrs ),
296311 }
312+ if i .partitionable && options .NumPartitions > 1 {
313+ n1qlOptions .NumPartitions = & options .NumPartitions
314+ }
297315
298316 // Initial retry 1 seconds, max wait 30s, waits up to 10m
299317 sleeper := base .CreateMaxDoublingSleeperFunc (20 , 1000 , 30000 )
@@ -344,11 +362,14 @@ type InitializeIndexOptions struct {
344362 MetadataIndexes CollectionIndexesType // indicate which indexes to create
345363 Serverless bool // if true, create indexes for serverless
346364 UseXattrs bool // if true, create indexes on xattrs, otherwise, use inline sync data
365+ NumPartitions uint32 // number of partitions to use for the index
347366}
348367
349368// Initializes Sync Gateway indexes for datastore. Creates required indexes if not found, then waits for index readiness.
350369func InitializeIndexes (ctx context.Context , n1QLStore base.N1QLStore , options InitializeIndexOptions ) error {
351-
370+ if options .NumPartitions < 1 {
371+ return fmt .Errorf ("Invalid number of partitions specified: %d, needs to be greater than 0" , options .NumPartitions )
372+ }
352373 base .InfofCtx (ctx , base .KeyAll , "Initializing indexes with numReplicas: %d..." , options .NumReplicas )
353374
354375 // Create any indexes that aren't present
@@ -360,7 +381,7 @@ func InitializeIndexes(ctx context.Context, n1QLStore base.N1QLStore, options In
360381 continue
361382 }
362383
363- fullIndexName := sgIndex .fullIndexName (options .UseXattrs )
384+ fullIndexName := sgIndex .fullIndexName (options .UseXattrs , options . NumPartitions )
364385
365386 err := sgIndex .createIfNeeded (ctx , n1QLStore , options )
366387 if err != nil {
@@ -371,7 +392,6 @@ func InitializeIndexes(ctx context.Context, n1QLStore base.N1QLStore, options In
371392
372393 fullIndexNames = append (fullIndexNames , fullIndexName )
373394 }
374-
375395 // Issue BUILD INDEX for any deferred indexes.
376396 if len (fullIndexNames ) > 0 {
377397 buildErr := base .BuildDeferredIndexes (ctx , n1QLStore , fullIndexNames )
@@ -391,7 +411,7 @@ func waitForIndexes(ctx context.Context, bucket base.N1QLStore, options Initiali
391411 var indexes []string
392412
393413 for _ , sgIndex := range sgIndexes {
394- fullIndexName := sgIndex .fullIndexName (options .UseXattrs )
414+ fullIndexName := sgIndex .fullIndexName (options .UseXattrs , options . NumPartitions )
395415 if ! sgIndex .shouldCreate (options ) {
396416 continue
397417 }
@@ -421,17 +441,18 @@ func removeObsoleteIndexes(ctx context.Context, bucket base.N1QLStore, previewOn
421441
422442 // Build set of candidates for cleanup
423443 removalCandidates := make ([]string , 0 )
444+ numPartitions := DefaultNumIndexPartitions // obsolete indexes is always partitions 1, greater than 1 needs to be removed manually
424445 for _ , sgIndex := range indexMap {
425446 // Current version, opposite xattr setting
426- removalCandidates = append (removalCandidates , sgIndex .fullIndexName (! useXattrs ))
447+ removalCandidates = append (removalCandidates , sgIndex .fullIndexName (! useXattrs , numPartitions ))
427448 // If using views we can remove current version for xattr setting too
428449 if useViews {
429- removalCandidates = append (removalCandidates , sgIndex .fullIndexName (useXattrs ))
450+ removalCandidates = append (removalCandidates , sgIndex .fullIndexName (useXattrs , numPartitions ))
430451 }
431452 // Older versions, both xattr and non-xattr
432453 for _ , prevVersion := range sgIndex .previousVersions {
433- removalCandidates = append (removalCandidates , sgIndex .indexNameForVersion (prevVersion , true ))
434- removalCandidates = append (removalCandidates , sgIndex .indexNameForVersion (prevVersion , false ))
454+ removalCandidates = append (removalCandidates , sgIndex .indexNameForVersion (prevVersion , true , numPartitions ))
455+ removalCandidates = append (removalCandidates , sgIndex .indexNameForVersion (prevVersion , false , numPartitions ))
435456 }
436457 }
437458
@@ -497,8 +518,8 @@ func replaceSyncTokensQuery(statement string, useXattrs bool) string {
497518}
498519
499520// Replace index tokens ($idx) in the provided createIndex statement with the appropriate token, depending on whether xattrs should be used.
500- func replaceIndexTokensQuery (statement string , idx SGIndex , useXattrs bool ) string {
501- return strings .Replace (statement , indexToken , idx .fullIndexName (useXattrs ), - 1 )
521+ func replaceIndexTokensQuery (statement string , idx SGIndex , useXattrs bool , numPartitions uint32 ) string {
522+ return strings .Replace (statement , indexToken , idx .fullIndexName (useXattrs , numPartitions ), - 1 )
502523}
503524
504525func copySGIndexes (inputMap map [SGIndexType ]SGIndex ) map [SGIndexType ]SGIndex {
@@ -521,7 +542,7 @@ func GetIndexesName(options InitializeIndexOptions) []string {
521542 continue
522543 }
523544 if sgIndex .shouldCreate (options ) {
524- indexesName = append (indexesName , sgIndex .fullIndexName (options .UseXattrs ))
545+ indexesName = append (indexesName , sgIndex .fullIndexName (options .UseXattrs , options . NumPartitions ))
525546 }
526547 }
527548 return indexesName
0 commit comments