AWS Neo4jとOracle PGXでC.elegansのGraphを可視化してみた話

# 1. 初めに
ここ最近グラフデータベースを触れる機会が多くなったので、備忘録としてNeo4jやOracle Graph Server & Client(PGX)の環境構築や可視化等の簡易的な使い方を纏めておこうと思い投稿します。特に両製品の比較を目的としたメリデメは書かず、それぞれの機能にフォーカスして色々試してみたいと思います。なお、筆者はグラフデータベース領域を専門としたプロフェッショナルではないことをあらかじめご理解ください。

# 本投稿のGoalと構成
## Goal
- Neo4jをインストールし、データセット:C.elegansを可視化する
- PGXをインストールし、データセット:C.elegansを可視化する

## 主な構成
- 前半:Neo4j (neo4j-4.2.1)、後半:PGX (Oracle Graph Server 21.1.0)
- Neo4j編
    -  データセット説明（CSVをそのまま使う)
    -  AWS上でNeoインストール (neo4j.confなど)
    -  Cypherクエリを投げて可視化+アルゴリズム試行

- PGX編
    - データセット説明 (RDBMS ⇨ Graph Server(PGX)へ)
    - OCI上でPGXインストール (pgx.confなど)
    - PGQLを投げて可視化+アルゴリズム試行
    - AutoRefreshを試す
- まとめ
    - 思ったこと等

# 2. Neo4j編
## 2.1 データセット説明（CSVをそのまま使う)
今回は、カエノラブディティス・エレガンスと呼ばれる線虫のニューロンやシナプスの神経ネットワークデータを使用します。
下記からC.elegansのデータをダウンロードし、SCP等でインスタンスに配置します。
[C.elegans](https://snap.stanford.edu/data/C-elegans-frontal.html)

## 2.2 AWS上でNeoインストール (neo4j.confなど)
### 2.2.1 EC2環境を準備 
```
tharashi@ORCL:~$ ssh ec2

       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/
2 package(s) needed for security, out of 5 available
Run "sudo yum update" to apply all updates.

ec2-user@ip-172-31-40-233 ~]$ sudo yum install java-11-amazon-corretto
ec2-user@ip-172-31-40-233 ~]$ java -version
openjdk version "11.0.9.1" 2020-11-04 LTS
OpenJDK Runtime Environment Corretto-11.0.9.12.1 (build 11.0.9.1+12-LTS)
OpenJDK 64-Bit Server VM Corretto-11.0.9.12.1 (build 11.0.9.1+12-LTS, mixed mode)
[ec2-user@ip-172-31-40-233 ~]$ 
```
### 2.2.2 Neo4jをEC2へインストール(To install Neo4j Community Edition as root:)
```
[root@ip-172-31-40-233 ~]# yum install neo4j-4.2.1 -y
...
Total                                                                                        16 MB/s | 111 MB  00:00:06     
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : cypher-shell-4.2.2-1.noarch                                                                              1/2 
  Installing : neo4j-4.2.1-1.noarch                                                                                     2/2 
  Verifying  : neo4j-4.2.1-1.noarch                                                                                     1/2 
  Verifying  : cypher-shell-4.2.2-1.noarch                                                                              2/2 

Installed:
  neo4j.noarch 0:4.2.1-1                                                                                                    

Dependency Installed:
  cypher-shell.noarch 0:4.2.2-1                                                                                             

Complete!
```
### 2.2.3 Neo4jをインストール後、構成ファイルを確認
```
[root@ip-172-31-40-233 ~]# vi /etc/neo4j/neo4j.conf

#*****************************************************************
# Neo4j configuration
#
# For more details and a complete list of settings, please see
# https://neo4j.com/docs/operations-manual/current/reference/configuration-settings/
#*****************************************************************

# The name of the default database
#dbms.default_database=neo4j

# Paths of directories in the installation.
dbms.directories.data=/var/lib/neo4j/data
dbms.directories.plugins=/var/lib/neo4j/plugins
dbms.directories.logs=/var/log/neo4j
dbms.directories.lib=/usr/share/neo4j/lib
dbms.directories.run=/var/run/neo4j
#dbms.directories.transaction.logs.root=data/transactions
...

[その他]
※ /etc/bin/内には、デフォルトで下記3つ存在する
neo4j 
neo4j-admin
neo4j-import
```

### 2.2.4 デフォルトの70行目をアンコメント
```
/etc/neo4j/neo4j.conf
64 #*****************************************************************
65 # Network connector configuration
66 #*****************************************************************
67 
68 # With default configuration Neo4j only accepts local connections.
69 # To accept non-local connections, uncomment this line:
70 dbms.default_listen_address=0.0.0.0
```

### 2.2.5 Neo4jのadminパスワードを変更
```
[root@ip-172-31-40-233 ~]# neo4j-admin set-initial-password <your_password>
Changed password for user 'neo4j'.
```

### 2.2.6 Neo4jのサービスを起動
```
[root@ip-172-31-40-233 ~]# systemctl status neo4j 
● neo4j.service - Neo4j Graph Database
   Loaded: loaded (/usr/lib/systemd/system/neo4j.service; disabled; vendor preset: disabled)
   Active: inactive (dead)
[root@ip-172-31-40-233 ~]# systemctl start neo4j 
[root@ip-172-31-40-233 ~]# systemctl status neo4j 
● neo4j.service - Neo4j Graph Database
   Loaded: loaded (/usr/lib/systemd/system/neo4j.service; disabled; vendor preset: disabled)
   Active: active (running) since Sat 2021-01-09 13:48:26 UTC; 2s ago
 Main PID: 3896 (java)
   CGroup: /system.slice/neo4j.service
           └─3896 /usr/bin/java -cp /var/lib/neo4j/plugins:/etc/neo4j:/usr/share/neo4j/lib/*:/var/lib/neo4j/plugins/* -XX...

Jan 09 13:48:27 ip-172-31-40-233.ap-northeast-1.compute.internal neo4j[3896]: Directories in use:
Jan 09 13:48:27 ip-172-31-40-233.ap-northeast-1.compute.internal neo4j[3896]: home:         /var/lib/neo4j
Jan 09 13:48:27 ip-172-31-40-233.ap-northeast-1.compute.internal neo4j[3896]: config:       /etc/neo4j
Jan 09 13:48:27 ip-172-31-40-233.ap-northeast-1.compute.internal neo4j[3896]: logs:         /var/log/neo4j
Jan 09 13:48:27 ip-172-31-40-233.ap-northeast-1.compute.internal neo4j[3896]: plugins:      /var/lib/neo4j/plugins
Jan 09 13:48:27 ip-172-31-40-233.ap-northeast-1.compute.internal neo4j[3896]: import:       /var/lib/neo4j/import
Jan 09 13:48:27 ip-172-31-40-233.ap-northeast-1.compute.internal neo4j[3896]: data:         /var/lib/neo4j/data
Jan 09 13:48:27 ip-172-31-40-233.ap-northeast-1.compute.internal neo4j[3896]: certificates: /var/lib/neo4j/certificates
Jan 09 13:48:27 ip-172-31-40-233.ap-northeast-1.compute.internal neo4j[3896]: run:          /var/run/neo4j
Jan 09 13:48:27 ip-172-31-40-233.ap-northeast-1.compute.internal neo4j[3896]: Starting Neo4j.
```

### 2.2.7 CypherのShellに入る
```
root@ip-172-31-40-233 bin]# ./cypher-shell -d system
username: neo4j
password: *************
Connected to Neo4j 4.2.0 at neo4j://localhost:7687 as user neo4j.
Type :help for a list of available commands or :exit to exit the shell.
Note that Cypher queries must end with a semicolon.
neo4j@system> 
neo4j@system> 
neo4j@system>
```

### 2.2.8 Neo4jのブラウザへ
http://18.183.223.190:7474/browser/  を叩いてneo4jのブラウザに入れます。
Username, Passwordについてはneo4j-adminコマンドで設定したものを入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577151/7e16f9e2-c3d0-56a9-1919-e121bc3b9177.png)

