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 }