2022/06/12
Unix Processes
Diving into Rack
Rackのrackup
コマンドは,プロセスをデーモン化できるため,そのコードを読んでいく。
exit if fork Process.setsid exit if fork Dir.chdir "/" STDIN.reopen "/dev/null" STDOUT.reopen "/dev/null", "a" STDERR.reopen "/dev/null", "a"
Daemonizing a Process, Step by Step
exit if fork
fork
メソッドは値を,親プロセス・子プロセスで計2回返す。親プロセスでは子プロセスのpidを,子プロセスではnilを返す。
そのため,親プロセスは終了し,子プロセスは存在したままになる。親プロセスの存在しない子プロセス(orphaned process)のppidは1となる。
Process.setsid
以下の3つのことを行う。
- プロセスが新しいセッションのセッションリーダーとなる
- プロセスが新しいプロセスグループのグループリーダーとなる
- プロセスはコントロールできるターミナルを持たない
Process Groups and Session Groups
全てのプロセスは一つのグループに属し,それぞれのグループは一意のIDを持つ。代表的なプロセスグループは,親プロセスとその子プロセス群。
普通,プロセスグループのidは,プロセスグループリーダーのpidと同じになる。
irb(main):005:0> puts Process.getpgrp 16852 => nil irb(main):006:0> puts Process.pid 16852 => nil
また,プロセスグループの集合がセッショングループ。
exit if fork
forkされ,プロセスグループとセッショングループのリーダーとなった子プロセス自身は終了し,その子プロセスがforkされる。
新しくforkされたプロセスは,プロセスグループのリーダーでもなく,セッションリーダーでもなく,支配できるターミナルも持っていない。
DIr.chdir "/"
カレントディレクトリを,ルートディレクトリに変更する。必ずしも必要な処理ではないが,デーモンのカレントディレクトリが実行中に消えないようにする。
STDIN.reopen "/dev/null" STDOUT.reopen "/dev/null", "a" STDERR.reopen "/dev/null", "a"
デーモンプロセスの出力先を変更する。
参考
Rails の select メソッド
カラムを指定することができるメソッド。以下のような,users
テーブルを考える。
irb(main):008:0> User => User(id: integer, name: string, department_id: integer, company_id: integer, created_at: datetime, updated_at: datetime)
上記の User モデルの一つ目のデータを取得した結果は以下となる。
irb(main):009:0> User.first User Load (0.4ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]] => #<User:0x000000010b861bc0 id: 1, name: "ユーザー_0", department_id: 1, company_id: 1, created_at: Sun, 12 Jun 2022 08:06:02.012826000 UTC +00:00, updated_at: Sun, 12 Jun 2022 08:06:02.012826000 UTC +00:00>
SELECT 句で"users".*
が指定され,全てのカラムが取得されている。しかし,id
とname
カラムのみが欲しい時は,select
メソッドを使うことで指定したカラムのみを取得できる。
irb(main):010:0> User.select("id", "name").first User Load (0.3ms) SELECT "users"."id", "users"."name" FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]] => #<User:0x000000010ae98580 id: 1, name: "ユーザー_0">
SELECT 句では,"users"."id", "users"."name"
が指定されており,id
とname
カラムのみを取得できる。
次に,users
テーブルが以下のようなdepartments
テーブルに属しているとする。
irb(main):011:0> Department => Department(id: integer, name: string, department_code: string, created_at: datetime, updated_at: datetime)
users
テーブルとdepartments
テーブルを内部結合し,select
メソッドを使ってusers
テーブルのid
とname
カラム,departments
テーブルのname
カラムを取得する。以下では,json 形式で出力する。
render json: User .joins(:department) .select( "name", "id", "departments.name" )
出力は以下となる。
[{"name":"部門_0","id":1},{"name":"部門_1","id":2},{"name":"部門_2","id":3},{"name":"部門_0","id":4},{"name":"部門_1","id":5},{"name":"部門_2","id":6},{"name":"部門_0","id":7},{"name":"部門_1","id":8},{"name":"部門_2","id":9},{"name":"部門_0","id":10},{"name":"部門_1","id":11},{"name":"部門_2","id":12}]
name
カラムがdepartments
テーブルのname
となってしまっている。users
テーブルとdepartments
テーブルのname
カラムの区別がされず,上書きされてしまっていることが原因。
以下のように,as
を使ってdepartments
テーブルのname
カラムを区別する。
render json: User .joins(:department) .select( "name", "id", "departments.name as department" )
以下のエラーが出てしまった。
missing attribute: department_id
どうやらas
に,テーブル名の単数形は使えないよう。修正したコード及びその結果は以下となる。
render json: User .joins(:department) .select( "name", "id", "departments.name as department_name" )
[{"name":"ユーザー_0","id":1,"department_name":"部門_0"},{"name":"ユーザー_1","id":2,"department_name":"部門_1"},{"name":"ユーザー_2","id":3,"department_name":"部門_2"},{"name":"ユーザー_3","id":4,"department_name":"部門_0"},{"name":"ユーザー_4","id":5,"department_name":"部門_1"},{"name":"ユーザー_5","id":6,"department_name":"部門_2"},{"name":"ユーザー_6","id":7,"department_name":"部門_0"},{"name":"ユーザー_7","id":8,"department_name":"部門_1"},{"name":"ユーザー_8","id":9,"department_name":"部門_2"},{"name":"ユーザー_9","id":10,"department_name":"部門_0"},{"name":"ユーザー_10","id":11,"department_name":"部門_1"},{"name":"ユーザー_11","id":12,"department_name":"部門_2"}]
また,SQL は以下のようになる。
SELECT "users"."name", "users"."id", departments.name as department_name FROM "users" INNER JOIN "departments" ON "departments"."id" = "users"."department_id"
参考
https://pikawaka.com/rails/select
eager_load と preload
joins
association をキャッシュしないため,join 先のテーブルで検索をかけたい時に使用する。
先ほど使用した,users
とdepartments
テーブルを考える。
users
のうち,departments
のname
が"部門_0"
であるものを取得する。
irb(main):019:0> User.joins(:department).where(department: { name: "部門_0" }).count User Count (2.4ms) SELECT COUNT(*) FROM "users" INNER JOIN "departments" "department" ON "department"."id" = "users"."department_id" WHERE "department"."name" = ? [["name", "部門_0"]] => 4
しかし,departments
はキャッシュしないため,departments.name
を取得しようとすると,N+1 となる。
irb(main):020:1* User.joins(:department).where(department: { name: "部門_0" }).each do |user| irb(main):021:1* puts user.department.name irb(main):022:0> end User Load (0.3ms) SELECT "users".* FROM "users" INNER JOIN "departments" "department" ON "department"."id" = "users"."department_id" WHERE "department"."name" = ? [["name", "部門_0"]] Department Load (0.3ms) SELECT "departments".* FROM "departments" WHERE "departments"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] 部門_0 Department Load (0.1ms) SELECT "departments".* FROM "departments" WHERE "departments"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] 部門_0 Department Load (0.1ms) SELECT "departments".* FROM "departments" WHERE "departments"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] 部門_0 Department Load (0.1ms) SELECT "departments".* FROM "departments" WHERE "departments"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] 部門_0
eager_load
指定した assocition を LEFT OUTER JOIN で引いてキャッシュする。JOIN しているため,JOIN 先のテーブルで絞り込みができる。
irb(main):024:0> User.eager_load(:department) SQL (0.5ms) SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, "users"."department_id" AS t0_r2, "users"."company_id" AS t0_r3, "users"."created_at" AS t0_r4, "users"."updated_at" AS t0_r5, "departments"."id" AS t1_r0, "departments"."name" AS t1_r1, "departments"."department_code" AS t1_r2, "departments"."created_at" AS t1_r3, "departments"."updated_at" AS t1_r4 FROM "users" LEFT OUTER JOIN "departments" ON "departments"."id" = "users"."department_id"
先ほどと同様に,departments
のname
で絞り込みをして,departments.name
を出力する。
irb(main):036:1* User.eager_load(:department).where(department: { name: "部門_0" }).each do |user| irb(main):037:1* puts user.department.name irb(main):038:0> end SQL (0.4ms) SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, "users"."department_id" AS t0_r2, "users"."company_id" AS t0_r3, "users"."created_at" AS t0_r4, "users"."updated_at" AS t0_r5, "department"."id" AS t1_r0, "department"."name" AS t1_r1, "department"."department_code" AS t1_r2, "department"."created_at" AS t1_r3, "department"."updated_at" AS t1_r4 FROM "users" LEFT OUTER JOIN "departments" "department" ON "department"."id" = "users"."department_id" WHERE "department"."name" = ? [["name", "部門_0"]] 部門_0 部門_0 部門_0 部門_0
association 先であるdepartments
テーブルがキャッシュされているため,N+1 は発生しない。
preload
preload
を実行した結果は以下となる。
irb(main):006:0> User.preload(:department) User Load (8.8ms) SELECT "users".* FROM "users" Department Load (3.3ms) SELECT "departments".* FROM "departments" WHERE "departments"."id" IN (?, ?, ?) [["id", 1], ["id", 2], ["id", 3]]
SQL 文が2回発行されている。users
テーブルのデータを全て取得した後に,その外部キーでdepartments
テーブルを取得し,キャッシュしている。そのため,eager_load
と同様に N+1 が発生しない。
irb(main):007:1* User.preload(:department).each do |user| irb(main):008:1* puts user.department.name irb(main):009:0> end User Load (0.9ms) SELECT "users".* FROM "users" Department Load (0.4ms) SELECT "departments".* FROM "departments" WHERE "departments"."id" IN (?, ?, ?) [["id", 1], ["id", 2], ["id", 3]] 部門_0 部門_1 部門_2 部門_0 部門_1 部門_2 部門_0 部門_1 部門_2 部門_0 部門_1 部門_2
しかし,association テーブルで絞り込もうとするとエラーとなる。
irb(main):012:0> User.preload(:department).where(department: { name: "部門_0" }).first User Load (1.1ms) SELECT "users".* FROM "users" WHERE "department"."name" = ? ORDER BY "users"."id" ASC LIMIT ? [["name", "部門_0"], ["LIMIT", 1]] / `initialize': SQLite3::SQLException: no such column: department.name (ActiveRecord::StatementInvalid)