Організація одночасного доступу до даних в хмарному сховищі Microsoft Azure Storage



У сучасних веб-додатках часто виникають ситуації, коли кілька користувачів працюють з одними і тими ж даними.

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

Найбільш часто розробники використовують такі три стратегії з управління одночасним доступом до даних:

  1. Оптимістичний паралелізм (Optimistic concurrency)

    Додаток оновить дані тільки після того, як перевірить їх на наявність змін з моменту останнього звернення до них.

    Наприклад, два користувача переглядають одну і ту ж wiki-сторінку, а потім одночасно вирішують її оновити.

    У цьому випадку вікі-платформа повинна забезпечити процес, при якому оновлення другого користувача не замістить оновлення першого, і обидва користувача зрозуміють було вироблено дію кожного з них.

    Ця стратегія використовується у веб-додатках найчастіше.

  2. Песимістичний паралелізм (Pessimistic concurrency)

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

    Наприклад, master/slave сценаріях реплікації даних, де тільки майстер виконує оновлення, як правило, тільки він може встановити довготривалу блокування даних, для запобігання редагування даних ким-небудь ще.

  3. «Виграє останній» (Last writer wins)

    Такий підхід дозволяє виконувати будь-які операції з даними, без повірки даних на оновлення з моменту останнього звернення додатки до них.

    Ця стратегія (або відсутність формальної стратегії), як правило, застосовується там, де дані розподілені таким чином, що ймовірність доступу декількох користувачів до одного і того ж розділу виключена.

    Також описаний вище спосіб корисний при обробці короткочасних потоків даних.
У цій статті розмова піде про те, як платформа Azure Storage спрощує розробку програм, що використовують сховища даних, надаючи підтримку всіх трьох стратегій організації одночасного доступу.

Azure Storage — спрощує хмарну розробку
Azure Storage підтримує всі три стратегії одночасного доступу до даних, хоча й існують деякі особливості підтримки оптимістичного і песимістичного паралелізму, так як спочатку сховища були спроектовані для моделі суворого паралелізму, яка гарантує, що в момент, коли сервіс сховища завершує операції додавання і редагування даних, всі подальші звернення до цих даних виявлять останнє оновлення.

Платформи сховищ, що використовують модель узгодженості, містять лад — затримку між періодом, коли здійснюється запис одним користувачем і періодом, коли оновлені дані видно іншим користувачам, що призводить до ускладнення розробки клієнтських додатків за необхідності запобігати невідповідності в даних для кінцевих користувачів.

В доповнення до вибору відповідної стратегії доступу до даних, розробники повинні звернути увагу на те, як платформа сховища організовує ізоляцію змін — часткові зміни одних і тих же об'єктів в транзакціях.

Сервіс Azure Storage використовує ізоляцію снэпшотами для забезпечення можливості одночасного читання і запису в межах одного розділу.

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

Організація одночасного доступу до BLOB-об'єктів
Ви можете вибрати, яку стратегію паралелізму використовувати для управління доступом до блоб-об'єктах і контейнерах в сервісі блобов.

Якщо ви не вкажете стратегію явно, то за замовчуванням буде використовуватися стратегія «Виграє останній».

Оптимістичний паралелізм для блобов і контейнерів

Сервіс сховища надає ідентифікатор кожного зберігається об'єкта. Цей ідентифікатор оновлюється кожен раз при виконанні операції оновлення над об'єктом. Ідентифікатор повертається клієнту як частина відповіді на запит HTTP GET, використовуючи заголовок ETag (тег сутності), визначений у протоколі HTTP.

Користувач, виконує оновлення на такому об'єкті, може відправити разом з оригінальним ETag умовний заголовок, для того щоб переконатися, що оновлення відбудеться тільки при певних умовах — в даному випадку умовою є заголовка «If-Match», який потрібно сервісом сховища.

