おいしい健康 開発者ブログ

株式会社おいしい健康で働くエンジニア・デザイナーが社内の様子をお伝えします。

1行の変更でWebサービスの速度を5倍にした話

こんにちは。おいしい健康エンジニアの近藤です。

おいしい健康では、iOS、Web(API / フロントエンド)、インフラ、SEOなど幅広く担当しています。 今回は掲題の通り、たった一行の変更でおいしい健康のWebサイトのパフォーマンスを大きく改善することが出来ましたので、ご紹介させていただきます。

PageSpeed Insightsとは

皆さんは、PageSpeed Insightsはご存知でしょうか?

PageSpeed Insightsとは、Googleが提供しているWebサイトのパフォーマンス(速度)を測定するためのツールです。 無料で利用することができ、改善事項なども細かく提示してくれますので、利用したことが無い方は、ぜひ一度お試しください。

developers.google.com

おいしい健康の速度スコア

お恥ずかしながら、おいしい健康のPageSpeed Insightsの速度スコアはとても低くなっていました。

私の方で https://oishi-kenko.com/ に対して、実行した結果は以下の通りです。

f:id:oishi-kenko:20190411154056p:plain

100点満点で、0〜49が遅いという評価のため、おいしい健康の11というスコアはとても遅いという位置づけでしょうか。

ただし、この速度スコアには少しカラクリがあり、モバイルのパフォーマンスについては、3G回線利用時の通信速度をベースとしたスコアとなっています。

そのため、4G回線を利用している場合は、おいしい健康の表示にそこまで遅いとは感じないはずです。 日本では、現在は4G回線が主流で、2020年には、NTTドコモKDDIソフトバンク楽天モバイルで、5Gの商用通信サービスが開始される見込みとの報道もありました。

しかし、日本以外ではまだ3G回線以下の速度の地域も多いことや、ページ読み込み速度はSEOの指標の一つになっていることもあり、可能であれば速度改善をしたいところです。

速度スコアの改善項目

PageSpeed Insightsで主に以下の項目が指摘されていました。

No 改善できる項目 短縮できる時間(推定)
1 レンダリングを妨げるリソースの除外 7.93s
2 テキスト圧縮の有効化 7.05s
3 キー リクエストのプリロード 6.12s
4 オフスクリーン画像の遅延読み込み 3.75s
5 次世代フォーマットでの画像の配信 1.95s
6 使用していない CSS の遅延読み込み 1.8s
7 効率的な画像フォーマット 0.9s

多くの改善項目がありますが、前述の通り、モバイルの速度スコアは3G回線利用時の通信速度がベースとなっており、通信容量の影響を非常に多く受けます。 そこで、通信容量の削減を行うことで、全体的に表示速度を改善できるのではないかという仮説を立て、先ずは、テキスト圧縮の有効化を実施することにしました。

テキスト圧縮の有効化

おいしい健康では、 AWS の S3 に CSS や JS などのアセットを配置し、CloudFront経由で配信しています。 今回はこれがgzipで圧縮されていなかったため、PageSpeed Insightsで改善項目として挙がっていました。

CloudFrontで配信しているファイルの圧縮は非常に簡単です。

docs.aws.amazon.com

おいしい健康では、Cfdefというgemを使い、CloudFrontの設定内容をDSLとして管理しているため、今回の対応内容としてはDSLの変更のみです。

実際の変更内容は以下の通りです。

-    compress false
+    compress true

タイトルの通り、diffは1行だけですね。

改善後の速度スコア

気になる改善後の速度スコアはこちらです。

タイトル通り、1行の変更で速度スコアが11から58と、ほぼ5倍になりました。

さいごに

この記事では、テキスト圧縮の有効化の対応内容をご紹介させていただきましたが、これ以外にもいくつか比較的簡単に改善できる項目もあるため、 皆さんも一度確認してみてはいかがでしょうか。

今回は以上です。

git commitする前にktlintでフォーマットチェックをかける

こんにちは。おいしい健康エンジニアの小林です。

昨年12月に入社しまして、Androidアプリの開発をしています。おいしい健康ではAndroidアプリをKotlinで開発しておりフォーマッターにはktlintを使用しています。

ktlintとは

Android公式のスタイルガイドに基づいてコードチェックをしてくれるKotlin用のlinterです。自動でフォーマットしてくれる機能も用意されています。

