Ошибка удаления из облачной таблицы Azure - ResourceNotFound - PullRequest
4 голосов
/ 29 сентября 2011

У меня периодически возникают проблемы с удалением объектов из таблицы Azure. Это затрагивает только около 1% моих попыток, и если тот же вызов будет повторен позже, он будет работать нормально, но я бы хотел выяснить причину этого! Я погуглил свои пальцы и обнаружил, что отсутствие документации о том, как создать очень надежный код для удаления, вставки и обновления, довольно удивительно ... все это кажется немного неудачным " попробуйте, в большинстве случаев это будет работать "

РЕДАКТИРОВАТЬ: Я удаляю текст, который был первоначально в этих вопросах, и заменяю его совершенно новым текстом, чтобы учесть то, что я пытался / что было предложено.

Таблицы Azure подвержены периодическим сбоям, таким как SQL Azure. Если так, то я бы сказал, что "saveChangesWithRetries" справился бы с этим? Это неправильно?

Итак ... довольно просто код, вызываемый примерно 250 раз в минуту на веб-роли Azure. Таблицы Azure используются как часть системы обмена сообщениями. Сообщения вставляются одним пользователем, загружаются другим, при успешной загрузке эти сообщения помечаются как прочитанные.

У каждого пользователя есть раздел для непрочитанных сообщений и раздел для прочитанных сообщений. Таким образом, чтобы пометить сообщение как «прочитанное», оно удаляется из непрочитанного раздела и перемещается в раздел чтения.

Из 250 раз, когда этот код вызывается в минуту, я получу от 2 до 10 следующих ошибок в финальном SaveChangesWithRetries (). Внутреннее исключение:

ResourceNotFound указанный ресурс не существует. RequestID: 652a3e13-3911-4503-8e49-6fec32a3c044 Время: 2011-09-28Т22: 09: 39.0795651Z

Я не представляю себе доступ к отдельному разделу чаще, чем несколько раз в минуту.

Это мой код:

    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's 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's 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");
            }
        }
    }

Я не понимаю, как ресурс может вообще не существовать, когда он был возвращен мне как часть запроса, а затем я проверил! = Null его.

Основываясь на предыдущих ответах, я добавил код для дополнительной проверки, чтобы узнать, был ли объект каким-либо образом удален. не было удалено

Моя трассировка возвращает это при ошибке:

AzureCloudTable_3_0_5.Message_MarkAsRead. Error.Stage.1: MessageID: 146751012, BoyID: 477296. Произошла ошибка при обработке этого запроса. в Microsoft.WindowsAzure.StorageClient.Tasks.Task 1.get_Result() at Microsoft.WindowsAzure.StorageClient.Tasks.Task 1.ExecuteAndWait () в BenderRestfulService_3_0_5.AzureCloudTable.Message_MarkAsRead (Int32 uid) в ... ResourceNotFound Указанный ресурс не существует. RequestId: 583c59df-fdac-47e4-a03c-7a4bc7d004c9 Время: 2011-10-05T16: 37: 36.7940530Z в System.Data.Services.Client.DataServiceContext.SaveResult.d__1e.MoveNext ()

AzureCloudTable_3_0_5.Message_MarkAsRead. Error.Stage.2: MessageID: 146751012, BoyID: 477296. Сообщение НЕ было удалено.

Я совершенно сбит с толку. Любой совет с благодарностью !!!

Стивен

Ответы [ 3 ]

2 голосов
/ 29 сентября 2011

Этот код выполняется в нескольких экземплярах роли или в нескольких приложениях? (Возможно, объект удаляется другим процессом между временем, когда вы читаете его из таблицы, и временем, когда вы пытаетесь удалить его.)

1 голос
/ 10 октября 2011

Проблема решена, и я объясню, что я нашел в случае, если это принесет пользу кому-то еще.

Это происходило из-за многопоточности (как ускользнуло от Smarx), из-за того, что я думал как разработчик SQL, и из-за того, что я считаю довольно странным / неожиданным поведением в коде Azure, и из-за недостатка глубины Примеры!

Итак, чтобы решить ее, я максимально упростил задачу.

Я создал таблицу, которая содержала одну сущность: PartitionKey 'a', RowKey '1'.

Я создал консольное приложение, которое выбрало «a» из таблицы, удалило его, изменило его на «b» и заново вставило.

Я запускал код по циклу тысячи раз, и все работало нормально.

Затем я переместил код в поток и запустил два потока. Сразу же я столкнулся с ошибками и потерял сущность где-то между удалением и вставкой.

Проблема 1: оба потока могут одновременно выбрать объект, оба могут проверить, существует ли он, затем оба могут попробовать и удалить его. Только один будет успешным при его удалении. Поэтому вам нужно отловить ошибку, убедиться, что ошибка содержит «ResourceNotFound», и, если это так, поверить, что объект действительно исчез, и продолжить работу как обычно.

Проблема 2: tableContext запоминает последнее неудачное действие, поэтому поток, в котором не удалось удалить, выдаст еще одну ошибку в SaveChangesWithRetries после вызова AddObject. Поэтому вам нужно использовать новый tableContext для AddObject

Проблема 3: Оба потока имеют шанс добавить сущность, но только один из них будет успешным. Даже если оба потока проверяют, существует ли объект перед его добавлением, они оба могут подумать, что он не существует, и оба пытаются добавить его. Итак, для простоты, пусть оба потока попробуют добавить его, один из них будет успешным, а другой сгенерирует ошибку «EntityAlreadyExists». Просто поймайте эту ошибку и продолжайте.

Вот мой рабочий код для этого простого примера, я изменил его для моего более сложного примера в исходном вопросе и теперь вообще не получаю ошибок.

    //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's 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");
    }

Я уверен, что есть гораздо более приятные способы сделать это, но из-за отсутствия хороших примеров, я просто иду с чем-то, что работает для меня!

Спасибо

0 голосов
/ 02 октября 2011

По умолчанию MergeOption.AppendOnly используется вашим tableServiceContext.MergeOption, это означает, что хранилище таблиц Azure будет отслеживать элементы вашей таблицы.Вы должны отсоединить элемент перед его удалением, например так:

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

Это должно избавить от любых проблем с отслеживанием элементов.

...