Mattermostバージョンアップにおける問題対応

はじめに

当社では社内のコミュニケーションツールとしてMattermostを運用しています。MattermostはSlackライクなオープンソースのチャットツールです。オンプレミスの運用が可能で、組織内・企業内のビジネスチャットとして広く利用されています。
このMattermostを運用している中で、

  • 投稿内容の反映に時間がかかる
  • 過去のチャット内容の検索に時間がかかる
  • スクロールして過去のチャットを表示するのに時間がかかる

という性能に関する問い合わせが多くなりました。

これらの性能に関する問題を解決するため、Mattermostのバージョンアップとシステム構成の見直しを行いました。
ここでは、バージョンアップ中に発生した問題への対応を通して得た知見を紹介します。

バージョンアップ

Mattermostはサーバー上で動作するアプリ(以下、Mattermostと表記)とDB(当社ではPostgreSQLを使用)により構成されています。
当初はMattermostをv5.35.2にバージョンアップする予定でしたが、以下の理由によりv6.2.2を採用することにしました。

バージョンアップ前後のMattermost・PostgreSQLのバージョンを以下に記載します。
Mattermostは有償版と無償版があり、私たちの環境では無償版のTeam Editionを使用しています。

対象 バージョンアップ前 バージョンアップ後
Mattermost 5.12.4 6.2.2
PostgreSQL 10 12

システム構成の変更

私たちは、MattermostをOpenStack上で構築したKubernetes(以下、k8sと表記)内で運用しています。
※Kubernetesのバージョンは1.10です。
今回はMattermostの性能改善のため、以下の方針に沿って、これまでk8s内でPodとして動かしていたDBを専用インスタンス(Trove1)として動かすように構成を変更することにしました。

  • k8s内のトラフィック減少
  • DBとk8s内のPodが互いに受ける、負荷などの影響の回避
  • NFS2からBlock3へ、DBのマウント先変更によるディスクI/O性能の向上

バージョンアップ前後のシステム構成を以下に記載します。

バージョンアップ前

system-before

バージョンアップ後

system-after

全文検索が使えない問題への対応

これまで私たちの環境では、日本語の全文検索機能を実現するためにMecab・textsearch_ja・pg_bigmという3つのプラグインを使用していました。
しかし、v5.35.2のMattermostでは、これらのプラグインが正常に動作しないことが分かりました。
その原因となっているソースファイル(post_store.go)を以下に記載します。

// v5.12.4
searchClause := fmt.Sprintf("AND %s @@  to_tsquery(:Terms)", searchType)

// v5.35.2
searchClause := fmt.Sprintf("to_tsvector('english', %s) @@  to_tsquery('english', :Terms)", searchType)

v5.35.2では、全文検索においてテキストを単語に分割する処理が、PostgreSQLの関数(to_tsvector)を使用するように変更されています。この関数の引数にenglishが指定されているため、投稿データを検索するときに日本語のテキストを単語に分割できなくなっています。ソースファイルからSQLを発行する部分を抜き出してenglishを削除して実行したところ、日本語での検索ができるようになりました。そのため、ソースファイルを修正する方法も検討しましたが、この方法を採用すると今後バージョンアップするたびに同様の修正を入れる必要があり、管理の手間がかかります。さらにMattermostの実験的な機能として、日本語にも対応する全文検索機能であるBleveがv5.24から実装されていることが分かったため、そちらを使うことにしました。

しかし、デフォルトの設定でBleveを使ってみたところ、以下の問題が発生しました。

  • インデックス作成に膨大な時間がかかる
  • 日本語の全文検索を行ったときの検索結果が実際の数より少ない

これらの問題にどのように対応したのか見ていきたいと思います。


インデックス作成に膨大な時間がかかる問題

Bleveは最初に機能を有効化したときに、これまでの投稿データのインデックスを作成します。Mattermostの運用を開始してから5年は経過しているため、データ量は膨大なサイズに上り、バージョンアップ時にはDBのdumpファイルが1.6GBありました。これらのすべてにインデックスを作成するのに、v5.35.2では7日間程度かかりました。バージョンアップするのにそれだけダウンタイムが発生するとユーザから苦情が寄せられることは明らかであるため、インデックス作成にかかる時間の短縮が求められました。
インデックス作成に膨大な時間がかかる原因は、Bleveのパラメータ(index_type)にありました。Bleveの設定ファイル(index_meta.json)に記載されているパラメータ(index_type)が、v5.35.2の時点ではupside_downでしたが、v6.2.2ではScorchに変更されていました。検索インデックスをScorch indexにすることで、インデックス作成にかかる時間が1日程度まで短縮されたことから、Scorch indexが使用可能なv6.2.2まで上げることを決めました。

