Error deleting from Cloud Cloud table - ResourceNotFound

I have a problem interrupting the removal of objects from the azure table. This affects only 1% of my attempts, and if the same call is made later, then it will work fine, but I would like to know the reason for this! I turned off my fingers and I found a lack of documentation on how to create very reliable code to remove, insert and update to be completely unexpected ... it all seems a bit hit and skipping, β€œtry, most of the time it will work”

EDIT: I delete the text that was originally on this question, and replaced it with a completely new text to take into account what I tried / which was suggested.

Azure tables suffer from intermittent crashes such as Azure SQL. If so, would I like to saveChangesWithRetries to handle this? It is not right?

So ... pretty simple code being called about 250 times per minute on Azure web roles. Azure tables are used as part of a messaging system. Messages are inserted by one user being downloaded by another; upon successful loading, these messages are marked as read.

Each user has a section for unread messages and a section for reading messages. Therefore, to mark a message as read, it is removed from the unread section and moved to the reading section.

Out of 250 times when this code is called per minute, I get 2 to 10 of the following errors in the final SaveChangesWithRetries (). Internal exception:

ResourceNotFound specified resource does not exist. RequestID: 652a3e13-3911-4503-8e49-6fec32a3c044 Time: 2011-09-28T22: 09: 39.0795651Z

I do not think that a separate section will be available more than a few times per minute.

