同時データ アクセス:Actian Zen がフラット ファイルより優れたデータ ストアである理由

プログラマーはデータ ストレージとしてフラット ファイルをよく利用しますが、その理由は使いやすく簡単に設定できるためです。しかし、Zen のようなデータベースを使用することも簡単で、コードとデータ管理タスクの両方が大幅に簡素化されます。

単一のアプリケーションまたはユーザーの場合、通常、ファイルへの書き込みおよびファイルからの読み取りは単純です。しかし、複数のアプリケーションが単一の保存場所に書き込んだり、単一の共有ファイルを読み取ろうとしたり、場合によっては更新しようとしたりした場合にはどうなるでしょうか? 標準のファイル アクセスでは、安全な同時読み取りと書き込みの問題を処理するのは開発者の責任です。

Actian Zen は、大きな IoT 展開などの複数レベルの分散データ環境における同時データ アクセスに対する高いニーズのサポートを簡単にします。

右側の「このページの内容」では当ページで扱うトピックを示しています。

フラット ファイルを使用した同時実行

同時データ アクセスというのはどういう意味でしょうか? これは、複数のプログラムが 1 つのデータ ファイルに同時にアクセスすることを意味します。ファイルを読み取るだけで変更しないのであれば、問題はありません。しかし、複数のリーダーとライターがいる場合は、どうなるかまったくわからなくなります。複数の書き込みルーチンを同時に実行すると、書き込み操作が交錯することがあるため、結果が変わる可能性があります。構造化データをファイルに書き込んでいる場合は、重複する書き込みがデータの構造に割り込み、その結果、ファイルが破損することになります。IoT デバイスやモバイル デバイスなど、共有データ リポジトリにデータを保存するさまざまなソースのデータではよくあることです。環境全体に展開された一連のセンサーを想像してください。すべてのセンサーによって生成されたデータは、分析用の単一の場所に収集する必要があります。  デバイスがデータを転送しているときに、重複する書き込みが発生する可能性があります。

フラット ファイルでは、破損の可能性を軽減する方法がいくつかあります。1 つはファイル ロックを使用することです。ファイル ロックの正確なメカニズムと機能はオペレーティング システムによって異なります。アプリケーションによってファイルがロックされているとき、別のアプリケーションがそのファイルの読み取りまたは変更を試みた場合は、ブロックされるかまたは失敗します。ファイルがロックされており、1 つのプログラムだけがアクセス可能であるとき、読み取りまたは書き込みするデータがある他のプログラムは、ファイルのロックが解除されるまで待つ必要があります。

次の例では、ファイルは排他アクセス用に開かれます。他のプロセスがそのファイルを排他アクセス用に開こうとした場合は失敗します。プログラムは、ファイルが利用可能になるまで待ち、自身のロックを正常に取得する必要があります。このコードは、任意の時間待機し、成功するまで試行します。

void write(vector<SensorReading> readings)
{
     HANDLE hFile;      

     do {
     hFile = CreateFile(L"sensorData.log", GENERIC_WRITE, 0,
                nullptr, OPEN_ALWAYS , FILE_ATTRIBUTE_NORMAL, nullptr);
     //start writing at the end of the file.
     SetFilePointer(hFile, 0, 0, FILE_END);
     //If the file couldn't be opened (something else has the exclusive lock)
     //wait for some random amount of time, but no more than 2 seconds, before
     //trying again
     if (hFile == INVALID_HANDLE_VALUE)
          Sleep(rand() % 2000);
     } while (hFile == INVALID_HANDLE_VALUE);
     for (int i = 0; i < readings.size(); ++i)
     {
        DWORD bytesWritten;
        WriteFile(hFile, reading[i], sizeof(SensorReading), &bytesWritten, 0);
     }
     CloseHandle(hFile);
}

