Heroku で Cloud SQL を利用する方法

Heroku で Cloud SQL を利用する方法

この記事は、 PLEX Advent Calendar 2024の8日目の記事です。

はじめに

こんにちは。コーポレートチームの山崎です。
とある社内向けプロジェクトの要件として Heroku 上で Rails を動かしつつ、DB は Cloud SQL を利用する必要がありました。
設定に手こずったので、備忘録がてら手順をまとめようと思います。

Heroku で Cloud SQL を利用するには

Google Cloud SQL Proxy buildpack」というビルドパックを利用して実現できます。
仕組みとしては、Google が提供する Cloud SQL Auth Proxy をビルド時にダウンロードし、プロキシ経由で Cloud SQL との通信が可能になるという感じです。
あくまで、有志によって作成されたビルドパックなので、調査・検証した上で利用することをお勧めします。

elements.heroku.com

cloud.google.com

設定方法

README に手順が書かれていますが、若干の補足を加えて説明します。 github.com

BuildPacks の設定

  • Settings の Buildpacks に https://github.com/DanielZambelli/heroku-buildpack-cloud-sql-proxy を追加

環境変数の設定

  • GCLOUD_INSTANCE を追加し、Cloud SQL インスタンスの接続名を設定
  • GCLOUD_CREDENTIALS を追加し、base64 エンコードしたサービスアカウントキーを設定
    • サービスアカウントに必要な権限は下記ドキュメントを参照してください
# base64 エンコードの例
base64 -i path/to/key-file.json | pbcopy
  • DB指定用の環境変数(DATABASE_URL等)として postgres://<username>:<password>@localhost:5432/<database-name> を設定

cloud.google.com

起動時の設定

Web サーバー起動前にプロキシを起動させる必要があります。
起動コマンドの前に bin/run_cloud_sql_proxy &>null && を追加すれば OK です。

# Procfile の例
web: bin/run_cloud_sql_proxy &>null && bin/rails server

注意点

heroku run実行時

heroku run コマンドやスケジューラは、アプリケーションの Web Worker とは異なる dyno 上で実行されます。
そのため、起動コマンドと同様にプロキシを実行させる必要があります。

# 例
heroku run 'bin/run_cloud_sql_proxy & rails db:seed' -a app-name

既存DBとのリプレイス

Heroku Postgres を設定した場合、DATABASE_URL という環境変数が追加され、データベースのURLが設定されます。 
アプリケーションに Heroku Postgres が存在する限り、DATABASE_URL を書き換えることができないため、削除するか、他の環境変数を指定して利用する必要があります。
削除する場合は、必ずバックアップを取得しましょう!!!

The DATABASE_URL config var designates the URL of an app’s primary Heroku Postgres database. For apps with a single database, its URL is automatically assigned to this config var.

devcenter.heroku.com

リージョン

Heroku では通常のCommon Runtimeの場合、USリージョンもしくはEUリージョンのみが利用可能です。
東京リージョンにするにはPrivate Spaces Runtimeを利用する必要があります。
Cloud SQL インスタンスを地理的距離が遠いリージョンに配置していると、許容できないほどの遅延が発生する可能性があるので注意しましょう。

devcenter.heroku.com

cloud.google.com

まとめ

特殊な事情や要件がない限り、Heroku から Cloud SQL を利用するというパターンは珍しいかもしれませんが、同じ境遇の方々の参考になればと思います。
速度的な不満は特にありません。(管理は若干面倒)

さいごに

現在プレックスではソフトウェアエンジニア、フロントエンドエンジニア、UIデザイナーを募集しています。

とても働きやすい環境なので、一緒に働いてみたいと思った方がいましたら、是非ご連絡をお待ちしています!

dev.plex.co.jp

RailsのDelegated Typesで実現する柔軟なモデル設計

この記事は、 PLEX Advent Calendar 2024 の7日目の記事です。

はじめに

こんにちは、株式会社プレックスのコーポレートチームの金山です。

この記事では、Railsアプリケーションの設計に役立つ「Delegated Types」について解説したいと思います。

「Delegated Types」がどのような課題を解決するのか、使い方やメリット・デメリット、実際に使ってみた感想をシェアしていきます。

背景と課題

とあるプロジェクトの開発で、通話履歴を管理する機能を実装することになりました。

通話には「企業への通話」と「個人への通話」の2つがあります。

これら2つの異なるモデルを画面にリスト表示し、ページネーション、ソート、検索機能を提供する必要がありました。

Railsでは異なるモデルを一緒に扱うことが難しいため、この課題を解決する方法として「単一テーブル継承」と「Delegated Types」を検討しました。

単一テーブル継承(STI)とは

STIは、複数のモデルを単一のテーブルで管理する方法です。

モデルの種類を識別するための型情報「type」カラムを持たせます。

今回の通話履歴で例えると、企業への通話履歴ならtypeが「CompanyCall」、個人への通話履歴ならtypeが「UserCall」となります。

STIを利用すると、異なるモデルのデータを1つのテーブルで管理できるため、検索や操作が簡単になります。

考慮すべき点

ひとつのテーブルにサブクラス固有の属性も含まれるため、未使用カラムが多くなりテーブルが肥大化する可能性があります。

また、モデルごとに異なるカラムにNOT NULL制約を適用できないため、データ整合性のチェックが難しくなります。

Delegated Typesとは

Delegated Typesは、STIの問題を解決するためのアプローチです。

共通の属性を親テーブルに保存し、固有の属性は別の子テーブルに分けて管理します。

例えば、今回の通話履歴の場合、通話日時などの共有属性はCallテーブルで持ち、モデルごとに異なる属性はサブテーブルで保持します。

親子の関連付けは◯◯able_typeと◯◯able_idで紐付けます。

これにより、単一のテーブルで、すべてのサブクラス間で不必要に共有される属性を定義する必要がなくなります。

考慮すべき点

テーブルを分けることで、複数テーブル間の結合が必要になりクエリのパフォーマンスが低下する場合があります。

また、レコード作成や削除処理では親子テーブルで同時に行う必要があり、パフォーマンスに影響します。

テーブルが分かれることから実装の複雑になるため、各テーブルの構造や関連付けを明確に設計する必要があります。

Delegated Typesの使い方

まずはマイグレーションファイルを作成します。

callableというポリモーフィック関連を定義します。

ポリモーフィック関連を定義する場合、◯◯ableが慣例的な命名規則です。

これを書くことでcallable_typeとcallable_idカラムが自動的に追加されます。

class CreateCalls < ActiveRecord::Migration[7.1]
  def change
    create_table(:calls, comment: '通話テーブル') do |t|
      t.references(:callable, polymorphic: true, null: false)
      t.datetime(:called_at, null: false, comment: '通話日時')
      t.integer(:duration, comment: '通話時間')

      t.timestamps
    end
  end
end

次にモデルの設定です。

class Call < ApplicationRecord
  delegated_type :callable, types: ['UserCall', 'CompanyCall'], dependent: :destroy
  accepts_nested_attributes_for :callable
end
class UserCall < ApplicationRecord
  has_one :call, as: :callable, touch: true, dependent: :destroy
  belongs_to :user
end
class CompanyCall < ApplicationRecord
  has_one :call, as: :callable, touch: true, dependent: :destroy
  belongs_to :company
end

マイグレーションとモデルを設定したら準備完了です。

ここからはどのように使用するのかを見ていきます。

作成方法

新しいCallオブジェクトを作成する際に、Callableサブクラスを同時に指定できます。

以下のようにするとCallsとUserCallsテーブルにそれぞれレコードが作成されます。

  Call.create!(
    assignee_user_id: User.ids.sample,
    called_at: Faker::Time.backward(days: 30),
    duration: Faker::Number.between(from: 1, to: 100),
    callable: UserCall.new(user_id: User.ids.sample)
  )