This is my code:

  public static void Message_MarkAsRead(int uid) { try { storageAccount = CloudStorageAccount.Parse(connectionString); tableClient = new CloudTableClient(storageAccount.TableEndpoint.AbsoluteUri, storageAccount.Credentials); tableClient.RetryPolicy = RetryPolicies.Retry(retryAmount, TimeSpan.FromSeconds(retrySeconds)); TableServiceContext tableServiceContext = tableClient.GetDataServiceContext(); tableServiceContext.IgnoreResourceNotFoundException = true; //the messageUserJoinerTable let us join messageId to userFromId and userToId //each message is inserted into the tables twice, once into the userFromId partition and also into the userToId partition #region get the userToId and userFromId for this message uid List<int> userIds = new List<int>(); var resultsUserIds = from messagesUserJoinerTable in tableServiceContext.CreateQuery<MessageUserJoinerDataEntity>(messageUserJoinerTableName) where messagesUserJoinerTable.PartitionKey == uid.ToString() select messagesUserJoinerTable; foreach (MessageUserJoinerDataEntity messageUserJoiner in resultsUserIds) { userIds.Add(messageUserJoiner.UserId); } #endregion #region then we need to check the partition for each of these users and mark the messages as read if (userIds.Count > 0) { foreach (int userId in userIds) { var resultsUnreadMessages = from messagesTable in tableServiceContext.CreateQuery<MessageDataEntity>(messageTableName) where messagesTable.PartitionKey == CreatePartitionKey(userId, false) && messagesTable.RowKey == CreateRowKey(uid) select messagesTable; //there should only ever be one as duplicate partition/rowkey is not allowed MessageDataEntity messageUnread = resultsUnreadMessages.FirstOrDefault(); if (messageUnread != null) { bool isUnreadMessageDeleted = false; //shallow copy the message for re-inserting as read MessageDataEntity messageRead = new MessageDataEntity(messageUnread); //delete the message try { tableServiceContext.Detach(messageUnread); tableServiceContext.AttachTo(messageTableName, messageUnread, "*"); tableServiceContext.DeleteObject(messageUnread); //this is where the error occurs tableServiceContext.SaveChangesWithRetries(); isUnreadMessageDeleted = true; } catch (Exception ex) { MyTrace.Trace("AzureCloudTable_" + Service.versionForTracing + ".Message_MarkAsRead. Error.Stage.1: MessageID:" + uid + ", UserID:" + userId + ". " + ex.Message + ex.StackTrace + ", " + ex.InnerException.Message + ex.InnerException.StackTrace, "Error. MarkAsRead"); //check to see if the message we tried to delete has already been deleted //if so, we just consume this error and continue by inserting the read message //else, we throw the exception outwards var resultsUnreadMessagesLastCheck = from messagesTable in tableServiceContext.CreateQuery<MessageDataEntity>(messageTableName) where messagesTable.PartitionKey == CreatePartitionKey(userId, false) && messagesTable.RowKey == CreateRowKey(uid) select messagesTable; //there should only ever be one as duplicate partition/rowkey is not allowed MessageDataEntity messageUnreadLastCheck = resultsUnreadMessages.FirstOrDefault(); if (messageUnreadLastCheck != null) { MyTrace.Trace("AzureCloudTable_" + Service.versionForTracing + ".Message_MarkAsRead. Error.Stage.2: MessageID:" + uid + ", UserID:" + userId + ". Message WAS deleted.", "Error. MarkAsRead"); //the message IS deleted, so although I don't understand why getting error in the first //place, the result should be the same throw ex; } else { //the message is NOT deleted, so we may as well give up now as I don't understand //what going on MyTrace.Trace("AzureCloudTable_" + Service.versionForTracing + ".Message_MarkAsRead. Error.Stage.2: MessageID:" + uid + ", UserID:" + userId + ". Message was NOT deleted.", "Error. MarkAsRead"); } } //mark the new message as read if (isUnreadMessageDeleted) { messageRead.PartitionKey = CreatePartitionKey(userId, true); messageRead.IsRead = true; //check if read message already exists in storage, if not, insert var resultsReadMessages = from messagesTable in tableServiceContext.CreateQuery<MessageDataEntity>(messageTableName) where messagesTable.PartitionKey == CreatePartitionKey(userId, true) && messagesTable.RowKey == CreateRowKey(uid) select messagesTable; //do the insert if (resultsReadMessages.FirstOrDefault() == null) { tableServiceContext.AddObject(messageTableName, messageRead); tableServiceContext.SaveChangesWithRetries(); } } } } } #endregion } catch (Exception ex) { try { MyTrace.Trace("AzureCloudTable_" + Service.versionForTracing + ".Message_MarkAsRead. Error: " + ex.Message + ex.StackTrace + ", " + ex.InnerException.Message + ex.InnerException.StackTrace, "Error. MarkAsRead"); } catch (Exception) { MyTrace.Trace("AzureCloudTable_" + Service.versionForTracing + ".Message_MarkAsRead. Error: " + ex.Message + ex.StackTrace, "Error. MarkAsRead"); } } } 

I do not understand how it is possible that the resource does not exist when it was returned to me as part of the request, and then I did a null check.

Based on the previous answers, I added code to perform an additional check in an attempt to find out if the object was somehow deleted. it has not been deleted

My trace returns this on error:

AzureCloudTable_3_0_5.Message_MarkAsRead. Error.Stage.1: MessageID: 146751012, BoyID: 477296. An error occurred while processing this request. in Microsoft.WindowsAzure.StorageClient.Tasks.Task 1.get_Result() at Microsoft.WindowsAzure.StorageClient.Tasks.Task 1.ExecuteAndWait () in BenderRestfulService_3_0_5.AzureCloudTable.Message_MarkAsRead (Int32 uotFound in ResourceNotFound ...) RequestId: 583c59df-fdac-47e4-a03c-7a4bc7d004c9 Time: 2011-10-05T16: 37: 36.7940530Z in System.Data.Services.Client.DataServiceContext.SaveResult.d__1e.MoveNext ()

AzureCloudTable_3_0_5.Message_MarkAsRead. Error.Stage.2: MessageID: 146751012, BoyID: 477296. The message is NOT deleted.

I am completely puzzled. Any advice is greatly appreciated !!!

Stephen

+4
source share
3 answers

The problem is resolved, and I will explain what I found if it benefits anyone else.

This was due to streaming (as Smarx avoided), the fact that I was thinking as a SQL developer, and that I would think of rather strange / unexpected behavior using Azure code and the real lack of in-depth Examples!

So, to solve this problem, I have simplified the problem as much as possible.

I created a table containing one object, PartitionKey 'a', RowKey '1'.

I created a console application that selected "a" from the table, deleted it, changed it to "b", and inserted it again.

I ran the code in a loop, thousands of times, and everything worked fine.

Then I moved the code to a thread and started two threads. Immediately, I ended up with errors and lost an entity somewhere between deletion and insertion.

Problem 1: two threads can select an object at the same time, both can check if it exists, and both can try and delete it. When deleting, only one will succeed. Therefore, you need to catch the error, make sure that the error contains "ResourceNotFound", and if so, believe that the object has really gone and continues as usual.

Problem 2: tableContext remembers the last failed action, so the thread in which the delete error will not cause another error in SaveChangesWithRetries after calling AddObject. So you need to use the new tableContext for AddObject

Problem 3: Both threads have a chance to add an object, but only one of them will be successful. Even if both threads check to see if objects exist before adding them, they might both think that it does NOT exist and both are trying to add it. Therefore, for simplicity, let both threads try to add and add it, one succeeds and someone throws an "EntityAlreadyExists" error. Just catch this mistake and continue.

Here is my working code for this simple example, I changed it for my more complex example in the original question and now I am not getting any errors at all.

  //method for shifting an entity backwards and forwards between two partitions, a and b private static void Shift(int threadNumber) { Console.WriteLine("Launching shift thread " + threadNumber); //set up access to the tables _storageAccount = CloudStorageAccount.Parse(_connectionString); _tableClient = new CloudTableClient(_storageAccount.TableEndpoint.AbsoluteUri, _storageAccount.Credentials); _tableClient.RetryPolicy = RetryPolicies.Retry(_retryAmount, TimeSpan.FromSeconds(_retrySeconds)); int lowerLimit = threadNumber * _limit; int upperLimit = (threadNumber + 1) * _limit; for (int i = lowerLimit; i < upperLimit; i++) { try { TableServiceContext tableServiceContextDelete = _tableClient.GetDataServiceContext(); tableServiceContextDelete.IgnoreResourceNotFoundException = true; string partitionKey = "a"; if (i % 2 == 1) { partitionKey = "b"; } //find the object with this partition key var results = from table in tableServiceContextDelete.CreateQuery<TableEntity>(_tableName) where table.PartitionKey == partitionKey && table.RowKey == "1" select table; TableEntity tableEntity = results.FirstOrDefault(); //shallow copy it if (tableEntity != null) { TableEntity tableEntityShallowCopy = new TableEntity(tableEntity); if (tableEntityShallowCopy.PartitionKey == "a") { tableEntityShallowCopy.PartitionKey = "b"; } else { tableEntityShallowCopy.PartitionKey = "a"; } //delete original try { tableServiceContextDelete.Detach(tableEntity); tableServiceContextDelete.AttachTo(_tableName, tableEntity, "*"); tableServiceContextDelete.DeleteObject(tableEntity); tableServiceContextDelete.SaveChangesWithRetries(); Console.WriteLine("Thread " + threadNumber + ". Successfully deleted. PK: " + tableEntity.PartitionKey); } catch (Exception ex1) { if (ex1.InnerException.Message.Contains("ResourceNotFound")) { //trying to delete an object that already been deleted so just continue } else { Console.WriteLine("Thread " + threadNumber + ". WTF?! Unexpected error during delete. Code: " + ex1.InnerException.Message); } } //move into new partition (a or b depending on where it was taken from) TableServiceContext tableServiceContextAdd = _tableClient.GetDataServiceContext(); tableServiceContextAdd.IgnoreResourceNotFoundException = true; try { tableServiceContextAdd.AddObject(_tableName, tableEntityShallowCopy); tableServiceContextAdd.SaveChangesWithRetries(); Console.WriteLine("Thread " + threadNumber + ". Successfully inserted. PK: " + tableEntityShallowCopy.PartitionKey); } catch (Exception ex1) { if (ex1.InnerException.Message.Contains("EntityAlreadyExists")) { //trying to add an object that already exists, so continue as normal } else { Console.WriteLine("Thread " + threadNumber + ". WTF?! Unexpected error during add. Code: " + ex1.InnerException.Message); } } } } catch (Exception ex) { Console.WriteLine("Error shifting: " + i + ". Error: " + ex.Message + ". " + ex.InnerException.Message + ". " + ex.StackTrace); } } Console.WriteLine("Done shifting"); } 

I am sure that there are much better ways to do this, but due to the lack of good examples, I am just going with something that works for me!

thanks

+1
source

Does this code work across multiple role instances or across multiple applications? (Perhaps an entity is deleted by another process between the moment you read it from the table and the time you try to delete it.)

+2
source

By default, MergeOption.AppendOnly used by your tableServiceContext.MergeOption , which means that Azure Table Storage will keep track of your table items. You must detach the item before deleting, for example:

 tableServiceContext.Detach(messageUnread); tableServiceContext.AttachTo(messageTableName, messageUnread, "*"); tableServiceContext.DeleteObject(messageUnread); tableServiceContext.SaveChangesWithRetries(); 

This should get rid of tracking issues.

0
source

All Articles