ktlint.github.io

ktlintはGradleプラグインが提供されているためAndroidプロジェクトへの導入が簡単です。

おいしい健康ではPullRequestのコードに対してCircle CI上でktlintを走らせるようにしています。

レビュワーの労力を、コードフォーマットのレビュー(本質的ではないこと)に使いたくないで非常に便利です。

Push前にもlintしたい

コードフォーマットの修正漏れがあっても気づけるようにCircle CI上でktlintを実行しているのですが、

  • ほぼ毎回Push後にフォーマットの修正漏れに気づく。
  • ./gradlew ktlintFormatかけてまたPush!
  • あ、ビルド渋滞が……
  • 修正漏れ分のビルド中止をするの面倒。
  • push前にフォーマットかけていれば、ビルドを無駄に走らせなくてもよかったのに。

と言った点から、ローカルでも自動チェックしたいなと考えました。 ./gradlew ktlint って叩けばチェック可能なんですが、コード書く度に手動で叩きたくないので(叩くの忘れるので)、何かをトリガーに自動化したいです。

Commit前にlintする

Command Line版のktlintに用意されてました。

$ ktlint --install-git-pre-commit-hook

と実行すると自動でpre-commitファイルが作成されます。

すでにpre-commitを使用している場合は、

git diff --name-only --cached --relative | grep '\.kt[s"]\?$' | xargs ktlint --relative .
if [ $? -ne 0 ]; then exit 1; fi

を追記すれば良さそうです。(ktlint --install-git-pre-commit-hookで生成された中身です。)

Commitすると以下のようにktlintが自動でチェックしてくれます。

$ git commit
app/src/main/java/com/example/MainActivity.kt:8:27: Missing spacing before "{"
app/src/main/java/com/example/MainActivity.kt:10:1: Unexpected indentation (1) (it should be 8) (cannot be auto-corrected)

これでローカルでフォーマットの修正漏れに気づくことができますね。

Android Studioで使う

Gitをコマンドで実行している場合は特別意識せずにgit commitすればktlintが走ってくれます。

Android StudioGUI上でGit Commitしている場合は、 Commit Changes の Run Git hooks オプションをオンにしてあげればOKです。

Android StudioのRun Git hooks
Run Git hooksをオンにする

Ktlintのコードチェックに失敗すると、以下のような目に悪いアラートが表示されるので、フォーマット忘れに一目で気づくことができますね。

ktlintの実行結果
ktlintの実行結果

これで修正忘れにプッシュ後気づくこともなくなり、Commit分のコード自体も綺麗になります。

今回は以上です。

BigQueryで協調フィルタリングを使って使用食材が似たレシピを探す話

こんにちは。おいしい健康エンジニアの花井です。

今年の8月に入社しまして、iOS(クライアント)、API(サーバーサイド)、データ分析など幅広くやっています。 言語で言うと、Ruby(Ruby on Rails), Swift, Pythonですね。

今回は、食材が似たレシピを探す話、と題しておいしい健康のデータ分析の話をします。

概要

おいしい健康で最も人気がある下記レシピに対して、使用食材が似ているレシピを探します。 oishi-kenko.com BigQueryはクエリを気軽に実行して試行錯誤できるので最適化問題のソルバーとしてとても優秀です!

おいしい健康のデータ分析まわり

f:id:oishi-kenko:20181109142033p:plain

おいしい健康ではデータウェアハウスとしてBigQueryを使っています。

BigQueryへのデータコピーには、WebやアプリのログではFluentd, データベースに保存しているレシピや食品成分表等のデータではEmbulkを使っています。

データ可視化にはGoogle Data Studio*1を使っており、レポートやダッシュボードをサクッと作り社内会議で共有しています。 Google Data Studioでは表現できない複雑な図表を作成するときはGoogle Colaboratory*2を使っています。

使用食材が似ているレシピを探す

レシピには食材・調理手順・調理器具など様々な要素がありますが、今回は食材のみ着目します。

定義

食材(Ingredient)の集合を {I = (i_1, i_2, \cdots, i_n)}, レシピ(Recipe)の集合を {R = (r_1, r_2, \cdots, r_m)}と表記します。

レシピ {r_i}に対する食材 {j}の使用量を {w_{r_i,j}}と定義し、 レシピ {r_i}に対する食材使用率ベクトル {v_{r_i}}を次のように定義します。

