문서 메뉴

문서 홈애플리케이션 개발Atlas Device SDK

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

이 페이지의 내용

  • 개요
  • 동기화할 데이터 확인
  • App Services 구성
  • 클라이언트 Realm 데이터 모델 및 구성
  • 어떤 데이터 동기화?
  • 동기화된 Realm에 쓰기
  • 성공적인 쓰기
  • 쓰기 보상
  • 쓰기 오류 정보 보상
  • 쿼리 구독과 일치하지 않는 쓰기
  • 권한이 일치하지 않는 쓰기
  • 성능 향상을 위한 그룹 쓰기
  • 앱 확장에서 동기화된 Realm에 쓰지 않기
  • 여러 프로세스에서 동기화된 Realm 열기와 관련된 충돌
  • 앱 확장에서 동기화된 Realm에 쓰기에 대한 대안
  • 디스크에 데이터 전달
  • 백업 Atlas Collection과 직접 통신

Flexible Sync를 사용하여 동기화된 영역에 데이터를 쓸 때 로컬 영역에 쓰는 것과 동일한 API를 사용할 수 있습니다. 그러나 애플리케이션을 개발할 때 유의해야 할 동작에는 몇 가지 차이점이 있습니다.

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

  • 동기화 구독 쿼리입니다.
    • 쓰기 작업이 구독의 쿼리와 일치하지 않는 경우 쓰기는 치명적이지 않은 보상 쓰기 오류(ErrorCompensatingWrite)로 되돌아갑니다.

  • App Services 앱 의 권한 입니다.
    • 권한 표현식과 일치하지 않는 데이터를 쓰려고 하면 쓰기가 치명적이지 않은 권한 거부 오류와 함께 되돌아갑니다. 클라이언트에서는 오류(ErrorCompensatingWrite)로 표시됩니다. 서버에서는 역할의 쓰기 필터에 의해 쓰기가 거부된 방법에 대한 자세한 내용을 확인할 수 있습니다.

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

경고

다중 프로세스 동기화는 지원되지 않습니다.

Device Sync는 현재 둘 이상의 프로세스에서 동기화된 Realm을 열거나 쓰기를 지원하지 않습니다. 제안된 대안을 포함한 자세한 내용 은 앱 확장에서 동기화된 Realm에 쓰기 금지를 참조하세요.

동기화된 영역에 쓸 수 있는 데이터는 영역을 열 때 사용하는 Realm Mobile Sync 구성, 권한 및 Flexible Sync 구독 쿼리의 교차점입니다.

이 페이지의 예제에서는 다음 구성 및 모델을 사용합니다.

Realm Mobile Sync는 다음과 같은 쿼리 가능 필드로 구성됩니다.

  • _id (항상 포함됨)

  • complexity

  • ownerId

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

{
"name": "owner-read-write",
"apply_when": {},
"document_filters": {
"read": { "ownerId": "%%user.id" },
"write": { "ownerId": "%%user.id" }
},
"read": true,
"write": true
}

이 페이지의 예제에서는 다음 객체 모델을 사용합니다.

class Item: Object {
@Persisted(primaryKey: true) var _id: ObjectId
@Persisted var ownerId: String
@Persisted var itemName: String
@Persisted var complexity: Int
}

동기화된 영역 구성은 해당 객체 모델을 사용하여 complexity 속성 값이 4 보다 작거나 같은 구독 쿼리와 일치하는 객체를 동기화합니다.

let app = App(id: YOUR_APP_ID_HERE)
do {
let user = try await app.login(credentials: Credentials.anonymous)
do {
var flexSyncConfig = user.flexibleSyncConfiguration()
flexSyncConfig.objectTypes = [Item.self]
let realm = try await Realm(configuration: flexSyncConfig)
let subscriptions = realm.subscriptions
try await subscriptions.update {
subscriptions.append(
QuerySubscription<Item>(name: "simple-items") {
$0.complexity <= 4
})
}
print("Successfully opened realm: \(realm)")
} catch {
print("Failed to open realm: \(error.localizedDescription)")
// handle error
}
} catch {
fatalError("Login failed: \(error.localizedDescription)")
}

권한과 결합된 구독 쿼리는 동기화된 영역이 다음과 같은 객체만 동기화한다는 것을 MEAN합니다.

  • ownerId 는 로그인한 사용자의 user.id 와 일치합니다(권한에 있음).

  • complexity 속성 값이 4 보다 작거나 같음(구독 쿼리에서)

ownerId 이 로그인한 사용자의 user.id 와 일치하지 않거나 complexity 속성 값이 4 보다 큰 Atlas collection 객체는 이 영역에 동기화할 수 없습니다.

Flexible Sync Realm에 대한 쓰기는 크게 두 가지 범주 중 하나로 분류될 수 있습니다.

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

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

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

// This write falls within the subscription query and complies
// with the Device Sync permissions, so this write should succeed.
do {
let learnRealm = Item()
learnRealm.ownerId = user.id
learnRealm.itemName = "Learn Realm CRUD stuff"
learnRealm.complexity = 3
try realm.write {
realm.add(learnRealm)
}
} catch {
print("Failed to write to realm: \(error.localizedDescription)")
}

