Azure / Azure/azure-rest-api-specs
[Design bug] It is impossible to determine when a Table Storage table is fully deleted
- Dominant language
- TypeSpec
- Stars
- 3.1k
- Forks
- 5.9k
- Avg merge
- 2d 22h
- Merged PRs (30d)
- 444
Description
(My apologies if this is the wrong place for bugs _relating to the design_ of (or lack-of) functionality in the Azure Storage REST service - as this is the repo for the API specs I assume it's also the place to report issues _with the specs's design_).
## Problem statement:
Deleting an Azure Table Storage table happens within milliseconds, however the table's name will still be reserved for an indeterminate amount of time ([over 40 seconds or more according to the docs](https://docs.microsoft.com/en-us/rest/api/storageservices/delete-table)) and it is currently (as of Q1 2021) **impossible** to determine if a table's name is still reserved or not without attempting to _create_ the table.
The REST API specs currently say this (emphasis mine):
> When a table is successfully deleted, it is immediately marked for deletion and is no longer accessible to clients. The table is later removed from the Table service during garbage collection.
> Note that deleting a table **is likely to take at least 40 seconds to complete**. If an operation is attempted against the table while it was being deleted, the service returns status code 409 (Conflict), with additional error information indicating that the table is being deleted.
The first problem is that the docs say "If an operation is attempted...". This is untrue. As far as I can tell, **only** an attempt to recreate the table will result in a HTTP 409 Conflict with the `TableBeingDeleted` error-code. Other operations, such as executing a query or attempting to delete the table twice will result in a HTTP 404 without the `TableBeingDeleted` error code.
The second problem is that this API design means that without using _only_ `CreateTableAsync` one cannot safely _observe_ the deleted-state of a table. This is a problem for test-code and other projects that require an Azure Storage account to be in a known-state - as they have to resort to `try` loops.
## Repro steps:
This is using the current `Microsoft.Azure.Cosmos.Table` NuGet package (1.0.8):
```
const String CONNECTION_STRING =
@"DefaultEndpointsProtocol=https;" +
@"AccountName=YOUR_ACCOUNT_NAME;" +
@"AccountKey=YOUR_ACCOUNT_KEY;";
String unpredictableTableName;
{
Byte[] bytes = new Byte[18];
using( RandomNumberGenerator rng = RandomNumberGenerator.Create() )
{
rng.GetNonZeroBytes( bytes );
}
unpredictableTableName = Convert.ToBase64String(bytes).Replace("/", "").Replace("+", "");
if (Char.IsDigit(unpredictableTableName[0])) unpredictableTableName = "A" + unpredictableTableName;
}
CloudStorageAccount atsAccount = CloudStorageAccount.Parse( connectionString: CONNECTION_STRING );
CloudTableClient atsClient = new CloudTableClient( atsAccount.TableStorageUri, atsAccount.Credentials );
CloudTable table = atsClient.GetTableReference( unpredictableTableName );
await table.CreateAsync(); // OK
await Task.Delay(100);
await table.DeleteAsync(); // OK
await Task.Delay(100);
try
{
await Task.Delay(100);
await table.DeleteAsync(); // Fails with ex.RequestInformation.ExtendedErrorInformation.ErrorCode == "ResourceNotFound". But the documentation says this should have "TableBeingDeleted".
}
catch( Exception e1 )
{
Console.WriteLine( "2nd DeleteAsync failed: {0}", e1.RequestInformation?.ExtendedErrorInformation?.ErrorCode );
}
try
{
await Task.Delay(100);
await table.CreateAsync(); // Fails: ex.RequestInformation.ExtendedErrorInformation.ErrorCode == "TableBeingDeleted"
}
catch( Exception e2 )
{
Console.WriteLine( "2nd CreateAsync failed: {0}", e2.RequestInformation?.ExtendedErrorInformation?.ErrorCode );
}
```
## Proposed solution:
There should be a `GET` endpoint to determine the busy/free/reserved status of a an Azure Table Storage table name (ditto CosmosDB if this is an issue there).
In the .NET SDK, I suppose it could look like this:
```
bool busy = await table.IsBeingDeletedAsync();
// or:
enum TableStatus
{
Extant,
NotFound,
Deleting
}
TableStatus status = await table.GetTableStatusAsync();
```
That way my program above just needs this:
```
await table.CreateAsync(); // OK
await Task.Delay(100);
await table.DeleteAsync(); // OK
TableStatus status = await table.GetTableStatusAsync();
while( status == TableStatus.Deleting )
{
await Task.Delay(100);
status = await table.GetTableStatusAsync();
}
await table.CreateAsync(); // OK
```
## Citations:
* https://stackoverflow.com/questions/15508517/the-correct-way-to-delete-and-recreate-a-windows-azure-storage-table-error-409
* https://social.msdn.microsoft.com/Forums/azure/en-US/68da3452-7925-4fda-ab5a-2e69facfa3ef/tables-taking-many-hours-to-delete?forum=windowsazuredata&forum=windowsazuredata
* https://social.msdn.microsoft.com/Forums/en-US/f79c24cc-81e5-4b9b-8e9c-2a066115936f/error-409-microsoftwindowsazurestoragestorageexception?forum=windowsazuredevelopment
Contributor guide
Research direction
Start with the Azure Table Storage REST API specification and the Delete Table documentation linked in the issue; compare the documented responses for repeated delete, read, and recreate operations. Done would require an agreed API design that lets clients distinguish an existing, absent, and deleting table name, along with matching specification and documentation updates.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- azure
- Domain
- api, cloud, databases
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100