たろすの技術メモ

Jot Down the Tech

ソフトウェアエンジニアのメモ書き

Sidekiqを導入する方法

概要

ActionMailerでdeliver_laterするため、以下を参考にしながらSidekiq(とRedis)を導入したのでその備忘録です。

prabinpoudel.com.np

gemを追加

gem 'sidekiq'
gem 'redis'

$ bundle installします。

Sidekiqを有効化

# config/application.rb
config.active_job.queue_adapter = :sidekiq

Web UI to Monitor Jobsを有効化

Sidekiq::Webconfig/routes.rbにマウントします。これにより、SidekiqのダッシュボードをWebブラウザから閲覧できるようになります。

# config/routes.rb

require 'sidekiq/web'

Myapp::Application.routes.draw do
  # mount Sidekiq::Web in your Rails app
  mount Sidekiq::Web => "/sidekiq"
end
  • mount:これはRailsのルーティングのメソッドで、特定のパスにRackベースのアプリケーションをマウントするために使用されます。RackはRubyでWebサーバーとアプリケーションを接続するためのインターフェースで、RailsSinatraもRackベースのフレームワークです。
  • Sidekiq::Web:これはSidekiqが提供するSinatraベースのWebアプリケーションで、Sidekiqのダッシュボードを提供します。このダッシュボードを通じて、現在のジョブの状態、ジョブの履歴、統計情報などを閲覧することができます。

設定ファイルを作成

config/initializers/sidekiq.rb

redis_config = YAML.load_file('config/redis.yml')[Rails.env]
redis_config['db'] = redis_config['db']['sidekiq']

Sidekiq.configure_server do |config|
  config.redis = {
    url: "redis://#{redis_config['host']}/#{redis_config['db']}"
  }
end

Sidekiq.configure_client do |config|
  config.redis = {
    url: "redis://#{redis_config['host']}/#{redis_config['db']}"
  }
end

このコードは、Rubyのジョブキュー処理ライブラリであるSidekiqの初期化設定を行うものです。

まず、最初の3行では、config/redis.ymlから現在の環境(Rails.env)に対応するRedisの設定をロードしています。その中で特にSidekiqが使用するデータベース(db)を指定します。

redis_config = YAML.load_file('config/redis.yml')[Rails.env]
redis_config['db'] = redis_config['db']['sidekiq']

次に、Sidekiqがサーバーモードで起動した時の設定を行います。configure_serverメソッドを用いて、Sidekiqがジョブを処理するために接続するRedisサーバーの設定を行います。ここで指定された設定はサーバーモードでのみ有効です。

Sidekiq.configure_server do |config|
  config.redis = {
    url: "redis://#{redis_config['host']}/#{redis_config['db']}"
  }
end

最後に、Sidekiqがクライアントモードで起動した時の設定を行います。configure_clientメソッドを用いて、Sidekiqがジョブをキューにプッシュするために接続するRedisサーバーの設定を行います。ここで指定された設定はクライアントモードでのみ有効です。

Sidekiq.configure_client do |config|
  config.redis = {
    url: "redis://#{redis_config['host']}/#{redis_config['db']}"
  }
end

これらの設定により、Sidekiqは指定されたRedisサーバーを用いてジョブのキューイングと処理を行うようになります。

config/redis.yml

default: &default
  db:
    sidekiq: 0

development:
  <<: *default
  host: redis

test:
  <<: *default
  host: redis

staging:
  <<: *default
  host: localhost

このコードは、アプリケーションのRedis設定を行うYAMLファイルです。

まず、defaultセクションでは、デフォルトの設定を定義しています。ここでは、異なるデータタイプごとにRedisデータベースを分けています。Sidekiqはデータベース0を使用します。これにより、それぞれのデータタイプが他のデータに影響を及ぼすことなく、同じRedisインスタンスを利用することができます。

default: &default
  db:
    sidekiq: 0

次に、各環境(開発、テスト、ステージング)の設定を定義しています。ここでは<<: *defaultを使ってデフォルトの設定を継承し、それぞれの環境でRedisサーバーのホスト名を指定しています。開発環境とテスト環境ではredisを、ステージング環境ではlocalhostをホスト名として使用します。

development:
  <<: *default
  host: redis

test:
  <<: *default
  host: redis

staging:
  <<: *default
  host: localhost