### 2.2.9 C.elegansのデータをImport用ディレクトリへ配置(SCPなど)
```
[root@ip-172-31-40-233 tmp]# cd /var/lib/neo4j/import
[root@ip-172-31-40-233 import]# ls
C_elegans_node.csv  C_elegans_reration.csv
```

### 2.2.10 C.elegansのデータをロード
```Cypher:NODE/EDGE作成
LOAD CSV WITH HEADERS FROM 'file:///C_elegans_node.csv' AS line
  CREATE (n:User {user_id:toInteger(line.node_id),name:line.name,cord_x:toFloat(line.posx),cord_y:toFloat(line.posy)})

LOAD CSV WITH HEADERS FROM 'file:///C_elegans_relation.csv' AS line
  MATCH (nf:User {user_id:toInteger(line.from)}), (nt:user_User {id:toInteger(line.to)})
  CREATE (nf)-[:connected]->(nt)
```
データロードの際のブラウザ画面は下記のようになる。
基本的にCypherクエリを打てる環境はブラウザ内で完結する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577151/bee6739d-f2ae-d7ee-a308-ab321c70c628.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577151/f6d3c8d3-471a-8a6a-0a63-8845951c20cc.png)

## 2.3 Cypherクエリを投げて可視化+アルゴリズム試行
### 2.3.1 C.elegansを可視化
もっともシンプルなCypherクエリを投げると、下図のような可視化になります。
特に可視化の設定などをいじってないにも関わらずこれだけの可視化ができるのはとても便利です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577151/0c3c1221-82db-0800-c5d0-4765af6fd5f7.png)

### 2.3.2 いくつかアルゴリズムを試行
#### 2.3.2.1 次数中心性(特定のノードに関連するエッジの多さ)
エッジの多さを計算するためにEdge数をCOUNTします。
今回のクエリにより、user_id=72のものが最も高いノードとなることがわかります。

