automerge

[์›๋ณธ ๋งํฌ]

automerge๋Š” CRDT ํŒจํ„ด์„ ์ง€์›ํ•˜๋Š” ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํ™˜๊ฒฝ์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋‹ค.

๊ตฌํ˜„์€ Rust์ง€๋งŒ, ์• ์ดˆ์— ๋ฒ”์šฉ์ ์ธ ์›น ํ™˜๊ฒฝ์„ ๊ณ ๋ คํ•œ ๊ฒƒ์ด๋ผ์„œ WASM์„ ํ†ตํ•œ Javascript ์—ฐ๋™์„ ์ง์ ‘ ์ง€์›ํ•œ๋‹ค.
๊ทธ๋ž˜์„œ WASM์ด ๋ถˆ๊ฐ€๋Šฅํ•œ ํ™˜๊ฒฝ์—๋ผ๋ฉด ์“ฐ์ง€ ๋ชปํ•  ๊ฒƒ์ด๋‹ค.

Rust๋‚˜ C๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅ์€ ํ•œ๋ฐ, ๋ฌธ์„œํ•˜๋„ ์ž˜ ์•ˆ๋ผ์žˆ๊ณ  ๊ถŒ์žฅํ•˜์ง€๋„ ์•Š๋Š”๋‹ค.

CRDT์— ๋Œ€ํ•œ ๊ฐœ๋… ์ฐธ์กฐ
https://blog.naver.com/sssang97/223113627152




๋ฐฉํ–ฅ์„ฑ๊ณผ ์›๋ฆฌ

automerge๊ฐ€ CRDT๋ฅผ ๊ตฌํ˜„ํ•œ ๋ฐฉ๋ฒ•์€ git์˜ ์›๋ฆฌ์™€ ํก์‚ฌํ•˜๋‹ค.
๊ฐ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ๋ณ„๋„์˜ ํ๋ฆ„์œผ๋กœ ๊ด€๋ฆฌํ•˜๋‹ค๊ฐ€, ๋ณ‘ํ•ฉํ•˜๋ฉด ๊ทธ๋•Œ ์ด๋ฆฌ์ €๋ฆฌ ๋ผ์›Œ๋„ฃ๊ณ  ๋ฐ€์–ด๋„ฃ๋Š” ์ž‘์—…์„ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

automerge๋Š” "automerge ๊ฐ์ฒด"๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ชจ๋“  ๋ณ€๊ฒฝ์— ๋Œ€ํ•œ ์ œ์–ด์™€ ๋ณ‘ํ•ฉ์„ ์ด๊ด„ํ•œ๋‹ค.

๊ฐ automerge ๊ฐ์ฒด๋Š” ๋ถˆ๋ณ€ ๊ฐ’์ด๊ณ , ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๋ฉด ์ƒˆ๋กœ์šด ๋ฒ„์ „์˜ ๊ฐ์ฒด๋ฅผ ์ƒˆ๋กœ ์ƒ์„ฑํ•œ๋‹ค.
์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํ™˜๊ฒฝ์˜ ์ƒํƒœ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค๋„ ๊ทธ๋Ÿฐ ๋ฐฉ์‹์„ ์ทจํ•˜๊ณค ํ•˜๋Š”๋ฐ, ๋น„์Šทํ•˜๋‹ค๊ณ  ๋ณด๋ฉด ๋œ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๋ณ€๊ฒฝ์ด ๋˜๋ฉด ๊ทธ ๋ณ€๊ฒฝ๋œ ์‚ฌ๋ณธ๋“ค์„ ๊ธฐ๋ฐ˜์œผ๋กœ, Merge๋ฅผ ํ•˜๋ฉด ์ ์ ˆํžˆ ๋ณ‘ํ•ฉ์ฒ˜๋ฆฌ๋Š” ํ•ด์ฃผ๋Š”๊ฒŒ ๊ธฐ๋ณธ์ ์ธ ์‚ฌ์šฉ๋ฒ•์ด๋‹ค.

์—ฌ๊ธฐ์„œ ๊ฐ automerge ๊ฐ์ฒด๋Š” ๋กœ์ปฌ์—์„œ ํ˜„์žฌ ์‚ฌ์šฉ์ž๊ฐ€ ๋ณ€๊ฒฝํ•œ ๋‚ด์—ญ์ผ ์ˆ˜๋„ ์žˆ๊ณ , ๋„คํŠธ์›Œํฌ๋กœ ๋‚ ๋ผ์˜จ ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž์˜ ๋ณ€๊ฒฝ๋‚ด์—ญ์ผ ์ˆ˜๋„ ์žˆ๋‹ค.
์ผ๋‹จ automerge ๊ฐ์ฒด๋“ค๋กœ ๋ฐ›์•„์˜ค๊ธฐ๋งŒ ํ•˜๋ฉด merge๋ฅผ ํ†ตํ•ด์„œ ์•ˆ์ •์ ์œผ๋กœ ๋ณ‘ํ•ฉํ•  ์ˆ˜ ์žˆ๋‹ค.