取得方法

Callモデルを使用して通話一覧を取得できます。

Call.user_callsとすれば、UserCallのCallのみ取得可能です。

注意点として親から子の属性を参照することはできません。

委任先の情報を取得するにはcallableを呼び出す必要があります。

Call.order(called_at: :desc).limit(10)

Call.user_calls

call = Call.find(1)
callable = call.callable

更新方法

Callモデルにaccepts_nested_attributes_for :callableを定義することで、Callableサブクラスを同時に更新できます。

親と子のレコードが同じトランザクションで更新されます。

class Call < ApplicationRecord
  delegated_type :callable, types: ['UserCall', 'CompanyCall'], dependent: :destroy
  accepts_nested_attributes_for :callable
end
call = Call.find(1)

call.update(
  called_at: Time.now,
  duration: Faker::Number.between(from: 1, to: 100),
  callable_attributes: { user_id: User.ids.sample }
)

削除方法

削除はシンプルにdestroyを呼び出します。

親と子のレコードが同じトランザクションで削除されます。

call = Call.find(1)
call.destroy

結果と感想

今回のプロジェクトでは、各通話ごとに異なる属性を持つことを考慮し、Delegated Typesを採用しました。

その結果、共通部分を明確にしつつ、通話ごとに異なる外部キーにNOT NULL制約をつけることで、データの整合性を保つことができました。

しかし一方で、ActiveRecordでの操作がやや煩雑になり、STIと比べてシンプルな操作がしづらい場面がありました。

どちらの方法を採用するかはプロジェクトの要件次第です。それぞれのメリットとデメリットを考慮した上で選択することが大切だと感じました。

プレックス入社!コーポレートチームで頑張ります!

はじめに

はじめまして、プレックスの前川と申します。

2024年11月に株式会社プレックス(以下、プレックス)にエンジニアとして入社しました。

入社して2週間と少しですが、自己紹介を兼ねて入社経緯や入社してからの感想などをまとめておきたいと思います。

一人でも多くの方にプレックスに興味を持っていただけると嬉しいです。

目次

  • 自己紹介
  • プレックスに入社した理由
  • 入社してからの感想
  • 最後に

自己紹介

私の経歴は以下の通りです。

年月 経歴
2016年5月 某個別指導塾企業へ入社。 教室長と教室売却をしていました。
2022年2月 ポテパンキャンプでRuby on Railsを主に学習
2022年8月 永産システム開発株式会社に入社
2024年11月 株式会社プレックスに入社

教室長時代は赤字を出している上手くいってない直営の教室に配属されて立て直しをしました。 黒字化したら学習塾を経営したいと考えている企業、個人の方に売却する事業です。

新規事業で前例がなかったのですが、立て直し自体に苦戦しているメンバーの中で、 唯一2教室売却する成果を出してましたので、向いていました。

そんな中、エンジニアに転向したきっかけは、教室にプログラミング授業が導入された事でした。 プログラミング自体の楽しさ、エンジニアの働き方などに憧れがあり、退職してからポテパンキャンプに通い始めました。

永産システム開発株式会社では、フロントエンド、バックエンド問わず幅広く開発に携わりました。 PMをする機会もあり、受託・請負の案件を上流〜下流まで経験できたことは大変勉強になりました。 2年弱ほどお世話になった後、プレックスに転職しました。

プレックスでは開発本部のコーポレートチームに所属し、社内で使用するシステムの開発・カスタマイズに従事しています。

プレックスに入社した理由

コーポレートエンジニアをやりたい!

コーポレートエンジニアって何?という方は下記の記事を参照ください!

product.plex.co.jp

プレックスにおけるコーポレートチームは 「オペレーションの効率化によって事業成長に貢献する」 をミッションとしています。 赤字の教室運営で、「日常の業務フローが非効率かつ、再現性がない」が共通の課題としてあったので、オペレーションの効率化 = 事業成長という価値観が一致していました。 また、人とコミュニケーションを取りながら実装するのが好きなので、社内間での仕様の確認・調整も向いているなと考えました。

入社してからの感想

kintoneプラグイン・カスタマイズの開発環境が整っている

入社2週間で7つのプラグイン・カスタマイズを修正・実装しました。 今までkintoneのplugin・カスタマイズの実装をしたことはなかったので、タスクに取り組む前は不安もありました。実際やってみると、ほぼ躓くことなく入社2日目には1つ目のタスクが完了しました。 どんな開発環境なんだろう?って気になる方は是非カジュアル面談などで質問してください!

ビジネスサイドの方ともコミュニケーションを取りやすい

オペレーションの効率化が事業成長につながるという共通認識と結果があるので、開発環境に実装したものを現場の方が試してフィードバックを頂けたり、改善に向けてフラットに話せます!コミュニケーションコストが低いというのはとてもありがたいです。

今のコーポレートチームはいい感じに担当領域を分担できそうなので、 自分はkintone周りを中心に理解を深めて、チームに貢献して行ければいいなと考えています!

最後に

入社して日が浅いので業務の一連の流れを理解している段階ですが、シャッフルランチやポケポケ大会など事業部を跨いだエンジニア間の交流もあり、今まで経験のなかった業務にも携わり、実りのあるオンボーディング期間を過ごせています。

最後にはなりますが、現在プレックスではソフトウェアエンジニア、フロントエンドエンジニアの募集もあります。 本記事を読み少しでも共感する点があれば、是非ご連絡ください!

dev.plex.co.jp

TypeScriptの型を復習しましょう

この記事は、 PLEX Advent Calendar 2024の5日目の記事です。

今回紹介する下記の型は、
よく使うもの、見かけた時にすぐに理解しづらいものを選んでいます。

TypeScriptの型を復習しましょう
TypeScriptの型を復習しましょう

リテラル

特定の値そのものを型として使用できる機能

// 例1:
type Direction = "north" | "south" | "east" | "west"
let myDirection: Direction = "north" // OK
myDirection = "up"                   // ❌ エラー: "up"は許可されていない

// 例2:
// HTTP メソッドを制限する
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE"

function request(url: string, method: HttpMethod) {
  // ...
}

request("/api/users", "GET")    // OK
request("/api/users", "PATCH")  // ❌ エラー

ユニオン型( | )

複数の型を組み合わせて、型の選択肢を作る機能

// 1. 基本的なユニオン型
type StringOrNumber = string | number
let value: StringOrNumber
value = "hello"  // OK
value = 42       // OK
value = true     // ❌ エラー: booleanは許可されていない

型の交差( & )

全ての型の特徴を持つ新しい型を作る機能

type BasicInfo = {
  id: number    // 数値型のID
  name: string  // 文字列型の名前
}
type PersonalUser = BasicInfo & {
  type: "personal"  // リテラル型で "personal" という固定値
  age: number       // 数値型の年齢
}

使用例:
const user: PersonalUser = {
  id: 1,
  name: "田中",
  type: "personal",
  age: 25
}

マップ型

既存の型を基に、新しい型を作成する機能

type GameSearchState = {
  gameId: number
  nameQuery: string
  isSoldOut: boolean
}
type UrlTypes<T> = {
  [K in keyof T as KebabCase<K>]: string
}
  1. ジェネリック型(<T>)を使用
  2. マップ型([K in keyof T])で型のキーをイテレート
  3. as KebabCase<K>でキーをケバブケース(ハイフン区切り)に変換
  4. 全ての値の型をstringに設定
// UrlTypes<GameSearchState>
{
  "game-id": string,
  "name-query": string,
  "is-sold-out": string
}

条件付き型 (T extends U ? X : Y)

条件に応じた型の分岐