```Cypher:次数中心性を求めるクエリ  
MATCH (u1:User)-[e:connected]->(u2:User) 
  RETURN u1, count(e) AS degree 
  ORDER BY degree 
  DESC LIMIT 3
//[結果]
╒══════════════════════════════════════════════════════════════════════╤════════╕
│"u1"                                                                  │"degree"│
╞══════════════════════════════════════════════════════════════════════╪════════╡
│{"name":"RIAR","cord_y":-0.0046538271360000005,"user_id":72,"cord_x":0│22      │
│.08989234568832001}                                                   │        │
├──────────────────────────────────────────────────────────────────────┼────────┤
│{"name":"RIAL","cord_y":-0.00393520658304,"user_id":71,"cord_x":0.0895│21      │
│2595038912001}                                                        │        │
├──────────────────────────────────────────────────────────────────────┼────────┤
│{"name":"AVER","cord_y":0.00318419750784,"user_id":35,"cord_x":0.08401│20      │
│382717568001}                                                         │        │
```
上記の結果から、user_id=72のノードをフィルタしたクエリを投げてみます。
"RIAR"というNAMEプロパティを持つNODE周りのエッジ数が多いことが読み取れます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577151/5cacc5c7-b173-ff00-edea-6d0ef2c2543d.png)


#### 2.3.2.2 コミュニティ検出(グラフ上でのクラスタリング)
コミュニティ検出のアルゴリズムを使用するために、/var/lib/neo4j/plugins にプラグイン(.jar)を入れます。マニュアルを見ると、graph-algorithms-algo-3.5.4.0.jar (NG: https://github.com/neo4j-contrib/neo4j-graph-algorithms/releases) は、現在depricatedとありました。今回のNeo4jのVersionでdeprecated版を使ったところ、Neo4jが立ち上がリませんでした(ログを見るとわかります)。そのため、下記の Neo4j Graph Data Science Library( GDS )を使用します。ダウンロードして.jarを/var/lib/neo4j/plugins配下に入れます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577151/3d9c01a3-a35d-30de-1c1e-0567af92e706.png)

```SQL:プラグインを読み込むために、Neo4j構成ファイルを修正
[root@ip-172-31-40-233 tmp]# cd /var/lib/neo4j/plugins
[root@ip-172-31-40-233 plugins]# ls
README.txt  neo4j-graph-data-science-1.4.1-standalone.jar

[root@ip-172-31-40-233 plugins]# vim /etc/neo4j/neo4j.conf
--[Before]
#dbms.security.procedures.unrestricted=my.extensions.example,my.procedures.*
#dbms.security.procedures.allowlist=apoc.coll.*,apoc.load.*,gds.*

--[After]
dbms.security.procedures.unrestricted=gds.*
dbms.security.procedures.allowlist=gds.*

[root@ip-172-31-40-233 plugins]# systemctl start neo4j
```
下記のdbms.procedureでgdsが入ったことを確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577151/1ae6c4f5-c81a-742c-bdac-da05680b9904.png)


```SQL:Graphカタログにデータを登録します
CALL gds.graph.create(
    'C_elegan_Graph',
    'User',
    {
        connected: {
            orientation: 'NATURAL'　//向きを指定
        }
    },{
        nodeProperties: 'cord_y'
    }
)
```
これで、C_elegan_Graphという名前でカタログに登録されたので、GDSが使えるようになります。今回はアルゴリズム: louvainを用います。モジュラリティ(分割されたコミュニティ内のエッジの数と、コミュニティ間同士のエッジの数との比較により、高密度でサブセットを形成しているかどうか)も算出します。

```Cypher:コミュニティカウントとモジュラリティ算出
CALL gds.louvain.stats('C_elegan_Graph')
YIELD communityCount
// 結果
╒════════════════╕
│"communityCount"│
╞════════════════╡
│10              │
└────────────────┘


CALL gds.louvain.mutate('C_elegan_Graph', { mutateProperty: 'communityId' })
YIELD communityCount, modularity, modularities
// 結果
╒════════════════╤══════════════════╤════════════════════╕
│"communityCount"│"modularity"      │"modularities"      │
╞════════════════╪══════════════════╪════════════════════╡
│10              │0.2876531619199035│[0.2876531619199035]│
└────────────────┴──────────────────┴────────────────────┘
```

#### 2.3.2.3 グラフエンべディング(Graph Embedding)
グラフエンベディング(グラフ上の性質を反映させるように、ノードごとに数値ベクトルを割り当てる)も試してみます。ここでは2次元にします。

```Cypher:グラフエンべディング(Graph_Embedding)
CALL gds.alpha.node2vec.stream('C_elegan_Graph', {walkLength: 50,walksPerNode:20,embeddingDimension: 2})

//結果
╒════════╤════════════════════════════════════════════╕
│"nodeId"│"embedding"                                 │
╞════════╪════════════════════════════════════════════╡
│171     │[-0.5261988639831543,-1.1787532567977905]   │
├────────┼────────────────────────────────────────────┤
│172     │[-0.3521956503391266,-1.2334128618240356]   │
├────────┼────────────────────────────────────────────┤
│173     │[-1.0288382768630981,-1.0470720529556274]   │
├────────┼────────────────────────────────────────────┤
│174     │[0.7258012294769287,-1.7890225648880005]    │
```


# 3. Oracle Graph Server & Client ( PGX )
## 3.1 データセット説明 (RDBMS ⇨ Graph Server(PGX)へ)
Neo4jと同じく 2.1 のデータセットを用います。

オブジェクトストレージから外部表として、Autonomous DBに格納します。
※ Neo4j同様に、ローカルファイルとしてグラフにロードすることも可能であるが、今回はDBからのAuto Reflesh機能を試したいため、いったんDBへリレーショナル形式で格納

## 3.2 OCI上でPGXインストール (pgx.confなど)
### 3.2.1 Compute(VM)環境を構築