경우에 따라서는 처음에는 성공한 것처럼 보이는 쓰기 작업이 실제로는 불법적인 쓰기 작업인 경우도 있습니다. 이 경우 객체는 데이터베이스에 쓰지만 데이터베이스가 백엔드와 동기화되면 Realm은 보상 쓰기라는 치명적이지 않은 오류 작업을 통해 쓰기를 되돌립니다. 보상 쓰기는 다음과 같은 경우에 발생할 수 있습니다.

  • 쓰기가 쿼리 구독과 일치하지 않음: 기록된 객체가 사용자의 권한과 일치하지만 쿼리 구독과 일치하지 않습니다.

  • 쓰기가 권한과 일치하지 않음: 기록된 객체가 쿼리 구독과 일치하지만 사용자의 권한과 일치하지 않습니다.

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

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

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

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

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

해당 객체에 대한 불법적인 쓰기와 해당 보상 쓰기 사이에 지정된 객체에 대한 모든 클라이언트 사이드 쓰기는 손실됩니다.

실제로 이는 객체가 영역에 기록된 후 서버가 보상 쓰기를 클라이언트에 다시 보낸 후에 사라지는 것처럼 보일 수 있습니다.

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

App Services 로그 에는 보상 쓰기 오류가 발생하는 이유에 대한 자세한 정보가 포함되어 있습니다.

버전 10.37.0의 새로운 기능.

클라이언트에서 보상 쓰기가 발생하는 이유에 대한 추가 정보를 얻을 수 있습니다. Swift SDK는 코드가 .writeRejected 인 SyncError에 CompensatingWriteInfo 필드를 노출합니다. 동기화 오류 처리기를 통해 이 정보에 액세스할 수 있습니다.

이 필드에는 다음을 제공하는 RLMCompensatingWriteInfo 객체 배열이 포함되어 있습니다.

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

  • 특정 객체의 primaryKey

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

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

예제

이 오류 핸들러는 쓰기 오류 보상에 대한 정보를 기록하는 방법의 예를 보여줍니다.

myApp.syncManager.errorHandler = { syncError, session in
if let thisError = syncError as? SyncError {
switch thisError.code {
// ... additional SyncError.code cases ...
case .writeRejected:
if let compensatingWriteErrorInfo = thisError.compensatingWriteInfo {
for anError in compensatingWriteErrorInfo {
print("A write was rejected with a compensating write error")
print("The write to object type: \(anError.objectType)")
print("With primary key of: \(anError.primaryKey)")
print("Was rejected because: \(anError.reason)")
}
}
}
}
}

위의 예제 오류 핸들러는 보상 쓰기 오류가 발생할 때 다음 출력을 생성합니다.

