SlideShare uma empresa Scribd logo
1 de 57
Baixar para ler offline
Railsエンジニアのための
SQLチューニング速習会 @ Wantedly
2015-12-10
Nao Minami (@south37)
自己紹介
• 1. SQLが実行されるとき、RDBの中で何が起きるか
を知る
• 2. Explain の読み方、適切なindexの張り方を知る
• 3. チューニングの為に気をつけるポイントを知る
今日速習する内容
セットアップ
$ git clone https://github.com/south37/sql-tuning
$ git checkout sql-tuning
$ bin/rake db:create
$ pg_restore -j 4 --verbose --no-acl --no-owner -d sql-tuning-dev db.dump
Explain してみよう
ActiveRecord::Relation#explain
$ Job.joins(:company).group('companies.country').where('companies.id < 1000’)
.select('companies.country', 'COUNT(jobs.id)').explain
=> EXPLAIN for: SELECT companies.country, COUNT(jobs.id) FROM "jobs" INNER JOIN "companies" ON
"companies"."id" = "jobs"."company_id" WHERE (companies.id < 1000) GROUP BY companies.country
QUERY PLAN
-------------------------------------------------------------------------------------------------------
HashAggregate (cost=1213.79..1220.12 rows=634 width=16)
-> Hash Join (cost=54.28..1188.79 rows=5000 width=16)
Hash Cond: (jobs.company_id = companies.id)
-> Seq Scan on jobs (cost=0.00..897.00 rows=50000 width=8)
-> Hash (cost=41.78..41.78 rows=1000 width=16)
-> Index Scan using companies_pkey on companies (cost=0.29..41.78 rows=1000 width=16)
Index Cond: (id < 1000)
$ Job.joins(:company).group('companies.country').where('companies.id < 1000’)
.select('companies.country', 'COUNT(jobs.id)').explain
=> EXPLAIN for: SELECT companies.country, COUNT(jobs.id) FROM "jobs" INNER JOIN "companies" ON
"companies"."id" = "jobs"."company_id" WHERE (companies.id < 1000) GROUP BY companies.country
QUERY PLAN
-------------------------------------------------------------------------------------------------------
HashAggregate (cost=1213.79..1220.12 rows=634 width=16)
-> Hash Join (cost=54.28..1188.79 rows=5000 width=16)
Hash Cond: (jobs.company_id = companies.id)
-> Seq Scan on jobs (cost=0.00..897.00 rows=50000 width=8)
-> Hash (cost=41.78..41.78 rows=1000 width=16)
-> Index Scan using companies_pkey on companies (cost=0.29..41.78 rows=1000 width=16)
Index Cond: (id < 1000)
ツリー構造
Explainの見方
実行計画はツリー状の構造
ツリー構造
HashAggregate
Hash Join
Seq ScanHash
Index Scan
$ Job.joins(:company).group('companies.country').where('companies.id < 1000’)
.select('companies.country', 'COUNT(jobs.id)').explain
=> EXPLAIN for: SELECT companies.country, COUNT(jobs.id) FROM "jobs" INNER JOIN "companies" ON
"companies"."id" = "jobs"."company_id" WHERE (companies.id < 1000) GROUP BY companies.country
$ Job.joins(:company).group('companies.country').where('companies.id < 1000’)
.select('companies.country', 'COUNT(jobs.id)').explain
=> EXPLAIN for: SELECT companies.country, COUNT(jobs.id) FROM "jobs" INNER JOIN "companies" ON
"companies"."id" = "jobs"."company_id" WHERE (companies.id < 1000) GROUP BY companies.country
QUERY PLAN
-------------------------------------------------------------------------------------------------------
HashAggregate (cost=1213.79..1220.12 rows=634 width=16)
-> Hash Join (cost=54.28..1188.79 rows=5000 width=16)
Hash Cond: (jobs.company_id = companies.id)
-> Seq Scan on jobs (cost=0.00..897.00 rows=50000 width=8)
-> Hash (cost=41.78..41.78 rows=1000 width=16)
-> Index Scan using companies_pkey on companies (cost=0.29..41.78 rows=1000 width=16)
Index Cond: (id < 1000)
コストの見方
コストの見方
Seq Scan on jobs (cost=0.00..897.00 rows=50000 width=8)
Index Scan using companies_pkey on companies (cost=0.29..41.78 rows=1000 width=16)
初期化コスト 総コスト 取得行数
1行あたりのデータサイズ(バイト)
総コスト = 初期化コスト +
(走査行数 × 1行あたりの取得コスト )
index 使うと初期化コストが存在
ANALYSE をつけると実際に実行
$ ActiveRecord::Base.connection.execute("EXPLAIN ANALYSE
#{Job.joins(:company).group('companies.country').where('companies.id < 1000').select('companies.country',
'COUNT(jobs.id)').to_sql}").each { |row| print row['QUERY PLAN']+"n" }
HashAggregate (cost=1213.79..1220.12 rows=634 width=16) (actual time=20.290..20.465 rows=950 loops=1)
-> Hash Join (cost=54.28..1188.79 rows=5000 width=16) (actual time=1.018..18.102 rows=4983 loops=1)
Hash Cond: (jobs.company_id = companies.id)
-> Seq Scan on jobs (cost=0.00..897.00 rows=50000 width=8) (actual time=0.009..6.352 rows=50000 loops=1)
-> Hash (cost=41.78..41.78 rows=1000 width=16) (actual time=0.995..0.995 rows=999 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 51kB
-> Index Scan using companies_pkey on companies (cost=0.29..41.78 rows=1000 width=16) (actual
time=0.022..0.527 rows=999 loops=1)
Index Cond: (id < 1000)
Explainの見方
より詳しく知りたい方はこちら:
http://www.postgresql.org/docs/current/static/sql-explain.html
HashAggregate
Hash Join
Seq ScanHash
Index Scan
最初のステップはデータの取得
$ Job.joins(:company).group('companies.country').where('companies.id < 1000’)
.select('companies.country', 'COUNT(jobs.id)').explain
=> EXPLAIN for: SELECT companies.country, COUNT(jobs.id) FROM "jobs" INNER JOIN "companies" ON
"companies"."id" = "jobs"."company_id" WHERE (companies.id < 1000) GROUP BY companies.country
index を知る
HashAggregate
Hash Join
Seq ScanHash
Index Scan
index の仕組み
B-tree index
• ノードあたり数百要素
• 300要素として、3段で2,700 万件格納
高速なデータ取得
index の利用
$ Job.where(id: 1).explain
=> EXPLAIN for: SELECT "jobs".* FROM "jobs" WHERE "jobs"."id" = $1 [["id", 1]]
QUERY PLAN
-----------------------------------------------------------------------
Index Scan using jobs_pkey on jobs (cost=0.29..8.31 rows=1 width=28)
Index Cond: (id = 1)
$ Job.where(id_without_index: 1).explain
=> EXPLAIN for: SELECT "jobs".* FROM "jobs" WHERE "jobs"."id_without_index"
= $1 [["id_without_index", 1]]
QUERY PLAN
--------------------------------------------------------
Seq Scan on jobs (cost=0.00..1022.00 rows=1 width=28)
Filter: (id_without_index = 1)
index有り
index無し

(Seq Scan)
index バッドパターン その1
「index を貼ったカラムに演算」
$ Profile.where('lower(email) = ?', 'minami@wantedly.com').limit(1).explain
=> EXPLAIN for: SELECT "profiles".* FROM "profiles"
WHERE (lower(email) = 'minami@wantedly.com') LIMIT 1
QUERY PLAN
------------------------------------------------------------------
Limit (cost=0.00..5.08 rows=1 width=54)
-> Seq Scan on profiles (cost=0.00..254.00 rows=50 width=54)
Filter: (lower(email) = 'minami@wantedly.com'::text)
index は key の比較で sort してるので、
演算が行われると利用できない
「クエリ書き換え」 or 「Indexes on Expression を利用」
index バッドパターン その2
「絞り込み条件の緩いWHERE」
$ Profile.where(gender: ‘female').explain
=> EXPLAIN for: SELECT "profiles".* FROM "profiles" WHERE
"profiles"."gender" = $1 [["gender", "female"]]
QUERY PLAN
--------------------------------------------------------------
Seq Scan on profiles (cost=0.00..229.00 rows=5038 width=54)
Filter: (gender = 'female'::text)
male female
profiles.gender の分布
デフォルトだと、 4分の1以下に絞り込まれる必要あり
なぜ絞り込み条件が緩いと
indexが使われないのか?
HDDへのランダムアクセスと
シーケンシャルアクセスの速度差が原因
Seq Scan Index Scan
(Random Access)
1 2 3 4 1 23 4
1要素単位だと高コスト
ちゃんと絞り込まれるならOK
$ BoxerProfile.where(gender: ‘female').explain
=> EXPLAIN for: SELECT "boxer_profiles".* FROM "boxer_profiles" WHERE "boxer_profiles"."gender" =
$1 [["gender", "female"]]
QUERY PLAN
-------------------------------------------------------------------------------------------------
Bitmap Heap Scan on boxer_profiles (cost=28.08..114.66 rows=1006 width=25)
Recheck Cond: (gender = 'female'::text)
-> Bitmap Index Scan on index_boxer_profiles_on_gender (cost=0.00..27.83 rows=1006 width=0)
Index Cond: (gender = 'female'::text)
male female
profiles.gender の分布
データの分布 = 「統計情報」が大事
余談: PostgreSQL 内での
データレイアウト
詳しく知りたい方は:
http://www.postgresql.org/docs/current/static/storage.html
または「内部構造から学ぶPostgreSQL 設計・運用計画の鉄則」
index のデメリット
• 1. 更新に時間がかかるようになる
• 2. HOT が効かない
1. 更新に時間がかかるようになる
B-tree index の更新が必要
2. HOT が効かない
HOTはPostgreSQL のカラムの更新を早くする仕組み
(必要な箇所のみを更新する)
詳しくはこちら:
http://lets.postgresql.jp/documents/tutorial/hot_1/
いろいろな index
• 1. Unique Indexes
• 2. Multicolumn Indexes
• 3. Indexes on Expressions
• 4. Partial Indexes
いろいろな index
• 1. Unique Indexes
• 2. Multicolumn Indexes
• 3. Indexes on Expressions
• 4. Partial Indexes
2. Multicolumn Indexes
create_table "tourist_spots", force: :cascade do |t|
t.text "country"
t.text "city"
end
add_index "tourist_spots", ["country", "city"],
name: "index_tourist_spots_on_country_and_city", using: :btree
複数カラムに対しての index
2. Multicolumn Indexes
$ TouristSpot.where(country: 'japan', city: 'tokyo').explain
=> EXPLAIN for: SELECT "tourist_spots".* FROM "tourist_spots" WHERE "tourist_spots"."country" = $1 AND
"tourist_spots"."city" = $2 [["country", "japan"], ["city", "tokyo"]]
QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Index Scan using index_tourist_spots_on_country_and_city on tourist_spots (cost=0.42..8.44 rows=1 width=52)
Index Cond: ((country = 'japan'::text) AND (city = 'tokyo'::text))
Multicolumn index有り
$ TouristSpotWithoutMultipleIndex.where(country: 'japan', city: 'tokyo').explain
=> EXPLAIN for: SELECT "tourist_spot_without_multiple_indices".* FROM "tourist_spot_without_multiple_indices"
WHERE "tourist_spot_without_multiple_indices"."country" = $1 AND
"tourist_spot_without_multiple_indices"."city" = $2 [["country", "japan"], ["city", "tokyo"]]
QUERY PLAN
-------------------------------------------------------------------------------------------------------------
Index Scan using index_tourist_spot_without_multiple_indices_on_city on tourist_spot_without_multiple_indices
(cost=0.42..8.44 rows=1 width=52)
Index Cond: (city = 'tokyo'::text)
Filter: (country = 'japan'::text)
Multicolumn index無し
2. Multicolumn Indexes
2. Multicolumn Indexes
先頭の要素の index としても効く
より詳細を知りたい方は:
http://www.postgresql.org/docs/current/static/indexes-multicolumn.html
$ TouristSpot.where(country: 'japan').explain
=> EXPLAIN for: SELECT "tourist_spots".* FROM "tourist_spots" WHERE "tourist_spots"."country" = $1
[["country", "japan"]]
QUERY PLAN
-------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on tourist_spots (cost=4.50..41.67 rows=10 width=52)
Recheck Cond: (country = 'japan'::text)
-> Bitmap Index Scan on index_tourist_spots_on_country_and_city (cost=0.00..4.49 rows=10 width=0)
Index Cond: (country = 'japan'::text)
3. Indexes on Expressions
関数などの返り値を key として index を作る事ができる
# db/migrate/db/migrate/20151210065304_add_indexes_on_~.rb
def up
execute <<-SQL
CREATE INDEX index_profiles_with_indexes_on_expressions_on_lower_email
ON profiles_with_indexes_on_expressions(lower(email));
SQL
end
def down
execute <<-SQL
DROP INDEX index_profiles_with_indexes_on_expressions_on_lower_email
SQL
end
3. Indexes on Expressions
lower(email) を index として利用
詳細はこちら:
http://www.postgresql.org/docs/current/static/indexes-expressional.html
$ ProfilesWithIndexesOnExpression.where("lower(email) = 'minami@wantedly.com'").explain
=> EXPLAIN for: SELECT "profiles_with_indexes_on_expressions".* FROM
"profiles_with_indexes_on_expressions" WHERE (lower(email) = 'minami@wantedly.com')
QUERY PLAN
------------------------------------------------------------------------------------------------------
Index Scan using index_profiles_with_indexes_on_expressions_on_lower_email on
profiles_with_indexes_on_expressions (cost=0.29..8.30 rows=1 width=48)
Index Cond: (lower(email) = 'minami@wantedly.com'::text)
HashAggregate
Hash Join
Seq ScanHash
Index Scan
次のステップはデータの結合(JOIN)
$ Job.joins(:company).group('companies.country').where('companies.id < 1000’)
.select('companies.country', 'COUNT(jobs.id)').explain
=> EXPLAIN for: SELECT companies.country, COUNT(jobs.id) FROM "jobs" INNER JOIN "companies" ON
"companies"."id" = "jobs"."company_id" WHERE (companies.id < 1000) GROUP BY companies.country
JOIN のアルゴリズム
index の有無や統計情報(データの量・分布)から、
最適なアルゴリズムが選ばれる
• 1. Nested Loop Join
• 2. Hash Join
• 3. Merge Join
遅い
早い
1. Nested Loop
テーブル1と2に対して、すべての組み合わせを試す
O(N × M) … 極めて遅い
レコード数N レコード数M
• レコード数が少なければ高速
• Table 2 に index を貼れば、
高速化が可能
2. Hash Join
テーブル2に対して、一度フルスキャンしてHashMapを作成
O(N + M) …Hash 生成のコストはかかるが、
Nested Loop よりはマシ
テーブル2の全てのレコード
をメモリに載せる必要あり
$ Job.joins(:company).group('companies.country').where('companies.id < 1000’)
.select('companies.country', 'COUNT(jobs.id)').explain
=> EXPLAIN for: SELECT companies.country, COUNT(jobs.id) FROM "jobs" INNER JOIN "companies" ON
"companies"."id" = "jobs"."company_id" WHERE (companies.id < 1000) GROUP BY companies.country
QUERY PLAN
-------------------------------------------------------------------------------------------------------
HashAggregate (cost=1213.79..1220.12 rows=634 width=16)
-> Hash Join (cost=54.28..1188.79 rows=5000 width=16)
Hash Cond: (jobs.company_id = companies.id)
-> Seq Scan on jobs (cost=0.00..897.00 rows=50000 width=8)
-> Hash (cost=41.78..41.78 rows=1000 width=16)
-> Index Scan using companies_pkey on companies (cost=0.29..41.78 rows=1000 width=16)
Index Cond: (id < 1000)
Hash Join のコスト
Hash の生成コスト(初期化コスト)
3. Merge Join
ソート済みのテーブル1と2に対して、1度だけフルスキャン
O(N+M) …最も高速
JOIN に使うカラムには、
index を貼りましょう
index があっても
JOIN が遅くなるケース
どんなに高速化しても O(N+M) にしかならない
Nが大きいと遅くなる
index があっても
JOIN が遅くなるケース
$ User.joins(:profile).select('COUNT(*)').explain
=> EXPLAIN for: SELECT COUNT(*) FROM "users" INNER JOIN "profiles" ON
"profiles"."user_id" = "users"."id"
QUERY PLAN
--------------------------------------------------------------------------------
Aggregate (cost=23288.72..23288.73 rows=1 width=0)
-> Hash Join (cost=354.30..23261.80 rows=10769 width=0)
Hash Cond: (users.id = profiles.user_id)
-> Seq Scan on users (cost=0.00..11441.64 rows=698964 width=4)
-> Hash (cost=219.69..219.69 rows=10769 width=4)
-> Seq Scan on profiles (cost=0.00..219.69 rows=10769 width=4)
JOIN される left relation は、
事前に絞り込んでおこう
$ User.where(registered: true).joins(:profile).select('COUNT(*)').explain
=> EXPLAIN for: SELECT COUNT(*) FROM "users" INNER JOIN "profiles" ON "profiles"."user_id" = "users"."id"
WHERE "users"."registered" = $1 [["registered", "t"]]
QUERY PLAN
-----------------------------------------------------------------------------------------------------------
Aggregate (cost=8131.17..8131.18 rows=1 width=0)
-> Hash Join (cost=1850.65..8128.51 rows=1065 width=0)
Hash Cond: (users.id = profiles.user_id)
-> Bitmap Heap Scan on users (cost=1496.35..6639.86 rows=69151 width=4)
Filter: registered
-> Bitmap Index Scan on index_users_on_registered (cost=0.00..1479.06 rows=69151 width=0)
Index Cond: (registered = true)
-> Hash (cost=219.69..219.69 rows=10769 width=4)
-> Seq Scan on profiles (cost=0.00..219.69 rows=10769 width=4)
HashAggregate
Hash Join
Seq ScanHash
Index Scan
ラストステップはデータの集約
(Aggregate)
$ Job.joins(:company).group('companies.country').where('companies.id < 1000’)
.select('companies.country', 'COUNT(jobs.id)').explain
=> EXPLAIN for: SELECT companies.country, COUNT(jobs.id) FROM "jobs" INNER JOIN "companies" ON
"companies"."id" = "jobs"."company_id" WHERE (companies.id < 1000) GROUP BY companies.country
GROUP BY の2つのアルゴリズム
• 1. Group Aggregate
• 2. Hash Aggregate
1. Group Aggregate
入力されたデータをグループキーで ソート後、
各グループを順番に処理
(index があってソート済みならパイプライン化も可能)
$ Job.joins(:company).group('companies.country').where('companies.id < 1000’)
.select('companies.country', 'COUNT(jobs.id)').explain
=> EXPLAIN for: SELECT companies.country, COUNT(jobs.id) FROM "jobs" INNER JOIN "companies" ON
"companies"."id" = "jobs"."company_id" WHERE (companies.id < 1000) GROUP BY companies.country
QUERY PLAN
-------------------------------------------------------------------------------------------------------
HashAggregate (cost=1213.79..1220.12 rows=634 width=16)
-> Hash Join (cost=54.28..1188.79 rows=5000 width=16)
Hash Cond: (jobs.company_id = companies.id)
-> Seq Scan on jobs (cost=0.00..897.00 rows=50000 width=8)
-> Hash (cost=41.78..41.78 rows=1000 width=16)
-> Index Scan using companies_pkey on companies (cost=0.29..41.78 rows=1000 width=16)
Index Cond: (id < 1000)
2. Hash Aggregate
グループキーを key とする、一時的な Hash Tableを作成
ORDER BY を指定する事で、 Sort 処理が入る
ラストステップが Sort と Limitの場合
$ PageViewLog.order(:viewed_at).limit(20).explain
=> EXPLAIN for: SELECT "page_view_logs".* FROM "page_view_logs"
ORDER BY "page_view_logs"."viewed_at" ASC LIMIT 20
QUERY PLAN
-----------------------------------------------------------------------------------
Limit (cost=22026.31..22026.36 rows=20 width=28)
-> Sort (cost=22026.31..23278.87 rows=501024 width=28)
Sort Key: viewed_at
-> Seq Scan on page_view_logs (cost=0.00..8694.24 rows=501024 width=28)
Disk sort になると、すごく遅い
ORDER BY には index
index があればすでに sort 済みなので、sort 処理が不要
$PageViewLogWithIndex.order(:viewed_at).limit(20).explain
=> EXPLAIN for: SELECT "page_view_log_with_indices".* FROM "page_view_log_with_indices"
ORDER BY "page_view_log_with_indices"."viewed_at" ASC LIMIT 20
QUERY PLAN
---------------------------------------------------------------------------------------------------
Limit (cost=0.42..1.09 rows=20 width=28)
-> Index Scan using index_page_view_log_with_indices_on_viewed_at on page_view_log_with_indices
(cost=0.42..16698.78 rows=501024 width=28)
その他、PostgreSQLに特徴的な
愉快な仲間たち
• 1. Window Functions
• 2. Json Type
• 3. Hstore
• 4. Materialized View
• 5. Stored Procedure (PL/pgSQL)
1. Window Functions
http://www.postgresql.org/docs/current/static/tutorial-window.html
$ Company.select('country, rank() OVER (PARTITION BY country ORDER BY id DESC)').explain
=> EXPLAIN for: SELECT country, rank() OVER (PARTITION BY country ORDER BY id DESC) FROM
"companies"
QUERY PLAN
----------------------------------------------------------------------------
WindowAgg (cost=936.35..1155.63 rows=10964 width=16)
-> Sort (cost=936.35..963.76 rows=10964 width=16)
Sort Key: country, id
-> Seq Scan on companies (cost=0.00..200.64 rows=10964 width=16)
Partition ごとに、値を計算
country | rank
--------------+------
britain | 1
china | 1
china | 2
china | 3
country_0 | 1
高機能な集約関数
2. Json Type
Json データを保存可能
ActiveREcord で対応済み
$ Event.create(payload: { kind: "user_renamed", change: ["jack", "john"]})
(0.1ms) BEGIN
SQL (1.7ms) INSERT INTO "events" ("payload", "created_at", "updated_at") VALUES ($1, $2, $3)
RETURNING "id" [["payload", "{"kind":"user_renamed","change":["jack","john"]}"],
["created_at", "2015-12-10 09:57:52.294809"], ["updated_at", "2015-12-10 09:57:52.294809"]]
(0.4ms) COMMIT
# db/migrate/~.rb
def change
create_table :events do |t|
t.json :payload
end
end
2. Json Type
http://www.postgresql.org/docs/current/static/functions-json.html
Json の値取得用の operator が存在
$ Event.where("payload->>'name' = ?", "test1").explain
=> EXPLAIN for: SELECT "events".* FROM "events" WHERE (payload->>'name' = 'test1')
QUERY PLAN
--------------------------------------------------------
Seq Scan on events (cost=0.00..24.85 rows=5 width=52)
Filter: ((payload ->> 'name'::text) = 'test1'::text)
3. Hstore
http://www.postgresql.org/docs/current/static/hstore.html
key, value のペアを1つの絡むに保存可能
問い合わせ用のオペレータあり
4. Materialized View
http://www.postgresql.org/docs/current/static/sql-creatematerializedview.html
キャッシュされた View
高速化は期待できるが、手動で Reflesh する必要あり
5. Stored Procedure (PL/pgSQL)
http://www.postgresql.org/docs/current/static/plpgsql.html
PostgreSQL で実行可能な function を定義可能
まとめ
SQLの実行時に選ばれる実行計画は、index の有無や
統計情報(データの量・分布)に依存する
適切な schema, index, query の選択によって、
高速化しよう
• WHERE, JOIN, ORDER BY, GROUP BY の key には index
• JOIN の前に絞り込めるだけ絞り込む
• JSON Type などもケースバイケースで

Mais conteúdo relacionado

Mais procurados

SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)Takuto Wada
 
MySQLアーキテクチャ図解講座
MySQLアーキテクチャ図解講座MySQLアーキテクチャ図解講座
MySQLアーキテクチャ図解講座Mikiya Okuno
 
例外設計における大罪
例外設計における大罪例外設計における大罪
例外設計における大罪Takuto Wada
 
君はyarn.lockをコミットしているか?
君はyarn.lockをコミットしているか?君はyarn.lockをコミットしているか?
君はyarn.lockをコミットしているか?Teppei Sato
 
ドメイン駆動設計 失敗したことと成功したこと
ドメイン駆動設計 失敗したことと成功したことドメイン駆動設計 失敗したことと成功したこと
ドメイン駆動設計 失敗したことと成功したことBIGLOBE Inc.
 
初心者向けMongoDBのキホン!
初心者向けMongoDBのキホン!初心者向けMongoDBのキホン!
初心者向けMongoDBのキホン!Tetsutaro Watanabe
 
Redisの特徴と活用方法について
Redisの特徴と活用方法についてRedisの特徴と活用方法について
Redisの特徴と活用方法についてYuji Otani
 
SQL大量発行処理をいかにして高速化するか
SQL大量発行処理をいかにして高速化するかSQL大量発行処理をいかにして高速化するか
SQL大量発行処理をいかにして高速化するかShogo Wakayama
 
3週連続DDDその1 ドメイン駆動設計の基本を理解する
3週連続DDDその1  ドメイン駆動設計の基本を理解する3週連続DDDその1  ドメイン駆動設計の基本を理解する
3週連続DDDその1 ドメイン駆動設計の基本を理解する増田 亨
 
PostgreSQLアンチパターン
PostgreSQLアンチパターンPostgreSQLアンチパターン
PostgreSQLアンチパターンSoudai Sone
 
ドメイン駆動設計サンプルコードの徹底解説
ドメイン駆動設計サンプルコードの徹底解説ドメイン駆動設計サンプルコードの徹底解説
ドメイン駆動設計サンプルコードの徹底解説増田 亨
 
ドメイン駆動設計のための Spring の上手な使い方
ドメイン駆動設計のための Spring の上手な使い方ドメイン駆動設計のための Spring の上手な使い方
ドメイン駆動設計のための Spring の上手な使い方増田 亨
 
DBスキーマもバージョン管理したい!
DBスキーマもバージョン管理したい!DBスキーマもバージョン管理したい!
DBスキーマもバージョン管理したい!kwatch
 
[AKIBA.AWS] EC2の基礎 - パフォーマンスを100%引き出すオプション設定 -
[AKIBA.AWS] EC2の基礎 - パフォーマンスを100%引き出すオプション設定 -[AKIBA.AWS] EC2の基礎 - パフォーマンスを100%引き出すオプション設定 -
[AKIBA.AWS] EC2の基礎 - パフォーマンスを100%引き出すオプション設定 -Shuji Kikuchi
 
Spring Boot の Web アプリケーションを Docker に載せて AWS ECS で動かしている話
Spring Boot の Web アプリケーションを Docker に載せて AWS ECS で動かしている話Spring Boot の Web アプリケーションを Docker に載せて AWS ECS で動かしている話
Spring Boot の Web アプリケーションを Docker に載せて AWS ECS で動かしている話JustSystems Corporation
 
SPAセキュリティ入門~PHP Conference Japan 2021
SPAセキュリティ入門~PHP Conference Japan 2021SPAセキュリティ入門~PHP Conference Japan 2021
SPAセキュリティ入門~PHP Conference Japan 2021Hiroshi Tokumaru
 
Azure Api Management 俺的マニュアル 2020年3月版
Azure Api Management 俺的マニュアル 2020年3月版Azure Api Management 俺的マニュアル 2020年3月版
Azure Api Management 俺的マニュアル 2020年3月版貴志 上坂
 
マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!mosa siru
 

Mais procurados (20)

SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)
 
MySQLアーキテクチャ図解講座
MySQLアーキテクチャ図解講座MySQLアーキテクチャ図解講座
MySQLアーキテクチャ図解講座
 
例外設計における大罪
例外設計における大罪例外設計における大罪
例外設計における大罪
 
君はyarn.lockをコミットしているか?
君はyarn.lockをコミットしているか?君はyarn.lockをコミットしているか?
君はyarn.lockをコミットしているか?
 
ドメイン駆動設計 失敗したことと成功したこと
ドメイン駆動設計 失敗したことと成功したことドメイン駆動設計 失敗したことと成功したこと
ドメイン駆動設計 失敗したことと成功したこと
 
初心者向けMongoDBのキホン!
初心者向けMongoDBのキホン!初心者向けMongoDBのキホン!
初心者向けMongoDBのキホン!
 
Redisの特徴と活用方法について
Redisの特徴と活用方法についてRedisの特徴と活用方法について
Redisの特徴と活用方法について
 
SQL大量発行処理をいかにして高速化するか
SQL大量発行処理をいかにして高速化するかSQL大量発行処理をいかにして高速化するか
SQL大量発行処理をいかにして高速化するか
 
3週連続DDDその1 ドメイン駆動設計の基本を理解する
3週連続DDDその1  ドメイン駆動設計の基本を理解する3週連続DDDその1  ドメイン駆動設計の基本を理解する
3週連続DDDその1 ドメイン駆動設計の基本を理解する
 
PostgreSQLアンチパターン
PostgreSQLアンチパターンPostgreSQLアンチパターン
PostgreSQLアンチパターン
 
ドメイン駆動設計サンプルコードの徹底解説
ドメイン駆動設計サンプルコードの徹底解説ドメイン駆動設計サンプルコードの徹底解説
ドメイン駆動設計サンプルコードの徹底解説
 
実践 NestJS
実践 NestJS実践 NestJS
実践 NestJS
 
ドメイン駆動設計のための Spring の上手な使い方
ドメイン駆動設計のための Spring の上手な使い方ドメイン駆動設計のための Spring の上手な使い方
ドメイン駆動設計のための Spring の上手な使い方
 
DBスキーマもバージョン管理したい!
DBスキーマもバージョン管理したい!DBスキーマもバージョン管理したい!
DBスキーマもバージョン管理したい!
 
[AKIBA.AWS] EC2の基礎 - パフォーマンスを100%引き出すオプション設定 -
[AKIBA.AWS] EC2の基礎 - パフォーマンスを100%引き出すオプション設定 -[AKIBA.AWS] EC2の基礎 - パフォーマンスを100%引き出すオプション設定 -
[AKIBA.AWS] EC2の基礎 - パフォーマンスを100%引き出すオプション設定 -
 
Spring Boot の Web アプリケーションを Docker に載せて AWS ECS で動かしている話
Spring Boot の Web アプリケーションを Docker に載せて AWS ECS で動かしている話Spring Boot の Web アプリケーションを Docker に載せて AWS ECS で動かしている話
Spring Boot の Web アプリケーションを Docker に載せて AWS ECS で動かしている話
 
SPAセキュリティ入門~PHP Conference Japan 2021
SPAセキュリティ入門~PHP Conference Japan 2021SPAセキュリティ入門~PHP Conference Japan 2021
SPAセキュリティ入門~PHP Conference Japan 2021
 
Azure Api Management 俺的マニュアル 2020年3月版
Azure Api Management 俺的マニュアル 2020年3月版Azure Api Management 俺的マニュアル 2020年3月版
Azure Api Management 俺的マニュアル 2020年3月版
 
ヤフー社内でやってるMySQLチューニングセミナー大公開
ヤフー社内でやってるMySQLチューニングセミナー大公開ヤフー社内でやってるMySQLチューニングセミナー大公開
ヤフー社内でやってるMySQLチューニングセミナー大公開
 
マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!
 

Destaque

Ruby on Rails on MySQL チューニング入門
Ruby on Rails on MySQL チューニング入門Ruby on Rails on MySQL チューニング入門
Ruby on Rails on MySQL チューニング入門だいすけ さとう
 
MySQL INDEX+EXPLAIN入門
MySQL INDEX+EXPLAIN入門MySQL INDEX+EXPLAIN入門
MySQL INDEX+EXPLAIN入門infinite_loop
 
Web エンジニアが postgre sql を選ぶ 3 つの理由
Web エンジニアが postgre sql を選ぶ 3 つの理由Web エンジニアが postgre sql を選ぶ 3 つの理由
Web エンジニアが postgre sql を選ぶ 3 つの理由Soudai Sone
 
ディープラーニングでおそ松さんの6つ子は見分けられるのか? FIT2016
ディープラーニングでおそ松さんの6つ子は見分けられるのか? FIT2016ディープラーニングでおそ松さんの6つ子は見分けられるのか? FIT2016
ディープラーニングでおそ松さんの6つ子は見分けられるのか? FIT2016Yota Ishida
 
リクルートにおける画像解析事例紹介
リクルートにおける画像解析事例紹介リクルートにおける画像解析事例紹介
リクルートにおける画像解析事例紹介Recruit Technologies
 
モバイルアプリ開発体制の継続的改善 in JaSST'14 Hokkaido
モバイルアプリ開発体制の継続的改善 in JaSST'14 Hokkaidoモバイルアプリ開発体制の継続的改善 in JaSST'14 Hokkaido
モバイルアプリ開発体制の継続的改善 in JaSST'14 HokkaidoKazuaki Matsuo
 
Oracleでモテる実行計画を固定させる2つの方法
Oracleでモテる実行計画を固定させる2つの方法Oracleでモテる実行計画を固定させる2つの方法
Oracleでモテる実行計画を固定させる2つの方法Daiki Mogmet Ito
 
WebSocket For Web Rubyists
WebSocket For Web RubyistsWebSocket For Web Rubyists
WebSocket For Web RubyistsMu-Fan Teng
 
Resemaraを支えた技術 フライングゲットガチャの舞台裏 #ksgstudy #ドリコム
Resemaraを支えた技術 フライングゲットガチャの舞台裏 #ksgstudy #ドリコムResemaraを支えた技術 フライングゲットガチャの舞台裏 #ksgstudy #ドリコム
Resemaraを支えた技術 フライングゲットガチャの舞台裏 #ksgstudy #ドリコムGo Sueyoshi (a.k.a sue445)
 
アクセスプラン(実行計画)の読み方入門
アクセスプラン(実行計画)の読み方入門アクセスプラン(実行計画)の読み方入門
アクセスプラン(実行計画)の読み方入門Akira Shimosako
 
20161122_How to start Recruiting Engineers_mercari_ishiguro
20161122_How to start Recruiting Engineers_mercari_ishiguro20161122_How to start Recruiting Engineers_mercari_ishiguro
20161122_How to start Recruiting Engineers_mercari_ishiguroTakaya Ishiguro
 
SQL 脳から見た Ruby
SQL 脳から見た RubySQL 脳から見た Ruby
SQL 脳から見た Rubyyancya
 
サイト/ブログから本文抽出する方法
サイト/ブログから本文抽出する方法サイト/ブログから本文抽出する方法
サイト/ブログから本文抽出する方法Takuro Sasaki
 
Wantedlyを使った採用 LT 20120704
Wantedlyを使った採用 LT 20120704Wantedlyを使った採用 LT 20120704
Wantedlyを使った採用 LT 20120704Akitsugu Otani
 
Redmineを快適に使うためのおすすめ初期設定
Redmineを快適に使うためのおすすめ初期設定Redmineを快適に使うためのおすすめ初期設定
Redmineを快適に使うためのおすすめ初期設定Go Maeda
 
デキるプログラマだけが知っているコードレビュー7つの秘訣
デキるプログラマだけが知っているコードレビュー7つの秘訣デキるプログラマだけが知っているコードレビュー7つの秘訣
デキるプログラマだけが知っているコードレビュー7つの秘訣Masahiro Nishimi
 
DevOps Practices: Configuration as Code
DevOps Practices:Configuration as CodeDevOps Practices:Configuration as Code
DevOps Practices: Configuration as CodeDoug Seven
 
クックパッドの開発プロセス
クックパッドの開発プロセスクックパッドの開発プロセス
クックパッドの開発プロセスHiroyuki Inoue
 
超絶技巧プログラミングと Ruby 3.0 (大江戸 Ruby 会議 05 コミッタ LT)
超絶技巧プログラミングと Ruby 3.0 (大江戸 Ruby 会議 05 コミッタ LT)超絶技巧プログラミングと Ruby 3.0 (大江戸 Ruby 会議 05 コミッタ LT)
超絶技巧プログラミングと Ruby 3.0 (大江戸 Ruby 会議 05 コミッタ LT)mametter
 

Destaque (20)

Ruby on Rails on MySQL チューニング入門
Ruby on Rails on MySQL チューニング入門Ruby on Rails on MySQL チューニング入門
Ruby on Rails on MySQL チューニング入門
 
MySQL INDEX+EXPLAIN入門
MySQL INDEX+EXPLAIN入門MySQL INDEX+EXPLAIN入門
MySQL INDEX+EXPLAIN入門
 
Web エンジニアが postgre sql を選ぶ 3 つの理由
Web エンジニアが postgre sql を選ぶ 3 つの理由Web エンジニアが postgre sql を選ぶ 3 つの理由
Web エンジニアが postgre sql を選ぶ 3 つの理由
 
ディープラーニングでおそ松さんの6つ子は見分けられるのか? FIT2016
ディープラーニングでおそ松さんの6つ子は見分けられるのか? FIT2016ディープラーニングでおそ松さんの6つ子は見分けられるのか? FIT2016
ディープラーニングでおそ松さんの6つ子は見分けられるのか? FIT2016
 
リクルートにおける画像解析事例紹介
リクルートにおける画像解析事例紹介リクルートにおける画像解析事例紹介
リクルートにおける画像解析事例紹介
 
モバイルアプリ開発体制の継続的改善 in JaSST'14 Hokkaido
モバイルアプリ開発体制の継続的改善 in JaSST'14 Hokkaidoモバイルアプリ開発体制の継続的改善 in JaSST'14 Hokkaido
モバイルアプリ開発体制の継続的改善 in JaSST'14 Hokkaido
 
Oracleでモテる実行計画を固定させる2つの方法
Oracleでモテる実行計画を固定させる2つの方法Oracleでモテる実行計画を固定させる2つの方法
Oracleでモテる実行計画を固定させる2つの方法
 
WebSocket For Web Rubyists
WebSocket For Web RubyistsWebSocket For Web Rubyists
WebSocket For Web Rubyists
 
Resemaraを支えた技術 フライングゲットガチャの舞台裏 #ksgstudy #ドリコム
Resemaraを支えた技術 フライングゲットガチャの舞台裏 #ksgstudy #ドリコムResemaraを支えた技術 フライングゲットガチャの舞台裏 #ksgstudy #ドリコム
Resemaraを支えた技術 フライングゲットガチャの舞台裏 #ksgstudy #ドリコム
 
アクセスプラン(実行計画)の読み方入門
アクセスプラン(実行計画)の読み方入門アクセスプラン(実行計画)の読み方入門
アクセスプラン(実行計画)の読み方入門
 
20161122_How to start Recruiting Engineers_mercari_ishiguro
20161122_How to start Recruiting Engineers_mercari_ishiguro20161122_How to start Recruiting Engineers_mercari_ishiguro
20161122_How to start Recruiting Engineers_mercari_ishiguro
 
SQL 脳から見た Ruby
SQL 脳から見た RubySQL 脳から見た Ruby
SQL 脳から見た Ruby
 
サイト/ブログから本文抽出する方法
サイト/ブログから本文抽出する方法サイト/ブログから本文抽出する方法
サイト/ブログから本文抽出する方法
 
Wantedlyを使った採用 LT 20120704
Wantedlyを使った採用 LT 20120704Wantedlyを使った採用 LT 20120704
Wantedlyを使った採用 LT 20120704
 
PostgreSQLの運用・監視にまつわるエトセトラ
PostgreSQLの運用・監視にまつわるエトセトラPostgreSQLの運用・監視にまつわるエトセトラ
PostgreSQLの運用・監視にまつわるエトセトラ
 
Redmineを快適に使うためのおすすめ初期設定
Redmineを快適に使うためのおすすめ初期設定Redmineを快適に使うためのおすすめ初期設定
Redmineを快適に使うためのおすすめ初期設定
 
デキるプログラマだけが知っているコードレビュー7つの秘訣
デキるプログラマだけが知っているコードレビュー7つの秘訣デキるプログラマだけが知っているコードレビュー7つの秘訣
デキるプログラマだけが知っているコードレビュー7つの秘訣
 
DevOps Practices: Configuration as Code
DevOps Practices:Configuration as CodeDevOps Practices:Configuration as Code
DevOps Practices: Configuration as Code
 
クックパッドの開発プロセス
クックパッドの開発プロセスクックパッドの開発プロセス
クックパッドの開発プロセス
 
超絶技巧プログラミングと Ruby 3.0 (大江戸 Ruby 会議 05 コミッタ LT)
超絶技巧プログラミングと Ruby 3.0 (大江戸 Ruby 会議 05 コミッタ LT)超絶技巧プログラミングと Ruby 3.0 (大江戸 Ruby 会議 05 コミッタ LT)
超絶技巧プログラミングと Ruby 3.0 (大江戸 Ruby 会議 05 コミッタ LT)
 

Semelhante a RailsエンジニアのためのSQLチューニング速習会

20090107 Postgre Sqlチューニング(Sql編)
20090107 Postgre Sqlチューニング(Sql編)20090107 Postgre Sqlチューニング(Sql編)
20090107 Postgre Sqlチューニング(Sql編)Hiromu Shioya
 
Pgunconf 20121212-postgeres fdw
Pgunconf 20121212-postgeres fdwPgunconf 20121212-postgeres fdw
Pgunconf 20121212-postgeres fdwToshi Harada
 
SQLチューニング入門 入門編
SQLチューニング入門 入門編SQLチューニング入門 入門編
SQLチューニング入門 入門編Miki Shimogai
 
Jpug study-pq 20170121
Jpug study-pq 20170121Jpug study-pq 20170121
Jpug study-pq 20170121Kosuke Kida
 
Maatkit で MySQL チューニング
Maatkit で MySQL チューニングMaatkit で MySQL チューニング
Maatkit で MySQL チューニングKensuke Nagae
 
Introduction new features in Spark 3.0
Introduction new features in Spark 3.0Introduction new features in Spark 3.0
Introduction new features in Spark 3.0Kazuaki Ishizaki
 
PostgreSQLクエリ実行の基礎知識 ~Explainを読み解こう~
PostgreSQLクエリ実行の基礎知識 ~Explainを読み解こう~PostgreSQLクエリ実行の基礎知識 ~Explainを読み解こう~
PostgreSQLクエリ実行の基礎知識 ~Explainを読み解こう~Miki Shimogai
 
問合せ最適化インサイド
問合せ最適化インサイド問合せ最適化インサイド
問合せ最適化インサイドTakahiro Itagaki
 
20221111_JPUG_CustomScan_API
20221111_JPUG_CustomScan_API20221111_JPUG_CustomScan_API
20221111_JPUG_CustomScan_APIKohei KaiGai
 
PostgreSQL13 新機能紹介
PostgreSQL13 新機能紹介PostgreSQL13 新機能紹介
PostgreSQL13 新機能紹介Satoshi Hirata
 
jQuery Performance Tips – jQueryにおける高速化 -
jQuery Performance Tips – jQueryにおける高速化 -jQuery Performance Tips – jQueryにおける高速化 -
jQuery Performance Tips – jQueryにおける高速化 -Hayato Mizuno
 
[東京] JapanSharePointGroup 勉強会 #2
[東京] JapanSharePointGroup 勉強会 #2[東京] JapanSharePointGroup 勉強会 #2
[東京] JapanSharePointGroup 勉強会 #2Atsuo Yamasaki
 
T sql の parse と generator
T sql の parse と generatorT sql の parse と generator
T sql の parse と generatorOda Shinsuke
 
[ウェビナー] Build 2018 アップデート ~ データ プラットフォーム/IoT編 ~
[ウェビナー] Build 2018 アップデート ~ データ プラットフォーム/IoT編 ~[ウェビナー] Build 2018 アップデート ~ データ プラットフォーム/IoT編 ~
[ウェビナー] Build 2018 アップデート ~ データ プラットフォーム/IoT編 ~Naoki (Neo) SATO
 
[機械学習]文章のクラス分類
[機械学習]文章のクラス分類[機械学習]文章のクラス分類
[機械学習]文章のクラス分類Tetsuya Hasegawa
 
YugabyteDBの実行計画を眺める(NewSQL/分散SQLデータベースよろず勉強会 #3 発表資料)
YugabyteDBの実行計画を眺める(NewSQL/分散SQLデータベースよろず勉強会 #3 発表資料)YugabyteDBの実行計画を眺める(NewSQL/分散SQLデータベースよろず勉強会 #3 発表資料)
YugabyteDBの実行計画を眺める(NewSQL/分散SQLデータベースよろず勉強会 #3 発表資料)NTT DATA Technology & Innovation
 
Let's scale-out PostgreSQL using Citus (Japanese)
Let's scale-out PostgreSQL using Citus (Japanese)Let's scale-out PostgreSQL using Citus (Japanese)
Let's scale-out PostgreSQL using Citus (Japanese)Noriyoshi Shinoda
 
Chugoku db 17th-postgresql-9.6
Chugoku db 17th-postgresql-9.6Chugoku db 17th-postgresql-9.6
Chugoku db 17th-postgresql-9.6Toshi Harada
 

Semelhante a RailsエンジニアのためのSQLチューニング速習会 (20)

20090107 Postgre Sqlチューニング(Sql編)
20090107 Postgre Sqlチューニング(Sql編)20090107 Postgre Sqlチューニング(Sql編)
20090107 Postgre Sqlチューニング(Sql編)
 
Pgunconf 20121212-postgeres fdw
Pgunconf 20121212-postgeres fdwPgunconf 20121212-postgeres fdw
Pgunconf 20121212-postgeres fdw
 
SQLチューニング入門 入門編
SQLチューニング入門 入門編SQLチューニング入門 入門編
SQLチューニング入門 入門編
 
Jpug study-pq 20170121
Jpug study-pq 20170121Jpug study-pq 20170121
Jpug study-pq 20170121
 
Maatkit で MySQL チューニング
Maatkit で MySQL チューニングMaatkit で MySQL チューニング
Maatkit で MySQL チューニング
 
Chugokudb18_2
Chugokudb18_2Chugokudb18_2
Chugokudb18_2
 
Introduction new features in Spark 3.0
Introduction new features in Spark 3.0Introduction new features in Spark 3.0
Introduction new features in Spark 3.0
 
PostgreSQLクエリ実行の基礎知識 ~Explainを読み解こう~
PostgreSQLクエリ実行の基礎知識 ~Explainを読み解こう~PostgreSQLクエリ実行の基礎知識 ~Explainを読み解こう~
PostgreSQLクエリ実行の基礎知識 ~Explainを読み解こう~
 
問合せ最適化インサイド
問合せ最適化インサイド問合せ最適化インサイド
問合せ最適化インサイド
 
20221111_JPUG_CustomScan_API
20221111_JPUG_CustomScan_API20221111_JPUG_CustomScan_API
20221111_JPUG_CustomScan_API
 
PostgreSQL13 新機能紹介
PostgreSQL13 新機能紹介PostgreSQL13 新機能紹介
PostgreSQL13 新機能紹介
 
jQuery Performance Tips – jQueryにおける高速化 -
jQuery Performance Tips – jQueryにおける高速化 -jQuery Performance Tips – jQueryにおける高速化 -
jQuery Performance Tips – jQueryにおける高速化 -
 
PostgreSQL 12の話
PostgreSQL 12の話PostgreSQL 12の話
PostgreSQL 12の話
 
[東京] JapanSharePointGroup 勉強会 #2
[東京] JapanSharePointGroup 勉強会 #2[東京] JapanSharePointGroup 勉強会 #2
[東京] JapanSharePointGroup 勉強会 #2
 
T sql の parse と generator
T sql の parse と generatorT sql の parse と generator
T sql の parse と generator
 
[ウェビナー] Build 2018 アップデート ~ データ プラットフォーム/IoT編 ~
[ウェビナー] Build 2018 アップデート ~ データ プラットフォーム/IoT編 ~[ウェビナー] Build 2018 アップデート ~ データ プラットフォーム/IoT編 ~
[ウェビナー] Build 2018 アップデート ~ データ プラットフォーム/IoT編 ~
 
[機械学習]文章のクラス分類
[機械学習]文章のクラス分類[機械学習]文章のクラス分類
[機械学習]文章のクラス分類
 
YugabyteDBの実行計画を眺める(NewSQL/分散SQLデータベースよろず勉強会 #3 発表資料)
YugabyteDBの実行計画を眺める(NewSQL/分散SQLデータベースよろず勉強会 #3 発表資料)YugabyteDBの実行計画を眺める(NewSQL/分散SQLデータベースよろず勉強会 #3 発表資料)
YugabyteDBの実行計画を眺める(NewSQL/分散SQLデータベースよろず勉強会 #3 発表資料)
 
Let's scale-out PostgreSQL using Citus (Japanese)
Let's scale-out PostgreSQL using Citus (Japanese)Let's scale-out PostgreSQL using Citus (Japanese)
Let's scale-out PostgreSQL using Citus (Japanese)
 
Chugoku db 17th-postgresql-9.6
Chugoku db 17th-postgresql-9.6Chugoku db 17th-postgresql-9.6
Chugoku db 17th-postgresql-9.6
 

RailsエンジニアのためのSQLチューニング速習会

  • 3.
  • 4. • 1. SQLが実行されるとき、RDBの中で何が起きるか を知る • 2. Explain の読み方、適切なindexの張り方を知る • 3. チューニングの為に気をつけるポイントを知る 今日速習する内容
  • 5. セットアップ $ git clone https://github.com/south37/sql-tuning $ git checkout sql-tuning $ bin/rake db:create $ pg_restore -j 4 --verbose --no-acl --no-owner -d sql-tuning-dev db.dump
  • 7. ActiveRecord::Relation#explain $ Job.joins(:company).group('companies.country').where('companies.id < 1000’) .select('companies.country', 'COUNT(jobs.id)').explain => EXPLAIN for: SELECT companies.country, COUNT(jobs.id) FROM "jobs" INNER JOIN "companies" ON "companies"."id" = "jobs"."company_id" WHERE (companies.id < 1000) GROUP BY companies.country QUERY PLAN ------------------------------------------------------------------------------------------------------- HashAggregate (cost=1213.79..1220.12 rows=634 width=16) -> Hash Join (cost=54.28..1188.79 rows=5000 width=16) Hash Cond: (jobs.company_id = companies.id) -> Seq Scan on jobs (cost=0.00..897.00 rows=50000 width=8) -> Hash (cost=41.78..41.78 rows=1000 width=16) -> Index Scan using companies_pkey on companies (cost=0.29..41.78 rows=1000 width=16) Index Cond: (id < 1000)
  • 8. $ Job.joins(:company).group('companies.country').where('companies.id < 1000’) .select('companies.country', 'COUNT(jobs.id)').explain => EXPLAIN for: SELECT companies.country, COUNT(jobs.id) FROM "jobs" INNER JOIN "companies" ON "companies"."id" = "jobs"."company_id" WHERE (companies.id < 1000) GROUP BY companies.country QUERY PLAN ------------------------------------------------------------------------------------------------------- HashAggregate (cost=1213.79..1220.12 rows=634 width=16) -> Hash Join (cost=54.28..1188.79 rows=5000 width=16) Hash Cond: (jobs.company_id = companies.id) -> Seq Scan on jobs (cost=0.00..897.00 rows=50000 width=8) -> Hash (cost=41.78..41.78 rows=1000 width=16) -> Index Scan using companies_pkey on companies (cost=0.29..41.78 rows=1000 width=16) Index Cond: (id < 1000) ツリー構造 Explainの見方
  • 9. 実行計画はツリー状の構造 ツリー構造 HashAggregate Hash Join Seq ScanHash Index Scan $ Job.joins(:company).group('companies.country').where('companies.id < 1000’) .select('companies.country', 'COUNT(jobs.id)').explain => EXPLAIN for: SELECT companies.country, COUNT(jobs.id) FROM "jobs" INNER JOIN "companies" ON "companies"."id" = "jobs"."company_id" WHERE (companies.id < 1000) GROUP BY companies.country
  • 10. $ Job.joins(:company).group('companies.country').where('companies.id < 1000’) .select('companies.country', 'COUNT(jobs.id)').explain => EXPLAIN for: SELECT companies.country, COUNT(jobs.id) FROM "jobs" INNER JOIN "companies" ON "companies"."id" = "jobs"."company_id" WHERE (companies.id < 1000) GROUP BY companies.country QUERY PLAN ------------------------------------------------------------------------------------------------------- HashAggregate (cost=1213.79..1220.12 rows=634 width=16) -> Hash Join (cost=54.28..1188.79 rows=5000 width=16) Hash Cond: (jobs.company_id = companies.id) -> Seq Scan on jobs (cost=0.00..897.00 rows=50000 width=8) -> Hash (cost=41.78..41.78 rows=1000 width=16) -> Index Scan using companies_pkey on companies (cost=0.29..41.78 rows=1000 width=16) Index Cond: (id < 1000) コストの見方
  • 11. コストの見方 Seq Scan on jobs (cost=0.00..897.00 rows=50000 width=8) Index Scan using companies_pkey on companies (cost=0.29..41.78 rows=1000 width=16) 初期化コスト 総コスト 取得行数 1行あたりのデータサイズ(バイト) 総コスト = 初期化コスト + (走査行数 × 1行あたりの取得コスト ) index 使うと初期化コストが存在
  • 12. ANALYSE をつけると実際に実行 $ ActiveRecord::Base.connection.execute("EXPLAIN ANALYSE #{Job.joins(:company).group('companies.country').where('companies.id < 1000').select('companies.country', 'COUNT(jobs.id)').to_sql}").each { |row| print row['QUERY PLAN']+"n" } HashAggregate (cost=1213.79..1220.12 rows=634 width=16) (actual time=20.290..20.465 rows=950 loops=1) -> Hash Join (cost=54.28..1188.79 rows=5000 width=16) (actual time=1.018..18.102 rows=4983 loops=1) Hash Cond: (jobs.company_id = companies.id) -> Seq Scan on jobs (cost=0.00..897.00 rows=50000 width=8) (actual time=0.009..6.352 rows=50000 loops=1) -> Hash (cost=41.78..41.78 rows=1000 width=16) (actual time=0.995..0.995 rows=999 loops=1) Buckets: 1024 Batches: 1 Memory Usage: 51kB -> Index Scan using companies_pkey on companies (cost=0.29..41.78 rows=1000 width=16) (actual time=0.022..0.527 rows=999 loops=1) Index Cond: (id < 1000)
  • 14. HashAggregate Hash Join Seq ScanHash Index Scan 最初のステップはデータの取得 $ Job.joins(:company).group('companies.country').where('companies.id < 1000’) .select('companies.country', 'COUNT(jobs.id)').explain => EXPLAIN for: SELECT companies.country, COUNT(jobs.id) FROM "jobs" INNER JOIN "companies" ON "companies"."id" = "jobs"."company_id" WHERE (companies.id < 1000) GROUP BY companies.country
  • 16. index の仕組み B-tree index • ノードあたり数百要素 • 300要素として、3段で2,700 万件格納 高速なデータ取得
  • 17. index の利用 $ Job.where(id: 1).explain => EXPLAIN for: SELECT "jobs".* FROM "jobs" WHERE "jobs"."id" = $1 [["id", 1]] QUERY PLAN ----------------------------------------------------------------------- Index Scan using jobs_pkey on jobs (cost=0.29..8.31 rows=1 width=28) Index Cond: (id = 1) $ Job.where(id_without_index: 1).explain => EXPLAIN for: SELECT "jobs".* FROM "jobs" WHERE "jobs"."id_without_index" = $1 [["id_without_index", 1]] QUERY PLAN -------------------------------------------------------- Seq Scan on jobs (cost=0.00..1022.00 rows=1 width=28) Filter: (id_without_index = 1) index有り index無し
 (Seq Scan)
  • 18. index バッドパターン その1 「index を貼ったカラムに演算」 $ Profile.where('lower(email) = ?', 'minami@wantedly.com').limit(1).explain => EXPLAIN for: SELECT "profiles".* FROM "profiles" WHERE (lower(email) = 'minami@wantedly.com') LIMIT 1 QUERY PLAN ------------------------------------------------------------------ Limit (cost=0.00..5.08 rows=1 width=54) -> Seq Scan on profiles (cost=0.00..254.00 rows=50 width=54) Filter: (lower(email) = 'minami@wantedly.com'::text) index は key の比較で sort してるので、 演算が行われると利用できない 「クエリ書き換え」 or 「Indexes on Expression を利用」
  • 19. index バッドパターン その2 「絞り込み条件の緩いWHERE」 $ Profile.where(gender: ‘female').explain => EXPLAIN for: SELECT "profiles".* FROM "profiles" WHERE "profiles"."gender" = $1 [["gender", "female"]] QUERY PLAN -------------------------------------------------------------- Seq Scan on profiles (cost=0.00..229.00 rows=5038 width=54) Filter: (gender = 'female'::text) male female profiles.gender の分布 デフォルトだと、 4分の1以下に絞り込まれる必要あり
  • 21. HDDへのランダムアクセスと シーケンシャルアクセスの速度差が原因 Seq Scan Index Scan (Random Access) 1 2 3 4 1 23 4 1要素単位だと高コスト
  • 22. ちゃんと絞り込まれるならOK $ BoxerProfile.where(gender: ‘female').explain => EXPLAIN for: SELECT "boxer_profiles".* FROM "boxer_profiles" WHERE "boxer_profiles"."gender" = $1 [["gender", "female"]] QUERY PLAN ------------------------------------------------------------------------------------------------- Bitmap Heap Scan on boxer_profiles (cost=28.08..114.66 rows=1006 width=25) Recheck Cond: (gender = 'female'::text) -> Bitmap Index Scan on index_boxer_profiles_on_gender (cost=0.00..27.83 rows=1006 width=0) Index Cond: (gender = 'female'::text) male female profiles.gender の分布 データの分布 = 「統計情報」が大事
  • 24. index のデメリット • 1. 更新に時間がかかるようになる • 2. HOT が効かない
  • 26. 2. HOT が効かない HOTはPostgreSQL のカラムの更新を早くする仕組み (必要な箇所のみを更新する) 詳しくはこちら: http://lets.postgresql.jp/documents/tutorial/hot_1/
  • 27. いろいろな index • 1. Unique Indexes • 2. Multicolumn Indexes • 3. Indexes on Expressions • 4. Partial Indexes
  • 28. いろいろな index • 1. Unique Indexes • 2. Multicolumn Indexes • 3. Indexes on Expressions • 4. Partial Indexes
  • 29. 2. Multicolumn Indexes create_table "tourist_spots", force: :cascade do |t| t.text "country" t.text "city" end add_index "tourist_spots", ["country", "city"], name: "index_tourist_spots_on_country_and_city", using: :btree 複数カラムに対しての index
  • 30. 2. Multicolumn Indexes $ TouristSpot.where(country: 'japan', city: 'tokyo').explain => EXPLAIN for: SELECT "tourist_spots".* FROM "tourist_spots" WHERE "tourist_spots"."country" = $1 AND "tourist_spots"."city" = $2 [["country", "japan"], ["city", "tokyo"]] QUERY PLAN -------------------------------------------------------------------------------------------------------------- Index Scan using index_tourist_spots_on_country_and_city on tourist_spots (cost=0.42..8.44 rows=1 width=52) Index Cond: ((country = 'japan'::text) AND (city = 'tokyo'::text)) Multicolumn index有り $ TouristSpotWithoutMultipleIndex.where(country: 'japan', city: 'tokyo').explain => EXPLAIN for: SELECT "tourist_spot_without_multiple_indices".* FROM "tourist_spot_without_multiple_indices" WHERE "tourist_spot_without_multiple_indices"."country" = $1 AND "tourist_spot_without_multiple_indices"."city" = $2 [["country", "japan"], ["city", "tokyo"]] QUERY PLAN ------------------------------------------------------------------------------------------------------------- Index Scan using index_tourist_spot_without_multiple_indices_on_city on tourist_spot_without_multiple_indices (cost=0.42..8.44 rows=1 width=52) Index Cond: (city = 'tokyo'::text) Filter: (country = 'japan'::text) Multicolumn index無し
  • 32. 2. Multicolumn Indexes 先頭の要素の index としても効く より詳細を知りたい方は: http://www.postgresql.org/docs/current/static/indexes-multicolumn.html $ TouristSpot.where(country: 'japan').explain => EXPLAIN for: SELECT "tourist_spots".* FROM "tourist_spots" WHERE "tourist_spots"."country" = $1 [["country", "japan"]] QUERY PLAN ------------------------------------------------------------------------------------------------------- Bitmap Heap Scan on tourist_spots (cost=4.50..41.67 rows=10 width=52) Recheck Cond: (country = 'japan'::text) -> Bitmap Index Scan on index_tourist_spots_on_country_and_city (cost=0.00..4.49 rows=10 width=0) Index Cond: (country = 'japan'::text)
  • 33. 3. Indexes on Expressions 関数などの返り値を key として index を作る事ができる # db/migrate/db/migrate/20151210065304_add_indexes_on_~.rb def up execute <<-SQL CREATE INDEX index_profiles_with_indexes_on_expressions_on_lower_email ON profiles_with_indexes_on_expressions(lower(email)); SQL end def down execute <<-SQL DROP INDEX index_profiles_with_indexes_on_expressions_on_lower_email SQL end
  • 34. 3. Indexes on Expressions lower(email) を index として利用 詳細はこちら: http://www.postgresql.org/docs/current/static/indexes-expressional.html $ ProfilesWithIndexesOnExpression.where("lower(email) = 'minami@wantedly.com'").explain => EXPLAIN for: SELECT "profiles_with_indexes_on_expressions".* FROM "profiles_with_indexes_on_expressions" WHERE (lower(email) = 'minami@wantedly.com') QUERY PLAN ------------------------------------------------------------------------------------------------------ Index Scan using index_profiles_with_indexes_on_expressions_on_lower_email on profiles_with_indexes_on_expressions (cost=0.29..8.30 rows=1 width=48) Index Cond: (lower(email) = 'minami@wantedly.com'::text)
  • 35. HashAggregate Hash Join Seq ScanHash Index Scan 次のステップはデータの結合(JOIN) $ Job.joins(:company).group('companies.country').where('companies.id < 1000’) .select('companies.country', 'COUNT(jobs.id)').explain => EXPLAIN for: SELECT companies.country, COUNT(jobs.id) FROM "jobs" INNER JOIN "companies" ON "companies"."id" = "jobs"."company_id" WHERE (companies.id < 1000) GROUP BY companies.country
  • 37. 1. Nested Loop テーブル1と2に対して、すべての組み合わせを試す O(N × M) … 極めて遅い レコード数N レコード数M • レコード数が少なければ高速 • Table 2 に index を貼れば、 高速化が可能
  • 38. 2. Hash Join テーブル2に対して、一度フルスキャンしてHashMapを作成 O(N + M) …Hash 生成のコストはかかるが、 Nested Loop よりはマシ テーブル2の全てのレコード をメモリに載せる必要あり
  • 39. $ Job.joins(:company).group('companies.country').where('companies.id < 1000’) .select('companies.country', 'COUNT(jobs.id)').explain => EXPLAIN for: SELECT companies.country, COUNT(jobs.id) FROM "jobs" INNER JOIN "companies" ON "companies"."id" = "jobs"."company_id" WHERE (companies.id < 1000) GROUP BY companies.country QUERY PLAN ------------------------------------------------------------------------------------------------------- HashAggregate (cost=1213.79..1220.12 rows=634 width=16) -> Hash Join (cost=54.28..1188.79 rows=5000 width=16) Hash Cond: (jobs.company_id = companies.id) -> Seq Scan on jobs (cost=0.00..897.00 rows=50000 width=8) -> Hash (cost=41.78..41.78 rows=1000 width=16) -> Index Scan using companies_pkey on companies (cost=0.29..41.78 rows=1000 width=16) Index Cond: (id < 1000) Hash Join のコスト Hash の生成コスト(初期化コスト)
  • 40. 3. Merge Join ソート済みのテーブル1と2に対して、1度だけフルスキャン O(N+M) …最も高速 JOIN に使うカラムには、 index を貼りましょう
  • 41. index があっても JOIN が遅くなるケース どんなに高速化しても O(N+M) にしかならない Nが大きいと遅くなる
  • 42. index があっても JOIN が遅くなるケース $ User.joins(:profile).select('COUNT(*)').explain => EXPLAIN for: SELECT COUNT(*) FROM "users" INNER JOIN "profiles" ON "profiles"."user_id" = "users"."id" QUERY PLAN -------------------------------------------------------------------------------- Aggregate (cost=23288.72..23288.73 rows=1 width=0) -> Hash Join (cost=354.30..23261.80 rows=10769 width=0) Hash Cond: (users.id = profiles.user_id) -> Seq Scan on users (cost=0.00..11441.64 rows=698964 width=4) -> Hash (cost=219.69..219.69 rows=10769 width=4) -> Seq Scan on profiles (cost=0.00..219.69 rows=10769 width=4)
  • 43. JOIN される left relation は、 事前に絞り込んでおこう $ User.where(registered: true).joins(:profile).select('COUNT(*)').explain => EXPLAIN for: SELECT COUNT(*) FROM "users" INNER JOIN "profiles" ON "profiles"."user_id" = "users"."id" WHERE "users"."registered" = $1 [["registered", "t"]] QUERY PLAN ----------------------------------------------------------------------------------------------------------- Aggregate (cost=8131.17..8131.18 rows=1 width=0) -> Hash Join (cost=1850.65..8128.51 rows=1065 width=0) Hash Cond: (users.id = profiles.user_id) -> Bitmap Heap Scan on users (cost=1496.35..6639.86 rows=69151 width=4) Filter: registered -> Bitmap Index Scan on index_users_on_registered (cost=0.00..1479.06 rows=69151 width=0) Index Cond: (registered = true) -> Hash (cost=219.69..219.69 rows=10769 width=4) -> Seq Scan on profiles (cost=0.00..219.69 rows=10769 width=4)
  • 44. HashAggregate Hash Join Seq ScanHash Index Scan ラストステップはデータの集約 (Aggregate) $ Job.joins(:company).group('companies.country').where('companies.id < 1000’) .select('companies.country', 'COUNT(jobs.id)').explain => EXPLAIN for: SELECT companies.country, COUNT(jobs.id) FROM "jobs" INNER JOIN "companies" ON "companies"."id" = "jobs"."company_id" WHERE (companies.id < 1000) GROUP BY companies.country
  • 45. GROUP BY の2つのアルゴリズム • 1. Group Aggregate • 2. Hash Aggregate
  • 46. 1. Group Aggregate 入力されたデータをグループキーで ソート後、 各グループを順番に処理 (index があってソート済みならパイプライン化も可能)
  • 47. $ Job.joins(:company).group('companies.country').where('companies.id < 1000’) .select('companies.country', 'COUNT(jobs.id)').explain => EXPLAIN for: SELECT companies.country, COUNT(jobs.id) FROM "jobs" INNER JOIN "companies" ON "companies"."id" = "jobs"."company_id" WHERE (companies.id < 1000) GROUP BY companies.country QUERY PLAN ------------------------------------------------------------------------------------------------------- HashAggregate (cost=1213.79..1220.12 rows=634 width=16) -> Hash Join (cost=54.28..1188.79 rows=5000 width=16) Hash Cond: (jobs.company_id = companies.id) -> Seq Scan on jobs (cost=0.00..897.00 rows=50000 width=8) -> Hash (cost=41.78..41.78 rows=1000 width=16) -> Index Scan using companies_pkey on companies (cost=0.29..41.78 rows=1000 width=16) Index Cond: (id < 1000) 2. Hash Aggregate グループキーを key とする、一時的な Hash Tableを作成
  • 48. ORDER BY を指定する事で、 Sort 処理が入る ラストステップが Sort と Limitの場合 $ PageViewLog.order(:viewed_at).limit(20).explain => EXPLAIN for: SELECT "page_view_logs".* FROM "page_view_logs" ORDER BY "page_view_logs"."viewed_at" ASC LIMIT 20 QUERY PLAN ----------------------------------------------------------------------------------- Limit (cost=22026.31..22026.36 rows=20 width=28) -> Sort (cost=22026.31..23278.87 rows=501024 width=28) Sort Key: viewed_at -> Seq Scan on page_view_logs (cost=0.00..8694.24 rows=501024 width=28) Disk sort になると、すごく遅い
  • 49. ORDER BY には index index があればすでに sort 済みなので、sort 処理が不要 $PageViewLogWithIndex.order(:viewed_at).limit(20).explain => EXPLAIN for: SELECT "page_view_log_with_indices".* FROM "page_view_log_with_indices" ORDER BY "page_view_log_with_indices"."viewed_at" ASC LIMIT 20 QUERY PLAN --------------------------------------------------------------------------------------------------- Limit (cost=0.42..1.09 rows=20 width=28) -> Index Scan using index_page_view_log_with_indices_on_viewed_at on page_view_log_with_indices (cost=0.42..16698.78 rows=501024 width=28)
  • 50. その他、PostgreSQLに特徴的な 愉快な仲間たち • 1. Window Functions • 2. Json Type • 3. Hstore • 4. Materialized View • 5. Stored Procedure (PL/pgSQL)
  • 51. 1. Window Functions http://www.postgresql.org/docs/current/static/tutorial-window.html $ Company.select('country, rank() OVER (PARTITION BY country ORDER BY id DESC)').explain => EXPLAIN for: SELECT country, rank() OVER (PARTITION BY country ORDER BY id DESC) FROM "companies" QUERY PLAN ---------------------------------------------------------------------------- WindowAgg (cost=936.35..1155.63 rows=10964 width=16) -> Sort (cost=936.35..963.76 rows=10964 width=16) Sort Key: country, id -> Seq Scan on companies (cost=0.00..200.64 rows=10964 width=16) Partition ごとに、値を計算 country | rank --------------+------ britain | 1 china | 1 china | 2 china | 3 country_0 | 1 高機能な集約関数
  • 52. 2. Json Type Json データを保存可能 ActiveREcord で対応済み $ Event.create(payload: { kind: "user_renamed", change: ["jack", "john"]}) (0.1ms) BEGIN SQL (1.7ms) INSERT INTO "events" ("payload", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["payload", "{"kind":"user_renamed","change":["jack","john"]}"], ["created_at", "2015-12-10 09:57:52.294809"], ["updated_at", "2015-12-10 09:57:52.294809"]] (0.4ms) COMMIT # db/migrate/~.rb def change create_table :events do |t| t.json :payload end end
  • 53. 2. Json Type http://www.postgresql.org/docs/current/static/functions-json.html Json の値取得用の operator が存在 $ Event.where("payload->>'name' = ?", "test1").explain => EXPLAIN for: SELECT "events".* FROM "events" WHERE (payload->>'name' = 'test1') QUERY PLAN -------------------------------------------------------- Seq Scan on events (cost=0.00..24.85 rows=5 width=52) Filter: ((payload ->> 'name'::text) = 'test1'::text)
  • 54. 3. Hstore http://www.postgresql.org/docs/current/static/hstore.html key, value のペアを1つの絡むに保存可能 問い合わせ用のオペレータあり
  • 56. 5. Stored Procedure (PL/pgSQL) http://www.postgresql.org/docs/current/static/plpgsql.html PostgreSQL で実行可能な function を定義可能
  • 57. まとめ SQLの実行時に選ばれる実行計画は、index の有無や 統計情報(データの量・分布)に依存する 適切な schema, index, query の選択によって、 高速化しよう • WHERE, JOIN, ORDER BY, GROUP BY の key には index • JOIN の前に絞り込めるだけ絞り込む • JSON Type などもケースバイケースで