```sql:ComuteインスタンスへSSH接続
[opc@kix-graph-server ~]$ cat /etc/oracle-release 
Oracle Linux Server release 7.9
[opc@kix-graph-server ~]$ sudo su
[root@kix-graph-server opc]# 
[root@kix-graph-server ~]# java -version
java version "15.0.2" 2021-01-19
Java(TM) SE Runtime Environment (build 15.0.2+7-27)
Java HotSpot(TM) 64-Bit Server VM (build 15.0.2+7-27, mixed mode, sharing)
```
今回は現時点で最新版のjdk-15.0.2_linux-x64_bin.rpmをインストールしておきます。
LTSのJava SE11 (LTS)でも可能です。

### 3.2.2 Oracle Graph Server 21.1.0 をCompute（VM)にインストール
[edelivery](https://edelivery.oracle.com/osdc/faces/SoftwareDelivery)からOracle Graph Server and Client 21.1.0に関連するファイルをインストールできる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577151/2b476e60-5378-b2d0-6344-78f17a6cb77b.png)

```sql:rpmを展開
[root@kix-graph-server tmp]# rpm -ivh oracle-graph-21.1.0.x86_64.rpm 
準備しています...              ################################# [100%]
更新中 / インストール中...
1:oracle-graph-0:21.1.0-0          ################################# [100%]
Starting post-installation process ...
1. Creating temp directory /opt/oracle/graph/pgx/tmp_data
2. Creating log directory /var/log/oracle/graph
3. Installing Python client ...
3. Generating demo certificate ...
`ca_certificate.pem' の所有者を root:root から oraclegraph:oraclegraph へ変更しました
`ca_key.pem' の所有者を root:root から oraclegraph:oraclegraph へ変更しました
`server_certificate.pem' の所有者を root:root から oraclegraph:oraclegraph へ変更しました
`server_key.pem' の所有者を root:root から oraclegraph:oraclegraph へ変更しました
`server_key_traditional.pem' の所有者を root:root から oraclegraph:oraclegraph へ変更しました
Certificates successfully generated in /etc/oracle/graph
Post-installation process completed
```

### 3.2.3 Graph用のユーザ(pgx_user)を作成してoraclegraphグループへ追加
```sql:ユーザの追加
[root@kix-graph-server ~]# useradd pgx_user
[root@kix-graph-server ~]# id pgx_user
uid=1001(pgx_user) gid=1001(pgx_user) groups=1001(pgx_user)

[root@kix-graph-server ~]# usermod -a -G oraclegraph pgx_user
[root@kix-graph-server ~]# id pgx_user
uid=1001(pgx_user) gid=1001(pgx_user) groups=1001(pgx_user),992(oraclegraph)
```
### 3.2.4 AutonomousDBのWalletをComputeに配置
```sql:Wallet.zipはOCIのコンソールからダウンロード
[root@kix-graph-server tmp]# mkdir -p /etc/wallet
[root@kix-graph-server tmp]# mv Wallet_PGXATP_oraora.zip
[root@kix-graph-server wallet]# unzip Wallet_PGXATP_oraora.zip 
Archive:  Wallet_PGXATP_oraora.zip
  inflating: README                  
  inflating: cwallet.sso             
  inflating: tnsnames.ora            
  inflating: truststore.jks          
  inflating: ojdbc.properties        
  inflating: sqlnet.ora              
  inflating: ewallet.p12             
  inflating: keystore.jks            
[root@kix-graph-server wallet]# ls
README                    cwallet.sso  keystore.jks      sqlnet.ora    truststore.jks
Wallet_PGXATP_oraora.zip  ewallet.p12  ojdbc.properties  tnsnames.ora
```

### 3.2.5 pgx.conf/server.confを編集する
```sql:GraphServerの構成ファイルを編集(JDBCとTLSの設定箇所)
[pgx_user@kix-graph-server ~]# cat /etc/oracle/graph/pgx.conf
{
  "allow_idle_timeout_overwrite": true,
  "allow_local_filesystem": false,
  "allow_task_timeout_overwrite": true,
  "authorization": [{
    "pgx_role": "GRAPH_ADMINISTRATOR",
    "pgx_permissions": []
  }, {
    "pgx_role": "GRAPH_DEVELOPER",
    "pgx_permissions": []
  }],
  "enable_gm_compiler": true,
  "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>"
  },
  "graph_algorithm_language": "JAVA",
  "in_place_update_consistency_model": "ALLOW_INCONSISTENCIES",
  "java_home_dir": "<system-java-home-dir>",
  "max_active_sessions": 1024,
  "max_queue_size_per_session": -1,
  "max_snapshot_count": 0,
  "memory_cleanup_interval": 60,
  "preload_graphs": [],
  "pgx_realm": {
    "implementation": "oracle.pg.identity.DatabaseRealm",
    "options": {
      "jdbc_url": "jdbc:oracle:thin:@pgxatp_low?TNS_ADMIN=/etc/wallet",←ココ
      "token_expiration_seconds": 3600,
      "connect_timeout_milliseconds": 10000,
      "connection_pool_cleanup_interval_secs": 10,
      "max_pool_size": 64,
      "max_num_users": 512,
      "max_num_token_refresh": 24
    }
  },
  "release_memory_threshold": 0.25,
  "session_idle_timeout_secs": 14400,
  "session_task_timeout_secs": 0,
  "strict_mode": true,
  "tmp_dir": "/tmp"
}
[pgx_user@kix-graph-server ~]$ cd /etc/oracle/graph/
[pgx_user@kix-graph-server graph]$ ls
ca_certificate.pem  log4j2-server.xml  pgx.conf          server_certificate.pem  server_key.pem
ca_key.pem          log4j2.xml         server.auth.conf  server.conf             wallets
[pgx_user@kix-graph-server graph]$ cat server.conf 
{
  "port": 7007,
  "enable_tls": false,←ココ
  "enable_client_authentication": false,
  "server_private_key": "/etc/oracle/graph/server_key.pem",
  "server_cert": "/etc/oracle/graph/server_certificate.pem",
  "ca_certs": [],
  "working_dir": "/opt/oracle/graph/pgx/tmp_data"
}
```

### 3.2.6 Graph Serverの認証認可の設定
```sql:AutonomousDB側で実行(Autonomousの場合、コンソール上でSQLDeveloperWebでSQLを叩けます)
SQL> CREATE ROLE graph_developer;
ロールが作成されました。

