go.temporal.io/server@v1.23.0/common/persistence/cassandra/history_store.go (about)

     1  // The MIT License
     2  //
     3  // Copyright (c) 2020 Temporal Technologies Inc.  All rights reserved.
     4  //
     5  // Copyright (c) 2020 Uber Technologies, Inc.
     6  //
     7  // Permission is hereby granted, free of charge, to any person obtaining a copy
     8  // of this software and associated documentation files (the "Software"), to deal
     9  // in the Software without restriction, including without limitation the rights
    10  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    11  // copies of the Software, and to permit persons to whom the Software is
    12  // furnished to do so, subject to the following conditions:
    13  //
    14  // The above copyright notice and this permission notice shall be included in
    15  // all copies or substantial portions of the Software.
    16  //
    17  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    18  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    19  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    20  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    21  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    22  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    23  // THE SOFTWARE.
    24  
    25  package cassandra
    26  
    27  import (
    28  	"context"
    29  	"fmt"
    30  
    31  	commonpb "go.temporal.io/api/common/v1"
    32  	"go.temporal.io/api/serviceerror"
    33  	"go.temporal.io/server/common/log"
    34  	p "go.temporal.io/server/common/persistence"
    35  	"go.temporal.io/server/common/persistence/nosql/nosqlplugin/cassandra/gocql"
    36  	"go.temporal.io/server/common/primitives"
    37  )
    38  
    39  const (
    40  	// below are templates for history_node table
    41  	v2templateUpsertHistoryNode = `INSERT INTO history_node (` +
    42  		`tree_id, branch_id, node_id, prev_txn_id, txn_id, data, data_encoding) ` +
    43  		`VALUES (?, ?, ?, ?, ?, ?, ?) `
    44  
    45  	v2templateReadHistoryNode = `SELECT node_id, prev_txn_id, txn_id, data, data_encoding FROM history_node ` +
    46  		`WHERE tree_id = ? AND branch_id = ? AND node_id >= ? AND node_id < ? `
    47  
    48  	v2templateReadHistoryNodeReverse = `SELECT node_id, prev_txn_id, txn_id, data, data_encoding FROM history_node ` +
    49  		`WHERE tree_id = ? AND branch_id = ? AND node_id >= ? AND node_id < ? ORDER BY branch_id DESC, node_id DESC `
    50  
    51  	v2templateReadHistoryNodeMetadata = `SELECT node_id, prev_txn_id, txn_id FROM history_node ` +
    52  		`WHERE tree_id = ? AND branch_id = ? AND node_id >= ? AND node_id < ? `
    53  
    54  	v2templateDeleteHistoryNode = `DELETE FROM history_node WHERE tree_id = ? AND branch_id = ? AND node_id = ? AND txn_id = ? `
    55  
    56  	v2templateRangeDeleteHistoryNode = `DELETE FROM history_node WHERE tree_id = ? AND branch_id = ? AND node_id >= ? `
    57  
    58  	// below are templates for history_tree table
    59  	v2templateInsertTree = `INSERT INTO history_tree (` +
    60  		`tree_id, branch_id, branch, branch_encoding) ` +
    61  		`VALUES (?, ?, ?, ?) `
    62  
    63  	v2templateReadAllBranches = `SELECT branch_id, branch, branch_encoding FROM history_tree WHERE tree_id = ? `
    64  
    65  	v2templateDeleteBranch = `DELETE FROM history_tree WHERE tree_id = ? AND branch_id = ? `
    66  
    67  	v2templateScanAllTreeBranches = `SELECT tree_id, branch_id, branch, branch_encoding FROM history_tree `
    68  )
    69  
    70  type (
    71  	HistoryStore struct {
    72  		Session gocql.Session
    73  		Logger  log.Logger
    74  		p.HistoryBranchUtilImpl
    75  	}
    76  )
    77  
    78  func NewHistoryStore(
    79  	session gocql.Session,
    80  	logger log.Logger,
    81  ) *HistoryStore {
    82  	return &HistoryStore{
    83  		Session: session,
    84  		Logger:  logger,
    85  	}
    86  }
    87  
    88  // AppendHistoryNodes upsert a batch of events as a single node to a history branch
    89  // Note that it's not allowed to append above the branch's ancestors' nodes, which means nodeID >= ForkNodeID
    90  func (h *HistoryStore) AppendHistoryNodes(
    91  	ctx context.Context,
    92  	request *p.InternalAppendHistoryNodesRequest,
    93  ) error {
    94  	branchInfo := request.BranchInfo
    95  	node := request.Node
    96  
    97  	if !request.IsNewBranch {
    98  		query := h.Session.Query(v2templateUpsertHistoryNode,
    99  			branchInfo.TreeId,
   100  			branchInfo.BranchId,
   101  			node.NodeID,
   102  			node.PrevTransactionID,
   103  			node.TransactionID,
   104  			node.Events.Data,
   105  			node.Events.EncodingType.String(),
   106  		).WithContext(ctx)
   107  		if err := query.Exec(); err != nil {
   108  			return convertTimeoutError(gocql.ConvertError("AppendHistoryNodes", err))
   109  		}
   110  		return nil
   111  	}
   112  
   113  	treeInfoDataBlob := request.TreeInfo
   114  	batch := h.Session.NewBatch(gocql.LoggedBatch).WithContext(ctx)
   115  	batch.Query(v2templateInsertTree,
   116  		branchInfo.TreeId,
   117  		branchInfo.BranchId,
   118  		treeInfoDataBlob.Data,
   119  		treeInfoDataBlob.EncodingType.String(),
   120  	)
   121  	batch.Query(v2templateUpsertHistoryNode,
   122  		branchInfo.TreeId,
   123  		branchInfo.BranchId,
   124  		node.NodeID,
   125  		node.PrevTransactionID,
   126  		node.TransactionID,
   127  		node.Events.Data,
   128  		node.Events.EncodingType.String(),
   129  	)
   130  	if err := h.Session.ExecuteBatch(batch); err != nil {
   131  		return convertTimeoutError(gocql.ConvertError("AppendHistoryNodes", err))
   132  	}
   133  	return nil
   134  }
   135  
   136  // DeleteHistoryNodes delete a history node
   137  func (h *HistoryStore) DeleteHistoryNodes(
   138  	ctx context.Context,
   139  	request *p.InternalDeleteHistoryNodesRequest,
   140  ) error {
   141  	branchInfo := request.BranchInfo
   142  	treeID := branchInfo.TreeId
   143  	branchID := branchInfo.BranchId
   144  	nodeID := request.NodeID
   145  	txnID := request.TransactionID
   146  
   147  	if nodeID < p.GetBeginNodeID(branchInfo) {
   148  		return &p.InvalidPersistenceRequestError{
   149  			Msg: "cannot delete from ancestors' nodes",
   150  		}
   151  	}
   152  
   153  	query := h.Session.Query(v2templateDeleteHistoryNode,
   154  		treeID,
   155  		branchID,
   156  		nodeID,
   157  		txnID,
   158  	).WithContext(ctx)
   159  	if err := query.Exec(); err != nil {
   160  		return gocql.ConvertError("DeleteHistoryNodes", err)
   161  	}
   162  	return nil
   163  }
   164  
   165  // ReadHistoryBranch returns history node data for a branch
   166  // NOTE: For branch that has ancestors, we need to query Cassandra multiple times, because it doesn't support OR/UNION operator
   167  func (h *HistoryStore) ReadHistoryBranch(
   168  	ctx context.Context,
   169  	request *p.InternalReadHistoryBranchRequest,
   170  ) (*p.InternalReadHistoryBranchResponse, error) {
   171  	branch, err := h.GetHistoryBranchUtil().ParseHistoryBranchInfo(request.BranchToken)
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  
   176  	treeID, err := primitives.ValidateUUID(branch.TreeId)
   177  	if err != nil {
   178  		return nil, serviceerror.NewInternal(fmt.Sprintf("ReadHistoryBranch - Gocql TreeId UUID cast failed. Error: %v", err))
   179  	}
   180  
   181  	branchID, err := primitives.ValidateUUID(request.BranchID)
   182  	if err != nil {
   183  		return nil, serviceerror.NewInternal(fmt.Sprintf("ReadHistoryBranch - Gocql BranchId UUID cast failed. Error: %v", err))
   184  	}
   185  
   186  	var queryString string
   187  	if request.MetadataOnly {
   188  		queryString = v2templateReadHistoryNodeMetadata
   189  	} else if request.ReverseOrder {
   190  		queryString = v2templateReadHistoryNodeReverse
   191  	} else {
   192  		queryString = v2templateReadHistoryNode
   193  	}
   194  
   195  	query := h.Session.Query(queryString, treeID, branchID, request.MinNodeID, request.MaxNodeID).WithContext(ctx)
   196  
   197  	iter := query.PageSize(request.PageSize).PageState(request.NextPageToken).Iter()
   198  	var pagingToken []byte
   199  	if len(iter.PageState()) > 0 {
   200  		pagingToken = iter.PageState()
   201  	}
   202  
   203  	nodes := make([]p.InternalHistoryNode, 0, request.PageSize)
   204  	message := make(map[string]interface{})
   205  	for iter.MapScan(message) {
   206  		nodes = append(nodes, convertHistoryNode(message))
   207  		message = make(map[string]interface{})
   208  	}
   209  
   210  	if err := iter.Close(); err != nil {
   211  		return nil, serviceerror.NewUnavailable(fmt.Sprintf("ReadHistoryBranch. Close operation failed. Error: %v", err))
   212  	}
   213  
   214  	return &p.InternalReadHistoryBranchResponse{
   215  		Nodes:         nodes,
   216  		NextPageToken: pagingToken,
   217  	}, nil
   218  }
   219  
   220  // ForkHistoryBranch forks a new branch from an existing branch
   221  // Note that application must provide a void forking nodeID, it must be a valid nodeID in that branch.
   222  // A valid forking nodeID can be an ancestor from the existing branch.
   223  // For example, we have branch B1 with three nodes(1[1,2], 3[3,4,5] and 6[6,7,8]. 1, 3 and 6 are nodeIDs (first eventID of the batch).
   224  // So B1 looks like this:
   225  //
   226  //	     1[1,2]
   227  //	     /
   228  //	   3[3,4,5]
   229  //	  /
   230  //	6[6,7,8]
   231  //
   232  // Assuming we have branch B2 which contains one ancestor B1 stopping at 6 (exclusive). So B2 inherit nodeID 1 and 3 from B1, and have its own nodeID 6 and 8.
   233  // Branch B2 looks like this:
   234  //
   235  //	  1[1,2]
   236  //	  /
   237  //	3[3,4,5]
   238  //	 \
   239  //	  6[6,7]
   240  //	  \
   241  //	   8[8]
   242  //
   243  // Now we want to fork a new branch B3 from B2.
   244  // The only valid forking nodeIDs are 3,6 or 8.
   245  // 1 is not valid because we can't fork from first node.
   246  // 2/4/5 is NOT valid either because they are inside a batch.
   247  //
   248  // Case #1: If we fork from nodeID 6, then B3 will have an ancestor B1 which stops at 6(exclusive).
   249  // As we append a batch of events[6,7,8,9] to B3, it will look like :
   250  //
   251  //	  1[1,2]
   252  //	  /
   253  //	3[3,4,5]
   254  //	 \
   255  //	6[6,7,8,9]
   256  //
   257  // Case #2: If we fork from node 8, then B3 will have two ancestors: B1 stops at 6(exclusive) and ancestor B2 stops at 8(exclusive)
   258  // As we append a batch of events[8,9] to B3, it will look like:
   259  //
   260  //	     1[1,2]
   261  //	     /
   262  //	   3[3,4,5]
   263  //	  /
   264  //	6[6,7]
   265  //	 \
   266  //	 8[8,9]
   267  func (h *HistoryStore) ForkHistoryBranch(
   268  	ctx context.Context,
   269  	request *p.InternalForkHistoryBranchRequest,
   270  ) error {
   271  
   272  	forkB := request.ForkBranchInfo
   273  	datablob := request.TreeInfo
   274  
   275  	cqlTreeID, err := primitives.ValidateUUID(forkB.TreeId)
   276  	if err != nil {
   277  		return serviceerror.NewInternal(fmt.Sprintf("ForkHistoryBranch - Gocql TreeId UUID cast failed. Error: %v", err))
   278  	}
   279  
   280  	cqlNewBranchID, err := primitives.ValidateUUID(request.NewBranchID)
   281  	if err != nil {
   282  		return serviceerror.NewInternal(fmt.Sprintf("ForkHistoryBranch - Gocql NewBranchID UUID cast failed. Error: %v", err))
   283  	}
   284  	query := h.Session.Query(v2templateInsertTree, cqlTreeID, cqlNewBranchID, datablob.Data, datablob.EncodingType.String()).WithContext(ctx)
   285  	err = query.Exec()
   286  	if err != nil {
   287  		return gocql.ConvertError("ForkHistoryBranch", err)
   288  	}
   289  
   290  	return nil
   291  }
   292  
   293  // DeleteHistoryBranch removes a branch
   294  func (h *HistoryStore) DeleteHistoryBranch(
   295  	ctx context.Context,
   296  	request *p.InternalDeleteHistoryBranchRequest,
   297  ) error {
   298  
   299  	batch := h.Session.NewBatch(gocql.LoggedBatch).WithContext(ctx)
   300  	batch.Query(v2templateDeleteBranch, request.BranchInfo.TreeId, request.BranchInfo.BranchId)
   301  
   302  	// delete each branch range
   303  	for _, br := range request.BranchRanges {
   304  		h.deleteBranchRangeNodes(batch, request.BranchInfo.TreeId, br.BranchId, br.BeginNodeId)
   305  	}
   306  
   307  	err := h.Session.ExecuteBatch(batch)
   308  	if err != nil {
   309  		return gocql.ConvertError("DeleteHistoryBranch", err)
   310  	}
   311  	return nil
   312  }
   313  
   314  func (h *HistoryStore) deleteBranchRangeNodes(
   315  	batch gocql.Batch,
   316  	treeID string,
   317  	branchID string,
   318  	beginNodeID int64,
   319  ) {
   320  
   321  	batch.Query(v2templateRangeDeleteHistoryNode,
   322  		treeID,
   323  		branchID,
   324  		beginNodeID)
   325  }
   326  
   327  func (h *HistoryStore) GetAllHistoryTreeBranches(
   328  	ctx context.Context,
   329  	request *p.GetAllHistoryTreeBranchesRequest,
   330  ) (*p.InternalGetAllHistoryTreeBranchesResponse, error) {
   331  
   332  	query := h.Session.Query(v2templateScanAllTreeBranches).WithContext(ctx)
   333  
   334  	iter := query.PageSize(request.PageSize).PageState(request.NextPageToken).Iter()
   335  
   336  	var pagingToken []byte
   337  	if len(iter.PageState()) > 0 {
   338  		pagingToken = iter.PageState()
   339  	}
   340  
   341  	branches := make([]p.InternalHistoryBranchDetail, 0, request.PageSize)
   342  	treeUUID := ""
   343  	branchUUID := ""
   344  	var data []byte
   345  	var encoding string
   346  
   347  	for iter.Scan(&treeUUID, &branchUUID, &data, &encoding) {
   348  		branch := p.InternalHistoryBranchDetail{
   349  			TreeID:   treeUUID,
   350  			BranchID: branchUUID,
   351  			Data:     data,
   352  			Encoding: encoding,
   353  		}
   354  		branches = append(branches, branch)
   355  
   356  		treeUUID = ""
   357  		branchUUID = ""
   358  		data = nil
   359  		encoding = ""
   360  	}
   361  
   362  	if err := iter.Close(); err != nil {
   363  		return nil, serviceerror.NewUnavailable(fmt.Sprintf("GetAllHistoryTreeBranches. Close operation failed. Error: %v", err))
   364  	}
   365  
   366  	response := &p.InternalGetAllHistoryTreeBranchesResponse{
   367  		Branches:      branches,
   368  		NextPageToken: pagingToken,
   369  	}
   370  
   371  	return response, nil
   372  }
   373  
   374  // GetHistoryTree returns all branch information of a tree
   375  func (h *HistoryStore) GetHistoryTree(
   376  	ctx context.Context,
   377  	request *p.GetHistoryTreeRequest,
   378  ) (*p.InternalGetHistoryTreeResponse, error) {
   379  
   380  	treeID, err := primitives.ValidateUUID(request.TreeID)
   381  	if err != nil {
   382  		return nil, serviceerror.NewInternal(fmt.Sprintf("ReadHistoryBranch. Gocql TreeId UUID cast failed. Error: %v", err))
   383  	}
   384  	query := h.Session.Query(v2templateReadAllBranches, treeID).WithContext(ctx)
   385  
   386  	pageSize := 100
   387  	var pagingToken []byte
   388  	treeInfos := make([]*commonpb.DataBlob, 0, pageSize)
   389  
   390  	var iter gocql.Iter
   391  	for {
   392  		iter = query.PageSize(pageSize).PageState(pagingToken).Iter()
   393  		pagingToken = iter.PageState()
   394  
   395  		branchUUID := ""
   396  		var data []byte
   397  		var encoding string
   398  		for iter.Scan(&branchUUID, &data, &encoding) {
   399  			treeInfos = append(treeInfos, p.NewDataBlob(data, encoding))
   400  
   401  			branchUUID = ""
   402  			data = []byte{}
   403  			encoding = ""
   404  		}
   405  
   406  		if err := iter.Close(); err != nil {
   407  			return nil, gocql.ConvertError("GetHistoryTree", err)
   408  		}
   409  
   410  		if len(pagingToken) == 0 {
   411  			break
   412  		}
   413  	}
   414  
   415  	return &p.InternalGetHistoryTreeResponse{
   416  		TreeInfos: treeInfos,
   417  	}, nil
   418  }
   419  
   420  func convertHistoryNode(
   421  	message map[string]interface{},
   422  ) p.InternalHistoryNode {
   423  	nodeID := message["node_id"].(int64)
   424  	prevTxnID := message["prev_txn_id"].(int64)
   425  	txnID := message["txn_id"].(int64)
   426  
   427  	var data []byte
   428  	var dataEncoding string
   429  	if _, ok := message["data"]; ok {
   430  		data = message["data"].([]byte)
   431  		dataEncoding = message["data_encoding"].(string)
   432  	}
   433  	return p.InternalHistoryNode{
   434  		NodeID:            nodeID,
   435  		PrevTransactionID: prevTxnID,
   436  		TransactionID:     txnID,
   437  		Events:            p.NewDataBlob(data, dataEncoding),
   438  	}
   439  }
   440  
   441  func convertTimeoutError(err error) error {
   442  	if timeoutErr, ok := err.(*p.TimeoutError); ok {
   443  		return &p.AppendHistoryTimeoutError{
   444  			Msg: timeoutErr.Msg,
   445  		}
   446  	}
   447  	return err
   448  }