/// <summary> /// Arguments used to create a point mapping. /// </summary> /// <param name="point">Point value being mapped.</param> /// <param name="shard">Shard used as the mapping target.</param> /// <param name="status">Status of the mapping.</param> public PointMappingCreationInfo(TKey point, Shard shard, MappingStatus status) { ExceptionUtils.DisallowNullArgument(shard, "shard"); this.Value = point; this.Shard = shard; this.Status = status; this.Key = new ShardKey(ShardKey.ShardKeyTypeFromType(typeof(TKey)), point); }
/// <summary> /// Looks up the key value and returns the corresponding mapping. /// </summary> /// <typeparam name="TMapping">Mapping type.</typeparam> /// <typeparam name="TKey">Key type.</typeparam> /// <param name="key">Input key value.</param> /// <param name="useCache">Whether to use cache for lookups.</param> /// <param name="constructMapping">Delegate to construct a mapping object.</param> /// <param name="errorCategory">Category under which errors must be thrown.</param> /// <returns>Mapping that contains the key value.</returns> protected TMapping Lookup <TMapping, TKey>( TKey key, bool useCache, Func <ShardMapManager, ShardMap, IStoreMapping, TMapping> constructMapping, ShardManagementErrorCategory errorCategory) where TMapping : class, IShardProvider { ShardKey sk = new ShardKey(ShardKey.ShardKeyTypeFromType(typeof(TKey)), key); if (useCache) { ICacheStoreMapping cachedMapping = this.Manager.Cache.LookupMappingByKey(this.ShardMap.StoreShardMap, sk); if (cachedMapping != null) { return(constructMapping(this.Manager, this.ShardMap, cachedMapping.Mapping)); } } // Cache-miss, find mapping for given key in GSM. TMapping m = null; IStoreResults gsmResult; Stopwatch stopwatch = Stopwatch.StartNew(); using (IStoreOperationGlobal op = this.Manager.StoreOperationFactory.CreateFindMappingByKeyGlobalOperation( this.Manager, "Lookup", this.ShardMap.StoreShardMap, sk, CacheStoreMappingUpdatePolicy.OverwriteExisting, errorCategory, true, false)) { gsmResult = op.Do(); } stopwatch.Stop(); Tracer.TraceVerbose( TraceSourceConstants.ComponentNames.BaseShardMapper, "Lookup", "Lookup key from GSM complete; Key type : {0}; Result: {1}; Duration: {2}", typeof(TKey), gsmResult.Result, stopwatch.Elapsed); // If we could not locate the mapping, we return null and do nothing here. if (gsmResult.Result != StoreResult.MappingNotFoundForKey) { return(gsmResult.StoreMappings.Select(sm => constructMapping(this.Manager, this.ShardMap, sm)).Single()); } return(m); }
/// <summary> /// Constructs range based on its low boundary value. The low boundary value is /// set to the one specified in <paramref name="low"/> while the /// high boundary value is set to maximum possible value i.e. +infinity. /// </summary> /// <param name="low">Low boundary value (inclusive).</param> public Range(TKey low) { ShardKeyType k = ShardKey.ShardKeyTypeFromType(typeof(TKey)); _r = new ShardRange( new ShardKey(k, low), new ShardKey(k, null)); this.Low = low; this.HighIsMax = true; }
/// <summary> /// Constructs range based on its low and high boundary values. /// </summary> /// <param name="low">Low boundary value (inclusive).</param> /// <param name="high">High boundary value (exclusive).</param> public Range(TKey low, TKey high) { ShardKeyType k = ShardKey.ShardKeyTypeFromType(typeof(TKey)); _r = new ShardRange( new ShardKey(k, low), new ShardKey(k, high)); this.Low = low; this.High = high; }
public RangeMappingCreationInfo(Range <TKey> value, Shard shard, MappingStatus status) { ExceptionUtils.DisallowNullArgument(value, "value"); ExceptionUtils.DisallowNullArgument(shard, "shard"); this.Value = value; this.Shard = shard; this.Status = status; this.Range = new ShardRange( new ShardKey(ShardKey.ShardKeyTypeFromType(typeof(TKey)), value.Low), new ShardKey(ShardKey.ShardKeyTypeFromType(typeof(TKey)), value.HighIsMax ? null : (object)value.High)); }
/// <summary> /// Gets all the mappings that exist within given range. /// </summary> /// <param name="range">Optional range value, if null, we cover everything.</param> /// <param name="shard">Optional shard parameter, if null, we cover all shards.</param> /// <param name="constructMapping">Delegate to construct a mapping object.</param> /// <param name="errorCategory">Category under which errors will be posted.</param> /// <param name="mappingType">Name of mapping type.</param> /// <returns>Read-only collection of mappings that overlap with given range.</returns> protected IReadOnlyList <TMapping> GetMappingsForRange <TMapping, TKey>( Range <TKey> range, Shard shard, Func <ShardMapManager, ShardMap, IStoreMapping, TMapping> constructMapping, ShardManagementErrorCategory errorCategory, string mappingType) where TMapping : class { ShardRange sr = null; if (shard != null) { ExceptionUtils.EnsureShardBelongsToShardMap( this.Manager, this.ShardMap, shard, "GetMappings", mappingType); } if (range != null) { sr = new ShardRange( new ShardKey(ShardKey.ShardKeyTypeFromType(typeof(TKey)), range.Low), new ShardKey(ShardKey.ShardKeyTypeFromType(typeof(TKey)), range.HighIsMax ? null : (object)range.High)); } IStoreResults result; using (IStoreOperationGlobal op = this.Manager.StoreOperationFactory.CreateGetMappingsByRangeGlobalOperation( this.Manager, "GetMappingsForRange", this.ShardMap.StoreShardMap, shard != null ? shard.StoreShard : null, sr, errorCategory, true, // Always cache. false)) { result = op.Do(); } return(result.StoreMappings .Select(sm => constructMapping(this.Manager, this.ShardMap, sm)) .ToList() .AsReadOnly()); }
/// <summary> /// Internal constructor used for deserialization from store representation of /// the mapping object. /// </summary> /// <param name="manager">Owning ShardMapManager.</param> /// <param name="shardMap">Owning shard map.</param> /// <param name="mapping">Storage representation of the mapping.</param> internal PointMapping( ShardMapManager manager, ShardMap shardMap, IStoreMapping mapping) { Debug.Assert(manager != null); this.Manager = manager; Debug.Assert(mapping != null); Debug.Assert(mapping.ShardMapId != default(Guid)); Debug.Assert(mapping.StoreShard.ShardMapId != default(Guid)); this.StoreMapping = mapping; _shard = new Shard(this.Manager, shardMap, mapping.StoreShard); this.Key = ShardKey.FromRawValue(ShardKey.ShardKeyTypeFromType(typeof(TKey)), mapping.MinValue); this.Value = (TKey)this.Key.Value; }
/// <summary> /// Creates a list based <see cref="ListShardMap{TKey}"/>. /// </summary> /// <typeparam name="TKey">Type of keys.</typeparam> /// <param name="shardMapName">Name of shard map.</param> /// <returns>List shard map with the specified name.</returns> public ListShardMap <TKey> CreateListShardMap <TKey>(string shardMapName) { ShardMapManager.ValidateShardMapName(shardMapName); using (ActivityIdScope activityIdScope = new ActivityIdScope(Guid.NewGuid())) { DefaultStoreShardMap dssm = new DefaultStoreShardMap( Guid.NewGuid(), shardMapName, ShardMapType.List, ShardKey.ShardKeyTypeFromType(typeof(TKey))); ListShardMap <TKey> listShardMap = new ListShardMap <TKey>(this, dssm); Tracer.TraceInfo( TraceSourceConstants.ComponentNames.ShardMapManager, "CreateListShardMap", "Start; ShardMap: {0}", shardMapName); Stopwatch stopwatch = Stopwatch.StartNew(); this.AddShardMapToStore("CreateListShardMap", dssm); stopwatch.Stop(); Tracer.TraceInfo( TraceSourceConstants.ComponentNames.ShardMapManager, "CreateListShardMap", "Added ShardMap to Store; ShardMap: {0} Duration: {1}", shardMapName, stopwatch.Elapsed); Tracer.TraceInfo( TraceSourceConstants.ComponentNames.ShardMapManager, "CreateListShardMap", "Complete; ShardMap: {0} Duration: {1}", shardMapName, stopwatch.Elapsed); return(listShardMap); } }
/// <summary> /// Internal constructor used for deserialization from store representation of /// the mapping object. /// </summary> /// <param name="manager">Owning ShardMapManager.</param> /// <param name="shardMap">Owning shard map.</param> /// <param name="mapping">Storage representation of the mapping.</param> internal RangeMapping( ShardMapManager manager, ShardMap shardMap, IStoreMapping mapping) { Debug.Assert(manager != null); this.Manager = manager; Debug.Assert(mapping != null); Debug.Assert(mapping.ShardMapId != default(Guid)); Debug.Assert(mapping.StoreShard.ShardMapId != default(Guid)); this.StoreMapping = mapping; _shard = new Shard(this.Manager, shardMap, mapping.StoreShard); this.Range = new ShardRange( ShardKey.FromRawValue(ShardKey.ShardKeyTypeFromType(typeof(TKey)), mapping.MinValue), ShardKey.FromRawValue(ShardKey.ShardKeyTypeFromType(typeof(TKey)), mapping.MaxValue)); this.Value = this.Range.High.IsMax ? new Range <TKey>(this.Range.Low.GetValue <TKey>()) : new Range <TKey>(this.Range.Low.GetValue <TKey>(), this.Range.High.GetValue <TKey>()); }
/// <summary> /// Splits the given mapping into 2 at the given key. The new mappings point to the same shard /// as the existing mapping. /// </summary> /// <param name="existingMapping">Given existing mapping.</param> /// <param name="splitAt">Split point.</param> /// <param name="lockOwnerId">Lock owner id of this mapping</param> /// <returns>Read-only collection of 2 new mappings thus created.</returns> internal IReadOnlyList <RangeMapping <TKey> > Split(RangeMapping <TKey> existingMapping, TKey splitAt, Guid lockOwnerId) { this.EnsureMappingBelongsToShardMap <RangeMapping <TKey> >( existingMapping, "Split", "existingMapping"); ShardKey shardKey = new ShardKey(ShardKey.ShardKeyTypeFromType(typeof(TKey)), splitAt); if (!existingMapping.Range.Contains(shardKey) || existingMapping.Range.Low == shardKey || existingMapping.Range.High == shardKey) { throw new ArgumentOutOfRangeException( "splitAt", Errors._ShardMapping_SplitPointOutOfRange); } IStoreShard newShard = new DefaultStoreShard( existingMapping.Shard.StoreShard.Id, Guid.NewGuid(), existingMapping.ShardMapId, existingMapping.Shard.StoreShard.Location, existingMapping.Shard.StoreShard.Status); IStoreMapping mappingToRemove = new DefaultStoreMapping( existingMapping.StoreMapping.Id, existingMapping.StoreMapping.ShardMapId, newShard, existingMapping.StoreMapping.MinValue, existingMapping.StoreMapping.MaxValue, existingMapping.StoreMapping.Status, existingMapping.StoreMapping.LockOwnerId); IStoreMapping[] mappingsToAdd = new IStoreMapping[2] { new DefaultStoreMapping( Guid.NewGuid(), newShard.ShardMapId, newShard, existingMapping.Range.Low.RawValue, shardKey.RawValue, (int)existingMapping.Status, lockOwnerId), new DefaultStoreMapping( Guid.NewGuid(), newShard.ShardMapId, newShard, shardKey.RawValue, existingMapping.Range.High.RawValue, (int)existingMapping.Status, lockOwnerId) }; using (IStoreOperation op = this.Manager.StoreOperationFactory.CreateReplaceMappingsOperation( this.Manager, StoreOperationCode.SplitMapping, this.ShardMap.StoreShardMap, new[] { new Tuple <IStoreMapping, Guid>(mappingToRemove, lockOwnerId) }, mappingsToAdd.Select(mappingToAdd => new Tuple <IStoreMapping, Guid>(mappingToAdd, lockOwnerId)).ToArray())) { op.Do(); } return(mappingsToAdd .Select(m => new RangeMapping <TKey>(this.Manager, this.ShardMap, m)) .ToList() .AsReadOnly()); }
/// <summary> /// Given a key value, asynchronously obtains a SqlConnection to the shard in the mapping /// that contains the key value. /// </summary> /// <typeparam name="TMapping">Mapping type.</typeparam> /// <typeparam name="TKey">Key type.</typeparam> /// <param name="key">Input key value.</param> /// <param name="constructMapping">Delegate to construct a mapping object.</param> /// <param name="errorCategory">Error category.</param> /// <param name="connectionString"> /// Connection string with credential information, the DataSource and Database are /// obtained from the results of the lookup operation for key. /// </param> /// <param name="options">Options for validation operations to perform on opened connection.</param> /// <returns>A task encapsulating an opened SqlConnection as the result.</returns> protected async Task <SqlConnection> OpenConnectionForKeyAsync <TMapping, TKey>( TKey key, Func <ShardMapManager, ShardMap, IStoreMapping, TMapping> constructMapping, ShardManagementErrorCategory errorCategory, string connectionString, ConnectionOptions options = ConnectionOptions.Validate) where TMapping : class, IShardProvider { ShardKey sk = new ShardKey(ShardKey.ShardKeyTypeFromType(typeof(TKey)), key); // Try to find the mapping within the cache. ICacheStoreMapping csm = this.Manager.Cache.LookupMappingByKey(this.ShardMap.StoreShardMap, sk); IStoreMapping sm; if (csm != null) { sm = csm.Mapping; } else { sm = await this.LookupMappingForOpenConnectionForKeyAsync( sk, CacheStoreMappingUpdatePolicy.OverwriteExisting, errorCategory).ConfigureAwait(false); } SqlConnection result; bool lookupMappingOnEx = false; CacheStoreMappingUpdatePolicy cacheUpdatePolicyOnEx = CacheStoreMappingUpdatePolicy.OverwriteExisting; try { // Initially attempt to connect based on lookup results from either cache or GSM. result = await this.ShardMap.OpenConnectionAsync( constructMapping(this.Manager, this.ShardMap, sm), connectionString, options).ConfigureAwait(false); csm.ResetTimeToLiveIfNecessary(); return(result); } catch (ShardManagementException smme) { // If we hit a validation failure due to stale version of mapping, we will perform one more attempt. if (((options & ConnectionOptions.Validate) == ConnectionOptions.Validate) && smme.ErrorCategory == ShardManagementErrorCategory.Validation && smme.ErrorCode == ShardManagementErrorCode.MappingDoesNotExist) { // Assumption here is that this time the attempt should succeed since the cache entry // has already been either evicted, or updated based on latest data from the server. lookupMappingOnEx = true; cacheUpdatePolicyOnEx = CacheStoreMappingUpdatePolicy.OverwriteExisting; } else { // The error was not due to validation but something else e.g. // 1) Shard map does not exist // 2) Mapping could not be found. throw; } } catch (SqlException) { // We failed to connect. If we were trying to connect from an entry in cache and mapping expired in cache. if (csm != null && csm.HasTimeToLiveExpired()) { using (IdLock _idLock = new IdLock(csm.Mapping.StoreShard.Id)) { // Similar to DCL pattern, we need to refresh the mapping again to see if we still need to go to the store // to lookup the mapping after acquiring the shard lock. It might be the case that a fresh version has already // been obtained by some other thread. csm = this.Manager.Cache.LookupMappingByKey(this.ShardMap.StoreShardMap, sk); // Only go to store if the mapping is stale even after refresh. if (csm == null || csm.HasTimeToLiveExpired()) { // Refresh the mapping in cache. And try to open the connection after refresh. lookupMappingOnEx = true; cacheUpdatePolicyOnEx = CacheStoreMappingUpdatePolicy.UpdateTimeToLive; } else { sm = csm.Mapping; } } } else { // Either: // 1) The mapping is still within the TTL. No refresh. // 2) Mapping was not in cache, we originally did a lookup for mapping in GSM and even then could not connect. throw; } } if (lookupMappingOnEx) { sm = await this.LookupMappingForOpenConnectionForKeyAsync( sk, cacheUpdatePolicyOnEx, errorCategory).ConfigureAwait(false); } // One last attempt to open the connection after a cache refresh result = await this.ShardMap.OpenConnectionAsync( constructMapping(this.Manager, this.ShardMap, sm), connectionString, options).ConfigureAwait(false); // Reset TTL on successful connection. csm.ResetTimeToLiveIfNecessary(); return(result); }