これにより、各環境ごとにRedisの接続設定を独立させることができます。これは、例えば開発環境と本番環境で異なるRedisサーバーを使用したいといったケースで有用です。

config/sidekiq.yml

:verbose: false
:pidfile: ./tmp/pids/sidekiq.pid
:logfile: ./log/sidekiq.log
:concurrency: 10
:queues:
  - default
  - test

このコードは、Sidekiqの設定ファイル config/sidekiq.yml の一部です。SidekiqはRubyのバックグラウンドジョブ処理ライブラリで、非同期にタスクを実行するために使用されます。

  • :verbose: false : Sidekiqがログに詳細な情報を出力するかどうかを制御します。ここでは、冗長な(verbose)出力をオフにしています。
  • :pidfile: ./tmp/pids/sidekiq.pid : SidekiqのプロセスIDを保存するためのファイルのパスを指定します。これはサーバーが起動している間、そのプロセスIDを保持します。
  • :logfile: ./log/sidekiq.log : Sidekiqのログが出力されるファイルのパスを指定します。
  • :concurrency: 10 : 同時に実行可能なジョブの最大数を指定します。この場合、10個のジョブを同時に処理できます。
  • :queues: : このセクションでは、Sidekiqがジョブを検索するキューの名前を指定します。Sidekiqは上から順にキューをチェックし、ジョブが存在すればそのジョブを実行します。ここでは 'default' と 'test' という2つのキューが設定されています。

コラム:concurrencyは何を指定するのが妥当か?

Sidekiqの concurrency 設定は、同時に処理されるジョブの数を指定します。適切な値は、アプリケーションの具体的な要件と、ホストシステムのリソース(CPU、メモリ、I/Oなど)に大きく依存します。

一般的なガイドラインとして、以下のような考え方があります:

  1. CPUの数とスレッドの数: CPUコアの数と同じ、またはそれより少し多い数の並行性が良いとされています。ただし、あまりに多くのスレッドを作成すると、システムのオーバーヘッドが大きくなり、パフォーマンスが低下する可能性があります。
  2. I/O待ちの時間: I/O待ちが多い(データベースへのクエリ、ネットワークリクエストなど)ジョブの場合、より高い並行性が有効になることがあります。これは、一部のジョブがI/Oによってブロックされている間に、他のジョブがCPUを使用できるからです。
  3. メモリ: 各スレッドはメモリを消費します。したがって、メモリ不足を引き起こさないためには、使用可能なメモリに基づいて並行性を設定する必要があります。

具体的な値を決めるには、異なる concurrency の値で実際にテストを行い、パフォーマンスを評価するのが最善の方法です。

Dockerを使用している場合

# == ここから ==
redis:
  image: redis:latest
  ports:
    - "6379:6379"
  volumes:
    - "./tmp/cache/redis:/data"
    - "./db/redis.conf:/etc/redis.conf"
# == ここまで ==

# 省略
app: &app
  tty: true
  stdin_open: true
  build: .
  command: bin/dev
  volumes:
    - .:/opt/app
    - app-bundle:/usr/local/bundle
  ports:
    - "3000:3000"
    - "3030:3030"
    - "3035:3035"
  depends_on:
    - db
    - redis
    - mail
  environment:
    REDIS_URL: redis://redis:6379 # 追加

# == ここから ==
sidekiq:
  <<: *app
  command: bundle exec sidekiq
  ports: []
  depends_on:
    - db
    - redis
# == ここまで追加 ==

Can't load Kernel binary: Invalid kernel binary format version.を解消する

stackoverflow.com

ある時から$ flutter ~ コマンドを実行するとこのログが出るようになったので原因と解決方法を調べました。

開発環境

  • 13.4.1(22F82)
  • Intel Iris Plus Graphics 1536 MB
$ fvm flutter --version
Flutter 3.0.4 • channel unknown • unknown source
Framework • revision 85684f9300 (12 months ago) • 2022-06-30 13:22:47 -0700
Engine • revision 6ba2af10bb
ToolsDart 2.17.5DevTools 2.12.2

$ flutter pub cache repairで直りました。

$ flutter pub cache repairとは

You can perform a clean reinstallation of all packages in your system cache:

$ dart pub cache repair

This command can be useful when packages in your system cache are somehow changed or broken.

For example, some editors make it easy to find implementation files for packages in the system cache, and you might accidentally edit one of those files.

日本語訳