全文検索を行ったときの検索結果が実際の数より少ない問題

インデックス作成が完了してから過去のチャットを検索すると、ある時期から先の期間の発言が結果に出力されない問題が発生しました。管理者画面上からはインデックスの状態が「完了」となっており、一見すると正常にインデックス作成が完了したように見えます。
以下はソースファイルからインデックスを作成する際のクエリ発行部分を抜き出したものです。処理の内容を簡単に説明すると、StartTimeからEndTimeまでの投稿データを取得するというものです。ただし、ここではデータ数が1000件までという制限が設けられており、投稿データ数がその制限を超えたためインデックス作成が失敗していました。

SELECT
  PostsQuery.*, Channels.TeamId, ParentPosts.CreateAt ParentCreateAt
FROM (
  SELECT
    *
  FROM
    Posts
  WHERE
    Posts.CreateAt >= :StartTime
  AND
    Posts.CreateAt < :EndTime
  ORDER BY
    CreateAt ASC
  LIMIT
    1000
  )
AS
  PostsQuery
LEFT JOIN
  Channels
ON
  PostsQuery.ChannelId = Channels.Id
LEFT JOIN
  Posts ParentPosts
ON
  PostsQuery.RootId = ParentPosts.Id

そこで、Mattermostのconfig.jsonにあるBulkIndexingTimeWindowSecondsの値を調整します。 BulkIndexingTimeWindowSecondsはインデックスを作成する際、一度にどのくらいの範囲(時間)のインデックスを取得するかを指定するもので、デフォルトでは3600秒となっています。私たちの環境ではこの値を1800秒にすることで、1回で取得する投稿データ数が1000件を超えないようになり、インデックス作成は問題なく完了するようになりました。

バージョンアップ手順

Mattermostのバージョンアップ手順のうち、重要なものを以下に記載します。

  • イメージ取得
  • イメージ登録
  • manifestファイル作成
  • 設定ファイルの準備
  • 既存DBの移行
  • 作成したmanifestファイルの反映
  • 過去のファイルの中身を検索する機能の有効化
  • インデックス付与

イメージ取得

MattermostのコンテナイメージをDockerHubの公式ページから取得します。

$ docker pull mattermost/mattermost-team-edition:6.2.2-rc1

イメージ登録

k8s内に構築済みのコンテナレジストリにイメージを登録します。

// タグ付け
$ docker tag mattermost/mattermost-team-edition 192.168.1.1:5000/mattermost/mattermost-team-edition:release-6.2
// リポジトリにpush
$ docker push 192.168.1.1:5000/mattermost/mattermost-team-edition:release-6.2

manifestファイル作成

Deployment用yml内のイメージファイル名・マウントパスを修正します。
なお、Service用ymlはバージョンアップによる修正箇所がないため適用手順は省略します。
以下にymlファイルの例を記載します。

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: mm
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: mm
    spec:
      containers:
        - name: mm
          image: 192.168.1.1:5000/mattermost/mattermost-team-edition:release-6.2
          env:
          - name: TZ
            value: "Asia/Tokyo"
          ports:
          - name: mm-port
            containerPort: 80
          volumeMounts:
          - mountPath: /mm/mattermost/mattermost-data
            name: mm-data
          - mountPath: /mattermost/config
            name: mm-conf
          - mountPath: /mattermost/logs
            name: mm-logs
          - mountPath: /mattermost/bleve
            name: mm-bleve
          - mountPath: /mattermost/plugins
            name: mm-plugins
      volumes:
      - name: mm-data
        hostPath:
          path: /XXX/data
      - name: mm-conf
        hostPath:
          path: /XXX/config
      - name: mm-logs
        hostPath:
          path: /XXX/logs
      - name: mm-bleve
        hostPath:
          path: /XXX/bleve-indexes
      - name: mm-plugins
        hostPath:
          path: /XXX/plugins

