eaglesakuraの技術ブログ

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

ssh接続もシリアル接続もできなくなったGoogle Compute Engineで動くサーバーを復旧させる

何が起きたか

  • 社内システムの動いているサーバーをメンテしようとした
  • 突然GCEのいつものssh接続ができなくなった
  • 冷や汗をかきながらシリアル接続を有効化するが、ログインできず
  • 自分だけが締め出されたかと思って別ユーザーでも試すがログインできず

状況確認

  • 基本的なステータスチェック(権限、ネットワークとか)はOK
  • ssh接続しようとするとPermissin Deniedする
  • 起動スクリプトを介して公開鍵を登録したりユーザーパスワードをリセットしてもログインできない
  • 基本的なLinux力が足りていない

原因

  • slackのWebhookが使えなくなった(Let’s Encryptの証明書が更新された)関係でいろいろいじったので、心当たりがありすぎる
  • 証明書を再インストールしたあたり、バックアップとらずにそぉい!とやったのがまずかった気がする
  • サーバーのdaemon自体は普通に動き続けていたため、普通に使われている(今日まで気づかなかったし)
  • ここ最近のデータを失うのはツライ
  • データはどうにか救わねばならない

対応

  • このサーバーで動いていたのは社内用のRedmineで、なおかつbitnamiパッケージで導入したので詳しい構成はわからない
  • /opt 配下にいろいろインストールされており、なおかつ正常に動いていた2週間前のバックアップは存在する
  • なので、「正常に動いていた時代のバックアップ」と「最新のデータ」を合体させて起動させれば何とかなるんじゃないか?という対処を考える

やった手順

  1. 壊れたサーバーのSnapshotをとってディスクAを作成する
  2. 2週間前のバックアップからディスクBを作成する
  3. Compute Engineのインスタンス(復旧くん)を新規に作成して、ディスクA/ディスクBをマウント
  4. 復旧くんの中でディスクA/optをディスクB/optにrsync
  5. 復旧君からディスクBを外す
  6. 壊れたサーバーの起動ディスクをディスクBに切り替える
  7. 起動

反省

  • sshから締め出されてもシリアルがあるから大丈夫だべ、と思って油断すると両方から締め出される
  • sudoするまえにバックアップを取ろう
  • メンテしたらちゃんと再起動とsshのログインは確かめよう
  • 2023年最大の冷や汗を体験した

FlutterのIsolate.spawnはやはり重いという話

Windows Desktopビルドで重い処理を裏で行ったらやっぱり重い

  • 画像のデコード、変換、解析といった重い処理をバックグラウンドで行おうとした
  • Flutterのcompute()関数を使用してawaitすると、1秒以上かかる
    • 実際の解析処理は0.2秒程度
  • 流石に重いので計測すると、何も行わない空の関数でも1秒弱実行にかかった
  • UIがブロッキングすることは無くなったが、そもそも時間がかかりすぎる

WindowsHello Worldで計測

  • flutter createした直後で同じように空の関数をコールして計測
  • 500ms〜600ms程度かかる
  • 遅い

解決方法

  • 起動したIsolateをキャッシュするようにした
  • ほぼ解析時間だけになった
  • プログラムは複雑になった
  • Isolateの起動は遅い。

XCode 14にアップデートしてflutter buildが通らなくなった際の対処

原因

  • XCode 14からCocoaPodsの生成物(Pod)にも署名を要求するようだ
    • n=1なので、他の不要な場合もあるかもしれない
  • Issueがある

github.com

対応

  • cocoapods-pod-signをインストール
  • Flutterは /usr/bin/gem のPATHを優先的に検索するようなので、複数バージョンのgemをインストールしてる場合は標準gemでinstallするほうが面倒がなくて良い
gem install cocoapods-pod-sign
platform :ios, '15.0'
plugin 'cocoapods-pod-sign'

Flutter + Redux Architectureでパフォーマンスチューニング

パフォーマンスチューニングが必要になった理由

  • プロジェクトでRedux Architectureを採用したが、求める機能に合致するライブラリがないためチーム内で自作した pub.dev
  • これにより求める機能が実装されたが、プロジェクトでは特定条件下でState更新が1000回 / 秒を超える場合がある
  • UI = f(State)を愚直に実装していたため、Widget.build()が1000回 / 秒走ることとなり、パフォーマンスに明らかな悪影響が出た