SQL> CREATE ROLE graph_administrator;
ロールが作成されました。

SQL> CREATE USER DB_PGX_USER IDENTIFIED BY パスワード TEMPORARY TABLESPACE TEMP;
ユーザーが作成されました。

SQL> GRANT CREATE SESSION TO DB_PGX_USER;
権限付与が成功しました。

SQL> GRANT graph_developer TO DB_PGX_USER;
権限付与が成功しました。

SQL> GRANT graph_administrator TO DB_PGX_USER;
権限付与が成功しました。

-- ※確認用SQL(ADMINで)
-- SELECT * FROM DBA_SYS_PRIVS where grantee='DB_PGX_USER';
-- SELECT * FROM dba_role_privs where grantee='DB_PGX_USER';
```

```sql:DB側でロールを作成する(20.4の場合は叩く必要なし)
DECLARE
  PRAGMA AUTONOMOUS_TRANSACTION;
  role_exists EXCEPTION;
  PRAGMA EXCEPTION_INIT(role_exists, -01921);
  TYPE graph_roles_table IS TABLE OF VARCHAR2(50);
  graph_roles graph_roles_table;
BEGIN
  graph_roles := graph_roles_table(
    'GRAPH_DEVELOPER',
    'GRAPH_ADMINISTRATOR',
    'PGX_SESSION_CREATE',
    'PGX_SERVER_GET_INFO',
    'PGX_SERVER_MANAGE',
    'PGX_SESSION_READ_MODEL',
    'PGX_SESSION_MODIFY_MODEL',
    'PGX_SESSION_NEW_GRAPH',
    'PGX_SESSION_GET_PUBLISHED_GRAPH',
    'PGX_SESSION_COMPILE_ALGORITHM',
    'PGX_SESSION_ADD_PUBLISHED_GRAPH');
  FOR elem IN 1 .. graph_roles.count LOOP
  BEGIN
    dbms_output.put_line('create_graph_roles: ' || elem || ': CREATE ROLE ' || graph_roles(elem));
    EXECUTE IMMEDIATE 'CREATE ROLE ' || graph_roles(elem);
  EXCEPTION
    WHEN role_exists THEN
      dbms_output.put_line('create_graph_roles: role already exists. continue');
    WHEN OTHERS THEN
      RAISE;
    END;
  END LOOP;
EXCEPTION
  when others then
    dbms_output.put_line('create_graph_roles: hit error ');
    raise;
END;
/
```


### 3.2.7 Graph Server(PGX)の起動
```sql:PGX起動
[root@kix-graph-server ~]# systemctl start pgx
[root@kix-graph-server ~]# systemctl status pgx
● pgx.service - Oracle Graph In-Memory Server
   Loaded: loaded (/etc/systemd/system/pgx.service; disabled; vendor preset: disabled)
   Active: active (running) since 土 2021-01-23 11:14:20 GMT; 1s ago
 Main PID: 18792 (bash)
   CGroup: /system.slice/pgx.service
           ├─18792 /bin/bash start-server
           └─19196 java -Dlog4j.configurationFile=/etc/oracle/graph/log4j2-server.xml -Doracle.jdbc.fanEnabled=false -cp ...

 1月 23 11:14:20 kix-graph-server systemd[1]: Started Oracle Graph In-Memory Server.
 1月 23 11:14:20 kix-graph-server bash[18792]: tput: No value for $TERM and no -T specified
 1月 23 11:14:20 kix-graph-server bash[18792]: tput: No value for $TERM and no -T specified
 1月 23 11:14:20 kix-graph-server bash[18792]: tput: No value for $TERM and no -T specified
 1月 23 11:14:20 kix-graph-server bash[18792]: tput: No value for $TERM and no -T specified
