行動すれば次の現実

テック中心の個人ブログ

MUI X Data Gridでstore props等の実践に近いデータセットを扱う方法

MUI XのData Gridは、複雑なデータセットを扱うウェブアプリケーションにおいて、エクセルライクなテーブル表示を簡単に実現できる強力なコンポーネントです。大量のデータを扱いながらユーザーに高度な検索、フィルタリング、ソート機能を提供する際に特に有効です。

公式ドキュメントでは、rowsプロパティにオブジェクトの配列を設定することでData Gridを構築する基本的な方法が紹介されています。しかし、実際のアプリケーション開発では、データがこのようなフラットな形式で提供されることは少なく、より複雑なデータ構造を扱う必要があります。

例えば、データベースからのレスポンスがネストされたオブジェクト形式である場合や、異なるエンドポイントからのデータを組み合わせて表示する必要がある場合、idをキーとして使用し、その他の情報を別のプロパティから取得する方法が有効です。

実装の改善例

以下の実装例では、商品情報を別のオブジェクトとして管理し、GridColDefのvalueGetterプロパティを使用して、各行のデータを動的に取得する方法を示しています。

基本形式

// ベーシックなData Gridの設定
const rows = [
  { id: 1, productName: '幕の内弁当', sellingPrice: 500, costPrice: 300, quantity: 10 },
  { id: 2, productName: '唐揚げ弁当', sellingPrice: 470, costPrice: 260, quantity: 5 }
];

const columns = [
  { field: 'productName', editable: false },
  { field: 'sellingPrice', editable: false },
  { field: 'costPrice', editable: false },
  { field: 'quantity', editable: true }
];

<DataGrid rows={rows} columns={columns} />;

改善された実装例

// 商品情報を別に管理
const products = {
  10: { name: '幕の内弁当', sellingPrice: 500, costPrice: 300 },
  11: { name: '唐揚げ弁当', sellingPrice: 470, costPrice: 260 }
};

const rows = [
  { id: 1, productId: 10, quantity: 10 },
  { id: 2, productId: 11, quantity: 5 }
];

const columns = [
  {
    field: 'productName',
    editable: false,
    valueGetter: params => products[params.row.productId].name
  },
  {
    field: 'sellingPrice',
    editable: false,
    valueGetter: params => products[params.row.productId].sellingPrice
  },
  {
    field: 'costPrice',
    editable: false,
    valueGetter: params => products[params.row.productId].costPrice
  },
  {
    field: 'quantity',
    editable: true
  }
];

<DataGrid rows={rows} columns={columns} />;

コードの解説

この改善された実装例では、valueGetter関数を使用して、rows配列内のproductIdに基づき、productsオブジェクトから動的に商品名、販売価格、原価を取得しています。これにより、データの構造がより柔軟になり、異なるデータソースからの情報を組み合わせやすくなります。

valueGetterを使用する際は、特に大量のデータを扱う場合、計算コストを考慮することが重要です。必要に応じて計算結果のメモ化を検討し、行の仮想化を活用してパフォーマンスを向上させることができます。

おわりに

MUI XのData Gridを使用することで、複雑なデータの扱いも簡単になり、アプリケーションのUI/UXを大幅に向上させることが可能です。今回の実装例のように、valueGetterを駆使することで、より柔軟かつ効率的なデータ管理が実現できます。公式ドキュメントでさらに詳しい情報を得ることができるので、ぜひ参照してください。

【Rails】Pumaのワーカー数、スレッド数とDB接続プール数の決め方

本記事では、Pumaのワーカー数、スレッド数の決め方、およびDB接続プール数の決め方と推奨値について解説します。

ワーカー数の決め方

基本的にはCPUの数に合わせることが推奨されます。

例えば、Herokuの場合、Standard-1XではCPU数が1なので、ワーカー数を「1」に設定し、Standard-2XではCPU数が2なので、「2」に設定するのが良いでしょう。

また、シングルコアCPUを使用している場合は、マルチプロセス構成よりもマルチスレッド構成を選択する方が効果的です。マルチコア環境では、ワーカー数を増やすことでパフォーマンスの向上や負荷の分散が期待できます。

スレッド数の決め方

スレッド数に関しては、5から20の範囲が無難です。

