InfoGrab Docs

MySQL에서 PostgreSQL로 수동 마이그레이션

요약

MySQL 데이터베이스를 PostgreSQL로 수동으로 마이그레이션하는 절차는 다음과 같습니다: 협업 플레이북 및 Boards 마이그레이션에 대한 자세한 내용은 플러그인 마이그레이션 섹션을 참조하세요. 수동 업그레이드가 적합한 방법인지 확실하지 않으신가요?

MySQL 데이터베이스를 PostgreSQL로 수동으로 마이그레이션하는 절차는 다음과 같습니다:

Tip

협업 플레이북 및 Boards 마이그레이션에 대한 자세한 내용은 플러그인 마이그레이션 섹션을 참조하세요.

수동 업그레이드가 적합한 방법인지 확실하지 않으신가요? Mattermost 배포에 맞춤 지침이 필요한 Mattermost 고객은 Mattermost 전문가 에게 문의할 수 있습니다.

도구 권장 사항#

Postgres로 수동 마이그레이션을 선호하는 경우, 마이그레이션 프로세스에 다음 도구를 권장합니다:

이 페이지에는 각 도구의 설치 방법과 데이터베이스 마이그레이션 진행 방법이 포함되어 있습니다.

필요한 도구를 설치한 후 시스템 요구 사항 및 구성 문서를 검토하고, 마이그레이션을 준비하기 위해 마이그레이션 전 무엇이 필요한지 파악하세요.

대상 데이터베이스 준비 부터 마이그레이션을 시작한 다음, 데이터 마이그레이션을 진행하고 모든 마이그레이션 후 단계를 완료하세요.

플레이북 및 Boards 마이그레이션에 대한 자세한 내용은 플러그인 마이그레이션 문서를 참조하세요.

pgloader#

pgloader 도구를 사용하여 MySQL에서 PostgreSQL로 데이터를 마이그레이션합니다.

pgloader 설치#

pgloader를 설치하려면 공식 설치 가이드를 참조하세요.

Note

MySQL v8을 사용하는 경우: pgloader 컴파일 바이너리의 알려진 버그 로 인해 소스에서 pgloader를 컴파일해야 합니다. 이 단계 에 따라 소스에서 빌드하세요.

또는 pgloader를 설치하거나 빌드하지 않고 mattermost-pgloader Docker 이미지를 사용할 수도 있습니다. 자세한 내용은 아래 문서를 참조하세요.

pgloader 사용#

Docker 이미지 pull 및 pgloader 검증

수동 마이그레이션을 위해 다음 명령을 실행하여 mattermost-pgloader 이미지를 pull하고 pgloader가 올바르게 작동하는지 확인합니다:

docker run -it --rm -v $(pwd):/home/migration mattermost/mattermost-pgloader:latest pgloader --version

이 명령은 mattermost/mattermost-pgloader:latest 이미지를 pull하고 pgloader를 실행하여 버전을 확인하고 예상대로 작동하는지 확인합니다.

로컬 디렉터리 매핑

-v $(pwd):/home/migration 플래그를 사용하여 현재 작업 디렉터리를 Docker 컨테이너에 매핑합니다. 이를 통해 로그 및 기타 파일 저장에 로컬 디렉터리를 사용할 수 있습니다.

네트워크 구성 설정

네트워크 요구 사항에 따라 --network 플래그를 설정합니다. 예를 들어 localhost에 접근하려면 네트워크를 host로 설정해야 합니다.

morph#

morph 도구는 PostgreSQL 스키마를 생성합니다.

Note

morphdbcmp 모두 Go 툴체인이 필요합니다. Go 컴파일러를 설치하려면 Go 문서를 따르세요.

morph 설치#

다음 명령을 실행하여 morph CLI를 설치할 수 있습니다:

go install github.com/mattermost/morph/cmd/morph@v1

dbcmp (선택)#

dbcmp 도구를 사용하면 마이그레이션 후 모든 테이블을 비교하고 두 스키마 간의 차이를 보고하여 데이터를 비교할 수 있습니다.

dbcmp 설치#

다음 명령을 실행하여 dbcmp를 설치할 수 있습니다:

go install github.com/mattermost/dbcmp/cmd/dbcmp@latest

수동 마이그레이션을 위한 시스템 요구 사항 및 구성#

수동 마이그레이션 프로세스를 시작하기 전에 시스템이 원활하고 효율적인 마이그레이션을 위한 필요 요구 사항을 충족하는지 확인해야 합니다. 다음 시스템 사양 및 조정을 강력히 권장합니다:

  • 충분한 시스템 메모리 리소스를 확보하세요. 기본값으로 16GB RAM을 권장합니다. 시스템 메모리가 부족한 경우 number of workers, prefetch rows, 특히 concurrency1 이상으로 설정된 경우 rows per range와 같은 pgloader 설정을 세부 조정할 수 있습니다. 이러한 조정은 사용 가능한 시스템 리소스에 따라 리소스 활용을 최적화하는 데 도움이 됩니다. 자세한 내용은 pgloader 문서를 참조하세요.
  • 마이그레이션 프로세스, 특히 대규모 데이터셋을 처리할 때 충분한 처리 성능을 갖춘 멀티코어 프로세서를 권장합니다.
  • MySQL 및 PostgreSQL 데이터베이스와 마이그레이션 프로세스 중 생성되는 임시 파일을 저장할 충분한 디스크 공간을 확보하세요. 필요한 디스크 공간은 마이그레이션되는 데이터베이스의 크기에 따라 다릅니다.
  • 마이그레이션 시간을 더 줄이려면 마이그레이션 프로세스를 시작하기 전에 대상 PostgreSQL 데이터베이스의 인덱스를 수동으로 삭제할 수 있습니다. 이 방법은 데이터 삽입 중 인덱스 빌드 오버헤드를 줄여 마이그레이션을 잠재적으로 가속화할 수 있습니다.

수동 마이그레이션 전#

Important

이 가이드는 v7.1 ESR 이상의 스키마가 필요합니다. 이전 버전이 있고 마이그레이션을 계획 중이라면 최소 v7.1로 Mattermost 서버를 업데이트하세요. 자세한 내용은 장기 지원 릴리즈 문서를 참조하세요.

    • MySQL 데이터를 백업합니다.
    • Mattermost 버전을 확인합니다. 자세한 내용은 정보 모달을 참조하세요.
    • 마이그레이션 기간을 예약합니다. 이 프로세스는 마이그레이션 중 Mattermost 서버를 중지해야 합니다.
    • 스키마 간 데이터 호환성을 위해 스키마 차이 섹션을 참조하세요.
    • 데이터베이스와 사용자를 생성하여 PostgreSQL 환경을 준비합니다. 자세한 내용은 데이터베이스 문서를 참조하세요.
    • PostgreSQL의 최신 버전에서 새로 생성된 사용자는 public 스키마에 대한 접근 권한이 없습니다. GRANT ALL ON SCHEMA public to mmuser를 실행하여 명시적으로 접근 권한을 부여해야 합니다.

스키마 차이#

수동 마이그레이션 전에 두 스키마의 차이로 인해 오류 없는 마이그레이션을 위해 몇 가지 수동 단계가 필요할 수 있습니다.

텍스트에서 가변 문자로#

Mattermost MySQL 스키마가 다양한 테이블에서 PostgreSQL 스키마의 varchar 표현 대신 text 컬럼 유형을 사용하므로 PostgreSQL 스키마 제한 내에서 크기가 일관되는지 확인하는 것을 권장합니다.

필요한 삭제 또는 업데이트가 있는지 확인할 수 있습니다. 예를 들어 Audits 테이블/Action 컬럼에서 확인하려면 다음을 실행합니다:

SELECT FROM mattermost.Audits where LENGTH(Action) > 512;

다음 테이블은 추가적인 결과 없이 진행할 수 있는 삭제 또는 업데이트를 보여줍니다.

테이블컬럼데이터 유형 변환삭제 시 결과
AuditsActiontext -> varchar(512)애플리케이션 작동에 부작용 없음 (영향받는 행을 삭제해야 함).
AuditsExtraInfotext -> varchar(1024)애플리케이션 작동에 부작용 없음 (영향받는 행을 삭제해야 함).
ClusterDiscoveryHostNametext -> varchar(512)애플리케이션 작동에 부작용 없음 (영향받는 행을 삭제해야 함).
CommandsIconURLtext -> varchar(1024)필드를 삭제하거나 새 URL로 업데이트할 수 있음.
CommandsAutoCompleteDesctext -> varchar(1024)필드를 삭제하거나 다시 작성할 수 있음.
CommandsAutoCompleteHinttext -> varchar(1024)필드를 삭제하거나 다시 작성할 수 있음.
RemoteClustersTopicstext -> varchar(512)필드를 제거할 수 있음.
SystemsValuetext -> varchar(1024)극히 드문 경우, 이상적으로는 발생하지 않아야 함.

다음 테이블은 스키마가 다를 수 있고 PostgreSQL 스키마 내의 데이터 크기 제약으로 인해 오류가 발생할 수 있는 여러 경우를 보여줍니다. 각 테이블/행에는 개별 검사가 필요하므로 삭제의 가능한 결과를 추가했습니다.

Tip

커뮤니티로부터 LinkMetadataFileInfo 테이블에 일부 오버플로가 있었다는 보고가 다수 접수되었으므로 특히 이 테이블을 확인하는 것을 권장합니다. MySQL 스키마의 데이터가 이러한 제한을 초과하는지 확인하세요.