\begin{align} \vec{v_{r_i}} = \frac{1}{\sum_{k=1}^n w_{r_i,k}} (w_{r_i,1}, w_{r_i,2}, \cdots, w_{r_i,n}) \end{align}

今回のデータ分析では食品成分表*3を利用し、調味料等を除外して食材の使用量を算出しています。

クエリでは、次のように実装できます。

協調フィルタリング

協調フィルタリングは、ユーザ間やアイテム間の類似性に基づいて推薦アイテムを決定する推薦アルゴリズムです。今回は類似性の指標としてコサイン類似度を採用し、設計します。

レシピ {r_i, r_j}に対するコサイン類似度 {s_{r_i r_j}}を次のように定義します。

\begin{align} s_{r_i r_j}= \frac{\sum_{k=1}^n v_{r_i,k} v_{r_j,k}}{\sqrt{\sum_{k=1}^n v_{r_i,k}^2} \sqrt{\sum_{k=1}^n v_{r_j,k}^2}} \end{align}

具体例を見てみましょう。

f:id:oishi-kenko:20181112185426p:plain

食材の集合 {I = (i_1, i_2, \cdots, i_n)}に対してそれぞれのレシピで使用してる食材は数種類なので、食材使用率ベクトルのほとんどの成分はゼロです。例にあげている2つのレシピに対する使用食材の成分だけ取り出すると次のようになります。

f:id:oishi-kenko:20181113114048p:plain

大雑把に言えば、この2つの図形がどのくらい一致するか、という指標がコサイン類似度です。 ベクトル同士の成す角度の近さを表現しており、1に近づくほど似ている、0に近づくほど似ていない、となります。

この例ではコサイン類似度は0.94958となり、よく似ていると判断できます。

まとめると、使用食材が似たレシピを探す話は、あるレシピ  {r_i}に対してコサイン類似度  {s_{r_i r_j}}が最大となるようなレシピ {r_j}を求める最適化問題となります。

\begin{align} arg\max_{r_j \in R} s_{r_i r_j} \end{align}

今回は、この最適化問題のソルバーとしてBigQueryを使い、総当たり*4で求めます。

BigQueryのメリット

Python等でも実装できますが、BigQueryはこんなメリットがあります。

  • 標準SQLが使えるので学習コストが安く、エンジニア間のコミュニケーションが容易。
  • メンテナンスコストも安い
  • 実行速度がとても速いため総当たりで解ける
  • Google製品と簡単に連携できて、結果出力やデータビジュアリゼーションの実行環境の構築コストが安い

コード

いよいよ実装に入ります。 BigQueryでは協調フィルタリングを次のようにシンプルに実装できます。

類似度1位を見てみよう

実は、コサイン類似度が最も大きいレシピはコサイン類似度で例を出したものでした。 oishi-kenko.com

しかし....

このレシピが最も似ているレシピだと納得できるでしょうか。

コサイン類似度が0.94958ですが、似ていると判断できるでしょうか。

「落とし揚げ」のレシピにはレタスも玉ねぎも生姜も食材に含まれていないです。

もう1度、食材使用率ベクトルを見てください。 f:id:oishi-kenko:20181113114048p:plain

豆腐と鶏ひき肉が多く含まれているレシピが選ばれていることが分かります。

つまり、食材使用率の低い食材が無視されてしまっています。

少量の食材でもレシピに含まれてほしいですね。

そこで、定義を変更します。

ingredient.total_gram / SUM(total_gram) OVER(PARTITION BY ingredient.recipe_id) AS gram_per_recipe

食材使用率に1を足し、下駄を履かせます。

少量の食材も考慮されるようにします。

1 + ingredient.total_gram / SUM(total_gram) OVER(PARTITION BY ingredient.recipe_id) AS gram_per_recipe

1位の結果を見てみましょう。 oishi-kenko.com コサイン類似度は0.80864です。

食材使用率ベクトルはこのようになりました。 f:id:oishi-kenko:20181113113904p:plain

5つの食材が被っているレシピが選ばれました。

レシピ類似度のデータビジュアリゼーション

以下はおまけです。 レシピ間の類似度ネットワーク*5を可視化してみます。

可視化対象

すべてのレシピ間を図示してしまうと辺だらけのネットワークになってしまうので、コサイン類似度が0.7以上のレシピ間を可視化します。

