やるきなし

2025/02/19 17:40 / librosa + Python 3.13

librosa + Python 3.13 がうまく動かない話.Python 3.13 では PEP 594 – Removing dead batteries from the standard libraryにより Multimedia modules の audioopaifcsunau が削除された影響で,単に pip install librosa しただけでは動かない.

というか librosa が依存する audioread (github) が audioopaifcsunau などに依存していてコケる.以下の通りそれぞれ代替があって,それらを pip install すれば良いのだけど,audioread の依存関係が更新されないので(というかメンテナンスが止まっている?),手動で対応する必要がある.

2025/02/09 00:58 / Mattermost Migration from MariaDB to PostgreSQL

Mattermost を Debian stable (Debian 12 bookworm) で運用しているのだけど,Debian では MySQL は Official には提供されていなくて,代わりに MariaDB を使うことになっている.

Mattermost 自体は RDB として MySQL と PostgreSQL をサポートしているのだけど,現在のバージョンである v10 からは MySQL を利用した新規 install はサポート外となり,また,次期バージョンである v11 で MySQL のサポートは削除される見込みとなっている.

ということで,そのうち MariaDB から PostgreSQL に移行する必要があったので,頑張って移行した.なお,MariaDB を利用していると MySQL との互換性の問題でさまざまな問題が発生しているのも,今回の移行を促す要因になった.たとえば,MariaDB を利用していると以下の様な問題がある.

移行方法(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 に書き込んでいく感じ.

以下では 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"

なお以下を想定.

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 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 を以下のような感じにすると良いと書かれているが,これで datetimetimestamp に 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

動作するようにするには以下の様に修正する.datetimetimestamptz not null default current_timestamp に cast して,元から指定されている defaultnot 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 TRANSACTIONCOMMIT を使って,失敗した場合は 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.jsonSqlSettingsDataSource を修正して,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.servicedockerpg.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

2024/09/14 10:18 / ラピート 関西空港→なんば

ラピートに乗る際,改札前でいつもどうしたら良いかわからなくなる.なので,通常は以下の2択.

南海のことなので券売機で特急券がクレジットカードで買えるようには思えない...と,調べてみたら買えるようになっている様子.知らなかった.

さて,今回は15分ぐらい余裕はあったので,Webで特急券購入.https://www.club-nankai.jp/sta/web/OnetimeTop.do から会員登録なしで購入できる.座席指定も可能.

座席指定で良い席を見繕っていたら,クレジットカード決済の段階で座席確保に失敗する(おそらく窓口購入でその座席が販売された).座席指定してから決済するまでに結構入力する項目があるので,何度か失敗したけど,4回目ぐらいでうまく購入できた.会員登録をしておけば入力する項目を減らせるのだろうけどほぼ南海乗らないので,また機会があれば会員登録する.

なお,関空やなんば周辺は海外からの人だらけ.

2024/09/11 15:30 / 画像から文字選択可能な2値画像のPDFを作成する

結論としては一旦2値画像のTIFFを作成して,Acrobat で PDF に変換,その後 Acrobat で OCR して保存する,ということ.

レシートとかをスキャナで取り込む環境がなくて,やむなくスマートフォンで撮影したりした場合,あとできれいな PDF にしたくなる(そんなこともない?).

2024/06/04 19:20 / CDFV2 Microsoft Outlook Message (.msg) 形式メールから mbox 形式(.eml)への変換

.msg 形式のメールが転送されてきて,これを印刷せよとの指示を受ける....eml であれば Wanderlust や Thunderbird に取り込めるので,変換することを検討.

libemail-outlook-message-perlというパッケージに含まれるmsgconvertを使うと良いらしい.

% sudo apt install libemail-outlook-message-perl
% msgconvert example.msg

example.eml が生成される.既存の mbox (複数メール/1ファイル)に追加したい場合は,--mbox オプションを使うらしい.

% msgconvert example.msg --mbox my_mbox