Documentation
# データモデリング

## ER図

```mermaid
erDiagram

users {
  string username "preferredUsername"
  string name "表示名"
  string bio
  string icon

  datetime createdAt
  datetime updatedAt
}

posts {
  string id
  string inReplyTo
  string body

  number likes
  number shares

  SS to
  SS cc

  datetime createdAt
  datetime updatedAt
}

revisions {
  string postId
  string body

  datetime updatedAt
}

liked {
  string objectId

  datetime createdAt
}

shares {
  string objectId

  datetime createdAt
}

replies {
  string postId
  string objectId

  datetime createdAt
}

likes {
  string postId
  string actorId

  datetime createdAt
}

followings {
  string actorId

  datetime createdAt
}

subscribers {
  string actorId

  datetime createdAt
}

timeline {
  string objectId
  datetime when
}

tombstones {
  string objectId
  string formerType

  datetime deletedAt
}

users 1 -- 0+ posts : has
users 1 -- 0+ liked : has
users 1 -- 0+ shares : has
users 1 -- 0+ followings : has
users 1 -- 0+ subscribers : has
users 1 -- 0+ timeline : has

posts 1 -- 0+ likes : has
posts 1 -- 0+ replies : has
posts 1 -- 0+ revisions : has
```

- 便宜上 user**s** として関係も描いているが、存在するuserは1人だけであり、関係は暗黙に存在する
  - usernameは便宜上 `me` で固定する: `@me@{domain}`
- tombstonesは、削除した「独り言」をtombstoneオブジェクトに置き換えるために利用する
  - postsのidを引いて、なかったときにtombstonesを引いて、あればtombstone、なければnot found、とする
- posts のid(またはpostId)には、ローカルなID部分のみを格納する(対外的には `https://{domain}/.../{postId}` のように変換してグローバルなURI(IRI)にする)
- actorId, objectIdには、グローバルなID(URI(IRI))をそのまま格納する
- 「独り言」の編集
  - revisionをもたせる
    - revisionなし → 最新版
    - revisionあり → そのバージョン
  - 編集するときは、現在の「最新版」をrevisionを付けてコピーして、revisionなしの方を更新する

## (Dynamo)DB設計

### メインデータ

|             | PK : S         | SK : S     | ctime : S    | mtime : S  | LSI1 : S   | s1 : S     | s2 : S | s3 : S    | n1 : N | n2 : N  | n3 : N   | ss1 : SS | ss2 : SS |
| ----------- | -------------- | ---------- | ------------ | ---------- | ---------- | ---------- | ------ | --------- | ------ | ------- | -------- | -------- | -------- |
| user(s)     | `ME`           |            | 作成日時     | 更新日時   |            | name       | bio    | icon      |
| posts       | `P#HHHHH`      | UUID v7    | 投稿日時     | (更新日時) |            | summary    | body   | inReplyTo | #likes | #shares | #replies | to       | cc       |
| revisions   | `P#{postId}#V` | revision   | 更新日時     |            |            | summary    | body   |           |
| replies     | `P#{postId}#R` | object URI | された日時   |            |            |            |        |           |        |         |          |          |          |
| likes       | `P#{postId}#L` | actor URI  | された日時   |
| shares      | `P#{postId}#S` | actor URI  | された日時   |            |
| liked       | `L#HHHHH`      | object URI | した日時     |            |
| shared      | `S#HHHHH`      | object URI | した日時     |            |
| followings  | `F`            | actor URI  | した日時     |
| subscribers | `SUB`          | actor URI  | された日時   |
| timeline    | `TL#HHHHH`     | 日時       |              |            | object URI |            |
| tombstones  | `D`            | object URI | 削除した日時 |            |            | formerType |

* `HHHHH`には、UNIX時間を16進数表示した(UUID v7の)上位5文字を含める。[メモ「postsのPK」を参照]#postsのpk
* timelineは基本的に挿入日時でソートすれば良いが、Deleteアクティビティに備えてオブジェクトのURIで検索できるようにLSIを作成する。

### 連合用データ

|         | PK : S           | SK          | expires : N | s1 : S        | s2 : S | json : Map |
| ------- | ---------------- | ----------- | ----------- | ------------- | ------ | ---------- |
| objects | `O#{object URI}` |             | +_max-age_  |               |        | object     |
| servers | `S#{hostname}`   |             | +_max-age_  | lrdd template |
| actors  | `A#{acct}`       |             | +_max-age_  |               |        | actor      |
| actors  | `A#{acct}`       | `pubkey`    | +_max-age_  | publicKeyPem  |        |            |
| actors  | `A#{acct}`       | `webfinger` | +_max-age_  | type          | href   |

## メモ

* foreignObjectsには、actorIdやobjectIdに対応する、他サーバのActorやNoteオブジェクトをキャッシュしておく
  * 参照したときになければ、URI(IRI)にアクセスしてオリジナルを取りに行く
  * Amazon S3やCloudflare R2にJSONをファイルとして置いておくようにしても良さそう

### postsのPK
LSIを入れる場合、パーティションキーの1つの値毎に10 GBまでのデータしか入らない。
そうでなくてもパーティションは分かれるようにするのが良さそう。
非常に大雑把に1投稿あたり1 MBになるとすると、パーティションキーあたり10,000件。

UUID v7なら時系列順にソートできるので、UNIX時間の一部となる先頭何文字かをprefixとしてPKに含めれば、使い勝手をあまり損ねずに分割できそう。

```console
$ printf '%d\n' 0x065400000 | xargs -r -I{} date -d @{}
Tue Oct 31 04:12:00 JST 2023
$ printf '%d\n' 0x065410000 | xargs -r -I{} date -d @{}
Tue Oct 31 22:24:16 JST 2023
$ printf '%d\n' 0x065411b07 | xargs -r -I{} date -d @{}
Wed Nov  1 00:19:35 JST 2023
$ printf '%d\n' 0x065420000 | xargs -r -I{} date -d @{}
Wed Nov  1 16:36:32 JST 2023
$ printf '%d\n' 0x065500000 | xargs -r -I{} date -d @{}
Sun Nov 12 07:28:16 JST 2023
$ printf '%d\n' 0x065600000 | xargs -r -I{} date -d @{}
Fri Nov 24 10:44:32 JST 2023
$ printf '%d\n' 0x066000000 | xargs -r -I{} date -d @{}
Sun Mar 24 19:27:12 JST 2024
```

こんな感じなので、5桁あれば約18時間確保できる。
18時間に10,000件投稿できるので、約6秒あたり1件は投稿できる計算になる。そんなにやらないだろう。

### timelineのPK

timelineはオブジェクトIDだけで良いので1件1 KBとして、パーティションキーあたり10,000,000件。
件数的にはあまり分けなくて良さそうだが、postsと合わせておく。
取得できる時間の範囲を揃えておくと都合が良さそう。