์„ค์น˜

nodejs์—์„œ ์“ด๋‹ค๋ฉด ๊ทธ๋ƒฅ npm์œผ๋กœ ์„ค์น˜ํ•˜๋Š” ๊ฒƒ๋งŒ์œผ๋กœ๋„ ๊ฐ„๋‹จํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

npm i @automerge/automerge @automerge/automerge-repo

ํ•˜์ง€๋งŒ React, Vue ๋“ฑ ๋ธŒ๋ผ์šฐ์ € ํ™˜๊ฒฝ์„ ๊ณ ๋ คํ•œ๋‹ค๋ฉด ์„ค์ •ํ•ด์•ผํ•  ๊ฒƒ์ด ๋ช‡๊ฐ€์ง€ ์žˆ๋‹ค.
์›น์–ด์…ˆ๋ธ”๋ฆฌ ์ข…์†์„ฑ์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ž์„ธํ•œ ๊ฒƒ์€ ๋ฌธ์„œ ๋‚ด ๊ฐ€์ด๋“œ๋ฅผ ์ฐธ์กฐํ•œ๋‹ค.
https://automerge.org/docs/library_initialization/

์—ฌ๊ธฐ์„œ๋Š” ์ผ๋‹จ node.js๋ฅผ ์จ์„œ๋งŒ ๊ฐ„๋‹จํ•˜๊ฒŒ ์›๋ฆฌ๋ฅผ ๋ณด์—ฌ๋ณด๊ฒ ๋‹ค.




๊ฐ„๋‹จํ•œ ์˜ˆ์‹œ: ํ…์ŠคํŠธ ๋ณ‘ํ•ฉ

CRDT์˜ ๋Œ€ํ‘œ์ ์ธ ์‚ฌ์šฉ๋ก€๋Š” ์•„๋ฌด๋ž˜๋„ ๋™์‹œ ํ…์ŠคํŠธ ํŽธ์ง‘ ๊ฐ™์€ ๊ฒฝ์šฐ์ผ ๊ฒƒ์ด๋‹ค.
automerge๋Š” ๋ฐฐ์—ด์ด๋‚˜ ์ˆซ์ž, ์˜ค๋ธŒ์ ํŠธ์— ๋Œ€ํ•ด์„œ๋„ ๋ณ‘ํ•ฉ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜์ง€๋งŒ, ๋‚ด๊ฐ€ ๋ณผ๋•Œ ๋Œ€๋ถ€๋ถ„์˜ ์‚ฌ์šฉ๋ก€๋Š” ํ…์ŠคํŠธ๋‹ค.

ํ•œ๋ฒˆ automerge๊ฐ€ ์–ด๋–ค ์‹์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š”์ง€๋ฅผ ๋ณด์—ฌ๋ณด๊ฒ ๋‹ค.
์•„๋ž˜๋Š” ๊ทธ๋ƒฅ ํ…์ŠคํŠธ ๊ฐ์ฒด๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  ํ…์ŠคํŠธ๋ฅผ ๋ผ์›Œ๋„ฃ๋Š” ๋ณ€๊ฒฝ์„ ํ•˜๋Š” ๊ฐ„๋‹จํ•œ ์ฝ”๋“œ๋‹ค.

import { next as Automerge } from "@automerge/automerge";

let doc = Automerge.from({ text: "hello world" });

// ๋ณ€๊ฒฝ์„ ์œ„ํ•œ ๋ณต์ œ๋ณธ ์ƒ์„ฑ
let forked = Automerge.clone(doc);
forked = Automerge.change(forked, (d) => {
  // ์ธ๋ฑ์Šค 5๋ถ€ํ„ฐ 0๊ฐœ๋ฅผ ์ง€์šฐ๊ณ  ๊ทธ ์ž๋ฆฌ์—" wonderful"๋ฅผ ์ถ”๊ฐ€
  Automerge.splice(d, ["text"], 5, 0, " wonderful");
});

// ๋ณ€๊ฒฝ์‚ฌํ•ญ ๋ณ‘ํ•ฉ
doc = Automerge.merge(doc, forked);

console.log(doc.text);

