Oracle PGXでjava.lang.OutOfMemoryErrorが発生した時の3つの対処法

##記事を書いた理由
Oracle PGXでOutOfMemoryErrorが発生した際、対処に土日を費やしてしまったため、対処法を備忘録として残しておきます。

##発生したエラー
DeepWalk、Pg2vecというアルゴリズムを動かそうとしている時、以下のエラーが発生しました。

```
pgx> graph=session.readGraphWithProperties("NCI109.json").undirect() #Load Data
==> PgxGraph[name=sub-graph_1,N=122494,E=265202,created=1567303434267]
pgx> model=analyst.deepWalkModelBuilder()\ #Create Model
..1>   .setWindowSize(3)\
..2>   .setWalksPerVertex(6)\
..3>   .setWalkLength(4)\
..4>   .build()
==> oracle.pgx.api.beta.mllib.DeepWalkModel@2abbd0d9
pgx> model.fit(graph) #Training
ERROR: java.lang.OutOfMemoryError: Cannot allocate new FloatPointer(24403600): totalBytes = 338M, physicalBytes = 955M
```

```
pgx> graph=session.readGraphWithProperties("NCI109.json").undirect() #Load Data
==> PgxGraph[name=sub-graph_1,N=122494,E=265202,created=1567303434267]
pgx> model = analyst.pg2vecModelBuilder()\  #Training
..1>         .setGraphLetIdPropertyName("graph_id")\
..2>         .setVertexPropertyNames(Arrays.asList("node_label"))\
..3>         .setWindowSize(4)\
..4>         .setWalksPerVertex(5)\
..5>         .setWalkLength(8)\
..6>         .build()
==> oracle.pgx.api.beta.mllib.Pg2vecModel@5a936e64
pgx> model.fit(graph) #Training
ERROR: java.lang.OutOfMemoryError: Java heap space
```

##PGXのメモリ構造
PGXのメモリには、大きく分けて2種類のメモリが存在します。**グラフのトポロジーを読み込むメモリ**と**ノードとエッジのプロパティーを読み込むメモリ**の2つです。