高負荷かつ長時間にわたる処理が多い場合は、スレッド数を少なめに設定することが望ましいです。

反対に、低負荷で高頻度の処理が多い場合は、スレッド数を多くすることが効果的です。 ただし、スレッド数を増やすとメモリ使用量も増加するため、最適な数値を見つけるためには実際にテストを行う必要があります。

経験則から、10から15の範囲が無難であると考えられます。

DB接続プール数

DB接続プール数は、DBのスペックやアプリケーションで必要となる同時接続数を考慮して決定します。

初期設定としては、5から20の範囲で始め、アプリケーションの実行状況を観察しながら最適な値に調整することをおすすめします。

特に、Sidekiqなどのバックグラウンドジョブライブラリを使用している場合は、スレッド数よりもDBコネクションプールの数を多く設定しないと、以下のような接続タイムアウトエラーが発生する可能性があります。

connection from the pool within 5.000 seconds (waited 5.000 seconds); all pooled connections were in  use(ActiveRecord::ConnectionTimeoutError)

このエラーはDB接続プールの不足が原因で発生することがあります。Sidekiqのデフォルトスレッド数は10なので、DB接続プール数は11以上を設定する必要があります。

終わりに

Pumaのワーカー数やスレッド数、DB接続プール数の設定は、アプリケーションの性能と安定性に大きな影響を与えます。これらの値はアプリケーションの実行状況やユーザーからの負荷によって異なりますので、定期的な監視と適切な調整が必要です。上記のガイドラインを参考に、最適な設定値を見つけることが重要です。

参考

https://devcenter.heroku.com/ja/articles/deploying-rails-applications-with-the-puma-web-server#recommended-default-puma-process-and-thread-configuration

勉強のための勉強は無駄である

プログラミングや新しい技術の学習は、Webエンジニアにとって避けては通れない道です。しかし、「とりあえず勉強しておく」という目的のない学習は、本当に効果的でしょうか?今回は、目的意識を持って学習することの重要性について、私の経験を交えてお話しします。

勉強のための勉強の問題点

「勉強のための勉強」とは、具体的な目的や実践できる場がないまま、知識を蓄えようとすることを指します。

このアプローチの問題点は、学んだ内容が実際の作業やプロジェクトに直結しないため、知識が活かされずに忘れ去られることが多いという点です。

例えば、「将来的に使えるかもしれない」という理由だけで新しいプログラミング言語を学んでも、実際のプロジェクトでその言語を使わなければ、時間が経つにつれて学んだことの大部分を忘れてしまうでしょう。

私自身も、新しい技術やツールに興味を持ち、試しに学ぶことはありますが、それらが具体的なプロジェクトや問題解決に結びつかないと、結局その知識は頭の片隅に追いやられ、時間の経過と共に消えていくことを痛感しています。目的のない学習は、長期的な成長やスキル向上に寄与しづらいと考えます。

目的意識を持つ学習の重要性

「勉強のための勉強」の問題点は前述の通りです。目的の無い学習は、知識として定着しづらいため結果として時間を無駄してしまいます。では、どうすれば勉強の時間を有効にできるのでしょうか?答えは、目的意識を持って学ぶことです。

目的意識を持つことは、一見すると難しそうに感じられるかもしれませんが、実際はそうではありません。普段の業務の中には、学ぶべき多くの目的が存在します。例えば以下のような目的です。

仕事でハマったことへの対応: 仕事でハマってしまった事柄について、時間をかけて深掘りする時間を設ける。深掘りすることで本質を理解することで再現性が生まれ、スキルアップに繋がる。

現在使用している技術に関する知識のアップデート: 現在使用している言語やフレームワークに関する知識を磨くことで、自身が対応できる範囲が拡張される。日々のタスクのクオリティやスピードが向上する。

プロジェクトの問題点を解決する技術: プロジェクトで直面している課題に対して、既存のフレームワークにとらわれずに新しい技術などを用いて改善できないか考えるこ。機械があればその技術を実際に適用することで知識がより深まる。

このように日々の業務それ自体が目的であり、目的を達成するための手段として勉強をすることで勉強のための勉強を防ぐことができます。

勉強は量よりも質が大事

