go.temporal.io/server@v1.23.0/common/persistence/cassandra/shard_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 "strings" 31 32 "go.temporal.io/server/common/log" 33 p "go.temporal.io/server/common/persistence" 34 "go.temporal.io/server/common/persistence/nosql/nosqlplugin/cassandra/gocql" 35 ) 36 37 const ( 38 templateCreateShardQuery = `INSERT INTO executions (` + 39 `shard_id, type, namespace_id, workflow_id, run_id, visibility_ts, task_id, shard, shard_encoding, range_id)` + 40 `VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?) IF NOT EXISTS` 41 42 templateGetShardQuery = `SELECT shard, shard_encoding ` + 43 `FROM executions ` + 44 `WHERE shard_id = ? ` + 45 `and type = ? ` + 46 `and namespace_id = ? ` + 47 `and workflow_id = ? ` + 48 `and run_id = ? ` + 49 `and visibility_ts = ? ` + 50 `and task_id = ?` 51 52 templateUpdateShardQuery = `UPDATE executions ` + 53 `SET shard = ?, shard_encoding = ?, range_id = ? ` + 54 `WHERE shard_id = ? ` + 55 `and type = ? ` + 56 `and namespace_id = ? ` + 57 `and workflow_id = ? ` + 58 `and run_id = ? ` + 59 `and visibility_ts = ? ` + 60 `and task_id = ? ` + 61 `IF range_id = ?` 62 ) 63 64 type ( 65 ShardStore struct { 66 ClusterName string 67 Session gocql.Session 68 Logger log.Logger 69 } 70 ) 71 72 func NewShardStore( 73 clusterName string, 74 session gocql.Session, 75 logger log.Logger, 76 ) *ShardStore { 77 return &ShardStore{ 78 ClusterName: clusterName, 79 Session: session, 80 Logger: logger, 81 } 82 } 83 84 func (d *ShardStore) GetOrCreateShard( 85 ctx context.Context, 86 request *p.InternalGetOrCreateShardRequest, 87 ) (*p.InternalGetOrCreateShardResponse, error) { 88 query := d.Session.Query(templateGetShardQuery, 89 request.ShardID, 90 rowTypeShard, 91 rowTypeShardNamespaceID, 92 rowTypeShardWorkflowID, 93 rowTypeShardRunID, 94 defaultVisibilityTimestamp, 95 rowTypeShardTaskID, 96 ).WithContext(ctx) 97 98 var data []byte 99 var encoding string 100 err := query.Scan(&data, &encoding) 101 if err == nil { 102 return &p.InternalGetOrCreateShardResponse{ 103 ShardInfo: p.NewDataBlob(data, encoding), 104 }, nil 105 } else if !gocql.IsNotFoundError(err) || request.CreateShardInfo == nil { 106 return nil, gocql.ConvertError("GetOrCreateShard", err) 107 } 108 109 // shard was not found and we should create it 110 rangeID, shardInfo, err := request.CreateShardInfo() 111 if err != nil { 112 return nil, err 113 } 114 115 query = d.Session.Query(templateCreateShardQuery, 116 request.ShardID, 117 rowTypeShard, 118 rowTypeShardNamespaceID, 119 rowTypeShardWorkflowID, 120 rowTypeShardRunID, 121 defaultVisibilityTimestamp, 122 rowTypeShardTaskID, 123 shardInfo.Data, 124 shardInfo.EncodingType.String(), 125 rangeID, 126 ).WithContext(ctx) 127 128 previous := make(map[string]interface{}) 129 applied, err := query.MapScanCAS(previous) 130 if err != nil { 131 return nil, gocql.ConvertError("GetOrCreateShard", err) 132 } 133 if !applied { 134 // conflict, try again 135 request.CreateShardInfo = nil // prevent loop 136 return d.GetOrCreateShard(ctx, request) 137 } 138 return &p.InternalGetOrCreateShardResponse{ 139 ShardInfo: shardInfo, 140 }, nil 141 } 142 143 func (d *ShardStore) UpdateShard( 144 ctx context.Context, 145 request *p.InternalUpdateShardRequest, 146 ) error { 147 query := d.Session.Query(templateUpdateShardQuery, 148 request.ShardInfo.Data, 149 request.ShardInfo.EncodingType.String(), 150 request.RangeID, 151 request.ShardID, 152 rowTypeShard, 153 rowTypeShardNamespaceID, 154 rowTypeShardWorkflowID, 155 rowTypeShardRunID, 156 defaultVisibilityTimestamp, 157 rowTypeShardTaskID, 158 request.PreviousRangeID, 159 ).WithContext(ctx) 160 161 previous := make(map[string]interface{}) 162 applied, err := query.MapScanCAS(previous) 163 if err != nil { 164 return gocql.ConvertError("UpdateShard", err) 165 } 166 167 if !applied { 168 var columns []string 169 for k, v := range previous { 170 columns = append(columns, fmt.Sprintf("%s=%v", k, v)) 171 } 172 173 return &p.ShardOwnershipLostError{ 174 ShardID: request.ShardID, 175 Msg: fmt.Sprintf("Failed to update shard. previous_range_id: %v, columns: (%v)", 176 request.PreviousRangeID, strings.Join(columns, ",")), 177 } 178 } 179 180 return nil 181 } 182 183 func (d *ShardStore) AssertShardOwnership( 184 ctx context.Context, 185 request *p.AssertShardOwnershipRequest, 186 ) error { 187 return nil 188 } 189 190 func (d *ShardStore) GetName() string { 191 return cassandraPersistenceName 192 } 193 194 func (d *ShardStore) GetClusterName() string { 195 return d.ClusterName 196 } 197 198 func (d *ShardStore) Close() { 199 if d.Session != nil { 200 d.Session.Close() 201 } 202 }