Docs Menu
Docs Home
/ /
Atlas Device SDK
/ /

동기화된 Realm에 데이터 쓰기 - C++ SDK

이 페이지의 내용

  • 동기화할 데이터 확인
  • App Services 구성
  • 클라이언트 Realm 데이터 모델 및 구성
  • 동기화된 Realm에 쓰기
  • 성공적인 쓰기
  • 쓰기 보상
  • 쓰기가 쿼리 구독과 일치하지 않음
  • 쓰기가 권한과 일치하지 않음
  • 쓰기 오류 정보 보상
  • 성능 향상을 위한 그룹 쓰기

Flexible Sync 를 사용하여 동기화된 영역 에 데이터를 쓸 때는 동기화되지 않은 영역 에서 CRUD 작업을 수행할 때와 동일한 API를 사용할 수 있습니다. 그러나 애플리케이션 을 개발할 때 염두에 두어야 할 동작에는 몇 가지 차이점이 있습니다.

동기화된 영역에 쓰는 경우 쓰기 작업이 다음 두 가지 모두 와 일치해야 합니다.

  • 동기화 구독 쿼리

  • App Services App의 권한

쿼리 구독이나 사용자의 권한과 일치하지 않는 데이터를 쓰려고 하면 Realm은 보상 쓰기라는 치명적이지 않은 오류 작업을 통해 쓰기를 되돌립니다.

앱의 권한을 구성하는 방법에 대해 자세히 알아보려면 Atlas App Services 문서에서 역할 기반 권한Device Sync 권한 가이드 를 참조하세요.

권한 거부 오류, 쓰기 오류 보상 및 기타 Device Sync 오류 유형에 대해 자세히 알아보려면 Atlas App Services 문서에서 동기화 오류 를 참조하세요.

동기화된 영역에 쓸 수 있는 데이터는 다음에 따라 결정됩니다.

  • Realm Mobile Sync 구성

  • 앱의 권한

  • 영역을 열 때 사용되는 Flexible Sync 구독 쿼리

이 페이지의 예제에서는 다음 Realm Mobile Sync 구성이 있는 Atlas App Services App과 다음 Realm SDK Realm 데이터 모델 및 구독이 있는 클라이언트 앱을 사용합니다.

이 예제에서 클라이언트 앱은 다음 객체 모델을 사용합니다.

struct Item {
realm::primary_key<realm::object_id> _id{realm::object_id::generate()};
std::string ownerId;
std::string itemName;
int64_t complexity;
};
REALM_SCHEMA(Item, _id, ownerId, itemName, complexity)

위의 예제 객체 모델을 기반으로 Realm Mobile Sync는 다음과 같은 쿼리 가능 필드로 구성됩니다.

  • _id (항상 포함됨)

  • complexity

  • ownerId

App Services App에는 사용자가 자신의 데이터만 읽고 쓸 수 있도록 구성된 권한이 있습니다.

{
"roles": [
{
"name": "readOwnWriteOwn",
"apply_when": {},
"document_filters": {
"write": {
"ownerId": "%%user.id"
},
"read": {
"ownerId": "%%user.id"
}
},
"read": true,
"write": true,
"insert": true,
"delete": true,
"search": true
}
]
}

ownerId 이(가) 로그인한 사용자의 user.identifier() 와(과) 일치하지 않는 Atlas collection 객체는 이 영역과 동기화할 수 없습니다.

이 페이지의 예제에서는 이 동기화 쿼리 및 객체 모델과 함께 다음과 같은 동기화 영역 구성을 사용합니다.

auto appConfig = realm::App::configuration();
appConfig.app_id = APP_ID;
auto app = realm::App(appConfig);
auto user = app.login(realm::App::credentials::anonymous()).get();
auto dbConfig = user.flexible_sync_configuration();
auto syncRealm = realm::db(dbConfig);
// Add subscription
auto subscriptionUpdateSuccess =
syncRealm.subscriptions()
.update([](realm::mutable_sync_subscription_set &subs) {
// Get Items from Atlas that match this query.
// Uses the queryable field `complexity`.
// Sync Item objects with complexity less than or equal to 4.
subs.add<realm::Item>(
"simple items", [](auto &obj) { return obj.complexity <= 4; });
})
.get();
struct Item {
realm::primary_key<realm::object_id> _id{realm::object_id::generate()};
std::string ownerId;
std::string itemName;
int64_t complexity;
};
REALM_SCHEMA(Item, _id, ownerId, itemName, complexity)

