Next.jsでReact Iconsを使うと開発サーバー(next dev)が重いので対策を入れた

株式会社リゾーム 業務ソリューション事業グループの岡部です。BOND WORKSのフロントエンドエンジニアをやっています。

BOND WORKSのフロントエンドはNext.jsで開発しており、アイコンライブラリとしてReact Icons(react-icons)を採用しています。

React IconsにはFont Awesome、Material Design Icons、Bootstrap Iconsなどをはじめとするメジャーなアイコンパックが多く含まれているため、個々のアイコンライブラリを別々にインストールする必要がありません。またReactコンポーネントとして提供されているため簡単に使えて便利です。

しかしNext.jsでReact Iconsを使用すると、開発サーバー(next dev)の実行時にパフォーマンスが著しく悪化する問題が発生しました。今回はこの問題について調査を行い、また暫定的ではありますが対策を入れたのでご紹介します。

TL;DR

  • Next.jsでReact Iconsを使うと next dev が重い
  • インポートの最適化を行う optimizePackageImports が効かず、アイコンパック内の全コンポーネントがバンドルされるのが原因
  • アイコンコンポーネントを @react-icons/all-files から個別にインポートすることで対策が可能

環境

  • Next.js 15.5.12 (App Router, Webpack)
  • React Icons 5.5.0

Next.jsとReact Iconsを組み合わせたときの問題点

前提知識:optimizePackageImports とは

Next.jsにはインポートを最適化する optimizePackageImports という機能があります。Vercelの解説記事によると、以下のようにバレルファイルからのインポートを検出し、モジュールを直接インポートする文に置換するそうです。コンパイル時に行われるので、バンドラにバレルファイルを解析させてTree shakingするよりも確実かつ低コストとのこと。

/* optimizePackageImports による最適化のイメージ */
// Before
import { module2 } from 'my-lib'
// After
import module2 from 'my-lib/module2'

React Iconsが最適化されない!

しかしReact Iconsでは optimizePackageImports が効かないみたいです(2026年2月現在)。公式ドキュメントには react-icons/* に対応していると書かれているのですが…🤔

下記Issueにあるように、アイコンを1つインポートしただけでアイコンパック全体がバンドルに含まれてしまいます。

最適化が効かない原因ですが、React Iconsは各アイコンコンポーネントを個別ファイルで提供していないため、import文を置換できないのだと思われます。

// これを最適化したいが…
import { IconName } from 'react-icons/md'
// ❌単体ファイルが存在しないのでこのように書けない!
import IconName from 'react-icons/md/IconName'

では、インポート元のファイルはどのような構造になっているのでしょうか?react-icons/md/index.mjs を見てみると、関数コンポーネントが直接定義・エクスポートされていることが分かります。1コンポーネント=1アイコンなので、Material Design Iconsパックに対応するこのファイルでは実に約4300個もの関数がエクスポートされ、ファイルサイズも2 MBを超えます。

// node_modules/react-icons/md/index.mjs より抜粋
export function Md123 (props) {
  return GenIcon({ /* 省略 */ })(props);
};
export function Md3dRotation (props) {
  return GenIcon({ /* 省略 */ })(props);
};
export function MdAbc (props) {
  return GenIcon({ /* 省略 */ })(props);
};
// ... (exportが4000個以上ある)

つまり、React Iconsにおけるアイコンパックごとのindexファイルは、厳密にはバレルファイルではないということですね。

開発体験の悪化

大量のexportを含むファイルは、ビルド時や実行時にパフォーマンスの問題を引き起こします。

本番環境ではビルド時の負荷こそ高いものの、Tree shakingが行われるため、実行時のパフォーマンスに問題はありませんでした。

しかし開発サーバー(next dev)ではTree shakingが行われないため、巨大なJavaScriptファイルをブラウザが読み込む必要があり、CPUに高い負荷がかかっていました。メモリの消費も激しかったです。特に複数のアイコンパックを参照している場合は顕著で、ページの初回表示に30秒~1分近くかかるなど、快適に開発できる状態ではありませんでした。

Issueに目立った動きがないのでしばらく修正は来なさそうですし、他のアイコンライブラリへの切り換えにも時間がかかります。そこで、Issue内のコメントを参考にして、@react-icons/all-files パッケージからアイコンを個別にインポートするという暫定対策を入れてみることにしました。

暫定対策:@react-icons/all-files からアイコンを個別にインポートする

React Iconsには、アイコンを個別のファイルに分けて提供する @react-icons/all-files パッケージが存在します。移行の際には以下のようにimport文を置き換える必要があります。

/* 置換例 */
// Before
import { MdMoreVert, MdPrint } from 'react-icons/md';

// After
import { MdMoreVert } from '@react-icons/all-files/md/MdMoreVert';
import { MdPrint } from '@react-icons/all-files/md/MdPrint';

1. @react-icons/all-files を導入する

肝心の @react-icons/all-files パッケージですが、npmの公式レジストリで公開されていません。下記Issueで言及されていますが、大量のアイコンを含む関係でファイル数が多くなりすぎて公開できないそうです。

そこで、当該Issue内のコメントにあるように、GitHubリポジトリのReleasesで配布されているものを使います。

# 5.5.0 を npm でインストールする例
npm install @react-icons/all-files@https://github.com/react-icons/react-icons/releases/download/v5.5.0/react-icons-all-files-5.5.0.tgz

2. import文を置換する

次にimportの置換を行います。

/* 置換例 (再掲) */
// Before
import { MdMoreVert, MdPrint } from 'react-icons/md';

// After
import { MdMoreVert } from '@react-icons/all-files/md/MdMoreVert';
import { MdPrint } from '@react-icons/all-files/md/MdPrint';

