AppPotを運用する上で問題が発生した場合を書きます。

1. AppPotのログ

アクセスログ
AppPotのログ

1.1. メモリ不足

AppPotはJavaで動いていますので使用できるメモリ量はJavaの仮想マシンが使用するメモリのサイズに依存します。
大量のデータを一度に取得したり、大量のトランザクションが発生した場合に、メモリが不足することでOutOfMemoryエラーや、処理の遅延が発生する可能性があります。

サービスのリリース前にはサービス稼働後に想定されるデータ量を事前に登録しておき、本番想定のトランザクション量をかけて負荷試験を行うことを推奨します。

その際、次の情報を取得して、メモリが不足していないかチェックしてください。

jvmstatを使用してテスト中のJVMのメモリ使用量の推移を確認。

jvmstat

アプリケーションサーバーのログにOutOfMemoryのエラーがでていないか確認。

{JBoss Home}/standalone/log/server.log

2. チューニング

2.1. データベースチューニング

AppPotのデータベースには自動的にオブジェクトIDにインデックスが生成されています。アプリのデータについては、オブジェクトID以外の項目にインデックスを追加することが許されています。

アプリのデータベースは次の命名ルールで作成されています。
apppot_{App ID}_{Version}


apppottrial_jp_co_ncdc_MedichineApp_1_2

.は_に置換されます。

データベースの例

mysql> show databases;
+------------------------------------------+
| Database                                 |
+------------------------------------------+
| information_schema                       |
| apppottrial_jp_co_ncdc_MedichineApp_1_2  |
| developer00                              |
| mysql                                    |
| stewsprint5                              |
| stewsprint5_jp_co_ncdc_MedichineApp1_1_0 |
| stewsprint5_ootani_1_0                   |
+------------------------------------------+
14 rows in set (0.00 sec)

アプリデータベースの中はAPObjectを継承して作成したオブジェクトの単位でテーブルが作成されています。

mysql> use stewsprint5_jp_co_ncdc_MedichineApp1_1_0
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> show tables;
+----------------------------------------------------+
| Tables_in_stewsprint5_jp_co_ncdc_MedichineApp1_1_0 |
+----------------------------------------------------+
| CaseReport                                         |
| Follow                                             |
+----------------------------------------------------+
2 rows in set (0.00 sec)

自動生成されたテーブルの構造は以下のようになっています。インデックスを付ける以外の操作はサポートされていませんのでご注意下さい。

mysql> desc CaseReport;
+-------------------------+--------------+------+-----+---------+-------+
| Field                   | Type         | Null | Key | Default | Extra |
+-------------------------+--------------+------+-----+---------+-------+
| isUsingLockForUpdate    | double       | YES  |     | NULL    |       |
| scopeType               | double       | YES  |     | NULL    |       |
| createTime              | double       | YES  |     | NULL    |       |
| updateTime              | double       | YES  |     | NULL    |       |
| memo                    | text         | YES  |     | NULL    |       |
| isFollow                | double       | YES  |     | NULL    |       |
| patientName             | text         | YES  |     | NULL    |       |
| persistentType          | double       | YES  |     | NULL    |       |
| scopeTypeForAutoRefresh | double       | YES  |     | NULL    |       |
| boolTest                | double       | YES  |     | NULL    |       |
| lifeSpan                | double       | YES  |     | NULL    |       |
| objectId                | varchar(255) | NO   | PRI | NULL    |       |
| syncStatus              | text         | YES  |     | NULL    |       |
| gender                  | double       | YES  |     | NULL    |       |
| isAutoRefresh           | double       | YES  |     | NULL    |       |
| age                     | double       | YES  |     | NULL    |       |
| autoRefreshInterval     | double       | YES  |     | NULL    |       |
| createUserId            | double       | YES  |     | NULL    |       |
| groupIds                | text         | YES  |     | NULL    |       |
| serverCreateTime        | varchar(255) | YES  |     | NULL    |       |
| serverUpdateTime        | varchar(255) | YES  |     | NULL    |       |
+-------------------------+--------------+------+-----+---------+-------+
21 rows in set (0.01 sec)

例1:createUserIdにインデックスを付ける場合

mysql> ALTER TABLE CaseReport ADD INDEX (createUserId)