[root@kix-graph-server ~]#
```

### 3.2.8 Graph Server接続用のトークンを取得
```sql:トークン取得
[pgx_user@kix-graph-server ~]$ curl -X POST -H 'Content-Type: application/json' -d '{"username": "db_pgx_user", "password": "パスワード"}' http://localhost:7007/auth/token
{"access_token":"eyJraWQiOiJ...6n8Q","token_type":"bearer","expires_in":14400}
```
### 3.2.9 Graph Serverへ接続
```sql:3.2.7で取得したトークンを使用する
[pgx_user@kix-graph-server ~]$ ls -lah /opt/oracle/graph/bin
total 20K
drwxr-xr-x.  2 root root          66 Jan 23 10:35 .
drwxr-xr-x. 10 root root        4.0K Jan 23 10:35 ..
-rwxr-xr-x.  1 root oraclegraph 2.3K Jan 22 18:07 opg
-rwxr-xr-x.  1 root oraclegraph 2.1K Jan 22 18:07 opg-groovy
-rwxr-xr-x.  1 root oraclegraph 2.3K Jan 22 18:07 opg-jshell
-rwxr-xr-x.  1 root oraclegraph 1.8K Jan 22 18:07 opgpy
[pgx_user@kix-graph-server ~]$ 
[pgx_user@kix-graph-server ~]$ cd /opt/oracle/graph/bin
[pgx_user@kix-graph-server bin]$ ./opg-jshell --base_url http://localhost:7007
Jan 23, 2021 11:30:49 AM java.util.prefs.FileSystemPreferences$1 run
INFO: Created user preferences directory.

enter authentication token (press Enter for no token):  --[先ほど取得したTokenを貼り付ける]
For an introduction type: /help intro
Oracle Graph Server Shell 21.1.0
Variables instance, session, and analyst ready to use.
opg-jshell> 
```

### 3.2.10 前述したリレーショナル表(外部表から表作成）を作っておく
```sql:外部表
GRANT DWROLE to DB_PGX_USER;
GRANT UNLIMITED TABLESPACE to DB_PGX_USER;

-- 外部表用のクレデンシャルを作成
BEGIN
DBMS_CLOUD.CREATE_CREDENTIAL(
credential_name =>'DB_PGX_USER_CRED',
username=>'fmshgj3@gmail.com',
password=>'t[ylRHM47]mr2(RrM#dC');
END; 

-- Node表
BEGIN
   DBMS_CLOUD.CREATE_EXTERNAL_TABLE(
   table_name =>'T_NODES',
   credential_name =>'DB_PGX_USER_CRED',
   file_uri_list =>'https://objectstorage.ap-tokyo-1.oraclecloud.com/n/nrp0revii2zt/b/bucket1/o/NODEC_elegans_node.csv',
   format => json_object('type' value 'csv','ignoremissingcolumns' value 'true', 'skipheaders' value '1'),
   column_list => 'id NUMBER, name VARCHAR2(40), posx NUMBER, posy NUMBER');
END;
/

-- Edge表
BEGIN
   DBMS_CLOUD.CREATE_EXTERNAL_TABLE(
   table_name =>'T_EDGES',
   credential_name =>'DB_PGX_USER_CRED',
   file_uri_list =>'https://objectstorage.ap-tokyo-1.oraclecloud.com/n/nrp0revii2zt/b/bucket1/o/EDGEC_elegans_reration.csv',
   format => json_object('type' value 'csv','ignoremissingcolumns' value 'true', 'skipheaders' value '1'),
   column_list => 'from_id NUMBER, to_id NUMBER');
END;

-- 今回のリレーショナル表を作成(Primary key追加)
CREATE TABLE TBL_EDGES AS SELECT * FROM T_NODES;
CREATE TABLE TBL_EDGES AS SELECT * FROM T_EDGES;
ALTER TABLE TBL_NODES ADD PRIMARY KEY(id);
ALTER TABLE T_EDGES ADD PRIMARY KEY(from_id);

-- Primary Keyを追加しない場合、下記が出るため
例外: oracle.pg.rdbms.pgql.PgqlToSqlException: 
Table STAGE.T_NODES does not exist or does not have a primary key, please provide one 
```

### 3.2.11 PGスキーマ(PG Schema)表を作成
```SQL:PGQLのDDL(Create_Property_Graph文)を用意しておく
[root@kix-graph-server graph]# cat create_pg.pgql
Create Property Graph Elegans_graph 
VERTEX TABLES (
  TBL_NODES LABEL NODE PROPERTIES (id, name, posx, posy)
)
EDGE TABLES (
  TBL_EDGES
   SOURCE KEY ( from_id ) REFERENCES TBL_NODES
   DESTINATION KEY ( to_id ) REFERENCES TBL_NODES
   LABEL CONNECTED NO PROPERTIES
)
```

```java:Create_PGする
opg-jshell> var username="DB_PGX_USER";
opg-jshell> var password="パスワード";
opg-jshell> var jdbc_url="jdbc:oracle:thin:@pgxatp_low?TNS_ADMIN=/etc/wallet";
opg-jshell> var conn = DriverManager.getConnection(jdbc_url, username, password);
opg-jshell> var pgql = PgqlConnection.getConnection(conn)
opg-jshell> conn.setAutoCommit(false)
opg-jshell> pgql.prepareStatement(Files.readString(Paths.get("/opt/oracle/graph/create_pg.pgql"))).execute()
opg-jshell> Consumer<String> query = q -> {
...>   try(var s = pgql.prepareStatement(q)){
...>     s.execute();
...>     s.getResultSet().print();
...>   }catch(Exception e){
...>     throw new RuntimeException(e);
...>   }
...> }
opg-jshell> query.accept("select count(n) from Elegans_graph match(n)");
+----------+
| count(n) |
+----------+
| 131      |
+----------+
opg-jshell> query.accept("select count(e) from Elegans_graph match()-[e]->()");
+----------+
| count(e) |
+----------+
| 764      |
+----------+
```

## 3.3 PGQLを投げて可視化+アルゴリズム試行
### 3.3.1 PGスキーマのGraphをPGXへロードする(RDBMS→GraphServer(PGX))
```java:PG_SCHEMA_to_PGX
opg-jshell> Supplier<GraphConfig> pgxConfig = () -> { return GraphConfigBuilder.forPropertyGraphRdbms()
...>   .setName("Elegans_graph")
...>   .addVertexProperty("ID", PropertyType.INTEGER)
...>   .addVertexProperty("NAME", PropertyType.STRING)
...>   .addVertexProperty("POSX", PropertyType.FLOAT)
...>   .addVertexProperty("POSY", PropertyType.FLOAT)
...>   .setPartitionWhileLoading(PartitionWhileLoading.BY_LABEL)
...>   .setLoadVertexLabels(true)
...>   .setLoadEdgeLabel(true)
...>   .build(); }
pgxConfig ==> $Lambda$840/0x000000010018c440@656c356c