์ด ์ฝ”๋“œ์ฒ˜๋Ÿผ, ์‹ค์งˆ์ ์ธ ์ตœ์ข… ๊ฐ’์— ๋Œ€ํ•œ ๋ณ€๊ฒฝ์€ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ root์— ๋ณ‘ํ•ฉํ•˜๋Š” ๊ฒƒ์œผ๋กœ ์ด๋ฃจ์–ด์ง„๋‹ค.
์ผ๋‹จ์€ ๋Œ๊ธด ํ–ˆ๋Š”๋ฐ, ์‚ฌ์‹ค ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ํ•˜๋‚˜๋ฟ์ด๋ผ์„œ CRDT๋ฅผ ์“ฐ๋Š” ์˜๋ฏธ๋Š” ์—†์—ˆ๋‹ค.

์ด๋ฒˆ์—๋Š” ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ํ•˜๋‚˜ ๋” ์ ์šฉํ•ด๋ณด๊ฒ ๋‹ค.

์ด๋ ‡๊ฒŒ ๋ณ€๊ฒฝ ๋‚ด์—ญ์„ ํ•˜๋‚˜ ๋งŒ๋“ค๊ณ 

๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋ณ‘ํ•ฉ์„ ์‹œ์ผœ์„œ ๋ณด๋ฉด ๋œ๋‹ค.

๋™์ผ ํ…์ŠคํŠธ์— ๋Œ€ํ•ด์„œ ๋ณ€๊ฒฝ์„ ์‹œ๋„ํ–ˆ์Œ์—๋„, 2๊ฐ€์ง€ ๋ณ€๊ฒฝ์ด ์ ๋‹นํžˆ ์„ž์ธ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

์•„๋ž˜๋Š” ์ „์ฒด ์ฝ”๋“œ๋‹ค.

import { next as Automerge } from "@automerge/automerge";

let doc = Automerge.from({ text: "hello world" });

// ๋ณ€๊ฒฝ์„ ์œ„ํ•œ ๋ณต์ œ๋ณธ ์ƒ์„ฑ
let forked = Automerge.clone(doc);
forked = Automerge.change(forked, (d) => {
  // ์ธ๋ฑ์Šค 5๋ถ€ํ„ฐ 0๊ฐœ๋ฅผ ์ง€์šฐ๊ณ  ๊ทธ ์ž๋ฆฌ์—" wonderful"๋ฅผ ์ถ”๊ฐ€
  Automerge.splice(d, ["text"], 5, 0, " wonderful");
});

// ๋ณ€๊ฒฝ์„ ์œ„ํ•œ ๋ณต์ œ๋ณธ ์ƒ์„ฑ - ๋‘๋ฒˆ์งธ
let forked2 = Automerge.clone(doc);
forked2 = Automerge.change(forked2, (d) => {
  // ์ธ๋ฑ์Šค 0๋ถ€ํ„ฐ 5๊ฐœ๋ฅผ ์ง€์šฐ๊ณ  ๊ทธ ์ž๋ฆฌ์— "Greetings"๋ฅผ ์ถ”๊ฐ€
  Automerge.splice(d, ["text"], 0, 5, "Greetings");
});

// ๋ณ€๊ฒฝ์‚ฌํ•ญ ๋ณ‘ํ•ฉ
doc = Automerge.merge(doc, forked);
doc = Automerge.merge(doc, forked2);

console.log(doc.text);

๊ทผ๋ฐ ์œ„ ์˜ˆ์ œ์—์„œ๋Š” ํ…์ŠคํŠธ๋ฅผ ์„œ๋กœ ๋‹ค๋ฅธ ์œ„์น˜์— ๊ตฌ๊ฒจ๋„ฃ์–ด์„œ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ณ‘ํ•ฉ์ด ๋๋‹ค.
๊ทธ๋Ÿฌ๋ฉด ๋™์ผํ•œ ์œ„์น˜์—์„œ ํ…์ŠคํŠธ๋ฅผ ์ง‘์–ด๋„ฃ์œผ๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ?

๊ทธ๋Ÿฐ ๊ฒฝ์šฐ์—๋Š” ๋‘˜๋‹ค ํ…์ŠคํŠธ๋ฅผ ๋„ฃ๋˜, ๋จผ์ € ์ˆ˜์ •ํ•œ ๋ณ€๊ฒฝ์„ ๋จผ์ € ๋ฐ˜์˜ํ•˜๋Š” ์‹์ด ๋œ๋‹ค.




์‹ค์‹œ๊ฐ„ ๋„คํŠธ์›Œํ‚น ๊ตฌ์กฐ ์ œ๊ณต