例2:groupIds, createUserIdにインデックスを付ける場合

mysql> ALTER TABLE CaseReport ADD INDEX (groupIds, createUserId)

2.2. ユーザーセッションのメンテナンス

AppPotの運用をしばらく行うとユーザーの認証に応じて、ユーザーのセッション情報がデータベースに蓄積されていきます。
これはログアウトを行うとユーザーのセッション情報は削除されますが、ログアウトを行わないまま
次のログインを行うと古いセッション情報が残ったままになるためです。

ユーザーセッション情報のデータが大量になるとAppPotの性能に影響を与えることがあるため、
ここではAppPotのデータベースにIndexを追加する方法と、有効期限が切れたセッション情報を削除する方法を説明します。

ユーザーのセッション情報はAppPotのデータベースのUserSessionテーブルに格納されています。

UserSessionテーブルへのIndexの追加

AppPotの制御用のデータベースに接続し、以下のSQLを実行してください。

ALTER TABLE UserSession ADD INDEX index_get_session(userId, appTableId, deviceUDID, tokenExpireDate);

ユーザーがログイン済みであるかチェックするSQLの負荷をMySQLの実行計画で確認するとSELECT時の検索対象の行が大幅にすくなっており、Indexが有効に効いていることが確認できます。

  • Index追加前
    MySQL [apppot]> EXPLAIN select * from UserSession where userId=205 and appTableId=4 and deviceUDID='apppotsdkjs' and tokenExpireDate>='2018-01-12 22:16:13';
    +----+-------------+-------------+------+---------------+------+---------+------+--------+-------------+
    | id | select_type | table       | type | possible_keys | key  | key_len | ref  | rows   | Extra       |
    +----+-------------+-------------+------+---------------+------+---------+------+--------+-------------+
    |  1 | SIMPLE      | UserSession | ALL  | NULL          | NULL | NULL    | NULL | 122381 | Using where |
    +----+-------------+-------------+------+---------------+------+---------+------+--------+-------------+
    1 row in set (0.01 sec)
    
  • Index追加後
    MySQL [apppot]> EXPLAIN select * from UserSession where userId=205 and appTableId=4 and deviceUDID='apppotsdkjs' and tokenExpireDate>='2018-01-12 22:16:13';
    +----+-------------+-------------+-------+--------------------------------+-------------------+---------+------+------+-----------------------+
    | id | select_type | table       | type  | possible_keys                  | key               | key_len | ref  | rows | Extra                 |
    +----+-------------+-------------+-------+--------------------------------+-------------------+---------+------+------+-----------------------+
    |  1 | SIMPLE      | UserSession | range | index_userId,index_get_session | index_get_session | 792     | NULL |  258 | Using index condition |
    +----+-------------+-------------+-------+--------------------------------+-------------------+---------+------+------+-----------------------+
    1 row in set (0.08 sec)
    

有効期限を過ぎたユーザーセッション情報の削除

有効期限の切れたセッション情報は以下のSQLで取得することができます。

select * from UserSession 
where tokenExpireDate < (NOW() + INTERVAL 9 HOUR) 
and tokenExpireDate > '1970-01-01 09:00:00'
order by tokenExpireDate;

AppPotのtokenExpireDateはデータベースのロケールに関わらず日本時間で記録されていますが、
mysqlコマンドでデータベースにアクセスする際にはデータベースの環境に依存して、NOW関数の結果が異なりなす。
上記の例ではmysqlコマンドでデータベースにアクセスした際の日付がUTCで返却されるため
NOW() + INTERVAL 9 HOURで現在時間に9時間足しています。
お使いのデータベースの環境に応じて変更してください。

‘1970-01-01 09:00:00’を対象外としているのは、この値がAppPotの内部的に制御用に使用される可能性があるためです。

以下のようなSQLをcronやジョブスケジューラで毎日、週次などで定期的時実行することで、
ユーザーセッション情報が増え続けることを防げます。
ここでは3日前に有効期限切れを起こしたユーザートークンを削除しています。

delete from UserSession 
where tokenExpireDate < (NOW() + INTERVAL 9 HOUR - INTERVAL 3 DAY)
and tokenExpireDate > '1970-01-01 09:00:00';