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  }