[K in keyof T as T[K] extends number ? K : never]: T[K]
  1. K in keyof T - 型Tのすべてのキーをイテレート
  2. as T[K] extends number ? K : never - 条件付き型で、値の型に基づいてキーをフィルタリング
  3. : T[K] - マッピングされた各キーの値の型を元の型と同じに この型は「数値型のプロパティのみを抽出する」という動作をする
type Example = {
  id: number
  name: string
  age: number
  isActive: boolean
}

type NumberPropertiesOnly = {
  [K in keyof Example as Example[K] extends number ? K : never]: Example[K]
}

// 結果として生成される型:
// {
//   id: number
//   age: number
// }

ReturnType

組み込みユーティリティ型の1つ
関数の戻り値の型を抽出する際に使用される

// 1. 基本的な関数の例
function getMessage(): string {
  return "Hello"
}

type MessageType = ReturnType<typeof getMessage>
// MessageType は string

// 2. 複雑な戻り値を持つ関数
function fetchUser() {
  return {
    id: 1,
    name: "田中",
    age: 25
  }
}

type User = ReturnType<typeof fetchUser>
// User は {
//   id: number
//   name: string
//   age: number
// }

Parameters

組み込みユーティリティ型の1つ
関数の引数の型をタプル型として抽出

// 1. 基本的な関数の例
function greet(name: string, age: number) {
  return `Hello, ${name}! You are ${age} years old.`
}

type GreetParams = Parameters<typeof greet>
// GreetParams は [string, number]

// パラメータを個別に取得することも可能
type FirstParam = Parameters<typeof greet>[0]  // string
type SecondParam = Parameters<typeof greet>[1] // number

// 2. オブジェクトパラメータを持つ関数
function createUser(params: {
  name: string
  age: number
  email: string
}) {
  return params
}

type CreateUserParams = Parameters<typeof createUser>[0]
// CreateUserParams は {
//   name: string
//   age: number
//   email: string
// }

as const, satisfies

const localizeData = {
  novel: {
    chapter: "チャプター",
    episode: "話",
  },
} as const satisfies {
  novel: {
    chapter: string
    episode: string
  }
}

as constの役割:
1. 全てのプロパティを読み取り専用(readonly)にする
2. リテラル型として扱われるようになる
3. "チャプター"string型ではなく"チャプター"というリテラル
satisfiesの役割:
1. オブジェクトが特定の型を満たすことを検証
2. 型の検証と推論の両方の利点を得られる
3. エラーを早期に発見できる

// ✅ OK - 型チェックが通る
const chapter = localizeData.novel.chapter  
// 型は "チャプター" (リテラル型)

// ❌ エラー - 読み取り専用なので変更不可
localizeData.novel.chapter = "章"  

// ✅ OK - 型チェックによる補完が効く
const hasChapter = "chapter" in localizeData.novel

この構文の利点:

  1. 型安全性:
    • オブジェクトの構造が指定した型と一致することを保証
    • 必要なプロパティの欠落を防ぐ
  2. リテラル型の保持:
    • as constにより、具体的な文字列値が型として保持される
    • より厳密な型チェックが可能
  3. IDE支援:
  4. 実行時の不変性:
    • オブジェクトとその中身が読み取り専用になる

参考元:
値・型・変数 | TypeScript入門『サバイバルTypeScript』
TypeScript特有の組み込み型関数 - log.pocka.io

【Next.js】Next/Imageの画像プレビューにて発生したメモリリークを追う

【Next.js】next/imageの画像プレビューにて発生したメモリリークを追う

この記事は、 PLEX Advent Calendar 2024の4日目の記事です。

こんにちは

株式会社プレックスでWebアプリケーションの開発をしているtetty0217 です。

はじめに

画像データを取り扱うフォームにおいて、入力した画像をプレビューで表示する機能が付属していることは珍しくないでしょう。

今日はそんなプレビュー機能の画像描画をnext/imageで無秩序に実装すると発生するクラッシュについて、Safariブラウザを題材として「なぜメモリリークが原因だったのか」「ブラウザでは何が起きていたのか?」という点をブラウザのレイヤー(Safari Web Inspector)とネイティブのレイヤー(Xcode)から追っていきます。

対象読者

  • Next.jsにおいて特に決まりなくnext/imageを使用している方
  • next/imageによるメモリリーク発生時のブラウザや端末では何が起こっているのか気になる方

インデックス

  • 【破壊編】クラッシュを起こしてみよう
  • 【調査編1】クラッシュの原因を追う
  • 【調査編2】メモリリークの発生タイミングを追う
  • 【#メモリ君を救いたい編】様々なアプローチ検証
  • まとめ

前提

大事ですね前提。本記事では主に下記を前提として検証をしています。

計測について

各測定については3回行ったうちの中間の値を利用しています(パフォーマンス測定ツールについては観測ごとに差分が出るため)

検証環境

検証アプリケーション

bulk-image-preview.vercel.app

ボタンをクリックしてから画像ファイルを選択すると、Blob化された画像データをGrid Layoutでnext/imageを通じて描画するだけのシンプルなアプリケーションです。

他の検証パターン

検証アプリケーションのコード

github.com

【破壊編】クラッシュを起こしてみよう

まずはこのGIFをご覧ください。

【Next.js】next/imageの画像プレビューにて発生したメモリリークを追う_demo1

これは検証アプリケーションのホームアプリで1.4MBの画像ファイルを60枚入力し意図的にクラッシュを発生させたものです。

お手元にiPhoneをお持ちでしたら、実際に検証アプリケーションにアクセスして数十枚〜程度の画像ファイルを入力してみるのもよいでしょう。

昨今のスマートフォン端末のカメラアプリで撮影した写真データはサイズが大きく(※数MB程度)、ある程度のファイル数をBlob化したものをnext/imageで一挙に描画しようとするとこのようになります。

【調査編1】クラッシュの原因を追う

この章では破壊編において発生したクラッシュにはどのような原因があるのかをXcodeを活用して観測していきます。

メモリリーク...っぽいけど断定できる理由は何か

でかいサイズのファイルを大量に描画しようとしてクラッシュしているんだから見ればわかるじゃないですかー。

まあ、それはそうなんですが、クラッシュした原因がメモリリークであるという論拠があってもいいですよね。

ということでXcodeのツールを使って探してみます。

なぜXcodeを使うのか

クラッシュしたかどうかは実際の画面でもSafari Web Inspectorでもわかるのですが、システムがなぜクラッシュしたかというのはネイティブのレイヤーを見ないとわからないのです。

よって、Safari App自体のプロセスを観測するためにXcodeを使用します。

クラッシュ時のログを見にいく

1.準備

スマートフォン端末をMacBookと接続しておきます。

2.コンソールアプリを開く

Xcodeのメニューバーから Window > Devices and Simulators を選択します。

Devices and Simulatorsのウィンドウが開いたら、MacBookと接続している端末を選択し表示された Open Console を選択するとコンソールアプリが開きます。

【Next.js】next/imageの画像プレビューにて発生したメモリリークを追う_コンソールアプリを開く

3.コンソールアプリでログを出力する

コンソールアプリが開けたら 開始 を選択してから破壊編の動作を再度行います。

【Next.js】next/imageの画像プレビューにて発生したメモリリークを追う_コンソールアプリでログを出力する

破壊編のクラッシュを引き起こしたところで停止をすると下記画像のように出力されたログを確認することができました。

【Next.js】next/imageの画像プレビューにて発生したメモリリークを追う_出力されたログを確認

4.クラッシュ原因には何がある?

クラッシュした原因を探そうにも何をどう見れば良いのか…ということでクラッシュレポートのタイプを特定するためにDeveloper AppleUnderstanding the exception types in a crash reportを見ていきましょう。

