github.com/matrixorigin/matrixone@v0.7.0/pkg/logservice/rsm.go (about)

     1  // Copyright 2021 - 2022 Matrix Origin
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package logservice
    16  
    17  import (
    18  	"encoding/binary"
    19  	"encoding/gob"
    20  	"io"
    21  
    22  	sm "github.com/lni/dragonboat/v4/statemachine"
    23  	pb "github.com/matrixorigin/matrixone/pkg/pb/logservice"
    24  )
    25  
    26  // XXX WHY BIG ENDIAN?
    27  var (
    28  	binaryEnc = binary.BigEndian
    29  )
    30  
    31  const (
    32  	firstLogShardID uint64 = 1
    33  	headerSize             = pb.HeaderSize
    34  )
    35  
    36  // used to indicate query types
    37  type leaseHolderIDQuery struct{}
    38  type indexQuery struct{}
    39  type truncatedLsnQuery struct{}
    40  type leaseHistoryQuery struct{ lsn uint64 }
    41  
    42  func getAppendCmd(cmd []byte, replicaID uint64) []byte {
    43  	if len(cmd) < headerSize+8 {
    44  		panic("cmd too small")
    45  	}
    46  	binaryEnc.PutUint32(cmd, uint32(pb.UserEntryUpdate))
    47  	binaryEnc.PutUint64(cmd[headerSize:], replicaID)
    48  	return cmd
    49  }
    50  
    51  func parseCmdTag(cmd []byte) pb.UpdateType {
    52  	return pb.UpdateType(binaryEnc.Uint32(cmd))
    53  }
    54  
    55  func parseTruncatedLsn(cmd []byte) uint64 {
    56  	return binaryEnc.Uint64(cmd[headerSize:])
    57  }
    58  
    59  func parseLeaseHolderID(cmd []byte) uint64 {
    60  	return binaryEnc.Uint64(cmd[headerSize:])
    61  }
    62  
    63  func parseTsoUpdateCmd(cmd []byte) uint64 {
    64  	return binaryEnc.Uint64(cmd[headerSize:])
    65  }
    66  
    67  func getSetLeaseHolderCmd(leaseHolderID uint64) []byte {
    68  	cmd := make([]byte, headerSize+8)
    69  	binaryEnc.PutUint32(cmd, uint32(pb.LeaseHolderIDUpdate))
    70  	binaryEnc.PutUint64(cmd[headerSize:], leaseHolderID)
    71  	return cmd
    72  }
    73  
    74  func getSetTruncatedLsnCmd(lsn uint64) []byte {
    75  	cmd := make([]byte, headerSize+8)
    76  	binaryEnc.PutUint32(cmd, uint32(pb.TruncateLSNUpdate))
    77  	binaryEnc.PutUint64(cmd[headerSize:], lsn)
    78  	return cmd
    79  }
    80  
    81  func getTsoUpdateCmd(count uint64) []byte {
    82  	cmd := make([]byte, headerSize+8)
    83  	binaryEnc.PutUint32(cmd, uint32(pb.TSOUpdate))
    84  	binaryEnc.PutUint64(cmd[headerSize:], count)
    85  	return cmd
    86  }
    87  
    88  type stateMachine struct {
    89  	shardID   uint64
    90  	replicaID uint64
    91  	state     pb.RSMState
    92  }
    93  
    94  var _ sm.IStateMachine = (*stateMachine)(nil)
    95  
    96  func newStateMachine(shardID uint64, replicaID uint64) sm.IStateMachine {
    97  	state := pb.RSMState{
    98  		Tso:          1,
    99  		LeaseHistory: make(map[uint64]uint64),
   100  	}
   101  	return &stateMachine{
   102  		shardID:   shardID,
   103  		replicaID: replicaID,
   104  		state:     state,
   105  	}
   106  }
   107  
   108  func (s *stateMachine) truncateLeaseHistory(lsn uint64) {
   109  	_, lsn = s.getLeaseHistory(lsn)
   110  	for key := range s.state.LeaseHistory {
   111  		if key < lsn {
   112  			delete(s.state.LeaseHistory, key)
   113  		}
   114  	}
   115  }
   116  
   117  func (s *stateMachine) getLeaseHistory(lsn uint64) (uint64, uint64) {
   118  	max := uint64(0)
   119  	lease := uint64(0)
   120  	for key, val := range s.state.LeaseHistory {
   121  		if key >= lsn {
   122  			continue
   123  		}
   124  		if key > max {
   125  			max = key
   126  			lease = val
   127  		}
   128  	}
   129  	return lease, max
   130  }
   131  
   132  func (s *stateMachine) handleSetLeaseHolderID(cmd []byte) sm.Result {
   133  	s.state.LeaseHolderID = parseLeaseHolderID(cmd)
   134  	s.state.LeaseHistory[s.state.Index] = s.state.LeaseHolderID
   135  	return sm.Result{}
   136  }
   137  
   138  func (s *stateMachine) handleTruncateLsn(cmd []byte) sm.Result {
   139  	lsn := parseTruncatedLsn(cmd)
   140  	if lsn > s.state.TruncatedLsn {
   141  		s.state.TruncatedLsn = lsn
   142  		s.truncateLeaseHistory(lsn)
   143  		return sm.Result{}
   144  	}
   145  	return sm.Result{Value: s.state.TruncatedLsn}
   146  }
   147  
   148  // handleUserUpdate returns an empty sm.Result on success or it returns a
   149  // sm.Result value with the Value field set to the current leaseholder ID
   150  // to indicate rejection by mismatched leaseholder ID.
   151  func (s *stateMachine) handleUserUpdate(cmd []byte) sm.Result {
   152  	if s.state.LeaseHolderID != parseLeaseHolderID(cmd) {
   153  		data := make([]byte, 8)
   154  		binaryEnc.PutUint64(data, s.state.LeaseHolderID)
   155  		return sm.Result{Data: data}
   156  	}
   157  	return sm.Result{Value: s.state.Index}
   158  }
   159  
   160  func (s *stateMachine) handleTsoUpdate(cmd []byte) sm.Result {
   161  	count := parseTsoUpdateCmd(cmd)
   162  	result := sm.Result{Value: s.state.Tso}
   163  	s.state.Tso += count
   164  	return result
   165  }
   166  
   167  func (s *stateMachine) Close() error {
   168  	return nil
   169  }
   170  
   171  func (s *stateMachine) Update(e sm.Entry) (sm.Result, error) {
   172  	cmd := e.Cmd
   173  	s.state.Index = e.Index
   174  
   175  	switch parseCmdTag(cmd) {
   176  	case pb.LeaseHolderIDUpdate:
   177  		return s.handleSetLeaseHolderID(cmd), nil
   178  	case pb.TruncateLSNUpdate:
   179  		return s.handleTruncateLsn(cmd), nil
   180  	case pb.UserEntryUpdate:
   181  		return s.handleUserUpdate(cmd), nil
   182  	case pb.TSOUpdate:
   183  		return s.handleTsoUpdate(cmd), nil
   184  	default:
   185  		panic("unknown entry type")
   186  	}
   187  }
   188  
   189  func (s *stateMachine) Lookup(query interface{}) (interface{}, error) {
   190  	if _, ok := query.(indexQuery); ok {
   191  		return s.state.Index, nil
   192  	} else if _, ok := query.(leaseHolderIDQuery); ok {
   193  		return s.state.LeaseHolderID, nil
   194  	} else if _, ok := query.(truncatedLsnQuery); ok {
   195  		return s.state.TruncatedLsn, nil
   196  	} else if v, ok := query.(leaseHistoryQuery); ok {
   197  		lease, _ := s.getLeaseHistory(v.lsn)
   198  		return lease, nil
   199  	}
   200  	panic("unknown lookup command type")
   201  }
   202  
   203  func (s *stateMachine) SaveSnapshot(w io.Writer,
   204  	_ sm.ISnapshotFileCollection, _ <-chan struct{}) error {
   205  	// FIXME: use gogoproto to marshal the state, need to figure out how to
   206  	// marshal to a io.Writer
   207  	enc := gob.NewEncoder(w)
   208  	return enc.Encode(s.state)
   209  }
   210  
   211  func (s *stateMachine) RecoverFromSnapshot(r io.Reader,
   212  	_ []sm.SnapshotFile, _ <-chan struct{}) error {
   213  	dec := gob.NewDecoder(r)
   214  	return dec.Decode(&s.state)
   215  }