// readGraphWithPropertiesでAPIでインメモリ側にGraphをもっていく
opg-jshell> var graph = session.readGraphWithProperties(pgxConfig.get())
graph ==> PgxGraph[name=Elegans_graph,N=131,E=764,created=1610848075827]
``` 

### 3.3.2 GraphVizへログイン
UserName/Password/SessionID(任意)を入力する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577151/0ea27693-b44c-5553-2c36-c0218ae65306.png)

```sql:PGQL投げる
SELECT node1,edge,node2
  MATCH (node1)-[edge]->(node2)
```

画面右上にてVisualのカスタムができる。
レイアウトを「ランダム」や「放射状」にすることも可能。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577151/4e875e94-bd18-bc99-2df9-b733b7730b9e.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577151/23be6b8f-aae1-f84f-3085-66676b90cf9d.png)
### 3.3.3 PGQLを変えて投げてみる
```sql:POSX,POSYごとのフィルタリングを施したグラフの可視化
SELECT node1,edge,node2
MATCH (node1)-[edge]->(node2)-[edge2]->(node3)
WHERE 0.0157 < (node1.POSX*node1.POSX + node1.POSY*node1.POSY)
```
↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577151/249b6e36-0f8f-ccf7-caa0-9d012d3a09f6.png)

```sql:2.3.2.1での結果を受けてRIAH周りを可視化
SELECT n1.NAME,e,n2.NAME
MATCH (n1)-[e]-(n2)
WHERE n1.NAME='RIAH'
```
↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577151/6c1cffa6-4469-ae73-75f6-aaec22fa3686.png)

※21.1においてGraphVizを使う場合、下記を実行すること(備忘録)

```md:(20.4の場合は必要ない)
# 1. /opt/oracle/graph/graphviz/のgraphviz-21.1.0-pgviz3.24.0-tomcat.warを解凍
# 2. web.xmlの下記を修正
- Session-Configの定義をFalseに修正
<session-config>
    <tracking-mode>COOKIE</tracking-mode>
    <cookie-config>
        <secure>false</secure>
- Contextの定義をhttp版に修正
<context-param>
        <param-name>pgx.base_url</param-name>
        <param-value>http://localhost:7007</param-value>
# 3.  /opt/oracle/graph/graphviz/のgraphviz-21.1.0-pgviz3.24.0-tomcat.warを固めてPGXを再起動
```

### 3.3.4 Analystの利用(ページランク)
アルゴリズムを１つだけ試す。ページランクは、ウェブページの重要度を決定するためのアルゴリズム。Googleの創設者のうちラリー・ペイジとセルゲイ・ブリンによって1998年に発明された([参考](https://en.wikipedia.org/wiki/PageRank))。

```java:analystの利用
opg-jshell> analyst.pagerank(graph)
$18 ==> VertexProperty[name=pagerank,type=double,graph=Elegans_graph]