ざっと見た限りではクラッシュ原因を分類するタイプが下記のように定義されています*1

  • EXC_BAD_ACCESS
    • 無効なメモリアクセス。解放済みポインタや無効なアドレスへのアクセスが原因で発生します。
  • EXC_BAD_INSTRUCTION
    • 無効またはサポートされていないCPU命令の実行。通常はバグやアサーション失敗によるものです。
  • EXC_BREAKPOINT
  • EXC_CRASH
    • abort()などによる明示的なクラッシュの発生。
  • EXC_RESOURCE
    • リソース制限(例: メモリ、CPU、ファイル記述子など)に達した場合に発生します。
  • EXC_GUARD
  • EXC_CORPSE_NOTIFY
    • クラッシュ後のリソースの通知に関連する特殊な例外(通常、開発者が直接関与することは少ない)。

5.クラッシュタイプの特定

先ほど出力したログにタイプをそれぞれ検索にかけてみると EXC_RESOURCE が引っかかりました。

【Next.js】next/imageの画像プレビューにて発生したメモリリークを追う_クラッシュタイプの特定

どうやら「EXC_RESOURCE: com.apple.WebKit.WebContent exceeded mem limit: InactiveSoft 1536 MB 」というエラーが発生しています。

内容としては「WebContentプロセス(WebKitレンダリングエンジン)が、メモリのソフト制限(InactiveSoft)1.5GBを超えている」というエラーです。

調査報告

破壊編で発生したクラッシュの原因はメモリリークであるということがわかりました。

【調査編2】メモリリークの発生タイミングを追う

調査編1でクラッシュの原因はメモリリークだったことがわかりましたが、この章では時系列や統計情報で状況確認をしていくためにSafari Web Inspectorを活用して調査をしていきます。

Inspectorを実機に接続する

1. 準備

Safariの開発者モード有効化に関しては下記をチェック!

product.plex.co.jp

2. 実機端末のWebインスペクタを有効化

  • 設定 > アプリ > Safari を開く
  • 画面最下部の 詳細 を開く
  • 表示された Webインスペクタ にチェックを入れると準備完了です

【Next.js】next/imageの画像プレビューにて発生したメモリリークを追う_実機端末のWebインスペクタを有効化

3. 実機のSafariブラウザとMacBookのWebインスペクタを接続

  1. MacBookSafariを開く
  2. スマートフォン端末で検証アプリケーションを開く
  3. Safariのメニューバーから 開発 > ${スマートフォン端末名称} を選択する

【Next.js】next/imageの画像プレビューにて発生したメモリリークを追う_端末設定

実機のSafariブラウザとMacBookのWeb Inspectorを接続できました!

試しにalert関数を実行してみると実機側にも反映されます。

【Next.js】next/imageの画像プレビューにて発生したメモリリークを追う_MacBookのWeb Inspector

【Next.js】next/imageの画像プレビューにて発生したメモリリークを追う_Safariブラウザ

破壊編の様子をツールで見てみる

実際にメモリリークが発生するまでをWeb InspectorのPerformance Timelineでプロファイルして見ていきましょう。

【Next.js】next/imageの画像プレビューにて発生したメモリリークを追う_メモリリークが発生

画像を入力してからFileをBlobに変換して描画をするまでを観測した図です。

CPUとメモリの使用率が急に0%になっている地点が破壊編で観測したメモリリークの発生箇所になります(崖になっているところ)

Performance Timelineを見ていく

WebkitのPerformance TimelineはChromeのPerformance Timelineより詳細度が低いですが、各DevToolsの中では情報がまとまって見やすいのではなかろうかと思います。

Timelineは各イベントを時系列でプロットしたイベントビューとイベントごとの詳細を表示する詳細ビューの2つのセクションに分かれています。

※プロファイリング中はJITの最適化が解除されていることに注意が必要です。

【Next.js】next/imageの画像プレビューにて発生したメモリリークを追う_Performance Timeline

スクリーンショット

観測範囲に時系列でViewPortのスクリーンショットを表示しています。

詳細ビューでは下記画像のようにViewPortの変遷を見ることができるのですが、どうやら入力した画像が描画され始めた辺りでメモリリークが発生したように伺えます。

【Next.js】next/imageの画像プレビューにて発生したメモリリークを追う_スクリーンショット

ネットワーク要求

観測範囲において時系列で発生したネットワークのアクティビティを表示しています。つまるところネットワークタブをより簡潔にしたビューです。

今回は画像の入力のみを行っていますから、アプリケーションのAsset関連の取得を除くと画像のBlobデータが60個表示されます。

【Next.js】next/imageの画像プレビューにて発生したメモリリークを追う_ネットワーク要求

レイアウトとレンダリング

観測範囲において時系列で発生した描画と描画に関連する処理が列挙されます。

ここではブラウザ上での再レンダリングによるスタイルの再計算や60個の画像データの処理がコンポジットに表示されました。

【Next.js】next/imageの画像プレビューにて発生したメモリリークを追う_レイアウトとレンダリング

メディアとアニメーション

観測範囲において時系列で発生したメディア要素とCSSのAnimationおよびTransitionの情報が列挙されます。

今回はスタイリングに使用したMUIの内部で処理されたTransitionが表示されています。

【Next.js】next/imageの画像プレビューにて発生したメモリリークを追う_メディアとアニメーション

JavaScriptイベント

観測範囲において時系列で発生したJavaScriptのアクティビティやスタック、非同期処理、イベントの情報をサマリーとして列挙されます。

CPU

観測範囲において処理された関連するすべてのスレッドの情報のサマリーが列挙されます。

ここではどのスレッドでどの程度のCPUが使用されたのか、処理のピーク、メインスレッドの各アクティビティの割合など統計を確認することができるので、パフォーマンスを確認する上でまず見るべきタイムラインになってきます。

【Next.js】next/imageの画像プレビューにて発生したメモリリークを追う_CPU

メモリ

観測範囲におけるメモリ使用量の大枠が内訳が表示されます。

【Next.js】next/imageの画像プレビューにて発生したメモリリークを追う_メモリ

上記のような確認を経て、ブラウザのクラッシュ発生時は「ViewPortに画像が描画され始めた箇所」でCPU/メモリの使用を維持できずに(クラッシュしたことで)Web Inspector上での観測が中断されたことがわかりました。

タイムラインの内容を分析する

もうnext/imageの箇所直して終わりじゃん!いいえ、まだまだ追っていきます。

今回はメモリリークが原因ということで特に数値が大きいCPU Timelineのプロファイルにフォーカスして見ていきます。

使用率の内訳

このようにCPU Timelineの詳細では観測範囲における総計を見ることができます。

【Next.js】next/imageの画像プレビューにて発生したメモリリークを追う_クラッシュ時総計

メインスレッドは40%弱使用している中、その他(WebKit thread + Other thread)ではなんと200%以上もCPUを使用しています。

比較のためnext/image → imgタグに置き換えた画面を使用してみると下記のような結果になりました。

【Next.js】next/imageの画像プレビューにて発生したメモリリークを追う_非クラッシュ時総計

メインスレッドは大きく変わっていませんが、その他のスレッドの特にWebkit ThreadがCPU使用率が激減しています。

Webkit Threadとは

WebKitが持つバックグラウンドの最適化処理などを実行するスレッドのまとまりを表しているものです。

調査報告

今回の主題である画像の最適化処理に関して、Webkit2にはバックグラウンドで処理を実行してブラウザのレスポンスに影響をなるべく及ぼさないようにするsplit process modelという概念がありました。

trac.webkit.org

next/imageのようにimgタグに対して画像描画の最適化を図るオーバーヘッドを持っているようなコンポーネントを多く描画することがこの別スレッドを大きく使用することになるみたいですね(もちろん画像サイズも。)