システムキャッシュにあるすべてのパッケージのクリーンな再インストールを実行できます:

このコマンドは、システムキャッシュにあるパッケージが何らかの理由で変更され たり壊れたりした場合に便利です。

例えば、エディタによってはシステムキャッシュにあるパッケージの実装ファ イルを簡単に見つけることができますが、そのようなファイルを誤って編集してしまうかもしれません。

dart.dev

ActionMailerのメソッドへ変数を渡す2つの方法

概要

ActionMailerのメソッドで変数を受け取る方法は2つあります。一つはHogeMailer.with(arg:).fugaの形式、もう一つはHogeMailer.fuga(arg)の形式です。どんな違いがあるのか調べてみました。

前提

両者の違い

HogeMailer.with(arg:).fugaの形式では「Mailerインスタンス全体で」参照でき、HogeMailer.fuga(arg)の形式では「呼び出したメソッド内でのみ」参照できるという違いがあります。

HogeMailer.with(arg:).fugaの形式

この形式では以下のようにメソッドを定義し、変数を受け取ります。

class HogeMailer < ApplicationMailer
  def fuga
    @arg = params[:arg]
    # do somethings..
  end
end

メソッド内でparams[:変数名]の形式でアクセスできます。また、fuga関数を呼び出した後に同じHogeMailerインスタンスで他のメソッドを呼び出してもparams[:変数名]の形式でアクセスできます。

【例】

# mailers/hoge_mailer.rb

class HogeMailer < ApplicationMailer
  def fuga
    @arg = params[:arg]
  end

  def piyo
    @arg = params[:arg]
  end
end
# controllers/hello_controller.rb

class HelloController < ApplicationController
  def index
    mailer = HogeMailer.with(arg: "arg")
    mailer.fuga.deliver_now
    mailer.piyo.deliver_now
  end
end

HogeMailer.fuga(arg)の形式

この形式では以下のようにメソッドを定義し、変数を受け取ります。

class HogeMailer < ApplicationMailer
  def fuga(arg)
    @arg = arg
    # do somethings..
  end
end

fugaメソッド内でのみアクセスできます。公開範囲を絞りたいときはこちらを使うと良いでしょう。

letter_openerを導入する

gemを追加

group :development do
  # 略
  gem 'letter_opener'
  gem 'letter_opener_web'
  # 略
end

インストール

$ bundle install

ルーティングを設定

# config/routes.rb
mount LetterOpenerWeb::Engine, at: '/letter_opener' if Rails.env.development?

Letter Opener Webのエンジン(エンジンとは、Railsアプリケーションと同様に動作するRailsコンポーネントで、その中に自身のMVCコンポーネントやルーティングを持つことができます)を、/letter_openerというパスでマウント(接続)しています。

環境設定

# config/environments/development.rb
config.action_mailer.delivery_method = :letter_opener_web

この設定はRailsのActionMailerでメールを送信する方法をletter_opener_webに設定しています。

letter_opener_webRuby on Railsの開発環境で使用するためのgemです。このgemを使うと、開発中に生成されたメールをブラウザで確認することができます。これは、実際にメールを送信せずにメールの内容やレイアウトを確認するためのツールとなります。

通常、実際のメールを送信する場合は、SMTPサーバーなどを設定します。しかし、開発環境ではメールを実際に送信するのではなく、生成されたメールの内容を確認したいことが多いです。そのような場合に、letter_opener_webを使うと便利です。

この設定は通常、config/environments/development.rbのような環境設定ファイルに書かれます。こうすることで、開発環境でのみletter_opener_webが使われ、本番環境では実際のメール送信方法(SMTPなど)が使われるように設定することができます。

任意の場所にデバッグ用リンクを追加

- if Rails.env.development?
  # = link_to "メール", '/letter_opener', target: '_blank'
  # 2023/07/06更新
  # 新しいタブまたはウィンドウでリンクを開くときに rel='noopener' を指定することを推奨しています。rel='noopener' を指定すると、新しいタブやウィンドウが元のページにアクセスするのを防ぐことができ、セキュリティ上の問題を防ぐことができるようです。
  = link_to "メール", '/letter_opener', target: '_blank', rel: 'noopener'

サーバーを再起動して/letter_openerにアクセスする。

GitHub ActionsのDangerでcouldn't find remote ref refs/heads/danger_base, danger_headエラー

はじめに