頂点 {V} : レシピ集合  {R = (r_1, r_2, \cdots, r_m)}

 {E} : { {(r_i, r_j) | s_{r_i r_j} \geqq 0.7 for \forall r_i, r_j \in R}}

データ取り出し

BigQueryのコンソール画面でGoogleスプレッドシートへ出力できる機能があるのですが、行数が多すぎるため使えません。 そんなときは、Google Colaboratoryが便利です。 下記のようにCSVファイルを簡単に取得できます。

可視化したネットワーク

ネットワーク可視化ソフトウェアプラットフォームCytoscape*6が便利です。

以下のフィルターを適用しました。

  • 頂点の次数*7が大きいほど大きくする描画する
  • 頂点の次数が大きいほど赤く、小さいほど青く描画する

巨大なネットワークと複数の小規模ネットワークができました。

f:id:oishi-kenko:20181112182528p:plain f:id:oishi-kenko:20181112182529p:plain

いくつかクラスターがあり、その中にハブとなるようなレシピが存在していることが分かります。

今回は可視化しただけですが、レシピのネットワーク構造を研究してサービスに活かせそうですね。

*1:https://datastudio.google.com/overview

*2:https://colab.research.google.com/

*3:おいしい健康では食品やレシピの栄養価は、主に文部科学省から食品の栄養価が書かれた一覧表(日本食品標準食品成分表 2015年版(七訂))を用いて算出しています。

*4:力まかせ探索、しらみつぶし探索とも言います

*5:数学用語ではグラフといいます。グラフG, 頂点集合V, 辺集合EのときG=(V,E)と表記します。この記事ではグラフではなくネットワークと書くことにします。

*6:バージョン3.7.0 https://cytoscape.org/

*7:頂点から出ている辺の数

Probot で GitHub の PullRequest のレビューアサイン, ブランチ削除を自動化する

Probot ご存知でしょうか!?

今回は、GitHub に便利な自動化ツールを追加できる Probot を弊社事例とともにお伝えしていきます。

Probot is 何

Probot は GitHub apps を Node.js により作れるフレームワークです。

probot.github.io

GitHubを使っているときに、「あぁ。。これ自動でなんとかしたい」と思うことはないでしょうか。

  • PullRequest(以下、PR) をマージしたら、ブランチを毎回手動で消すのめんどい
  • レビュワーを自動でアサインしてほしい
  • 最低一人に approve をしてもらわないと、マージできないルールで運用しているので、approve がないPRはマージボタンを無効にしたい

こういったことを実現するために自分で GitHub apps を書いたり、 APIでなんとかしようとしたりも出来ますが、そこまで時間的コストをかけられない。という方々も多いハズ。

Probot は GitHub Apps の開発フレームワークを提供していますが、更に Probot で開発された GitHub Apps の一覧も公開してくれており、既に公開されている GitHub Apps を使うことで、上記の様な願いはすぐに叶えられるようになっています 😇

おすすめ GitHub Apps by Probot

ここからは、弊社でも使っている便利な GitHub Apps をご紹介します。

Auto Assign

probot.github.io

PR を出すたびに指定した GitHub ユーザーの中から何名かをランダムに選択しレビューワーへのリクエストを飛ばす GitHub App です。

おいしい健康では、(ほぼ)すべてのPRを必ず他のエンジニアがコードレビューをします。

コードレビューでは、プログラムが設計通りにかけているかを始め、より良い解決方法はないか。などを中心にやり取りが行われます。在籍期間が長くなればなるほど、コードレビューを活発に行える一方で、新しく入ってきたばかりの人は、書かれたコードの前後背景がわからなかったり、コメントするポイントがわからなかったりして、レビューに参加しにくいことも珍しくありません。

そこで、この Auto Assign の登場です。

レビュワーはランダムに割り当てられるため、誰もが均等にレビューをする機会を得ることになります。

前後背景の知識を持っている人は、コードに考慮漏れがないかなどを中心に。 前後背景の知識を持っていない人は、今後前後背景の知識を蓄えられるように。この Auto Assign は役立っています。 もちろん、前後背景を知らない人たちばかりがアサインされてしまい、ちょっと不安だな。と思うときには、手動で新たにレビューワーを追加するなどの対応は必要になってきます。

なんにせよ、この GitHub Apps を入れてから、レビューが活発になったのは確かです。