「勉強のための勉強」という言葉にもあるように、誰しも目的が曖昧なまま勉強をしてしまった経験があるかと思います。具体的な目的や実践できる場がない状態で知識を蓄える行為は知識として蓄えるのが難しいため非効率だと言うことを説明しました。

勉強をする上で、最も重要な要素は量ではなく質を高めることです。目的意識を持って学ぶことで、その場限りの情報の集積ではなく、情報を実務に生かし、問題解決の能力を磨くことができます。

勉強は、自己の成長と直結しており、キャリア形成にも不可欠です。勉強のためだけの勉強ではなく、実際の仕事や目標達成に役立てることが重要です。これが、学習の質を高め、個人の成長を最大化する鍵になると私は考えています。

Next.js 13のRoute Groupsでページごとにレイアウトを使い分ける方法

Next.jsのRoute Groupsを使用することで、ページの種類ごとに異なるレイアウトを簡単に適用することができます。

例えばログイン済みユーザー用のページ、非ログインユーザー用のページ、管理者用のページといった具合にページの種類ごとにレイアウトを分けることが可能となります。

本記事では、Route Groupsを使用したレイアウトの切替の実装イメージと注意点についてを説明します。

ディレクトリ構成

app
├─ layout.tsx ・・・①共通レイアウト
├─ (auth) ・・・ログイン済みユーザー用
       ├─ layout.tsx ・・・②専用レイアウト
       ├─ home
            ├─ page.tsx ・・・専用ページ(/home)
       ├─ account
            ├─ page.tsx ・・・専用ページ(/account)
├─ (portal) ・・・非ログインユーザー用
       ├─ layout.tsx ・・・専用レイアウト
       ├─ page.tsx ・・・専用ページ(/)

上記のような構成でディレクトリおよび、layout.tsxやpage.tsxを配置します。

このような構成を取ることで、ログイン済みユーザー用と非ログインユーザー用のレイアウトを分けることができます。

ここでポイントなのが、①共通レイアウトの存在です。 この共通レイアウトがなければ、パーシェルレンダリングが機能しなくなるため、条件によっては画面遷移時にFull Page Reloadが発生してしまうことがあります。

各種レイアウトのサンプル

①共通レイアウト

共通レイアウトではシンプルにhtmlタグとbodyタグのみを構成します。こうすることで最低限htmlとbodyタグが再レンダリング対象外になり、Full Page Reloadを防ぐとこができます。

import './globals.css'
import {Inter} from 'next/font/google'
const inter = Inter({subsets: ['latin']})

export const metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

export default function RootLayout({
                                     children,
                                   }: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
    <body className={inter.className}>
    {children}
    </body>
    </html>
  )
}

②専用レイアウト

対して専用レイアウトでは、htmlタグとbodyタグを含めない構成にします。

import Navigation from "@/app/(auth)/navigation";

export default function RootLayout({
                                     children,
                                   }: {
  children: React.ReactNode
}) {
  return (
    <Navigation>
      {children}
    </Navigation>
  )
}

Next.jsとRails APIのMonorepo管理とDockerを使った開発環境構築

フロントエンドにNext.js、バックエンドにRailsのAPIモードを採用した構成において、一つのリポジトリで管理するMonorepoの環境をDockerを利用して開発環境を構築する方法について解説します。

環境情報

  • Next.js 13.4.7
  • Rails 7.0.5
  • Postgresql 15.3
  • Docker 20.10.12
  • docker-compose 1.29.2

セットアップ前のProcjectの構成

.
├── front
       ├── Dockerfile
├── api
       ├── Dockerfile
       ├── Gemfile
       ├── Gemfile.lock # 空ファイル
       ├── entrypoint.sh 
├── docker-compose.yml

docker-compose.ymlの定義

version: "3.9"
services:
  front:
    build:
      context: ./front/
      dockerfile: Dockerfile
    volumes:
      - ./front/app:/usr/src/app
    command: 'yarn dev'
    ports:
      - "8000:3000"
  api:
    tty: true
    build:
      context: ./api/
      dockerfile: Dockerfile
    ports:
      - 3000:3000
    volumes:
      - ./api:/app
      - bundle:/usr/local/bundle
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    depends_on:
      - db
  db:
    image: postgres:15.3
    volumes:
      - postgres:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    environment:
      POSTGRES_USER: myapp
      POSTGRES_PASSWORD: password
      POSTGRES_DB: app_development
      TZ: "Asia/Tokyo"
      POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --locale=C"
