From f096b067fd2171919810d6e9536e9068b899f7db Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Fri, 31 Oct 2025 18:29:16 -0700 Subject: [PATCH] weed master add peers=none option for faster startup (#7419) * weed master -peers=none * single master mode only when peers is none * refactoring * revert duplicated code * revert * Update weed/command/master.go Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * preventing "none" passed to other components if master is not started --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- weed/command/master.go | 38 ++++++++++++++++++++++++++++++++++---- weed/command/server.go | 18 +++++++++++++----- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/weed/command/master.go b/weed/command/master.go index 7721c0634..11c57701b 100644 --- a/weed/command/master.go +++ b/weed/command/master.go @@ -38,6 +38,10 @@ var ( m MasterOptions ) +const ( + raftJoinCheckDelay = 1500 * time.Millisecond // delay before checking if we should join a raft cluster +) + type MasterOptions struct { port *int portGrpc *int @@ -74,7 +78,7 @@ func init() { m.ip = cmdMaster.Flag.String("ip", util.DetectedHostAddress(), "master | address, also used as identifier") m.ipBind = cmdMaster.Flag.String("ip.bind", "", "ip address to bind to. If empty, default to same as -ip option.") m.metaFolder = cmdMaster.Flag.String("mdir", os.TempDir(), "data directory to store meta data") - m.peers = cmdMaster.Flag.String("peers", "", "all master nodes in comma separated ip:port list, example: 127.0.0.1:9093,127.0.0.1:9094,127.0.0.1:9095") + m.peers = cmdMaster.Flag.String("peers", "", "all master nodes in comma separated ip:port list, example: 127.0.0.1:9093,127.0.0.1:9094,127.0.0.1:9095; use 'none' for single-master mode") m.volumeSizeLimitMB = cmdMaster.Flag.Uint("volumeSizeLimitMB", 30*1000, "Master stops directing writes to oversized volumes.") m.volumePreallocate = cmdMaster.Flag.Bool("volumePreallocate", false, "Preallocate disk space for volumes.") m.maxParallelVacuumPerServer = cmdMaster.Flag.Int("maxParallelVacuumPerServer", 1, "maximum number of volumes to vacuum in parallel per volume server") @@ -105,6 +109,9 @@ var cmdMaster = &Command{ The example security.toml configuration file can be generated by "weed scaffold -config=security" + For single-master setups, use -peers=none to skip Raft quorum wait and enable instant startup. + This is ideal for development or standalone deployments. + `, } @@ -181,6 +188,9 @@ func startMaster(masterOption MasterOptions, masterWhiteList []string) { // start raftServer metaDir := path.Join(*masterOption.metaFolder, fmt.Sprintf("m%d", *masterOption.port)) + + isSingleMaster := isSingleMasterMode(*masterOption.peers) + raftServerOption := &weed_server.RaftServerOption{ GrpcDialOption: security.LoadClientTLS(util.GetViper(), "grpc.master"), Peers: masterPeers, @@ -203,6 +213,11 @@ func startMaster(masterOption MasterOptions, masterWhiteList []string) { if raftServer == nil { glog.Fatalf("please verify %s is writable, see https://github.com/seaweedfs/seaweedfs/issues/717: %s", *masterOption.metaFolder, err) } + // For single-master mode, initialize cluster immediately without waiting + if isSingleMaster { + glog.V(0).Infof("Single-master mode: initializing cluster immediately") + raftServer.DoJoinCommand() + } } ms.SetRaftServer(raftServer) r.HandleFunc("/cluster/status", raftServer.StatusHandler).Methods(http.MethodGet, http.MethodHead) @@ -230,10 +245,10 @@ func startMaster(masterOption MasterOptions, masterWhiteList []string) { } go grpcS.Serve(grpcL) - timeSleep := 1500 * time.Millisecond - if !*masterOption.raftHashicorp { + // For multi-master mode with non-Hashicorp raft, wait and check if we should join + if !*masterOption.raftHashicorp && !isSingleMaster { go func() { - time.Sleep(timeSleep) + time.Sleep(raftJoinCheckDelay) ms.Topo.RaftServerAccessLock.RLock() isEmptyMaster := ms.Topo.RaftServer.Leader() == "" && ms.Topo.RaftServer.IsLogEmpty() @@ -292,9 +307,24 @@ func startMaster(masterOption MasterOptions, masterWhiteList []string) { select {} } +func isSingleMasterMode(peers string) bool { + p := strings.ToLower(strings.TrimSpace(peers)) + return p == "none" +} + func checkPeers(masterIp string, masterPort int, masterGrpcPort int, peers string) (masterAddress pb.ServerAddress, cleanedPeers []pb.ServerAddress) { glog.V(0).Infof("current: %s:%d peers:%s", masterIp, masterPort, peers) masterAddress = pb.NewServerAddress(masterIp, masterPort, masterGrpcPort) + + // Handle special case: -peers=none for single-master setup + if isSingleMasterMode(peers) { + glog.V(0).Infof("Running in single-master mode (peers=none), no quorum required") + cleanedPeers = []pb.ServerAddress{masterAddress} + return + } + + peers = strings.TrimSpace(peers) + cleanedPeers = pb.ServerAddresses(peers).ToAddresses() hasSelf := false diff --git a/weed/command/server.go b/weed/command/server.go index f2e2e1b58..8c99d04fd 100644 --- a/weed/command/server.go +++ b/weed/command/server.go @@ -232,10 +232,17 @@ func runServer(cmd *Command, args []string) bool { *isStartingFiler = true } + var actualPeersForComponents string if *isStartingMasterServer { + // If we are starting a master, validate and complete the peer list _, peerList := checkPeers(*serverIp, *masterOptions.port, *masterOptions.portGrpc, *masterOptions.peers) - peers := strings.Join(pb.ToAddressStrings(peerList), ",") - masterOptions.peers = &peers + actualPeersForComponents = strings.Join(pb.ToAddressStrings(peerList), ",") + } else if *masterOptions.peers != "" { + if isSingleMasterMode(*masterOptions.peers) { + glog.Fatalf("'-master.peers=none' is only valid when starting a master server, but master is not starting.") + } + // If not starting a master, just use the provided peers + actualPeersForComponents = *masterOptions.peers } if *serverBindIp == "" { @@ -249,7 +256,8 @@ func runServer(cmd *Command, args []string) bool { // ip address masterOptions.ip = serverIp masterOptions.ipBind = serverBindIp - filerOptions.masters = pb.ServerAddresses(*masterOptions.peers).ToServiceDiscovery() + // Use actualPeersForComponents for volume/filer, not masterOptions.peers which might be "none" + filerOptions.masters = pb.ServerAddresses(actualPeersForComponents).ToServiceDiscovery() filerOptions.ip = serverIp filerOptions.bindIp = serverBindIp if *s3Options.bindIp == "" { @@ -259,11 +267,11 @@ func runServer(cmd *Command, args []string) bool { sftpOptions.bindIp = serverBindIp } iamOptions.ip = serverBindIp - iamOptions.masters = masterOptions.peers + iamOptions.masters = &actualPeersForComponents webdavOptions.ipBind = serverBindIp serverOptions.v.ip = serverIp serverOptions.v.bindIp = serverBindIp - serverOptions.v.masters = pb.ServerAddresses(*masterOptions.peers).ToAddresses() + serverOptions.v.masters = pb.ServerAddresses(actualPeersForComponents).ToAddresses() serverOptions.v.idleConnectionTimeout = serverTimeout serverOptions.v.dataCenter = serverDataCenter serverOptions.v.rack = serverRack