Webkitあたりはちょっと記憶が薄いところを掘り起こしてきましたので、情報が古かったり誤っていたら是非ご指摘をいただけると幸いです。

【#メモリ君を救いたい編】様々なアプローチ検証

この章では破壊編と同じデータ量を使い、画像の加工方式や描画量、next/image以外を使うなどアプローチを変えて事象を見てみます。

【パターン1】next/imageからimgタグに置き換える

next/imageは上述の通り、imgタグを描画するにあたってさまざまな最適化を行うことから画像の描画にあたってのオーバーヘッドが乗っているコンポーネントになります。

今回のプレビュー機能にはユーザーに対し、これから投稿される画像を「事細かに確認してほしい」というメッセージはありません。

よって選択した画像が大まかにあってそうか確認してもらえればよいという解釈の元、next/imageではなくネイティブのimgタグを使用してみました。

【Next.js】next/imageの画像プレビューにて発生したメモリリークを追う_demo2

https://bulk-image-preview.vercel.app/img-tag

結果としては同じデータ量にも関わらずクラッシュすることはありませんでした。

これは調査編2で観測した通り、CPUとメモリの使用率が大きく下がったことが要因としてあります。

そのため、試しに同じサイズの画像ファイルを200件に増やして追加してみても無事描画されました。

【パターン2】入力サイズは同じだが描画範囲を減らしてみる

サイズの大きいリストを表示するためには、画面のパフォーマンスと体験を両立させるために仮想スクロールといったDOMの描画を制限するという手法があります。

next/imageとデータ量は同じとして描画量を減らしてみるとどうなるでしょうか?簡単に描画するnext/imageを3つに減らしてみましょう。

【Next.js】next/imageの画像プレビューにて発生したメモリリークを追う_demo3

https://bulk-image-preview.vercel.app/narrow-image

CPUタイムラインの総計

【Next.js】Next/Imageの画像プレビューにて発生したメモリリークを追う_Pattern2

描画するDOMを激減させたので、それは大丈夫でしょうと思われるかもしれません。私もそう思います!

実際に処理される画像データは変わりありませんが、next/imageが描画される量によって違いがあるということで、やはりnext/imageをたくさん使うと大変だということがわかります。

imgタグに対して行った200件を追加してみると、少し時間がかかりつつも描画されたのでnext/imageのオーバーヘッドがいかに大きいかがわかります。

【パターン3】画像ファイルを圧縮してからBlob化する

最後に、単純にnext/imageが描画される量が単に多いだけがメモリリークの理由なのか?ということを見ていきたいと思いますので、Blob自体のサイズを削減してみましょう。

今回は例としてblueimp-load-imageを利用して画像ファイルの圧縮を行います。

【Next.js】next/imageの画像プレビューにて発生したメモリリークを追う_demo4

https://bulk-image-preview.vercel.app/compressed-image

CPU Timeline

【Next.js】Next/Imageの画像プレビューにて発生したメモリリークを追う_Pattern3

どうやら、サイズの大きいBlobほどnext/imageにおけるオーバーヘッドが大きくなるようですね。 圧縮処理を追加しているため破壊編の例よりはメインスレッドの使用率が高く、Blobサイズが小さくなったためnext/imageのオーバーヘッドが小さくなっていることがわかります。

ちなみに限界まで試したところ150件でOOM(Out of Memory)がかかりました。

調査報告

メモリリークを引き起こさないためには色々なアプローチが取れそうだということがわかったので、サービスの体験に応じて最適化を考えていければと思います。

本記事では詳しく言及しませんが、ファイルのBlob化においてデータが不要になったタイミングでメモリへの参照を破棄しておくことです。

createObjectURLによってBlob化されたデータがunloadのタイミングで自動破棄されるまではメモリへの参照が維持されますから、アプリケーションを途中で落とさないためにも破棄するBlobに対してrevokeObjectURLをしておくことは必須です。

developer.mozilla.org

まとめ

ここまで読んでくださりありがとうございました。

本記事では、Next.jsのnext/imageを使用した画像プレビュー機能で発生するメモリリークについて遠回りや深追いをしてみました。

今回のような雑多な調査は時間がかかりますし、仕事の面では使う機会の少ない知識が多く入ってきます(※もちろんそうではない職域やポジションはあると思います。)

しかし、問題に対して仮説検証の末すぐに解決するのではなく、たまには事象そのものを深く追ってみたり、違う視点で見てみるのもよいのではないでしょうか?

今後のエンジニア人生でヒラメキの材料になったり技術トークの良いネタになるでしょう。

PS.本記事でお気づきの点があればぜひコメントください!

最後に

現在プレックスではソフトウェアエンジニア、フロントエンドエンジニア、UIデザイナーなど各業種を募集しています。

メンバー全員で行動や技能の基準値を上げていくことで強い組織を目指しています。

一緒に働いてみたいと思った方がいましたら、是非ご連絡をお待ちしています!

埋め込み[dev.plex.co.jp]

関連リンク

Safari Web Inspector

developer.apple.com

Webkit

webkit.org

webkit.org

trac.webkit.org

Developer Apple

developer.apple.com

developer.apple.com

developer.apple.com

GitHub

github.com

Next.js

nextjs.org

*1:ChatGPTによってドキュメントの内容を簡略化してもらいました

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_アイキャッチ この記事は、 PLEX Advent Calendar 2024の3日目の記事です。

こんにちは

株式会社プレックスでWebアプリケーションの開発をしているtetty0217 です。

はじめに

皆さんはどのようなタイミング・方法でWebフロントエンドアプリケーションのモバイルビューを開発・デバッグしているでしょうか。

PCブラウザのレスポンシブモードを使って開発したり、QA期間に実機端末でデバッグしたりなど様々でしょう。

本記事はその実機端末に近い環境であるiOS SimulatorとAndroid Emulatorを使用してアプリケーションをデバッグするための環境をセットアップすることにフォーカスした解説記事です。

まとまった情報が出回っていない中、社内メンバーに対するオンボーディングを一括しておこなうためにもこの記事を執筆したのでぜひご利用いただけると幸いです。

対象読者

  • スマートフォン端末が不足している」「スマートフォン端末特有の不具合やAPIをローカル環境で再現したい」そんな開発者の方にお役立ちな内容となっております。
  • 知りたい箇所だけピックアップして読んでいただけると良いです。

章立て

前提

大事ですね前提。本記事では下記の環境をデバッグに使用しております。

デバッグに使うPC

Macbook Apple M3 Pro, Sonoma version 14.6

デバッグに使うアプリケーション

例として今回はcreate-next-app@latestを使用しています。

Terminal実行

公式に沿って実行します。

