Misskey API の使い方

Misskey API の基本的な使い方をまとめておきます。
他の人のノートを取得したくて調べてみました。
ちなみに、`userId` で沼って意外と時間がかかってしまいました。

公式サイトの説明が一番わかり易い。
misskey-hub.net

トークンの取得以外は単体で完結しています。

トークンの取得

「設定>API」からトークンを取得できる。
こういう記事もある。
Misskeyのアクセストークン発行方法 | BaskMedia

メタデータの取得

`host`にはホスト名を、`token`には先程取得したトークンを代入する。

host = ""
token = ""
headers = {'Content-Type': 'application/json'}
url = f"https://{host}/api"

body = {
"i" : token,
"detail": False,
}
r = requests.post(f"{url}/meta", headers=headers, json=body)
print(r.text)

ノートの作成

`host`にはホスト名を、`token`には先程取得したトークンを代入する。

host = ""
token = ""
headers = {'Content-Type': 'application/json'}
url = f"https://{host}/api"

text = "Hello, World!"
body = {
    "i" : token,
    "visibility": "home",
    "text": text,
}
r = requests.post(f"{url}/notes/create", headers=headers, json=body)

ノートの検索

`host` にはホスト名を、`token` には先程取得したトークンを代入する。
`user_id` にはユーザーID を入れる。
自分のユーザーID は、「設定>その他>アカウント情報」から見ることができる。
他の人のものは、検索タブでその人を指定して検索を行い、開発者ツールのネットワークから「search」を探して、リクエストの body を見るとわかる。

host = ""
token = ""
headers = {'Content-Type': 'application/json'}
url = f"https://{host}/api"

user_id = ""
body = {
    "i" : token,
    "limit": 10,
    "query": "",
    "userId": user_id, 
}
r = requests.post(f"{url}/notes/search", headers=headers, json=body)
print(r.text)

Raspberry Pi クラスター

Raspberry Pi を集めて、Kubernetes クラスターを作った。

コンテナランタイムは cri-o を、CNI は Flannel を利用した。

後述の参考のコピペがほとんど。

sudo がついてたりついてなかったりする。

以下の Ansible を見ればわかる。そのうち変わってるかも。

https://github.com/kamojiro/study/tree/main/raspberry_pi_cluster/ansible

基本的にはこれを書き下したものになるはず。

基本情報

ハードウェア

ソフトウェア

  • OS

- Ubuntu 22.04.1 LTS

IP アドレスとか

raspberry pi host role IP address MAC address disk
4B k8s-master master 192.168.10.101 e4:5f:01:e2:59:e4
k8s-worker1 worker 192.168.10.102 e4:5f:01:e2:59:14 128GB
k8s-worker2 worker 192.168.10.103 e4:5f:01:e2:5a:1e

rpi-imager

これを見ながらいい感じにやる。公開鍵も登録できて楽。

Raspberry Pi で【リアル☆Kubernetes】を作る!!

ネットワーク関連の設定

やらなくていいかも(よくわからずにやった)

/etc/netplan/99-network.yaml

network:
  version: 2
  renderer: networkd
  ethernets:
    eth0:
      dhcp4: false
      dhcp6: false
      addresses:
        - 192.168.1.101/24 # ラズパイ毎でIPを変更してください。(末尾がそれぞれ101, 102, 103になります。)
      routes: # 自宅のルーターからゲートウェイを調べて下さい。ここを間違えるとラズパイはネットに繋がらなくなります…
        - to: default
          via: 192.168.10.1
      nameservers:
        addresses:
          - 192.168.10.1
sudo netplan apply

ホスト名の変更

@raspberry_pi

ラズパイのOSインストール時に指定したので以下のコマンドは不要

sudo hostnamectl set-hostname <hostname>
hostname

DNS的なののために、etc/hosts に追記

192.168.1.101   k8s-master kube-master.kamoj.com
192.168.1.102   k8s-worker1 kube-master1.kamoj.com
192.168.1.103   k8s-worker2 kube-worker2.kamoj.com

IPv6 の無効化

etc/sysctl.conf の最後に追記

net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.eth0.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1

timezone, keymap の変更

