github.com/m3db/m3@v1.5.0/src/dbnode/integration/integration.go (about)

     1  // Copyright (c) 2016 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package integration
    22  
    23  import (
    24  	"fmt"
    25  	"sync"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/stretchr/testify/require"
    30  	"github.com/uber-go/tally"
    31  	"go.uber.org/zap"
    32  
    33  	"github.com/m3db/m3/src/cluster/shard"
    34  	"github.com/m3db/m3/src/dbnode/client"
    35  	"github.com/m3db/m3/src/dbnode/integration/generate"
    36  	"github.com/m3db/m3/src/dbnode/namespace"
    37  	"github.com/m3db/m3/src/dbnode/persist/fs"
    38  	persistfs "github.com/m3db/m3/src/dbnode/persist/fs"
    39  	"github.com/m3db/m3/src/dbnode/runtime"
    40  	"github.com/m3db/m3/src/dbnode/storage"
    41  	"github.com/m3db/m3/src/dbnode/storage/bootstrap"
    42  	"github.com/m3db/m3/src/dbnode/storage/bootstrap/bootstrapper"
    43  	"github.com/m3db/m3/src/dbnode/storage/bootstrap/bootstrapper/commitlog"
    44  	bfs "github.com/m3db/m3/src/dbnode/storage/bootstrap/bootstrapper/fs"
    45  	"github.com/m3db/m3/src/dbnode/storage/bootstrap/bootstrapper/peers"
    46  	"github.com/m3db/m3/src/dbnode/storage/bootstrap/bootstrapper/uninitialized"
    47  	"github.com/m3db/m3/src/dbnode/storage/bootstrap/result"
    48  	"github.com/m3db/m3/src/dbnode/storage/index"
    49  	"github.com/m3db/m3/src/dbnode/storage/index/compaction"
    50  	"github.com/m3db/m3/src/dbnode/storage/repair"
    51  	"github.com/m3db/m3/src/dbnode/topology"
    52  	"github.com/m3db/m3/src/dbnode/topology/testutil"
    53  	xmetrics "github.com/m3db/m3/src/dbnode/x/metrics"
    54  	"github.com/m3db/m3/src/m3ninx/doc"
    55  	"github.com/m3db/m3/src/m3ninx/index/segment/builder"
    56  	"github.com/m3db/m3/src/m3ninx/index/segment/fst"
    57  	idxpersist "github.com/m3db/m3/src/m3ninx/persist"
    58  	"github.com/m3db/m3/src/x/instrument"
    59  	xretry "github.com/m3db/m3/src/x/retry"
    60  	xtime "github.com/m3db/m3/src/x/time"
    61  )
    62  
    63  const (
    64  	multiAddrPortStart = 9000
    65  	multiAddrPortEach  = 5
    66  )
    67  
    68  // TODO: refactor and use m3x/clock ...
    69  type conditionFn func() bool
    70  
    71  func waitUntil(fn conditionFn, timeout time.Duration) bool {
    72  	deadline := time.Now().Add(timeout)
    73  	for time.Now().Before(deadline) {
    74  		if fn() {
    75  			return true
    76  		}
    77  		time.Sleep(100 * time.Millisecond)
    78  	}
    79  	return false
    80  }
    81  
    82  func newMultiAddrTestOptions(opts TestOptions, instance int) TestOptions {
    83  	bind := "127.0.0.1"
    84  	start := multiAddrPortStart + (instance * multiAddrPortEach)
    85  	return opts.
    86  		SetID(fmt.Sprintf("testhost%d", instance)).
    87  		SetTChannelNodeAddr(fmt.Sprintf("%s:%d", bind, start)).
    88  		SetTChannelClusterAddr(fmt.Sprintf("%s:%d", bind, start+1)).
    89  		SetHTTPNodeAddr(fmt.Sprintf("%s:%d", bind, start+2)).
    90  		SetHTTPClusterAddr(fmt.Sprintf("%s:%d", bind, start+3)).
    91  		SetHTTPDebugAddr(fmt.Sprintf("%s:%d", bind, start+4))
    92  }
    93  
    94  func newMultiAddrAdminClient(
    95  	t *testing.T,
    96  	adminOpts client.AdminOptions,
    97  	topologyInitializer topology.Initializer,
    98  	origin topology.Host,
    99  	instrumentOpts instrument.Options,
   100  	customOpts ...client.CustomAdminOption,
   101  ) client.AdminClient {
   102  	if adminOpts == nil {
   103  		adminOpts = client.NewAdminOptions()
   104  	}
   105  
   106  	adminOpts = adminOpts.
   107  		SetOrigin(origin).
   108  		SetInstrumentOptions(instrumentOpts).
   109  		SetClusterConnectConsistencyLevel(topology.ConnectConsistencyLevelAny).
   110  		SetTopologyInitializer(topologyInitializer).
   111  		SetClusterConnectTimeout(time.Second).(client.AdminOptions)
   112  
   113  	for _, o := range customOpts {
   114  		adminOpts = o(adminOpts)
   115  	}
   116  
   117  	adminClient, err := client.NewAdminClient(adminOpts)
   118  	require.NoError(t, err)
   119  
   120  	return adminClient
   121  }
   122  
   123  // BootstrappableTestSetupOptions defines options for test setups.
   124  type BootstrappableTestSetupOptions struct {
   125  	FinalBootstrapper            string
   126  	BootstrapBlocksBatchSize     int
   127  	BootstrapBlocksConcurrency   int
   128  	BootstrapConsistencyLevel    topology.ReadConsistencyLevel
   129  	TopologyInitializer          topology.Initializer
   130  	TestStatsReporter            xmetrics.TestStatsReporter
   131  	DisableCommitLogBootstrapper bool
   132  	DisablePeersBootstrapper     bool
   133  	UseTChannelClientForWriting  bool
   134  	EnableRepairs                bool
   135  	ForceRepairs                 bool
   136  	RepairType                   repair.Type
   137  	AdminClientCustomOpts        []client.CustomAdminOption
   138  }
   139  
   140  type closeFn func()
   141  
   142  func newDefaulTestResultOptions(
   143  	storageOpts storage.Options,
   144  ) result.Options {
   145  	return result.NewOptions().
   146  		SetClockOptions(storageOpts.ClockOptions()).
   147  		SetInstrumentOptions(storageOpts.InstrumentOptions()).
   148  		SetDatabaseBlockOptions(storageOpts.DatabaseBlockOptions()).
   149  		SetSeriesCachePolicy(storageOpts.SeriesCachePolicy())
   150  }
   151  
   152  // NewDefaultBootstrappableTestSetups creates dbnode test setups.
   153  func NewDefaultBootstrappableTestSetups( // nolint:gocyclo
   154  	t *testing.T,
   155  	opts TestOptions,
   156  	setupOpts []BootstrappableTestSetupOptions,
   157  ) (testSetups, closeFn) {
   158  	var (
   159  		replicas        = len(setupOpts)
   160  		setups          []TestSetup
   161  		cleanupFns      []func()
   162  		cleanupFnsMutex sync.RWMutex
   163  
   164  		appendCleanupFn = func(fn func()) {
   165  			cleanupFnsMutex.Lock()
   166  			defer cleanupFnsMutex.Unlock()
   167  			cleanupFns = append(cleanupFns, fn)
   168  		}
   169  	)
   170  
   171  	shardSet, err := newTestShardSet(opts.NumShards(), opts.ShardSetOptions())
   172  	require.NoError(t, err)
   173  	for i := 0; i < replicas; i++ {
   174  		var (
   175  			instance                    = i
   176  			usingCommitLogBootstrapper  = !setupOpts[i].DisableCommitLogBootstrapper
   177  			usingPeersBootstrapper      = !setupOpts[i].DisablePeersBootstrapper
   178  			finalBootstrapperToUse      = setupOpts[i].FinalBootstrapper
   179  			useTChannelClientForWriting = setupOpts[i].UseTChannelClientForWriting
   180  			bootstrapBlocksBatchSize    = setupOpts[i].BootstrapBlocksBatchSize
   181  			bootstrapBlocksConcurrency  = setupOpts[i].BootstrapBlocksConcurrency
   182  			bootstrapConsistencyLevel   = setupOpts[i].BootstrapConsistencyLevel
   183  			topologyInitializer         = setupOpts[i].TopologyInitializer
   184  			testStatsReporter           = setupOpts[i].TestStatsReporter
   185  			enableRepairs               = setupOpts[i].EnableRepairs
   186  			forceRepairs                = setupOpts[i].ForceRepairs
   187  			repairType                  = setupOpts[i].RepairType
   188  			origin                      topology.Host
   189  			instanceOpts                = newMultiAddrTestOptions(opts, instance)
   190  			adminClientCustomOpts       = setupOpts[i].AdminClientCustomOpts
   191  		)
   192  
   193  		if finalBootstrapperToUse == "" {
   194  			finalBootstrapperToUse = bootstrapper.NoOpNoneBootstrapperName
   195  		}
   196  
   197  		if topologyInitializer == nil {
   198  			// Setup static topology initializer
   199  			var (
   200  				start         = multiAddrPortStart
   201  				hostShardSets []topology.HostShardSet
   202  			)
   203  
   204  			for i := 0; i < replicas; i++ {
   205  				id := fmt.Sprintf("testhost%d", i)
   206  				nodeAddr := fmt.Sprintf("127.0.0.1:%d", start+(i*multiAddrPortEach))
   207  				host := topology.NewHost(id, nodeAddr)
   208  				if i == instance {
   209  					origin = host
   210  				}
   211  				hostShardSet := topology.NewHostShardSet(host, shardSet)
   212  				hostShardSets = append(hostShardSets, hostShardSet)
   213  			}
   214  
   215  			staticOptions := topology.NewStaticOptions().
   216  				SetShardSet(shardSet).
   217  				SetReplicas(replicas).
   218  				SetHostShardSets(hostShardSets)
   219  			topologyInitializer = topology.NewStaticInitializer(staticOptions)
   220  		}
   221  
   222  		instanceOpts = instanceOpts.
   223  			SetClusterDatabaseTopologyInitializer(topologyInitializer).
   224  			SetUseTChannelClientForWriting(useTChannelClientForWriting)
   225  
   226  		if i > 0 {
   227  			// NB(bodu): Need to reset the global counter of number of index
   228  			// claim manager instances after the initial node.
   229  			persistfs.ResetIndexClaimsManagersUnsafe()
   230  		}
   231  		setup, err := NewTestSetup(t, instanceOpts, nil, opts.StorageOptsFn())
   232  		require.NoError(t, err)
   233  		topologyInitializer = setup.TopologyInitializer()
   234  
   235  		instrumentOpts := setup.StorageOpts().InstrumentOptions()
   236  		logger := instrumentOpts.Logger()
   237  		logger = logger.With(zap.Int("instance", instance))
   238  		instrumentOpts = instrumentOpts.SetLogger(logger)
   239  		if testStatsReporter != nil {
   240  			scope, _ := tally.NewRootScope(tally.ScopeOptions{Reporter: testStatsReporter}, 100*time.Millisecond)
   241  			instrumentOpts = instrumentOpts.SetMetricsScope(scope)
   242  		}
   243  		setup.SetStorageOpts(setup.StorageOpts().SetInstrumentOptions(instrumentOpts))
   244  
   245  		var (
   246  			bsOpts            = newDefaulTestResultOptions(setup.StorageOpts())
   247  			finalBootstrapper bootstrap.BootstrapperProvider
   248  
   249  			adminOpts = client.NewAdminOptions().
   250  					SetTopologyInitializer(topologyInitializer).(client.AdminOptions).
   251  					SetOrigin(origin)
   252  
   253  			// Prevent integration tests from timing out when a node is down
   254  			retryOpts = xretry.NewOptions().
   255  					SetInitialBackoff(1 * time.Millisecond).
   256  					SetMaxRetries(1).
   257  					SetJitter(true)
   258  			retrier = xretry.NewRetrier(retryOpts)
   259  		)
   260  
   261  		switch finalBootstrapperToUse {
   262  		case bootstrapper.NoOpAllBootstrapperName:
   263  			finalBootstrapper = bootstrapper.NewNoOpAllBootstrapperProvider()
   264  		case bootstrapper.NoOpNoneBootstrapperName:
   265  			finalBootstrapper = bootstrapper.NewNoOpNoneBootstrapperProvider()
   266  		case uninitialized.UninitializedTopologyBootstrapperName:
   267  			finalBootstrapper = uninitialized.NewUninitializedTopologyBootstrapperProvider(
   268  				uninitialized.NewOptions().
   269  					SetInstrumentOptions(instrumentOpts), nil)
   270  		default:
   271  			panic(fmt.Sprintf(
   272  				"Unknown final bootstrapper to use: %v", finalBootstrapperToUse))
   273  		}
   274  
   275  		if bootstrapBlocksBatchSize > 0 {
   276  			adminOpts = adminOpts.SetFetchSeriesBlocksBatchSize(bootstrapBlocksBatchSize)
   277  		}
   278  		if bootstrapBlocksConcurrency > 0 {
   279  			adminOpts = adminOpts.SetFetchSeriesBlocksBatchConcurrency(bootstrapBlocksConcurrency)
   280  		}
   281  		adminOpts = adminOpts.SetStreamBlocksRetrier(retrier)
   282  
   283  		adminClient := newMultiAddrAdminClient(
   284  			t, adminOpts, topologyInitializer, origin, instrumentOpts, adminClientCustomOpts...)
   285  		setup.SetStorageOpts(setup.StorageOpts().SetAdminClient(adminClient))
   286  
   287  		storageIdxOpts := setup.StorageOpts().IndexOptions()
   288  		fsOpts := setup.StorageOpts().CommitLogOptions().FilesystemOptions()
   289  		if usingPeersBootstrapper {
   290  			var (
   291  				runtimeOptsMgr = setup.StorageOpts().RuntimeOptionsManager()
   292  				runtimeOpts    = runtimeOptsMgr.Get().
   293  						SetClientBootstrapConsistencyLevel(bootstrapConsistencyLevel)
   294  			)
   295  			runtimeOptsMgr.Update(runtimeOpts)
   296  
   297  			peersOpts := peers.NewOptions().
   298  				SetResultOptions(bsOpts).
   299  				SetAdminClient(adminClient).
   300  				SetIndexOptions(storageIdxOpts).
   301  				SetFilesystemOptions(fsOpts).
   302  				// PersistManager need to be set or we will never execute
   303  				// the persist bootstrapping path
   304  				SetPersistManager(setup.StorageOpts().PersistManager()).
   305  				SetIndexClaimsManager(setup.StorageOpts().IndexClaimsManager()).
   306  				SetCompactor(newCompactor(t, storageIdxOpts)).
   307  				SetRuntimeOptionsManager(runtimeOptsMgr).
   308  				SetContextPool(setup.StorageOpts().ContextPool())
   309  
   310  			finalBootstrapper, err = peers.NewPeersBootstrapperProvider(peersOpts, finalBootstrapper)
   311  			require.NoError(t, err)
   312  		}
   313  
   314  		if usingCommitLogBootstrapper {
   315  			bootstrapCommitlogOpts := commitlog.NewOptions().
   316  				SetResultOptions(bsOpts).
   317  				SetCommitLogOptions(setup.StorageOpts().CommitLogOptions()).
   318  				SetRuntimeOptionsManager(runtime.NewOptionsManager())
   319  
   320  			finalBootstrapper, err = commitlog.NewCommitLogBootstrapperProvider(bootstrapCommitlogOpts,
   321  				mustInspectFilesystem(fsOpts), finalBootstrapper)
   322  			require.NoError(t, err)
   323  		}
   324  
   325  		persistMgr, err := persistfs.NewPersistManager(fsOpts)
   326  		require.NoError(t, err)
   327  
   328  		bfsOpts := bfs.NewOptions().
   329  			SetResultOptions(bsOpts).
   330  			SetFilesystemOptions(fsOpts).
   331  			SetIndexOptions(storageIdxOpts).
   332  			SetCompactor(newCompactor(t, storageIdxOpts)).
   333  			SetPersistManager(persistMgr).
   334  			SetIndexClaimsManager(setup.StorageOpts().IndexClaimsManager())
   335  
   336  		fsBootstrapper, err := bfs.NewFileSystemBootstrapperProvider(bfsOpts, finalBootstrapper)
   337  		require.NoError(t, err)
   338  
   339  		processOpts := bootstrap.NewProcessOptions().
   340  			SetTopologyMapProvider(setup).
   341  			SetOrigin(setup.Origin())
   342  		provider, err := bootstrap.NewProcessProvider(fsBootstrapper, processOpts, bsOpts, fsOpts)
   343  		require.NoError(t, err)
   344  
   345  		setup.SetStorageOpts(setup.StorageOpts().SetBootstrapProcessProvider(provider))
   346  
   347  		if enableRepairs {
   348  			setup.SetStorageOpts(setup.StorageOpts().
   349  				SetRepairEnabled(true).
   350  				SetRepairOptions(
   351  					setup.StorageOpts().RepairOptions().
   352  						SetType(repairType).
   353  						SetForce(forceRepairs).
   354  						SetRepairThrottle(time.Millisecond).
   355  						SetRepairCheckInterval(time.Millisecond).
   356  						SetAdminClients([]client.AdminClient{adminClient}).
   357  						SetDebugShadowComparisonsPercentage(1.0).
   358  						// Avoid log spam.
   359  						SetDebugShadowComparisonsEnabled(false)))
   360  		}
   361  
   362  		setups = append(setups, setup)
   363  		appendCleanupFn(func() {
   364  			setup.Close()
   365  		})
   366  	}
   367  
   368  	return setups, func() {
   369  		cleanupFnsMutex.RLock()
   370  		defer cleanupFnsMutex.RUnlock()
   371  		for _, fn := range cleanupFns {
   372  			fn()
   373  		}
   374  	}
   375  }
   376  
   377  func writeTestDataToDiskWithIndex(
   378  	metadata namespace.Metadata,
   379  	s TestSetup,
   380  	seriesMaps generate.SeriesBlocksByStart,
   381  ) error {
   382  	if err := writeTestDataToDisk(metadata, s, seriesMaps, 0); err != nil {
   383  		return err
   384  	}
   385  	for blockStart, series := range seriesMaps {
   386  		docs := generate.ToDocMetadata(series)
   387  		if err := writeTestIndexDataToDisk(
   388  			metadata,
   389  			s.StorageOpts(),
   390  			idxpersist.DefaultIndexVolumeType,
   391  			blockStart,
   392  			s.ShardSet().AllIDs(),
   393  			docs,
   394  		); err != nil {
   395  			return err
   396  		}
   397  	}
   398  	return nil
   399  }
   400  
   401  func writeTestDataToDisk(
   402  	metadata namespace.Metadata,
   403  	setup TestSetup,
   404  	seriesMaps generate.SeriesBlocksByStart,
   405  	volume int,
   406  	generatorOptionsFns ...func(generate.Options) generate.Options,
   407  ) error {
   408  	ropts := metadata.Options().RetentionOptions()
   409  	gOpts := setup.GeneratorOptions(ropts)
   410  	for _, fn := range generatorOptionsFns {
   411  		gOpts = fn(gOpts)
   412  	}
   413  	writer := generate.NewWriter(gOpts)
   414  	return writer.WriteData(namespace.NewContextFrom(metadata), setup.ShardSet(), seriesMaps, volume)
   415  }
   416  
   417  func writeTestSnapshotsToDiskWithPredicate(
   418  	metadata namespace.Metadata,
   419  	setup TestSetup,
   420  	seriesMaps generate.SeriesBlocksByStart,
   421  	volume int,
   422  	pred generate.WriteDatapointPredicate,
   423  	snapshotInterval time.Duration,
   424  ) error {
   425  	ropts := metadata.Options().RetentionOptions()
   426  	writer := generate.NewWriter(setup.GeneratorOptions(ropts))
   427  	return writer.WriteSnapshotWithPredicate(
   428  		namespace.NewContextFrom(metadata), setup.ShardSet(), seriesMaps, volume, pred, snapshotInterval)
   429  }
   430  
   431  func concatShards(a, b shard.Shards) shard.Shards {
   432  	all := append(a.All(), b.All()...)
   433  	return shard.NewShards(all)
   434  }
   435  
   436  func newClusterShardsRange(from, to uint32, s shard.State) shard.Shards {
   437  	return shard.NewShards(testutil.ShardsRange(from, to, s))
   438  }
   439  
   440  func newClusterEmptyShardsRange() shard.Shards {
   441  	return shard.NewShards(testutil.Shards(nil, shard.Available))
   442  }
   443  
   444  func waitUntilHasBootstrappedShardsExactly(
   445  	db storage.Database,
   446  	shards []uint32,
   447  ) {
   448  	for {
   449  		if hasBootstrappedShardsExactly(db, shards) {
   450  			return
   451  		}
   452  		time.Sleep(time.Second)
   453  	}
   454  }
   455  
   456  func hasBootstrappedShardsExactly(
   457  	db storage.Database,
   458  	shards []uint32,
   459  ) bool {
   460  	for _, namespace := range db.Namespaces() {
   461  		expect := make(map[uint32]struct{})
   462  		pending := make(map[uint32]struct{})
   463  		for _, shard := range shards {
   464  			expect[shard] = struct{}{}
   465  			pending[shard] = struct{}{}
   466  		}
   467  
   468  		for _, s := range namespace.Shards() {
   469  			if _, ok := expect[s.ID()]; !ok {
   470  				// Not expecting shard
   471  				return false
   472  			}
   473  			if s.IsBootstrapped() {
   474  				delete(pending, s.ID())
   475  			}
   476  		}
   477  
   478  		if len(pending) != 0 {
   479  			// Not all shards bootstrapped
   480  			return false
   481  		}
   482  	}
   483  
   484  	return true
   485  }
   486  
   487  func newCompactor(
   488  	t *testing.T,
   489  	opts index.Options,
   490  ) *compaction.Compactor {
   491  	compactor, err := newCompactorWithErr(opts)
   492  	require.NoError(t, err)
   493  	return compactor
   494  }
   495  
   496  func newCompactorWithErr(opts index.Options) (*compaction.Compactor, error) {
   497  	return compaction.NewCompactor(opts.MetadataArrayPool(),
   498  		index.MetadataArrayPoolCapacity,
   499  		opts.SegmentBuilderOptions(),
   500  		opts.FSTSegmentOptions(),
   501  		compaction.CompactorOptions{
   502  			FSTWriterOptions: &fst.WriterOptions{
   503  				// DisableRegistry is set to true to trade a larger FST size
   504  				// for a faster FST compaction since we want to reduce the end
   505  				// to end latency for time to first index a metric.
   506  				DisableRegistry: true,
   507  			},
   508  		})
   509  }
   510  
   511  func writeTestIndexDataToDisk(
   512  	md namespace.Metadata,
   513  	storageOpts storage.Options,
   514  	indexVolumeType idxpersist.IndexVolumeType,
   515  	blockStart xtime.UnixNano,
   516  	shards []uint32,
   517  	docs []doc.Metadata,
   518  ) error {
   519  	blockSize := md.Options().IndexOptions().BlockSize()
   520  	fsOpts := storageOpts.CommitLogOptions().FilesystemOptions()
   521  	writer, err := fs.NewIndexWriter(fsOpts)
   522  	if err != nil {
   523  		return err
   524  	}
   525  	segmentWriter, err := idxpersist.NewMutableSegmentFileSetWriter(fst.WriterOptions{})
   526  	if err != nil {
   527  		return err
   528  	}
   529  
   530  	shardsMap := make(map[uint32]struct{})
   531  	for _, shard := range shards {
   532  		shardsMap[shard] = struct{}{}
   533  	}
   534  	volumeIndex, err := fs.NextIndexFileSetVolumeIndex(
   535  		fsOpts.FilePathPrefix(),
   536  		md.ID(),
   537  		blockStart,
   538  	)
   539  	if err != nil {
   540  		return err
   541  	}
   542  	writerOpts := fs.IndexWriterOpenOptions{
   543  		Identifier: fs.FileSetFileIdentifier{
   544  			Namespace:   md.ID(),
   545  			BlockStart:  blockStart,
   546  			VolumeIndex: volumeIndex,
   547  		},
   548  		BlockSize:       blockSize,
   549  		Shards:          shardsMap,
   550  		IndexVolumeType: indexVolumeType,
   551  	}
   552  	if err := writer.Open(writerOpts); err != nil {
   553  		return err
   554  	}
   555  
   556  	builder, err := builder.NewBuilderFromDocuments(builder.NewOptions())
   557  	for _, doc := range docs {
   558  		_, err = builder.Insert(doc)
   559  		if err != nil {
   560  			return err
   561  		}
   562  	}
   563  
   564  	if err := segmentWriter.Reset(builder); err != nil {
   565  		return err
   566  	}
   567  	if err := writer.WriteSegmentFileSet(segmentWriter); err != nil {
   568  		return err
   569  	}
   570  	if err := builder.Close(); err != nil {
   571  		return err
   572  	}
   573  	return writer.Close()
   574  }