$ npm i -g create-next-app
$ npx create-next-app
✔ What is your project named? … simulator-app
✔ Would you like to use TypeScript? … No / Yes
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like your code inside a `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to use Turbopack for next dev? … No / Yes
✔ Would you like to customize the import alias (@/* by default)? … No / Yes
✔ What import alias would you like configured? … @/*

dependencies

※package.jsonの一部を抜粋

"dependencies": {
  "react": "19.0.0-rc-66855b96-20241106",
  "react-dom": "19.0.0-rc-66855b96-20241106",
  "next": "15.0.3"
},
"devDependencies": {
  "typescript": "^5",
  "@types/node": "^20",
  "@types/react": "^18",
  "@types/react-dom": "^18"
}

【導入編】iOS Simulatorと開発サーバーの接続

この章の目標

iOSが動作するSimulatorを登録しローカルの開発サーバーにアクセスして画面を表示します。 今回はiPhone15(iOS 17.5)と接続します。

Xcodeデバッグ用デバイスを登録しよう

XcodeのVersionは15.4を使用します。

※上記以降のバージョンを使用する場合はUIや手順が多少変わっている場合があることに注意してください。

1. XcodeのSimulator管理パネル

  • Xcodeを開いたらメニューバーから Settings を選択しましょう。
  • 表示されたダイアログのメニューから Platforms を選択すると下のようなダイアログの画面が表示されます。

※環境やリリースされているiOS Versionよっては表示が多少異なります。

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_XcodeのSimulator管理パネル

2.iOSのRuntimeをインストール

初期表示では最新のiOS Versionと各種デバイスが表示されていますが、特定のバージョンを必要とする場合は+からバージョンをDownload & Installしましょう。

本記事ではiOS 17.5を選択します。

こちらをインストールすることでiOS 17.5が動作するiPhone / iPadをシミュレートすることが可能になります。

〜しばし待たれよ〜

3.Simulatorを起動

ようやくインストールが終わりましたね。おめでとうございます。

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_Simulatorを起動
インストールが完了したらXcodeのメニューバーから Open Developer Tool > Simulator を選択します。

起動したSimulatorアプリのメニューバーからFile > Open Simulator > iPhone 15を選択するとデバイスが起動します。

次回以降の起動をスムーズにするため、Simulator AppをDockに登録しておきましょう。

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_iPhoneが起動した

Simulatorから開発サーバーにアクセスしよう

1.開発サーバーを起動

今回はNext.jsを使用しているのでnext serverを起動します。

Next.jsの開発サーバーはデフォルトでhttp://localhost:3000として起動します。

$ npm run dev 

PCのブラウザでhttp://localhost:3000にアクセスした時の画面

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_PCのブラウザでlocalhost:3000にアクセスした時の画面
localhost:3000

2.iOS Simulatorからローカル環境にアクセス

基本的にSimulatorのブラウザからローカル環境のURLへアクセスできます。

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_Simulatorのブラウザからローカル環境のURLへアクセス

試しにUser-Agentを表示してみましたが実機同様のものとなっていますね。 これでiOS Simulatorからローカルの開発サーバーへ接続することができました!

HMR(Hot Module Replacement)なども基本的に機能するのでスマートフォン端末に近しい環境で開発をすることができます。

別のモデルでSimulatorを起動する方法

特定の機種で不具合が発生するといったことは時折あるでしょう。

そんな時も対象のモデルを用意してローカルで確認することができます。

モデルを登録

SimulatorアプリのメニューバーからFile > New Simulatorを選択すると新しいSimulatorの登録ダイアログが表示されますので、必要なモデルとOS Versionを選択して登録するとFile > Open Simulatorから起動できるようになります。

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_新しいSimulatorの登録ダイアログ
新しいSimulatorの登録ダイアログ

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_起動したいデバイスを選択
起動したいデバイスを選択

デバッグ編】iOS Simulator × Safari Web Inspector

この章の目的

皆さんが普段からDevtoolsなどでアプリケーションをデバッグしているように、Simulator上で動作するブラウザをDevtoolsでデバッグします。

今回はiOS Simulatorに初期インストールされたSafariを使用しますのでSafari Web Inspectorを使用します。

PCのSafariの開発者モードを有効化する

SafariにはDeveloper用のメニューが存在し、今回のようにSafari上で動作するアプリケーションをデバッグするツールがいくつか搭載されています。

Safariのメニューバーに 開発 メニューがない方は有効化しましょう。

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_Safariのメニューバー

1. 機能の有効化

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_&#x60;設定&#x60;から表示されるダイアログ
Safariのメニューバーの設定から表示されるダイアログのメニューにある 詳細 を選択すると表示される Webデベロッパ用の機能を表示 にチェックを入れます。

有効化するとSafariのメニューバーに 開発が表示されます。

開発メニューにはセッションのあるデバイスが表示されますので、iOS Simulatorとの接続にもこのメニューを使用することになります。

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_Safariの開発メニュー

iOS Simulator上のサンプルアプリケーションをデバッグする

先ほど起動したiOS Simulator上のSafariで再度 localhost:3000 にアクセスしましょう。

Web Inspectorを開く

localhostにアクセスした状態でSafariのメニューバーの開発を選択すると下記画像のようにiPhone 15とlocalhostが表示されています。

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_Safariの開発メニューのシミュレータ

localhostを選択することでいつものようにWeb Inspectorが起動し、macOSSafari上で動作するアプリケーションと同じようにデバッグをすることが可能となります。

スマートフォン端末により近い環境でデザインやエラー、パフォーマンスに関して調査することができますのでぜひ使っていきましょう!

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_Safari Web Inspector
Safari Web Inspector

ホームアプリの場合

サービスによってはPWAを動作させていたり、よりネイティブアプリに近しい状態で使用を推奨するものもあるでしょう。

その場合、スマートフォン端末同様にホーム画面にアイコンを追加することが可能です。

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_ホームアプリの場合
メニューを開き

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_ホームアプリの場合2
ホーム画面に追加する

追加するとアプリアイコンが表示されます。 そして、このホームアプリについても上記のようにSafari Web Inspectorと接続することができるわけです。

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_ホームアプリが追加された
ホームアプリが追加された

【導入編】Android Emulatorと開発サーバーの接続

この章の目的

Android Emulatorで動作する仮想デバイス(AVD)を登録し、ローカルの開発サーバーにアクセスして画面を表示します。

使用ツール

AVDを管理できるツールはいくつかありますが、今回はその中でもメジャーであるAndroid Studioを使用していきます。

公式サイトから最新のアプリをインストールしておきましょう。

インストールウィザードの選択肢などはデフォルトのままでOKです。

Android StudioでAVDを登録しよう

Android StudioのVersionは 2024.2.1 Patch 2 を使用します。

※上記以降のバージョンを使用する場合はUIや手順が多少変わっている場合があることに注意してください。

1. Android Studioで端末管理パネルを開く

インストールしたAndroid Studioを起動しましょう。

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_Android Studio
Android Studio

初期ダイアログが表示されたらMore Actions > Virtual Device Managerを選択するとデバイスマネージャーパネルが表示されます。

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_Virtual Device Manager

2. AVDを作成する

AVDを作成するためにパネルメニューの から作成パネルを開きます。 【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_AVD作成

次に作成したいデバイスを選択します。

今回は比較的新しい「Pixel 9」を使用しましょう。

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_AVDを選択

Next を選択しEmulator動作させるハードウェアプロファイルを選択します。

今回は初期インストールされているAndroid 15.0のVanilla Ice Creamを利用しましょう。

デバッグに使用したいプロファイルがインストールされていない場合、このパネルで対象のインストールボタンを選択しインストールをしておきましょう。

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_プロファイルを選択

最後にイメージの設定パネルが表示されますが特に変更せずに Finish を選択してAVDの作成が完了します。

3. Emulatorを起動

AVDの作成が完了すると端末管理パネルに戻り、作成したデバイスが端末一覧に追加されていることがわかります。

早速 ▶︎ を選択し起動してみましょう!

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_デバイスを起動する

しばらく待つとEmulatorが起動しPixel 9が表示されます。

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_Android Emulator
Android Emulator

Android Emulatorから開発サーバーにアクセスしよう

1. 開発サーバーを起動

Next.jsの開発サーバーはデフォルトでhttp://localhost:3000として起動します。

$ npm run dev 

PCのブラウザでhttp://localhost:3000にアクセスした時の画面

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_PCのブラウザでlocalhost:3000にアクセスした時の画面
localhost:3000

2. Android Emulatorからローカル環境にアクセス

バイスにはGoogle Chromeが初期インストールされていますので、アプリを起動してURL欄に 10.0.2.2:3000 を入力すると開発サーバーにアクセスすることができます。

Android Emulatorの仮想デバイスは開発マシン(今回はmacOS)から分離されたネットワーク環境で動作しているため、いわゆるループバックアドレス127.0.0.1 )は 10.0.2.2フォワードされています。 よって、開発サーバーである http://localhost:3000 にEmulatorからアクセスする際はhttp://10.0.2.2:3000を見る必要があるのです。

詳細はAndroid DevelopersのNetwork address spaceを参照してください。

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_Android Emulatorと開発サーバーを接続

試しにUser-Agentも表示してみましたが実機同様のものとなっていますね。 これでAndroid Emulatorで動作するデバイスからローカルの開発サーバーへ接続することができました!

HMR(Hot Module Replacement)なども基本的に機能するのでスマートフォン端末に非常に近しい環境で開発をすることができます。

デバッグ編】Android Emulator × Chrome Devtools

皆さんが普段からDevtoolsなどでアプリケーションをデバッグしているように、Emulator上で動作するブラウザをDevtoolsでデバッグします。

今回はEmulatorに初期インストールされたGoogle Chromeを使用しますのでChrome Devtoolsを使用します。

Emulatorで動作するデバイスの開発者モードを有効化する

DevtoolsとEmulatorを接続するためにEmulator側のデバイスで開発者モードを有効化する必要があります。

開発者モードの有効化をする方法はデバイスによって異なりますが今回はPixelの例を記載します。

  • Settings > About emulated device メニューを開きます。
  • 最下部にある「Build number」を7回タップします
  • You are now a developer!」が表示されたら完了

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_About emulated device

※他機種の開発モード有効化の例はAndroid DevelopersのConfigure on-device developer optionsを確認しましょう。

PCのGoogle Chromeでデバイス接続設定をする

1. 準備

先ほど起動したAndroid Emulator上のChrome Appで再度 10.0.2.2:3000 にアクセスしておきましょう。

PCのGoogle Chromechrome://inspect/#devices アクセスしましょう。

2. 設定

環境によって異なりますが、下記のような画面が表示されます。

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_Inspect Deviceメニュー

Discover USB devices にチェックを入れ Port forwarding…. を選択して表示されるダイアログに開発サーバーの情報を入力します。

  • Port: 3000
  • IP address and port: localhost:3000
  • Enable port forwarding にチェックを入れる

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_Discover USB devices

Discover network targets にチェックを入れ Configure…. を選択して表示されるダイアログの Enable port forwarding にチェックを入れます。

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_Discover network targets

設定を終え接続可能なデバイスが表示されたら成功です!

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_接続可能なデバイス
接続可能なデバイスが出てきた

Android Emulator上のサンプルアプリケーションをデバッグする

先ほど表示されたデバイスのInspectを選択します。

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_Inspect

接続が成功した場合、いつも我々が見ているChrome Devtoolsがウィンドウ表示されてAndroidで動作するアプリケーションに関する様々デバッグが可能となります。

インストールしたChrome Extensionも基本的には利用することが可能ですので、スマートフォン端末でより近い環境で精度の高いデバッグをしていきましょう!

PC画面の開発時同様にEmulator上のブラウザで動作するアプリケーションに対してLighthouseも動作させることができます。

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_lighthouse実行してみた
lighthouse実行してみた

【Tips】特定のデバイスCLIから起動する

私は基本キーボードで操作をしている人間なので、SImulator/Emulatorの起動をCommand lineから実行したいです。 ここではインストールされているデバイスをCommand lineから起動する手段を共有します。

iOS Simulator編

例としてiOS17.5が動作するiPhone15を起動してみます。

1. デバイスのIDを確認する

インストールしてあるデバイス情報を出力し該当のiOS VersionのデバイスのIDを確認します。

まず、xcrunコマンドを使用しSimulatorに関する情報を出力してみます。

出力フォーマットは${DeviceName} (${DeviceID}) (${Launch Status}) ... なのでiOS 17.5で動作するiPhone 15のIDは F8220220-F8EA-4E78-B963-02B499C5CA2C のようです。

$ xcrun simctl list devicetypes
...
== Devices ==
-- iOS 17.5 --
    iPhone SE (3rd generation) (8539CB79-7931-4284-978A-3C46EA69315C) (Shutdown) 
    iPhone 15 (F8220220-F8EA-4E78-B963-02B499C5CA2C) (Shutdown) 
    iPhone 15 Plus (FF11D10B-BBEF-45E2-9EF3-0A3240A3AB56) (Shutdown) 
    iPhone 15 Pro (18082B41-EEDD-4A44-AF82-37A5D4BBFB08) (Shutdown) 
    iPhone 15 Pro Max (9ED779BA-F500-4CAC-8250-FDB36C85F6BC) (Booted) 
    iPad (10th generation) (B6460D49-C26B-446B-BD49-B47D226DE8E9) (Shutdown) 
    iPad mini (6th generation) (FF5B67AF-076C-4804-AD83-F75CAD235D61) (Shutdown) 
    iPad Air 11-inch (M2) (24C94583-82DB-49BF-986D-C99FAFB2C3B5) (Shutdown) 
    iPad Air 13-inch (M2) (3563CE6B-DF57-4940-8E12-5756CCF1F76D) (Shutdown) 
    iPad Pro 11-inch (M4) (B9EE1571-B15E-4FD9-9383-6B1C84AB6B56) (Shutdown) 
    iPad Pro 13-inch (M4) (DD13C38F-FAF6-4B0F-B474-B1DAA2A42644) (Shutdown) 
...

2. デバイスを起動する

先ほど確認したデバイスIDをbootしてからSimulatorを開きます。

$ xcrun simctl boot F8220220-F8EA-4E78-B963-02B499C5CA2C
$ open -a Simulator 

開きました!

【Webフロントエンド開発】モバイルビューをデバッグするためのSimulator/Emulator活用 2024年版_iPhoneが起動した

Terminalで再度リストを出力するとiOS 17.5のiPhone 15が起動状態(Booted)となっていることがわかります。

$ xcrun simctl list devicetypes
...
== Runtimes ==
iOS 17.5 (17.5 - 21F79) - com.apple.CoreSimulator.SimRuntime.iOS-17-5
== Devices ==
-- iOS 17.5 --
    iPhone SE (3rd generation) (8539CB79-7931-4284-978A-3C46EA69315C) (Shutdown) 
    iPhone 15 (F8220220-F8EA-4E78-B963-02B499C5CA2C) (Booted) 
    iPhone 15 Plus (FF11D10B-BBEF-45E2-9EF3-0A3240A3AB56) (Shutdown) 
    iPhone 15 Pro (18082B41-EEDD-4A44-AF82-37A5D4BBFB08) (Shutdown) 
    iPhone 15 Pro Max (9ED779BA-F500-4CAC-8250-FDB36C85F6BC) (Shutdown) 
    iPad (10th generation) (B6460D49-C26B-446B-BD49-B47D226DE8E9) (Shutdown) 
    iPad mini (6th generation) (FF5B67AF-076C-4804-AD83-F75CAD235D61) (Shutdown) 
    iPad Air 11-inch (M2) (24C94583-82DB-49BF-986D-C99FAFB2C3B5) (Shutdown) 
    iPad Air 13-inch (M2) (3563CE6B-DF57-4940-8E12-5756CCF1F76D) (Shutdown) 
    iPad Pro 11-inch (M4) (B9EE1571-B15E-4FD9-9383-6B1C84AB6B56) (Shutdown) 
    iPad Pro 13-inch (M4) (DD13C38F-FAF6-4B0F-B474-B1DAA2A42644) (Shutdown) 
...

対話型スクリプトで更に楽をしたい

私は対話型のスクリプトを作成してiOS SimulatorとAndroid Emulatorを起動しています。 大量のDeviceとRuntimeをインストールしている方やそれぞれの名称を覚えることが面倒な方はご参考までに。

github.com

まとめ

ローカル環境で開発している時、実機端末で動作するAPIを確認する時、特定のデバイスで不具合が発生した時、様々なケースでSimulator/Emulatorを使った開発環境は役立っていくことでしょう。

チームメンバーに毎回こういった情報をオンボーディングするのも大変ですので、この資料が皆さんに少しでも役立てば嬉しく思います。

最後に

現在プレックスではソフトウェアエンジニア、フロントエンドエンジニア、UIデザイナーなど各業種を募集しています。

メンバー全員で行動や技能の基準値を上げていくことで強い組織を目指しています。

一緒に働いてみたいと思った方がいましたら、是非ご連絡をお待ちしています!

dev.plex.co.jp

関連リンク

iOS

developer.apple.com developer.apple.com developer.apple.com

Android

developer.android.com developer.android.com developer.chrome.com

【DBクライアントツール論争】それでも私はDBeaverを使う

この記事は、 PLEX Advent Calendar 2024の1日目の記事です。

はじめに

こんにちは、Plex Job開発チームの栃川です。

最近、チーム内で「最強のDBクライアントツールは何か」について話す機会がありました。

私は、「DBeaver」というクライアントツールを激推ししているのですが、メンバーには受け入れてもらえなかったので、DBeaverの普及を兼ねてこちらで紹介していきたいと思います。

dbeaver.io

社内のDBクライアントツール事情

DBeaverの良さを語る前に、まずは社内で実際に使われているツール事情を紹介します。

開発メンバーに何を使っているか聞いてみたところ、下記2つのクライアントツールが使われているようです。

DataGrip

www.jetbrains.com

TablePlus

  • 無償ツール。シンプルでモダンなSQLクライアントツール

tableplus.com

これら2つのツールを実際に使っているメンバーに、どんなところが良いかを聞いてみました。

「DataGrip」を使うメンバーの声

DataGripは有償ツールになるので、やはり機能性の良さについて挙げるメンバーが多かったです。 また、わかりやすく美しいUIである点も推しポイントの1つのようです。

「とにかく美しいUIがたまらない。テーマの切り替えなどもできる点はGoodです」(Tさん)

「 AI機能を活用することで、ストレスなくクエリ書ける点が気に入ってます」(Sさん)

「クエリプランやEXPLAINのフレームグラフを可視化してくれるところがお気に入りです 」(Iさん)

「TablePlus」を使うメンバーの声

無償ツールでありながら、DataGripに劣らないほどの機能性とUIを兼ね備えている点が良いとの声が目立ちました。

「無償ツールでありながら、UIが本当に良い」(Yさん)

「業務を行う上では十分な機能が備わっている点が安心」(Yさん)

「無償・有償が選べるので、徐々にグレードアップできるのが良いです」(Kさん)

布教に失敗、DBeaverが受け入れられなかったワケとは

実は、かつてチームメンバーにDBeaverを布教していたことがあります。

新しく入社してきたメンバーには必ず「DBeaverが良いよ」と声かけをしていましたが、 選ばれるのはDataGripかTablePlusでした。

メンバーがDBeaverを受け入れてくれなかった最大の理由は、「UIが馴染みにくい」ということでした。

たしかにDBeaverのデザインは、1画面に多くの導線を配置しているので、初見ですぐに馴染むのは難しいのかもしれないです。

実際、私自身が使い始めた頃はかなり苦戦しましたし、当時はこのデザインにかなり抵抗感を感じていました(笑)

その点、DataGripやTablePlusは、画面構成がシンプルで直感的に操作しやすいので、多くのメンバーに利用されているのも納得できます。

▽ DataGripの画面

▽ TablePlusの画面

それでも私はDBeaverを使い続ける

一見、UIが馴染みにくく、高機能ゆえの扱いにくさがあるように思えるDBeaver。

それでも私がDBeaverを使い続ける理由は、後述する4つメリットが特に際立っていると思うからです。

コミュニティーの活発さ

DBeaverの開発は非常に活発で、コミュニティーの熱量の高さが際立っています。

新しい機能や改善が迅速に反映されるため、ツールは日々便利さを増しています。問題が発生した場合でも、バグ修正が速やかに行われるため、安心して利用を続けることができます。

さらに、コミュニティーが活発であることから、突然開発が止まるといった心配が少なく、長期的にこのツールを活用することが可能です。

直近の開発状況はGitHubで確認できるので、興味のある方はぜひご覧ください。

github.com

すべてをカバーできる多機能っぷり

DBeaverは単なるSQLクライアントツールにとどまらない、多機能さが大きな魅力です。

対応するデータベースの種類が非常に豊富で、主要なリレーショナルデータベースだけでなく、NoSQLデータベースやクラウドデータベースにも対応しています。 一つのツールでさまざまなデータベースを操作できるため、環境ごとに別のツールを用意する必要がありません。

また、GUIによる直感的な操作から、SQLエディタでの高度なクエリ作成、ER図の生成、データのインポート・エクスポート、さらにジョブスケジューリングやカスタムプラグインの利用など、開発者の幅広いニーズに応える機能が揃っています。

そのため技術スタックが変化しても、DBeaverならその都度ツールを見直す必要がありません。この柔軟性により、DBeaverは長期的なキャリアのパートナーとなります

充実した情報量による安心感

DBeaverの大きな強みは情報量の豊富さにあります。

冒頭でも少し触れたように、DBeaverをすぐに使いこなせる人はそう多くはないでしょう。

しかし、DBeaverを使う人は多く存在するため、Google検索をすれば、やりたいことを詳しく解説しているブログや記事が簡単に見つかります。 さらに、公式ドキュメントも非常に丁寧に作られており、迷うことなく操作方法や設定方法を理解できます。

使いはじめは慣れることが少し大変かもしれませんが、充実した情報のおかげで初心者でも安心して使えますし、上級者にとってはさらに自分好みにすることだってできます

最強のコストパフォーマンス

DBeaverの最大の魅力は、ほとんどが無料で使えるという点です。

開発者にとって、ツール選びは生産性に直結する重要な決断です。

確かに、開発生産性を向上させるための投資は価値がありますし、開発生産性により大きな利益を享受できることもあります。

しかし、費用はかからないに越したことはありません。

特に、限られた予算で運営されるプロジェクトやベンチャー企業においては、ツールのコストを抑えることは非常に大きな意味を持ちます

DBeaverはその期待に応える存在です。 無料でありながら、多機能で高い信頼性を誇るこのツールは、予算を気にせず導入できるだけでなく、その分浮いたコストを他の重要な投資に回すことができます。

「高品質 × 無料」を提供してくれるDBeaverは、まさに開発者の理想的な選択肢といえるのではないでしょうか。

余談: DBeaverの読み方について

「DBeaver」の読み方について、私はずっと「ディービーエバー」と呼んでいましたが、正しくは「ディービーバー」と読むようです。

公式サイトにビーバーのマスコットがいる理由が長い間わからなかったのですが、名前とかけていたのですね。

まとめ

DBeaverの良さについて、思うままに書き連ねてみました。

もちろん、他のツールにはそれぞれの魅力があり、DBeaverにも賛否両論があるのは承知の上です。

それでも、私はその多機能性や無料で使える利便性、そして日々進化を続ける開発コミュニティーの力強さを信じて、DBeaverを使い続けたいと思います。

さいごに

現在プレックスではソフトウェアエンジニア、フロントエンドエンジニア、UIデザイナーを募集しています。

とても働きやすい環境なので、一緒に働いてみたいと思った方がいましたら、是非ご連絡をお待ちしています!

dev.plex.co.jp