eaglesakuraの技術ブログ

技術的な話題とか、メモとか。

APIサーバーの実行環境の所感 2020

Google App Engine

  • 言語/ランタイムバージョンが限定される
  • 複数バージョンをデプロイして、複数の指定バージョンを簡単に試せる
  • staticファイルの配信が簡単
  • Go1.9時代(第1世代GAE)はいつも使っていた
  • 今はstaticファイルの配信に使う

Google Cloud Run

  • Runtimeが固定されない
  • デプロイが簡単、Dockerfileがそのまま使えて嬉しい
  • インスタンス起動後、10〜20分くらい生きている。その後死ぬ。

Google Cloud Run / Go

  • Go 1.13以降で主に使う
  • スピンアップが早い。コールドスタート早い。ちょっぱや。
  • Golangのサーバーサイド開発者自体を捕獲する難易度が多少高い
  • ライブラリが豊富

Google Cloud Run / Kotlin

  • Kotlinしゅき。
  • 今日試した
  • Ktor使ってスピンアップ2.3秒、以後8ms程度が最速値(Hello Worldのテキストを返すだけ)
  • まあ許せなくもない気もする
  • Cloud Schedulerを用いて1分に1回pingすることで0円インスタンスを1個キープできそう

Firebase Hosting

  • マイクロサービスアーキテクチャで作ったときにCloud Runのサービスルーティングに使う
  • staticファイルの配信としては個人的には使いにくい

Windows環境のRobolectricでUri.toFile() KTXが正常に動作しない

問題点

  • Windowsファイルシステムはドライブレター(C:とか)やパスセパレータ(¥)がLinux/Macと異なる
  • Uri.fromFile()を実行したとき、ドライブレターやバッククォート(日本語だと円マーク)を含んだ文字列がURLエンコードされる

KTXUri.toFile()を行うとどうなるか

  • toFile()は単純に Uri.path 値をFileにラップして返すだけである
  • Windows環境でRobolectricを使用し、Uri.fromFile().pathを取得するとnullになる
  • 結果、File(null)が渡されることとなり、 UnitTestを実行しているカレントディレクトリ と解釈され、ほとんどの場合はその後のテストが落ちる

どうしたか?

  • Uri.path をチェックし、問題があるようならURLデコードを行ってからFileにラップするような拡張関数を書いてやる

こんなの

internal const val SCHEME_FILE = "file://"

fun Uri.toFileCompat(): File {
    val path = this.path ?: ""
    return if (path.isEmpty()) {
        // Windows path.
        val path = toString().substring(SCHEME_FILE.length)
        File(URLDecoder.decode(path, "UTF-8"))
    } else {
        toFile()
    }
}

楽観的な話

  • そのうち公式も直すんじゃねかな?

WindowsのNTFSはtree()が遅い

起きた事象

  • WindowsAndroid StudioのGradle Sync(像のアイコン押すと走るやつ)が異様に遅い
  • 普通に gradlew 実行だけでも遅い
  • MacUbuntuだと早い
  • 具体的にはこのくらい
    • Windows: gradlew 40秒, WSL2も同じくらい
      • NVMe PCIe 4.0接続しているので、ストレージ自体の接続速度に問題は無いと思われる
    • Mac: gradlew 5秒
    • Ubuntu: gradlew 3秒

遅かった理由

  • CIのキャッシュキーにするため、特定ファイル名のハッシュ値を計算していた
  • ハッシュ計算を(やり忘れないように)gradleのビルドスクリプトの中で(常に)起動していた
  • ファイル一覧を列挙するとき、(実装を面倒くさがって)fileTree()によるリポジトリの全探索を行った
  • 結果として、NTFSの上でファイルツリーの単純全検索が走ることとなった
  • 特に build/ 配下の検索が走ってしまったのが痛かったと思われる

改善

  • ハッシュ計算処理を改善したところ圧倒的改善を行えた

反省点

  • 開発環境にWindowsを使うならファイル全検索はやめよう
  • Linuxファイルシステムは早いので気づかないかもしれない
    • それでも全検索はモノグサしすぎだったかもしれない

