【mac】gethのデータを外付けSSDへ移行【ストレージ不足解消】

## はじめに
**mac**のストレージがカツカツになりました。
ストレージ不足のせいで**Xcode**のアップデートができない困った状況です…。
![ss01](https://user-images.githubusercontent.com/13220051/103729040-d81fb980-5022-11eb-9586-31649c7d65c8.png)
でも、私の環境における諸悪の根源はわかっています。
**Ethereum**の**DApps**作業用に使っている**geth**のデータディレクトリです。
![ss02](https://user-images.githubusercontent.com/13220051/103729041-d9e97d00-5022-11eb-9840-44af2579d39a.png)
**rinkeby**ディレクトリだけで**78GB**も喰っています…。
こいつをなんとかしてストレージ不足を解消することにしました。

で、試行錯誤の結果、**geth**のデータを外付け**SSD**へ移行できたので、その際の手順をメモしておきます。

## 環境
私の環境は下記となります。

**PC**：**MacBook Air**(**13-inch, Early 2014**)
**OS**：**Catalina**(**10.15.6**)
**HD**：**256GB**（フラッシュストレージ）

**geth**のバージョン：**1.9.9**

外付け**SSD**：**SanDisk Extreme** ポータブル **SSD**(**250GB**)
（*※ヨドバシで8800円でした*）

では、手順を見ていきましょう。

## 外付けSDDの形式確認／フォーマット
外付け**SSD**を**Windows**パソコンと共有できたら便利なので互換性のある**exFAT**にしたい所ですが、私の環境では**geth**の起動時に下記のエラーがでて同期できませんでした。

``` :exFATで発生したエラー
Fatal: Error starting protocol stack: listen unix /Volumes/ssd_drive/geth/rinkeby/geth.ipc: bind: invalid argument
```

ですので、外付け**SSD**のフォーマットは**APFS**にしました。
（*※すでに外付け**SSD**が**APFS**でフォーマットされている場合は、下記の手順.2(フォーマット)はスキップしてください*）
（*※途中まで試した感じだと[Mac OS拡張(ジャーナル)]でも動きそうな感じはしましたが、最後までは試していません*）

### 手順.1：ディスクユーティリティの起動
外付け**SSD**を**mac**に接続して[**Launcpad**]→[**その他**]→[**ディスクユーティリティ**]を起動します。

### 手順.2：外付けSSDのフォーマット
<font color="Red">**＊ご注意＊**
**以下の手順を試す際は、間違ってmacのHDをフォーマットしないように、対象のドライブが外付けのSSDになっているかをよく確認した上で行ってください**</font>

対象の外付け**SSD**を右クリックして[**消去...**]を選択します。
![ss03](https://user-images.githubusercontent.com/13220051/103729042-da821380-5022-11eb-83fe-b66fdb678102.png)
開いたダイアログにて、フォーマットを[**APFS**]に設定して、[**消去**]を実行します。
（※*APFSが表示されない場合／エラーがでる場合は後述の[補足：APFSの設定がうまくいかない場合]を参照ください*）
（※*セキュリティオプションで[最も安全]を選ぶと時間がかかるのでご注意ください＝試しにやったら日が暮れました…*）
![ss04](https://user-images.githubusercontent.com/13220051/103729044-dbb34080-5022-11eb-9b16-b2225b5d8170.png)
### 補足：APFSの設定がうまくいかない場合
**case.1**
**APFS**が選択できない場合は、一旦[**Mac OS拡張(ジャーナル)**]形式で消去した後、対象の**SSD**を右クリックしたメニューから[**APFSに変換...**]を選択します。
![ss06](https://user-images.githubusercontent.com/13220051/103729046-dc4bd700-5022-11eb-885c-78a8f22baa63.png)

**case.2**
**APFS**変換時に「**A GUID Partition Table (GPT) partitioning scheme is required.**」エラーがでる場合は[**diskutil**]にて初期化します。

まず、[ターミナル]を起動します。

続いて、下記のコマンドにて外付け**SSDの**ディスク番号を確認します。

``` :ディスク番号を確認する
　$ diskutil list
```

**＊実行例＊**
> [MacBook-Air:~ hakumai$ diskutil list
> /dev/disk0 (internal, physical):
>    #:                       TYPE NAME                    > SIZE       IDENTIFIER
>    0:      GUID_partition_scheme                        *251.0 GB   disk0
>    1:                        EFI EFI                     > 209.7 MB   disk0s1
>    2:                 Apple_APFS Container disk1         250.8 GB   disk0s2
> 
> /dev/disk1 (synthesized):
>    #:                       TYPE NAME                    > SIZE       IDENTIFIER
>    0:      APFS Container Scheme -                      +250.8 GB   disk1
>                                  Physical Store disk0s2
>    1:                APFS Volume Macintosh HD - Data     221.2 GB   disk1s1
>    2:                APFS Volume Preboot                 82.3 MB    disk1s2
>    3:                APFS Volume Recovery                528.8 MB   disk1s3
>    4:                APFS Volume VM                      1.1 GB     disk1s4
>    5:                APFS Volume Macintosh HD            11.1 GB    disk1s5
> 
> /dev/disk2 (external, physical):
>    #:                       TYPE NAME                    > SIZE       IDENTIFIER
>    0:      GUID_partition_scheme                        *250.1 GB   disk2
>    1:                        EFI EFI                     209.7 MB   disk2s1
>    2:       Microsoft Basic Data 名称未設定              249.8 GB   disk2s2

↑ 私の環境では、外付け**SSD**が[**disk2**]として認識されていました。


ディスク番号がわかったら、下記のコマンドにて外付け**SSD**をフォーマットします。

``` :対象のディスクをAPFSでフォーマットする
　$ diskutil eraseDisk APFS [設定するドライブ名] [対象のディスク番号]
```

**＊実行例＊**
> [MacBook-Air:~ hakumai$ diskutil eraseDisk APFS ssd_drive disk2
> Started erase on disk2
> Unmounting disk
> Creating the partition map
> Waiting for partitions to activate
> Formatting disk2s2 as APFS with name ssd_drive
> Mounting disk
> Finished erase on disk2

↑ドライブ名を[**ssd_drive**]、ディスク番号を[**disk2**]でフォーマットした例です。

**case.3**
[**diskutil**]の[**APFS**]指定で失敗する場合は、オプション[**JHFS+**]（**Mac OS**拡張）で初期化し、その後[**case.1**]の手順をお試しください。

``` :対象のディスクをJHFS+でフォーマットする
　$ diskutil eraseDisk JHFS+ [設定するドライブ名] [対象のディスク番号]
```

### 手順.3：ドライブ名の設定
ドライブ名をクリックしてお好きな名前をつけましょう。
ここでは[**ssd_drive**]としました。
![ss05](https://user-images.githubusercontent.com/13220051/103729045-dbb34080-5022-11eb-8a36-121de3ea2c8c.png)
**geth**の同期時に、このドライブ名でパスを指定することになります。
例えば上記例であれば、パスは[**/Volumes/ssd_drive**]となります。

## gethで同期
外付け**SSD**に適当なディレクトリを作成して**geth**の同期を開始します。
（※*ここでは接続先をRinkebyテストネットとしています*）
（※*外付けSSDに作成した[geth/rinkeby]ディレクトリを保存先にしたいので、パスとして[/Volumes/ssd_drive/geth/rinkeby]を指定しています*）

``` :gethで同期
　$ geth --rinkeby --datadir /Volumes/ssd_drive/geth/rinkeby
```

↓同期中、**retry**が出まくったのでかなりドキドキしました（*※外付け**SSD**への書き込みが遅いせいかもしれません…*）。

> count=1920 elapsed=10.325ms  processed=163021371 pending=6704   retry=0    duplicate=18423 unexpected=60488
> INFO [01-06|08:37:07.173] Imported new state entries               count=1920 elapsed=12.420ms  processed=163023291 pending=5893   retry=0    duplicate=18423 unexpected=60488
> INFO [01-06|08:37:07.927] Imported new state entries               count=1536 elapsed=8.910ms   processed=163024827 pending=5925   retry=0    duplicate=18423 unexpected=60488
> INFO [01-06|08:37:08.735] Imported new state entries               count=1536 elapsed=8.045ms   processed=163026363 pending=6121   retry=0    duplicate=18423 unexpected=60488
> INFO [01-06|08:37:09.517] Imported new state entries               count=1536 elapsed=9.212ms   processed=163027899 pending=5965   retry=0    duplicate=18423 unexpected=60488
> INFO [01-06|08:37:10.249] Imported new state entries               count=1920 elapsed=11.310ms  processed=163029819 pending=5222   retry=0    duplicate=18423 unexpected=60488
> INFO [01-06|08:37:10.916] Imported new state entries               count=1536 elapsed=9.066ms   processed=163031355 pending=4988   retry=0    duplicate=18423 unexpected=60488

ですが、ひたすら放置しておいたら、同期が完了してくれました。
↓下記のログが出力されたら同期の完了です（※*gethを終了するには[Ctrl+C]を入力します*）。
> INFO [01-06|08:59:34.821] Fast sync complete, auto disabling 

同期にかかった時間は２４時間弱、ほぼ丸一日でした。

感覚的には**mac**の**HD**(フラッシュストレージ)を対象にした時に比べて、２〜３倍遅い印象です。
**mac**側のコネクタが**Type-A**なので、**Type-C**の利用できる**mac**ならもっと速いのではないかと思います。

## truffleとの連携確認：読み込み
同期したデータディレクトリを対象に**geth**を**RPC**モードで起動します。

``` :gethをPRCモードで起動
$ geth --allow-insecure-unlock --rpc --rpcapi personal,web3,eth,net --verbosity 0 console --rinkeby --datadir /Volumes/ssd_drive/geth/rinkeby
```

次に、ターミナルの別タブから**Truffle**を起動します
（*※rinkebyのネットワーク設定がされているプロジェクトフォルダへ移動してから起動してください*）

``` :Truffleの起動 
$ truffle console --network rinkeby
```
テストアカウントの残高を確認します（*※残高表示が０だったり実際の値と違う場合は同期が完了していないので再同期してください*）。

``` :残高確認
truffle(rinkeby)> web3.eth.getBalance( accounts[0] )
'474167714324901098982'
```

**MetaMask**等で残高を増減した後、再度残高を確認します（※*ここでは[0.16ETH]減らしてみました*）。

``` :再度残高確認
truffle(rinkeby)> web3.eth.getBalance( accounts[0] )
'474005299324901098982'
```

ちゃんと同期がとれています。

## [truffle]との連携確認：デプロイ
スマートコントラクトをデプロイしてみます。
（*※プロジェクトのビルドが通り、migrateの設定に問題がない状況でデプロイしてください*）

``` :コントラクトのデプロイ
[truffle(rinkeby)> migrate --f 2

Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.

Migrations dry-run (simulation)
===============================
> Network name:    'rinkeby-fork'
> Network id:      4
> Block gas limit: 10000000 (0x989680)

2_deploy_contracts.js
=====================

   Replacing 'HelloWorld'
   ----------------------
   > block number:        7844413
   > block timestamp:     1609893660
   > account:             0xebfCB28c530a9aAD2e5819d873EB5Cc7b215d1E1
   > balance:             474.003238390901098982
   > gas used:            1030467 (0xfb943)
   > gas price:           2 gwei
   > value sent:          0 ETH
   > total cost:          0.002060934 ETH

   -------------------------------------
   > Total cost:         0.002060934 ETH

Summary
=======
> Total deployments:   1
> Final cost:          0.002060934 ETH

Starting migrations...
======================
> Network name:    'rinkeby'
> Network id:      4
> Block gas limit: 10000000 (0x989680)

2_deploy_contracts.js
=====================

   Replacing 'HelloWorld'
   ----------------------
   > transaction hash:    0xdf4bb76e2ca3913cd637cd7ceaeec0aafc409743493cf3fbb10c85249c7f2ec7
   > Blocks: 1            Seconds: 4
   > contract address:    0xaD1c3A4E6E19955C4a3cdB993843d7aC78F7294c
   > block number:        7844412
   > block timestamp:     1609893664
   > account:             0xebfCB28c530a9aAD2e5819d873EB5Cc7b215d1E1
   > balance:             473.984389984901098982
   > gas used:            1045467 (0xff3db)
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.02090934 ETH

   > Saving artifacts
   -------------------------------------
   > Total cost:          0.02090934 ETH

Summary
=======
> Total deployments:   1
> Final cost:          0.02090934 ETH
```

デプロイも問題なく完了しました。
外部クライアントから適当にアクセスし、その結果も**Ethescan**から確認できました。

[https://rinkeby.etherscan.io/address/0xaD1c3A4E6E19955C4a3cdB993843d7aC78F7294c](https://rinkeby.etherscan.io/address/0xaD1c3A4E6E19955C4a3cdB993843d7aC78F7294c)

どうやら問題なく動いているようです。
結果として、**mac**側のストレージから[**geth**]のデータディレクトリを削除することができました。
![ss07](https://user-images.githubusercontent.com/13220051/103729047-dce46d80-5022-11eb-88e1-bb7671a73fff.png)
空き容量も**100GB**を回復して、**Xcode**のアップデートも無事完了しました。

めでたしめでたし。

## まとめ
**geth**の同期先を外付け**SSD**を対象にした場合、**mac**の**HD**と比べて同期に時間がかかるので、ストレージをとるか速さをとるかで検討しましょう。
たまに**solidity**をいじってデプロイする程度の**geth**利用であれば、データを外付け**SSD**へ移行するのは十分ありだと思います。