> For the complete documentation index, see [llms.txt](/llms.txt)

# GS2-Datastore

バイナリデータストレージ機能




GS2-Datastore を利用することで、任意のバイナリデータをサーバーに保存できます。

アップロードしたデータにはアクセス権限を設定でき、`public`（全員に公開）、`protected`（指定したユーザーIDにのみ公開：最大100件）、`private`（自分のみ）から選択します。

GS2-Datastore は UGC やレースゲームのゴーストデータのようなデータをアップロードするのが主たる目的で、プレイヤーのユーザーデータを保存するのには必ずしも適切ではありません。

なぜなら、所持品の所持数量をバイナリデータとして保存していると、セーブデータやアプリ本体の改竄でアイテムを増殖したり、入手量を不正に増加させた改造アプリでのプレイを許してしまうためです。
所持品の所持数量であれば、GS2-Inventory のような専用のマイクロサービスを利用することで、そのような不正行為は行えなくなります。

ユーザーデータの中でも、コンフィグの設定値など、改竄されてもゲームバランスに影響を与えることがないデータについて保存することを否定するわけではありません。

## ユースケース

GS2-Datastore が想定する代表的なユースケースは以下の通りです。

- ユーザーが投稿したスクリーンショットやリプレイなどの UGC コンテンツの保存
- レースゲームにおけるゴーストデータの共有
- フォトモードで撮影した画像のクラウド共有
- ゲーム設定など改竄されてもゲームバランスに影響を与えないユーザーデータの保存
- ステージのカスタムマップデータの公開・共有

## アクセススコープ

データオブジェクトには 3 種類のアクセススコープがあり、用途に応じて使い分けます。

| スコープ | 説明 | 主な用途 |
| --- | --- | --- |
| `public` | 全プレイヤーがダウンロード可能 | UGC コンテンツの公開、ゴースト共有 |
| `protected` | `allowUserIds` に指定したユーザーのみがダウンロード可能（最大100件） | フレンドのみへの共有 |
| `private` | アップロードしたユーザー本人のみがダウンロード可能 | 個人のセーブデータ、設定値 |

スコープと許可ユーザーは `UpdateDataObject` で後から変更することも可能です。

## アーキテクチャ

GS2-Datastore はバイナリデータの保存場所などを記録したメタデータを管理しており、実際のバイナリデータの保存には外部のクラウドストレージを利用しています。
そのため、アップロード・ダウンロード処理は複数のステップを必要とします。

この処理の流れは、Game Engine 向けの SDK ではラップした高レベルなAPIが用意されているため、気にする必要はありませんが
各種プログラミング言語向けの SDK には高レベルなAPIは用意されていませんので、利用者自身で複数ステップを処理する必要があります。

### アップロードプロセス

```mermaid
sequenceDiagram
  actor Player as プレイヤー
  participant Namespace as GS2-Datastore#Namespace
  participant Storage as Cloud Storage
  Player->>Namespace: PrepareUpload
  Namespace-->>Player: Cloud Storage URL
  Player->>Storage: Upload Payload
  Storage-->>Player: OK
  Player->>Namespace: DoneUpload
  Namespace->>Storage: Check exists
  Namespace-->>Player: OK
```

### ダウンロードプロセス

```mermaid
sequenceDiagram
  actor Player as プレイヤー
  participant Namespace as GS2-Datastore#Namespace
  participant Storage as Cloud Storage
  Player->>Namespace: PrepareDownload
  Namespace-->>Player: Cloud Storage URL
  Player->>Storage: Download
  Storage-->>Player: Payload
```

### アップロード処理中のダウンロード

GS2-Datastore はすでにアップロードしたデータを更新することができます。
Prepare ReUpload を呼び出してから Done Upload を呼び出すまでの間は、更新前の古いファイルがダウンロード可能な状態となり、中途半端なデータがダウンロードされることはありません。

### アップロードデータの過去バージョンの取得

GS2-Datastore では過去30日分の過去バージョンにアクセスできるようになっています。
データオブジェクトの更新履歴（DataObjectHistory）を取得することで、過去の各世代の世代IDを取得でき、その世代IDを指定してデータをダウンロードすることが可能です。