Delete merged branch

probot.github.io

PR をマージしたら、マージ済みのブランチを自動で消してくれる GitHub App です。 マージ後のブランチは基本的に用済みであり、トラブルがあれば Restore もできるため、消してしまうのが弊社の通常運用になっています。

この GitHub App を導入するまでは定期的に消し忘れているものをチェックしたり、あまり消す習慣のない人にはリマインドを送ったりと、地味に手間な運用が行われていました。

すごく簡易な機能だけの GitHub App ですが、めちゃくちゃ役立っています。

おわりに

今回は便利 GitHub Apps の紹介がメインとなりましたが、自分で作るのもとても簡単なようです。(私はまだ作っていない) Probot を用いた自作 GitHub App の良い事例がでてきましたら、またお知らせします。

ペアプログラミングを導入して分かったこと

こんにちは。おいしい健康エンジニアの近藤です。

WWDC2018で他のエンジニアと情報交換させていただいた中で、他社ではペアプログラミングを導入して開発速度が2倍に上がったというお話を聞き、おいしい健康でも少しずつペアプログラミングを導入しています。今回はペアプログラミングを導入してみて分かったペアプログラミングのメリット、デメリットを紹介いたします。

f:id:oishi-kenko:20180817173215j:plain ※画像はペアプロの相方が出社するまでの間の場所取りとして活躍している人形です。

おいしい健康でのペアプログラミングのやり方

おいしい健康では、複雑なモデル周りの設計をするときや、比較的難易度の高いアプリの画面を実装するときに、ペアプログラミングをしながら最初に実装方針を決めています。
また、最近では、毎日エンジニア同士でランダムにペアを決めて、ペア同士は必ず隣に座るという運用も始めました。これによってフリーアドレスでもエンジニア同士で気軽に相談できるようにしました。こちらはペアプログラミングに限らず、テディベア効果も狙ったものになります。

ペアプログラミングのメリット

設計や実装のスピードが格段に上がる

ペアプログラミング中は途中で集中力が途切れたり、Slackを見たりする暇がなく、強制的にプログラミングが進むため、格段にスピードが上がります。

レビューによる手戻りが少ない

最初にペアプログラミングで実装方針を決めているため、大きな手戻りが発生することがありません。レビューでは細かい修正点だけを見ていけばよく、レビュー時間の短縮にも繋がります。

知識の底上げ、新メンバーのフォローアップ

新メンバーが新しく開発に入るタイミングでペアプログラミングをすることで、最初に詰まりやすいポイントに対してすぐにフォローすることができ、エンジニア全体の知識の底上げにも繋がります。

ペアプログラミングのデメリット

ナビゲータ役のエンジニアへの負担

弊社のペアプログラミングでは主にベテランエンジニアがナビゲータ、新メンバーがドライバーとして行っていることが多いため、ベテランエンジニアに負担が行ってしまうことがあります。こちらはペアプログラミングを続けていくことによって、エンジニア全体の知識が平準化されることで徐々にナビゲータ役のエンジニアが増えていけば解決されるかも知れません。

ペア席が取りづらい

おいしい健康ではフリーアドレスを導入しているため、外部ディスプレイ席を隣同士で2台確保しづらい場合があります。またフリーアドレスのメリットも少し薄れてしまいます。

単純作業の場合には向かない

比較的難易度の高い設計や実装の場合はペアプログラミングが効果を発揮しますが、ただ手を動かすだけの単純作業の場合は、一人で作業をした方が効率が良い場合があります。

さいごに

いくつかデメリットもありましたが、全体的にはペアプログラミングを導入したあとの方が実装スピードも上がり、エンジニア全体の知識の底上げにも繋がり良かったと思います。 開発速度が2倍とまでは行きませんでしたが、開発効率や品質向上の施策の一つとしてはオススメです。

参加してわかった WWDC ラボでの過ごしかた

はじめに

エンジニアの濵田です。昨年に引き続き、カリフォルニア州サンノゼで開かれた WWDC 2018 に参加してきました。

前回の WWDC 2017 参加で悔しかったことの一つに、「ラボ」と呼ばれるイベントにほとんど顔をださなかったことがあります。あとから、「ラボには絶対行ったほうがいい」という話をきいて、惜しいことしたなと残念でした。幸運にも再度 WWDC に参加することができたので、今回はラボ参加を優先して5日間を過ごしました 💪。