vector<SensorReading> read()
{
     SensorReading reading;
     vector<SensorReading> readingList;
     HANDLE hFile;
     do {
     hFile = CreateFile(L"sensorData.log", GENERIC_READ, 0,
                nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
     //If the file couldn't be opened (something else has the exclusive lock)
     //wait for some random amount of time, but no more than 2 seconds, before
     //trying again
     if (hFile == INVALID_HANDLE_VALUE)
          Sleep(rand() % 2000);
     } while (hFile == INVALID_HANDLE_VALUE);
     DWORD bytesRead;
     do {
            ReadFile(hFile, reading, sizeof(SensorReading), &bytesRead, nullptr);
            readingList.push_back(reading);
     } while (bytesRead);
     CloseHandle(hFile);
     return readingList;
}

プログラムがファイルへのアクセスを取得し、すべての操作を完了したら、次のプログラムがアクセスを取得します。そのため、各プログラムは互いに干渉されないですべての書き込みを正常に実行できますが、次のようなパフォーマンスの影響があります。同時に 1 つのプログラムしかファイルにアクセスできないため、プログラムのすべてのインスタンスがタスクを完了するには、より多くの時間を要します。さらに、アクセスの待機を処理するための追加ロジックが各アプリケーションに必要となります。

排他的オープンに起因するファイル アクセスの完全なシリアル化を回避するために、プログラムは部分的ロックを使用して、特定のバイト範囲への排他アクセスを許可することができます。他のプロセスは、ロックされた範囲外のバイトにアクセスして読み取りや書き込み、他のロックのアサートを行うことができます。

複数のクライアントがこのファイルにアクセスしている場合、同じバイトにアクセスしようとしない限り問題はありません。しかし、複数のクライアントがファイルの同じ部分にアクセスしようとした場合は、エラーが発生する可能性があるため、プログラムにはそれらの状況を処理するための追加のコードが必要になります。

複数のデバイスがそれらのセンサー データを共通の場所に格納することを想像してください。ファイルのセクションがロックされている場合、一部のセンサーのレコードは正常に格納されるが、それ以外は失敗するという結果になる可能性があります。必要なファイルのセクションのロックが解除されるまで待機するためのコードを追加することができます。しかし、2 つのデバイスの両方が、もう一方のデバイスが必要とするデータのセクションをロックしている場合は、結果としてデッドロックになる可能性があります。つまり、それぞれが、他方がファイル ロックを解放するのを待って動きが取れない状態になります。

クライアントがさらに追加されるにつれ、このようなイベントが発生する可能性が上がります。さらに、ファイルをある環境から別の環境にコピーする場合には、ファイルはすべて同じ形式および構造体を使用している必要があるか、または変換するツールが必要になります。製品間で同じファイル形式が確実に使用されるようにするには、開発者間での調整が必要です。形式の違いは、抽出、変換、格納(ETL)処理と、データ転送用の追加オーバーヘッドの必要性を意味します。

Zen を使用した同時データ アクセス

Actian Zen は、さまざまなオペレーティング システムやアーキテクチャを実行している異なるデバイス間の同時データ アクセスを調整します。Windows、MacOS、Linux を実行している PC や、Raspberry Pi、Windows IoT Core、iOS/Android モバイル デバイスなどの IoT エンドポイントで動作します。これらすべてのデバイスについて、データは同じファイル形式を使用して保存されます。データまたはファイル全体をある Zen インスタンスから別のインスタンスへコピーする必要がある場合、ETL 処理は必要ありません。

特別なコードなしで、2 つのデバイスが同じファイルを開くことができます。これは、ファイルが別のコンピューター上にある場合でも安全に行えます。次のコードを使用すれば、複数のデバイスが互いに干渉することなく、それぞれのセンサー データを同じファイルに書き込むことができます。

void WriteSensorData(vector<SensorReading> readings)
{
     BtrieveClient btrieveClient;
     BtrieveFile btrieveFile;
     btrieveClient.FileOpen(&btrieveFile, FILE_NAME, NULL, Btrieve::OPEN_MODE_NORMAL);
     for (int i = 0; i < readings.size(); ++i)
     {
     btrieveFile.RecordCreate((char*)&readings[i], sizeof(SensorReading));
     }
     btrieveClient.FileClose(&btrieveFile);
     btrieveClient.Reset();
}

Zen エンジンは、特定ファイルへのハンドルを持つすべてのプログラムによるアクセスを調整することで、アプリケーションに追加のコードを必要としないで、書き込み操作が一貫していて安全に完了するようにしています。

プログラムがファイルからレコードを読み取るとき、データが断続的な状態になることはありません。Zen によって自動的に行われます。

更新を実行する必要のあるアプリケーションは、ロック バイアスを使用してレコードを取得することを選択できます。これは、単純に Lock_Mode を RecordRetrieve 呼び出しに追加するだけです。アプリケーションは同時に単一のレコード、または複数のレコードをロックすることができます。また、レコードをロックに使用できるようになるまで読み取りとロックの要求を待つか、あるいは、目的のレコードが別のユーザーによって既にロックされている場合にはステータス コード("レコードは使用中")を返すかどうかを指定できます。

ロックの調整のすべては Zen エンジンによって処理されるので、アプリケーションはロックの有無にかかわらず取得したいかどうかを示す必要があるだけです。次のコード サンプルは、この機能の実装を示しています。

vector<SensorReading> ReadSensorData()
{
     BtrieveClient btrieveClient;
     BtrieveFile btrieveFile;
     Btrieve::StatusCode status;
     vector<SensorReading> readingList;
     SensorReading sensorReading;

     btrieveClient.FileOpen(&btrieveFile, SENSOR_FILE_NAME, NULL, Btrieve::OPEN_MODE_NORMAL);

     int bytesRead = btrieveFile.RecordRetrieveFirst(
          Btrieve::INDEX_NONE, (char*)&sensorReading,
          sizeof(SensorReading),  Btrieve::LOCK_MODE_SINGLE_WAIT);
     status = btrieveFile.GetLastStatusCode();
     while (status == Btrieve::STATUS_CODE_NO_ERROR)
     {
     readingList.push_back(sensorReading);
     btrieveFile.RecordRetrieveNext((char*)&sensorReading, sizeof(SensorReading),               
          Btrieve::LOCK_MODE_SINGLE_WAIT);
     status = btrieveFile.GetLastStatusCode();
     }
     btrieveClient.FileClose(&btrieveFile);
     btrieveClient.Reset();
     return readingList;
}

Zen は、デバイス間でデータを同期するためのソリューションも提供しています。複数のクライアントが挿入を実行している場合に、コードで行う必要のある特別なことは何もありません。データが正しく挿入されるように Zen が自動的に処理します。

いくつかの挿入/更新/削除操作のすべてが正常に完了するようにするために、それらを 1 つの単位として実行する必要がある場合があります。トランザクションを使用してこれらの操作を包含すると、すべての操作が完了するか、またはどの操作も完了しないことが Zen エンジンによって保証されます。Zen エンジンは、同時に発生する複数のトランザクションが同じ行に影響を与えない限り、それらのトランザクションを処理します。

トランザクションは 1 つの単位として、正常に完了するか、または失敗します。失敗した場合、操作はデータ ファイルに変更を加えません。そのため、部分的な結果も一貫性のないデータも生じません。

次のコードでは、2 つのレコードに影響を与えるトランザクションが実行されます。センサーの読み取り状況は、メンテナンス イベントの影響を受けた可能性のある測定値があることを示すフラグをセットするよう更新され、メンテナンスに関する新しいイベント レコードがログに書き込まれます。

btrieveClient.TransactionBegin(
     Btrieve::TransactionMode::TRANSACTION_MODE_CONCURRENT_WRITE_WAIT);
try {
     status = eventFile.RecordCreate((char*)&eventRecord, sizeof(Event));
     if (status == Btrieve::STATUS_CODE_NO_ERROR) throw "Record could not be created";
     int bytesRead = sensorFile.RecordRetrieve(
          Btrieve::COMPARISON_EQUAL, Btrieve::INDEX_1,
          (char*)sensorRecordKey, sizeof(sensorRecordKey),
          (char*)&sensorReading, sizeof(SensorReading)
     );

     if (bytesRead == -1) throw "Record could not be retrieved";
     sensorReading.maintenance = true;
     status = sensorFile.RecordUpdate((char*)&sensorReading, sizeof(SensorReading));
     if (status == Btrieve::STATUS_CODE_NO_ERROR) throw "Record was not updated";
     status = btrieveClient.TransactionEnd();
     return true;
}
catch (const char * msg)
{
     btrieveClient.TransactionAbort();
     cerr << message;
     return false;
}

Zen でのデータ アクセスのロック

さまざまな種類のロックを Zen データ ファイルに適用することができます。

排他ロックを使用すると、データ ファイル全体をロックすることができ、単一のプログラムまたはユーザーのみがファイルを利用できます。また、アプリケーションは単一のレコードまたは一連のレコードをロックすることもできます。アプリケーションまたはプロセスが既にレコードをロックしており、別のアプリケーションが同一のレコードのロックを要求した場合、後の要求は、要求したレコードが使用可能になるまで待機するように設定することができます。

プログラムは、まったく待機しないことも選択できます。レコードのロックを直ちに取得する(レコードが使用可能な場合)か、待機しないで直ちに失敗するかのいずれかになります。待機の動作はアプリケーション次第です。ほとんどのアプリケーションはノーウェイト(待機なし)ロックを使用しており、独自の再試行ロジックを実装しています。

さらに、多くの操作では、ロックは明示的である必要はありませんが、それでも Zen エンジンはデータ整合性を保護します。2 つのプロセスが同じレコードを更新している場合、2 番目のプロセスは操作のエラー ステータスを受け取ります。エラー ステータスは、レコード内のデータが、読み取ったときから書き込もうとしたときまでの間に既に変更されていることを知らせます。

まとめ

最も一般的なシナリオの場合、Zen インスタンスは、数百の同時ユーザーが同じデバイスで実行されているアプリケーションであろうと、ネットワーク経由で接続されたピア デバイスで実行されているアプリケーションであろうと対応することができます。

マルチプラットフォーム環境での同時データ アクセスの調整は重要な問題です。Zen データベース製品ファミリは、さまざまなデバイスの種類、オペレーティング システム、および開発環境にわたって機能するソリューションです。ACID の原則に準拠しているため、Zen を使用すれば、接続の失敗を原因とする破損や、同時データ アクセスによって生じる可能性のある他の問題からデータを保護できます。

評価版ページから Actian Zen v15 評価版を入手すると、多くの Zen 製品を試してみることができます。エッジおよび組み込みデバイスの Zen ソリューションの詳細については、Actian Zen ページを参照してください。