これは削除済みのデータにも適用されており、削除リクエスト後 30 日後に実際に削除されます。
ただし、法的要件によってデータの削除を行った場合は、この条件に当てはまらないことがあります。

### データサイズとステータス

1つのデータオブジェクトの最大サイズは10MBです。アップロード中は`UPLOADING`、完了すると`ACTIVE`、削除リクエスト後は`DELETED`に遷移します。`DELETED`状態のデータは30日以内であれば`restoreDataObject`で復元できます。

```mermaid
stateDiagram-v2
  [*] --> UPLOADING: PrepareUpload
  UPLOADING --> ACTIVE: DoneUpload
  ACTIVE --> UPLOADING: PrepareReUpload
  ACTIVE --> DELETED: DeleteDataObject
  DELETED --> ACTIVE: RestoreDataObject (30日以内)
  DELETED --> [*]: 30 日経過
```

### データオブジェクトの主な属性

| 属性 | 説明 |
| --- | --- |
| `dataObjectId` | データオブジェクトの一意な ID（GRN）|
| `name` | データオブジェクト名。ユーザーごとに一意 |
| `userId` | アップロードしたユーザーのID |
| `scope` | アクセススコープ (`public` / `protected` / `private`) |
| `allowUserIds` | `protected` スコープで参照を許可するユーザーIDの一覧 |
| `platform` | アップロードに利用されたプラットフォーム情報 |
| `status` | データの状態 (`UPLOADING` / `ACTIVE` / `DELETED`) |
| `generation` | 現在世代の識別子 |
| `previousGeneration` | 1つ前の世代の識別子 |

### スクリプトトリガー

データオブジェクトのアップロード完了報告の前後で GS2-Script を呼び出すイベントトリガーを設定できます。同期実行で完了報告の拒否を行ったり、非同期実行で Amazon EventBridge を利用した外部連携も可能です。

設定できる主なイベントトリガーとスクリプト設定名は以下の通りです。

- `doneUploadScript`（完了通知: `doneUploadDone`）: アップロード完了報告の前後

同期実行のスクリプトでアップロードを拒否することで、画像の中身を別サービスで検査し、不適切なコンテンツであれば登録を拒否するといった運用が可能です。

## 実装例

### データのアップロード

データのアップロードは PrepareUpload → クラウドストレージへの PUT → DoneUpload の 3 ステップで構成されますが、ゲームエンジン向け SDK では `UploadAsync` 1 つの呼び出しで完結します。



**Unity**
```csharp

    var result = await gs2.Datastore.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).UploadAsync(
        name: "dataObject-0001",
        scope: "public",
        data: data
    );

    var item = await result.ModelAsync();
    var dataObjectId = item.DataObjectId;
```
**Unreal Engine**
```cpp

    const auto Domain = Gs2->Datastore->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    );
    const auto Future = Domain->Upload(
        "dataObject-0001", // name
        data, // data
        "public" // scope
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
```


### 既存データの再アップロード

同じ名前のデータオブジェクトに対して新しいバイナリを書き込む際は再アップロードを使用します。
再アップロード中も、`DoneUpload` が呼び出されるまでの間は更新前の古いデータが取得できます。



**Unity**
```csharp

    var domain = await gs2.Datastore.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).DataObject(
        dataObjectName: "dataObject-0001"
    ).ReUploadAsync(
        data: newData
    );
```
**Unreal Engine**
```cpp

    const auto Domain = Gs2->Datastore->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->DataObject(
        "dataObject-0001" // dataObjectName
    );
    const auto Future = Domain->ReUpload(
        NewData
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
```


### データのダウンロード(データオブジェクトID指定)



**Unity**
```csharp

    var binary = await gs2.Datastore.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).DownloadAsync(
        dataObjectId: dataObjectId
    );
```
**Unreal Engine**
```cpp

    const auto Domain = Gs2->Datastore->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    );
    const auto Future = Domain->Download(
        dataObjectId // dataObjectId
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
```


### データのダウンロード(ユーザーIDとデータオブジェクト名を指定)



