Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
exists?で起きるN+1問題にSetで対処する
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
patorash
July 21, 2021
Technology
820
0
Share
exists?で起きるN+1問題にSetで対処する
Ruby on Railsでよく発生するN+1問題の解決方法として、exists?の場合は標準ライブラリのSetを使うといいかもしれない、という話です。
patorash
July 21, 2021
More Decks by patorash
See All by patorash
中間管理職はそこそこ楽しい
patorash
0
30
情報共有戦略と戦術
patorash
1
1.3k
DBのメタデータを管理する文化を作る
patorash
0
690
Stimulusのススメ
patorash
0
88
ActiveRecordの速度改善Tips2020冬
patorash
0
81
わかった気になる!OpenID Connect
patorash
2
2.2k
Indexの種類
patorash
1
810
Start-SQLの紹介
patorash
0
760
RailsアプリにGraphQLを導入してみた話
patorash
1
680
Other Decks in Technology
See All in Technology
AI時代における技術的負債への取り組み
codenote
1
1.5k
Eight Engineering Unit 紹介資料
sansan33
PRO
3
7.3k
自分のハンドルは自分で握れ! ― 自分のケイパビリティを増やし、メンバーのケイパビリティ獲得を支援する ― / Take the wheel yourself
takaking22
1
910
EBS暗号化に失敗してEC2が動かなくなった話
hamaguchimmm
2
200
サイボウズ 開発本部採用ピッチ / Cybozu Engineer Recruit
cybozuinsideout
PRO
10
78k
M5Stack CoreS3とZephyr(RTOS)で Edge AIっぽいことしてみた
iotengineer22
0
200
AI時代 に増える データ活用先
takahal
0
230
「誰一人取り残されない」 AIエージェント時代のプロダクト設計思想 Product Management Summit 2026
mizushimac
0
120
ハーネスエンジニアリングをやりすぎた話 ~そのハーネスは解体された~
gotalab555
4
1.7k
AWS DevOps Agentはチームメイトになれるのか?/ Can AWS DevOps Agent become a teammate
kinunori
6
740
最近の技術系の話題で気になったもの色々(IoT系以外も) / IoTLT 花見予定会(たぶんBBQ) @都立潮風公園バーベキュー広場
you
PRO
1
240
Oracle AI Database@AWS:サービス概要のご紹介
oracle4engineer
PRO
4
2.4k
Featured
See All Featured
Digital Ethics as a Driver of Design Innovation
axbom
PRO
1
260
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
25
1.9k
The Anti-SEO Checklist Checklist. Pubcon Cyber Week
ryanjones
0
120
Public Speaking Without Barfing On Your Shoes - THAT 2023
reverentgeek
1
370
WCS-LA-2024
lcolladotor
0
540
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
52
5.9k
Leveraging Curiosity to Care for An Aging Population
cassininazir
1
220
Heart Work Chapter 1 - Part 1
lfama
PRO
5
35k
Noah Learner - AI + Me: how we built a GSC Bulk Export data pipeline
techseoconnect
PRO
0
160
The untapped power of vector embeddings
frankvandijk
2
1.7k
Abbi's Birthday
coloredviolet
2
7.1k
Getting science done with accelerated Python computing platforms
jacobtomlinson
2
180
Transcript
exists?で起きるN+1問 題にSetで対処する 2021-07-21 株式会社リゾーム 社内勉強会 @patorash
稀によくあるコード # CSVファイルの行数だけUser.exists?が実行されるやーつ csv.foreach do |row| name = row['名前'] age
= row['年齢'] hobby = row['趣味'] # User.find_or_create_by!とか言わないで… unless User.exists?(name: name, age: age, hobby: hobby) User.create!(name: name, age: age, hobby: hobby) end end
何が問題か? • CSVファイルの⾏数だけexists?が実⾏される • exists?の1回の実⾏時間が仮に1msだったとしても、CSVが1,000⾏あれば1秒か かる。1万⾏あれば10秒、10万⾏あれば100秒…と増えていく • usersテーブルにデータがたくさんあると、exists?の時間は更にかかる • 1msではなく5msなら?
• 1,000⾏なら5秒、1万⾏なら50秒、10万⾏なら500秒
どうしてこんなことに… • 重複データを検知したいから
どうすれば… • とはいえ、重複しているかどうかは DBに問い合わせしなければわからな いじゃないか! • N回のクエリが発⽣するのも⽌むを 得ない!
そう考えていた頃が私にもありました
標準ライブラリSetを使え! • Setは、数学の集合を扱うクラス • 集合とは、重複のないオブジェクトの集まりです。Arrayの持つ演算機能とHash の⾼速な検索機能を合わせ持ちます。 Setは内部記憶としてHashを使うため、集合要素の等価性はObject.eql?と Object#hashを⽤いて判断されます。したがって、集合の各要素には、これらの メソッドが適切に定義されている必要があります。 集合の順序は保証されません。
(Ruby リファレンスマニュアルより)
どういうこと? • Setを使うと、配列の要素毎にhash関数が呼ばれて、それがkeyに設定された Hashを持つことになる。(内部的に) # これはつまり… set = Set.new(["a", "b"])
# 内部としてはこういう感じになっている hash = ["a", "b"].each_with_object({}) {|v, o| o[v.hash] = v } # => {690777552598146486=>"a", -2489798041940868951=>"b"} # これはつまり… set.include?("a") # => true # こういうこと。総当りせず、hash値がキーとヒットするかを調べるので速い hash.keys.include?("a".hash) # => true
つまり… • usersテーブルの内容を全て持ってきてSetに⼊れてしまえばN回発⾏される クエリは必要ない!(富豪的発想) • なぜ富豪的? • メモリにusersテーブルの全データを載せるから
修正後 # 事前に全データを取得して集合を作る user_data = User.in_batches.flat_map do |records| records.pluck(:name, :age,
:hobby) end.to_set # N回クエリが呼ばれなくなったやつ users = csv.map do |row| name = row['名前'] age = row['年齢'].to_i hobby = row['趣味'] # CSVの行には重複データがない前提 unless user_data.include?([name, age, hobby]) User.new(name: name, age: age, hobby: hobby) end end User.import!(users) # activerecord-importでバルクインサート
推測するな、計測せよ • ⽐較対象 • exists?(1万回クエリ呼ぶ) • Array#include?(全データをロードして線形探索。つまり、O(n)) • Set#include?(全データをロードしてハッシュ探索。つまり、O(1)) •
事前準備 • rails newしてsqlite3でusersテーブルに1万件登録 • 名前1..名前10000というデータ • 1万件の配列のデータで⽐較させる • 名前10001..名前20000というデータ。exists?やinclude?は必ずfalseとなる。
推測するな、計測せよ(結果) bin/rails runner script/benchmark.rb Warming up -------------------------------------- exists? 1.000 i/100ms
Array#include? 1.000 i/100ms Set#include? 1.000 i/100ms Calculating ------------------------------------- exists? 0.085 (± 0.0%) i/s - 1.000 in 11.734614s Array#include? 0.059 (± 0.0%) i/s - 1.000 in 17.012424s Set#include? 8.503 (± 0.0%) i/s - 43.000 in 5.064850s Comparison: Set#include?: 8.5 i/s exists?: 0.1 i/s - 99.78x (± 0.00) slower Array#include?: 0.1 i/s - 144.66x (± 0.00) slower
推測するな、計測せよ 1. Set#include?(全データをロードしてハッシュ探索) 2. exists?(1万回クエリ呼ぶ) ・・・100倍遅い 3. Array#include?(全データをロードして線形探索) ・・・145倍遅い
ご清聴ありがとうございました