CRDT๊ฐ€ ํ•„์š”ํ•  ์ •๋„์˜ ํ™˜๊ฒฝ์—์„œ๋Š” ์›น์†Œ์ผ“์ด๋‚˜ WebRTC๋ฅผ ํ†ตํ•œ ์‹ค์‹œ๊ฐ„ ์ฒ˜๋ฆฌ๊ฐ€ ๋™๋ฐ˜๋  ๊ฒฝ์šฐ๊ฐ€ ์žฆ์€๋ฐ, CRDT๋Š” ๊ทธ์— ๋Œ€ํ•œ ์•ฝ๊ฐ„ ์ค€๋น„๋œ ๊ตฌ์„ฑ์„ ์–ด๋А ์ •๋„ ์ œ๊ณตํ•œ๋‹ค.

์ด๋ฆ„์ด @automerge/automerge-repo-storage-*๋กœ ์‹œ์ž‘๋˜๋Š” ๋ชจ๋“ˆ๋“ค์€ ์Šคํ† ๋ฆฌ์ง€ ๊ธฐ๋ฐ˜์œผ๋กœ automerge ๊ฐ์ฒด๋ฅผ ์ €์žฅํ•˜๊ณ  ๋™๊ธฐํ™”ํ•˜๋Š” ๊ธฐ๋Šฅ์„, @automerge/automerge-repo-network-*๋กœ ์‹œ์ž‘๋˜๋Š” ๋ชจ๋“ˆ๋“ค์€ ๋„คํŠธ์›Œํฌ ํ†ต์‹ ์„ ๊ธฐ๋ฐ˜์œผ๋กœ automerge ๊ฐ์ฒด๋ฅผ ์ฃผ๊ณ ๋ฐ›์œผ๋ฉด์„œ ๋™๊ธฐํ™”ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค.

์˜ˆ๋ฅผ ๋“ค๋ฉด, ์ด๊ฑด React์˜ ์ผ๋ฐ˜์ ์ธ ๊ตฌ์„ฑ์ด๋‹ค.

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
import { IndexedDBStorageAdapter } from "@automerge/automerge-repo-storage-indexeddb"
import { BroadcastChannelNetworkAdapter } from '@automerge/automerge-repo-network-broadcastchannel'
import { Repo } from '@automerge/automerge-repo'
import { RepoContext } from '@automerge/automerge-repo-react-hooks'

const broadcast = new BroadcastChannelNetworkAdapter();
const indexedDB = new IndexedDBStorageAdapter();

const repo = new Repo({
  storage: indexedDB,
  network: [broadcast],
})

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <RepoContext.Provider value={repo}>
      <App />
    </RepoContext.Provider>
  </React.StrictMode>
)

์ด๋Ÿฐ ์‹์œผ๋กœ ์–ด๋Œ‘ํ„ฐ๋ฅผ ์ดˆ๊ธฐํ™”ํ•ด์„œ repo ๋‹จ์œ„๋ฅผ ์ƒ์„ฑํ•˜๋ฉด, ์ € repo ๊ฐ์ฒด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ automerge ์ž‘์—… ๋‹จ์œ„๋ฅผ ๋งŒ๋“ค๊ฑฐ๋‚˜ ์กฐํšŒ, ๋‹ค๋ฅธ ๋„คํŠธ์›Œํฌ ์‚ฌ์šฉ์ž๋กœ์˜ ๋™๊ธฐํ™”๊นŒ์ง€๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

React์— ๋Œ€ํ•œ ์˜ˆ์ œ์ฝ”๋“œ๋Š” ๋‹ค์Œ ๋ฌธ์„œ์— ์ ๋‹นํžˆ ์ ํ˜€์žˆ๋‹ค. ๋” ์ž์„ธํ•œ ์‚ฌ์šฉ๋ฒ•์„ ๋ฌธ์„œ๋ฅผ ์ฐธ์กฐํ•˜๋Š”๊ฒŒ ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค.
CRDT์—์„œ ์ œ๊ณตํ•˜๋Š” ๋™๊ธฐํ™” ๊ตฌ์„ฑ์„ ํ™œ์šฉํ•˜๋ ค๋ฉด ์ด๊ฒƒ์ €๊ฒƒ ๊ฐ–๋‹ค์จ์•ผํ•  ๊ฒƒ์ด ๋งŽ๋‹ค.
https://automerge.org/docs/quickstart/#manage-docs-with-a-repo



์ฐธ์กฐ
https://automerge.org/docs/hello/
https://github.com/automerge/automerge