**Unity**
```csharp

    var binary = await gs2.Datastore.Namespace(
        namespaceName: "namespace-0001"
    ).User(
        userId: "user-0001"
    ).DataObject(
        dataObjectName: "dataObject-0001"
    ).DownloadByUserIdAndDataObjectNameAsync(
    );
```
**Unreal Engine**
```cpp

    const auto Domain = Gs2->Datastore->Namespace(
        "namespace-0001" // namespaceName
    )->User(
        "user-0001" // userId
    )->DataObject(
        "dataObject-0001" // dataObjectName
    );
    const auto Future = Domain->DownloadByUserIdAndDataObjectName(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
```


### 自分がアップロードしたデータのダウンロード(データオブジェクト名を指定)



**Unity**
```csharp

    var binary = await gs2.Datastore.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).DataObject(
        dataObjectName: "dataObject-0001"
    ).DownloadOwnAsync(
    );
```
**Unreal Engine**
```cpp

    const auto Domain = Gs2->Datastore->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->DataObject(
        "dataObject-0001" // dataObjectName
    );
    const auto Future = Domain->DownloadOwn(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
```


### 自分がアップロードしたデータの一覧取得



**Unity**
```csharp

    var items = await gs2.Datastore.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).DataObjectsAsync(
    ).ToListAsync();
```
**Unreal Engine**
```cpp

    const auto It = Gs2->Datastore->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->DataObjects();
    TArray<Gs2::UE5::Datastore::Model::FEzDataObjectPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }
```


### データオブジェクトのアクセススコープを変更

`UpdateDataObject` を利用するとアクセススコープや許可ユーザーリストを後から変更できます。
たとえば、最初は `private` で作成し、共有準備が整ったタイミングで `public` に切り替えるといった運用が可能です。



**Unity**
```csharp

    var domain = await gs2.Datastore.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).DataObject(
        dataObjectName: "dataObject-0001"
    ).UpdateDataObjectAsync(
        scope: "protected",
        allowUserIds: new [] { "user-0002", "user-0003" }
    );
```
**Unreal Engine**
```cpp

    const auto Domain = Gs2->Datastore->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->DataObject(
        "dataObject-0001" // dataObjectName
    );
    const auto Future = Domain->UpdateDataObject(
        "protected", // scope
        { "user-0002", "user-0003" } // allowUserIds
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
```


### データオブジェクトの削除

削除リクエスト後 30 日以内であれば `RestoreDataObject` で復元することができます。



**Unity**
```csharp

    await gs2.Datastore.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).DataObject(
        dataObjectName: "dataObject-0001"
    ).DeleteDataObjectAsync(
    );
```
**Unreal Engine**
```cpp

    const auto Domain = Gs2->Datastore->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->DataObject(
        "dataObject-0001" // dataObjectName
    );
    const auto Future = Domain->DeleteDataObject(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
```


### データオブジェクトの更新履歴の取得

過去の世代を取得することで、ロールバックや過去のリプレイ閲覧などを実現できます。



**Unity**
```csharp

    var items = await gs2.Datastore.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).DataObject(
        dataObjectName: "dataObject-0001"
    ).DataObjectHistoriesAsync(
    ).ToListAsync();
```
**Unreal Engine**
```cpp

    const auto It = Gs2->Datastore->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->DataObject(
        "dataObject-0001" // dataObjectName
    )->DataObjectHistories();
    TArray<Gs2::UE5::Datastore::Model::FEzDataObjectHistoryPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }
```


## 古い世代のデータへのアクセス制限

データをダウンロードする際には世代IDを指定して、具体的なファイルを特定します。世代IDをダウンロードリクエストに付加することで、リストアップした時点でのデータを確実にダウンロードすることを保証できます。

ただし、いつまでも世代の古いデータにアクセスできることが望ましくない場合もあります。そのため、データの所有者以外は、更新後60分以内かつ1つ前の世代に限り古い世代のデータのダウンロードを許可するオプションが存在します。

## 詳細なリファレンス

[GS2-Datastore API リファレンス](../../api_reference/datastore)