ある日、Pull requestを作成した時に走るDangerが表題のエラーで落ちたので解決法を調べました。

実行コマンド

$ bundle exec danger --fail-on-errors=true

エラーログ(抜粋)

fatal: couldn't find remote ref refs/heads/danger_base
fatal: couldn't find remote ref refs/heads/danger_head

...

Cannot find a merge base between danger_base and danger_head. If you are using shallow clone/fetch, try increasing the --depth (RuntimeError)

BaseブランチとHEADブランチが見つからないというエラーです。以下のようにfetch-depth: 0を指定すれば(デフォルトは1)、すべてのgit履歴を取得するのでエラーは解消されますが、毎回すべて取得するのは効率が悪いので別の手段を考えました。

uses: actions/checkout@v3
  with:
  fetch-depth: 0

Dangerコマンドの前にBaseブランチとHEADブランチをfetchする

以下のように明示的にブランチを指定するとエラーは解消されました。

- name: Fetch base and head branches
  run: |
    git fetch origin ${{ github.base_ref }}:${{ github.base_ref }}
    git fetch origin ${{ github.head_ref }}:${{ github.head_ref }}
- name: Run Danger
  run: bundle exec danger --head=${{ github.head_ref }} --base=${{ github.base_ref }} --fail-on-errors=true
  env:
    DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}

おわりに

これがベストプラクティスかどうか分からないので知っている方いれば教えてください。

【Flutter】BitriseでFirebase Test LabへUploadしIntegration Testする方法

以下の記事を参考に、「GitHubで作成したFlutterリポジトリでPull Requestを作成した時にBitrise経由でFirebase Test LabでIntegration Testを行う方法」について調べたので、その備忘録です。

adityadroid.medium.com

はじめに

  • Firebase Test LabにapkファイルをUploadし、そこでIntegration Testするために必要なGoogle Cloud ConsoleとBitriseの設定についてのお話です
  • iOSについて言及しません
  • FlutterのIntegration Testについて言及しません
  • Android用の環境構築(android/app/src/androidTest/java/YOUR_APP_PACKAGE_NAME/MainActivityTest.javaapp/build.gradleの追記箇所)は参考記事通りで問題ないので割愛します

前提

  • Firebase・Bitriseプロジェクト作成済み&Flutterリポジトリと連携済み

Google Cloud ConsoleとBitriseの設定

Google Cloud Console

  • サービスアカウントを作成し「編集者」権限を付与
    • 権限がないと以下のエラーになる
      • ERROR: (gcloud.firebase.test.android.run) Unable to access the test environment catalog: ResponseError 403: Not authorized for project [REDACTED]
  • Cloud Tool Results APIを有効化
  • Credentialsファイル(JSON)をダウンロード

Bitrise

FirebaseのプロジェクトIDをSecret($FIREBASE_PROJECT_ID)に保存

参考記事ではReplace variables in inputs?をtrueにしていますが、後から上書きする予定がなければfalseにしておきましょう。また、PR作成時などに実行するWorkflowの場合はExpose for Pull Requests?をtrueにしないと、後述するScript Stepで取得できません。

Expose for Pull Requestsについて

You can decide which secrets should be exposed for / available in Pull Request builds.Be careful, exposing a secret is a potential security risk.

どのシークレットを公開するか/Pull Requestビルドで利用できるようにするかを決めることができます。シークレットを公開することは潜在的なセキュリティリスクとなるので注意してください。

CredentialsファイルをBitriseにUpload

参考記事ではGoogle Cloud ConsoleからダウンロードしたCredentialsファイル(JSON)もコピペしてSecretsに直書きしていると思われますが、やり方が分からなかったので「File Downloder」Stepを使用します。

手順

  • 「Code Signing&Files」タブを開き「GENERIC FILE STORAGE」にCredentialsファイルを追加する
    • 注意:「File Storage ID」を入力しないと、UploadするFileを選択できない(今回はFIREBASE_TEST_LAB_SERVICE_ACCOUNT_JSON_KEYと入力します。これで$BITRISEIO_FIREBASE_TEST_LAB_SERVICE_ACCOUNT_JSON_KEY_URLでファイルダウンロードURLを取得できます。)
  • 「Flutter Build」Stepよりも前に「File Downloder」Stepを追加
  • 「File Downloder」Stepで以下の項目を入力する
    • Download source URL: $BITRISEIO_FIREBASE_TEST_LAB_SERVICE_ACCOUNT_JSON_KEY_URL
    • Download destination path: ./gcloud_key_file.json
      • ファイル名やパスは何でも良いが後述するScript Stepと揃える