今回は OS インストール時に設定したので不要

# タイムゾーンの変更
sudo timedatectl set-timezone Asia/Tokyo

# keymapの変更
sudo localectl set-keymap jp106

ルーターから固定IPアドレスを設定する

NECルーターなら以下のように固定 IP を割り当てることができる

ssh

今回は OS インストール時に設定したので不要

iptablesがブリッジを通過するトラフィックを処理できるようにする

lsmod | grep br_netfilter
sudo modprobe br_netfilter
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sysctl --system

cri-o

カーネルパラメーターの設定

sudo modprobe overlay

# 必要なカーネルパラメータの設定をします。これらの設定値は再起動後も永続化されます。
cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf
net.bridge.bridge-nf-call-iptables  = 1
net.ipv4.ip_forward                 = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF
sudo sysctl --system

準備

sudo apt install -y curl gnupg2 libseccomp2

cri-o のインストール

OS=xUbuntu_22.04
VERSION=1.25
echo "deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/ /" > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
echo "deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/$VERSION/$OS/ /" > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable:cri-o:$VERSION.list

mkdir -p /usr/share/keyrings

curl -L https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable:cri-o:$VERSION/$OS/Release.key | apt-key add -
curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/Release.key | apt-key add -

apt update
apt install -y cri-o
apt install -y cri-o-runc

cri-o と cri-o-runc を並べてインストールしようと失敗する(?)