volumes:
  bundle:
  postgres:

front/Dockerfileの定義

FROM node:20.3.1
ENV TZ Asia/Tokyo
WORKDIR /usr/src/app

api/Dockerfileの定義

FROM ruby:3.2.2
ENV LANG=C.UTF-8 \
  TZ=Asia/Tokyo
WORKDIR /app
RUN apt-get update -qq && apt-get install -y postgresql-client

COPY Gemfile Gemfile
COPY Gemfile.lock Gemfile.lock

RUN bundle install

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

CMD ["rails", "server", "-b", "0.0.0.0"]

api/Gemfileの定義

source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby "3.2.2"

gem "rails", "~> 7.0.5"

api/entrypoint.shの定義

#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"

front環境の構築

cd front
# next.jsのセットアップ
docker-compose run --rm front yarn create next-app .
# frontサービスのみ起動
docker-compose up front

http://localhost:8000/ にアクセスして下記のようなウェルカム画面が出力されれば完了です。

api環境の構築

cd api
# Railsのセットアップ
docker-compose run --rm --no-deps api bundle exec rails new . --api --database=postgresql

国際化とタイムゾーンの設定

日本をベースにしたサービスの場合、下記を追加しておきます。

config/application.rb

module App
  class Application < Rails::Application
    config.load_defaults 7.0
    config.api_only = true
    # 下記を追加
    config.time_zone = 'Tokyo'
    config.active_record.default_timezone = :local
    config.i18n.default_locale = :ja
  end
end

ホスト許可の設定

development.rbに以下の設定を追加します。これを追加することでapiホストからのリクエストができるようになります。

config/environments/development.rb

Rails.application.configure do
・・・
  config.hosts << "api"
end

データベースセットアップ

セットアップが完了したら設定ファイルをconfig/database.ymlを以下のように書き換えます。

api/config/database.yml

default: &default
  adapter: postgresql
  encoding: utf8
  host: db
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: myapp
  password: password

development:
  <<: *default
  database: app_development

test:
  <<: *default
  database: app_test

production:
  <<: *default
  database: app_production
  username: myapp
  password: <%= ENV["APP_DATABASE_PASSWORD"] %>

その後、データベースを構築して、起動確認を行います。

docker-compose run --rm api rails db:create
docker-compose up

frontは http://localhost:8000/ でNext.jsの画面が出力され、apiは http://localhost:3000/ で下記のような画面が出力されれば作業は完了です。

セットアップ後のProcjectの構成

バージョンによって違いはあるかもしれませんが、最終的にはプロジェクトは以下のような構成になります。

ChatGPTに負けない技術ブログの書き方

ChatGPTのような言語モデルが登場したことで、情報の収集や問題解決が容易になりました。しかし、それによって技術ブログの存在意義が低下したわけではありません。むしろ、技術ブログが果たすべき役割がさらに重要性を増したと私は考えます。

ググり力がなくても問題が解決できる時代に

ChatGPT登場前は、自分で問題を解決するためには、疑問点を分解して検索エンジンで検索するための「ググり力(ググラビリティ)」が必要でした。しかし、ChatGPTが登場したことで、検索エンジンでの検索に頼る必要が少なくなり、疑問点をそのまま入力すれば答えが得られるようになりました。

これによって、ググり力がなくてもある程度の問題であれば解決できるようになったと言えます。

ChatGPTは個別事象の大量製造機

技術ブログは、個別の技術的な問題や事象について、深堀りした情報を提供しているケースが多くあります。そのため、読者は自分自身の問題について類似の技術ブログを検索し、問題解決のための情報を得ることができます。

全く同じ事象というのはなかなかありませんので、ブログの内容の本質を理解して応用する力が必要とされていました。

しかし、ChatGPTが登場したことで、個別の事象について最適な回答を瞬時に提供することができるようになりました。今や、読者が抱えている問題についての技術ブログが存在しなくても、ChatGPTが最適な回答を提供する世界になりました。そのため、ChatGPTは個別事象の大量製造機と言えるかと思います。