테이블컬럼데이터 유형 변환삭제 시 결과
CompliancesKeywordstext -> varchar(512)컴플라이언스 필터를 업데이트해야 함.
CompliancesEmailstext -> varchar(1024)컴플라이언스 필터를 업데이트해야 함.
FileInfoPathtext -> varchar(512)이 파일에 대한 이전 링크가 작동하지 않음 (영향받는 행을 삭제해야 함).
FileInfoThumbnailPathtext -> varchar(512)이 파일에 대한 이전 링크가 작동하지 않음 (영향받는 행을 삭제해야 함).
FileInfoPreviewPathtext -> varchar(512)이 파일에 대한 이전 링크가 작동하지 않음 (영향받는 행을 삭제해야 함).
FileInfoNametext -> varchar(256)이 파일에 대한 이전 링크가 작동하지 않음 (영향받는 행을 삭제해야 함).
FileInfoMimeTypetext -> varchar(256)이 파일에 대한 이전 링크가 작동하지 않음 (영향받는 행을 삭제해야 함).
LinkMetadataURLtext -> varchar(2048)이 파일에 대한 이전 링크가 작동하지 않음 (영향받는 행을 삭제해야 함).
RemoteClustersSiteURLtext -> varchar(512)이전 원격 클러스터가 제거됨 (영향받는 행을 삭제해야 함).
SessionsDeviceIdtext -> varchar(512)해당 기기의 사용자가 로그아웃됨 (영향받는 행을 삭제해야 함).
UploadSessionsFileNametext -> varchar(256)업로드 세션이 손실됨 (영향받는 행을 삭제해야 함).
UploadSessionsPathtext -> varchar(512)업로드 세션이 손실됨 (영향받는 행을 삭제해야 함).

전체 텍스트 인덱스#

PostsFileInfo 테이블의 일부 단어가 전체 텍스트 검색 인덱싱의 최대 토큰 길이 제한을 초과할 수 있습니다. 이 경우 PostgreSQL 스키마에서 idx_posts_message_txtidx_fileinfo_content_txt 인덱스를 삭제하고, 마이그레이션 후 다음 쿼리를 실행하여 이 인덱스를 생성합니다:

마이그레이션 중 오류를 방지하기 위해 다음 쿼리가 포함되어 있습니다:

DROP INDEX IF EXISTS {{ .source_db }}.idx_posts_message_txt;
DROP INDEX IF EXISTS {{ .source_db }}.idx_fileinfo_content_txt;

지원되지 않는 유니코드 시퀀스#

PostgreSQL에서 허용되지 않는 특정 유니코드 시퀀스가 있는데, 바로 입니다. 이 시퀀스가 MySQL 데이터베이스의 여러 테이블의 여러 행에 나타날 수 있습니다. 해당 경우 마이그레이션 중 다음과 같은 오류가 발생할 수 있습니다: unsupported Unicode escape sequence: cannot be converted to text.. 이를 방지하려면 마이그레이션을 시작하기 전에 데이터를 정리하는 것이 좋습니다. 다음 쿼리를 사용하여 시퀀스를 빈 문자열로 교체할 수 있습니다.

Note