Нижче в загальних рисах представлено план цього процесу:

  1. Отримуємо блоб з сервісу сховища, відповідь включає параметр заголовка HTTP ETag, який ідентифікує поточну версію об'єкта в сервісі сховища.
  2. Коли ви оновлюєте блоб, увімкніть отриманий параметр ETag з попереднього кроку в умовний заголовок If-Match, який ви відправляєте сервісу.
  3. Сервіс порівнює значення ETag в запиті з поточним значенням ETag в блобе.
  4. Якщо поточне значення ETag блоба відрізняється від ETag в заголовку запиту If-Match, сервіс повертає клієнту помилку 412. Це вказує клієнту на те, що інший процес оновив блоб, з тих пір, як клієнт його запитував.
  5. Якщо поточне значення ETag не відрізняється від значення ETag в заголовку запиту, то сервіс виконає запитану операцію і оновить поточне значення ETag блоба, щоб показати, що було оновлення даних.
Нижче представлений З# фрагмент показує простий приклад створення умови If-Match c допомогою класу AccessCondition, заснованого на значенні ETag, яке було отримано з властивостей, раніше витягнутого або доданого блоба.

Потім це умова використовує об'єкт AccessCondition під час оновлення блоба: об'єкт AccessCondition додає заголовок If-Match запит.

Якщо інший процес оновив блоб, то сервіс блобов поверне повідомлення HTTP 412 (Precondition Failed).

Повний приклад можна скачати тут.

// Отримує Etag з результату, раніше виробленої блоб-операції UploadText
string orignalETag = blockBlob.Properties.ETag;
// Цей код імітує оновлення стороннім процесом
string helloText = "Blob updated by a third party.";
// Не надано жодного etag, тому початковий блоб оновлено (це згенерує новий etag)
blockBlob.UploadText(helloText);
Console.WriteLine("Blob updated. Updated ETag = {0}", blockBlob.Properties.ETag);
// Тепер пробуємо відновити блоб, використовуючи початковий ETag, отриманий при створенні блоба 
try
{
Console.WriteLine("Trying to update blob using orignal etag to generate if-match access condition");
blockBlob.UploadText(helloText,accessCondition:
AccessCondition.GenerateIfMatchCondition(orignalETag));
}
catch (StorageException ex)
{
if (ex.RequestInformation.HttpStatusCode == (int)HttpStatusCode.PreconditionFailed)
{
Console.WriteLine("Precondition failure as expected. Blob's orignal etag no longer matches");
}
}


Сервіс сховища також підтримує й інші умовні заголовки, такі як If-Modified-Since, If-Unmodified-Since та If-None-Match.

Додаткова інформація документація на MSDN.

У таблиці наведені операції над контейнерами, що приймають умовні заголовки, такі як If-Match,в запиті і повертають ETag у відповіді:











Операція Повертає значенняETag Приймає умовні заголовки Create Container Yes No Get Container Properties Yes No Get Container Metadata Yes No Set Container Metadata Yes Yes Get Container ACL Yes No Set Container ACL Yes Yes (*) Delete Container No Yes Lease Container Yes Yes List Blobs No No
(*) Дозволу, визначаються SetContainerACL кешуються, а їх оновлення займає 30 секунд, протягом яких не гарантується узгодженість оновлень.

У таблиці наведені операції блобов, які беруть участь у запиті умовні заголовки, такі як If-Match, та повертають значення ETag:


















Операція Повертає значення ETag Приймає умовні заголовки Put Blob Yes Yes Get Blob Yes Yes Get Blob Properties Yes Yes Set Blob Properties Yes Yes Get Blob Metadata Yes Yes Set Blob Metadata Yes Yes Lease Blob (*) Yes Yes Snapshot Blob Yes Yes Copy Blob Yes Yes (for source and destination blob) Abort Copy Blob No No Delete Blob No Yes Put Block No No Put Block List Yes Yes Get Block List Yes No Put Page Yes Yes Get Page Ranges Yes Yes
(*) Лізинг блоба не змінює його Etag.

Песимістичний паралелізм у блобах

