go.temporal.io/server@v1.23.0/common/persistence/sql/shard.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 sql 26 27 import ( 28 "context" 29 "database/sql" 30 "fmt" 31 32 "go.temporal.io/api/serviceerror" 33 34 "go.temporal.io/server/common/log" 35 "go.temporal.io/server/common/persistence" 36 "go.temporal.io/server/common/persistence/sql/sqlplugin" 37 ) 38 39 type sqlShardStore struct { 40 SqlStore 41 currentClusterName string 42 } 43 44 // newShardPersistence creates an instance of ShardManager 45 func newShardPersistence( 46 db sqlplugin.DB, 47 currentClusterName string, 48 logger log.Logger, 49 ) (persistence.ShardStore, error) { 50 return &sqlShardStore{ 51 SqlStore: NewSqlStore(db, logger), 52 currentClusterName: currentClusterName, 53 }, nil 54 } 55 56 func (m *sqlShardStore) GetClusterName() string { 57 return m.currentClusterName 58 } 59 60 func (m *sqlShardStore) GetOrCreateShard( 61 ctx context.Context, 62 request *persistence.InternalGetOrCreateShardRequest, 63 ) (*persistence.InternalGetOrCreateShardResponse, error) { 64 row, err := m.Db.SelectFromShards(ctx, sqlplugin.ShardsFilter{ 65 ShardID: request.ShardID, 66 }) 67 switch err { 68 case nil: 69 return &persistence.InternalGetOrCreateShardResponse{ 70 ShardInfo: persistence.NewDataBlob(row.Data, row.DataEncoding), 71 }, nil 72 case sql.ErrNoRows: 73 default: 74 return nil, serviceerror.NewUnavailable(fmt.Sprintf("GetOrCreateShard: failed to get ShardID %v. Error: %v", request.ShardID, err)) 75 } 76 77 if request.CreateShardInfo == nil { 78 return nil, serviceerror.NewNotFound(fmt.Sprintf("GetOrCreateShard: ShardID %v not found. Error: %v", request.ShardID, err)) 79 } 80 81 rangeID, shardInfo, err := request.CreateShardInfo() 82 if err != nil { 83 return nil, serviceerror.NewUnavailable(fmt.Sprintf("GetOrCreateShard: failed to encode shard info for ShardID %v. Error: %v", request.ShardID, err)) 84 } 85 row = &sqlplugin.ShardsRow{ 86 ShardID: request.ShardID, 87 RangeID: rangeID, 88 Data: shardInfo.Data, 89 DataEncoding: shardInfo.EncodingType.String(), 90 } 91 _, err = m.Db.InsertIntoShards(ctx, row) 92 if err == nil { 93 return &persistence.InternalGetOrCreateShardResponse{ 94 ShardInfo: shardInfo, 95 }, nil 96 } else if m.Db.IsDupEntryError(err) { 97 // conflict, try again 98 request.CreateShardInfo = nil // prevent loop 99 return m.GetOrCreateShard(ctx, request) 100 } else { 101 return nil, serviceerror.NewUnavailable(fmt.Sprintf("GetOrCreateShard: failed to insert into shards table. Error: %v", err)) 102 } 103 } 104 105 func (m *sqlShardStore) UpdateShard( 106 ctx context.Context, 107 request *persistence.InternalUpdateShardRequest, 108 ) error { 109 return m.txExecute(ctx, "UpdateShard", func(tx sqlplugin.Tx) error { 110 if err := lockShard(ctx, 111 tx, 112 request.ShardID, 113 request.PreviousRangeID, 114 ); err != nil { 115 return err 116 } 117 result, err := tx.UpdateShards(ctx, &sqlplugin.ShardsRow{ 118 ShardID: request.ShardID, 119 RangeID: request.RangeID, 120 Data: request.ShardInfo.Data, 121 DataEncoding: request.ShardInfo.EncodingType.String(), 122 }) 123 if err != nil { 124 return err 125 } 126 rowsAffected, err := result.RowsAffected() 127 if err != nil { 128 return fmt.Errorf("rowsAffected returned error for shardID %v: %v", request.ShardID, err) 129 } 130 if rowsAffected != 1 { 131 return fmt.Errorf("rowsAffected returned %v shards instead of one", rowsAffected) 132 } 133 return nil 134 }) 135 } 136 137 func (m *sqlShardStore) AssertShardOwnership( 138 ctx context.Context, 139 request *persistence.AssertShardOwnershipRequest, 140 ) error { 141 return nil 142 } 143 144 // initiated by the owning shard 145 func lockShard( 146 ctx context.Context, 147 tx sqlplugin.Tx, 148 shardID int32, 149 oldRangeID int64, 150 ) error { 151 152 rangeID, err := tx.WriteLockShards(ctx, sqlplugin.ShardsFilter{ 153 ShardID: shardID, 154 }) 155 switch err { 156 case nil: 157 if rangeID != oldRangeID { 158 return &persistence.ShardOwnershipLostError{ 159 ShardID: shardID, 160 Msg: fmt.Sprintf("Failed to update shard. Previous range ID: %v; new range ID: %v", oldRangeID, rangeID), 161 } 162 } 163 return nil 164 case sql.ErrNoRows: 165 return serviceerror.NewUnavailable(fmt.Sprintf("Failed to lock shard with ID %v that does not exist.", shardID)) 166 default: 167 return serviceerror.NewUnavailable(fmt.Sprintf("Failed to lock shard with ID: %v. Error: %v", shardID, err)) 168 } 169 } 170 171 // initiated by the owning shard 172 func readLockShard( 173 ctx context.Context, 174 tx sqlplugin.Tx, 175 shardID int32, 176 oldRangeID int64, 177 ) error { 178 rangeID, err := tx.ReadLockShards(ctx, sqlplugin.ShardsFilter{ 179 ShardID: shardID, 180 }) 181 switch err { 182 case nil: 183 if rangeID != oldRangeID { 184 return &persistence.ShardOwnershipLostError{ 185 ShardID: shardID, 186 Msg: fmt.Sprintf("Failed to lock shard. Previous range ID: %v; new range ID: %v", oldRangeID, rangeID), 187 } 188 } 189 return nil 190 case sql.ErrNoRows: 191 return serviceerror.NewUnavailable(fmt.Sprintf("Failed to lock shard with ID %v that does not exist.", shardID)) 192 default: 193 return serviceerror.NewUnavailable(fmt.Sprintf("Failed to lock shard with ID: %v. Error: %v", shardID, err)) 194 } 195 }