Flutter Build Stepの後にScript Stepを追加

タイトルは分かりやすく「Firebase Test Lab」

#!/usr/bin/env bash
# fail if any commands fails
set -e
# debug log
set -x
#1. Install gcloud CLI
curl https://sdk.cloud.google.com | bash
source $HOME/google-cloud-sdk/path.bash.inc
gcloud version
#2. Prepare The service account json for auth

#3. Authenticate gcloud CLI with firebase and select the firebase project
gcloud auth activate-service-account --key-file=gcloud_key_file.json
gcloud --quiet config set project $FIREBASE_PROJECT_ID
#4. Build the Instrumentation Test
pushd android
./gradlew app:assembleAndroidTest
./gradlew app:assembleDebug -Ptarget="integration_test/main_test.dart"
popd
#5. Run it on Test Lab using gcloud CLI.
gcloud firebase test android run \
  --type instrumentation \
  --app "$BITRISE_SOURCE_DIR/build/app/outputs/apk/debug/app-debug.apk" \
  --test "$BITRISE_SOURCE_DIR/build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk" \
  --timeout 30m

Workflowを走らせればFirebase Test LabにapkがUploadされIntegration Testが実行されるはずです。

その他エラー

Flutter Build Stepを追加していなかったため以下のエラーが発生

+ ./gradlew app:assembleAndroidTest
/var/folders/sj/_4jj239s3j33w5w1bdh98yj80000gn/T/bitrise432719702/step_src/._script_cont: line 17: ./gradlew: No such file or directory
exit status 1

./gradlewはgitignoreになっているので、GitHub上に無い

Flutter v3.7.0ざっくりまとめ

※この記事はFlutter v3.7.0ざっくりまとめの記事をエクスポートしたものです。内容が古くなっている可能性があります。

以下の記事をざっくりまとめました。

medium.com

Material 3サポートの強化

該当PR↓

github.com

  • 使用するには以下のコードを記述する
MaterialApp ( 
 theme : ThemeData ( 
   useMaterial3 : true, // この行
   colorSchemeSeed : Colors.green, 
 ), 
);

Material Design3とは

m3.material.io

qiita.com

Menu bars and cascading menus

  • メニューバーとカスケードメニュー(PCでよくある階層メニュー)を作れるようになった

Impeller preview

  • iOSのstableチャンネルで、新しいレンダリングエンジン"Impeller"がPreviewできるようになった
  • パフォーマンスはほとんどのアプリでSkiaと同等かそれ以上
  • 今後の安定版リリースではデフォルトでImpellerが使用される
  • ImpellerのWiki

github.com

github.com

iOS release validation

  • flutter build ipa コマンドがリリース前にアプリに加える必要がある変更があるかどうかを通知する

DevTools updates

  • 新機能
    • アプリの現在のメモリ割り当てをクラスおよびメモリ タイプ別に分析する機能
    • 実行時に一連のクラスにメモリを割り当てているコード パスを調査する機能
    • メモリ スナップショットを比較して 2 つの時点間のメモリ管理を理解する機能

docs.flutter.dev

  • パフォーマンスページの新機能
    • フレーム分析タブ
      • 選択した Flutter フレームの分析情報
      • インサイトには、Flutter フレームのコストのかかる部分をより詳細にトレースする方法に関する提案や、Flutter フレームで検出されたコストのかかる操作に関する警告が含まれる場合がある

Custom context menus

  • アプリの任意の場所にCustom Context Menuを作成できるように
    • contextMenuBuilder
  • テキスト選択以外でも使用できる
    • ContextMenuController
  • サンプル↓

github.com

CupertinoListSection and CupertinoListTile widgets

Scrolling improvements

  • スクロールの改善
  • 特にMacOSで改善
  • 新しいWidgetはリストに追加された (またはリストから削除された) アイテムをアニメーション化する

Internationalization tools and docs

  • 国際化サポートを刷新

docs.flutter.dev

Text magnifier

  • AndroidiOSのテキスト選択中に表示される拡大鏡がFlutterで動作するように
  • 無効化またはカスタマイズしたい場合は、magnifierConfigurationプロパティを参照

以上です。