본문으로 건너뛰기

소개

merql은 순수 PHP 라이브러리로, 데이터베이스 상태의 3방향 병합을 수행합니다. 테이블 스냅샷을 만들고, 스냅샷 간 변경 세트를 계산하고, 공통 base를 기준으로 두 변경 세트를 병합하며, 열 수준에서 충돌을 감지합니다. 소스 파일에 적용하는 git과 같은 알고리즘을 행과 열에 적용합니다.

외부 도구가 필요하지 않습니다. 플러그형 연결 어댑터와 방언 드라이버를 통해 MySQL과 SQLite을 지원합니다.

빠른 예제

병합에는 동일한 데이터의 세 상태가 필요합니다. 두 복사본이 함께 시작한 base와 독립적으로 변경된 두 복사본입니다. 여기서는 샌드박스를 운영 데이터베이스에서 복사했으며 이후 양쪽을 모두 편집했습니다.

PHP
use Merql\Connection;
use Merql\Merql;

$live    = Connection::sqlite('/data/live.sqlite');
$sandbox = Connection::sqlite('/data/sandbox.sqlite');

// Base: live at the moment the sandbox was copied from it.
Merql::init($live);
Merql::snapshot('base');

// ... copy live.sqlite to sandbox.sqlite, then both databases change ...

// Ours: the sandbox with our edits. Theirs: live with its own changes.
Merql::init($sandbox);
Merql::snapshot('ours');
Merql::init($live);
Merql::snapshot('theirs');

$result = Merql::merge('base', 'ours', 'theirs');

if ($result->isClean()) {
    foreach ($result->operations() as $operation) {
        echo "{$operation->type} {$operation->table} #{$operation->rowKey} ({$operation->source})\n";
    }
} else {
    echo "Conflicts: {$result->conflictCount()}\n";
}

각 스냅샷은 해당 상태가 있는 데이터베이스에서 가져옵니다. 세 개 모두 한 데이터베이스에서 차례로 가져오면 서로 분기한 두 복사본을 나타내지 못합니다. theirs에 이미 ours의 변경 사항이 포함되어 ours가 삽입한 모든 행이 insert_insert 충돌로 보고됩니다.

결과에는 양쪽의 작업을 통해 base를 병합 상태로 바꾸는 방법이 나열되며, 각 작업에는 출처(ours, theirs 또는 merged)가 표시됩니다. 샌드박스의 변경 사항을 운영 환경에 기록하려면 운영 환경에 아직 없는 작업만 보호된 적용으로 적용합니다.

또는 CLI을 사용하면 각 snapshot마다 MERQL_DB_DSN에 지정된 데이터베이스에 연결됩니다.

셸
MERQL_DB_DSN="sqlite:/data/live.sqlite" vendor/bin/merql snapshot base
# ... copy live.sqlite to sandbox.sqlite, then both databases change ...
MERQL_DB_DSN="sqlite:/data/sandbox.sqlite" vendor/bin/merql snapshot ours
MERQL_DB_DSN="sqlite:/data/live.sqlite" vendor/bin/merql snapshot theirs

vendor/bin/merql merge base ours theirs --dry-run

merql을 사용하는 이유

상태를 비교하는 대부분의 데이터베이스 도구는 행 수준에서 작업합니다. 두 버전에서 행의 열 하나라도 다르면 행 전체를 충돌로 표시합니다. 따라서 두 사람이 같은 행의 서로 다른 열을 편집해도 잘못된 충돌이 발생합니다.

merql은 열 수준에서 병합합니다. 한쪽이 title을 변경하고 다른 쪽이 status을 변경하면 충돌 없이 병합됩니다. 양쪽에서 같은 열을 서로 다른 값으로 변경한 경우에만 충돌이 발생합니다.

TEXT 및 JSON 열은 셀 수준 병합으로 한 단계 더 세밀하게 처리할 수 있습니다. 텍스트 열의 서로 다른 행이나 JSON 열의 서로 다른 키를 편집한 경우 같은 3방향 알고리즘으로 충돌 없이 병합할 수 있습니다. 셀 수준 병합은 선택 사항입니다. ThreeWayMerge에 CellMergeConfig를 전달하세요. Merql 파사드와 CLI는 열 수준에서만 병합합니다. 자세한 내용은 셀 수준 병합을 참고하세요.

병합 모델

코드
         Base (snapshot at fork time)
        /                              \
   Ours (one copy's changes)            Theirs (the other copy's changes)
        \                              /
         --------- MERGE ----------
                     |
              Merged result
              + conflicts (if any)

git과 마찬가지로 세 상태를 사용합니다. Base는 공통 조상입니다. Ours와 theirs는 각자 독립적으로 분기한 뒤의 두 복사본이며, 각각 해당 데이터베이스에서 캡처합니다. 병합 규칙은 양쪽을 동일하게 취급합니다. 이 이름은 각 작업의 source, 충돌 유형(update_delete은 ours가 업데이트하고 theirs가 삭제했다는 뜻), OursWins 및 TheirsWins 해결 정책에 표시됩니다. 공통 base가 없으면 "추가됨"과 "변경되지 않음"을 구분할 수 없습니다.

병합은 그 자체로 아무것도 쓰지 않습니다. 병합 작업은 base를 병합된 상태로 변환하므로, Merql::apply()은 base가 여전히 저장된 데이터베이스에서만 모든 작업을 오류 없이 실행합니다. 샌드박스의 편집 내용을 라이브 환경으로 승격하는 경우처럼, 한쪽의 변경 사항을 다른 쪽의 상태가 이미 저장된 데이터베이스에 반영하려면 아직 적용되지 않은 작업만 적용하세요. 가드 적용을 참조하세요.

요구 사항

  • PHP 8.2 이상.
  • 텍스트 diff를 제공하는 pitmaster/pitmaster 종속성에 필요한 ext-mbstring 및 ext-zlib.
  • 사용하는 어댑터용 데이터베이스 확장: ext-pdo와 ext-pdo_sqlite 또는 ext-pdo_mysql, 또는 ext-mysqli. merql의 composer.json은 ext-pdo 및 ext-mysqli만 제안하므로 Composer은 이를 확인하지 않습니다.
  • MySQL 또는 SQLite 데이터베이스. 다른 엔진에는 사용자 지정 드라이버가 필요합니다.

해당하지 않는 것

merql은 스키마 마이그레이션 도구가 아닙니다. 데이터베이스를 복제하거나 실시간으로 동기화하지 않습니다. 특정한 한 가지 문제를 해결합니다. 세 가지 데이터베이스 상태가 주어졌을 때 충돌을 식별해 네 번째 병합 상태를 생성합니다.

문서

  • 시작하기 -- 설치, 첫 스냅샷, 첫 병합.
  • CLI 참조 -- vendor/bin/merql 명령의 전체 참조.
  • PHP API 참조 -- 모든 클래스의 전체 API 문서.
  • 병합 계획 -- 안정적인 작업 ID, 변경 그룹, 선택 항목.
  • 가드 적용 -- 라이브 행 사전 조건을 적용한 선택적 쓰기.
  • 롤백 -- 롤백 아티팩트 및 드리프트 확인.