이 쿼리를 스크립트에서 그대로 사용하거나 MySQL 콘솔에서 정의할 때 구분 기호를 다른 것으로 설정해야 할 수 있습니다(예: DELIMITER //). 프로시저 정의가 완료되면 구분 기호를 원래대로 재설정하세요(예: DELIMITER ;).

CREATE PROCEDURE SanitizeUnsupportedUnicode()
BEGIN
 DECLARE done INT DEFAULT FALSE;
 DECLARE curTableName text;
 DECLARE curColumnName text;
 DECLARE cursors CURSOR FOR
    SELECT table_name, column_name
    FROM information_schema.COLUMNS
    WHERE data_type = 'json'
    AND table_schema = DATABASE();
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;

OPEN cursors;

WHILE NOT DONE DO
 FETCH cursors INTO curTableName, curColumnName;
 SET @query_string = CONCAT('UPDATE ', curTableName, ' SET ', curColumnName, ' = REPLACE(', curColumnName, ', \'\\\\u0000\', \'\') WHERE ', curColumnName, ' LIKE \'%\\u0000%\';');

 PREPARE dynamic_query FROM @query_string;
 EXECUTE dynamic_query;
 DEALLOCATE PREPARE dynamic_query;
END WHILE;

CLOSE cursors;
END;

CALL SanitizeUnsupportedUnicode();

DROP PROCEDURE IF EXISTS SanitizeUnsupportedUnicode;
Note

마이그레이션 중 invalid byte sequence for encoding 'UTF8': 0x00" 오류를 일으키는 허용되지 않는 특정 바이트 시퀀스 값도 있습니다. 이 오류를 방지하려면 텍스트 캐스팅 규칙에 remove-null-characters 절을 추가할 수 있습니다. 그러나 pgloader가 데이터를 즉시 수정하므로 비교 단계에서 (영향받은 경우) 테이블 간에 차이가 있을 수 있습니다.

이전 구성/버전의 아티팩트가 남아있을 수 있음#

v6.4 이전에는 Mattermost가 스키마 마이그레이션 처리에 golang-migrate를 사용했습니다. 더 이상 사용하지 않으므로 schema_migrations 테이블을 제외합니다. v6.4 이전부터 Mattermost를 사용했다면 이 테이블을 삭제하고 비교에서 제외하는 것을 고려하세요.

DROP TABLE mattermost.schema_migrations;

일부 커뮤니티 멤버는 SharedChannelRemotes 테이블에 descriptionnextsyncat 컬럼이 있었다고 보고했습니다. 이 컬럼들은 테이블에서 제거해야 합니다. 다음 DDL을 실행하여 컬럼을 삭제하는 것을 고려하세요. (이 마이그레이션은 Mattermost의 이후 버전에 추가될 예정입니다.)

ALTER TABLE SharedChannelRemotes DROP COLUMN description, DROP COLUMN nextsyncat;

이전에 릴리즈된 96번째 마이그레이션에 오류가 확인되었습니다. 마이그레이션을 진행하기 전에 특정 컬럼을 제거해야 합니다. Threads 테이블이 예상된 상태가 되도록 다음 준비된 구문을 실행하세요:

SET @preparedStatement = (SELECT IF(
 (
    SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
    WHERE table_name = 'Threads'
    AND table_schema = DATABASE()
    AND column_name = 'TeamId'
 ) > 0,
 'ALTER TABLE Threads DROP COLUMN TeamId;',
 'SELECT 1'
));

PREPARE alterIfExists FROM @preparedStatement;
EXECUTE alterIfExists;
DEALLOCATE PREPARE alterIfExists;

데이터베이스 내 구성#

Mattermost 구성을 처리하기 위해 데이터베이스를 이전에 사용했다면, 해당 테이블은 마이그레이션 스크립트 로 MySQL 데이터베이스에서 마이그레이션되지 않습니다.

두 가지 마이그레이션이 필요합니다:

  • 데이터베이스 구성을 파일 시스템으로 마이그레이션
  • 파일 시스템 구성을 다시 데이터베이스로 마이그레이션
데이터베이스 구성을 파일 시스템으로 마이그레이션

다음과 같이 mmctl config migrate 명령을 사용하여 구성을 파일 시스템으로 마이그레이션합니다:

mmctl config migrate "<DB_USER>:<DB_PASS>@tcp(<DB_HOST>:3306)/<DB_NAME>?charset=utf8mb4,utf8&readTimeout=30s&writeTimeout=30s&multiStatements=true" /opt/mattermost/config/config.json --local

여기서 <DB_USER>, <DB_PASS>, <DB_HOST>, <DB_NAME>을 실제 환경 값으로 교체합니다. 이 명령을 실행할 때 반드시 --local을 사용하세요. 첫 번째 매개변수(<DB_USER>, <DB_PASS>)는 구성이 저장된 데이터베이스이고, 두 번째 매개변수(<DB_HOST>, <DB_NAME>)는 구성을 저장할 파일입니다.

구성 파일에서 새 변경 사항을 반영하도록 SqlSettings.DataSourceSqlSettings.DriverName 필드를 업데이트합니다. json 파일을 열고 해당 필드를 변경합니다.

파일 시스템 구성을 다시 데이터베이스로 마이그레이션

구성을 다시 데이터베이스에 저장하려면 mmctl config migrate 명령을 다시 사용하여 매개변수를 반대로 합니다. 대상 데이터베이스로 다시 이동할 때 새 데이터베이스 자격 증명을 사용하세요.

SELECT * FROM Configurations WHERE Active = 't';

SqlSettings.DataSourceSqlSettings.DriverName 필드를 적절히 업데이트해야 합니다. 또한 마이그레이션이 완료된 후 MM_CONFIG 환경 변수가 새 DSN을 가리켜야 합니다.

대상 데이터베이스 준비#

필요한 사양에 따라 PostgreSQL 데이터베이스 스키마가 올바르게 구조화되도록 테이블과 인덱스를 생성하는 것이 중요합니다. Mattermost 저장소에 필요한 모든 SQL 쿼리가 포함되어 있으므로 다음 단계를 실행하여 이를 활용할 수 있습니다:

  • 특정 버전에 맞는 mattermost 저장소를 복제합니다:
git clone -b <현재 버전 (예: release-7.8)> git@github.com:mattermost/mattermost.git --depth=1
  • 다음 명령으로 morph CLI를 사용하여 PostgreSQL 데이터베이스에서 모든 스키마 마이그레이션*을 실행합니다:
morph apply up --driver postgres --dsn "postgres://user:pass@localhost:5432/<target_db_mame>?sslmode=disable" --path ./mattermost/db/migrations/postgres --number -1

* v8 이후 프로젝트 재구성으로 인해 마이그레이션 디렉터리가 ./mattermost/server/channels/db/migrations/postgres/로 변경되었습니다. Mattermost 저장소를 복제한 위치 기준으로 --path 플래그를 적절히 설정하세요.

데이터 마이그레이션#

스키마를 원하는 상태로 설정했으면 pgloader를 실행하여 데이터 마이그레이션을 시작할 수 있습니다.

Note

아래 예시에서 두 데이터베이스의 호스트는 동일한 인스턴스에 있다고 가정합니다. 다른 머신에 있는 경우 주소를 업데이트하세요. 또한 .load 파일을 --dry-run 플래그를 사용하여 테스트할 수 있습니다. 예를 들어 pgloader --dry-run migration.load 명령입니다.

데이터 마이그레이션의 기본 구성에 다음을 사용합니다:

LOAD DATABASE
 FROM      mysql://{{ .mysql_user }}:{{ .mysql_password }}@{{ .mysql_address }}/{{ .source_db }}
 INTO      pgsql://{{ .pg_user }}:{{ .pg_password }}@{{ .postgres_address }}/{{ .target_db }}

WITH data only,
 workers = 8, concurrency = 1,
 multiple readers per thread, rows per range = 10000,
 prefetch rows = 10000, batch rows = 2500,
 create no tables, create no indexes,
 preserve index names

SET PostgreSQL PARAMETERS
 maintenance_work_mem to '128MB',
 work_mem to '12MB'

SET MySQL PARAMETERS
 net_read_timeout  = '120',
 net_write_timeout = '120'

CAST column Channels.Type to "channel_type" drop typemod,
 column Teams.Type to "team_type" drop typemod,
 column UploadSessions.Type to "upload_session_type" drop typemod,
 column ChannelBookmarks.Type to "channel_bookmark_type" drop typemod,
 column Drafts.Priority to text,
 type int when (= precision 11) to integer drop typemod,
 type bigint when (= precision 20) to bigint drop typemod,
 type text to varchar drop typemod using remove-null-characters,
 type tinyint when (<= precision 4) to boolean using tinyint-to-boolean,
 type json to jsonb drop typemod using remove-null-characters

EXCLUDING TABLE NAMES MATCHING ~<IR_>, ~<focalboard>, 'schema_migrations', 'db_migrations', 'db_lock',
 'Configurations', 'ConfigurationFiles', 'db_config_migrations', 'calls'

BEFORE LOAD DO
 $ ALTER SCHEMA public RENAME TO {{ .source_db }}; $,
 $ TRUNCATE TABLE {{ .source_db }}.systems; $,
 $ DROP INDEX IF EXISTS {{ .source_db }}.idx_posts_message_txt; $,
 $ DROP INDEX IF EXISTS {{ .source_db }}.idx_fileinfo_content_txt; $

AFTER LOAD DO
 $ UPDATE {{ .source_db }}.db_migrations set name='add_createat_to_teamembers' where version=92; $,
 $ ALTER SCHEMA {{ .source_db }} RENAME TO public; $,
 $ SELECT pg_catalog.set_config('search_path', '"$user", public', false); $,
 $ ALTER USER {{ .pg_user }} SET SEARCH_PATH TO 'public'; $;

이 구성 파일(예: migration.load)을 저장한 후 다음 명령으로 pgloader를 실행할 수 있습니다:

pgloader migration.load > migration.log

마이그레이션 결과를 자유롭게 기여하거나 보고해 주세요.

마이그레이션 후#

전체 텍스트 인덱스 복원#

PostsFileInfo 테이블 접근의 성능 저하를 방지하려면 마이그레이션이 완료된 후 다음 쿼리를 실행해야 합니다:

CREATE INDEX IF NOT EXISTS idx_posts_message_txt ON public.posts USING gin(to_tsvector('english', message));
CREATE INDEX IF NOT EXISTS idx_fileinfo_content_txt ON public.fileinfo USING gin(to_tsvector('english', content));
Note

PostsFileInfo 테이블의 항목 중 일부가 위에서 언급한 제한을 초과하는 경우 인덱스 생성 쿼리는 ERROR: string is too long for tsvector 로그와 함께 경고를 표시합니다. 이는 tsvector에 맞지 않는 내용이 무시되었음을 의미합니다. 잘린 내용을 여전히 인덱싱하려면 인덱스 생성 시 내용에 substring() 함수를 사용할 수 있습니다.

1000000의 substring 길이부터 시작하여 오류가 지속되면 점차 줄입니다:

-- 1000000부터 시작
CREATE INDEX IF NOT EXISTS idx_fileinfo_content_txt ON public.fileinfo USING gin(to_tsvector('english', substring(content,0,1000000)));

-- 실패하면 800000 시도
CREATE INDEX IF NOT EXISTS idx_fileinfo_content_txt ON public.fileinfo USING gin(to_tsvector('english', substring(content,0,800000)));

-- 필요에 따라 계속 줄임 (예: 600000, 500000) 인덱스가 성공적으로 생성될 때까지

데이터 비교#

데이터베이스 비교를 단순화하기 위해 dbcmp라는 내부 도구를 개발했습니다. 두 데이터베이스의 모든 테이블을 확인하고 스키마 차이를 보고합니다. 그러나 dbcmp는 개별 행을 비교하지 않습니다. 대신 지정된 페이지 크기를 기반으로 체크섬 값을 계산하고 비교합니다. 따라서 행 수준의 차이를 생성할 수 없습니다.

마이그레이션 중 (기본값이나 당사가 제공한 것 이외의) 사용자 지정 캐스팅 규칙이 사용된 경우 데이터 무결성을 확인하는 추가 검사로 dbcmp를 사용하는 것을 권장합니다. 그렇지 않으면 이 도구를 실행할 필요가 없습니다.

이 도구에는 비교를 실행하는 몇 가지 플래그가 있습니다:

Usage:
  dbcmp [flags]

Flags:
      --exclude strings   비교에서 테이블 제외, 쉼표로 구분된 값을 사용합니다.
      --include strings   비교에 일치하는 테이블만 포함, 쉼표로 구분된 값을 사용합니다.
  -h, --help              dbcmp 도움말
      --source string     소스 데이터베이스 dsn
      --target string     대상 데이터베이스 dsn
  -v, --version           dbcmp 버전
Note

--exclude--include 플래그는 상호 배타적이며 함께 사용할 수 없습니다.

우리의 경우 다음 명령을 간단히 실행할 수 있습니다:

dbcmp --source "${MYSQL_DSN}" --target "${POSTGRES_DSN} " --include="posts,users"

명령 예시는 다음과 같습니다: dbcmp --source "user:password@tcp(address:3306)/db_name --target "postgres://user:password@address:5432/db_name

Note

POSTGRES_DSNpostgres:// 접두사로 시작해야 합니다. 이를 통해 dbcmp가 데이터베이스에 연결할 때 사용할 드라이버를 결정합니다.

또 다른 제외 항목은 작은 차이(단일 마이그레이션 이름의 오타)가 있고 diff를 생성하는 db_migrations 테이블입니다. morph와 공식 mattermost 소스로 PostgreSQL 스키마를 생성했으므로 안심하고 건너뛸 수 있습니다. 한편 systems 테이블은 일부 마이그레이션 중에 추가된 추가 키가 있는 경우 추가적인 diff가 있을 수 있습니다. 문제가 발생하면 systems 테이블을 제외하고 systems 테이블의 데이터는 상대적으로 작으므로 수동으로 비교하는 것을 고려하세요.

Note

마이그레이션 중 remove-null-characters 변환 함수가 사용되고 MySQL 데이터베이스에 0x00 바이트 시퀀스가 있었다면 해당 테이블은 비교 단계에서 차이가 있을 것입니다.

검색 경로 복원#

pgloader 구성 파일(예: migration.load)을 자세히 살펴보면 데이터베이스 사용자의 search_pathpublic으로 설정된 것을 확인할 수 있습니다. 이것이 Mattermost 애플리케이션의 유일한 요구 사항입니다. 그러나 검색 경로에 다른 스키마를 포함해야 하는 경우 특정 요구 사항에 맞게 search_path를 수정해야 합니다.

플러그인 마이그레이션#

플러그인 측면에서는 위에서 한 것과 다른 접근 방식을 사용합니다. 이번에는 morph 도구를 사용하여 테이블과 인덱스를 생성하지 않을 것입니다. 대신 pgloader를 사용하여 테이블을 생성하겠습니다. 협업 플레이북과 Boards가 SQL 쿼리를 용이하게 하기 위해 애플리케이션 로직을 활용하기 때문입니다. 하지만 이 시점에서 애플리케이션 수준을 사용하지 않으려 합니다.

협업 플레이북#

플레이북에 제공된 pgloader 구성은 v1.38.1을 기준으로 하며 마이그레이션을 수행하려면 플러그인이 최소 v1.36.0 이상이어야 합니다.

마이그레이션 준비가 되면 pgloader를 실행하여 스키마데이터 마이그레이션을 시작할 수 있습니다.

데이터 마이그레이션의 기본 구성에 다음을 사용합니다:

LOAD DATABASE
 FROM      mysql://{{ .mysql_user }}:{{ .mysql_password }}@{{ .mysql_address }}/{{ .source_db }}
 INTO      pgsql://{{ .pg_user }}:{{ .pg_password }}@{{ .postgres_address }}/{{ .target_db }}

WITH include drop, create tables, create indexes, no foreign keys,
 workers = 8, concurrency = 1,
 multiple readers per thread, rows per range = 50000,
 preserve index names

SET PostgreSQL PARAMETERS
 maintenance_work_mem to '128MB',
 work_mem to '12MB'

SET MySQL PARAMETERS
 net_read_timeout  = '120',
 net_write_timeout = '120'

CAST column IR_ChannelAction.ActionType to text drop typemod,
 column IR_ChannelAction.TriggerType to text drop typemod,
 column IR_Incident.ChecklistsJSON to "json" drop typemod

INCLUDING ONLY TABLE NAMES MATCHING
 ~/IR_/

BEFORE LOAD DO
 $ ALTER SCHEMA public RENAME TO {{ .source_db }}; $

AFTER LOAD DO
 $ ALTER TABLE {{ .source_db }}.IR_ChannelAction ALTER COLUMN ActionType TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_ChannelAction ALTER COLUMN TriggerType TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN ReminderMessageTemplate TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN ReminderMessageTemplate SET DEFAULT ''::text;  $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN ConcatenatedInvitedUserIDs TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN ConcatenatedInvitedUserIDs SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN ConcatenatedWebhookOnCreationURLs TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN ConcatenatedWebhookOnCreationURLs SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN ConcatenatedInvitedGroupIDs TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN ConcatenatedInvitedGroupIDs SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN Retrospective TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN Retrospective SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN MessageOnJoin TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN MessageOnJoin SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN ConcatenatedWebhookOnStatusUpdateURLs TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN ConcatenatedWebhookOnStatusUpdateURLs SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN CategoryName TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN CategoryName SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN ConcatenatedBroadcastChannelIds TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN ConcatenatedBroadcastChannelIds SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN ChannelIDToRootID TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN ChannelIDToRootID SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ReminderMessageTemplate TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ReminderMessageTemplate SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ConcatenatedInvitedUserIDs TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ConcatenatedInvitedUserIDs SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ConcatenatedWebhookOnCreationURLs TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ConcatenatedWebhookOnCreationURLs SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ConcatenatedInvitedGroupIDs TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ConcatenatedInvitedGroupIDs SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN MessageOnJoin TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN MessageOnJoin SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN RetrospectiveTemplate TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN RetrospectiveTemplate SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ConcatenatedWebhookOnStatusUpdateURLs TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ConcatenatedWebhookOnStatusUpdateURLs SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ConcatenatedSignalAnyKeywords TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ConcatenatedSignalAnyKeywords SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN CategoryName TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN CategoryName SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ChecklistsJSON TYPE JSON USING ChecklistsJSON::JSON; $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ConcatenatedBroadcastChannelIds TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ConcatenatedBroadcastChannelIds SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN RunSummaryTemplate TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN RunSummaryTemplate SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ChannelNameTemplate TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ChannelNameTemplate SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_PlaybookMember ALTER COLUMN Roles TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Category_Item ADD CONSTRAINT ir_category_item_categoryid FOREIGN KEY (CategoryId) REFERENCES {{ .source_db }}.IR_Category(Id); $,
 $ ALTER TABLE {{ .source_db }}.IR_Metric ADD CONSTRAINT ir_metric_metricconfigid FOREIGN KEY (MetricConfigId) REFERENCES {{ .source_db }}.IR_MetricConfig(Id); $,
 $ ALTER TABLE {{ .source_db }}.IR_Metric ADD CONSTRAINT ir_metric_incidentid FOREIGN KEY (IncidentId) REFERENCES {{ .source_db }}.IR_Incident(Id); $,
 $ ALTER TABLE {{ .source_db }}.IR_MetricConfig ADD CONSTRAINT ir_metricconfig_playbookid FOREIGN KEY (PlaybookId) REFERENCES {{ .source_db }}.IR_Playbook(Id); $,
 $ ALTER TABLE {{ .source_db }}.IR_PlaybookAutoFollow ADD CONSTRAINT ir_playbookautofollow_playbookid FOREIGN KEY (PlaybookId) REFERENCES {{ .source_db }}.IR_Playbook(Id); $,
 $ ALTER TABLE {{ .source_db }}.IR_PlaybookMember ADD CONSTRAINT ir_playbookmember_playbookid FOREIGN KEY (PlaybookId) REFERENCES {{ .source_db }}.IR_Playbook(Id); $,
 $ ALTER TABLE {{ .source_db }}.IR_Run_Participants ADD CONSTRAINT ir_run_participants_incidentid FOREIGN KEY (IncidentId) REFERENCES {{ .source_db }}.IR_Incident(Id); $,
 $ ALTER TABLE {{ .source_db }}.IR_StatusPosts ADD CONSTRAINT ir_statusposts_incidentid FOREIGN KEY (IncidentId) REFERENCES {{ .source_db }}.IR_Incident(Id); $,
 $ ALTER TABLE {{ .source_db }}.IR_TimelineEvent ADD CONSTRAINT ir_timelineevent_incidentid FOREIGN KEY (IncidentId) REFERENCES {{ .source_db }}.IR_Incident(Id); $,
 $ CREATE UNIQUE INDEX IF NOT EXISTS ir_playbookmember_playbookid_memberid_key on {{ .source_db }}.IR_PlaybookMember(PlaybookId,MemberId); $,
 $ CREATE INDEX IF NOT EXISTS ir_statusposts_incidentid_postid_key on {{ .source_db }}.IR_StatusPosts(IncidentId,PostId); $,
 $ CREATE INDEX IF NOT EXISTS ir_playbookmember_playbookid on {{ .source_db }}.IR_PlaybookMember(PlaybookId); $,
 $ ALTER SCHEMA {{ .source_db }} RENAME TO public; $,
 $ SELECT pg_catalog.set_config('search_path', '"$user", public', false); $,
 $ ALTER USER {{ .pg_user }} SET SEARCH_PATH TO 'public'; $;
pgloader playbooks.load > playbooks_migration.log

Focalboard#

v9.0부터 Boards는 Focalboard 플러그인으로 완전히 커뮤니티 지원으로 전환됩니다. 따라서 이 가이드는 스키마 v7.10.x 버전만 다룹니다. 공식 발표.

마이그레이션 준비가 되면 pgloader를 실행하여 스키마데이터 마이그레이션을 시작할 수 있습니다.

데이터 마이그레이션의 기본 구성에 다음을 사용합니다:

LOAD DATABASE
 FROM      mysql://{{ .mysql_user }}:{{ .mysql_password }}@{{ .mysql_address }}/{{ .source_db }}
 INTO      pgsql://{{ .pg_user }}:{{ .pg_password }}@{{ .postgres_address }}/{{ .target_db }}

WITH include drop, create tables, create indexes, reset sequences,
 workers = 8, concurrency = 1,
 multiple readers per thread, rows per range = 50000,
 preserve index names

SET PostgreSQL PARAMETERS
 maintenance_work_mem to '128MB',
 work_mem to '12MB'

SET MySQL PARAMETERS
 net_read_timeout  = '120',
 net_write_timeout = '120'

CAST column focalboard_blocks.fields to "json" drop typemod,
 column focalboard_blocks_history.fields to "json" drop typemod,
 column focalboard_schema_migrations.name to "varchar" drop typemod,
 column focalboard_sessions.props to "json" drop typemod,
 column focalboard_teams.settings to "json" drop typemod,
 column focalboard_users.props to "json" drop typemod,
 type int when (= precision 11) to int4 drop typemod,
 type json to jsonb drop typemod

INCLUDING ONLY TABLE NAMES MATCHING
 ~/focalboard/

BEFORE LOAD DO
 $ ALTER SCHEMA public RENAME TO {{ .source_db }}; $

AFTER LOAD DO
 $ UPDATE {{ .source_db }}.focalboard_blocks SET "fields" = '{}'::json WHERE "fields"::text = ''; $,
 $ UPDATE {{ .source_db }}.focalboard_blocks_history SET "fields" = '{}'::json WHERE "fields"::text = ''; $,
 $ UPDATE {{ .source_db }}.focalboard_sessions SET "props" = '{}'::json WHERE "props"::text = ''; $, 
 $ UPDATE {{ .source_db }}.focalboard_teams SET "settings" = '{}'::json WHERE "settings"::text = ''; $,
 $ UPDATE {{ .source_db }}.focalboard_users SET "props" = '{}'::json WHERE "props"::text = ''; $, 
 $ ALTER SCHEMA {{ .source_db }} RENAME TO public; $,
 $ SELECT pg_catalog.set_config('search_path', '"$user", public', false); $,
 $ ALTER USER {{ .pg_user }} SET SEARCH_PATH TO 'public'; $;
pgloader focalboard.load > focalboard_migration.log

Calls#

v9.9보다 높은 Mattermost 버전 또는 v0.27 이상의 Calls 플러그인을 실행 중인 경우 플러그인 데이터를 마이그레이션할 수 있습니다. Boards 및 Playbooks 마이그레이션과 유사한 방식으로 접근하여 pgloader가 테이블을 생성하도록 합니다.

마이그레이션 준비가 되면 pgloader를 실행하여 스키마데이터 마이그레이션을 시작할 수 있습니다.

데이터 마이그레이션의 기본 구성에 다음을 사용합니다:

LOAD DATABASE
 FROM      mysql://{{ .mysql_user }}:{{ .mysql_password }}@{{ .mysql_address }}/{{ .source_db }}
 INTO      pgsql://{{ .pg_user }}:{{ .pg_password }}@{{ .postgres_address }}/{{ .target_db }}

WITH include drop, create tables, create indexes, reset sequences,
 workers = 8, concurrency = 1,
 multiple readers per thread, rows per range = 50000,
 preserve index names

SET PostgreSQL PARAMETERS
 maintenance_work_mem to '128MB',
 work_mem to '12MB'

SET MySQL PARAMETERS
 net_read_timeout  = '120',
 net_write_timeout = '120'

CAST type json to jsonb drop typemod

INCLUDING ONLY TABLE NAMES MATCHING
 ~/calls/

BEFORE LOAD DO
 $ ALTER SCHEMA public RENAME TO {{ .source_db }}; $

AFTER LOAD DO
 $ ALTER SCHEMA {{ .source_db }} RENAME TO public; $,
 $ SELECT pg_catalog.set_config('search_path', '"$user", public', false); $,
 $ ALTER USER {{ .pg_user }} SET SEARCH_PATH TO 'public'; $;
pgloader calls.load > calls_migration.log

트러블슈팅#

MySQL에서 PostgreSQL로 마이그레이션 중 오류 트러블슈팅 을 참조하세요.

MySQL에서 PostgreSQL로 수동 마이그레이션

원문 보기
요약

MySQL 데이터베이스를 PostgreSQL로 수동으로 마이그레이션하는 절차는 다음과 같습니다: 협업 플레이북 및 Boards 마이그레이션에 대한 자세한 내용은 플러그인 마이그레이션 섹션을 참조하세요. 수동 업그레이드가 적합한 방법인지 확실하지 않으신가요?

MySQL 데이터베이스를 PostgreSQL로 수동으로 마이그레이션하는 절차는 다음과 같습니다:

Tip

협업 플레이북 및 Boards 마이그레이션에 대한 자세한 내용은 플러그인 마이그레이션 섹션을 참조하세요.

수동 업그레이드가 적합한 방법인지 확실하지 않으신가요? Mattermost 배포에 맞춤 지침이 필요한 Mattermost 고객은 Mattermost 전문가 에게 문의할 수 있습니다.

도구 권장 사항#

Postgres로 수동 마이그레이션을 선호하는 경우, 마이그레이션 프로세스에 다음 도구를 권장합니다:

이 페이지에는 각 도구의 설치 방법과 데이터베이스 마이그레이션 진행 방법이 포함되어 있습니다.

필요한 도구를 설치한 후 시스템 요구 사항 및 구성 문서를 검토하고, 마이그레이션을 준비하기 위해 마이그레이션 전 무엇이 필요한지 파악하세요.

대상 데이터베이스 준비 부터 마이그레이션을 시작한 다음, 데이터 마이그레이션을 진행하고 모든 마이그레이션 후 단계를 완료하세요.

플레이북 및 Boards 마이그레이션에 대한 자세한 내용은 플러그인 마이그레이션 문서를 참조하세요.

pgloader#

pgloader 도구를 사용하여 MySQL에서 PostgreSQL로 데이터를 마이그레이션합니다.

pgloader 설치#

pgloader를 설치하려면 공식 설치 가이드를 참조하세요.

Note

MySQL v8을 사용하는 경우: pgloader 컴파일 바이너리의 알려진 버그 로 인해 소스에서 pgloader를 컴파일해야 합니다. 이 단계 에 따라 소스에서 빌드하세요.

또는 pgloader를 설치하거나 빌드하지 않고 mattermost-pgloader Docker 이미지를 사용할 수도 있습니다. 자세한 내용은 아래 문서를 참조하세요.

pgloader 사용#

Docker 이미지 pull 및 pgloader 검증

수동 마이그레이션을 위해 다음 명령을 실행하여 mattermost-pgloader 이미지를 pull하고 pgloader가 올바르게 작동하는지 확인합니다:

docker run -it --rm -v $(pwd):/home/migration mattermost/mattermost-pgloader:latest pgloader --version

이 명령은 mattermost/mattermost-pgloader:latest 이미지를 pull하고 pgloader를 실행하여 버전을 확인하고 예상대로 작동하는지 확인합니다.

로컬 디렉터리 매핑

-v $(pwd):/home/migration 플래그를 사용하여 현재 작업 디렉터리를 Docker 컨테이너에 매핑합니다. 이를 통해 로그 및 기타 파일 저장에 로컬 디렉터리를 사용할 수 있습니다.

네트워크 구성 설정

네트워크 요구 사항에 따라 --network 플래그를 설정합니다. 예를 들어 localhost에 접근하려면 네트워크를 host로 설정해야 합니다.

morph#

morph 도구는 PostgreSQL 스키마를 생성합니다.

Note

morphdbcmp 모두 Go 툴체인이 필요합니다. Go 컴파일러를 설치하려면 Go 문서를 따르세요.

morph 설치#

다음 명령을 실행하여 morph CLI를 설치할 수 있습니다:

go install github.com/mattermost/morph/cmd/morph@v1

dbcmp (선택)#

dbcmp 도구를 사용하면 마이그레이션 후 모든 테이블을 비교하고 두 스키마 간의 차이를 보고하여 데이터를 비교할 수 있습니다.

dbcmp 설치#

다음 명령을 실행하여 dbcmp를 설치할 수 있습니다:

go install github.com/mattermost/dbcmp/cmd/dbcmp@latest

수동 마이그레이션을 위한 시스템 요구 사항 및 구성#

수동 마이그레이션 프로세스를 시작하기 전에 시스템이 원활하고 효율적인 마이그레이션을 위한 필요 요구 사항을 충족하는지 확인해야 합니다. 다음 시스템 사양 및 조정을 강력히 권장합니다:

  • 충분한 시스템 메모리 리소스를 확보하세요. 기본값으로 16GB RAM을 권장합니다. 시스템 메모리가 부족한 경우 number of workers, prefetch rows, 특히 concurrency1 이상으로 설정된 경우 rows per range와 같은 pgloader 설정을 세부 조정할 수 있습니다. 이러한 조정은 사용 가능한 시스템 리소스에 따라 리소스 활용을 최적화하는 데 도움이 됩니다. 자세한 내용은 pgloader 문서를 참조하세요.
  • 마이그레이션 프로세스, 특히 대규모 데이터셋을 처리할 때 충분한 처리 성능을 갖춘 멀티코어 프로세서를 권장합니다.
  • MySQL 및 PostgreSQL 데이터베이스와 마이그레이션 프로세스 중 생성되는 임시 파일을 저장할 충분한 디스크 공간을 확보하세요. 필요한 디스크 공간은 마이그레이션되는 데이터베이스의 크기에 따라 다릅니다.
  • 마이그레이션 시간을 더 줄이려면 마이그레이션 프로세스를 시작하기 전에 대상 PostgreSQL 데이터베이스의 인덱스를 수동으로 삭제할 수 있습니다. 이 방법은 데이터 삽입 중 인덱스 빌드 오버헤드를 줄여 마이그레이션을 잠재적으로 가속화할 수 있습니다.

수동 마이그레이션 전#

Important

이 가이드는 v7.1 ESR 이상의 스키마가 필요합니다. 이전 버전이 있고 마이그레이션을 계획 중이라면 최소 v7.1로 Mattermost 서버를 업데이트하세요. 자세한 내용은 장기 지원 릴리즈 문서를 참조하세요.

    • MySQL 데이터를 백업합니다.
    • Mattermost 버전을 확인합니다. 자세한 내용은 정보 모달을 참조하세요.
    • 마이그레이션 기간을 예약합니다. 이 프로세스는 마이그레이션 중 Mattermost 서버를 중지해야 합니다.
    • 스키마 간 데이터 호환성을 위해 스키마 차이 섹션을 참조하세요.
    • 데이터베이스와 사용자를 생성하여 PostgreSQL 환경을 준비합니다. 자세한 내용은 데이터베이스 문서를 참조하세요.
    • PostgreSQL의 최신 버전에서 새로 생성된 사용자는 public 스키마에 대한 접근 권한이 없습니다. GRANT ALL ON SCHEMA public to mmuser를 실행하여 명시적으로 접근 권한을 부여해야 합니다.

스키마 차이#

수동 마이그레이션 전에 두 스키마의 차이로 인해 오류 없는 마이그레이션을 위해 몇 가지 수동 단계가 필요할 수 있습니다.

텍스트에서 가변 문자로#

Mattermost MySQL 스키마가 다양한 테이블에서 PostgreSQL 스키마의 varchar 표현 대신 text 컬럼 유형을 사용하므로 PostgreSQL 스키마 제한 내에서 크기가 일관되는지 확인하는 것을 권장합니다.

필요한 삭제 또는 업데이트가 있는지 확인할 수 있습니다. 예를 들어 Audits 테이블/Action 컬럼에서 확인하려면 다음을 실행합니다:

SELECT FROM mattermost.Audits where LENGTH(Action) > 512;

다음 테이블은 추가적인 결과 없이 진행할 수 있는 삭제 또는 업데이트를 보여줍니다.

테이블컬럼데이터 유형 변환삭제 시 결과
AuditsActiontext -> varchar(512)애플리케이션 작동에 부작용 없음 (영향받는 행을 삭제해야 함).
AuditsExtraInfotext -> varchar(1024)애플리케이션 작동에 부작용 없음 (영향받는 행을 삭제해야 함).
ClusterDiscoveryHostNametext -> varchar(512)애플리케이션 작동에 부작용 없음 (영향받는 행을 삭제해야 함).
CommandsIconURLtext -> varchar(1024)필드를 삭제하거나 새 URL로 업데이트할 수 있음.
CommandsAutoCompleteDesctext -> varchar(1024)필드를 삭제하거나 다시 작성할 수 있음.
CommandsAutoCompleteHinttext -> varchar(1024)필드를 삭제하거나 다시 작성할 수 있음.
RemoteClustersTopicstext -> varchar(512)필드를 제거할 수 있음.
SystemsValuetext -> varchar(1024)극히 드문 경우, 이상적으로는 발생하지 않아야 함.

다음 테이블은 스키마가 다를 수 있고 PostgreSQL 스키마 내의 데이터 크기 제약으로 인해 오류가 발생할 수 있는 여러 경우를 보여줍니다. 각 테이블/행에는 개별 검사가 필요하므로 삭제의 가능한 결과를 추가했습니다.

Tip

커뮤니티로부터 LinkMetadataFileInfo 테이블에 일부 오버플로가 있었다는 보고가 다수 접수되었으므로 특히 이 테이블을 확인하는 것을 권장합니다. MySQL 스키마의 데이터가 이러한 제한을 초과하는지 확인하세요.

테이블컬럼데이터 유형 변환삭제 시 결과
CompliancesKeywordstext -> varchar(512)컴플라이언스 필터를 업데이트해야 함.
CompliancesEmailstext -> varchar(1024)컴플라이언스 필터를 업데이트해야 함.
FileInfoPathtext -> varchar(512)이 파일에 대한 이전 링크가 작동하지 않음 (영향받는 행을 삭제해야 함).
FileInfoThumbnailPathtext -> varchar(512)이 파일에 대한 이전 링크가 작동하지 않음 (영향받는 행을 삭제해야 함).
FileInfoPreviewPathtext -> varchar(512)이 파일에 대한 이전 링크가 작동하지 않음 (영향받는 행을 삭제해야 함).
FileInfoNametext -> varchar(256)이 파일에 대한 이전 링크가 작동하지 않음 (영향받는 행을 삭제해야 함).
FileInfoMimeTypetext -> varchar(256)이 파일에 대한 이전 링크가 작동하지 않음 (영향받는 행을 삭제해야 함).
LinkMetadataURLtext -> varchar(2048)이 파일에 대한 이전 링크가 작동하지 않음 (영향받는 행을 삭제해야 함).
RemoteClustersSiteURLtext -> varchar(512)이전 원격 클러스터가 제거됨 (영향받는 행을 삭제해야 함).
SessionsDeviceIdtext -> varchar(512)해당 기기의 사용자가 로그아웃됨 (영향받는 행을 삭제해야 함).
UploadSessionsFileNametext -> varchar(256)업로드 세션이 손실됨 (영향받는 행을 삭제해야 함).
UploadSessionsPathtext -> varchar(512)업로드 세션이 손실됨 (영향받는 행을 삭제해야 함).

전체 텍스트 인덱스#

PostsFileInfo 테이블의 일부 단어가 전체 텍스트 검색 인덱싱의 최대 토큰 길이 제한을 초과할 수 있습니다. 이 경우 PostgreSQL 스키마에서 idx_posts_message_txtidx_fileinfo_content_txt 인덱스를 삭제하고, 마이그레이션 후 다음 쿼리를 실행하여 이 인덱스를 생성합니다:

마이그레이션 중 오류를 방지하기 위해 다음 쿼리가 포함되어 있습니다:

DROP INDEX IF EXISTS {{ .source_db }}.idx_posts_message_txt;
DROP INDEX IF EXISTS {{ .source_db }}.idx_fileinfo_content_txt;

지원되지 않는 유니코드 시퀀스#

PostgreSQL에서 허용되지 않는 특정 유니코드 시퀀스가 있는데, 바로 입니다. 이 시퀀스가 MySQL 데이터베이스의 여러 테이블의 여러 행에 나타날 수 있습니다. 해당 경우 마이그레이션 중 다음과 같은 오류가 발생할 수 있습니다: unsupported Unicode escape sequence: cannot be converted to text.. 이를 방지하려면 마이그레이션을 시작하기 전에 데이터를 정리하는 것이 좋습니다. 다음 쿼리를 사용하여 시퀀스를 빈 문자열로 교체할 수 있습니다.

Note

이 쿼리를 스크립트에서 그대로 사용하거나 MySQL 콘솔에서 정의할 때 구분 기호를 다른 것으로 설정해야 할 수 있습니다(예: DELIMITER //). 프로시저 정의가 완료되면 구분 기호를 원래대로 재설정하세요(예: DELIMITER ;).

CREATE PROCEDURE SanitizeUnsupportedUnicode()
BEGIN
 DECLARE done INT DEFAULT FALSE;
 DECLARE curTableName text;
 DECLARE curColumnName text;
 DECLARE cursors CURSOR FOR
    SELECT table_name, column_name
    FROM information_schema.COLUMNS
    WHERE data_type = 'json'
    AND table_schema = DATABASE();
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;

OPEN cursors;

WHILE NOT DONE DO
 FETCH cursors INTO curTableName, curColumnName;
 SET @query_string = CONCAT('UPDATE ', curTableName, ' SET ', curColumnName, ' = REPLACE(', curColumnName, ', \'\\\\u0000\', \'\') WHERE ', curColumnName, ' LIKE \'%\\u0000%\';');

 PREPARE dynamic_query FROM @query_string;
 EXECUTE dynamic_query;
 DEALLOCATE PREPARE dynamic_query;
END WHILE;

CLOSE cursors;
END;

CALL SanitizeUnsupportedUnicode();

DROP PROCEDURE IF EXISTS SanitizeUnsupportedUnicode;
Note

마이그레이션 중 invalid byte sequence for encoding 'UTF8': 0x00" 오류를 일으키는 허용되지 않는 특정 바이트 시퀀스 값도 있습니다. 이 오류를 방지하려면 텍스트 캐스팅 규칙에 remove-null-characters 절을 추가할 수 있습니다. 그러나 pgloader가 데이터를 즉시 수정하므로 비교 단계에서 (영향받은 경우) 테이블 간에 차이가 있을 수 있습니다.

이전 구성/버전의 아티팩트가 남아있을 수 있음#

v6.4 이전에는 Mattermost가 스키마 마이그레이션 처리에 golang-migrate를 사용했습니다. 더 이상 사용하지 않으므로 schema_migrations 테이블을 제외합니다. v6.4 이전부터 Mattermost를 사용했다면 이 테이블을 삭제하고 비교에서 제외하는 것을 고려하세요.

DROP TABLE mattermost.schema_migrations;

일부 커뮤니티 멤버는 SharedChannelRemotes 테이블에 descriptionnextsyncat 컬럼이 있었다고 보고했습니다. 이 컬럼들은 테이블에서 제거해야 합니다. 다음 DDL을 실행하여 컬럼을 삭제하는 것을 고려하세요. (이 마이그레이션은 Mattermost의 이후 버전에 추가될 예정입니다.)

ALTER TABLE SharedChannelRemotes DROP COLUMN description, DROP COLUMN nextsyncat;

이전에 릴리즈된 96번째 마이그레이션에 오류가 확인되었습니다. 마이그레이션을 진행하기 전에 특정 컬럼을 제거해야 합니다. Threads 테이블이 예상된 상태가 되도록 다음 준비된 구문을 실행하세요:

SET @preparedStatement = (SELECT IF(
 (
    SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
    WHERE table_name = 'Threads'
    AND table_schema = DATABASE()
    AND column_name = 'TeamId'
 ) > 0,
 'ALTER TABLE Threads DROP COLUMN TeamId;',
 'SELECT 1'
));

PREPARE alterIfExists FROM @preparedStatement;
EXECUTE alterIfExists;
DEALLOCATE PREPARE alterIfExists;

데이터베이스 내 구성#

Mattermost 구성을 처리하기 위해 데이터베이스를 이전에 사용했다면, 해당 테이블은 마이그레이션 스크립트 로 MySQL 데이터베이스에서 마이그레이션되지 않습니다.

두 가지 마이그레이션이 필요합니다:

  • 데이터베이스 구성을 파일 시스템으로 마이그레이션
  • 파일 시스템 구성을 다시 데이터베이스로 마이그레이션
데이터베이스 구성을 파일 시스템으로 마이그레이션

다음과 같이 mmctl config migrate 명령을 사용하여 구성을 파일 시스템으로 마이그레이션합니다:

mmctl config migrate "<DB_USER>:<DB_PASS>@tcp(<DB_HOST>:3306)/<DB_NAME>?charset=utf8mb4,utf8&readTimeout=30s&writeTimeout=30s&multiStatements=true" /opt/mattermost/config/config.json --local

여기서 <DB_USER>, <DB_PASS>, <DB_HOST>, <DB_NAME>을 실제 환경 값으로 교체합니다. 이 명령을 실행할 때 반드시 --local을 사용하세요. 첫 번째 매개변수(<DB_USER>, <DB_PASS>)는 구성이 저장된 데이터베이스이고, 두 번째 매개변수(<DB_HOST>, <DB_NAME>)는 구성을 저장할 파일입니다.

구성 파일에서 새 변경 사항을 반영하도록 SqlSettings.DataSourceSqlSettings.DriverName 필드를 업데이트합니다. json 파일을 열고 해당 필드를 변경합니다.

파일 시스템 구성을 다시 데이터베이스로 마이그레이션

구성을 다시 데이터베이스에 저장하려면 mmctl config migrate 명령을 다시 사용하여 매개변수를 반대로 합니다. 대상 데이터베이스로 다시 이동할 때 새 데이터베이스 자격 증명을 사용하세요.

SELECT * FROM Configurations WHERE Active = 't';

SqlSettings.DataSourceSqlSettings.DriverName 필드를 적절히 업데이트해야 합니다. 또한 마이그레이션이 완료된 후 MM_CONFIG 환경 변수가 새 DSN을 가리켜야 합니다.

대상 데이터베이스 준비#

필요한 사양에 따라 PostgreSQL 데이터베이스 스키마가 올바르게 구조화되도록 테이블과 인덱스를 생성하는 것이 중요합니다. Mattermost 저장소에 필요한 모든 SQL 쿼리가 포함되어 있으므로 다음 단계를 실행하여 이를 활용할 수 있습니다:

  • 특정 버전에 맞는 mattermost 저장소를 복제합니다:
git clone -b <현재 버전 (예: release-7.8)> git@github.com:mattermost/mattermost.git --depth=1
  • 다음 명령으로 morph CLI를 사용하여 PostgreSQL 데이터베이스에서 모든 스키마 마이그레이션*을 실행합니다:
morph apply up --driver postgres --dsn "postgres://user:pass@localhost:5432/<target_db_mame>?sslmode=disable" --path ./mattermost/db/migrations/postgres --number -1

* v8 이후 프로젝트 재구성으로 인해 마이그레이션 디렉터리가 ./mattermost/server/channels/db/migrations/postgres/로 변경되었습니다. Mattermost 저장소를 복제한 위치 기준으로 --path 플래그를 적절히 설정하세요.

데이터 마이그레이션#

스키마를 원하는 상태로 설정했으면 pgloader를 실행하여 데이터 마이그레이션을 시작할 수 있습니다.

Note

아래 예시에서 두 데이터베이스의 호스트는 동일한 인스턴스에 있다고 가정합니다. 다른 머신에 있는 경우 주소를 업데이트하세요. 또한 .load 파일을 --dry-run 플래그를 사용하여 테스트할 수 있습니다. 예를 들어 pgloader --dry-run migration.load 명령입니다.

데이터 마이그레이션의 기본 구성에 다음을 사용합니다:

LOAD DATABASE
 FROM      mysql://{{ .mysql_user }}:{{ .mysql_password }}@{{ .mysql_address }}/{{ .source_db }}
 INTO      pgsql://{{ .pg_user }}:{{ .pg_password }}@{{ .postgres_address }}/{{ .target_db }}

WITH data only,
 workers = 8, concurrency = 1,
 multiple readers per thread, rows per range = 10000,
 prefetch rows = 10000, batch rows = 2500,
 create no tables, create no indexes,
 preserve index names

SET PostgreSQL PARAMETERS
 maintenance_work_mem to '128MB',
 work_mem to '12MB'

SET MySQL PARAMETERS
 net_read_timeout  = '120',
 net_write_timeout = '120'

CAST column Channels.Type to "channel_type" drop typemod,
 column Teams.Type to "team_type" drop typemod,
 column UploadSessions.Type to "upload_session_type" drop typemod,
 column ChannelBookmarks.Type to "channel_bookmark_type" drop typemod,
 column Drafts.Priority to text,
 type int when (= precision 11) to integer drop typemod,
 type bigint when (= precision 20) to bigint drop typemod,
 type text to varchar drop typemod using remove-null-characters,
 type tinyint when (<= precision 4) to boolean using tinyint-to-boolean,
 type json to jsonb drop typemod using remove-null-characters

EXCLUDING TABLE NAMES MATCHING ~<IR_>, ~<focalboard>, 'schema_migrations', 'db_migrations', 'db_lock',
 'Configurations', 'ConfigurationFiles', 'db_config_migrations', 'calls'

BEFORE LOAD DO
 $ ALTER SCHEMA public RENAME TO {{ .source_db }}; $,
 $ TRUNCATE TABLE {{ .source_db }}.systems; $,
 $ DROP INDEX IF EXISTS {{ .source_db }}.idx_posts_message_txt; $,
 $ DROP INDEX IF EXISTS {{ .source_db }}.idx_fileinfo_content_txt; $

AFTER LOAD DO
 $ UPDATE {{ .source_db }}.db_migrations set name='add_createat_to_teamembers' where version=92; $,
 $ ALTER SCHEMA {{ .source_db }} RENAME TO public; $,
 $ SELECT pg_catalog.set_config('search_path', '"$user", public', false); $,
 $ ALTER USER {{ .pg_user }} SET SEARCH_PATH TO 'public'; $;

이 구성 파일(예: migration.load)을 저장한 후 다음 명령으로 pgloader를 실행할 수 있습니다:

pgloader migration.load > migration.log

마이그레이션 결과를 자유롭게 기여하거나 보고해 주세요.

마이그레이션 후#

전체 텍스트 인덱스 복원#

PostsFileInfo 테이블 접근의 성능 저하를 방지하려면 마이그레이션이 완료된 후 다음 쿼리를 실행해야 합니다:

CREATE INDEX IF NOT EXISTS idx_posts_message_txt ON public.posts USING gin(to_tsvector('english', message));
CREATE INDEX IF NOT EXISTS idx_fileinfo_content_txt ON public.fileinfo USING gin(to_tsvector('english', content));
Note

PostsFileInfo 테이블의 항목 중 일부가 위에서 언급한 제한을 초과하는 경우 인덱스 생성 쿼리는 ERROR: string is too long for tsvector 로그와 함께 경고를 표시합니다. 이는 tsvector에 맞지 않는 내용이 무시되었음을 의미합니다. 잘린 내용을 여전히 인덱싱하려면 인덱스 생성 시 내용에 substring() 함수를 사용할 수 있습니다.

1000000의 substring 길이부터 시작하여 오류가 지속되면 점차 줄입니다:

-- 1000000부터 시작
CREATE INDEX IF NOT EXISTS idx_fileinfo_content_txt ON public.fileinfo USING gin(to_tsvector('english', substring(content,0,1000000)));

-- 실패하면 800000 시도
CREATE INDEX IF NOT EXISTS idx_fileinfo_content_txt ON public.fileinfo USING gin(to_tsvector('english', substring(content,0,800000)));

-- 필요에 따라 계속 줄임 (예: 600000, 500000) 인덱스가 성공적으로 생성될 때까지

데이터 비교#

데이터베이스 비교를 단순화하기 위해 dbcmp라는 내부 도구를 개발했습니다. 두 데이터베이스의 모든 테이블을 확인하고 스키마 차이를 보고합니다. 그러나 dbcmp는 개별 행을 비교하지 않습니다. 대신 지정된 페이지 크기를 기반으로 체크섬 값을 계산하고 비교합니다. 따라서 행 수준의 차이를 생성할 수 없습니다.

마이그레이션 중 (기본값이나 당사가 제공한 것 이외의) 사용자 지정 캐스팅 규칙이 사용된 경우 데이터 무결성을 확인하는 추가 검사로 dbcmp를 사용하는 것을 권장합니다. 그렇지 않으면 이 도구를 실행할 필요가 없습니다.

이 도구에는 비교를 실행하는 몇 가지 플래그가 있습니다:

Usage:
  dbcmp [flags]

Flags:
      --exclude strings   비교에서 테이블 제외, 쉼표로 구분된 값을 사용합니다.
      --include strings   비교에 일치하는 테이블만 포함, 쉼표로 구분된 값을 사용합니다.
  -h, --help              dbcmp 도움말
      --source string     소스 데이터베이스 dsn
      --target string     대상 데이터베이스 dsn
  -v, --version           dbcmp 버전
Note

--exclude--include 플래그는 상호 배타적이며 함께 사용할 수 없습니다.

우리의 경우 다음 명령을 간단히 실행할 수 있습니다:

dbcmp --source "${MYSQL_DSN}" --target "${POSTGRES_DSN} " --include="posts,users"

명령 예시는 다음과 같습니다: dbcmp --source "user:password@tcp(address:3306)/db_name --target "postgres://user:password@address:5432/db_name

Note

POSTGRES_DSNpostgres:// 접두사로 시작해야 합니다. 이를 통해 dbcmp가 데이터베이스에 연결할 때 사용할 드라이버를 결정합니다.

또 다른 제외 항목은 작은 차이(단일 마이그레이션 이름의 오타)가 있고 diff를 생성하는 db_migrations 테이블입니다. morph와 공식 mattermost 소스로 PostgreSQL 스키마를 생성했으므로 안심하고 건너뛸 수 있습니다. 한편 systems 테이블은 일부 마이그레이션 중에 추가된 추가 키가 있는 경우 추가적인 diff가 있을 수 있습니다. 문제가 발생하면 systems 테이블을 제외하고 systems 테이블의 데이터는 상대적으로 작으므로 수동으로 비교하는 것을 고려하세요.

Note

마이그레이션 중 remove-null-characters 변환 함수가 사용되고 MySQL 데이터베이스에 0x00 바이트 시퀀스가 있었다면 해당 테이블은 비교 단계에서 차이가 있을 것입니다.

검색 경로 복원#

pgloader 구성 파일(예: migration.load)을 자세히 살펴보면 데이터베이스 사용자의 search_pathpublic으로 설정된 것을 확인할 수 있습니다. 이것이 Mattermost 애플리케이션의 유일한 요구 사항입니다. 그러나 검색 경로에 다른 스키마를 포함해야 하는 경우 특정 요구 사항에 맞게 search_path를 수정해야 합니다.

플러그인 마이그레이션#

플러그인 측면에서는 위에서 한 것과 다른 접근 방식을 사용합니다. 이번에는 morph 도구를 사용하여 테이블과 인덱스를 생성하지 않을 것입니다. 대신 pgloader를 사용하여 테이블을 생성하겠습니다. 협업 플레이북과 Boards가 SQL 쿼리를 용이하게 하기 위해 애플리케이션 로직을 활용하기 때문입니다. 하지만 이 시점에서 애플리케이션 수준을 사용하지 않으려 합니다.

협업 플레이북#

플레이북에 제공된 pgloader 구성은 v1.38.1을 기준으로 하며 마이그레이션을 수행하려면 플러그인이 최소 v1.36.0 이상이어야 합니다.

마이그레이션 준비가 되면 pgloader를 실행하여 스키마데이터 마이그레이션을 시작할 수 있습니다.

데이터 마이그레이션의 기본 구성에 다음을 사용합니다:

LOAD DATABASE
 FROM      mysql://{{ .mysql_user }}:{{ .mysql_password }}@{{ .mysql_address }}/{{ .source_db }}
 INTO      pgsql://{{ .pg_user }}:{{ .pg_password }}@{{ .postgres_address }}/{{ .target_db }}

WITH include drop, create tables, create indexes, no foreign keys,
 workers = 8, concurrency = 1,
 multiple readers per thread, rows per range = 50000,
 preserve index names

SET PostgreSQL PARAMETERS
 maintenance_work_mem to '128MB',
 work_mem to '12MB'

SET MySQL PARAMETERS
 net_read_timeout  = '120',
 net_write_timeout = '120'

CAST column IR_ChannelAction.ActionType to text drop typemod,
 column IR_ChannelAction.TriggerType to text drop typemod,
 column IR_Incident.ChecklistsJSON to "json" drop typemod

INCLUDING ONLY TABLE NAMES MATCHING
 ~/IR_/

BEFORE LOAD DO
 $ ALTER SCHEMA public RENAME TO {{ .source_db }}; $

AFTER LOAD DO
 $ ALTER TABLE {{ .source_db }}.IR_ChannelAction ALTER COLUMN ActionType TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_ChannelAction ALTER COLUMN TriggerType TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN ReminderMessageTemplate TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN ReminderMessageTemplate SET DEFAULT ''::text;  $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN ConcatenatedInvitedUserIDs TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN ConcatenatedInvitedUserIDs SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN ConcatenatedWebhookOnCreationURLs TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN ConcatenatedWebhookOnCreationURLs SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN ConcatenatedInvitedGroupIDs TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN ConcatenatedInvitedGroupIDs SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN Retrospective TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN Retrospective SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN MessageOnJoin TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN MessageOnJoin SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN ConcatenatedWebhookOnStatusUpdateURLs TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN ConcatenatedWebhookOnStatusUpdateURLs SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN CategoryName TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN CategoryName SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN ConcatenatedBroadcastChannelIds TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN ConcatenatedBroadcastChannelIds SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN ChannelIDToRootID TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Incident ALTER COLUMN ChannelIDToRootID SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ReminderMessageTemplate TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ReminderMessageTemplate SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ConcatenatedInvitedUserIDs TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ConcatenatedInvitedUserIDs SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ConcatenatedWebhookOnCreationURLs TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ConcatenatedWebhookOnCreationURLs SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ConcatenatedInvitedGroupIDs TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ConcatenatedInvitedGroupIDs SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN MessageOnJoin TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN MessageOnJoin SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN RetrospectiveTemplate TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN RetrospectiveTemplate SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ConcatenatedWebhookOnStatusUpdateURLs TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ConcatenatedWebhookOnStatusUpdateURLs SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ConcatenatedSignalAnyKeywords TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ConcatenatedSignalAnyKeywords SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN CategoryName TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN CategoryName SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ChecklistsJSON TYPE JSON USING ChecklistsJSON::JSON; $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ConcatenatedBroadcastChannelIds TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ConcatenatedBroadcastChannelIds SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN RunSummaryTemplate TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN RunSummaryTemplate SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ChannelNameTemplate TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Playbook ALTER COLUMN ChannelNameTemplate SET DEFAULT ''::text; $,
 $ ALTER TABLE {{ .source_db }}.IR_PlaybookMember ALTER COLUMN Roles TYPE varchar(65536); $,
 $ ALTER TABLE {{ .source_db }}.IR_Category_Item ADD CONSTRAINT ir_category_item_categoryid FOREIGN KEY (CategoryId) REFERENCES {{ .source_db }}.IR_Category(Id); $,
 $ ALTER TABLE {{ .source_db }}.IR_Metric ADD CONSTRAINT ir_metric_metricconfigid FOREIGN KEY (MetricConfigId) REFERENCES {{ .source_db }}.IR_MetricConfig(Id); $,
 $ ALTER TABLE {{ .source_db }}.IR_Metric ADD CONSTRAINT ir_metric_incidentid FOREIGN KEY (IncidentId) REFERENCES {{ .source_db }}.IR_Incident(Id); $,
 $ ALTER TABLE {{ .source_db }}.IR_MetricConfig ADD CONSTRAINT ir_metricconfig_playbookid FOREIGN KEY (PlaybookId) REFERENCES {{ .source_db }}.IR_Playbook(Id); $,
 $ ALTER TABLE {{ .source_db }}.IR_PlaybookAutoFollow ADD CONSTRAINT ir_playbookautofollow_playbookid FOREIGN KEY (PlaybookId) REFERENCES {{ .source_db }}.IR_Playbook(Id); $,
 $ ALTER TABLE {{ .source_db }}.IR_PlaybookMember ADD CONSTRAINT ir_playbookmember_playbookid FOREIGN KEY (PlaybookId) REFERENCES {{ .source_db }}.IR_Playbook(Id); $,
 $ ALTER TABLE {{ .source_db }}.IR_Run_Participants ADD CONSTRAINT ir_run_participants_incidentid FOREIGN KEY (IncidentId) REFERENCES {{ .source_db }}.IR_Incident(Id); $,
 $ ALTER TABLE {{ .source_db }}.IR_StatusPosts ADD CONSTRAINT ir_statusposts_incidentid FOREIGN KEY (IncidentId) REFERENCES {{ .source_db }}.IR_Incident(Id); $,
 $ ALTER TABLE {{ .source_db }}.IR_TimelineEvent ADD CONSTRAINT ir_timelineevent_incidentid FOREIGN KEY (IncidentId) REFERENCES {{ .source_db }}.IR_Incident(Id); $,
 $ CREATE UNIQUE INDEX IF NOT EXISTS ir_playbookmember_playbookid_memberid_key on {{ .source_db }}.IR_PlaybookMember(PlaybookId,MemberId); $,
 $ CREATE INDEX IF NOT EXISTS ir_statusposts_incidentid_postid_key on {{ .source_db }}.IR_StatusPosts(IncidentId,PostId); $,
 $ CREATE INDEX IF NOT EXISTS ir_playbookmember_playbookid on {{ .source_db }}.IR_PlaybookMember(PlaybookId); $,
 $ ALTER SCHEMA {{ .source_db }} RENAME TO public; $,
 $ SELECT pg_catalog.set_config('search_path', '"$user", public', false); $,
 $ ALTER USER {{ .pg_user }} SET SEARCH_PATH TO 'public'; $;
pgloader playbooks.load > playbooks_migration.log

Focalboard#

v9.0부터 Boards는 Focalboard 플러그인으로 완전히 커뮤니티 지원으로 전환됩니다. 따라서 이 가이드는 스키마 v7.10.x 버전만 다룹니다. 공식 발표.

마이그레이션 준비가 되면 pgloader를 실행하여 스키마데이터 마이그레이션을 시작할 수 있습니다.

데이터 마이그레이션의 기본 구성에 다음을 사용합니다:

LOAD DATABASE
 FROM      mysql://{{ .mysql_user }}:{{ .mysql_password }}@{{ .mysql_address }}/{{ .source_db }}
 INTO      pgsql://{{ .pg_user }}:{{ .pg_password }}@{{ .postgres_address }}/{{ .target_db }}

WITH include drop, create tables, create indexes, reset sequences,
 workers = 8, concurrency = 1,
 multiple readers per thread, rows per range = 50000,
 preserve index names

SET PostgreSQL PARAMETERS
 maintenance_work_mem to '128MB',
 work_mem to '12MB'

SET MySQL PARAMETERS
 net_read_timeout  = '120',
 net_write_timeout = '120'

CAST column focalboard_blocks.fields to "json" drop typemod,
 column focalboard_blocks_history.fields to "json" drop typemod,
 column focalboard_schema_migrations.name to "varchar" drop typemod,
 column focalboard_sessions.props to "json" drop typemod,
 column focalboard_teams.settings to "json" drop typemod,
 column focalboard_users.props to "json" drop typemod,
 type int when (= precision 11) to int4 drop typemod,
 type json to jsonb drop typemod

INCLUDING ONLY TABLE NAMES MATCHING
 ~/focalboard/

BEFORE LOAD DO
 $ ALTER SCHEMA public RENAME TO {{ .source_db }}; $

AFTER LOAD DO
 $ UPDATE {{ .source_db }}.focalboard_blocks SET "fields" = '{}'::json WHERE "fields"::text = ''; $,
 $ UPDATE {{ .source_db }}.focalboard_blocks_history SET "fields" = '{}'::json WHERE "fields"::text = ''; $,
 $ UPDATE {{ .source_db }}.focalboard_sessions SET "props" = '{}'::json WHERE "props"::text = ''; $, 
 $ UPDATE {{ .source_db }}.focalboard_teams SET "settings" = '{}'::json WHERE "settings"::text = ''; $,
 $ UPDATE {{ .source_db }}.focalboard_users SET "props" = '{}'::json WHERE "props"::text = ''; $, 
 $ ALTER SCHEMA {{ .source_db }} RENAME TO public; $,
 $ SELECT pg_catalog.set_config('search_path', '"$user", public', false); $,
 $ ALTER USER {{ .pg_user }} SET SEARCH_PATH TO 'public'; $;
pgloader focalboard.load > focalboard_migration.log

Calls#

v9.9보다 높은 Mattermost 버전 또는 v0.27 이상의 Calls 플러그인을 실행 중인 경우 플러그인 데이터를 마이그레이션할 수 있습니다. Boards 및 Playbooks 마이그레이션과 유사한 방식으로 접근하여 pgloader가 테이블을 생성하도록 합니다.

마이그레이션 준비가 되면 pgloader를 실행하여 스키마데이터 마이그레이션을 시작할 수 있습니다.

데이터 마이그레이션의 기본 구성에 다음을 사용합니다:

LOAD DATABASE
 FROM      mysql://{{ .mysql_user }}:{{ .mysql_password }}@{{ .mysql_address }}/{{ .source_db }}
 INTO      pgsql://{{ .pg_user }}:{{ .pg_password }}@{{ .postgres_address }}/{{ .target_db }}

WITH include drop, create tables, create indexes, reset sequences,
 workers = 8, concurrency = 1,
 multiple readers per thread, rows per range = 50000,
 preserve index names

SET PostgreSQL PARAMETERS
 maintenance_work_mem to '128MB',
 work_mem to '12MB'

SET MySQL PARAMETERS
 net_read_timeout  = '120',
 net_write_timeout = '120'

CAST type json to jsonb drop typemod

INCLUDING ONLY TABLE NAMES MATCHING
 ~/calls/

BEFORE LOAD DO
 $ ALTER SCHEMA public RENAME TO {{ .source_db }}; $

AFTER LOAD DO
 $ ALTER SCHEMA {{ .source_db }} RENAME TO public; $,
 $ SELECT pg_catalog.set_config('search_path', '"$user", public', false); $,
 $ ALTER USER {{ .pg_user }} SET SEARCH_PATH TO 'public'; $;
pgloader calls.load > calls_migration.log

트러블슈팅#

MySQL에서 PostgreSQL로 마이그레이션 중 오류 트러블슈팅 을 참조하세요.