結論

Protocol BuffersのAndroid向け実装としてSquare Wireを採用した所感

Wire

square.github.io

Protocol Buffersを採用した理由

  • 大量のデータを扱う処理で、画面外の情報を一時的にファイルやDBに退避したい
    • ついでにIntentで渡したりするのも簡単にやりたい
  • モデルの定義を簡素化したい
  • 銀の弾丸を求めて色々検証した結果、Protocol Buffers+Wireプラグインに落ち着いた

Protocol Buffers公式プラグイン(javaliteとか)の採用を見送った理由

  • AndroidのBundleやIntentにモデルを直接putできない
    • 自分でByteArrayにエンコード/デコードしなければならない
    • やってやれないことはないけどそこまで手間をかけたくない
  • javalite実装が3.0.0(2016年頃)で止まっている
    • Firestoreとかでまだ使われているから、何かあればメンテするとは思われるが。

Wireプラグインを採用した理由

  • Proto3に対応している
  • Android Parcelizeをimplしたモデルを出力してくれる
    • 実装を見ると、ByteArrayにエンコード(Protocol Buffers形式として)してputしている
    • kotlin-parcelizeを使うことで簡単にIntentに乗せられる
  • コミット履歴からみて、アクティブにメンテされている

Wireプラグインの問題

  • Moshi Adapterを提供してる(JSONシリアライズもできる)ように見えるが、必要なClassが出力されずに例外を投げた

追記: Wire プラグインの不具合

AndroidアプリプロジェクトのJDK11対応

AndroidJDK

  • Android Studioに付属しているJDKはJava8相当
  • なので、公式にはJava8でビルドするのが良いと思われる

なぜJava11対応が必要になったのか

  • CircleCIが用意しているAndroidビルド用Docker Imageが突然Java11になった
  • ああもうしゃーないなぁとか思いながらも、変更することにした
    • RobolectricがAPI29以降Java9以上必須となっていたので、まあいい機会だと

JDKインストール

  • 好きな方法でインストールする
  • 以下、Windows環境
  • sdkman でinstallし、currentを切り替えた
  • JAVA_HOMEは変更なし

Android Studioの設定変更

  • Project Structureから使用するJDKを切り替える
  • デフォルトはAndroid Studio付属になっている
  • build.gradleの構成?によってはProject Structureが開かない不具合があるので、その場合はDefault Project Structureで切り替え、.ideaディレクトリを削除して再度プロジェクトファイルを生成させれば良い

_JAVA_OPTIONSの設定

  • 初めて知ったが、 JAVA_OPTIONS_JAVA_OPTIONS は明示的に異なり、通常ユーザー向けのRuntimeは前者を、開発者向けのJDKは後者をロードする
  • _JAVA_OPTIONSに -Djavax.net.ssl.trustStoreType=JKS を追加する

robolectricの設定

  • sdk=28 とかで固定を入れている場合、sdk=29 に変更して実行環境を調整

CircleCI設定

  • circleci/android:api-30 とか切り替えちゃっていいんじゃないかな

dokka設定

  • ドキュメントをdokka出力しているなら、バージョンを変更する必要がある
  • JDKのClass名が変わったりとかしたので
// /build.gradle
classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.4.0-rc"
dokkaHtml {
    outputFormat = 'javadoc'
    outputDirectory = "$buildDir/dokka"
    noStdlibLink = true
    dokkaSourceSets {
        noAndroidSdkLink = true
        configureEach {
            noStdlibLink = true
            noJdkLink = true
            noAndroidSdkLink = true
        }
    }
}
}

メイン開発PCのパーツ交換をした

メイン開発マシンのスペックアップのため、一部のパーツを入れ替えたので今後のためにメモ。

メイン開発マシンのスペック(2017~)

基本スペック

表OS / Windows 10 / ストレージ512GB割当

  • Android(Kotlin) / Flutter / Go Serverあたりの開発
  • Adobe製品
  • Steamとかのゲーム(リモートプレイの母艦)