設定ファイルの準備

config.jsonを修正します。

  • これまでとは異なりDBにTroveを使用するため、これまでコンテナ名で指定していた部分にTroveインスタンスのIPアドレスを指定します。

     "SqlSettings": {
         "DriverName": "postgres",
         "DataSource": "postgres://user:password@192.168.1.2:5432/mattermost?sslmode=disable&connect_timeout=10",
  • ローカルにあるプラグイン用ファイルを管理者画面からアップロードできるようにします。
    ※必要な場合のみ有効化します。

     "PluginSettings": {
         "Enable": true,
         "EnableUploads": true,
  • 後述の過去のファイルの中身を検索する機能の有効化に必要な設定を行います。
    v5.35.0で添付ファイルの中身を検索する機能が実装されましたが、バージョンアップ後に投稿されるデータの添付ファイルだけでなく、過去の添付ファイルの中身も検索対象とするためには、この設定変更が必要です。

    {
    "ServiceSettings": {
      ……
      "EnableLocalMode": true,
      ……
      },
  • 全文検索機能であるBleveを使用できるようにします。

     "BleveSettings": {
         "IndexDir": "/mattermost/bleve",
         "EnableIndexing": true,
         "EnableSearching": true,
         "EnableAutocomplete": true,
         "BulkIndexingTimeWindowSeconds": 1800
     },

既存DBの移行

  • 既存DBのPodに入り、投稿データのバックアップを取得します。
    ※バックアップを取得した後、既存DBのPodは削除します。

    $ pg_dumpall > /XXX/alldump_docker.sql
  • ホストマシンでMattermost用にマウントするディレクトリのパーミッションを変更します。
    v6.2.2ではPod内で動作するプロセスの実行ユーザがrootからmattermostに変更になっているため、添付ファイル等のDBに格納されないデータの保管用にホストマシン上のディレクトリをマウントしている場合は、ホストマシンでパーミッション変更が必要になります。

    $ chown -R 2000:2000 /XXXX/mattermost-data
  • psqlをインストールしたコンテナやPodを用意し、TroveのDBにデータをインポートします。

    psql -h 192.168.1.2:5432 -U postgres -f /XXX/alldump_docker.sql

作成したmanifestファイルの反映

作成したmanifestファイルを指定して以下のコマンドを実行することで、manifestファイルの変更点をPodに反映させます。

kubectl apply -f /XXX/mm.yml

過去のファイルの中身を検索する機能の有効化

MattermostのPodに入り、以下のコマンドを実行します。

mmctl extract run --local

インデックス作成

管理ユーザでMattermostにログインし、「システムコンソール」→「実験的機能」→「Bleve」を選択し、「インデックス付与を今すぐ開始する」を押します。
※投稿データのサイズによってはかなり時間がかかります。
bleve.png

「状態」が成功になれば完了です。
bleve-after.png

おわりに

バージョンアップとシステム構成の変更を行った環境で検証した結果、本稿の冒頭で記載した性能問題が改善されていることが確認できました。また、日本語検索についても問題なく動作していることを確認できました。 Mattermostはオンプレミスで使えるということから、Slackのようなチャットツールを導入したいがセキュリティ上の問題で使えないという場面では選択肢の筆頭に上がるOSSではないかと思います。
本稿がMattermostを運用中の方やこれから導入を検討する方のお役に立てば幸いです。


  1. OpenStackのDBaaSであるTroveの機能を使用しています。Troveについては下記の記事をご覧ください。
    OpenStack の DBaaS(Trove)導入 - Trove 導入入門編

  2. NFSはOpenStackが提供する共有ファイルシステム(Manila)を使用しています。

  3. BlockはOpenStackが提供するブロックストレージサービス(Cinder)を使用しています。
    Cinderについては下記の記事をご覧ください。
    OpenStack環境でBlockストレージを増設する - 基礎知識編
    OpenStack環境でBlockストレージを増設する - 増設計画編
    OpenStack環境でBlockストレージを増設する - 増設作業編

ALPHA SYSTEMS INC.

株式会社アルファシステムズは、ITサービス事業を展開しています。このブログで、技術的な取り組みを紹介しています。