graph.queryPgql("SELECT n.NAME, n.pagerank WHERE (n) ORDER BY n.pagerank DESC ").print(5) 
opg-jshell> graph.queryPgql("SELECT n.NAME, n.pagerank WHERE (n) ORDER BY n.pagerank DESC ").print(5) 
+-------------------------------+
| n.NAME | n.pagerank           |
+-------------------------------+
| RIH    | 0.04109789438551924  |
| CEPVL  | 0.02105463790978605  |
| OLLL   | 0.02087632956600805  |
| CEPDL  | 0.01996911832888276  |
| ADLL   | 0.018816382801831996 |
+-------------------------------+
$34 ==> PgqlResultSetImpl[graph=Elegans_graph,numResults=131]
```

## 3.4.1 AutoRefreshを試す
### 3.4.1 Autorefleshの設定
Autorefleshは、定期的かつ自動的にGraphをリフレッシュして、インメモリGraph(PGX側)とPGスキーマ表(DB側)の同期化を維持できる機能である。

```sql:AutoRefreshを有効にする
[root@graphserver ~]# systemctl stop pgx
[root@graphserver ~]# vi /etc/oracle/graph/pgx.conf
... 
"allow_user_auto_refresh": true  -- 1行追加する
...
[root@graphserver ~]# systemctl start pgx
```

```java:Autorefreshで用いるGraphを構成する
opg-jshell> Supplier<GraphConfig> pgxConfig = () -> { return GraphConfigBuilder.forPropertyGraphRdbms()
.setJdbcUrl("jdbc:oracle:thin:@pgxatp_low?TNS_ADMIN=/etc/wallet")
.setUsername("DB_PGX_USER")
.setPassword("WelCome##1234")
.setName("Elegans_graph")
.addVertexProperty("ID", PropertyType.INTEGER)
.addVertexProperty("NAME", PropertyType.STRING)
.addVertexProperty("POSX", PropertyType.FLOAT)
.addVertexProperty("POSY", PropertyType.FLOAT)
.setPartitionWhileLoading(PartitionWhileLoading.BY_LABEL)
.setLoadVertexLabels(true)
.setLoadEdgeLabel(true)
.setAutoRefresh(true)
.setFetchIntervalSec(60)
.setUpdateIntervalSec(1200)
.setCreateEdgeIdIndex(true)
.setCreateEdgeIdMapping(true)
.build();}
```

```java:C.elegansのGraph_on_RDBMSをC.elegansのGraph_on_PGXとして、ロードする
opg-jshell> var graph = session.readGraphWithProperties(pgxConfig.get());
graph ==> PgxGraph[name=Elegans_graph,N=131,E=764,created=1610879917577]
```

### 3.4.2 AutoRefleshの実施確認
```java:Snapshotを取得する
opg-jshell> session.getAvailableSnapshots(graph)
$13 ==> [GraphMetaData [numVertices=131, numEdges=764, memoryMb=0, dataSourceVersion=36983636967145, creationRequestTimestamp=1610879914656 (2021-01-17 10:38:34.656), creationTimestamp=1610879917577 (2021-01-17 10:38:37.577), vertexIdType=long, edgeIdType=long, directed=true]]
```

```sql:新規ノード追加
SQL> insert into ELEGANS_GRAPHVT$ (VID,VL,K,T,V,VN) values (1000,'NODE','ID',4,1000,1000); 
SQL> insert into ELEGANS_GRAPHVT$ (VID,VL,K,T,V) values (1000,'NODE','NAME',1,'ZZZZ');
SQL> commit; 
```

下記結果から、60秒( setFetchIntervalSec(60) )待機した後、Snapshotを取得するとノード数が131 -> 132に自動的に更新されたことが確認できる。

```java:再度Snapshotを取得する
opg-jshell> session.getAvailableSnapshots(graph)
$29 ==> [GraphMetaData [numVertices=132, numEdges=764, memoryMb=0, dataSourceVersion=36983646246803, creationRequestTimestamp=1610881121437 (2021-01-17 10:58:41.437), creationTimestamp=1610881122016 (2021-01-17 10:58:42.016), vertexIdType=long, edgeIdType=long, directed=true], GraphMetaData [numVertices=131, numEdges=764, memoryMb=0, dataSourceVersion=36983636967145, creationRequestTimestamp=1610879914656 (2021-01-17 10:38:34.656), creationTimestamp=1610879917577 (2021-01-17 10:38:37.577), vertexIdType=long, edgeIdType=long, directed=true]]
```

# 4. まとめ
- GraphデータベースのNeo4jとPGXに触れてみた
- それぞれGraphServerの構成は非常に似ているので、どちらか一方を経験すればセットアップにはそこまで時間がかからないように思う
- 今回はデータがとてもシンプルである(エッジに数値プロパティ等なかった)ため、複雑な分析はできなさそうだったが、今回一連の手順は纏められたのでもう少しリアリティのある、かつ量の多いデータを使って分析してみたいと思った

# 5. 参考URL
- [Cypher マニュアル](https://neo4j.com/docs/cypher-manual/current/functions/)
- [GDS インストール](https://graphaware.com/neo4j/2020/04/26/graph-datascience-neo4j-4.html)
- [コミュニティ検出 マニュアル](https://neo4j.com/docs/graph-algorithms/current/algorithms/community/)
- [louvain マニュアル](https://neo4j.com/docs/graph-data-science/current/algorithms/louvain/)
- [node2vec マニュアル](https://neo4j.com/docs/graph-data-science/current/algorithms/node2vec/)
- [C.elegansデータ](https://snap.stanford.edu/data/C-elegans-frontal.html)
- [外部表データ連携TIPS](https://qiita.com/ora_gonsuke777/items/7f790554a84c1aa16a71)
- [AutonomousでのPGX構築TIPS](https://qiita.com/kk31108424/items/6a18b8923a8ae7a744df)
- [Oracle labs PGX マニュアル](https://docs.oracle.com/cd/E56133_01/latest/index.html)
- [Oracle property-graph マニュアル](https://docs.oracle.com/en/database/oracle/property-graph/20.4/spgdg/property-graph-overview-spgdg.html#GUID-B817927C-916F-4101-A714-55FB42FD2C35)
- [PGX Analyst マニュアル](https://docs.oracle.com/cd/E56133_01/latest/javadocs/oracle/pgx/api/Analyst.html)
- [Autoreflesh マニュアル](https://docs.oracle.com/cd/E56133_01/19.1.0/tutorials/auto-refresh.html)