裏OS / Ubuntu 20.04 / ストレージ512GB割当

  • Android(Kotlin) / Flutter / Go Serverあたりの開発に使用
  • 特にAndroid Studioで顕著だが、Windowsでの動作が緩慢なため切り替える場合が多い

不満だった点

アップグレード計画

  • シングルスレッド性能の向上と、スペックダウンしないという要件を満たすため、Ryzen 3950X以上のスペックが必要
  • 第3世代Ryzen TRは非常に高額になっている

乗り換え先選定

  • 2017年購入時と同じく、サイコム の水冷BTOが第一候補
  • 予算的にはまあギリいけるか、的な範囲
  • 筐体がかっこ悪くなっている
    • 追加購入して、デスクトップ2台分の接地面積を取ることも難しい
    • 中古で売るにしても面倒だけが多い
  • 筐体を変えない(Fractal DesignDefine R5)ため、パーツ交換を行うことにする

交換内容

  • Ryzen 3950Xを導入することにした
    • TRを使うと、「長く使わないともったいない」ってなってしまう
    • Ryzen進化速度早いので、まあ2~3年もしたらRyzen9系に追い抜かれるだろう
    • シングルスレッド性能も3950Xと3960Xはほとんど変わらない
  • ソケットが異なるため、自動的にマザーボードも交換することになる
  • 元々の水冷クーラーがAM4非対応(オプション別売り、終売)のため、水冷クーラーも交換することになる
  • 最大メモリスロットが8スロット -> 4スロットに半減するため、メモリも交換することになる
  • PCIe 4が使えることと、Windowsの動作速度向上のためストレージも買い換えることになる
  • 後々グラボを載せ替えるのも面倒なので、グラフィックボードも交換しておく
    • Lightroomのプレビューやスクロールの遅さも気になってたしね
    • せっかくならレイトレーシング対応がいいね
    • せっかくならゲームももう少し品質向上できるやつがいいね
  • という理由でドミノ倒しのように殆どの機材が入れ替えとなる

嘆き

  • タイムセールで買っておきゃよかったぁ!!!!

最終的な構成変更

  • Ryzen 1950X -> Ryzen 3950X
  • 水冷クーラー Corsairの昔の -> Corsair h100i Pro RGB
  • メモリ 2400MHz 8GB×8 -> 3600MHz 16×4
  • マザーボード X399 Taichi -> X570 Taichi
  • ストレージ NVMe 512GB×2(PCIe 3.0) -> NVMe 512×2(PCIe 4.0)
  • グラボ GTX 1060 -> RTX 2080 Super

交換後の感触

  • ゲームのフレームレート向上した!!
    • BIOHAZARD RE2のオプションもりもりがだいぶできるようになってきた
    • ライザのアトリエ 4Kで起動できるようになった
    • オプションもりもりでもFF15がそこそこ動くようになった
  • Windows10 2004がインストールできるようになった
    • Ryzen 1950X+X399Taichiにはインストール不可能だった
    • WSL2サポートされた
    • 超快適
  • Cinebenchスコアが6000 -> 9000に向上
  • Androidプロジェクトはまだ触ってない。快適だと嬉しい。

Mac版IntellijでGradle Syncが出来なくなった場合の対処

発生した問題

  • 最近購入したMac Book Pro 13inch / 2020 / RAM32GBでIntellijをインストール
  • gradle syncが行えない
    • 必ず失敗し、ログも表示されない
  • IntelliJ自体のログを見ると、 Java home is different と言われてDaemon接続に失敗しているらしい

解決

  • Androidアプリビルドのために、 Java Zulu 1.8.0_252 をsdkmanを通して利用している
  • 最近のバージョンから、JAVA_HOMEが変更になった
    • 以前はインストール直下がJAVA_HOMEとして機能した
    • 新しいバージョンでは、インストール直下に zulu-8.jdk というディレクトリが使われて、そっちがJAVA_HOMEとして機能している
  • 間違ったJAVA_HOMEを指定していたので、正常にgradle syncできなかった
    • zulu-8.jdkIntellijに読ませることで解決

まじかよ

  • 何じゃこりゃ
  • 半日潰してしまった