이 동기화 쿼리를 사용하면 complexity 속성의 값이 4 보다 큰 Atlas collection 객체를 이 realm에 동기화할 수 없습니다.

Flexible Sync Realm에 대한 쓰기는 쓰기가 권한Flexible Sync 구독 쿼리와 일치하는지 여부에 따라 크게 두 가지 범주 중 하나로 분류될 수 있습니다.

  • 성공적인 쓰기: 기록된 객체가 쿼리 구독 및 사용자의 권한과 모두 일치합니다. 객체가 영역에 성공적으로 쓰고 App Services 백엔드 및 기타 기기와 성공적으로 동기화됩니다.

  • 쓰기 보상: 기록된 객체가 구독 쿼리와 일치하지 않거나 사용자에게 쓰기를 수행할 수 있는 충분한 권한이 없습니다. Realm은 보상 쓰기 작업을 통해 불법적인 쓰기를 되돌립니다.

쿼리 구독과 일치하지 않는 객체를 작성하려는 경우 객체가 쿼리 구독과 일치하는 다른 영역을 열 수 있습니다. 또는 권한이나 구독 쿼리를 적용하지 않는 동기화되지 않은 영역에 객체를 쓸 수 있습니다.

쓰기가 클라이언트의 사용자 권한 쿼리 구독 모두와 일치하면 Realm C++ SDK는 동기화된 Realm에 객체를 성공적으로 쓸 수 있습니다. 이 객체는 장치가 네트워크에 연결되어 있을 때 App Services 백엔드와 동기화됩니다.

// Per the Device Sync permissions, users can only read and write data
// where the `Item.ownerId` property matches their own user ID.
auto simpleItem =
realm::Item{.ownerId = user.identifier(),
.itemName = "This item meets sync criteria",
.complexity = 3};
// `simpleItem` successfully writes to the realm and syncs to Atlas
// because its data matches the subscription query (complexity <= 4)
// and its `ownerId` field matches the user ID.
syncRealm.write([&] { syncRealm.add(std::move(simpleItem)); });

쓰기가 쿼리 구독 이나 사용자 권한과 일치하지 않으면 Realm은 쓰기를 되돌리고 Compensating_write_error_info 객체 배열을 제공합니다.

좀 더 구체적으로 말하자면, 쿼리 구독의 범위를 벗어나거나 사용자의 권한과 일치하지 않는 데이터를 쓰면 다음과 같은 상황이 발생합니다.

  1. 클라이언트 영역에는 '불법적인' 쓰기에 대한 개념이 없으므로 Realm이 App Services 백엔드로 변경 집합을 해결할 때까지 처음에는 쓰기가 성공합니다.

  2. 동기화되면 서버는 규칙과 권한을 적용합니다. 서버는 사용자에게 쓰기를 수행할 수 있는 권한 부여가 없다고 판단합니다.

  3. 서버는 "보상 쓰기(compensating write)"라고 하는 되돌리기 작업을 클라이언트에 다시 보냅니다.

  4. 클라이언트의 영역이 잘못된 쓰기 작업을 되돌립니다.

해당 객체에 대한 불법적인 쓰기와 해당 보상 쓰기 사이에 지정된 객체에 대한 모든 클라이언트 사이드 쓰기는 손실됩니다. 실제로 이는 쓰기가 성공한 것처럼 보일 수 있지만 Realm이 App Services 백엔드와 동기화되고 보상 쓰기를 수행하면 객체가 '사라집니다'.

이 경우 Atlas App Services 로그 를 참조하거나 클라이언트의 compensating_writes_info() 함수를 사용하여 오류에 대한 추가 정보를 얻을 수 있습니다. 자세한 내용은 이 페이지의 쓰기 오류 정보 보상 하기 섹션을 참조하세요.

위에서 설명한 Flexible Sync Realm의 구성 을 감안할 때 이 객체를 쓰려고 하면 객체가 쿼리 구독과 일치하지 않기 때문에 보상 쓰기 오류가 발생합니다.