A write was rejected with a compensating write error
The write to object type: Optional("Item")
With primary key of: objectId(641382026852d9220b2e2bbf)
Was rejected because: Optional("write to \"641382026852d9220b2e2bbf\" in table \"Item\" not
allowed; object is outside of the current query view")
  • Optional("Item") 이 메시지의 는 Item Swift Template 앱에서 사용되는 객체입니다.

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

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

  • 이 예제에서 object is outside of the current query view 이 발생한 이유는 쿼리 구독이 객체의 isComplete 속성이 true 이어야 하도록 설정되어 있고 클라이언트가 이 속성이 false 인 객체를 작성하려고 시도했기 때문입니다.

구독 쿼리와 일치하는 경우에만 Flexible Sync 영역에 객체를 쓸 수 있습니다. 구독 쿼리와 일치하지 않는 쓰기를 수행하면 Realm은 처음에 객체를 쓰지만 보상 쓰기를 수행합니다. 이는 구독 쿼리와 일치하지 않는 불법적인 쓰기를 되돌리는 치명적이지 않은 작업입니다.

실제로 이는 쓰기가 성공한 것처럼 보일 수 있지만 Realm이 App Services 백엔드와 동기화되고 보상 쓰기를 수행하면 객체가 '사라집니다'.

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

위의 Flexible Sync Realm 구성을 고려할 때 이 객체를 작성하려는 시도는 쿼리 구독과 일치하지 않습니다.

do {
let fixTheBug = Item()
fixTheBug.ownerId = user.id
fixTheBug.itemName = "Fix the bug with the failing method"
// The complexity of this item is `7`. This is outside the bounds
// of the subscription query, so this write triggers a compensating write.
fixTheBug.complexity = 7
try realm.write {
realm.add(fixTheBug)
}
} catch {
print("Failed to write to realm: \(error.localizedDescription)")
}

이 시나리오에서 클라이언트 사이드 로그의 오류 메시지는 다음과 같습니다.

Sync: Connection[1]: Session[1]: Received: ERROR "Client attempted a
write that is outside of permissions or query filters; it has been
reverted" (error_code=231, try_again=true, error_action=Warning)

이 시나리오에서 App Services 로그의 오류 메시지는 다음과 같습니다.

"FlexibleSync_Item": {
"63bdfc40f16be7b1e8c7e4b7": "write to \"63bdfc40f16be7b1e8c7e4b7\"
in table \"FlexibleSync_Item\" not allowed; object is outside of
the current query view"
}

클라이언트에 쓰기를 시도하면 객체가 사용자의 서버 측 쓰기 권한과 일치하지 않을 때 보상 쓰기 오류를 trigger할 수도 있습니다.

클라이언트에서 이러한 유형의 쓰기는 쿼리 구독과 일치하지 않는 쓰기와 동일하게 작동합니다. 실제로 이는 쓰기가 성공한 것처럼 보일 수 있지만 Realm이 App Services 백엔드와 동기화되고 보상 쓰기를 수행하면 객체가 '사라집니다'.

위에서 자세히 설명한 Realm Mobile Sync 구성의 권한이 주어지면 ownerId 속성이 로그인한 사용자의 user.id 와 일치하지 않는 객체를 작성하려고 시도하는 것은 합법적인 쓰기가 아닙니다.

do {
let itemWithWrongOwner = Item()
// 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, so
// it triggers a compensating write.
itemWithWrongOwner.ownerId = "This string does not match the user.id"
itemWithWrongOwner.itemName = "Write code that generates a permission error"
itemWithWrongOwner.complexity = 1
try realm.write {
realm.add(itemWithWrongOwner)
}
} catch {
print("Failed to write to realm: \(error.localizedDescription)")
}

이 시나리오의 클라이언트 오류는 쿼리 필터 외부에 있는 객체를 쓰려고 할 때 발생하는 오류와 동일합니다.

Sync: Connection[1]: Session[1]: Received: ERROR "Client attempted a
write that is outside of permissions or query filters; it has been
reverted" (error_code=231, try_again=true, error_action=Warning)

App Services 로그의 오류 메시지는 쿼리 구독 문제가 아니라 권한 문제임을 확인하는 데 도움이 되는 몇 가지 추가 정보를 제공합니다. 이 예에서 오류 메시지는 객체가 사용자의 역할과 일치하지 않음을 보여줍니다.

"FlexibleSync_Item": {
"63bdfc40f16be7b1e8c7e4b8": "write to \"63bdfc40f16be7b1e8c7e4b8\"
in table \"FlexibleSync_Item\" was denied by write filter in role
\"owner-read-write\""
}

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

Share Extension과 같은 앱 확장을 사용하는 앱을 개발하는 경우 해당 확장의 동기화된 영역에 쓰지 않도록 합니다. Realm Mobile Sync는 최대 하나의 프로세스에서 동기화된 영역 열기를 지원합니다. 실제로 앱이 앱 확장에서 동기화된 영역을 사용하는 경우 간헐적으로 충돌이 발생할 수 있습니다.

공유 확장 프로그램 또는 다른 앱 확장에서 동기화된 영역을 열려고 할 때 해당 영역이 메인 앱에 열려 있지 않은 경우 공유 확장 프로그램에서 쓰기가 성공할 수 있습니다. 그러나 동기화된 영역이 기본 앱에 이미 열려 있거나 백그라운드에서 데이터를 동기화하는 경우 Realm::MultiSyncAgents 과 관련된 충돌이 발생할 수 있습니다. 이 시나리오에서는 장치를 다시 시작해야 할 수 있습니다.

앱 확장에서 동기화된 영역에서 읽거나 동기화된 영역에 쓰기를 수행해야 하는 경우 몇 가지 권장되는 대안이 있습니다.

  • 오프라인 우선: 디스크의 데이터를 메인 앱과 주고 받습니다.

  • 항상 최신 상태 유지: 네트워크 연결을 통해 지원 Atlas collection과 직접 통신

오프라인 우선 기능이 앱에서 가장 중요한 고려 사항인 경우 디스크의 데이터를 메인 앱과 주고 받을 수 있습니다. 객체를 동기화되지 않은 영역에 복사하여 앱 그룹의 앱 간에 읽고 공유할 수 있습니다. 또는 온디스크 대기열을 사용하여 메인 앱과 데이터를 주고받으면서 동기화된 영역에만 쓸 수 있습니다. 그러면 장치의 네트워크 연결에 관계없이 App Extension과 언제든지 정보를 공유할 수 있습니다.

모든 기기에서 항상 정보를 최신 상태로 유지하는 것이 앱에서 가장 중요한 고려 사항인 경우, 네트워크를 통해 지원 Atlas collection에서 직접 데이터를 읽거나 쓸 수 있습니다. 필요에 따라 다음 도구 중 하나를 사용하여 Atlas와 직접 통신할 수 있습니다.

그러면 네트워크에 연결된 모든 장치는 위의 옵션과 같이 사용자가 메인 앱을 열 때까지 기다리지 않고 항상 최신 정보를 얻을 수 있습니다.

이 옵션을 사용하려면 앱 확장을 사용할 때 사용자의 장치가 네트워크에 연결되어 있어야 합니다. 이에 대한 대안으로 네트워크 연결을 확인할 수 있습니다. 그런 다음 사용자의 장치가 네트워크에 연결되지 않은 경우 위의 디스크 옵션을 사용합니다.

← 유연한 동기화 구독 관리 - Swift SDK