```bash
sudo systemctl daemon-reload
sudo systemctl start crio
sudo systemctl enable crio
systemctl status crio

コントロールプレーンノードの kubelet によって使用される cgroup ドライバーの設定

CRI の場合は、/etc/default/kubelet

KUBELET_EXTRA_ARGS=--cgroup-driver=systemd
sudo systemctl daemon-reload
sudo systemctl restart kubelet

おまじない

再起動後とか、うまくいかないときはこれを再実行してみる。

sudo modprobe br_netfilter
echo '1' > /proc/sys/net/ipv4/ip_forward

Kubernetes

kubeadmのインストール | Kubernetes

requirements

  • OS: Ubuntu 16.04+
  • memory >= 2GB per machine
  • core >= 2 per machine
  • クラスター内のすべてのマシン間で通信可能なネットワーク
  • ユニークなhostname、MACアドレス、とproduct_uuid
  • port: private なのでOK
  • swap: off

VXLAN モジュールのインストール

Flannel で使うので。

sudo apt install -y linux-modules-extra-raspi

Flannel のデプロイ前までに再起動が必要。

cgroup で memory の有効化(よくわからない)

/boot/firmware/cmdline.txt の末尾に cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory を追記

おまじない

echo '1' > /proc/sys/net/ipv4/ip_forward

swap の無効化

sudo swapoff -a

iptables が nftables バックエンドを使用しないようにする

sudo apt install -y iptables arptables ebtables
# レガシーバイナリがインストールされていることを確認してください
sudo apt install -y iptables arptables ebtables

# レガシーバージョンに切り替えてください。
sudo update-alternatives --set iptables /usr/sbin/iptables-legacy
sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
sudo update-alternatives --set arptables /usr/sbin/arptables-legacy
sudo update-alternatives --set ebtables /usr/sbin/ebtables-legacy

準備

sudo apt install -y apt-transport-https curl

kubeadm、kubelet、kubectlのインストール

sudo apt update && sudo apt install -y apt-transport-https curl
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF
sudo apt update
sudo apt install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl

Kubernetes クラスターの作成

コントロールプレーンの設定@master

sudo kubeadm init --apiserver-advertise-address=192.168.10.101 --pod-network-cidr=10.244.0.0/16
  • apiserver-advertise-address
    • このオプションを利用して明示的にAPIサーバーのadvertise addressを設定します。
    • 明示的に指定しない場合はデフォルトゲートウェ 佐野なつイに関連付けられたネットワークインターフェースを使用して設定されます。
  • pod-network-cidr
    • Flannelを使用する場合、こちらを指定する必要があります。
    • Flannelはノード間をつなぐネットワークに仮想的なトンネルを構成することで、クラスター内のPod同士の通信を実現しています。
    • /16と広めに設定します(GitHub - flannel-io/flannel)。

初期化後、kubeadm join 192.168.1.101:6443 --token ...という出力が出たら、どこかのテキストエディタにコピーしておきます。
このコマンドはワーカーノードを追加する際に利用します。

kubectl の設定

# ホームディレクトリに.kubeディレクトリを作成
mkdir -p ~/.kube
# Kubernetesのadmin.confを.kubeディレクトリのconfigファイルへコピー
sudo cp -i /etc/kubernetes/admin.conf ~/.kube/config
# configファイルの所有者がrootになっているのでk8suserへ変更
sudo chown $(id -u):$(id -g) ~/.kube/config
# .bashrcへ環境変数の追加
echo 'KUBECONFIG=$HOME/.kube/config' >> ~/.bashrc
# コマンドの入力補完を設定
echo "source <(kubectl completion bash)" >> $HOME/.bashrc
# 変更を適用
source ~/.bashrc

Flannel のデプロイ@master

Flannel をデプロイするまでは、coredns は pending 状態になっている。

以下は、やってなかったらやっておく

sudo apt install -y linux-modules-extra-raspi
sudo reboot
kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml
kubectl get pods -n kube-flannel

MetalLB のデプロイ

IaaS だと、type: LoadBalancer で IP アドレスを払い出してくれる。
そんな感じで、IP アドレスの自動割当をやってくる。

kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.7/config/manifests/metallb-native.yaml
# ...

kubectl create secret generic -n metallb-system memberlist --from-literal=secretkey="$(openssl rand -base64 128)"
kubectl get pod -n metallb-system

ワーカーノードの追加

modprobe br_netfilter
sudo kubeadm join 192.168.1.101:6443 --token 3u2z7v.qx81p3azvu15ftzw --discovery-token-ca-cert-hash sha256:0e731a79605ba03cfbc823e86e8b81b2fcdcf7f1887c1d2d86239ed87fff8d04
kubectl get nodes
kubectl label node k8s-worker1 node-role.kubernetes.io/worker=worker
kubectl label node k8s-worker2 node-role.kubernetes.io/worker=worker
kubectl get nodes

確認

$ kubectl get nodes
NAME          STATUS   ROLES           AGE   VERSION
k8s-master    Ready    control-plane   46h   v1.26.0
k8s-worker1   Ready    worker          33m   v1.26.0
k8s-worker2   Ready    worker          19m   v1.26.0
$ kubectl get pods --all-namespaces 
NAMESPACE        NAME                                 READY   STATUS    RESTARTS       AGE
kube-flannel     kube-flannel-ds-dm67n                1/1     Running   0              33m
kube-flannel     kube-flannel-ds-hj2zl                1/1     Running   516            45h
kube-flannel     kube-flannel-ds-kx9c8                1/1     Running   0              20m
kube-system      coredns-787d4945fb-dhmzs             1/1     Running   0              46h
kube-system      coredns-787d4945fb-zjp28             1/1     Running   0              46h
kube-system      etcd-k8s-master                      1/1     Running   1              46h
kube-system      kube-apiserver-k8s-master            1/1     Running   1              46h
kube-system      kube-controller-manager-k8s-master   1/1     Running   1              46h
kube-system      kube-proxy-5hxm7                     1/1     Running   1              46h
kube-system      kube-proxy-8dtn8                     1/1     Running   0              20m
kube-system      kube-proxy-xfrd2                     1/1     Running   0              33m
kube-system      kube-scheduler-k8s-master            1/1     Running   1              46h
metallb-system   controller-577b5bdfcc-ql2fb          1/1     Running   1 (31m ago)    4h28m
metallb-system   speaker-j5shp                        1/1     Running   0              19m
metallb-system   speaker-lfvxc                        1/1     Running   0              32m
metallb-system   speaker-ttxpb                        1/1     Running   2 (101m ago)   4h28m

おまけ

Kubernetes クラスターの削除

sudo kubeadm reset && rm ~/.kube/config && rm -r /etc/cni/net.d && sudo iptables -F && sudo iptables -t nat -F && sudo iptables -t mangle -F && sudo iptables -X
sudo kubeadm init --apiserver-advertise-address=192.168.10.101 --pod-network-cidr=10.244.0.0/16
sudo cp -i /etc/kubernetes/admin.conf ~/.kube/config && sudo chown $(id -u):$(id -g) ~/.kube/config

gRPC

下書きに残っていて書く気は無いけど、もったいないので出しておく。

gRPCの備忘録

gRPC の g は、多分 google の g。
google が開発したものは g を付けがちなので。

概要

google に開発されたハイパフォーマンスな RPC のプロトコルです。
高速かつ HTTP/2 の機能を利用した双方向通信なども利用でき、インターフェース定義言語(Protocol Buffers)をベースに、複数言語のクライアントモジュールとサーバーサイドモジュールを生成できます。

gRPC

仕組み

Protocol Buffers から、クライアントモジュールとサーバーサイドモジュールを生成する。(protoc)
以下の図にある、gRPC サーバーや gRPC Stub は単一の Protocol Buffers から生成されています。
C++Ruby など様々な言語のモジュールを自動生成します。

引用元:https://www.grpc.io/docs/what-is-grpc/introduction/

特徴

  • 高速な通信
  • 複数の通信方法
  • 単一の Protocol Buffers から複数言語のモジュールを生成

高速な通信

複数の通信方法

HTTP/1.1 までで利用されてきた、GET、POST などのメソッドは、1つのリクエストに対して、1つのレスポンスを返すという方式でした。
HTTP/2 では、ストリームという機能により、複数のリクエスト、レスポンスを同時に処理することができるようになりました。
gRPC には、以下の4つのサービスメソッドがあります。

  • unary RPC

- リクエスト 1 : レスポンス 1

  • server streaming RPC

- リクエスト 1 : レスポンス 多

  • client streaming RPC

- リクエスト 多 : レスポンス 1

  • bidirectional streaming RPC

- リクエスト 多 : レスポンス 多

単一の Protocol Buffers から複数言語のモジュールを生成

gRPC では、Protocol Buffers というインターフェース定義言語を用いてサービスを定義しています。
これを基に各言語のモジュールを自動生成します。
多くの場合、各言語のビルドツールなどが依存関係などを考慮して自動でダウンロードしてくれます。
たまにその機能を実装していないツールもあります。

Rust MongoDB Driver

Rust MongoDB Driver

Rust の MongoDB Driver を少しやったので。
Rust よくわからないので結構詰まった。
MongoDB Driver のバージョンは、2.x です。

ソースコード

サンプルコード

use mongodb::bson::{doc};
use mongodb::options::{FindOptions,ClientOptions};
use mongodb::{Client, Collection};
use tokio;
use crate::CustomError::NotFound;
use futures::stream::StreamExt;
use serde::{Serialize,Deserialize};
use derive_more::{Display, Error};
const DB_NAME: &str = "todo_list";
const COLLECTION_NAME: &str = "todos";

#[derive(Debug, Serialize, Deserialize)]
pub struct Todo {
    // #[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
    pub name: String,
}

#[derive(Clone, Debug)]
pub struct MongoDbClient {
    client: Client,
}

impl MongoDbClient{
    pub async fn new(mongodb_uri: String) -> Self {
        println!("{}", mongodb_uri);
        let mut client_options = ClientOptions::parse(mongodb_uri).await.unwrap();
        println!("bbb");
        client_options.app_name = Some("My App".to_string());
        let client = Client::with_options(client_options).expect("failed to create client");
        for db_name in client.list_database_names(None, None).await.expect("failed to get database list") {
            println!("{}", db_name);
        }
        MongoDbClient{
            client: client,
        }
    }

    pub async fn get_all_todos(&self) -> Result<Vec<Todo>>{
        let mut result = Vec::new();
        let collection = self.get_todos_collection().await;
        let filter = doc!{};
        let find_options = FindOptions::builder().sort(doc! {"name": 1}).build();
        let mut todos = collection.find(filter, find_options).await?;
        while let Some(todo) = todos.next().await {
            result.push(todo?)
        }
        Ok(result)
    }

    pub async fn create_todo(&self, todo: Todo) -> Result<Todo>{
        let collection = self.get_todos_collection().await;
        let insert_result = collection.insert_one(todo, None).await?;
        let filter = doc!{"_id": &insert_result.inserted_id};
        collection.find_one(filter, None).await?.ok_or(NotFound {
            message: String::from("Can't find a created todo")
        })
    }

    pub async fn get_todos_collection(&self) -> Collection<Todo>{
        self.client
            .database(DB_NAME)
            .collection::<Todo>(COLLECTION_NAME)
    }

}

#[derive(Debug, Display, Error)]
pub enum CustomError {
    #[display(fmt = message)]
    MongoDbError {
        message: String,
    },
    #[display(fmt = message)]
    NotFound {
        message: String,
    },
}

impl From<mongodb::error::Error> for CustomError {
    fn from(source: mongodb::error::Error) -> Self {
        Self::MongoDbError {
            message: source.to_string(),
        }
    }
}

impl From<mongodb::bson::oid::Error> for CustomError {
    fn from(source: mongodb::bson::oid::Error) -> Self {
        Self::NotFound {
            message: source.to_string(),
        }
    }
}


#[allow(dead_code)]
type Result<T> = std::result::Result<T,CustomError>;

#[tokio::main]
async fn main(){
    // let _ = _connecting().await;
    let mongodb_uri = "mongodb://localhost:27017".to_string();
    // let _ = _connecting().await;
    let mongodb_client = MongoDbClient::new(mongodb_uri).await;
    let todo_list = match mongodb_client.get_all_todos().await{
        Ok(x) => x,
        Err(_) => Vec::new(),
    };
    eprintln!("zz {}", todo_list.len());
    for x in todo_list{
        println!("{}", x.name)
    }
    let todo = Todo{
        name: "hogeee".to_string(),
    };
    let _ = mongodb_client.create_todo(todo).await;

}

Cargo.toml

[package]
name = "mongodb"
version = "0.1.0"
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
mongodb = "*"
tokio = "*"
serde = "*"
derive_more = "*"
futures = "*"


grundy数とNim

典型90の031で困ったので。

031 - VS AtCoder(★6)
https://atcoder.jp/contests/typical90/tasks/typical90_ae

納得できなかったので、簡単に証明をあてたり補足したり。

grundy

いろいろと端折ってDAGから初めます。
詳細は、以下などを参照ください。
Grundy数(Nim数, Nimber)の理論

 V = \{0, 1, \ldots, n-1\} を頂点集合とし、 E を辺集合とします。
ここで、 E の元は頂点  i から  j への辺を、 (i, j) で表すものとします。
また、grundy[i] で頂点  igrundy 数を表すことにします。
また、 {\rm mex} により、集合に含まれない最小の非負整数を表すものとします。

grundy 数が  0 で後手必勝、 0 より大きい値で先手必勝とします。

grundy数の性質

1.  grundy[i] = 0 は後手必勝
2.  grundy[i] = x = {\rm mex}(\{j | (i,j) \in E\})
3.  grundy[i] = x である場合、任意の  p (0 \geq p < x) に対して、 grundy[j] = x なる頂点  j への辺が存在する(遷移できる)

考察1

 grundy[i] = 0 のとき、 (i, j) \in E なる任意の  j に対して、grundy[j] >0 が成立する。

性質2を書き下した。

考察2

 grundy[i] = x のとき、 (i, j) \in E なる任意の  j に対して、grundy[j] \neq x が成立する。
さらに、各  p (0 \geq p < x) に対して、 (i, j_p) \in E なる  j_p を選んで、 grundy[j_p] = p となるようにできる。

性質 2 と 3 を書き下した。

Nim

Nim の後手必勝の条件は、 x_0 \ xor\  \cdots \ xor \ x_{n-1} = 0 であることです。
(以下、この条件を  {\rm Xor} \  x_i = 0 のように表します。)
ここで、 x_i は、grundy 数です。
これを証明します。

方針

競技プログラミングでよく出てくるゲームでは相手にある状態(負けの状態)を押し付け続けることが必勝法になります。

証明

相手に、 {\rm Xor} \  x_i = 0 を押し付け続けることができれば勝利できます。
特に、すべての  i に対して、 x_i = 0 であれば、 {\rm Xor} \  x_i = 0 であるため、この状態であれば負けています。

あとは、自分の手番において  {\rm Xor} \  x_i \neq 0 であれば、相手に押し付け続けることができることを示せば十分です。
これは、grundy数の性質の 3. から grundy数をそれより小さい、任意の数字にできることから分かります。
また、 xor の性質から、 {\rm Xor} \  x_i = 0 に操作を行ったとき、必ず  {\rm Xor} \  x_i \neq 0 となります。

補足

 grundy[i] = x でも、ある  y > x と頂点  j が存在して、 (i, j) \in E と、 grundy[j] = y となることがあります。
これは、 grundy[i] = {\rm mex}(\{0, 1, 3,4\}) = 2 などの場合に発生します。

なぜこれを考える必要がないのか?がこの補足です。
簡単に言うと、利点がないため必勝法において利用されることがないためです。

grundy数の性質 3. から、grundy数が大きくなるほど(元の選択肢を包含する形で)選択肢が増えます。

例えば、grundy数が 2 のときは、grundy数が 0、1 になる頂点に移動できます。
grundy数が 4 のときは、grundy数が 0、1、2、3 になる頂点に移動できます。
これは、grundy数が 2 の場合の移動先をすべて含んでいます。

相手の選択肢を増やす操作に利点がありません。
特に、grundy数はmexで十分意味を持つことも分かります。

MongoDB

MongoDB

MongoDB はドキュメントデータベースです。
The most popular database for modern apps | MongoDB

ドキュメントデータベースは、JSON のような形式でデータを保存するデータベースです。
感覚的には、キーバリューストアのバリュー部分に JSON を利用できるデータベースです。

MongoDB では、JSON を拡張した BSON(Binary JSON)を利用しています。

f:id:kamojirobrothers:20210506155947p:plain
引用元:https://docs.mongodb.com/manual/core/data-modeling-introduction/

データ形式の変更が多いゲームアプリケーションや、様々なデータ形式で出力されるログなどの格納先として利用されます。

余談ですが、MongoDB のドキュメントは様々な情報が丁寧に記載されており、読みやすくかつ非常に読み応えがあります。
The MongoDB 4.4 Manual — MongoDB Manual

RDBMS とよく比較される NoSQL です。
お気持ちとしては、データベースのスキーマをデータベースで決めるのが RDBMS で、ドキュメントストアはアプリケーションで設計します。
どっちが大変かくらいの差のような気がしてます。
基本的には、RDBMS を使うのが良さそうな気がしています。
システムが超大規模になるとか、ユースケースにめっちゃ合ってるとかなら、MongoDB を利用するのもありみたいです。
system-design-primer/README-ja.md at master · donnemartin/system-design-primer · GitHub

というか、大規模な場合だとシャーディング構成になるけど、シャーディングのバックアップ/リストアって有料版の機能なんですよね。
つまり、そういうこと。

MongoDB の特徴

前述のように、ドキュメントストアであることが大きな特徴です。
ドキュメントストアですと、Apache CouchDB もありますが、こちらは MongoDB よりもシンプルなものになっています。
MongoDB は何でもできるデータベースという感じです。

アーキテクチャ

BSON をドキュメントと呼び、ドキュメントの集まりをコレクションといいます。
コレクションは、RDB のテーブルに対応するものです。
コレクションの集まりをデータベースと呼びます。

f:id:kamojirobrothers:20210506162320p:plain
引用元:https://docs.mongodb.com/manual/core/databases-and-collections/#collections

MongoDB にドキュメントを格納すると、実際には以下のようになります。

var mydoc = {
               _id: ObjectId("5099803df3f4948bd2f98391"),
               name: { first: "Alan", last: "Turing" },
               birth: new Date('Jun 23, 1912'),
               death: new Date('Jun 07, 1954'),
               contribs: [ "Turing machine", "Turing test", "Turingery" ],
               views : NumberLong(1250000)
            }

_id は、ObjectId と呼ばれ、ドキュメントを一意に特定するもので、RDB での主キーに対応するものになります。
その他、利用できる型は以下にまとめられています。
BSON Types — MongoDB Manual

インデックス

RDB と同様にインデックスを張ることができます。
デフォルトでは、_id がインデックスと利用されますが、ランダムな値であるため高速化に寄与しません。
高速化をおこなうために、特定のフィールドに対してインデックスを張ることができます。
このインデックスは必ずしもすべてのドキュメントに対応させる必要はなく、特定のフィールドを持つものだけを対象としてインデックスを張れます。

Change Stream

コレクションを監視して、更新処理を時系列データとして利用できます。
例えば、変更を検知した場合に、Apache Kafka などのメッセージングキューにデータを送信する、などの処理ができます。

MongoDB Kafka Connector — MongoDB Kafka Connector

レプリカセット

レプリケーションにより冗長化できます。MongoDB ではプライマリーとセカンダリーからなるレプリケーションクラスターをレプリカセットと呼びます。

f:id:kamojirobrothers:20210513233326p:plain
https://docs.mongodb.com/manual/replication/#replication-in-mongodb

一般的なレプリケーションと同様に、プライマリーのデータをセカンダリーに同期します。
レプリケーションは、oplog (operation log)と呼ばれる、実行コマンドを記録したログを用いて行われます。
また、レプリケーションは非同期レプリケーションです。

各ノード同士は、ハートビートにより生存確認を行っており、プライマリーに障害が発生した場合にはセカンダリーをマスターに昇格させます。
このとき、クォーラムを行うために3台以上のノードが必要となります。
しかし、コストなどの理由により高性能なサーバーを2台しか用意できない場合、データをもたない Arbiter というノードを利用できます。
Arbiter はデータを持たず、クォーラムによるマスターの選出にのみ参加します。

f:id:kamojirobrothers:20210513234023p:plain
https://docs.mongodb.com/manual/replication/#replication-in-mongodb

話をセカンダリーに戻すと、セカンダリーサーバーは複数の種類があります。
主に以下のものがあります。

種類 マスターに昇格する アプリケーションからの参照 マスターデータとの違い
通常 同じ
Priority 0 Replica Set Member 同じ
Hidden Replica Set Member 同じ
Delayed Replica Set Member 古い

Hidden Replica Set Member は、Priority 0 Replica Set Member であり、Hidden Replica Set Member は、Delayed Replica Set Member です。

Priority 0 Replica Set Member は、広域災害などに備えてプライマリーや通常のセカンダリーから離れたデータセンターに配置します。
Hidden Replica Set Member は、管理者用のセカンダリーになります。通常のアプリケーションからのデータを処理していないため、データ分析などの用途に利用しやすいです。
Delayed Replica Set Member は、不足の事態によりデータが失われた場合に備えたものになります。

シャーディング

MongoDB は、ドキュメントストアであり、水平スケーリングに適しています。
サーバーの台数を増やすことで、参照・更新ともに負荷分散が実現可能です。

f:id:kamojirobrothers:20210513235649p:plain
https://docs.mongodb.com/manual/core/replica-set-members/#secondaries

Shard、Config Server、mongos から構成されます。

  • Shard:MongoDB インスタンスです。冗長性を確保するためにレプリカセットにすることが多いです。
  • Config Server:クラスターを管理するサーバーであり、メタデータを保持しています。可用性を確保するためにレプリカセットにすることが多いです。
  • mongos:アプリケーションサーバーに配置されるルーターです。リバースプロキシのような役割を担っており、Config Server から取得したメタデータを元に、データを Shard たちにルーティングします。

シャーディング方式としては、ハッシュ値を利用したハッシュシャーディングと値を範囲を利用したレンジシャーディングが利用できます。

シャーディングにおいて、データはチャンクと呼ばれる単位で管理されています。
最大64MBほどのデータとなっています。
MongoDB では、シャードごとのデータが均等になるようにチャンクを統合・分割したり、シャード間で移動させることで適切な状態を保ってくれます。
特に、チャンクの格納されるデータは上記のシャーディング方式に従ってまとめられています。
いい感じにデータが分散できるように、シャーディング方式を選択する必要があります。