// The complexity of this item is `7`. This is outside the bounds
// of the subscription query, which triggers a compensating write.
auto complexItem =
realm::Item{._id = primaryKey,
.ownerId = user.identifier(),
.itemName = "Test compensating writes",
.complexity = 7};
// This should trigger a compensating write error when it tries to sync
// due to server-side permissions, which gets logged with the error handler.
syncRealm.write([&] { syncRealm.add(std::move(complexItem)); });
Connection[2]: Session[10]: Received: ERROR "Client attempted a write that is not allowed; it has been reverted" (error_code=231, is_fatal=false, error_action=Warning)

App Services 로그에 다음과 같은 오류 메시지가 표시됩니다.

Error:
Client attempted a write that is not allowed; it has been reverted (ProtocolErrorCode=231)
Details:
{
"Item": {
"ObjectID(\"6557ddb0bf050934870ca0f5\")": "write to ObjectID(\"6557ddb0bf050934870ca0f5\")
in table \"Item\" not allowed; object is outside of
the current query view"
}
}

위에 자세히 설명된 Device Sync 구성의 권한 이 주어졌을 때 이 객체를 쓰려고 하면 ownerId 속성이 로그인한 사용자의 user.identifier() 와 일치하지 않기 때문에 보상 쓰기 오류가 발생합니다.

// The `ownerId` of this item does not match the user ID of the logged-in
// user. The user does not have permissions to make this write, which
// triggers a compensating write.
auto itemWithWrongOwner = realm::Item{
.ownerId = "not the current user",
.itemName = "Trigger an incorrect permissions compensating write",
.complexity = 1};
syncRealm.write([&] { syncRealm.add(std::move(itemWithWrongOwner)); });
Connection[2]: Session[11]: Received: ERROR "Client attempted a write that is not allowed; it has been reverted" (error_code=231, is_fatal=false, error_action=Warning)

App Services 로그에 다음과 같은 오류 메시지가 표시됩니다.

Error:
Client attempted a write that is outside of permissions or query filters; it has been reverted (ProtocolErrorCode=231)
Details:
{
"Item": {
"ObjectID(\"6557ddbabf050934870ca0f8\")": "write to ObjectID(\"6557ddbabf050934870ca0f8\")
in table \"Item\" was denied by write filter in role \"readOwnWriteOwn\""
}
}

다음을 포함하는 compensating_write_error_info 구조체의 배열을 제공하는 Compensating_writes_info() 함수를 사용하여 클라이언트에서 보상 쓰기가 발생하는 이유에 대한 추가 정보를 얻을 수 있습니다.

  • 클라이언트가 쓰려고 시도한 객체의 object_name

  • 특정 객체의 primary_key

  • 보상 쓰기 오류의 경우 reason 입니다.

이 정보는 Atlas App Services 로그 에서 찾을 수 있는 정보와 동일합니다. C++ SDK는 편의와 디버깅 목적으로 이 객체를 클라이언트에 노출합니다.

다음은 쓰기 오류 보상에 대한 정보를 기록하는 방법의 예입니다.

auto info = receivedSyncError.compensating_writes_info();
for (auto &v : info) {
std::cout << "A write was rejected with a compensating write error.\n";
std::cout << "An object of type " << v.object_name << "\n";
std::cout << "was rejected because " << v.reason << ".\n";
}
A write was rejected with a compensating write error.
An object of type Item
was rejected because write to ObjectID("6557ddb0bf050934870ca0f5") in
table "Item" not allowed; object is outside of the current query view.
  • 이 메시지의 Item 는 이 페이지의 객체 모델 에서 사용된 Item 객체입니다.

  • table "Item" 는 이 객체가 동기화될 Atlas collection을 나타냅니다.

  • 이 메시지에 object is outside of the current query view 가 표시되는 이유는 객체의 complexity 속성이 4 보다 작거나 같을 것을 요구하도록 쿼리 구독이 설정되었기 때문입니다. 클라이언트가 이 경계 외부에 객체를 쓰려고 시도했습니다.

  • 기본 키는 클라이언트가 쓰려고 시도한 특정 객체의 objectId 입니다.

구독 세트에 대한 모든 쓰기 트랜잭션(write transaction)에는 성능이 소모됩니다. 세션 중에 영역 객체를 여러 번 업데이트해야 하는 경우 모든 변경이 완료될 때까지 편집한 객체를 메모리에 보관하는 것이 좋습니다. 이렇게 하면 모든 변경 사항 대신 완전하고 업데이트된 객체만 영역에 기록하므로 동기화 성능이 향상됩니다.

돌아가기

동기화 구독 관리