これからの技術ブログの立ち位置

ChatGPTの登場により、情報収集や問題解決に必要な知識がより簡単に得られるようになりました。しかし、技術ブログはまだまだその存在意義があります。今後の技術ブログの立ち位置について、以下に私の考えを示します。

技術の深堀りや現場で活かされる実践的な内容

自分自身が体験して技術的な事象や問題について、深堀りした内容や付随情報を提供することで、ChatGPTとは異なる価値を提供できると考えます。現場レベルの実践的なノウハウや、いろいろな事象をつなぎ合わせた複合的な内容について発信することで、読者にとって有益な情報を提供することができます。

例えば、自分でWebサイトを作成するときに、JavaScriptを利用してスムーズな動きを実現する方法について知りたいとしましょう。このような問題に直面した場合、技術ブログは大きな助けになります。

JavaScriptについて十分な知識がなくても、技術ブログを通じて、実践的な手順や具体的な例を知ることができます。さらに、技術ブログは、読者にとって重要な実践的なアドバイスを提供することもあります。これらの情報は、プログラマーが日々の作業において直面する問題を解決するための貴重な情報源となります。

1つのテーマについてのシリーズ記事

技術ブログは、1つのテーマについて、複数回にわたってシリーズ記事を書くことで、より深く掘り下げた内容を提供することができます。例えば、入門書のようにターゲットを絞ったシリーズ物にすることで、読者にとってより興味深く、わかりやすい形で情報を提供することができます。

セルフブランディングとして活用

技術ブログをセルフブランディングのために活用することができます。自分自身のポートフォリオとして、自分が行ったプロジェクトや、取り組んだ技術についてブログにまとめることで、自分自身のスキルアップや就職活動の際に有益な情報を提供することができます。

また、自分自身の作業ログとして技術ブログを活用することで、自分自身が行ったことを記録することができます。

システム開発に関する思想的な内容

自分自身が考えるシステム開発に関する思想的な内容を発信するために、技術ブログが有効な手段と考えられます。思想には正解はありませんので、自分自身の考え方を発信することができます。

システム開発には、開発者の思想や哲学が大きな影響を与えるため、技術ブログを通じて、開発者の思想的な観点や、開発におけるベストプラクティスなどについて情報提供することができます。

例えば、システム開発におけるアジャイル開発やDevOpsなどの手法、コードの品質や保守性、可読性についての考え方など、より思想的な内容についても技術ブログを通じて発信していくことが有効です。

さいごに

ChatGPTの登場により、個別の事象に関する記事ばかりを書いていると、この先はなかなか読まれなくなるのではないかと考えます。

しかし、技術ブログは、単に個別の事象を解決するための情報提供だけでなく、自分自身が学んだ知識や経験を整理し、スキルアップやキャリアアップにつなげることができる貴重な手段です。また、他の人に役立つ情報を提供することで、業界の貢献につながると考えます。

技術ブログは自己成長や社会貢献に繋がる重要な存在であるため、私は今後も積極的に記事を書いていきたいと思います。

ファイルを指定の件数ごとに分割するLinuxコマンド

10,000行あるrow.csvを1,000行ごとに分割してファイルを生成する例を説明します。

コマンド例

split -l 1000 -d rows.txt rows_
  • -lオプションには分割する行数を指定します。
  • -dオプションを指定すると、ファイル名の末尾につく数字の連番が付与されます。
  • rows_の部分にはファイル名の接頭辞を指定します。この接頭辞に数字の連番が付与されます。

実行後すると以下のファイルが生成されます。

rows_00
rows_01
rows_02
rows_03
rows_04
rows_05
rows_06
rows_07
rows_08
rows_09

このままでは拡張子が付いていないので、下記のスクリプトを実行してcsvの拡張子を付与します。

rename.sh

#!/bin/bash

for f in "$1"*
do
    mv -- "$f" "${f}.csv"
done
sh rename.sh rows_

実行後すると以下のファイルに変更されます。

rows_00.csv
rows_01.csv
rows_02.csv
rows_03.csv
rows_04.csv
rows_05.csv
rows_06.csv
rows_07.csv
rows_08.csv
rows_09.csv