Mattermost を Debian stable (Debian 12 bookworm) で運用しているのだけど,Debian では MySQL は Official には提供されていなくて,代わりに MariaDB を使うことになっている.
Mattermost 自体は RDB として MySQL と PostgreSQL をサポートしているのだけど,現在のバージョンである v10 からは MySQL を利用した新規 install はサポート外となり,また,次期バージョンである v11 で MySQL のサポートは削除される見込みとなっている.
ということで,そのうち MariaDB から PostgreSQL に移行する必要があったので,頑張って移行した.なお,MariaDB を利用していると MySQL との互換性の問題でさまざまな問題が発生しているのも,今回の移行を促す要因になった.たとえば,MariaDB を利用していると以下の様な問題がある.
-
ハッシュタグを利用した検索が機能しない.#定期
ではなく "#定期"
のように文字列として検索する必要がある("
で括るという Workaround でなんとなかっていたので深追いしていない).
-
Mattermost 10.2 以降で Mobile App への Push Notification が動作しない.以下のようなエラーが出て Push Notification 不可(メールの Notification,Browser への Notification は問題なし).
{"timestamp":"2025-01-XX XX:XX:XX.XXX +09:00","level":"error","msg":"Failed to send mobile app sessions","caller":"app/notification_push.go:123","logSource":"notifications","type":"push","status":"error","reason":"fetch_error","user_id":"(snip)","error":"getMobileAppSessions: We encountered an error while finding user sessions., failed to find Sessions with userId=(snip): Error 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '>>'$.last_removed_device_id', '')' at line 9"}
移行方法(MySQL から PostgreSQL)は https://docs.mattermost.com/deploy/postgres-migration.htmlに詳細が記載されている.最近は Automated PostgreSQL migration が用意されていて,かなり自動化されている.以前は用意されていなかったので,一度 manual migration のリハーサルをしていたので,それを思い出しつつ作業.
なお,同じサーバで MariaDB を利用し続けたいので(WordPressなどで利用),PostgreSQL は Docker で動作させる.
準備
基本的には database schema の調整をして,pgloaderで MySQL (MariaDB) から読み出しつつ PostgreSQL に書き込んでいく感じ.
- 必要な package の install
sudo apt install postgresql-client mariadb-client pgloader
- Docker 環境の準備
migration-assist
の準備
以下では Data Source Name (DSN) を何度も使うので,変数にしておく.以下のような感じ.PostgreSQL と MySQL (MariaDB) で微妙に syntax が異なるので注意.
PDSN="postgres://mmuser:mmuser_password@127.0.0.1:5432/mattermost?sslmode=disable"
MDSN="mmuser:mmuser_password@tcp(127.0.0.1:3306)/mattermost"
なお以下を想定.
- database name:
mattermost
- database user:
mmuser
- database password:
mmuser_password
- PostgreSQL server:
localhost:5432
- MySQL (MariaDB) server:
localhost:3306
Step 1 - Check the MySQL database schema
https://docs.mattermost.com/deploy/postgres-migration-assist-tool.html の Step 1.
以下のような結果だった.
./migration-assist mysql "$MDSN"
2025-02-07 19:21:47 pinging mysql...
2025-02-07 19:21:47 connected to mysql successfully...
2025-02-07 19:21:48 running checks for artifacts...
2025-02-07 19:21:48 a fix is required for: schema_migrations
2025-02-07 19:21:48 4 checks been made, 1 fix(es) is required for artifacts
2025-02-07 19:21:48 running checks for unicode...
2025-02-07 19:21:49 11 checks been made, all good for unicode
2025-02-07 19:21:49 running checks for varchar...
2025-02-07 19:22:07 8 checks been made, all good for varchar
2025-02-07 19:22:07 running checks for varchar-extended...
2025-02-07 19:22:15 12 checks been made, all good for varchar-extended
1 fix(es) is required for artifacts なので,./migration-assist mysql --fix-artifacts "$MDSN"
で修正を行う.修正前後で dump して,diff を取ると安心.
mysqldump -u mmuser -p mattermost --host localhost --port 3306 > dump0.sql
./migration-assist mysql --fix-artifacts "$MDSN"
mysqldump -u mmuser -p mattermost --host localhost --port 3306 > dump1.sql
diff -uw dump0.sql dump1.sql
Step 2 - Create the PostgreSQL database schema
https://docs.mattermost.com/deploy/postgres-migration-assist-tool.html の Step 2.
まず PostgreSQL のサーバを Docker で起動する必要があるが,https://docs.mattermost.com/install/install-docker.html に Mattermost を Docker 上で丸ごと動かす方法が解説されていて,具体的には https://github.com/mattermost/docker のファイルを流用して PostgreSQL/Docker 環境を作る.より詳細には以下のファイルさえあれば良い.
-
docker-compose.yml
-
docker-compose.yml
には mattermost
の section もあるが,これはテストに使える(つまり,docker と PostgreSQL は別 container として起動する).
-
postgres
の section に以下を書いておき,PostgreSQL server に localhost:5432
でアクセスできるようにしておく.
ports:
- 5432:5432
-
mattermost
の section に以下を書いておき(APP_PORT=8065
), http://localhost:5432/
でテストできるようにしておく.
ports:
- ${APP_PORT}:${APP_PORT}
-
env.example
を .env
として用意.
以下の変数を適当に調整.Docker PostgreSQL は最新版(17.2)を利用することにした (PostgreSQLは 毎年メジャーバージョンアップがあり,最新の 17 (17.2) は Nov 2029 までサポート).
POSTGRES_DATA_PATH
は https://github.com/tianon/docker-postgres-upgrade を利用した upgrade が容易な path とした(version/data/
).
POSTGRES_IMAGE_TAG=17.2-alpine
POSTGRES_DATA_PATH=/somewhere/17.2/data/
POSTGRES_USER=mmuser
POSTGRES_PASSWORD=mmuser_password
POSTGRES_DB=mattermost
MATTERMOST_IMAGE_TAG=release-10
APP_PORT=8065
準備ができたら docker compose up -d postgres
で PostgreSQL container (のみ)を起動する.あとは以下を実行するだけ...
./migration-assist postgres "$PDSN" --run-migrations --mattermost-version=v10.4.2
だが,
An Error Occurred: could not check schema owner: the user "mmuser" is not owner of the "public" schema
のエラーが発生するので(PostgreSQL 13-alpine
では発生しなかったはず),https://github.com/mattermost/migration-assist/issues/16 にあるとおり PostgreSQL に接続して以下を実行する.
ALTER SCHEMA public OWNER TO mmuser;
GRANT ALL ON SCHEMA public to mmuser;
これは,たとえば docker exec
を使って以下のように送り込めば良い (PostgreSQL のコンテナ名は docker-postgres-1
を想定).
% cat hoge.sql
ALTER SCHEMA public OWNER TO mmuser;
ALL ON SCHEMA public to mmuser;
% (docker exec -i docker-postgres-1 bash -c "cat > /tmp/work.sql") < hoge.sql
% docker exec docker-postgres-1 bash -c 'psql mattermost mmuser < /tmp/work.sql'
いろいろ調べていてこんなテクニックがあることを知った...
なお,最初からやり直すには docker compose down
でコンテナを止めて,sudo rm -rf /somewhere/17.2/data
する(要 sudo
).
Step 3, 4, 5 - Generate a pgloader configuration / Run pgloader / Restore full-text indexes
https://docs.mattermost.com/deploy/postgres-migration-assist-tool.html の Step 3, 4, 5.
pgloader 用の configuration を生成して,pgloader を動かす.そして後処理(index 生成)をする.
./migration-assist pgloader --mysql="$MDSN" --postgres="$PDSN" > migration.load
pgloader migration.load > migration.log
./migration-assist postgres post-migrate "$PDSN"
特に問題なく終了する(migration.load
にいろいろ Warning が出ているがひとまず気にしないことにした; 後述のとおり動作したので).
なお,以下のようなメッセージが表示されるかもしれない.
could not find the default schema "public" in search_path, consider setting it from the postgresql console
https://github.com/mattermost/migration-assist/issues/37 で議論されているが,これは最後に修正する.互換性の問題かなにかで,最終的には以下のような妙な状態になる.イミフ.
mattermost=# SHOW search_path;
search_path
---------------------
"""$user"", public"
(1 row)
Step 6 - Complete plugin migrations
https://docs.mattermost.com/deploy/postgres-migration-assist-tool.html の Step 6.
ここが最も厄介.以下を実行すれば良いのだが,手元の環境では pgloader boards.load
がコケて大変だった.
./migration-assist pgloader boards --mysql="$MDSN" --postgres="$PDSN" > boards.load
./migration-assist pgloader playbooks --mysql="$MDSN" --postgres="$PDSN" > playbooks.load
./migration-assist pgloader calls --mysql="$MDSN" --postgres="$PDSN" > calls.load
pgloader playbooks.load > playbooks_migration.log
pgloader calls.load > calls.log
pgloader boards.load > boards_migration.log
先に pgloader boards.load
を実行して失敗すると,その後ものはことごとく失敗することになるので,上では最後に pgloader boards.load
している.
どの pgloader configuration (.load) も以下のように public を mattermost に rename して,最後に mattermost を public に rename するのだが,失敗すると mattermost のままになり,以降の .load では public が見つからないということでコケる.
BEFORE LOAD DO
$$ ALTER SCHEMA public RENAME TO mattermost; $$
AFTER LOAD DO
$$ UPDATE mattermost.focalboard_blocks SET "fields" = '{}'::json WHERE "fields"::text = ''; $$,
$$ UPDATE mattermost.focalboard_blocks_history SET "fields" = '{}'::json WHERE "fields"::text = ''; $$,
$$ UPDATE mattermost.focalboard_sessions SET "props" = '{}'::json WHERE "props"::text = ''; $$,
$$ UPDATE mattermost.focalboard_teams SET "settings" = '{}'::json WHERE "settings"::text = ''; $$,
$$ UPDATE mattermost.focalboard_users SET "props" = '{}'::json WHERE "props"::text = ''; $$,
$$ ALTER SCHEMA mattermost RENAME TO public; $$,
Focalboard (Mattermost Boars) Migration
pgloader boards.load
で生じるエラーは以下のようなもので,timestamptz not null default 'current_timestamp(6)'
という変な type で table を生成しようとしてしまう.
2025-02-07T12:17:46.160002Z ERROR Database error 22007: invalid input syntax for type timestamp with time zone: "current_timestamp(6)
QUERY: CREATE TABLE mattermost.focalboard_blocks
(
id varchar(36) not null,
insert_at timestamptz not null default 'current_timestamp(6)',
parent_id varchar(36) default NULL,
schema bigint default NULL,
type text default NULL,
title text default NULL,
fields json default NULL,
create_at bigint default NULL,
update_at bigint default NULL,
delete_at bigint default NULL,
root_id varchar(36) default NULL,
modified_by varchar(36) not null,
channel_id varchar(36) not null,
created_by varchar(36) not null,
board_id varchar(36) default NULL
);
2025-02-07T12:17:46.160002Z FATAL Failed to create the schema, see above.
KABOOM!
2025-02-07T12:17:46.168002Z ERROR Database error 42P01: relation "mattermost.focalboard_blocks" does not exist
https://github.com/dimitri/pgloader/issues/341#issuecomment-2045367908 では boards.load
を以下のような感じにすると良いと書かれているが,これで datetime
を timestamp
に cast して,その timestamp
をさらに cast しようとしているように見えるが,これはまともに動作しない.
LOAD
DATABASE from mysql://xxx:xxx@mariadb:3306/mattermost
INTO postgresql://xxx:xxxx@localhost:5432/mattermost
CAST type datetime to timestamp, type timestamp to "timestamptz not null default current_timestamp" drop default drop not null
動作するようにするには以下の様に修正する.datetime
を timestamptz not null default current_timestamp
に cast して,元から指定されている default
と not null
を drop する(詳しくは pgloader の document 参照).
--- boards.load 2025-02-08 20:38:05.717358154 +0900
+++ boards.load.ok 2025-02-08 20:38:07.077369466 +0900
@@ -22,7 +22,8 @@
column focalboard_teams.settings to "json" drop typemod using remove-null-characters,
column focalboard_users.props to "json" drop typemod using remove-null-characters,
type int when (= precision 11) to int4 drop typemod,
- type json to jsonb drop typemod using remove-null-characters
+ type json to jsonb drop typemod using remove-null-characters,
+ type datetime to "timestamptz not null default current_timestamp" drop default drop not null
INCLUDING ONLY TABLE NAMES MATCHING
~/focalboard/
これで,cast (pgloader) した際の,MariaDB と PostgreSQL のテーブルは以下のとおり.
[MariaDB]
CREATE TABLE `focalboard_blocks` (
`id` varchar(36) NOT NULL,
`insert_at` datetime(6) NOT NULL DEFAULT current_timestamp(6),
`parent_id` varchar(36) DEFAULT NULL,
`schema` bigint(20) DEFAULT NULL,
`type` text DEFAULT NULL,
`title` text DEFAULT NULL,
`fields` text DEFAULT NULL,
`create_at` bigint(20) DEFAULT NULL,
`update_at` bigint(20) DEFAULT NULL,
`delete_at` bigint(20) DEFAULT NULL,
`root_id` varchar(36) DEFAULT NULL,
`modified_by` varchar(36) NOT NULL,
`channel_id` varchar(36) NOT NULL,
`created_by` varchar(36) NOT NULL,
`board_id` varchar(36) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_blocks_board_id_parent_id` (`board_id`,`parent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
[PostgreSQL]
CREATE TABLE public.focalboard_blocks (
id character varying(36) NOT NULL,
insert_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP(6) NOT NULL,
parent_id character varying(36) DEFAULT NULL::character varying,
schema bigint,
type text,
title text,
fields json,
create_at bigint,
update_at bigint,
delete_at bigint,
root_id character varying(36) DEFAULT NULL::character varying,
modified_by character varying(36) NOT NULL,
channel_id character varying(36) NOT NULL,
created_by character varying(36) NOT NULL,
board_id character varying(36) DEFAULT NULL::character varying
);
まだ続く.これでも focalboard_blocks_history
の移行に失敗する.以下.
2025-02-07T14:14:24.260002Z ERROR Database error 22P02: invalid input syntax for type json
DETAIL: The input string ended unexpectedly.
CONTEXT: JSON data, line 1:
COPY focalboard_blocks_history, line 4, column fields: ""
2025-02-07T14:14:24.356003Z ERROR PostgreSQL Database error 42P07: relation "idx_preferences_category" already exists
QUERY: CREATE INDEX idx_preferences_category ON mattermost.focalboard_preferences (category);
2025-02-07T14:14:24.376003Z ERROR PostgreSQL Database error 42P07: relation "idx_preferences_name" already exists
QUERY: CREATE INDEX idx_preferences_name ON mattermost.focalboard_preferences (name);
2025-02-07T14:14:24.448004Z LOG report summary reset
table name errors rows bytes total time
------------------------------------------- --------- --------- --------- --------------
before load 0 1 0.008s
fetch meta data 0 47 0.060s
Create Schemas 0 0 0.000s
Create SQL Types 0 0 0.000s
Create tables 0 36 0.048s
Set Table OIDs 0 18 0.004s
------------------------------------------- --------- --------- --------- --------------
mattermost.focalboard_blocks_history 1 0 0.100s
mattermost.focalboard_blocks 0 1258 539.9 kB 0.080s
mattermost.focalboard_boards_history 0 175 210.8 kB 0.072s
mattermost.focalboard_subscriptions 0 157 18.4 kB 0.080s
mattermost.focalboard_boards 0 21 32.3 kB 0.088s
mattermost.focalboard_categories 0 54 7.8 kB 0.068s
mattermost.focalboard_file_info 0 0 0.048s
mattermost.focalboard_board_members 0 24 1.2 kB 0.060s
mattermost.focalboard_sharing 0 1 0.1 kB 0.052s
mattermost.focalboard_notification_hints 0 0 0.064s
mattermost.focalboard_teams 0 0 0.064s
mattermost.focalboard_category_boards 0 24 3.2 kB 0.008s
mattermost.focalboard_system_settings 0 6 0.2 kB 0.004s
mattermost.focalboard_sessions 0 0 0.004s
mattermost.focalboard_schema_migrations 0 40 1.0 kB 0.008s
mattermost.focalboard_board_members_history 0 24 1.9 kB 0.004s
mattermost.focalboard_preferences 0 0 0.004s
mattermost.focalboard_users 0 0 0.004s
------------------------------------------- --------- --------- --------- --------------
これについては https://github.com/mattermost/migration-assist/issues/17 で議論されているが,手元では focalboard_blocks_history
で何故か fields が空文字になっているものがあり,それを以下のように {}
(JSONの空オブジェクト)に更新するとうまく行くようになった(START TRANSACTION
と COMMIT
を使って,失敗した場合は ROLLBACK
すること想定,念の為).
% mysql --port 3306 -h localhost -p --user=mmuser mattermost
> SELECT id, fields FROM focalboard_blocks_history WHERE JSON_VALID(fields) = 0;
(fields が空文字になって JSON_VALID に失敗していること確認した)
> START TRANSACTION;
> UPDATE focalboard_blocks_history SET fields = '{}' WHERE fields = '';
> COMMIT;
なお,そもそも Focalboard (Mattermost Boards) はほとんど利用していないので,個別に board を古いシステムから export して,新システムに import するという手もあった.
Step 7 - Configure Mattermost to utilize the new PostgreSQL database
https://docs.mattermost.com/deploy/postgres-migration-assist-tool.html の Step 7.
MariaDB でのシステムは生かしたまま,Docker で起動させる Mattermost で,動作確認をする.先の .env
に以下の箇所があるが,これに従って MariaDB でのシステムの各ファイルを copy して(以下の通り chown
も必要),config.json
を調整する.
# Mattermost settings
## Inside the container the uid and gid is 2000. The folder owner can be set with
## `sudo chown -R 2000:2000 ./volumes/app/mattermost`.
MATTERMOST_CONFIG_PATH=./volumes/app/mattermost/config
MATTERMOST_DATA_PATH=./volumes/app/mattermost/data
MATTERMOST_LOGS_PATH=./volumes/app/mattermost/logs
MATTERMOST_PLUGINS_PATH=./volumes/app/mattermost/plugins
MATTERMOST_CLIENT_PLUGINS_PATH=./volumes/app/mattermost/client/plugins
MATTERMOST_BLEVE_INDEXES_PATH=./volumes/app/mattermost/bleve-indexes
あとは,docker compose up -d mattermost
で Mattermost のコンテナを起動して,http://localhost:8065
にアクセスして確認.ブラウザで直接アクセスするには portfoward するか,IP アドレス指定をする.WebSocket まわりで不具合生じていたが(CORS関連?),本番環境だったら大丈夫だろうということで無視.
多分この段階で,mattermost が postgres に接続できない云々問題が生じるはずで,これが上述の could not find the default schema "public" in search_path
問題.PostgreSQL に接続して以下を実行する(前述のとおり docker exec
を使うと簡単).https://github.com/mattermost/migration-assist/issues/16 参照.
ALTER user mmuser in database mattermost set search_path to 'public';
Docker で動作確認ができたら,MariaDB の Mattermost を一旦停止して(もちろん MariaDB は起動したまま),Migration を最初からやりなおして(Mattermost を止めているので DB へのアクセスもない),Mattermost の config.json
の SqlSettings
の DataSource
を修正して,Mattermost を起動する.PostgreSQL と MySQL (MariaDB) で微妙な Data Source Name (DSN) 差に注意.
その他
以下のような感じで Calls 周りでエラーが生じる(ほぼ使ってないけど...).
{"timestamp":"2025-02-08 XX:XX:XX.XXX +09:00","level":"error","msg":"failed to get all calls channels","caller":"app/plugin_api.go:1011","plugin_id":"com.mattermost.calls","origin":"main.(*Plugin).handleGetAllCallChannelStates api.go:169","err":"failed to get calls channels: sql: Scan error on column index 2, name \"props\": unsupported source type string"}
Calls 関連のテーブルは以下で,text
になっている箇所は jsonb
になっている必要がある.https://github.com/mattermost/mattermost-plugin-calls/tree/main/server/db/migrations/postgres 付近の SQL 参照.
% docker exec -it docker-postgres-1 psql mattermost mmuser
mattermost=# \d public.calls
Table "public.calls"
Column | Type | Collation | Nullable | Default
--------------+------------------------+-----------+----------+-------------------------
id | character varying(26) | | not null |
channelid | character varying(26) | | | NULL::character varying
startat | bigint | | |
endat | bigint | | |
createat | bigint | | |
deleteat | bigint | | |
title | character varying(256) | | | NULL::character varying
postid | character varying(26) | | | NULL::character varying
threadid | character varying(26) | | | NULL::character varying
ownerid | character varying(26) | | | NULL::character varying
participants | text | | not null |
stats | text | | not null |
props | text | | not null |
Indexes:
"idx_XXXXX_primary" PRIMARY KEY, btree (id)
"idx_calls_channel_id" btree (channelid)
"idx_calls_end_at" btree (endat)
mattermost=# \d public.calls_channels
Table "public.calls_channels"
Column | Type | Collation | Nullable | Default
-----------+-----------------------+-----------+----------+---------
channelid | character varying(26) | | not null |
enabled | boolean | | |
props | text | | not null |
Indexes:
"idx_XXXXX_primary" PRIMARY KEY, btree (channelid)
mattermost=# \d public.calls_sessions
Table "public.calls_sessions"
Column | Type | Collation | Nullable | Default
------------+-----------------------+-----------+----------+-------------------------
id | character varying(26) | | not null |
callid | character varying(26) | | | NULL::character varying
userid | character varying(26) | | | NULL::character varying
joinat | bigint | | |
unmuted | boolean | | |
raisedhand | bigint | | |
Indexes:
"idx_XXXXX_primary" PRIMARY KEY, btree (id)
"idx_calls_sessions_call_id" btree (callid)
mattermost=# \d calls_jobs
Table "public.calls_jobs"
Column | Type | Collation | Nullable | Default
-----------+-----------------------+-----------+----------+-------------------------
id | character varying(26) | | not null |
callid | character varying(26) | | | NULL::character varying
type | character varying(64) | | | NULL::character varying
creatorid | character varying(26) | | | NULL::character varying
initat | bigint | | |
startat | bigint | | |
endat | bigint | | |
props | text | | not null |
Indexes:
"idx_XXXXX_primary" PRIMARY KEY, btree (id)
"idx_calls_jobs_call_id" btree (callid)
正しく JSON 形式の文字列が text
として入っていれば,以下で jsonb
に変換できる.
ALTER TABLE calls ALTER COLUMN participants TYPE JSONB USING participants::JSONB;
ALTER TABLE calls ALTER COLUMN props TYPE JSONB USING props::JSONB;
ALTER TABLE calls ALTER COLUMN stats TYPE JSONB USING stats::JSONB;
ALTER TABLE calls_channels ALTER COLUMN props TYPE JSONB USING props::JSONB;
ALTER TABLE calls_jobs ALTER COLUMN props TYPE JSONB USING props::JSONB;
手元環境では calls
のテーブルに謎の制御文字が入り込んでしまっていたので,以下で個別に修正してから上の ALTER TABLE
した.
UPDATE calls set participants = '[]' where id = 'xxxxxxxxxxxxxxxxxxxxxxxxxx';
UPDATE calls set stats = '{}' where id = 'xxxxxxxxxxxxxxxxxxxxxxxxxx';
UPDATE calls set props = '{"hosts":["xxxxxxxxxxxxxxxxxxxxxxxxxx"],"node_id":"xxxxxxxxxxxxxxxxxxxxxxxxxx","participants":{"xxxxxxxxxxxxxxxxxxxxxxxxxx":{}}}' where id = 'xxxxxxxxxxxxxxxxxxxxxxxxxx;
その他(systemd)
手元では systemd で mattermost を起動している.以下のように mattermost.service
が dockerpg.service
に依存することにしておき,/etc/systemd/system/dockerpg.service を適当に作成する (start: docker compose up -d postgres
,stop: docker compose down
するように適当に書く; 本当に適当なのでここでは晒さない).
[/etc/systemd/system/mattermost.service]
-After=mysql.service
-Requires=mysql.service
+After=dockerpg.service
+Requires=dockerpg.service
追記 2025-02-09
以下のような感じで Mattermost Playbooks 周りでエラーが出ていた.
{"timestamp":"2025-02-09 XX:XX:XX.XXX +09:00","level":"error","msg":"An internal error has occurred. Check app server logs for details.","caller":"app/plugin_api.go:1011","plugin_id":"playbooks","request_id":"xxxxxxxxxxxxxxxxxxxxxxxxxx","error":"sql: Scan error on column index 2, name \"digestnotificationsettingsjson\": unsupported Scan, storing driver.Value type string into type *json.RawMessage\nfailed to get userInfo by userId 'xxxxxxxxxxxxxxxxxxxxxxxxxx'\ngithub.com/mattermost/mattermost-plugin-playbooks/server/sqlstore.(*userInfoStore).Get\n\tgithub.com/mattermost/mattermost-plugin-playbooks/server/sqlstore/user_info.go:51\ngithub.com/mattermost/mattermost-plugin-playbooks/server/api.(*BotHandler).connect\n\tgithub.com/mattermost/mattermost-plugin-playbooks/server/api/bot.go:175\ngithub.com/mattermost/mattermost-plugin-playbooks/server/api.NewBotHandler.withContext.func3\n\tgithub.com/mattermost/mattermost-plugin-playbooks/server/api/context.go:36\nnet/http.HandlerFunc.ServeHTTP\n\tnet/http/server.go:2141\ngithub.com/mattermost/mattermost-plugin-playbooks/server/api.MattermostAuthorizationRequired.func1\n\tgithub.com/mattermost/mattermost-plugin-playbooks/server/api/api.go:109\nnet/http.HandlerFunc.ServeHTTP\n\tnet/http/server.go:2141\ngithub.com/mattermost/mattermost-plugin-playbooks/server/api.LogRequest.func1\n\tgithub.com/mattermost/mattermost-plugin-playbooks/server/api/logger.go:46\nnet/http.HandlerFunc.ServeHTTP\n\tnet/http/server.go:2141\ngithub.com/gorilla/mux.(*Router).ServeHTTP\n\tgithub.com/gorilla/mux@v1.8.0/mux.go:210\ngithub.com/mattermost/mattermost-plugin-playbooks/server/api.(*Handler).ServeHTTP\n\tgithub.com/mattermost/mattermost-plugin-playbooks/server/api/api.go:60\nmain.(*Plugin).ServeHTTP\n\tgithub.com/mattermost/mattermost-plugin-playbooks/server/plugin.go:96\ngithub.com/mattermost/mattermost-server/v6/plugin.(*hooksRPCServer).ServeHTTP\n\tgithub.com/mattermost/mattermost-server/v6@v6.0.0-20221206174448-c3c81cb3d6a2/plugin/client_rpc.go:453\nreflect.Value.call\n\treflect/value.go:596\nreflect.Value.Call\n\treflect/value.go:380\nnet/rpc.(*service).call\n\tnet/rpc/server.go:382\nruntime.goexit\n\truntime/asm_amd64.s:1650","plugin_caller":"github.com/mattermost/mattermost-plugin-playbooks/server/api/api.go:85"}
digestnotificationsettingsjson
付近でコケている様子で,https://github.com/mattermost/mattermost-plugin-playbooks/blob/master/server/sqlstore/migrations/postgres/000077_add_digest_notification_settings_json_to_ir_userinfo.up.sql によるとこれは JSON
type のはずが,text
type になっていた.
mattermost=# \d ir_userinfo
Table "public.ir_userinfo"
Column | Type | Collation | Nullable | Default
--------------------------------+-----------------------+-----------+----------+---------
id | character varying(26) | | not null |
lastdailytododmat | bigint | | |
digestnotificationsettingsjson | text | | |
Indexes:
"idx_XXXXX_primary" PRIMARY KEY, btree (id)
調べたところ保存されているデータも JSON が text で格納されていたので,以下で修正.
mattermost=# ALTER TABLE ir_userinfo ALTER COLUMN digestnotificationsettingsjson TYPE JSON USING digestnotificationsettingsjson::JSON;
mattermost=# \d ir_userinfo
Table "public.ir_userinfo"
Column | Type | Collation | Nullable | Default
--------------------------------+-----------------------+-----------+----------+---------
id | character varying(26) | | not null |
lastdailytododmat | bigint | | |
digestnotificationsettingsjson | json | | |
Indexes:
"idx_XXXXX_primary" PRIMARY KEY, btree (id)
追記 2025-02-10
PostgreSQL で運用する Mattermost では日本語検索がまともに機能しないことがわかった.
ということで,pg_cjk_parserでお手軽対応をした.なお,MariaDB で日本語検索する件は2020/02/29 18:50 / Mattermost で日本語検索 (Mariadb+Mroonga)参照.
以下の Dockerfile で PostgreSQL のイメージを build して,それを docker compose で起動.
ARG POSTGRES_VERSION=17.2
FROM postgres:$POSTGRES_VERSION-alpine AS build
ARG POSTGRES_VERSION=17.2
RUN apk update && apk add musl-dev icu-dev llvm19-dev clang19 make
RUN mkdir -p /root/parser
WORKDIR /root/parser
COPY pg_cjk_parser.c /root/parser/
COPY pg_cjk_parser.control /root/parser/
COPY Makefile /root/parser/
COPY pg_cjk_parser--0.0.1.sql /root/parser/
COPY zht2zhs.h /root/parser/
RUN make clean && make install
FROM postgres:$POSTGRES_VERSION-alpine
COPY --from=build /root/parser/pg_cjk_parser.bc /usr/local/lib/postgresql/bitcode
COPY --from=build /root/parser/pg_cjk_parser.so /usr/local/lib/postgresql/
COPY --from=build /root/parser/pg_cjk_parser--0.0.1.sql /usr/local/share/postgresql/extension/
COPY --from=build /root/parser/pg_cjk_parser.control /usr/local/share/postgresql/extension/
あとは,pg_cjk_parser のページにかかれている通り,以下の SQL を実行する(public ではなくなんとなく pg_catalog にした).
CREATE EXTENSION pg_cjk_parser;
CREATE TEXT SEARCH PARSER pg_catalog.pg_cjk_parser (
START = prsd2_cjk_start,
GETTOKEN = prsd2_cjk_nexttoken,
END = prsd2_cjk_end,
LEXTYPES = prsd2_cjk_lextype,
HEADLINE = prsd2_cjk_headline);
CREATE TEXT SEARCH CONFIGURATION pg_catalog.config_2_gram_cjk (
PARSER = pg_cjk_parser
);
SET default_text_search_config = 'pg_catalog.config_2_gram_cjk';
ALTER TEXT SEARCH CONFIGURATION pg_catalog.config_2_gram_cjk
ADD MAPPING FOR asciihword
WITH simple;
ALTER TEXT SEARCH CONFIGURATION pg_catalog.config_2_gram_cjk
ADD MAPPING FOR cjk
WITH simple;
ALTER TEXT SEARCH CONFIGURATION pg_catalog.config_2_gram_cjk
ADD MAPPING FOR email
WITH simple;
ALTER TEXT SEARCH CONFIGURATION pg_catalog.config_2_gram_cjk
ADD MAPPING FOR asciiword
WITH english_stem;
ALTER TEXT SEARCH CONFIGURATION pg_catalog.config_2_gram_cjk
ADD MAPPING FOR entity
WITH simple;
ALTER TEXT SEARCH CONFIGURATION pg_catalog.config_2_gram_cjk
ADD MAPPING FOR file
WITH simple;
ALTER TEXT SEARCH CONFIGURATION pg_catalog.config_2_gram_cjk
ADD MAPPING FOR float
WITH simple;
ALTER TEXT SEARCH CONFIGURATION pg_catalog.config_2_gram_cjk
ADD MAPPING FOR host
WITH simple;
ALTER TEXT SEARCH CONFIGURATION pg_catalog.config_2_gram_cjk
ADD MAPPING FOR hword
WITH simple;
ALTER TEXT SEARCH CONFIGURATION pg_catalog.config_2_gram_cjk
ADD MAPPING FOR hword_asciipart
WITH simple;
ALTER TEXT SEARCH CONFIGURATION pg_catalog.config_2_gram_cjk
ADD MAPPING FOR hword_numpart
WITH simple;
ALTER TEXT SEARCH CONFIGURATION pg_catalog.config_2_gram_cjk
ADD MAPPING FOR hword_part
WITH simple;
ALTER TEXT SEARCH CONFIGURATION pg_catalog.config_2_gram_cjk
ADD MAPPING FOR int
WITH simple;
ALTER TEXT SEARCH CONFIGURATION pg_catalog.config_2_gram_cjk
ADD MAPPING FOR numhword
WITH simple;
ALTER TEXT SEARCH CONFIGURATION pg_catalog.config_2_gram_cjk
ADD MAPPING FOR numword
WITH simple;
ALTER TEXT SEARCH CONFIGURATION pg_catalog.config_2_gram_cjk
ADD MAPPING FOR protocol
WITH simple;
ALTER TEXT SEARCH CONFIGURATION pg_catalog.config_2_gram_cjk
ADD MAPPING FOR sfloat
WITH simple;
ALTER TEXT SEARCH CONFIGURATION pg_catalog.config_2_gram_cjk
ADD MAPPING FOR tag
WITH simple;
ALTER TEXT SEARCH CONFIGURATION pg_catalog.config_2_gram_cjk
ADD MAPPING FOR uint
WITH simple;
ALTER TEXT SEARCH CONFIGURATION pg_catalog.config_2_gram_cjk
ADD MAPPING FOR url
WITH simple;
ALTER TEXT SEARCH CONFIGURATION pg_catalog.config_2_gram_cjk
ADD MAPPING FOR url_path
WITH simple;
ALTER TEXT SEARCH CONFIGURATION pg_catalog.config_2_gram_cjk
ADD MAPPING FOR version
WITH simple;
ALTER TEXT SEARCH CONFIGURATION pg_catalog.config_2_gram_cjk
ADD MAPPING FOR word
WITH simple;
default_text_search_config
の設定が,セッション終了毎に元の pg_catalog.english
に戻ってしまって,これにかなり悩んだ.最終的には postgresql.conf
(手元環境では /somewhere/17.2/data/postgresql.conf
)に以下を書いて,コンテナを再起動したら,設定が反映された(コンテナ再起動ではなく pg_ctl reload
で良かったのかもしれない).
default_text_search_config = 'pg_catalog.config_2_gram_cjk'
インデックスを再生成する必要があるので,以下を実行する(ここは english
ままで良いらしい).
DROP INDEX idx_posts_message_txt;
DROP INDEX idx_fileinfo_content_txt;
CREATE INDEX IF NOT EXISTS idx_fileinfo_content_txt ON fileinfo USING gin(to_tsvector('english', content));
CREATE INDEX IF NOT EXISTS idx_posts_message_txt ON posts USING gin(to_tsvector('english', message));
なお,pg_cjk_parser にも書いてあるが,CJK文字列「のび太の牧場物語」を「のび」「び太」「太の」「の牧」「牧場」「場物」「物語」のように分解するので,一文字での検索はできない(「AのB」とかがあればこれには「の」でマッチするが,「のび太の牧場物語」には「の」はマッチしない).が,これで十分だ...
もとの状態に戻すには以下(要インデックス再生成).
SET default_text_search_config = 'pg_catalog.english';
DROP TEXT SEARCH CONFIGURATION pg_catalog.config_2_gram_cjk;
DROP TEXT SEARCH PARSER pg_catalog.pg_cjk_parser;
DROP EXTENSION pg_cjk_parser;
pg_cjk_parser が改良されたり,Mattermost の仕様が変わったとしても,たぶん default_text_search_config
を入れ替えて再度インデックスを生成すれば良いだけなので,おそらくメンテナンス簡単.
Related articles