Щоб заблокувати блоб для виняткового використання, застосовується механізм лізингу. При використанні лізингу ви вказуєте його тривалість: 15 — 60 секунд, або без закінчення, що означає виключну блокування. Ще ви можете продовжити блокування, або розблокувати блоб після завершення роботи з ним. Сервіс блобов автоматично відключає лізинг, якщо строк його дії на блобе минув.

Лізинг дозволяє використовувати різні стратегії синхронізації, включаючи ексклюзивну запис/ розділене читання, ексклюзивну запис / ексклюзивне читання, розділену запис / ексклюзивне читання.

Там, де існує лізинг, сервіс сховища впорядковує ексклюзивну запис (операції put, set, delete). Для забезпечення ексклюзивності операцій читання, розробнику потрібно передбачити, щоб всі клієнтські додатки використовували ідентифікатор лізингу, і тільки один клієнт в один момент часу мав відповідний ідентифікатор лізингу. Операції читання, які не включають в себе ідентифікатор лізингу відбуваються в розділеному читанні.

У коді нижче (C#) показаний ексклюзивний 30 секундний лізинг на блобе, оновлення блоба і припинення лізингу. Якщо необхідний лізинг на блобе вже встановлений, то при спробі установки нового, сервіс блобов поверне результат «HTTP (409) Conflict».

При створенні запиту на оновлення блоба в службі сховища, в коді, для інформації про лізинг, використовується об'єкт AccessCondition.

Повний приклад можна скачати тут.

// Встановлюємо лізинг на 15 секунд
string lease = blockBlob.AcquireLease(TimeSpan.FromSeconds(15), null);
Console.WriteLine("Blob lease acquired. Lease = {0}", lease);

// Оновлюємо блоб, використовуючи лізинг. Це операція пройде успішно
const string helloText = "Blob updated";
var accessCondition = AccessCondition.GenerateLeaseCondition(lease);
blockBlob.UploadText(helloText, accessCondition: accessCondition);
Console.WriteLine("Blob updated using an exclusive lease");

//Імітація оновлення блоба без лізингу стороннім процесом Simulate third party update to blob without lease
try
{
// Операція нижче не відбудеться, оскільки не надано відповідного лізингу
Console.WriteLine("Trying to update blob without valid lease");
blockBlob.UploadText("Update without lease, will fail");
}
catch (StorageException ex)
{
if (ex.RequestInformation.HttpStatusCode == (int)HttpStatusCode.PreconditionFailed)
Console.WriteLine("Precondition failure as expected. Blob's lease does not match");
else
throw;
}


Якщо зробити операцію запису на блобе з лізингом без передачі ідентифікатора лізингу, то запит впаде з помилкою 412. Зверніть увагу — якщо термін лізингу закінчиться перед викликом методу UploadText, а ви все ще передаєте ідентифікатор лізингу, запит знову ж таки впаде з помилкою 412.

Для отримання інформації про управління тривалістю лізингу і ідентифікатором лізингу, перегляньте документацію.

У наступному списку наведені операції блобов, які можуть використовувати лізинг для песимістичного паралелізму:
  • Put Blob
  • Get Blob
  • Get Blob Properties
  • Set Blob Properties
  • Get Blob Metadata
  • Set Blob Metadata
  • Delete Blob
  • Put Block
  • Put Block List
  • Get Block List
  • Put Page
  • Get Page Ranges
  • Snapshot Blob — ідентифікатор лізингу не потрібно
  • Copy Blob — ідентифікатор вимагається, якщо на блобе встановлений лізинг
  • Abort Copy Blob — ідентифікатор вимагається, якщо на блобе встановлений не обмежений лізинг
  • Lease Blob


Песимістичний паралелізм для контейнерів

Лізинг на контейнерах надає підтримку тієї ж стратегії синхронізації, що і на блобах (ексклюзивна запис / розділене читання, ексклюзивна запис / ексклюзивне читання і розділена запис / ексклюзивне читання), однак, на відміну від блобов, сервіс сховища застосовує стратегію ексклюзивності для операцій видалення.

Для видалення контейнера з активним лізингом клієнт повинен включити ідентифікатор активного лізингу в запит на видалення.

Інші операції над лізинг-контейнером не повинні обов'язково містити ідентифікатор лізингу, і такі операції називаються розділяються.

Якщо потрібні операції ексклюзивного оновлення (put або set) або читання, то розробникам потрібно, щоб кожен клієнт використовував ідентифікатор лізингу і тільки один клієнт використовував відповідний на даний момент ідентифікатор.

Нижче перераховані операції контейнера, які можуть використовувати лізинг для песимістичного паралелізму:
  • Delete Container
  • Get Container Properties
  • Get Container Metadata
  • Set Container Metadata
  • Get Container ACL
  • Set Container ACL
  • Lease Container
  Додаткова інформація:
Організація паралелізму в сервісі Tables
Сервіс таблиць під час роботи з сутностями за замовчуванням використовує оптимістичну стратегію одночасного доступу до даних, на відміну від сервісу блобов, де треба явно вибрати використання оптимістичного паралелізму.

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

Щоб використовувати оптимістичний паралелізм і перевірити, чи була модифікована сутність іншим процесом з моменту її вибірки з сервісу таблиць, ви можете використовувати отримане під час вибірки значення ETag.

План цього процесу наведено нижче:

  1. Витяг сутності з сервісу сховища таблиць. Відповідь включає значення ETag — поточний ідентифікатор пов'язаний із сутністю в сховище.
  2. Коли ви оновлюєте сутність, увімкніть отриманий параметр ETag з попереднього кроку в умовний заголовок If-Match, який ви відправляєте сервісу.
  3. Сервіс порівнює значення ETag в запиті з поточним значенням ETag сутності.
  4. Якщо поточне значення ETag суті відрізняється від ETag в заголовку запиту If-Match, сервіс повертає клієнту помилку 412. Це вказує клієнту на те, що інший процес оновив сутність, з тих пір, як клієнт її запитував.
  5. Якщо поточне значення ETag не відрізняється від значення ETag в заголовку запиту If-Match або If-Match заголовок містить символ (*), то сервіс виконає запитану операцію і оновить поточне значення ETag суті, щоб показати, що відбулося оновлення даних.
Зверніть увагу, що на відміну від сервісу блобов сервіс таблиць вимагає щоб клієнт включав заголовок If-Match запити на оновлення. Однак, залишається можливість примусового безумовного оновлення (стратегія «виграє останній») і обходу перевірок при встановленні клієнтом значення заголовка If-Matchдорівнює символу (*) у запиті.

Код нижче (C#) демонструє сутність customer, створену або виділений з наявних даних з оновленим адресою email. Первісна операція вставки або витягу зберігає значення ETag в об'єкті customer і, так як приклад використовує той же екземпляр об'єкта під час виконання операції заміни, він автоматично відправляє значення ETag назад в сервіс таблиць, дозволяючи сервісу перевірити порушення при одночасному доступі.

Якщо інший процес оновив сутність у сховище таблиць, служба повертає повідомлення зі статусом HTTP 412 (Precondition Failed).

Повний приклад доступний тут

try
{
customer.Email = "updatedEmail@contoso.org";
TableOperation replaceCustomer = TableOperation.Replace(customer);
customerTable.Execute(replaceCustomer);
Console.WriteLine("Replace operation succeeded.");
}
catch (StorageException ex)
{
if (ex.RequestInformation.HttpStatusCode == 412)
Console.WriteLine("Optimistic concurrency violation - entity has changed since it was retrieved.");
else
throw; 
}


Для явної блокування перевірки одночасного доступу, ви повинні встановити властивість ETag об'єкта employee в значення «*» перед проведенням операції оновлення.

customer.ETag = "*";

У таблиці показано як табличні операції використовують значення ETag:









Операції Повертає значення ETag Вимагає умовні заголовки Query Entities Yes No Insert Entity Yes No Update Entity Yes Yes Merge Entity Yes Yes Delete Entity No Yes Insert or Replace Entity Yes No Insert or Merge Entity Yes No
Зверніть увагу, що операції InsertorReplaceEntity та InsertorMergeEntityне виконують ніяких перевірок одночасного доступу, тому що вони не відправляють значення ETag в сервіс таблиць.

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

Якщо виникла необхідність у песимістичною блокування, то підходящим варіантом доступу до таблиць для розробників може стати виділення спеціального блоба для кожної таблиці і спроба його лізингу перед кожною операцією на таблиці.

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

Зверніть увагу, що мінімальна тривалість лізингу становить 15 секунд, що потребує особливої уваги при розробці масштабованих рішень.

Додаткова інформація:
Організація паралелізму в сервісі сховища черг
Для черг існує один сценарій, при якому виникає необхідність використання стратегії паралельного доступу — це коли кілька клієнтів одночасно отримують повідомлення з черги. Коли повідомлення отримано черги, відповідь включає в себе саме повідомлення і значення pop receipt value, яке потрібно для подальшого видалення повідомлення.

Повідомлення не видаляється з черги автоматично, але, після того як воно було вилучено воно стає для клієнтів не видимим на певний час visibilitytimeout.

Клієнт, який отримує повідомлення, очікує видалення повідомлення після того як воно було опрацьовано і до моменту, визначеного елементом TimeNextVisible у відповіді.

Для визначення TimeNextVisible значення visibilitytimeout додається до часу отримання повідомлення.

Сервіс сховища черг не підтримує оптимістичну або песимістичну стратегію, тому клієнти, які обробляють повідомлення, витягнуті з черги, повинні забезпечити идемпотентный спосіб обробки повідомлень.

Для операцій оновлення, таких як SetQueueServiceProperties, SetQueueMetaData, SetQueueACL і UpdateMessage використовується стратегія «виграє останній».

Додаткова інформація:
Організація паралелізму в сервісі зберігання файлів
Доступ до файлового сервісу може бути здійснено за допомогою двох різних протоколів: SMB і REST. Сервіс REST не підтримує оптимістичну або песимістичну блокування, і всі оновлення будуть проводитися на підставі стратегії «виграє останній».

Клієнти, що використовують SMB, можуть застосовувати механізм блокування на рівні файлової системи для управління доступом до подільних (shared) файлів, включаючи можливість песимістичною блокування.

Коли SMB-клієнт відкриває файл, він визначає параметр доступу до файлу і режим розділяється доступу. В результаті установки опції доступу до файлу в значення «Write» або «Read/Write» і режиму розділяється доступу в значення «None», файл буде заблокований SMB-клієнтом до його закриття.

Якщо операція REST проводиться на файлі, заблокованому SMB-клієнт, то REST сервіс поверне помилку 409 з кодом «Sharing Violation».

Коли SMB-клієнт відкриває файл для вилучення, він позначає файл як «очікує видалення» до тих пір, поки всі інші SMB-клієнти не закриють його. Поки файл відзначений як очікує видалення, будь REST-операції на цьому файлі будуть повертати помилку 409 з кодом SMBDeletePending. Код помилки 404 (Not Found) не буде повертатися, так як існує можливість, що SMB-клієнт прибере прапор очікування видалення перед тим як закрити файл. Іншими словами, код помилки 404 повернеться тільки тоді, коли файл буде дійсно видалено.

Зверніть увагу, що поки файл перебуває в стані очікуванні видалення SMB-клієнт, він не буде включений в результати видачі List Files.

треба ще враховувати, що операції REST Delete File і Delete Directory виконуються атомарно і не призводять до встановлення стану «очікує видалення».

Додаткова інформація:
Висновок
Хмарне сховище Microsoft Azure було спроектовано для задоволення потреб складних веб-додатків. При цьому, від розробників не потрібно жертвувати або переосмислювати ключові шаблони проектування, такі як одночасний доступ до даних або забезпечення коректного стану даних. Механізми їх забезпечення включені в саме сховище.

Повний приклад програми, використаного в статті:
Детальна інформації про Azure Storage:
Корисні посилання

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.