効率よくラボを楽しむにはいくつかコツがあることもわかったので、参加する上でのチップスや会場での流れを簡単にまとめます。

続きを読む

Linuxでプロセスごとに開いているファイルディスクリプタの数を調べる

こんにちは 山下(@tomorrowkey) です。

Linuxをセットアップする時にすぐに数を大きくするfile descriptorの数ですが、どのプロセスがどのくらいファイルを開いているか確認する方法を知らなかったので調べました。 早速最終的なコマンドから。例えばunicornが開いているファイルの数を調べるならこんな感じ。

for i in $(ps aux | grep "[u]nicorn" | awk '{print $2}'); do  ls /proc/$i/fd | wc -l; done

開いているファイルディスクリプタ/proc/$PID/fdにあるので、数を数えればOKです。
調べたことなど書きます。

dockerでfile descriptorの数を設定する

検証にはdockerを使います。 起動時に--ulimitというオプションを渡すだけで制限が可能です。 検証用なので極端に小さく設定します。

$ docker run --ulimit="nofile=64" --rm -it ruby:2.4.2-jessie /bin/bash
root@ea6e45c79d10:/# ulimit -n
64

irbを起動しただけのfile descriptorの数を調べる

プロセスIDを調べます。

# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.1  20244  3208 pts/0    Ss   00:35   0:00 /bin/bash
root         7  0.1  0.1  20244  3196 pts/1    Ss   00:35   0:00 /bin/bash
root        14  1.6  0.4  47672  9632 pts/0    Sl+  00:35   0:00 irb
root        16  0.0  0.1  17500  2072 pts/1    R+   00:35   0:00 ps aux

file descriptorの数を調べます。

# ls /proc/14/fd | wc -l
9

9個でした。

今度はlsofで数を確認します。 rubyのdockerイメージにはlsofがインストールされていないので、インストールします。

# apt-get update && apt-get install -y lsof
# lsof -p 14
COMMAND PID USER   FD   TYPE DEVICE SIZE/OFF    NODE NAME
irb      14 root  cwd    DIR   0,46     4096   52302 /
irb      14 root  rtd    DIR   0,46     4096   52302 /
irb      14 root  txt    REG  254,1   146248 3145942 /usr/local/bin/ruby
irb      14 root  mem    REG  254,1   171800 1444564 /lib/x86_64-linux-gnu/libtinfo.so.5.9
irb      14 root  mem    REG  254,1   151120 1444515 /lib/x86_64-linux-gnu/libncurses.so.5.9
irb      14 root  mem    REG  254,1   297040 1444546 /lib/x86_64-linux-gnu/libreadline.so.6.3
irb      14 root  mem    REG  254,1   260648 3146915 /usr/local/lib/ruby/2.4.0/x86_64-linux/readline.so
irb      14 root  mem    REG  254,1   292976 3146919 /usr/local/lib/ruby/2.4.0/x86_64-linux/stringio.so
irb      14 root  mem    REG  254,1    33400 3146874 /usr/local/lib/ruby/2.4.0/x86_64-linux/enc/trans/transdb.so
irb      14 root  mem    REG  254,1   183168 3146831 /usr/local/lib/ruby/2.4.0/x86_64-linux/enc/encdb.so
irb      14 root  mem    REG  254,1  1738176 1444479 /lib/x86_64-linux-gnu/libc-2.19.so
irb      14 root  mem    REG  254,1  1051056 1444509 /lib/x86_64-linux-gnu/libm-2.19.so
irb      14 root  mem    REG  254,1    35176 1444487 /lib/x86_64-linux-gnu/libcrypt-2.19.so
irb      14 root  mem    REG  254,1    14664 1444492 /lib/x86_64-linux-gnu/libdl-2.19.so
irb      14 root  mem    REG  254,1   137384 1444543 /lib/x86_64-linux-gnu/libpthread-2.19.so
irb      14 root  mem    REG  254,1 15803280 3145980 /usr/local/lib/libruby.so.2.4.2
irb      14 root  mem    REG  254,1   140928 1444461 /lib/x86_64-linux-gnu/ld-2.19.so
irb      14 root    0u   CHR  136,0      0t0       3 /dev/pts/0
irb      14 root    1u   CHR  136,0      0t0       3 /dev/pts/0
irb      14 root    2u   CHR  136,0      0t0       3 /dev/pts/0
irb      14 root    3r  FIFO   0,10      0t0   54293 pipe
irb      14 root    4w  FIFO   0,10      0t0   54293 pipe
irb      14 root    5r  FIFO   0,10      0t0   54294 pipe
irb      14 root    6w  FIFO   0,10      0t0   54294 pipe
irb      14 root    7u   CHR  136,0      0t0       3 /dev/pts/0
irb      14 root    8u   CHR  136,0      0t0       3 /dev/pts/0