import文は以下のように複数行で記述される場合もあり、単純な文字列置換では対応が難しいです。

// 1行の場合
import { MdMoreVert, MdPrint } from 'react-icons/md';

// 複数行の場合
import {
  MdKeyboardArrowLeft,
  MdKeyboardArrowRight,
  MdKeyboardDoubleArrowLeft,
  MdKeyboardDoubleArrowRight,
} from 'react-icons/md';

そこで、AST(抽象構文木)を扱うことができる jscodeshift を使ってCodemodを作成することにしました。

Codemodの処理の流れは以下のようになります。

  1. react-icons/* からのimport文を探す

    例だと import { MdMoreVert, MdPrint } from 'react-icons/md' の部分

  2. import文からアイコンパック名を取得

    例だと 'react-icons/md'md の部分

  3. importされている識別子を取得

    例だと MdMoreVertMdPrint の2つ

  4. 2, 3で得た情報を元に個別のimport文を作成

    例だと以下の2つのimport文を表すオブジェクトを作成

    • import { MdMoreVert } from '@react-icons/all-files/md/MdMoreVert'
    • import { MdPrint } from '@react-icons/all-files/md/MdPrint'
  5. 実際のソースファイルを、元のimport文を置き換える形で書き換える

また、optimizePackageImports の問題が修正されたときに戻せるよう、逆の置換をするCodemodも作成しました。

改善を確認🚀

対策を入れた上で next dev を動かしてみると、改善前は表示に30秒以上かかっていたページが10~15秒ほどで表示されました!PCの負荷も体感できるレベルで軽減され、開発体験が向上しました🎉

念のためバンドルアナライザを使い、余計なファイルや巨大なIndexファイルの読み込みが無いことも確認しました。

おわりに

React Iconsはとても便利なライブラリですが、利用時は注意が必要です。

問題を調査し対策する中で、たくさんの学びがありました。

  • バレルファイルの問題点
  • Next.jsがどのようにインポートを最適化するか
  • jscodeshiftの使い方
  • などなど…

似たような状況で困っている方の参考になれば幸いです。

HanamiとRLSを活用したマルチテナントデータ管理

株式会社リゾーム 業務ソリューション事業グループの藤岡です。

弊社ではショッピングセンター向けの製品を自社開発しており、私は BOND WORKS という製品の開発に携わっています。 この製品はマルチテナントアプリケーションであり、テナントごとにデータを分離する必要があります。

今回は、このマルチテナントデータを安全に扱うために、どのような方法を採用したのかをご紹介します。

開発情報

マルチテナントデータを扱う方法

マルチテナントのデータを扱う際に最も気を付けなくてはならないのは、 他テナントへのデータ漏洩 です。
一般的な分離手法には以下の3つがありますが、それぞれにメリットとデメリットがあります。

インスタンス分離

テナントごとにデータベースインスタンスを立てて分離する方法です。

メリット
  • 物理的に分離されるため最高レベルの安全性
デメリット
  • コネクション管理の複雑化
  • マイグレーション適用時間の増加
  • インフラ運用コストの増加

データベース分離

テナントごとに異なるデータベースを作成する方法です。

メリット
デメリット

テーブル共有

同一テーブルに複数テナントのデータを格納する方法です。
テーブルに tenant_id のような識別子を持たせ、クエリ時に必ず tenant_id を検索条件に含めます。

メリット
デメリット
  • WHERE句に tenant_id を書き忘れると、他テナントのデータが見えてしまうリスクがある

テーブル共有方法で安全に分離するには

インスタンス分離やデータベース分離は、初期のうちは問題ないものの、テナントが増えると負債になる可能性があります。

そのため、弊社ではテーブル共有方式を採用しましたが、この方式には2つの課題があります。

  1. アプリケーションの実装ミス
    クエリに WHERE tenant_id = ? を追加し忘れるという些細なミスが、大規模な情報漏洩に直結します。
    人間が書く以上、このミスを100%防ぐことは不可能です。

  2. 直接データベースを操作する時のリスク
    アプリケーション側の制御でテナントのデータ制御を行う仕組みを実装しても、運用調査などでエンジニアがGUIツールやSQLコンソールからデータベースを直接操作する際は、アプリケーション側の制御は一切効きません。

これらの課題を解決するのが、PostgreSQLの Row Level Security (RLS) です。

RLSとは

PostgreSQLの Row Level Security(行レベルセキュリティ)は、テーブルの各行へのアクセスをデータベースレベルで制御する機能 です。
RLSを有効にすると、テーブルへのアクセス時に定義済みのポリシーが必ず適用されるため、アプリケーション側でWHERE句を書き忘れても、データベース側で確実にフィルタリングされます。

www.postgresql.jp

RLSの適用方法

テーブル作成のマイグレーションで、RLSの設定を追加します。

-- テーブル作成
CREATE TABLE tenants (
  id SERIAL PRIMARY KEY,
  name VARCHAR(128) NOT NULL,
);

CREATE TABLE books (
  id SERIAL PRIMARY KEY,
  tenant_id INTEGER NOT NULL REFERENCES tenants(id),
  name VARCHAR(128) NOT NULL,
);

-- パフォーマンスのためインデックスを作成
CREATE INDEX idx_books_tenant_id ON books(tenant_id);

-- RLSを有効化
ALTER TABLE books ENABLE ROW LEVEL SECURITY;

-- テーブルオーナーにもRLSを適用
ALTER TABLE books FORCE ROW LEVEL SECURITY;

-- ポリシーを作成
CREATE POLICY tenant_policy ON books
  FOR ALL
  TO PUBLIC
  USING (tenant_id = current_setting('app.tenant_id', TRUE)::integer);

セッション変数でテナントIDを設定する事で、設定したテナントIDのデータのみにアクセス出来ます。

-- テナントID=123のデータのみアクセス可能にする
SET app.tenant_id = 123;

-- この後のクエリは自動的にtenant_id=123でフィルタリングされる
SELECT * FROM books;  -- tenant_id=123のデータのみ返る

RLSの動作確認

実際にRLSが機能しているか確認してみましょう。

-- テナントID=1のデータを挿入
SET app.tenant_id = 1;
INSERT INTO books (tenant_id, name) VALUES (1, 'Ruby入門');

-- テナントID=2のデータを挿入
SET app.tenant_id = 2;
INSERT INTO books (tenant_id, name) VALUES (2, 'Hanami入門');

-- テナントID=1として検索
SET app.tenant_id = 1;
SELECT * FROM books;
--  id | tenant_id |   name   
-- ----+-----------+----------
--   1 |         1 | Ruby入門
-- (1 row)  <-- テナント2のデータは見えない

-- テナントID未設定で検索
RESET app.tenant_id;
SELECT * FROM books;
--  id | tenant_id | name 
-- ----+-----------+------
-- (0 rows) <-- どのデータも見えない

このように、WHERE句を書かなくても自動的にテナント分離が行われます。

Hanami / ROM-rb でRLSの自動適用

RLSの強力さは分かりましたが、開発者が毎回 SET app.tenant_id を手動で実行していては、結局「書き忘れ」のリスクが残ります。
そのため弊社では、HTTPリクエストごとに異なるテナントIDを保持するために RequestStore を利用し、Sequelのコネクション管理部分にフックを差し込みました。

github.com

# RLSを適用するSequel拡張
module Sequel
  module RLSConnectionManager
    def synchronize(server = nil)
      super(server) do |conn|
        # リクエストスコープからテナントIDを取得
        tenant_id = RequestStore.store[:tenant_id]
        conn.exec("SET app.tenant_id = #{tenant_id}")
        yield conn
      end
    end
  end
end

これにより、HTTPリクエスト時にテナントIDをRequestStoreに保存しておき、コネクション取得のたびにSETを実行することで、以降のDBアクセスでは自動的にRLSが適用されます。

リクエスト処理の流れ

  1. HTTPリクエスト受信
  2. HanamiのミドルウェアでURLをパースしてテナントIDを特定
  3. RequestStore[:tenant_id] = 特定したテナントID
  4. DBクエリ実行時、自動でSET app.tenant_id = XXXを実行
  5. PostgreSQLがRLSポリシーに基づいてフィルタリング
  6. 該当テナントのデータのみ返却

まとめ

リクエスト毎にRLSの設定を自動で行える仕組みを構築したことで、テーブル共有方式のメリットを維持しつつ、アプリケーション層でのデータの安全性を堅牢に実現できました。

Railsと違い、HanamiにおけるRLS適用事例は少なく、実装には苦労しましたが、本記事が Hanami + ROM-rb でマルチテナントアプリケーションを開発している方の参考になれば幸いです。

新人がモブレビューに参加して感じたメリット

株式会社リゾーム 業務ソリューション事業グループの水谷です。

私は BOND WORKS という製品のフロントエンド開発を担当しています。 私の所属する開発チームでは、コードレビューの一環として「モブレビュー」を取り入れています。 新人エンジニアである私は、チーム参加後からこのモブレビューに参加しており、気づけば約1年が経ちました。 今回は、私のチームでどのようにモブレビューを行っているのかを簡単に紹介しつつ、参加し始めた当時を振り返りながら、新人の立場でどのようなメリットを感じていたのかをまとめます。 本記事が、弊社に興味を持っている方にとって、現場の開発の雰囲気を知る一例になれば幸いです。

モブレビューの概要

モブレビューとは?

モブレビューとは、複数人で同時に同じコードを見ながら、会話をしつつレビューを進める取り組みです。 だれか一人がレビューする場合と比べて、一人では気づきにくい点に気づけたり、レビューの中で得られた気づきや決定した方針をチーム内で共有しやすいといった良さがあります。

私のチームでは、週に2回、各1時間の時間を設け、定期的にモブレビューを行っています。

モブレビューの流れ

レビュー対象は、レビュー待ちになっているプルリクエストです。

モブレビューでは進行役のメンバーが一人います。 その進行役と、プルリクエストを作成した実装者との対話を中心に、メンバー全員で画面の動きや、処理が複雑なコードを確認していきます。

その流れの中で、気づいた点があればその場で確認したり、必要に応じてコードへの指摘や改善案を共有したりします。

モブレビュー参加当初の不安

当時の私は、約半年間の学習期間を経て、開発チームに参加しました。 参加当初は、同じチームの先輩エンジニアとのモブプログラミングが中心で、一人で実装を進められるレベルではありませんでした。

また、開発に関するミーティングに参加した経験もなく、モブレビューが初めて参加するミーティングだったと思います。 そのため、初参加の際はかなり緊張しており、「ミーティングの内容についていけるだろうか」「自分の実装箇所をきちんと説明できるだろうか」といった不安を抱えていました。

実際にモブレビューに参加してみると、どうしても内容が理解できない場面はありましたし、自分の実装箇所についても、どこからどこまで説明すればよいのか分からず、難しさを感じることが多かったです。

それでも、そうした悩みや不安以上に、モブレビューを通して得られるものがありました。

新人目線で感じたメリット

担当外の機能も含めて、プロダクト全体の理解が深まる

チーム開発では、各メンバーがそれぞれ異なる機能の実装を担当します。 モブレビューでは、コードだけでなく実際に実装した画面の動きも確認するため、機能間で共通している処理や似たアクションが想像以上に存在することに気付きました。

それまでの私は、自分の担当機能の仕様を確認し、その機能について一からコーディングすることが多かったです。 しかし、どこが共通部分で、どこがその機能固有の部分なのかを知ることで、実装時に参考にできそうな箇所や、あらかじめ気をつけるべき点が自然に見えるようになりました。

もちろん、モブレビューに参加しなくても、コードをひとつひとつ確認すればこれらの情報にたどり着くことはできます。 それでも、実際に実装を担当した本人から実装の意図や考え方を直接聞ける点はとても勉強になりますし、理解のスピードや効率は大きく違うと感じています。

経験あるエンジニア同士の会話から学べる

モブレビューは対話形式で進められるため、聞いているだけでも多くの学びがあります。

個人でのレビューやモブプログラミングだと、どうしても自分と先輩エンジニアとのやり取りが中心になり、自分が疑問に思った点が会話のメインになることが多いです。

一方で、モブレビューでは、他のメンバー同士の会話を聞くことができるため、自分では意識していなかった分野や観点の知識に触れられるのが大きなメリットだと思います。

コードレビューで何を見るべきか明確になる

開発業務では、モブレビューだけでなく、個別にコードレビューを行う場面も多くあります。 自分が他のメンバーのコードをレビューする機会もあるため、その際に「どこに着目してレビューをすればいいのか」を知ることができました。

モブレビューでは先輩エンジニアが実際にどのような順番でコードを確認し、どんな観点に注目しているのかを見ることができます。 その場のやり取りを通じて、レビューの流れや視点が自然と掴めるようになり、自分自身の理解も大きく進みました。

自身の書くコードの質が上がる

反対に、自分のコードをレビューしてもらう際にも、「どのあたりを見られるのか」が分かってくるため、コーディングの段階から意識すべき点が理解できるようになります。

レビューで指摘されがちなパターンを学んだことで、指摘を受ける前に自分で問題に気づける場面が以前より増えました。

おわりに

モブレビューは最初は少しハードルが高く映るかもしれませんが、新人にとって得られる学びは多く、成長のきっかけになる場でもあります。 モブレビューに参加して1年以上経った現在では、コーディングやプロダクトについて理解できる範囲が広がり、自身の成長に繋がったと実感しています。 まだまだ学ぶことは多いですが、チームの一員として今後も成長を重ねていければと思います。

リゾームの開発OJTでの学びと成長ポイント

株式会社リゾーム 業務ソリューション事業部 企画開発課の梶谷です。

今年入社し、5月から12月頭まで開発OJTに参加しましたので、今回はその具体的な内容をご紹介します。

私はIT系の専門学校を卒業してはいるものの、コードを自力で書いた経験はあまりなく実務スキルがほとんどない状態でOJTをスタートしました。

ほぼ未経験で弊社の開発OJTに参加して成長したことや、特にありがたかった点もお伝えしますので、就職活動中の方や開発系のOJTに関わる方の参考になれば幸いです。

開発OJT概要

まずは具体的にどのような取り組みを行ったか紹介していきます。 OJTの取り組みは、大きく3つのカテゴリに分けられます。

  • 通期で継続して行った内容
  • 期間を区切って集中的に取り組んだ内容
  • 個人で自主的に進めた内容

この3つに分けて、具体的にご紹介します。

通期で取り組んだこと

OJT全体を通して継続して行った取り組みです。

昼会

毎日、昼休み後に実施しました。 当日の取り組み内容を自分の言葉で解説する形で先輩に報告し、理解度を確認してもらう時間として活用していました。 報告の中で不明点が明らかになった場合はその場で先輩から説明を受け、疑問を解消しました。

OJTフィードバック

毎週金曜日に実施しました。先輩から今週の課題に対するフィードバックを受け、理解度や改善点を確認した後、次週の予定を確認し目標を設定していました。

日報の記入

毎日、その日の作業内容や理解できたこと、課題を記録しました。記入後、先輩からアドバイスをコメントしてもらい、改善点や次の取り組みを確認しました。

メンター面談

月に一回ほど実施しました。OJTで技術面をサポートする先輩とは別に、メンターの先輩と業務やキャリア、働き方など幅広い相談ができる場で、学習以外の不安も解消できました。

期間を区切って取り組んだこと

特定の期間で集中的に進めた学習です。 製品で使用している技術を中心に学んでいます。

ZENStudy:Webアプリケーション開発(5月頭~7月半ば)

www.nnn.ed.nico

こちらの学習ツールを使ってプログラミングの基礎文法やWebアプリケーションの仕組み、アルゴリズムなどについて学び、プログラミングについての基礎知識を実際に手を動かしながら身につけました。

プロを目指す人のためのRuby入門(7月半ば~8月半ば)

こちらの本を読み弊社製品の開発言語であるRubyについて学習しました。

1冊ですべて身につくHTML & CSSとWebデザイン入門講座(7月半ば~8月半ば)

こちらの本を読んで、読書会として開発OJT参加メンバーで感想を発表することでHTMLとCSS、Webデザインについての知識を身につけました。

Railsチュートリアル(8月半ば~10月半ば)

railstutorial.jp

こちらの教材を利用し、教材を見ながら実際にサンプルアプリケーションを作成することでRailsについての基礎知識を身につけました。

RSpecによるRailsテスト⼊⾨(10月半ば~10月末)

leanpub.com

こちらの教材を進めることを通してRSpecでのテストについてやテスト駆動開発の知識を身につけました。

オリジナルアプリケーション制作(11月頭~12月頭)

設計から自分で考えてアプリケーションを実装し、作成したものを部内で発表しました。

個人での取り組み

その他、個人で取り組んでいた取り組みについても紹介します。

タッチタイピング練習

私はタッチタイピングがあまりできなかったので業務内で時間を決めてタイピングソフトでタッチタイピングの練習をしていました。

未経験としてありがたかったこと

ほぼ未経験で研修を受けてみて特にありがたかったことについて挙げていきます。

毎日の昼会で質問できる環境

OJT開始直後は「わからないところがわからない」といった状態になっていました。

昼会で自分の言葉で説明する際に初めて分からないことが発覚することが多かったのですが、先輩に理解度を把握してもらえたことで適切なサポートを受けられ、分からない部分をその場で解消できました。また、学習内容以外にも業務に関する悩みも相談できたので安心して学習に集中できました。

課題を最後までやり切る方針

学習進度が同期よりも遅れてしまうことがありましたが、途中で中断することなく最後までやり切るための時間を確保していただきました。 その結果、基礎からしっかり理解でき、後々の応用力につながりました。

定期的なフィードバック

毎週の振り返りで、今週の課題と次週の目標が明確になりました。自分の進捗を客観的に確認でき、モチベーションを維持できたと思います。

初歩的な内容から学習を始められたこと

ZENStudyやRuby入門など、基礎から段階的に学べる教材が用意されていたので未経験でも取り組みやすく、理解を積み重ねながら進められました。 また、タッチタイピングの練習時間を設けて貰えたりと業務に直結する基礎的なスキルを身につける機会があったこともありがたかったです。

一から自力でアプリ開発を行い、成果を大人数の前で発表する機会があったこと

オリジナルアプリケーション制作で読書会用の書籍と感想登録ツールを開発したのですが、設計から開発まで初めて自力で完成させることができたので貴重な経験となりました。

これまで実用性のあるアプリケーションを作ったことはありませんでしたが、実際に使用を想定したものを制作できたことで、新しい視点を得られたと思います。 また、発表という明確なゴールと制作期限があったことで、開発にメリハリをつけて取り組めました。

成長したこと

研修を通して成長できた点についても挙げていきます。

理解度を言語化し、質問できるようになった

昼会で説明する習慣により、分からない部分を明確化できるようになりました。毎日先輩に説明することで説明力と論理的な整理力が向上したと思います。

疑問をすぐ解消する姿勢が身についた

毎日の昼会で分からないところを先輩に相談できたこともあり、疑問を放置せず、早めに相談する習慣が定着しました。 学習効率が上がり、課題を着実に進められるようになったと思います。

焦りをコントロールし、着実に進める力を得た

学習進度に遅れをとってしまった際、同期との進捗差に焦りを感じても、優先度を整理しキャッチアップする方法を学びました。 理解を優先する重要性を実感し、落ち着いて学習に取り組めるようになりました。

チームコミュニケーション力の向上

毎日の昼会や週次フィードバックで、進捗共有や相談の習慣が身につきました。 チームで協力しながら進める意識が強まったと思います。

おわりに

ほぼ未経験からのスタートでしたが、OJTを通じて基礎から応用まで幅広く学ぶことができました。

「わからないところがわからない」という段階から始まったOJTでしたが、少しずつできることが増えました。 オリジナルアプリケーション制作の際に、自力で開発を進められるようになっていたことは大きな成長だと思っています。

今後は製品に関するOJTが始まり、実際の開発に関わる機会も増えていくと思います。これまでに身につけた知識を生かし、さらに成長できるよう努力していきます。

同じように挑戦する方にとって、この記事が少しでも参考になれば嬉しいです。

新卒エンジニアがリモートワークをやるうえで気づいた工夫

株式会社リゾーム 業務ソリューション事業グループの富田です。 今回は、2025年4月入社の私がリモートワークをやる上で気づいた社内での工夫、やって良かったことを紹介します。リモートワーク勤務に興味のある方や、弊社に興味を持っていただいた方の参考になればと思います。

リモートワークで気づいた工夫

私がリモートワークで気づいた、社内で行われている工夫について紹介します。

コミュニケーションの工夫

リモートワークは人と話す機会が少ないと思っていたのですが、実際は人と話す機会が多く、会議が業務時間の半分以上を占めることもありました。また、コミュニケーションの大半が通話やテキストチャットを使っているため通常以上にコミュニケーション能力が必要だと感じました。その問題の対策として、リアクションやビデオ通話が活用され、文字や音声だけでは伝わらない感情を表現でき、効果的な取り組みだと思いました。私も他の人の投稿に「いいね」をたくさん押して、自分の気持ちを伝えるようにしています。

自己管理の難しさと日報の役割

リモートでは、作業を直接確認できないため、自分で仕事やスケジュールを管理する必要があります。また、自宅で作業をしているので、仕事とプライベートの切り替えが難しいと感じるときがあります。私の部署では、朝会で予定を共有し、夕会で日報を作成してチームメンバー全員が進捗を確認しています。予定や日報を書くことで、進捗の整理ができるだけではなく、アドバイスや新しい気づきを得られ、気分の切り替えにもつながるので、とても助かっています。

孤独感を感じたときに助かること

一人で作業をすることが多いため、孤独感を感じることがあります。特にアイデアが出てこないときや、自分の作業が間違っていないか不安になるときに、孤独感を強く感じます。そんなとき、私の部署で取り組んでいる「分報」を活用しています。「分報」とはチャット上で自分の作業内容や感じていることをリアルタイムに書き込む仕組みです。これによって、自分の作業内容を客観的に整理できたり、つぶやきにリアクションをもらえることで、孤独感が和らぎ安心します。

チームメンバーのサポート

私が新人研修から配属されたときに感じたことですが、チームメンバーのサポートが強く印象に残っています。どんな質問にも丁寧に答えていただき、分からないことを分報に書き込むとアドバイスをいただけるので非常に助かりました。こうした助け合いが結局、一番大切なのかもしれないと思いました。

リモートワークでやって良かったこと

私がリモートワークで意識している所、やって良かったと思っている所を紹介します。

積極的な自己開示

『自己開示?』と思うかもしれませんが、私はこれがとても重要だと考えています。出社時と違い、自分を知ってもらえる機会が少ないため、特に新入社員はどんな人で何を考えているのかが分かりにくいです。相手にとって不安を与えないように、自己開示の機会を見つけて、自分のことを知ってもらえるように意識しました。開示する内容は、基本的になんでも良いと思います。「好きなもの」、「興味のあるもの」、「過去の経験」などポジティブに伝えました。積極的な自己開示をしたことで、共通の話題が広がり、チームメンバーとの距離が縮まりました。

適度に休憩をとる

私は作業に集中しすぎると、休憩を忘れがちになります。特に、一人で作業をしていると、つい長時間作業してしまい、その後の集中力が途切れてしまうことがあります。そうならないために、適度な休憩を取りながら、効率的に作業を進めることが大切だと思います。私の場合、少し歩いて体を動かす、カフェオレを飲む、音楽を聴くなどのリフレッシュをして、気分を切り替えています。

快適な作業環境づくり

一日の大部分を過ごすリモート環境を快適にすることは大事なことだと思っています。特にリモート環境は、自由に作ることが出来るからこそ、こだわりたい部分です。私の場合、モバイルモニターや専用の机と椅子を用意し、気分転換に音楽を聴ける環境を作っています。さらに、椅子に座り続けて疲れたときは座椅子に移動できるようにしており、こうした工夫で常に快適な状態で作業できています。

終業後の運動習慣

リモートワークでは、外出する機会が少なくなり、座りっぱなしで作業することが多くなりがちです。私も運動不足を感じました。長く健康に仕事を続けるためには、意識的に体を動かすことが大事だと思います。私は仕事終わりに、エアロバイクを使い、運動不足を解消するようにしています。エアロバイクは、ゲームや動画を楽しみながらできるので、無理なく続けられ、とてもおすすめです。

終わりに

入社してから気づいたリモートワークの工夫、やって良かったことを紹介しました。まだ入社して一年も経っていない新人ですが、これからリモートワークを始める方や興味のある方にとって、少しでも参考になれば嬉しいです。最後まで読んでいただき、ありがとうございました。今後も気づきを発信していきたいと思います。

サポート役としての新人育成体験

株式会社リゾーム 業務ソリューション事業グループの土井です。 今回は、2024年度に新人が同じ部署に配属された際、OJTトレーナーではなくサポート役として関わった経験をご紹介します。 新たにOJTに関わる可能性が出てきた方や就職活動中に弊社に興味を持っていただいた方の参考になればと思います。

新入社員研修の概要

まず最初に2024年度の新入社員が入社からどのような流れで研修を受けていったかを簡単に紹介します。

新入社員研修(4月)

ビジネスマナー、自社製品、SC業界についてなどの、今後仕事をしていくうえでの基礎的な内容を他部署の新入社員と一緒に研修を受けてもらいました。

開発部門の研修(5月~10月)

開発の業務を行っていくうえで必要なプログラミングの基礎知識や、実務で使用していく言語やフレームワークについて書籍やEラーニング教材を使用して学習してもらいました。

実務(10月~)

実際の業務にサポートをしつつ慣れていってもらいました。

OJTの概要

OJTでの取り組みは先ほど紹介した開発部門での研修以降行っていました。具体的にどのような取り組みを行っていたかをこちらも簡単に紹介します。

出社でのフォロー

所属する部署は基本的には在宅勤務でのリモートワークですが、最初の1か月間は慣れてもらうことや質問のしやすさ等を考慮して、出社での勤務とフォローを行いました。

昼会

日々の進捗、疑問点などを共有してもらうために毎日お昼休みの後に、新人、OJTトレーナーの先輩、私の3人で昼会を開催していました。特に在宅勤務になってからはコミュニケーションの場としても役立ったと感じています。

週次のふりかえりMTG

その週での取り組みのふりかえりや、次週のタスクの計画をする会を毎週金曜日の定時前1時間ほど昼会と同じく3人で行っていました。昼会とは異なり毎回レジュメを作成し、OJTでの活動全体を振り返ることで進捗や課題を整理していました。

モブプログラミング

Visual Studio CodeのLive Share機能を使用してモブプログラミング*1を実施しました。同じコードを共有して操作することで指導がしやすくなり、リアルタイムで相談しながらコーディングを進めることができました。

Live Share機能については以下リンクをご参照ください。 learn.microsoft.com

実施してよかったと思うこと

ここからは、実際にやってみて「これは良かった!」と思った取り組みをご紹介します。 OJTトレーナーではなくあくまでサポートという立場で以下2点は特にうまくいったと思うので紹介します。

先輩の負担を減らす

OJTをスムーズに進めるために、会議室の予約やteams会議の作成など雑用的な部分は実施するようにしていました。 ただ新人にもできるようになってもらう必要があるので、余裕が出てきたタイミングで予約や会議作成自体を依頼するようにしていました。

書記的な役割

週次のふりかえりMTGなどで事務的な記述を行う作業は出来るだけ実施するようにしていました。 MTGの進め方として会話をしながら書いていく場面も多く、そのような場面ではOJTトレーナーの先輩や新人に記入してもらっていました。

特に効果的だったと個人的に思っているのはモブプログラミングの際です。 モブプログラミングの進行上、どうしてもコードを記述→相談→またコードの記述といった流れになります。新人はメモを取る余裕はないと思い、裏でメモの作成を行っていました。会話をしながら作成したコード自体に加えて、こちらで作成したメモを使うことでモブプログラミングのふりかえりの助けになったのではと思います。 

反省点

振り返ってみて、改善できたと思う点が1つあります。OJTで使用するドキュメントの整理をOJTトレーナーの先輩が作成してくださっていたのですが、私も作成にもっと関わるべきだったと思います。負担の分散はもちろんですが、書記的な役割を果たすうえでドキュメント化をするところまで念頭に置いておくべきだったという反省があります。

実際の評価

今回のブログ執筆を機に改めてOJTの先輩と新人から以下のようなフィードバックを頂きました。 自ら考えて実施した「先輩の負担を減らす」こと「書記的な役割」が実際に役に立てていたようでよかったです。 一方で新人と比較的近い立場であることがメンタル面で役立っていたのは新たな気づきでした。

OJTトレーナーの先輩から

  • 会議でのメモ取りが非常に助かった
    • 話の流れや重要なポイントを記録してくれたことで、振り返りがしやすくなった
    • 教えながら同時にメモを取るのは難しいため、サポーターの恩恵を強く感じた
  • 会議予約などの率先した対応
    • 「次は○日の×時に会議室Bでやりましょうか」という会話をキャッチしてすぐに予約してくれることで、予定の抜け漏れが防止され安心できた
  • メンター的な役割
    • 年齢・キャリア的に新人の目線に近く、メンタリング的なサポートも果たしていた
  • チーム全体への効果
    • チームとしてフロントエンド技術を改めて学ぶことが出来たため、結果的にチーム全体のレベルアップにつながった

新人から

  • 入社時期の近い先輩の意見を聞けた点
    • ReactやNext.jsの学習で、過去に同じように勉強した人の目線で助言をもらえたのが良かった
    • 特にNext.jsは難しく感じていたが、「自分も難しかった」と私から一言もらえるだけでメンタル的に楽になった
  • モブプログラミングで複数の視点を得られた点
    • 「コードはどこから読むのがいいか?」といった議題で、OJTトレーナーの先輩からだけでなく複数の意見を聞けたことで、考え方を学ぶ上で非常に助かった
  • 書記・メモのサポート
    • メモを取っていたが、より詳細な手順を記録してくれていたため、聞き直しが減った
    • 新人自身で作成したメモと私が作成したメモを見比べることで、認識の間違いに気づけた場面もあった

おわりに

OJTトレーナーではなくサポート役として新人育成に関わった経験を振り返ってみると、直接教える立場ではなくとも貢献できる場面がたくさんあると感じました。周囲をサポートし、仕事のしやすい環境を整えることは、通常業務でも求められる大切な役割だと改めて感じました。 今後OJTに関わるとなるとサポートとしてではなく、OJTトレーナーとしてかかわると思うのでこの経験を活かしていければと思います。

*1:モブプログラミングは複数人で同じコードに対してコーディングを行うこと。同じコードを見ながら作業を進めるためコミュニケーションを密にとりながら作業を行える。

ActiveJobの優先度制御にNice値を使用する

株式会社リゾーム 業務ソリューション事業グループの原です。

弊社ではショッピングセンター向けの製品の自社開発を行っており、私はBOND GATEという製品の開発に携わっています。 こちらはRuby on Railsで開発しており、その中の管理者用の機能の一つとして、ログの内容をCSVファイルにして出力する機能があります。

今回は、この機能を一部の環境で使用した際に、処理が重くなり動作しなくなる……という事象が発生しました。 そのときの対応・検証についてまとめたので、似たような問題が起きた際の解決策となると思い、記事にしています。

経緯

ログ内容のCSVファイルを出力するActiveJobを実行した際に、ある環境だけメモリ使用率が100%まで達し、処理ができなくなるという状態が発生しました。

原因を調べたところ、大量のActiveRecordeachで処理しており、環境によっては100万件以上になってしまうためメモリが足りなくなってしまう……という状態でした。

これに関してはfind_eachを使うことでメモリ使用率は抑えられる……のですが、今度はCPU使用率が90%前後になっていました。 BOND GATEはAPサーバとワーカーを1台サーバーで動かしているので、ワーカーがCPUを90%も使っていたらAPサーバが動けなくなってしまいます。

この負荷への対処方法を検討していた際、当初はActiveJobのCPU使用率が高いことから、リソースの制限を加える方向で考えていました。

チームで調査を進める中で、「プロセスの優先度を下げれば、他の処理に影響を与えずに済むのでは?」というアイデアが出ました。調べてみると、Linuxには「Nice値」というプロセスの優先度を調整する仕組みがあることがわかりました。これを使えば、他の処理に影響を与えずに負荷をコントロールできそうです。

本当に効果があるのかを確かめるため、検証を行うことにしました。

Nice値の設定

webkaru.net

0を標準として、-20~19の範囲で数字を大きくするほど優先度は低く、下げると高くなります。逆にしないように注意。

ActiveJobアダプタにはsidekiqなどがありますが、BOND GATEではqueというgemを使用しています。

github.com

queのプロセスはsystemdで設定しているので、Nice値もここから設定できました。

[Service]
Type=forking
WorkingDirectory=#{current_path}
ExecStart=/opt/bondgate/bin/que_#{File.basename(deploy_to)}.sh
PIDFile=#{shared_path}/pids/que.pid
User=********
Group=********
Nice=19

これでデプロイするとqueのNice値が19になったので、ここからstress-ngコマンドを使って負荷をかけていきます。

まずはstress-ngの負荷を色々変えてみて、topコマンドでジョブ実行中のqueがどのくらいのCPU使用率になるのかを見てみます。 (COMMAND列は長くなり見づらくなったため書き換えています)

  • stress-ng 100%
    • queはほとんど使われなくなる
stress-ng --cpu 0 --cpu-load 100
    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                                     
2703937 ********  20   0   84508   7736   4016 R  93.4   0.2   1:43.14 stress-ng 100                                            
2703938 ********  20   0   84508   6220   4020 R  92.1   0.2   1:42.42 stress-ng 100                                            
2424664 ********  39  19 1745624 483748  11368 S   1.0  12.3  38:38.20 que
  • stress-ng 50%
    • queの使用率は70~80%
    • 負荷なしのときより多少使用率は減るが、あまり変わらない印象
stress-ng --cpu 0 --cpu-load 50
    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                                     
2424664 ********  39  19 1745624 483752  11368 R  78.7  12.3  40:13.35 que
2704125 ********  20   0   84508   6424   4232 S  46.8   0.2   0:52.49 stress-ng 50
2704126 ********  20   0   84508   6424   4236 R  45.5   0.2   0:52.48 stress-ng 50                                             
  • stress-ng 80%
    • queの使用率は45%前後
 stress-ng --cpu 0 --cpu-load 80
    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                                     
2704529 ********  20   0   84508   7612   3884 R  76.7   0.2   0:54.29 stress-ng 80                                             
2704530 ********  20   0   84508   7612   3888 R  75.1   0.2   0:54.32 stress-ng 80                                             
2424664 ********  39  19 1745624 483740  11368 S  42.5  12.3  43:41.81 que
  • stress-ng 80% + abコマンド
    • abコマンド(Apache Bench)を使って並列で100アクセスさせる負荷を追加でかけてみました
    • queの使用率は20~30%程度
stress-ng --cpu 0 --cpu-load 80
ab -c 100 -n 1000 https://********
    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                 
2706435 ********  20   0   84508   6300   4100 R  56.1   0.2   1:35.51 stress-ng 80                         
2706434 ********  20   0   84508   6300   4096 R  54.5   0.2   1:35.82 stress-ng 80                         
2424664 ********  39  19 1745624 484084  11368 S  22.3  12.3  45:59.07 que

その後Nice値を変えて結果が変わるのかも見てみました。reniceコマンドで実行中のプロセスのNice値を変更できます。

renice 0 -p <PID>

queの優先度が高くなるのだから、先程よりCPU使用率は上がるはずです。

しかしqueのNice値を0にしてみても特に結果が変わりませんでした。 stress-ngの負荷が高すぎてこうなるのかわかりませんが、Nice値の設定は本当に効いてるの?と少し不安になります。 そこで逆にqueのNice値を0、stress-ngのNice値を上げてみました。

こちらはreniceではなく、実行時にNice値指定すればOKです。

  • Nice値10
nice -n 10 stress-ng --cpu 0 --cpu-load 100
    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                                
3138372 ********  30  10   84508   7808   4096 R  93.0   0.2   0:25.72 stress-ng 100                                       
3138371 ********  30  10   84508   7808   4092 R  90.7   0.2   0:25.48 stress-ng 100                                       
2424664 ********  20   0 1703336 383344  10660 S  14.6   9.7  91:41.96 que
  • Nice値19
nice -n 19 stress-ng --cpu 0 --cpu-load 100
    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                                
3138439 ********  39  19   84508   8140   4164 R  82.0   0.2   0:17.98 stress-ng 100                                       
3138440 ********  39  19   84508   6360   4168 R  76.3   0.2   0:18.31 stress-ng 100                                       
2424664 ********  20   0 1703336 383304  10660 S  40.0   9.7  92:03.43 que

10、19と変えてみるとちゃんとstress-ngのCPU使用率が減り、queは増えました。 それでも大半はstress-ngが占めているので、元々の処理の重さで変わりそうですが、ちゃんと効いてそうです。 ちなみにNice値を1上げるだけだと特に変わらないように見えたので、ある程度大きく変更したほうが良いかもしれません。

queue_with_priority を設定する

これでOK……とはいかず、そもそも原因となったジョブが7~8分かけてCSV出力していたので、そこに他のジョブが入ってくるとその間は処理を待つことになります。 可能性は低いとは思いますが、CSV出力処理を連打して複数個queに登録されてしまったら、30分ほど他のジョブが捌けない、という状態になってしまいます。

幸い、このジョブは裏でCSV生成・出力処理を行い、後ほどダウンロードできるようになったらお知らせします、というものなので後回しになっても良いものです。 ということでqueに登録するジョブそのものの優先度を設定することにしました。

railsguides.jp

queue_with_priorityでジョブの優先度を設定することができます。 既に設定されているところはないかとコードを確認してみると、どうやらメール送信のジョブでqueue_with_priority(200)に設定されていたので、同様に設定してみました。

class CreateCsvJob < ApplicationJob
  # CSV出力は優先度を下げるため、200に設定
  queue_with_priority(200)

これで他のジョブより後に実行されるようになるはずです。

確認方法としてはまずこのジョブを数回queに登録しておきます。 その間に別のジョブ(今回は別のデータのzipファイル出力処理)を実行します。 優先度が同じならばCSV出力がすべて終わった後にzip出力処理が行われるはずですが、優先度を下げてるので現在のCSV出力が終わったら、残っているCSVの前にzip出力処理が実行されるはずです。 この方法で無事優先度が下がっていることを確認しました。今度こそ問題なさそうです。

まとめ

今回は優先度周りの設定をすることで処理の重いジョブを上手く捌くことができました。

私自身はこういった対応方法に疎く、「ジョブが重いなら処理をどうにかして軽くするしか無い」……という方向に行きがちなのですが、チームメンバーから対応方法についてのアプローチを頂き、今回の対応をしました。

今回の対応のように、別の視点からの解決方法がないか、探せるように知識をつけていければ良いなと思う出来事でした。