###グラフのトポロジーを読み込むメモリ
ここには、グラフのトポロジー（プロパティーを考慮せず、各ノードと各エッジがどのように繋がっているかを表す情報）が読み込まれます。
PGXでは、グラフのトポロジーを格納するために、メモリの消費量を最小限に抑え、読み込み速度を最速化できる**圧縮行格納方式（CSR : Compressed Sparse Row）**を採用しています。
圧縮行格納方式に関しては、[Wikipedia](https://ja.wikipedia.org/wiki/%E7%96%8E%E8%A1%8C%E5%88%97#%E5%9C%A7%E7%B8%AE%E8%A1%8C%E6%A0%BC%E7%B4%8D%E6%96%B9%E5%BC%8F_(CRS)%E3%83%BBCompressed_Sparse_Row_(CSR))の解説がわかりやすいので、そちらをご参照ください。

###プロパティーを読み込むメモリ
ここには、各ノードと各エッジのプロパティーが読み込まれます。

####プロパティのメモリ消費量

| Property Type  |  Size in Bytes   |
|:-----------------:|:------------------:|
| int |     4 | 
| float         |            4 |
| long           |              8 |
| double     |                8 |
| boolean     |             1 |
| date       |           8 |
| local date      |           4 |
| time     |           4 |
| timestamp    |           8 |
| time with timezone   |           8 |
| timestamp with timezone   |           12 |
| point 2d     |           16 |
| string     |           variable |
[Document](https://docs.oracle.com/cd/E56133_01/latest/reference/memory.html)より引用

##考えられる原因
OutOfMemoryErrorの原因として考えられるものは、以下の3つです。

- On-heap領域が足りなくなったにも関わらず、PGXがメモリを獲得しようとしている
- Off-heap領域が足りなくなったにも関わらず、PGXがメモリを獲得しようとしている
- PGXで使用できるメモリ領域が少ない(OS上で設定されている)

これらは、ログを見ることで解析することができます。
PGXで詳細なログを見る場合は、以下のコマンドを実行します。

```
pgx> :ll root debug
==> log level of root logger set to DEBUG
```
その他にも、様々なログレベルを設定することができます。詳細は、[Document](https://docs.oracle.com/cd/E56133_01/2.7.0/reference/config/logging.html)を参照してください。

##対処法：On-heap領域が足りなくなった場合
On-heap領域が足りなくなった場合は、単純にPGXに割り当てるヒープ領域を増やします。
###ローカルでJavaアプリケーションとしてPGXを実行している場合
アプリケーション起動時に、以下のオプションを設定します。

例）ヒープ領域の初期サイズを1024MB、最大サイズを2048MBに設定する

```java
java -Xms1024m -Xmx2048m ＜メインクラス名＞
```

###PGXシェルでPGXを実行している場合
環境変数``JAVA_OPTS``を設定してから、再度PGXを起動します。

例）ヒープ領域の初期サイズを1024MB、最大サイズを2048MBに設定する

```
export JAVA_OPTS="-Xms1024m -Xmx2048m"
echo $JAVA_OPTS
-Xms1024m -Xmx2048m
sh $PGX_HOME/bin/pgx
```
##対処法：Off-heap領域が足りなくなった場合
``pgx.conf``内の``max_off_heap_size``を確認します。
(``max_off_heap_size``は、新たなメモリ領域の獲得をしなくなるための、単なる閾値です。もともと設定されていても、設定されていた値以上にメモリが獲得されることもあります。）

```
vi $PGX_HOME/conf/pgx.conf
```

```
{
  "allow_idle_timeout_overwrite": true,
  "allow_local_filesystem": false,
  "allow_task_timeout_overwrite": true,
  "enable_gm_compiler": true,
  "max_off_heap_size":1024,  #これを無くしてみる
  "enterprise_scheduler_config": {
    "analysis_task_config": {
      "priority": "MEDIUM",
      "weight": "<no-of-CPUs>",
      "max_threads": "<no-of-CPUs>"
    },
    "fast_analysis_task_config": {
      "priority": "HIGH",
      "weight": 1,
      "max_threads": "<no-of-CPUs>"
    },
    "num_io_threads_per_task": "<no-of-CPUs>"
  },
  "graphs": [],
  "max_active_sessions": 1024,
  "max_queue_size_per_session": -1,
  "max_snapshot_count": 0,
  "memory_cleanup_interval": 600,
  "path_to_gm_compiler": null,
  "release_memory_threshold": 0.85,
  "session_idle_timeout_secs": 0,
  "session_task_timeout_secs": 0,
  "strict_mode": true,
  "tmp_dir": "tmp_data",
  "in_place_update_consistency_model" : "ALLOW_INCONSISTENCIES"
}
```

##対処法：PGXで使用できるメモリ領域が少ない場合
上記2つの方法を試してもうまくいかない場合は、PGXで使用できるメモリ領域が少ないことを疑いましょう。
（今回は、この方法を試してうまくいくようになりました。）
その場合は、OS上でメモリ割り当てを増やしてあげます。
私はDockerを使用してPGXを動かしているのですが、どうも割り当てられているメモリ容量が小さいように感じました。

今までは見逃していたのですが、PGX起動時に、割り当てられたメモリの総量を確認すると、``1999MB``しか割り当てられていないことがわかりました。

```
Kohei:NCI109 kohei$ pgx
tput: unknown terminfo capability 'setafsd'
[WARNING] PGX shell is using Groovy installed in /opt/groovy-2.5.8. It is the responsibility of the user to use the latest version of Groovy and to protect against known Groovy vulnerabilities.
[WARNING] The Groovy-based PGX shell will be removed and replaced by a jshell-based PGX shell.
Sep 01, 2019 1:56:34 AM java.util.prefs.FileSystemPreferences$1 run
INFO: Created user preferences directory.
01:56:36,187 INFO Ctrl$1 - >>> start engine
01:56:37,070 INFO StartupLogging - >>> PGX sm engine 19.2.1/ API 3.4.1 running.
01:56:37,071 INFO StartupLogging - >>> PGQL version 1.2
01:56:37,072 INFO StartupLogging - >>> Built from commit fdc975ef0f19828115fd76ed19ca79c45af52d2e
01:56:37,072 INFO StartupLogging - >>> Built at 2019-06-27T09:38:59.982-07:00
01:56:37,073 INFO StartupLogging - >>> JDK version: 1.8.0_212
01:56:37,073 INFO StartupLogging - >>> Operating system: Linux
01:56:37,074 INFO StartupLogging - >>> Number of processors: 2
01:56:37,076 INFO StartupLogging - >>> Total system memory: 1999 MB #これを増やしたい！

PGX Shell 19.2.1
PGX server version: 19.2.1 type: SM running in embedded mode.
PGX server API version: 3.4.1
PGQL version: 1.2
type :help for available commands
variables instance, session and analyst ready to use
pgx> 
```
###Dockerのメモリを増やす
ということで、Dockerが使用できるメモリを増やします。

1. クジラのマークをクリックします（充電しろよ、というツッコミは無視します）。
<img width="1440" alt="スクリーンショット 2019-09-01 16.50.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248756/c8a10814-9d21-fa11-ecee-c771e1f4550c.png">

2. Preferencesをクリックします。
<img width="1440" alt="スクリーンショット 2019-09-01 16.53.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248756/8875d6f6-61b9-37bc-9872-44e4076982e8.png">

3. Advancedタブを選択し、割り当てたいメモリ量を調整します。
<img width="504" alt="スクリーンショット 2019-09-01 16.55.19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248756/fa7867cb-6035-9c6e-e7c0-098ef35679c3.png">

4. Apply & Restartをクリックし、Dockerが再起動するのを待ちます。
<img width="507" alt="スクリーンショット 2019-09-01 16.57.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248756/9f3166df-a0a1-2787-e8de-af20d6e8190f.png">

5. Dockerが再起動したら、再度PGXを起動します。

```
kohei$ pgx
tput: unknown terminfo capability 'setafsd'
[WARNING] PGX shell is using Groovy installed in /opt/groovy-2.5.8. It is the responsibility of the user to use the latest version of Groovy and to protect against known Groovy vulnerabilities.
[WARNING] The Groovy-based PGX shell will be removed and replaced by a jshell-based PGX shell.
Sep 01, 2019 7:58:52 AM java.util.prefs.FileSystemPreferences$1 run
INFO: Created user preferences directory.
07:58:55,010 INFO Ctrl$1 - >>> start engine
07:58:56,168 INFO StartupLogging - >>> PGX sm engine 19.2.1/ API 3.4.1 running.
07:58:56,173 INFO StartupLogging - >>> PGQL version 1.2
07:58:56,173 INFO StartupLogging - >>> Built from commit fdc975ef0f19828115fd76ed19ca79c45af52d2e
07:58:56,174 INFO StartupLogging - >>> Built at 2019-06-27T09:38:59.982-07:00
07:58:56,178 INFO StartupLogging - >>> JDK version: 1.8.0_212
07:58:56,178 INFO StartupLogging - >>> Operating system: Linux
07:58:56,180 INFO StartupLogging - >>> Number of processors: 2
07:58:56,185 INFO StartupLogging - >>> Total system memory: 5453 MB  #1999MBから増加した！

PGX Shell 19.2.1
PGX server version: 19.2.1 type: SM running in embedded mode.
PGX server API version: 3.4.1
PGQL version: 1.2
type :help for available commands
variables instance, session and analyst ready to use
pgx> 
```
メモリ容量が増えました！早速実行します！

###DeepWalk

```
pgx> graph=session.readGraphWithProperties("NCI109.json").undirect()
==> PgxGraph[name=sub-graph_1,N=122494,E=265202,created=1567324836478]
pgx> model=analyst.deepWalkModelBuilder()\
..1> .setWindowSize(3)\
..2> .setWalksPerVertex(6)\
..3> 
..3> .setWalkLength(4)\
..4> .build()
==> null
pgx> model.getLoss() #学習後の損失を算出する
==> -2.85346752548799
pgx> sim=model.computeSimilars(111,3) #ID111のノードと似ているノードを3つ見つける
==> oracle.pgx.api.beta.frames.internal.PgxFrameImpl@bf75b5c
pgx> sim.print()
+--------------------------------+
| dstVertex | similarity         |
+--------------------------------+
| 111       | 1.0000001192092896 |
| 85        | 0.9343633055686951 |
| 81        | 0.8769448399543762 |
+--------------------------------+
==> oracle.pgx.api.beta.frames.internal.PgxFrameImpl@bf75b5c

```
###Pg2vec
```
pgx> graph=session.readGraphWithProperties("NCI109.json").undirect()
==> PgxGraph[name=sub-graph_1,N=122494,E=265202,created=1567325544948]
pgx> model = analyst.pg2vecModelBuilder()\
..1>        .setGraphLetIdPropertyName("graph_id")\
..2>        .setVertexPropertyNames(Arrays.asList("node_label"))\
..3>        .setWindowSize(4)\
..4>        .setWalksPerVertex(5)\
..5>        .setWalkLength(8)\
..6>        .build()
==> oracle.pgx.api.beta.mllib.Pg2vecModel@1727e03a
pgx> model.fit(graph)
==> null
pgx> similars = model.computeSimilars(52, 10) #ID52のグラフレットと似ているグラフレットを10個見つける
==> oracle.pgx.api.beta.frames.internal.PgxFrameImpl@78d71df1
pgx> similars.print()
+----------------------------------+
| dstGraphlet | similarity         |
+----------------------------------+
| 52          | 1.0                |
| 42          | 0.8582297563552856 |
| 49          | 0.8517727255821228 |
| 74          | 0.8314518332481384 |
| 483         | 0.8259901404380798 |
| 48          | 0.8057224154472351 |
| 102         | 0.7991676330566406 |
| 73          | 0.7878939509391785 |
| 55          | 0.7401301860809326 |
| 381         | 0.7370997071266174 |
+----------------------------------+
==> oracle.pgx.api.beta.frames.internal.PgxFrameImpl@78d71df1
```

動くようになりました！

##まとめ
PGXでOutOfMemoryErrorが発生したら、まずは以下の3つの方法を順に試すべきです。
詳しくは[Document](https://docs.oracle.com/cd/E56133_01/latest/reference/memory.html)にも記載されているので、ご参照ください。

- On-heap領域を増やす
- Off-heap領域を増やす
- PGXで使用できるメモリ領域を増やす