解決方法

  • Stateを流すためのStreamと、WidgetをビルドするためのStreamを分けた
  • StateをハンドリングするためのStreamは取りこぼしがあってはならない(イベントハンドリングなど)ため、実装をそのままとした
  • それとは別に、WidgetをビルドするためのStreamを追加した
    • こちらは最大で60fpsでデータが流れる仕様となっているため、仮に1000回 / 秒State更新が走ってもWidgetに反映されるのは60回 / 秒が最大である
  /// レンダリング用に流通量を制限したStreamを生成する.
  BehaviorSubject<TState> _initializeRenderStream(Duration renderingInterval) {
    final result = BehaviorSubject<TState>.seeded(state);
    _subscription.add(
      Stream.periodic(
        renderingInterval,
        (computationCount) => state,
      ).distinct().listen(result.add),
    );
    return result;
  }
  • これによりWidget.build()のスパイクによる異常rebuildを回避してパフォーマンスが向上した

flutter build ipaのad-hocビルドが異様に遅い問題の対応

発生した問題

  • AppStore用ビルドは20分前後でビルドが完了する
  • 検証用のAd-Hocビルドが60分以上時間がかかる
  • XCode 13.2.1, Flutter 2.10.4

試したこと

  • Swift/Clangの最適化レベルを変更 -> 効果なし
  • dSYM出力無効化 -> 効果なし

突き止めた問題

  • --export-options-plist でAd-Hoc用ビルドにするとビルド時間が3倍に伸びる
  • 試しにビルド20分のAppStore用ビルドをAdHoc用のplistに切り替えるとビルド時間が同じくらいに遅くなった
  • ログを観察しても、ipa出力時に異様に時間がかかっているので間違いなさそう

plist仕様を確認

  • plist仕様を確認し、AdHoc用途で不要そうでdefault trueの項目をチェック
  • これらを明示的にfalseにしたところ、ビルド時間が20分まで早くなった
 <key>uploadBitcode</key>
    <false/>
    <key>compileBitcode</key>
    <false/>
  • compileBitcode が一番怪しいけど、そこまで切り分けはツラいのでこれでヨシとする
  • なんでこのオプションで異様に時間がかかるのか(appstoreビルドではtrueでも影響ないのか)はよくわからない

dartのFreezedとPImplイディオムでメンバ保護を行う

前提

Flutterの開発でReduxアーキテクチャを採用している。

Stateの管理に freezed を使うと、簡単な代わりに全てのメンバがpublicになってしまう。

一部のメンバはprivateにしたり、計算済みの値をキャッシュしたい。

改善

概念。

@freezed
class ExampleStateImpl with _$ExampleStateImpl {
  factory ExampleStateImpl({
    /// このデータはpublicにしたくない.
    /// dataListの合計計算は必要になる時まで計算を遅延したい
    required List<int> dataList,
  }) = _ExampleStateImpl;

  const ExampleStateImpl._();
}
class ExampleState {
   // Stateの実装を隠す
   final ExampleStateImpl _impl;

   factory ExampleStateImpl() {
       return ExampleStateImpl._(ExampleStateImpl(dataList: []));
   }

   ExampleStateImpl._(this._impl);

   final int? _dataListSumCache;
   
   int get dataListSum {
      // 合計値を計算して返す。
      // 計算済みのキャッシュがあればそっちを返す
   }

   ExampleState addData(int newValue) {
      return ExampleState(_impl.copyWith(
          // Freezedの機能を使ってデータコピーを返す
      ));
   }
}

実際はもっと複雑なパターンとなるが、Freezedのメンバーを直接見せたくないけどcopyWithの恩恵は欲しい場合に使う。

dartでKotlin.internal funっぽいことをする

dartのinternal fun

dart 2.15には)ない(たぶん)。

特定packageだけでアクセスしたいメソッドとか作れない。

作ったらみんなアクセスされてしまう。

解決方法

// src/example.dart
class Example {
  String _value = '';

  String get value => _value;
}

extension ExampleSetValue on Example {
  void setValue(String s) {
    _value = s;
  }
}
library example;

// exportするときに、Extensionを隠してやる
export 'src/example.dart' hide ExampleSetValue;

これで行儀良く使っている限りは外部のpackageはExample.setValue()をコールできない。

package内は 'src/example.dart' を直接importする。

無理矢理importすれば使えなくもないので、lintと併用で。