// Marks the partition populated unless it is already switched. async Task TryMarkPartitionPopulatedAsync(string partitionKey, TableRequestOptions requestOptions, OperationContext operationContext) { var markPopulatedBatch = new TableBatchOperation(); markPopulatedBatch.Insert(new MTableEntity { PartitionKey = partitionKey, RowKey = ROW_KEY_PARTITION_META, partitionState = MTablePartitionState.POPULATED }); markPopulatedBatch.Insert(new DynamicTableEntity { PartitionKey = partitionKey, RowKey = ROW_KEY_PARTITION_POPULATED_ASSERTION }); try { await oldTable.ExecuteBatchAsync(markPopulatedBatch, requestOptions, operationContext); } // XXX: Optimization opportunity: if we swap the order of the // inserts, we can tell here if the partition is already switched. catch (StorageException ex) { if (ex.GetHttpStatusCode() != HttpStatusCode.Conflict) { throw ChainTableUtils.GenerateInternalException(ex); } } await monitor.AnnotateLastBackendCallAsync(); }
// NOTE: Mutates entity internal async Task TryCopyEntityToNewTableAsync(MTableEntity entity, TableRequestOptions requestOptions, OperationContext operationContext) { try { await newTable.ExecuteAsync(TableOperation.Insert(entity), requestOptions, operationContext); } catch (StorageException ex) { if (ex.GetHttpStatusCode() != HttpStatusCode.Conflict) { throw ChainTableUtils.GenerateInternalException(ex); } await monitor.AnnotateLastBackendCallAsync(); return; } await monitor.AnnotateLastBackendCallAsync( spuriousETagChanges : new List <SpuriousETagChange> { new SpuriousETagChange(entity.PartitionKey, entity.RowKey, entity.ETag) }); }
async Task CleanupAsync(TableRequestOptions requestOptions, OperationContext operationContext) { // Clean up MTable-specific data from new table. // Future: Query only ones with properties we need to clean up. IQueryStream <MTableEntity> newTableStream = await newTable.ExecuteQueryStreamedAsync( new TableQuery <MTableEntity>(), requestOptions, operationContext); MTableEntity newEntity; while ((newEntity = await newTableStream.ReadRowAsync()) != null) { // XXX: Consider factoring out this "query and retry" pattern // into a separate method. Attempt: TableOperation cleanupOp = newEntity.deleted ? TableOperation.Delete(newEntity) : TableOperation.Replace(newEntity.Export <DynamicTableEntity>()); TableResult cleanupResult; try { cleanupResult = await newTable.ExecuteAsync(cleanupOp, requestOptions, operationContext); } catch (StorageException ex) { if (ex.GetHttpStatusCode() == HttpStatusCode.NotFound) { // Someone else deleted it concurrently. Nothing to do. await monitor.AnnotateLastBackendCallAsync(); continue; } else if (ex.GetHttpStatusCode() == HttpStatusCode.PreconditionFailed) { await monitor.AnnotateLastBackendCallAsync(); // Unfortunately we can't assume that anyone who concurrently modifies // the row while the table is in state USE_NEW_HIDE_METADATA will // clean it up, because of InsertOrMerge. (Consider redesign?) // Re-retrieve row. TableResult retrieveResult = await newTable.ExecuteAsync( TableOperation.Retrieve <MTableEntity>(newEntity.PartitionKey, newEntity.RowKey), requestOptions, operationContext); await monitor.AnnotateLastBackendCallAsync(); if ((HttpStatusCode)retrieveResult.HttpStatusCode == HttpStatusCode.NotFound) { continue; } else { newEntity = (MTableEntity)retrieveResult.Result; goto Attempt; } } else { throw ChainTableUtils.GenerateInternalException(ex); } } await monitor.AnnotateLastBackendCallAsync( spuriousETagChanges : cleanupResult.Etag == null?null : new List <SpuriousETagChange> { new SpuriousETagChange(newEntity.PartitionKey, newEntity.RowKey, cleanupResult.Etag) }); } // Delete everything from old table! No one should be modifying it concurrently. IQueryStream <MTableEntity> oldTableStream = await oldTable.ExecuteQueryStreamedAsync( new TableQuery <MTableEntity>(), requestOptions, operationContext); MTableEntity oldEntity; while ((oldEntity = await oldTableStream.ReadRowAsync()) != null) { await oldTable.ExecuteAsync(TableOperation.Delete(oldEntity), requestOptions, operationContext); await monitor.AnnotateLastBackendCallAsync(); } }
internal async Task EnsurePartitionSwitchedAsync(string partitionKey, TableRequestOptions requestOptions, OperationContext operationContext) { var metaQuery = new TableQuery <MTableEntity> { FilterString = ChainTableUtils.GeneratePointRetrievalFilterCondition( new PrimaryKey(partitionKey, ROW_KEY_PARTITION_META)) }; Recheck: MTablePartitionState? state; if (IsBugEnabled(MTableOptionalBug.EnsurePartitionSwitchedFromPopulated)) { state = null; } else { state = (from r in (await oldTable.ExecuteQueryAtomicAsync(metaQuery, requestOptions, operationContext)) select r.partitionState).SingleOrDefault(); await monitor.AnnotateLastBackendCallAsync(); } switch (state) { case null: try { await oldTable.ExecuteAsync(TableOperation.Insert(new MTableEntity { PartitionKey = partitionKey, RowKey = ROW_KEY_PARTITION_META, partitionState = MTablePartitionState.SWITCHED }), requestOptions, operationContext); } catch (StorageException ex) { if (ex.GetHttpStatusCode() != HttpStatusCode.Conflict) { throw ChainTableUtils.GenerateInternalException(ex); } if (!IsBugEnabled(MTableOptionalBug.EnsurePartitionSwitchedFromPopulated)) { await monitor.AnnotateLastBackendCallAsync(); // We could now be in POPULATED or SWITCHED. // XXX: In production, what's more likely? Is it faster // to recheck first or just try the case below? goto Recheck; } } await monitor.AnnotateLastBackendCallAsync(); return; case MTablePartitionState.POPULATED: try { var batch = new TableBatchOperation(); batch.Replace(new MTableEntity { PartitionKey = partitionKey, RowKey = ROW_KEY_PARTITION_META, ETag = ChainTable2Constants.ETAG_ANY, partitionState = MTablePartitionState.SWITCHED }); batch.Delete(new MTableEntity { PartitionKey = partitionKey, RowKey = ROW_KEY_PARTITION_POPULATED_ASSERTION, ETag = ChainTable2Constants.ETAG_ANY, }); await oldTable.ExecuteBatchAsync(batch, requestOptions, operationContext); } catch (ChainTableBatchException ex) { // The only way this can fail (within the semantics) is // if someone else moved the partition to SWITCHED. if (!(ex.FailedOpIndex == 1 && ex.GetHttpStatusCode() == HttpStatusCode.NotFound)) { throw ChainTableUtils.GenerateInternalException(ex); } } await monitor.AnnotateLastBackendCallAsync(); return; case MTablePartitionState.SWITCHED: // Nothing to do return; } }
async Task CleanupAsync(TableRequestOptions requestOptions, OperationContext operationContext) { string previousPartitionKey = null; await WalkTableInParallel(newTable, requestOptions, operationContext, async (newEntity) => { if (newEntity.PartitionKey != previousPartitionKey) { if (previousPartitionKey != null) { Console.WriteLine("Cleaned Partition: {0}", previousPartitionKey); } previousPartitionKey = newEntity.PartitionKey; } // XXX: Consider factoring out this "query and retry" pattern // into a separate method. Attempt: TableOperation cleanupOp = newEntity.deleted ? TableOperation.Delete(newEntity) : TableOperation.Replace(newEntity.Export <DynamicTableEntity>()); TableResult cleanupResult; StorageException ex; try { cleanupResult = await newTable.ExecuteAsync(cleanupOp, requestOptions, operationContext); ex = null; } catch (StorageException exToHandle) { cleanupResult = null; ex = exToHandle; } if (ex != null) { if (ex.GetHttpStatusCode() == HttpStatusCode.NotFound) { // Someone else deleted it concurrently. Nothing to do. //await monitor.AnnotateLastBackendCallAsync(); return((IQueryStream <MTableEntity>)null); } else if (ex.GetHttpStatusCode() == HttpStatusCode.PreconditionFailed) { //await monitor.AnnotateLastBackendCallAsync(); // Unfortunately we can't assume that anyone who concurrently modifies // the row while the table is in state USE_NEW_HIDE_METADATA will // clean it up, because of InsertOrMerge. (Consider redesign?) // Re-retrieve row. TableResult retrieveResult = await newTable.ExecuteAsync( TableOperation.Retrieve <MTableEntity>(newEntity.PartitionKey, newEntity.RowKey), requestOptions, operationContext); //await monitor.AnnotateLastBackendCallAsync(); if ((HttpStatusCode)retrieveResult.HttpStatusCode == HttpStatusCode.NotFound) { return(null); } else { newEntity = (MTableEntity)retrieveResult.Result; goto Attempt; } } else { throw ChainTableUtils.GenerateInternalException(ex); } } else { await monitor.AnnotateLastBackendCallAsync( spuriousETagChanges: cleanupResult.Etag == null ? null : new List <SpuriousETagChange> { new SpuriousETagChange(newEntity.PartitionKey, newEntity.RowKey, cleanupResult.Etag) }); } return((IQueryStream <MTableEntity>)null); }); await WalkTableInParallel(oldTable, requestOptions, operationContext, async (oldEntity) => { await oldTable.ExecuteAsync(TableOperation.Delete(oldEntity), requestOptions, operationContext); await monitor.AnnotateLastBackendCallAsync(); return((IQueryStream <MTableEntity>)null); }); }