FDカラムに注目すると…

  • cwd -> current working directory
  • rtd -> root directory
  • txt -> program text
  • mem -> memory-mapped device
  • \[0-9]+r\ -> read-onlyで開いているファイル
  • \[0-9]+w\ -> write-onlyで開いているファイル
  • \[0-9]+u\ -> read-writeで開いてるファイル

lsofでfile descriptorの数を調べるにはFDが数字から始まるものを数えるとよさそう。 その他の値についてはこちら https://linux.die.net/man/8/lsof

lsofを使えばどのファイルを開いているかまで分かります。

たくさんのファイルを開いてfile descriporの数を調べる

スクリプトでファイルをたくさん開きます。

files = []
(1..(2 ** 10)).each do |i|
  puts i
  files << File.open("#{i}", "w")
  sleep 0.5
end
1
2
3
# ...
54
55
56
Errno::EMFILE: Too many open files @ rb_sysopen - 56
    from (irb):4:in `initialize'
  from (irb):4:in `open'
  from (irb):4:in `block in irb_binding'
    from (irb):2:in `each'
  from (irb):2
  from /usr/local/bin/irb:11:in `<main>'

無事ファイルの開き過ぎでエラーを吐きました。 irbをそのままに、違うターミナルでfile descriptorの数を調べます。

# for i in $(ps aux | grep "[i]rb" | awk '{print $2}'); do  ls /proc/$i/fd | wc -l; done
64

lsofで確認します。

# lsof -p $(ps aux | grep "[i]rb" | awk '{print $2}' | tr -d " ")
COMMAND PID USER   FD   TYPE DEVICE SIZE/OFF    NODE NAME
irb      14 root  cwd    DIR   0,46     4096   52302 /
irb      14 root  rtd    DIR   0,46     4096   52302 /
irb      14 root  txt    REG  254,1   146248 3145942 /usr/local/bin/ruby
irb      14 root  mem    REG  254,1   171800 1444564 /lib/x86_64-linux-gnu/libtinfo.so.5.9
irb      14 root  mem    REG  254,1   151120 1444515 /lib/x86_64-linux-gnu/libncurses.so.5.9
irb      14 root  mem    REG  254,1   297040 1444546 /lib/x86_64-linux-gnu/libreadline.so.6.3
irb      14 root  mem    REG  254,1   260648 3146915 /usr/local/lib/ruby/2.4.0/x86_64-linux/readline.so
irb      14 root  mem    REG  254,1   292976 3146919 /usr/local/lib/ruby/2.4.0/x86_64-linux/stringio.so
irb      14 root  mem    REG  254,1    33400 3146874 /usr/local/lib/ruby/2.4.0/x86_64-linux/enc/trans/transdb.so
irb      14 root  mem    REG  254,1   183168 3146831 /usr/local/lib/ruby/2.4.0/x86_64-linux/enc/encdb.so
irb      14 root  mem    REG  254,1  1738176 1444479 /lib/x86_64-linux-gnu/libc-2.19.so
irb      14 root  mem    REG  254,1  1051056 1444509 /lib/x86_64-linux-gnu/libm-2.19.so
irb      14 root  mem    REG  254,1    35176 1444487 /lib/x86_64-linux-gnu/libcrypt-2.19.so
irb      14 root  mem    REG  254,1    14664 1444492 /lib/x86_64-linux-gnu/libdl-2.19.so
irb      14 root  mem    REG  254,1   137384 1444543 /lib/x86_64-linux-gnu/libpthread-2.19.so
irb      14 root  mem    REG  254,1 15803280 3145980 /usr/local/lib/libruby.so.2.4.2
irb      14 root  mem    REG  254,1   140928 1444461 /lib/x86_64-linux-gnu/ld-2.19.so
irb      14 root    0u   CHR  136,0      0t0       3 /dev/pts/0
irb      14 root    1u   CHR  136,0      0t0       3 /dev/pts/0
irb      14 root    2u   CHR  136,0      0t0       3 /dev/pts/0
irb      14 root    3r  FIFO   0,10      0t0   54293 pipe
irb      14 root    4w  FIFO   0,10      0t0   54293 pipe
irb      14 root    5r  FIFO   0,10      0t0   54294 pipe
irb      14 root    6w  FIFO   0,10      0t0   54294 pipe
irb      14 root    7u   CHR  136,0      0t0       3 /dev/pts/0
irb      14 root    8u   CHR  136,0      0t0       3 /dev/pts/0
irb      14 root    9w   REG  254,1        0 3149355 /1
irb      14 root   10w   REG  254,1        0 3149365 /2
irb      14 root   11w   REG  254,1        0 3149369 /3
irb      14 root   12w   REG  254,1        0 3149372 /4
irb      14 root   13w   REG  254,1        0 3149422 /5
irb      14 root   14w   REG  254,1        0 3149423 /6
irb      14 root   15w   REG  254,1        0 3149425 /7
irb      14 root   16w   REG  254,1        0 3149455 /8
irb      14 root   17w   REG  254,1        0 3149457 /9
irb      14 root   18w   REG  254,1        0 3149458 /10
irb      14 root   19w   REG  254,1        0 3149459 /11
irb      14 root   20w   REG  254,1        0 3149460 /12
irb      14 root   21w   REG  254,1        0 3149461 /13
irb      14 root   22w   REG  254,1        0 3149462 /14
irb      14 root   23w   REG  254,1        0 3149463 /15
irb      14 root   24w   REG  254,1        0 3149464 /16
irb      14 root   25w   REG  254,1        0 3149465 /17
irb      14 root   26w   REG  254,1        0 3149466 /18
irb      14 root   27w   REG  254,1        0 3149467 /19
irb      14 root   28w   REG  254,1        0 3149468 /20
irb      14 root   29w   REG  254,1        0 3149469 /21
irb      14 root   30w   REG  254,1        0 3149470 /22
irb      14 root   31w   REG  254,1        0 3149471 /23
irb      14 root   32w   REG  254,1        0 3149472 /24
irb      14 root   33w   REG  254,1        0 3149473 /25
irb      14 root   34w   REG  254,1        0 3149474 /26
irb      14 root   35w   REG  254,1        0 3149475 /27
irb      14 root   36w   REG  254,1        0 3149476 /28
irb      14 root   37w   REG  254,1        0 3149477 /29
irb      14 root   38w   REG  254,1        0 3149478 /30
irb      14 root   39w   REG  254,1        0 3149479 /31
irb      14 root   40w   REG  254,1        0 3149480 /32
irb      14 root   41w   REG  254,1        0 3149481 /33
irb      14 root   42w   REG  254,1        0 3149482 /34
irb      14 root   43w   REG  254,1        0 3149483 /35
irb      14 root   44w   REG  254,1        0 3149484 /36
irb      14 root   45w   REG  254,1        0 3149485 /37
irb      14 root   46w   REG  254,1        0 3149486 /38
irb      14 root   47w   REG  254,1        0 3149487 /39
irb      14 root   48w   REG  254,1        0 3149488 /40
irb      14 root   49w   REG  254,1        0 3149489 /41
irb      14 root   50w   REG  254,1        0 3149490 /42
irb      14 root   51w   REG  254,1        0 3149491 /43
irb      14 root   52w   REG  254,1        0 3149492 /44
irb      14 root   53w   REG  254,1        0 3149493 /45
irb      14 root   54w   REG  254,1        0 3149494 /46
irb      14 root   55w   REG  254,1        0 3149495 /47
irb      14 root   56w   REG  254,1        0 3149496 /48
irb      14 root   57w   REG  254,1        0 3149497 /49
irb      14 root   58w   REG  254,1        0 3149498 /50
irb      14 root   59w   REG  254,1        0 3149499 /51
irb      14 root   60w   REG  254,1        0 3149500 /52
irb      14 root   61w   REG  254,1        0 3149501 /53
irb      14 root   62w   REG  254,1        0 3149502 /54
irb      14 root   63w   REG  254,1        0 3149503 /55

64個ファイルを開いていることが確認できました。

以上です。