同時更新の制御

Server Code は多くのユーザーによって同時に実行されるため、排他制御が必要になる場合があります。リレーショナルデータベースでは、トランザクションとロックによる排他制御がよく使用されますが、Kii Cloud では楽観的ロックによる同時実行制御をサポートしています。楽観的ロックについては、更新チェック をご覧ください。

Server Code を同期的に書いたとしても、同時更新に対する解決策にはなりません。複数のユーザーが同時に Server Code を実行した場合は、それぞれのリクエストが平行して処理されます。

更新チェックを行うには、KiiObject.save メソッドの第 2 引数の overwrite パラメータを false に設定します。更新の衝突を検知した場合は、エラーメッセージとして OBJECT_VERSION_IS_STALE: から始まる文字列が返るため、再試行を行って、一貫性が失われないように更新できます。

たとえば、システムでユニークなシーケンス値(1, 2, 3,…)を返す Server Code が必要な場合は、次のようなコードとなります。

function main(params, context, done) {
  // Instantiate a KiiObject.
  var object = context.getAppAdminContext().objectWithURI('Set the URI of an existing KiiObject here');

  // Return the value of the sequence field.
  getSequence(object, 1, done);
}

function getSequence(object, loop, done) {
  // Return an error if the retry count is more than 100.
  if (loop > 100) {
    done("Server Busy");
    return;
  }

  // Refresh the KiiObject.
  object.refresh({
    success: function(object) {
      // Increment the value of the sequence field.
      var sequence = object.get('sequence') + 1;
      object.set('sequence', sequence);

      // Save the KiiObject.
      object.save({
        success: function(theObject) {
          // Return the incremented value of the sequence field.
          done(sequence);
        },
        failure: function(theObject, errorString) {
          // If the overwrite check returned an error
          if (errorString.split(":")[0] === "OBJECT_VERSION_IS_STALE") {
            // Increment the retry count and retry the processing.
            getSequence(object, loop + 1, done);
          } else {
            done("Unexpected Error:" + errorString);
            return;
          }
        }
      }, false);
    },
    failure: function(theObject, errorString) {
      done("Unexpected Error:" + errorString);
      return;
    }
  });
}

ここでは以下の処理を行っています。

  • main 関数が手動実行のエンドポイントです。オブジェクトを用意したら、getSequence メソッドを呼び出します。objectWithURI() メソッドの引数には、初期値 sequence を設定したアプリケーションスコープの KiiObject の URI を指定することを想定しています。
  • getSequence メソッドでは、refresh → sequence フィールドのインクリメント → save という処理を行います。save に成功した場合は、シーケンス値を返します。
  • KiiObject.save メソッドの overwrite パラメータが false のため、refresh から save までの間に他の処理によって更新された場合、OBJECT_VERSION_IS_STALE エラーが発生します。この場合は、getSequence メソッドの再帰によって、もう一度 refresh からやり直します。
  • 再帰によるスタックオーバーフローを防止するため、100 回でエラーになるように停止処理を入れています。

特に、アプリケーションスコープやグループスコープのように、複数のユーザーから共有される領域に書き込む場合は同時更新が発生しやすくなるため、注意が必要です(ユーザースコープでも、1 人が複数の端末で同時ログインした場合は同じ現象が起きます)。同時更新に対して、どこまで対処するかはアプリの仕様次第です。一貫性を保つよう再試行する、一貫性が失われるのを許容する、単純に更新エラーとするなど、複数の選択肢があります。

システムで一意の ID を生成したい場合(Thing の vendorThingID など)、ここでの方法を使用することもできますが、クライアント側で ID を生成する方法もあります。生成した UUID や現在時刻などを ID とし、重複時には再試行するなどの方法も考えられます。