SlideShare uma empresa Scribd logo
1 de 52
Оптимизация Rails:  быстрее, выше, SQL'нее Александр Дымо Ruby And Rails Barcamp 2009 www.acunote.com
О чем речь? Оптимизация Rails приложения опыт 3.5 лет разработки нестандартный путь ^^^^^^^^ Александр Дымо Director of Engineering [email_address]
Что за приложение? Acunote  www.acunote.com Онлайновое средство управления проектами для компаний и команд использующих Agile методологии разработки (в частности, Scrum) ~4900 организаций Хостинг на Engine Yard (3 VPS) Хостинг на серверах клиента nginx + mongrel PostgreSQL
Стандартные пути оптимизации 1. Более эффективный Ruby код RubyProf в помощь
Стандартные пути оптимизации 1. Более эффективный Ruby код RubyProf в помощь 2. Давайте перепишем все на C Date-Performance: http://github.com/rtomayko/date-performance/ Monkeysupport: http://github.com/burke/monkeysupport http://burkelibbey.posterous.com/
А мы пойдем своим путем 1. Более эффективный Ruby код RubyProf в помощь 2. Давайте перепишем все на C Date-Performance Monkeysupport 3. Наш путь -  перепишем все на SQL !
Так все же, о чем речь? Активное использование SQL Перенос функциональности и логики в SQL Почему Postgres? Оптимизация БД Тестирование производительности
Активное использование SQL Выборка дополнительных атрибутов к моделям: foos = Foo.find(:all, :select => "*, 2+2 as hard_stuff") Почему? Производительность: в SQL: explain analyze select 2+2 as hard_stuff; QUERY PLAN ------------------------------------------------------------------- Result  (cost=0.00..0.01 rows=1 width=0)  (actual time= 0.009..0.012  rows=1 loops=1) Total runtime: 0.068 ms а в Ruby: sprintf("%0.3 ms", Benchmark.realtime{ 2+2 }*1000) >  0.017 ms 2x!
Активное использование SQL Выборка дополнительных атрибутов к моделям: Tasks Tags Tasks_Tags id serial id serial tag_id integer name varchar name varchar task_id integer tasks = Task.find(:all, :include => :tags) > 0.058 сек 2 SQL запроса select * from tasks select * from tags inner join tasks_tags on tags.id = tasks_tags.tag_id where tasks_tags.task_id in (1,2,3,..) Rals должен создать модели для каждого тэга а это не быстро и занимает память
Активное использование SQL Выборка дополнительных атрибутов к моделям: Tasks Tags Tasks_Tags id serial id serial tag_id integer name varchar name varchar task_id integer tasks = Task.find(:all, :select => "*,  array( select tags.name from tags inner join tasks_tags  on (tags.id = tasks_tags.tag_id) where tasks_tasks.task_id=tasks.id) as tag_names ") > 0.018 сек 1 SQL запрос Rails не создает модели >3x быстрее (было  0.058  сек, стало  0.018  сек) 3x!
Активное использование SQL Выборка дополнительных атрибутов к моделям: Tasks Tags Tasks_Tags id serial id serial tag_id integer name varchar name varchar task_id integer tasks = Task.find(:all, :select => "*,  array( select tags.name from tags inner join tasks_tags  on (tags.id = tasks_tags.tag_id) where tasks_tasks.task_id=tasks.id) as tag_names ") puts tasks.first.tag_names > "{Foo,Bar,Zee}"
Активное использование SQL Выборка дополнительных атрибутов соединениями  требует указания параметра  select  в методе  find : foos = Foo.find(:all,  :joins => "left outer join bars using bar_id" не добавит атрибуты из bars потому что Rails сделает > select foos.* from foos left outer join bars... Нужно добавлять select: foos = Foo.find(:all,  :select => "*" ,  :joins => "left outer join bars using bar_id" > select foos.* from foos left outer join bars...
Активное использование SQL Еще о предварительной загрузке ассоциаций... она не работает с find_by_sql class  Foo belongs_to  :bar end foos = Foo. find_by_sql ( 'select * from foos inner join bars' ) foos. first . bar   #еще 1 SQL запрос!
Активное использование SQL Virtual Attributes plugin: http://github.com/acunote/virtual_attributes/ class  Bar end class  Foo belongs_to  :bar preloadable_association  :bar end foos = Foo. find_by_sql ( ' select * from foos  left outer join  (select id as preloaded_bar_id,  name as preloaded_bar_name  from bars) as bars  on foos.bar_id = bars.preloaded_bar_id' ) foos. first . bar #no extra SQL query!
Так все же, о чем речь? Активное использование SQL Перенос функциональности и логики в SQL Почему Postgres? Оптимизация БД Тестирование производительности
Acunote > Сложные запросы Дерево задач (3 уровня) (+2 соединения и 1 вл. запрос) Тэги к задачам (+2 вложенных запроса) Счетчики свойств задач (+4 вложенных запроса) Последние значения изменяемых во времени атрибутов (+4 соединения с "бухгалтерскими запросами") и т.д... -  12 соединений и вложенных запросов 1 4 2 3
Acunote > Один за всех! Этот "монстро" выбирает дерево из нескольких сотен элементов и загружает всю необходимую информацию к ним не более чем за 50 мс! Даже на EeePC это занимает 58мс Эквивалентный Ruby/Rails код занимал до 8сек
Acunote > "Бухгалтерский" запрос Issues Timecells Цель: выбрать самое последнее значение из ячеек
Acunote > &quot;Бухгалтерский&quot; запрос Issues Timecells select  *  from   Issues   left   outer   join  ( select   distinct   on (issue_id)  issue_id  as  cell_issue_id,  value  as  cell_value,  date  as  cell_date  from   Timecells   where  date <= '2009-06-09'  order by  issue_id, date desc ) as cells  on  (issues.id = cells.cell_issue_id)
Acunote > Эффективные деревья Обычный способ моделирования деревьев в Rails : create table  Task ( id  serial   not null , parent_id  integer ) class  Task < ActiveRecord::Base acts_as_tree end Использует N+1 запросов для загрузки N узлов из дерева: (root) select * from tasks where parent_id = nil - 1 select * from tasks where parent_id = 1 - 11 select * from tasks where parent_id = 11 - 111 select * from tasks where parent_id = 111 - 112 select * from tasks where parent_id = 112 - 12 select * from tasks where parent_id = 12 - 2 select * from tasks where parent_id = 2 - 21 select * from tasks where parent_id = 21
Acunote > Эффективные деревья Обычный способ моделирования деревьев в Rails : create table  Task ( id  serial   not null , parent_id  integer ) class  Task < ActiveRecord::Base acts_as_tree end А должно быть так: select * from tasks  left outer join (select id as parents_id, parent_id as parents_parent_id from tasks) as parents on (tasks.parent_id = parents_id) left outer join  (select id as parents_parents_id from tasks) as parents_parents on (parents_parent_id = parents_parents_id)
Acunote > Разбиение по страницам Разбиение по страницам это: select * from Issues  where <conditions>  limit N offset M
Acunote > Efficient Pagination Но будьте осторожны со вложенными запросами: select *,  (select count(*) from attachments  where issue_id = issues.id) as num_attachments  from issues limit 100 offset 0 ; Limit  (cost=0.00..831.22 rows=100 width=143) (actual time=0.050..1.242 rows=100 loops=1) ->  Seq Scan on issues  (cost=0.00..2509172.92 rows=301866 width=143)  (actual time=0.049..1.119 rows=100 loops=1) SubPlan ->  Aggregate  (cost=8.27..8.28 rows=1 width=0)  (actual time=0.006..0.006 rows=1 loops= 100 ) ->  Index Scan using attachments_issue_id_idx on attachments  (cost=0.00..8.27 rows=1 width=0) (actual time=0.004..0.004 rows=0 loops= 100 ) Index Cond: (issue_id = $0) Total runtime: 1.383 ms
Acunote > Efficient Pagination Но будьте осторожны со вложенными запросами: select *,  (select count(*) from attachments  where issue_id = issues.id) as num_attachments  from issues limit 100 offset 100 ; Limit  (cost=831.22..1662.44 rows=100 width=143) (actual time=1.070..7.927 rows=100 loops=1) ->  Seq Scan on issues  (cost=0.00..2509172.92 rows=301866 width=143)  (actual time=0.039..7.763 rows=200 loops=1) SubPlan ->  Aggregate  (cost=8.27..8.28 rows=1 width=0)  (actual time=0.034..0.034 rows=1 loops= 200 ) ->  Index Scan using attachments_issue_id_idx on attachments  (cost=0.00..8.27 rows=1 width=0) (actual time=0.032..0.032 rows=0 loops= 200 ) Index Cond: (issue_id = $0) Total runtime: 8.065 ms
Acunote > Efficient Pagination Но будьте осторожны со вложенными запросами: они выполняются  limit + offset раз ! Используйте соединения в таком случае
Acunote > Пользователи и роли Упрощенная модель ролей для пользователей: Users Role Roles_Users id serial id serial user_id integer name varchar name varchar role_id integer privilege1 boolean privilege2 boolean ... user = User.find(:first, :include => :roles) can_do_1 = user.roles.any { |role| role.privilege1? }
Acunote > Пользователи и роли Упрощенная модель ролей для пользователей: Users Role Roles_Users id serial id serial user_id integer name varchar name varchar role_id integer privilege1 boolean privilege2 boolean ... user = User.find(:first, :include => :roles) can_do_1 = user.roles.any { |role| role.privilege1? } В чем проблема? - 2 SQL запроса - заставляем Rails создавать объекты ролей - заставляем Ruby делать цикл
Acunote > Пользователи и роли Перепишем на SQL: Users Role Roles_Users id serial id serial user_id integer name varchar name varchar role_id integer privilege1 boolean user = User.find(:first, :select => &quot;*&quot;, :joins => &quot; inner join (select user_id,  bool_or(privilege1) as privilege1 from roles_users inner join roles on (roles.id = roles_users.role_id) group by user_id ) as roles_users on (users.id = roles_users.user_id) &quot; ) can_do_1 = ActiveRecord::ConnectionAdapters::Column. value_to_boolean(user.privilege1)
Acunote > Пользователи и роли Есть ли эффект от такого SQL? цифры из жизни: can_do_1 = user.roles.any { |role| role.privilege1? } > код с использованием такого подхода исполнялся  2.1 сек can_do_1 = ActiveRecord::ConnectionAdapters::Column. value_to_boolean(user.privilege1) >  код с использованием такого подхода исполнялся  64 мс !!!
Acunote > OLAP и его подобия Делаете операции аггрегирования и выборки  на больших объемах данных? Делайте их в SQL! цифры из жизни: 600 000 строк с данными, куб в 3х измерениях,  срез и аггрегирование  в Ruby: ~1 Gb памяти, ~90 сек в SQL:  до 5 сек
Общий вывод Хотите оптимизировать? Правило такое: Пишите Ruby код который генерирует SQL который экономит гигабайт памяти который выполняется в десятки/сотни раз быстрее и лучше масштабируется
Так все же, о чем речь? Активное использование SQL Перенос функциональности и логики в SQL Почему Postgres? Оптимизация БД Тестирование производительности
Почему Postgres? Соответствие стандарту Хорошая документация Понятный процесс разработки Плюс при принятии на работу
Postgres спасет отца русской демократии Ограничения, ссылочная целостность Автоопределение deadlock'ов Хороший оптимизатор (иногда делает чудеса) Понятный Explain Analyze Полезные надстройки над стандартом (напр. массивы)
Postgres спасет отца русской демократии Вывод: если СУБД для вас - это не только средство хранения данных, используйте Postgres
Так все же, о чем речь? Активное использование SQL Перенос функциональности и логики в SQL Почему Postgres? Оптимизация БД Тестирование производительности
Оптимизация БД > Основы Единственный путь оптимизации PostgreSQL: explain analyze explain analyze explain analyze ...
Оптимизация БД > „Холодные“ запросы EXPLAIN ANALYZE все объясняет, но... ... его нужно делать и для „холодного“ состояния базы! Пример: сложный запрос к таблице из 230 000 строк 9 вложенных запросов и соединений: холодное состояние:  28  сек, горячее состояние:  2.42  сек Перезапуск СУБД не помогает! Нужно очищать дисковый кэш:  sudo echo 3 | sudo tee /proc/sys/vm/drop_caches  (Linux)
Оптимизация БД > Общий сервер Если ваш хостинг предоставляет общий сервер БД для всех клиентов, то вы соревнуетесь с ними за кэш в памяти: 1. две БД с одинаковой загрузкой честно поделят кэш
Optimize Database > Shared Database Если ваш хостинг предоставляет общий сервер БД для всех клиентов, то вы соревнуетесь с ними за кэш в памяти: 2. менее нагруженная БД проигрывает битву за кэш
Optimize Database > Shared Database В результате, ваша БД будет всегда холодной и вы будете читать данные с диска а не с памяти! помните пример со сложным запросом: с диска:  28  сек, из памяти:  2.42  сек Решения:  оптимизация для холодного состояния указание большего кол-ва условий выборки sudo echo 3 | sudo tee /proc/sys/vm/drop_caches
Оптимизация БД > array() Используйте  any(array ())  всесто  in()   для того чтобы получить subselect а не join explain analyze select * from issues where id in (select issue_id from tags_issues); QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------- Merge IN Join  (actual time=0.096..576.704 rows=55363 loops=1) Merge Cond: (issues.id = tags_issues.issue_id) ->  Index Scan using issues_pkey on issues  (actual time=0.027..270.557 rows=229991 loops=1) ->  Index Scan using tags_issues_issue_id_key on tags_issues  (actual time=0.051..73.903 rows=70052loops=1) Total runtime:  605.274 ms explain analyze select * from issues where id = any( array( (select issue_id from tags_issues) ) ); QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------ Bitmap Heap Scan on issues  (actual time=247.358..297.932 rows=55363 loops=1) Recheck Cond: (id = ANY ($0)) InitPlan ->  Seq Scan on tags_issues  (actual time=0.017..51.291 rows=70052 loops=1) ->  Bitmap Index Scan on issues_pkey  (actual time=246.589..246.589 rows=70052 loops=1) Index Cond: (id = ANY ($0)) Total runtime:  325.205 ms 2x!
Оптимизация БД > Условия выборки Самостоятельно указывайте условия выборки во вложенных запросах и соединениях PostgreSQL зачастую сам не может этого сделать select *, ( select notes.author from notes   where notes.bug_id = bugs.id ) as note_authors from bugs where  org_id = 1 select *, ( select notes.author from notes where notes.bug_id = bugs.id and  org_id = 1 ) as note_authors from bugs where  org_id = 1 Bugs id serial name varchar org_id integer Notes id serial name varchar bug_id integer org_id integer
Так все же, о чем речь? Активное использование SQL Перенос функциональности и логики в SQL Почему Postgres? Оптимизация БД Тестирование производительности
Тестирование производительности Во первых, создайте набор тестов, измеряющих производительность самых популярных страниц. Например: Benchmark Burndown 120  0.70 ± 0.00 Benchmark Inc. Burndown 120  0.92 ± 0.01 Benchmark Sprint 20 x (1+5) (C)  0.45 ± 0.00 Benchmark Issues 100 (C)  0.34 ± 0.00 Benchmark Prediction 120  0.56 ± 0.00 Benchmark Progress 120  0.23 ± 0.00 Benchmark Sprint 20 x (1+5)  0.93 ± 0.00 Benchmark Timeline 5x100  0.11 ± 0.00 Benchmark Signup  0.77 ± 0.00 Benchmark Export  0.20 ± 0.00 Benchmark Move Here 20/120  0.89 ± 0.00 Benchmark Order By User  0.98 ± 0.00 Benchmark Set Field (EP)  0.21 ± 0.00 Benchmark Task Create + Tag  0.23 ± 0.00 ...
Тестирование производительности Еще один тип интеграционного тестирования: class  RenderingTest < ActionController::IntegrationTest   def   test_sprint_rendering login_with   users (:user), &quot;user&quot;   benchmark  :title => &quot;Sprint 20 x (1+5) (C)&quot;, :route => &quot;projects/1/sprints/3/show&quot;, :assert_template => &quot;tasks/index&quot; end end Benchmark Sprint 20 x (1+5) (C)  0.45 ± 0.00
Тестирование производительности Еще один тип интеграционного тестирования: def   benchmark (options = {}) (0..100). each   do  |i| GC. start pid =  fork   do begin out = File. open (&quot;values&quot;, &quot;a&quot;) ActiveRecord::Base. transaction   do elapsed_time = Benchmark:: realtime   do request_method = options[:post]  ?  :post  :  :get send (request_method, options[:route]) end out. puts  elapsed_time  if  i > 0 out. close raise  CustomTransactionError end rescue  CustomTransactionError exit end end Process:: waitpid  pid ActiveRecord::Base. connection . reconnect ! end values = File. read (&quot;values&quot;) print  &quot;#{ mean (values).to_02f} ± #{ sigma (values).to_02f}&quot; end
Тестирование производительности Осторожно!   Потеря 10мс в тесте зачастую только кажется безобидной Потому что может случиться так, что эти 10мс уходят на случайно добавленный SQL запрос
Тестирование производительности Тесты запросов def  test_queries queries =  track_queries   do get :index end assert_equal  queries, [ &quot;Foo Load&quot;, &quot;Bar Load&quot;, &quot;Moo Create&quot; ] end
Тестирование производительности module  ActiveSupport class  BufferedLogger attr_reader  :tracked_queries def  tracking=(val) @tracked_queries = [] @tracking = val end def  add_with_tracking(severity, message = nil, progname = nil, &block) @tracked_queries << $1 if @tracking && message =~ /3[56]1m(.* (Load|Create|Update|Destroy)) / @tracked_queries << $1 if @tracking && message =~ /3[56]1m(SQL) / add_without_tracking (severity, message, progname, &block) end alias_method_chain :add, :tracking end end class  ActiveSupport::TestCase def  track_queries(&block) RAILS_DEFAULT_LOGGER. tracking  = true yield result = RAILS_DEFAULT_LOGGER. tracked_queries RAILS_DEFAULT_LOGGER. tracking  = false result end end
Интересно оптимизировать Rails? Приходите в нашу команду! http://www.acunote.com/pluron/jobs
Cпасибо за внимание! Вопросы? Наш блог про Rails Performance: http://blog.pluron.com Александр Дымо Director of Engineering [email_address]

Mais conteúdo relacionado

Mais procurados

Толстая модель. История разработки ORM
Толстая модель. История разработки ORMТолстая модель. История разработки ORM
Толстая модель. История разработки ORMMikhail Shamin
 
Лекция 5. Встроенные коллекции и модуль collections.
Лекция 5. Встроенные коллекции и модуль collections.Лекция 5. Встроенные коллекции и модуль collections.
Лекция 5. Встроенные коллекции и модуль collections.Roman Brovko
 
Профилирование и отладка Django
Профилирование и отладка DjangoПрофилирование и отладка Django
Профилирование и отладка DjangoVladimir Rudnyh
 
Лекция 10. Классы 2.
Лекция 10. Классы 2.Лекция 10. Классы 2.
Лекция 10. Классы 2.Roman Brovko
 
Обзор ES2015(ES6)
Обзор ES2015(ES6)Обзор ES2015(ES6)
Обзор ES2015(ES6)Alex Filatov
 
Лекция 12. Быстрее, Python, ещё быстрее.
Лекция 12. Быстрее, Python, ещё быстрее.Лекция 12. Быстрее, Python, ещё быстрее.
Лекция 12. Быстрее, Python, ещё быстрее.Roman Brovko
 
Лекция 13. Многопоточность и GIL
Лекция 13. Многопоточность и GILЛекция 13. Многопоточность и GIL
Лекция 13. Многопоточность и GILRoman Brovko
 
Лекция 2. Всё, что вы хотели знать о функциях в Python.
Лекция 2. Всё, что вы хотели знать о функциях в Python.Лекция 2. Всё, что вы хотели знать о функциях в Python.
Лекция 2. Всё, что вы хотели знать о функциях в Python.Roman Brovko
 
Делаем кроссбраузерные тесты поверх Webdriver
Делаем кроссбраузерные тесты поверх WebdriverДелаем кроссбраузерные тесты поверх Webdriver
Делаем кроссбраузерные тесты поверх WebdriverSQALab
 
Лекция 9. Модули, пакеты и система импорта.
Лекция 9. Модули, пакеты и система импорта.Лекция 9. Модули, пакеты и система импорта.
Лекция 9. Модули, пакеты и система импорта.Roman Brovko
 
SQL Tricky (Иван Фролков)
SQL Tricky (Иван Фролков)SQL Tricky (Иван Фролков)
SQL Tricky (Иван Фролков)Ontico
 
Лекция 8. Итераторы, генераторы и модуль itertools.
 Лекция 8. Итераторы, генераторы и модуль itertools. Лекция 8. Итераторы, генераторы и модуль itertools.
Лекция 8. Итераторы, генераторы и модуль itertools.Roman Brovko
 
Магия в Python: Дескрипторы. Что это?
Магия в Python: Дескрипторы. Что это?Магия в Python: Дескрипторы. Что это?
Магия в Python: Дескрипторы. Что это?PyNSK
 
Оптимизация производительности Python
Оптимизация производительности PythonОптимизация производительности Python
Оптимизация производительности PythonPyNSK
 
Лекция 1. Начало.
Лекция 1. Начало.Лекция 1. Начало.
Лекция 1. Начало.Roman Brovko
 
2015-12-05 Александр Коротков, Иван Панченко - Слабо-структурированные данные...
2015-12-05 Александр Коротков, Иван Панченко - Слабо-структурированные данные...2015-12-05 Александр Коротков, Иван Панченко - Слабо-структурированные данные...
2015-12-05 Александр Коротков, Иван Панченко - Слабо-структурированные данные...HappyDev
 
Лекция 6. Классы 1.
Лекция 6. Классы 1.Лекция 6. Классы 1.
Лекция 6. Классы 1.Roman Brovko
 
Лекция 3. Декораторы и модуль functools.
Лекция 3. Декораторы и модуль functools.Лекция 3. Декораторы и модуль functools.
Лекция 3. Декораторы и модуль functools.Roman Brovko
 
Разработка расширяемых приложений на Django
Разработка расширяемых приложений на DjangoРазработка расширяемых приложений на Django
Разработка расширяемых приложений на DjangoMoscowDjango
 

Mais procurados (19)

Толстая модель. История разработки ORM
Толстая модель. История разработки ORMТолстая модель. История разработки ORM
Толстая модель. История разработки ORM
 
Лекция 5. Встроенные коллекции и модуль collections.
Лекция 5. Встроенные коллекции и модуль collections.Лекция 5. Встроенные коллекции и модуль collections.
Лекция 5. Встроенные коллекции и модуль collections.
 
Профилирование и отладка Django
Профилирование и отладка DjangoПрофилирование и отладка Django
Профилирование и отладка Django
 
Лекция 10. Классы 2.
Лекция 10. Классы 2.Лекция 10. Классы 2.
Лекция 10. Классы 2.
 
Обзор ES2015(ES6)
Обзор ES2015(ES6)Обзор ES2015(ES6)
Обзор ES2015(ES6)
 
Лекция 12. Быстрее, Python, ещё быстрее.
Лекция 12. Быстрее, Python, ещё быстрее.Лекция 12. Быстрее, Python, ещё быстрее.
Лекция 12. Быстрее, Python, ещё быстрее.
 
Лекция 13. Многопоточность и GIL
Лекция 13. Многопоточность и GILЛекция 13. Многопоточность и GIL
Лекция 13. Многопоточность и GIL
 
Лекция 2. Всё, что вы хотели знать о функциях в Python.
Лекция 2. Всё, что вы хотели знать о функциях в Python.Лекция 2. Всё, что вы хотели знать о функциях в Python.
Лекция 2. Всё, что вы хотели знать о функциях в Python.
 
Делаем кроссбраузерные тесты поверх Webdriver
Делаем кроссбраузерные тесты поверх WebdriverДелаем кроссбраузерные тесты поверх Webdriver
Делаем кроссбраузерные тесты поверх Webdriver
 
Лекция 9. Модули, пакеты и система импорта.
Лекция 9. Модули, пакеты и система импорта.Лекция 9. Модули, пакеты и система импорта.
Лекция 9. Модули, пакеты и система импорта.
 
SQL Tricky (Иван Фролков)
SQL Tricky (Иван Фролков)SQL Tricky (Иван Фролков)
SQL Tricky (Иван Фролков)
 
Лекция 8. Итераторы, генераторы и модуль itertools.
 Лекция 8. Итераторы, генераторы и модуль itertools. Лекция 8. Итераторы, генераторы и модуль itertools.
Лекция 8. Итераторы, генераторы и модуль itertools.
 
Магия в Python: Дескрипторы. Что это?
Магия в Python: Дескрипторы. Что это?Магия в Python: Дескрипторы. Что это?
Магия в Python: Дескрипторы. Что это?
 
Оптимизация производительности Python
Оптимизация производительности PythonОптимизация производительности Python
Оптимизация производительности Python
 
Лекция 1. Начало.
Лекция 1. Начало.Лекция 1. Начало.
Лекция 1. Начало.
 
2015-12-05 Александр Коротков, Иван Панченко - Слабо-структурированные данные...
2015-12-05 Александр Коротков, Иван Панченко - Слабо-структурированные данные...2015-12-05 Александр Коротков, Иван Панченко - Слабо-структурированные данные...
2015-12-05 Александр Коротков, Иван Панченко - Слабо-структурированные данные...
 
Лекция 6. Классы 1.
Лекция 6. Классы 1.Лекция 6. Классы 1.
Лекция 6. Классы 1.
 
Лекция 3. Декораторы и модуль functools.
Лекция 3. Декораторы и модуль functools.Лекция 3. Декораторы и модуль functools.
Лекция 3. Декораторы и модуль functools.
 
Разработка расширяемых приложений на Django
Разработка расширяемых приложений на DjangoРазработка расширяемых приложений на Django
Разработка расширяемых приложений на Django
 

Destaque

La identitat digital
La identitat digitalLa identitat digital
La identitat digitaljarmall
 
Alexander Dymo - RubyConf 2014 - Ruby Performance Secrets and How to Uncover ...
Alexander Dymo - RubyConf 2014 - Ruby Performance Secrets and How to Uncover ...Alexander Dymo - RubyConf 2014 - Ruby Performance Secrets and How to Uncover ...
Alexander Dymo - RubyConf 2014 - Ruby Performance Secrets and How to Uncover ...Alexander Dymo
 
Alexander Dymo - RailsConf 2014 - Improve performance: Optimize Memory and Up...
Alexander Dymo - RailsConf 2014 - Improve performance: Optimize Memory and Up...Alexander Dymo - RailsConf 2014 - Improve performance: Optimize Memory and Up...
Alexander Dymo - RailsConf 2014 - Improve performance: Optimize Memory and Up...Alexander Dymo
 
Alexander Dymo - IT-клуб Николаева - April 2011 - Ruby: Beaty and the Beast
Alexander Dymo - IT-клуб Николаева - April 2011 - Ruby: Beaty and the BeastAlexander Dymo - IT-клуб Николаева - April 2011 - Ruby: Beaty and the Beast
Alexander Dymo - IT-клуб Николаева - April 2011 - Ruby: Beaty and the BeastAlexander Dymo
 

Destaque (6)

La identitat digital
La identitat digitalLa identitat digital
La identitat digital
 
Team02
Team02Team02
Team02
 
Alexander Dymo - RubyConf 2014 - Ruby Performance Secrets and How to Uncover ...
Alexander Dymo - RubyConf 2014 - Ruby Performance Secrets and How to Uncover ...Alexander Dymo - RubyConf 2014 - Ruby Performance Secrets and How to Uncover ...
Alexander Dymo - RubyConf 2014 - Ruby Performance Secrets and How to Uncover ...
 
Team05
Team05Team05
Team05
 
Alexander Dymo - RailsConf 2014 - Improve performance: Optimize Memory and Up...
Alexander Dymo - RailsConf 2014 - Improve performance: Optimize Memory and Up...Alexander Dymo - RailsConf 2014 - Improve performance: Optimize Memory and Up...
Alexander Dymo - RailsConf 2014 - Improve performance: Optimize Memory and Up...
 
Alexander Dymo - IT-клуб Николаева - April 2011 - Ruby: Beaty and the Beast
Alexander Dymo - IT-клуб Николаева - April 2011 - Ruby: Beaty and the BeastAlexander Dymo - IT-клуб Николаева - April 2011 - Ruby: Beaty and the Beast
Alexander Dymo - IT-клуб Николаева - April 2011 - Ruby: Beaty and the Beast
 

Semelhante a Alexander Dymo - Barcamp 2009 - Faster Higher Sql

React со скоростью света: не совсем обычный серверный рендеринг
React со скоростью света: не совсем обычный серверный рендерингReact со скоростью света: не совсем обычный серверный рендеринг
React со скоростью света: не совсем обычный серверный рендерингTimophy Chaptykov
 
Ember.js - Назад в Будущее - Odessa JS 2014
Ember.js - Назад в Будущее - Odessa JS 2014Ember.js - Назад в Будущее - Odessa JS 2014
Ember.js - Назад в Будущее - Odessa JS 2014Andrey Listochkin
 
Борис Сазонов, RAII потоки и CancellationToken в C++
Борис Сазонов, RAII потоки и CancellationToken в C++Борис Сазонов, RAII потоки и CancellationToken в C++
Борис Сазонов, RAII потоки и CancellationToken в C++Sergey Platonov
 
XPath локаторы в Selenium WebDriver
XPath локаторы в Selenium WebDriverXPath локаторы в Selenium WebDriver
XPath локаторы в Selenium WebDriverИлья Кожухов
 
Advanced Sql Injection
Advanced Sql InjectionAdvanced Sql Injection
Advanced Sql InjectionDmitry Evteev
 
View как чистая функция от состояния базы данных - Илья Беда, bro.agency
View как чистая функция от состояния базы данных  - Илья Беда, bro.agencyView как чистая функция от состояния базы данных  - Илья Беда, bro.agency
View как чистая функция от состояния базы данных - Илья Беда, bro.agencyit-people
 
Тестирование программных фильтров безопасности
Тестирование программных фильтров безопасностиТестирование программных фильтров безопасности
Тестирование программных фильтров безопасностиZestranec
 
Sergii Tsypanov "Performance 1001 Tips"
Sergii Tsypanov "Performance 1001 Tips"Sergii Tsypanov "Performance 1001 Tips"
Sergii Tsypanov "Performance 1001 Tips"LogeekNightUkraine
 
Ecma script 6 in action
Ecma script 6 in actionEcma script 6 in action
Ecma script 6 in actionYuri Trukhin
 
Solit 2014, EcmaScript 6 in Action, Трухин Юрий
Solit 2014, EcmaScript 6 in Action, Трухин Юрий Solit 2014, EcmaScript 6 in Action, Трухин Юрий
Solit 2014, EcmaScript 6 in Action, Трухин Юрий solit
 
Превышаем скоростные лимиты с Angular 2 / Алексей Охрименко (IPONWEB)
Превышаем скоростные лимиты с Angular 2 / Алексей Охрименко (IPONWEB)Превышаем скоростные лимиты с Angular 2 / Алексей Охрименко (IPONWEB)
Превышаем скоростные лимиты с Angular 2 / Алексей Охрименко (IPONWEB)Ontico
 
Превышаем скоростные лимиты с Angular 2
Превышаем скоростные лимиты с Angular 2Превышаем скоростные лимиты с Angular 2
Превышаем скоростные лимиты с Angular 2Oleksii Okhrymenko
 
Зачем нужна Scala?
Зачем нужна Scala?Зачем нужна Scala?
Зачем нужна Scala?Vasil Remeniuk
 
Batch processing in rails
Batch processing in railsBatch processing in rails
Batch processing in railssergeymoiseev
 
Производительность параметрического поиска на основе опенсорс-платформы
Производительность параметрического поиска на основе опенсорс-платформыПроизводительность параметрического поиска на основе опенсорс-платформы
Производительность параметрического поиска на основе опенсорс-платформыYandex
 

Semelhante a Alexander Dymo - Barcamp 2009 - Faster Higher Sql (20)

React со скоростью света: не совсем обычный серверный рендеринг
React со скоростью света: не совсем обычный серверный рендерингReact со скоростью света: не совсем обычный серверный рендеринг
React со скоростью света: не совсем обычный серверный рендеринг
 
PT Hackday#2
PT Hackday#2PT Hackday#2
PT Hackday#2
 
Ember.js - Назад в Будущее - Odessa JS 2014
Ember.js - Назад в Будущее - Odessa JS 2014Ember.js - Назад в Будущее - Odessa JS 2014
Ember.js - Назад в Будущее - Odessa JS 2014
 
Борис Сазонов, RAII потоки и CancellationToken в C++
Борис Сазонов, RAII потоки и CancellationToken в C++Борис Сазонов, RAII потоки и CancellationToken в C++
Борис Сазонов, RAII потоки и CancellationToken в C++
 
XPath локаторы в Selenium WebDriver
XPath локаторы в Selenium WebDriverXPath локаторы в Selenium WebDriver
XPath локаторы в Selenium WebDriver
 
Advanced Sql Injection
Advanced Sql InjectionAdvanced Sql Injection
Advanced Sql Injection
 
View как чистая функция от состояния базы данных - Илья Беда, bro.agency
View как чистая функция от состояния базы данных  - Илья Беда, bro.agencyView как чистая функция от состояния базы данных  - Илья Беда, bro.agency
View как чистая функция от состояния базы данных - Илья Беда, bro.agency
 
Erlang tasty & useful stuff
Erlang tasty & useful stuffErlang tasty & useful stuff
Erlang tasty & useful stuff
 
PT MIFI Labsql
PT MIFI LabsqlPT MIFI Labsql
PT MIFI Labsql
 
Тестирование программных фильтров безопасности
Тестирование программных фильтров безопасностиТестирование программных фильтров безопасности
Тестирование программных фильтров безопасности
 
Sergii Tsypanov "Performance 1001 Tips"
Sergii Tsypanov "Performance 1001 Tips"Sergii Tsypanov "Performance 1001 Tips"
Sergii Tsypanov "Performance 1001 Tips"
 
Ecma script 6 in action
Ecma script 6 in actionEcma script 6 in action
Ecma script 6 in action
 
Solit 2014, EcmaScript 6 in Action, Трухин Юрий
Solit 2014, EcmaScript 6 in Action, Трухин Юрий Solit 2014, EcmaScript 6 in Action, Трухин Юрий
Solit 2014, EcmaScript 6 in Action, Трухин Юрий
 
Превышаем скоростные лимиты с Angular 2 / Алексей Охрименко (IPONWEB)
Превышаем скоростные лимиты с Angular 2 / Алексей Охрименко (IPONWEB)Превышаем скоростные лимиты с Angular 2 / Алексей Охрименко (IPONWEB)
Превышаем скоростные лимиты с Angular 2 / Алексей Охрименко (IPONWEB)
 
Превышаем скоростные лимиты с Angular 2
Превышаем скоростные лимиты с Angular 2Превышаем скоростные лимиты с Angular 2
Превышаем скоростные лимиты с Angular 2
 
Зачем нужна Scala?
Зачем нужна Scala?Зачем нужна Scala?
Зачем нужна Scala?
 
UWDC 2013, Yii2
UWDC 2013, Yii2UWDC 2013, Yii2
UWDC 2013, Yii2
 
Jdbc in java
Jdbc in javaJdbc in java
Jdbc in java
 
Batch processing in rails
Batch processing in railsBatch processing in rails
Batch processing in rails
 
Производительность параметрического поиска на основе опенсорс-платформы
Производительность параметрического поиска на основе опенсорс-платформыПроизводительность параметрического поиска на основе опенсорс-платформы
Производительность параметрического поиска на основе опенсорс-платформы
 

Alexander Dymo - Barcamp 2009 - Faster Higher Sql

  • 1. Оптимизация Rails: быстрее, выше, SQL'нее Александр Дымо Ruby And Rails Barcamp 2009 www.acunote.com
  • 2. О чем речь? Оптимизация Rails приложения опыт 3.5 лет разработки нестандартный путь ^^^^^^^^ Александр Дымо Director of Engineering [email_address]
  • 3. Что за приложение? Acunote www.acunote.com Онлайновое средство управления проектами для компаний и команд использующих Agile методологии разработки (в частности, Scrum) ~4900 организаций Хостинг на Engine Yard (3 VPS) Хостинг на серверах клиента nginx + mongrel PostgreSQL
  • 4. Стандартные пути оптимизации 1. Более эффективный Ruby код RubyProf в помощь
  • 5. Стандартные пути оптимизации 1. Более эффективный Ruby код RubyProf в помощь 2. Давайте перепишем все на C Date-Performance: http://github.com/rtomayko/date-performance/ Monkeysupport: http://github.com/burke/monkeysupport http://burkelibbey.posterous.com/
  • 6. А мы пойдем своим путем 1. Более эффективный Ruby код RubyProf в помощь 2. Давайте перепишем все на C Date-Performance Monkeysupport 3. Наш путь - перепишем все на SQL !
  • 7. Так все же, о чем речь? Активное использование SQL Перенос функциональности и логики в SQL Почему Postgres? Оптимизация БД Тестирование производительности
  • 8. Активное использование SQL Выборка дополнительных атрибутов к моделям: foos = Foo.find(:all, :select => &quot;*, 2+2 as hard_stuff&quot;) Почему? Производительность: в SQL: explain analyze select 2+2 as hard_stuff; QUERY PLAN ------------------------------------------------------------------- Result (cost=0.00..0.01 rows=1 width=0) (actual time= 0.009..0.012 rows=1 loops=1) Total runtime: 0.068 ms а в Ruby: sprintf(&quot;%0.3 ms&quot;, Benchmark.realtime{ 2+2 }*1000) > 0.017 ms 2x!
  • 9. Активное использование SQL Выборка дополнительных атрибутов к моделям: Tasks Tags Tasks_Tags id serial id serial tag_id integer name varchar name varchar task_id integer tasks = Task.find(:all, :include => :tags) > 0.058 сек 2 SQL запроса select * from tasks select * from tags inner join tasks_tags on tags.id = tasks_tags.tag_id where tasks_tags.task_id in (1,2,3,..) Rals должен создать модели для каждого тэга а это не быстро и занимает память
  • 10. Активное использование SQL Выборка дополнительных атрибутов к моделям: Tasks Tags Tasks_Tags id serial id serial tag_id integer name varchar name varchar task_id integer tasks = Task.find(:all, :select => &quot;*, array( select tags.name from tags inner join tasks_tags on (tags.id = tasks_tags.tag_id) where tasks_tasks.task_id=tasks.id) as tag_names &quot;) > 0.018 сек 1 SQL запрос Rails не создает модели >3x быстрее (было 0.058 сек, стало 0.018 сек) 3x!
  • 11. Активное использование SQL Выборка дополнительных атрибутов к моделям: Tasks Tags Tasks_Tags id serial id serial tag_id integer name varchar name varchar task_id integer tasks = Task.find(:all, :select => &quot;*, array( select tags.name from tags inner join tasks_tags on (tags.id = tasks_tags.tag_id) where tasks_tasks.task_id=tasks.id) as tag_names &quot;) puts tasks.first.tag_names > &quot;{Foo,Bar,Zee}&quot;
  • 12. Активное использование SQL Выборка дополнительных атрибутов соединениями требует указания параметра select в методе find : foos = Foo.find(:all, :joins => &quot;left outer join bars using bar_id&quot; не добавит атрибуты из bars потому что Rails сделает > select foos.* from foos left outer join bars... Нужно добавлять select: foos = Foo.find(:all, :select => &quot;*&quot; , :joins => &quot;left outer join bars using bar_id&quot; > select foos.* from foos left outer join bars...
  • 13. Активное использование SQL Еще о предварительной загрузке ассоциаций... она не работает с find_by_sql class Foo belongs_to :bar end foos = Foo. find_by_sql ( 'select * from foos inner join bars' ) foos. first . bar #еще 1 SQL запрос!
  • 14. Активное использование SQL Virtual Attributes plugin: http://github.com/acunote/virtual_attributes/ class Bar end class Foo belongs_to :bar preloadable_association :bar end foos = Foo. find_by_sql ( ' select * from foos left outer join (select id as preloaded_bar_id, name as preloaded_bar_name from bars) as bars on foos.bar_id = bars.preloaded_bar_id' ) foos. first . bar #no extra SQL query!
  • 15. Так все же, о чем речь? Активное использование SQL Перенос функциональности и логики в SQL Почему Postgres? Оптимизация БД Тестирование производительности
  • 16. Acunote > Сложные запросы Дерево задач (3 уровня) (+2 соединения и 1 вл. запрос) Тэги к задачам (+2 вложенных запроса) Счетчики свойств задач (+4 вложенных запроса) Последние значения изменяемых во времени атрибутов (+4 соединения с &quot;бухгалтерскими запросами&quot;) и т.д... - 12 соединений и вложенных запросов 1 4 2 3
  • 17. Acunote > Один за всех! Этот &quot;монстро&quot; выбирает дерево из нескольких сотен элементов и загружает всю необходимую информацию к ним не более чем за 50 мс! Даже на EeePC это занимает 58мс Эквивалентный Ruby/Rails код занимал до 8сек
  • 18. Acunote > &quot;Бухгалтерский&quot; запрос Issues Timecells Цель: выбрать самое последнее значение из ячеек
  • 19. Acunote > &quot;Бухгалтерский&quot; запрос Issues Timecells select * from Issues left outer join ( select distinct on (issue_id) issue_id as cell_issue_id, value as cell_value, date as cell_date from Timecells where date <= '2009-06-09' order by issue_id, date desc ) as cells on (issues.id = cells.cell_issue_id)
  • 20. Acunote > Эффективные деревья Обычный способ моделирования деревьев в Rails : create table Task ( id serial not null , parent_id integer ) class Task < ActiveRecord::Base acts_as_tree end Использует N+1 запросов для загрузки N узлов из дерева: (root) select * from tasks where parent_id = nil - 1 select * from tasks where parent_id = 1 - 11 select * from tasks where parent_id = 11 - 111 select * from tasks where parent_id = 111 - 112 select * from tasks where parent_id = 112 - 12 select * from tasks where parent_id = 12 - 2 select * from tasks where parent_id = 2 - 21 select * from tasks where parent_id = 21
  • 21. Acunote > Эффективные деревья Обычный способ моделирования деревьев в Rails : create table Task ( id serial not null , parent_id integer ) class Task < ActiveRecord::Base acts_as_tree end А должно быть так: select * from tasks left outer join (select id as parents_id, parent_id as parents_parent_id from tasks) as parents on (tasks.parent_id = parents_id) left outer join (select id as parents_parents_id from tasks) as parents_parents on (parents_parent_id = parents_parents_id)
  • 22. Acunote > Разбиение по страницам Разбиение по страницам это: select * from Issues where <conditions> limit N offset M
  • 23. Acunote > Efficient Pagination Но будьте осторожны со вложенными запросами: select *, (select count(*) from attachments where issue_id = issues.id) as num_attachments from issues limit 100 offset 0 ; Limit (cost=0.00..831.22 rows=100 width=143) (actual time=0.050..1.242 rows=100 loops=1) -> Seq Scan on issues (cost=0.00..2509172.92 rows=301866 width=143) (actual time=0.049..1.119 rows=100 loops=1) SubPlan -> Aggregate (cost=8.27..8.28 rows=1 width=0) (actual time=0.006..0.006 rows=1 loops= 100 ) -> Index Scan using attachments_issue_id_idx on attachments (cost=0.00..8.27 rows=1 width=0) (actual time=0.004..0.004 rows=0 loops= 100 ) Index Cond: (issue_id = $0) Total runtime: 1.383 ms
  • 24. Acunote > Efficient Pagination Но будьте осторожны со вложенными запросами: select *, (select count(*) from attachments where issue_id = issues.id) as num_attachments from issues limit 100 offset 100 ; Limit (cost=831.22..1662.44 rows=100 width=143) (actual time=1.070..7.927 rows=100 loops=1) -> Seq Scan on issues (cost=0.00..2509172.92 rows=301866 width=143) (actual time=0.039..7.763 rows=200 loops=1) SubPlan -> Aggregate (cost=8.27..8.28 rows=1 width=0) (actual time=0.034..0.034 rows=1 loops= 200 ) -> Index Scan using attachments_issue_id_idx on attachments (cost=0.00..8.27 rows=1 width=0) (actual time=0.032..0.032 rows=0 loops= 200 ) Index Cond: (issue_id = $0) Total runtime: 8.065 ms
  • 25. Acunote > Efficient Pagination Но будьте осторожны со вложенными запросами: они выполняются limit + offset раз ! Используйте соединения в таком случае
  • 26. Acunote > Пользователи и роли Упрощенная модель ролей для пользователей: Users Role Roles_Users id serial id serial user_id integer name varchar name varchar role_id integer privilege1 boolean privilege2 boolean ... user = User.find(:first, :include => :roles) can_do_1 = user.roles.any { |role| role.privilege1? }
  • 27. Acunote > Пользователи и роли Упрощенная модель ролей для пользователей: Users Role Roles_Users id serial id serial user_id integer name varchar name varchar role_id integer privilege1 boolean privilege2 boolean ... user = User.find(:first, :include => :roles) can_do_1 = user.roles.any { |role| role.privilege1? } В чем проблема? - 2 SQL запроса - заставляем Rails создавать объекты ролей - заставляем Ruby делать цикл
  • 28. Acunote > Пользователи и роли Перепишем на SQL: Users Role Roles_Users id serial id serial user_id integer name varchar name varchar role_id integer privilege1 boolean user = User.find(:first, :select => &quot;*&quot;, :joins => &quot; inner join (select user_id, bool_or(privilege1) as privilege1 from roles_users inner join roles on (roles.id = roles_users.role_id) group by user_id ) as roles_users on (users.id = roles_users.user_id) &quot; ) can_do_1 = ActiveRecord::ConnectionAdapters::Column. value_to_boolean(user.privilege1)
  • 29. Acunote > Пользователи и роли Есть ли эффект от такого SQL? цифры из жизни: can_do_1 = user.roles.any { |role| role.privilege1? } > код с использованием такого подхода исполнялся 2.1 сек can_do_1 = ActiveRecord::ConnectionAdapters::Column. value_to_boolean(user.privilege1) > код с использованием такого подхода исполнялся 64 мс !!!
  • 30. Acunote > OLAP и его подобия Делаете операции аггрегирования и выборки на больших объемах данных? Делайте их в SQL! цифры из жизни: 600 000 строк с данными, куб в 3х измерениях, срез и аггрегирование в Ruby: ~1 Gb памяти, ~90 сек в SQL: до 5 сек
  • 31. Общий вывод Хотите оптимизировать? Правило такое: Пишите Ruby код который генерирует SQL который экономит гигабайт памяти который выполняется в десятки/сотни раз быстрее и лучше масштабируется
  • 32. Так все же, о чем речь? Активное использование SQL Перенос функциональности и логики в SQL Почему Postgres? Оптимизация БД Тестирование производительности
  • 33. Почему Postgres? Соответствие стандарту Хорошая документация Понятный процесс разработки Плюс при принятии на работу
  • 34. Postgres спасет отца русской демократии Ограничения, ссылочная целостность Автоопределение deadlock'ов Хороший оптимизатор (иногда делает чудеса) Понятный Explain Analyze Полезные надстройки над стандартом (напр. массивы)
  • 35. Postgres спасет отца русской демократии Вывод: если СУБД для вас - это не только средство хранения данных, используйте Postgres
  • 36. Так все же, о чем речь? Активное использование SQL Перенос функциональности и логики в SQL Почему Postgres? Оптимизация БД Тестирование производительности
  • 37. Оптимизация БД > Основы Единственный путь оптимизации PostgreSQL: explain analyze explain analyze explain analyze ...
  • 38. Оптимизация БД > „Холодные“ запросы EXPLAIN ANALYZE все объясняет, но... ... его нужно делать и для „холодного“ состояния базы! Пример: сложный запрос к таблице из 230 000 строк 9 вложенных запросов и соединений: холодное состояние: 28 сек, горячее состояние: 2.42 сек Перезапуск СУБД не помогает! Нужно очищать дисковый кэш: sudo echo 3 | sudo tee /proc/sys/vm/drop_caches (Linux)
  • 39. Оптимизация БД > Общий сервер Если ваш хостинг предоставляет общий сервер БД для всех клиентов, то вы соревнуетесь с ними за кэш в памяти: 1. две БД с одинаковой загрузкой честно поделят кэш
  • 40. Optimize Database > Shared Database Если ваш хостинг предоставляет общий сервер БД для всех клиентов, то вы соревнуетесь с ними за кэш в памяти: 2. менее нагруженная БД проигрывает битву за кэш
  • 41. Optimize Database > Shared Database В результате, ваша БД будет всегда холодной и вы будете читать данные с диска а не с памяти! помните пример со сложным запросом: с диска: 28 сек, из памяти: 2.42 сек Решения: оптимизация для холодного состояния указание большего кол-ва условий выборки sudo echo 3 | sudo tee /proc/sys/vm/drop_caches
  • 42. Оптимизация БД > array() Используйте any(array ()) всесто in() для того чтобы получить subselect а не join explain analyze select * from issues where id in (select issue_id from tags_issues); QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------- Merge IN Join (actual time=0.096..576.704 rows=55363 loops=1) Merge Cond: (issues.id = tags_issues.issue_id) -> Index Scan using issues_pkey on issues (actual time=0.027..270.557 rows=229991 loops=1) -> Index Scan using tags_issues_issue_id_key on tags_issues (actual time=0.051..73.903 rows=70052loops=1) Total runtime: 605.274 ms explain analyze select * from issues where id = any( array( (select issue_id from tags_issues) ) ); QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------ Bitmap Heap Scan on issues (actual time=247.358..297.932 rows=55363 loops=1) Recheck Cond: (id = ANY ($0)) InitPlan -> Seq Scan on tags_issues (actual time=0.017..51.291 rows=70052 loops=1) -> Bitmap Index Scan on issues_pkey (actual time=246.589..246.589 rows=70052 loops=1) Index Cond: (id = ANY ($0)) Total runtime: 325.205 ms 2x!
  • 43. Оптимизация БД > Условия выборки Самостоятельно указывайте условия выборки во вложенных запросах и соединениях PostgreSQL зачастую сам не может этого сделать select *, ( select notes.author from notes where notes.bug_id = bugs.id ) as note_authors from bugs where org_id = 1 select *, ( select notes.author from notes where notes.bug_id = bugs.id and org_id = 1 ) as note_authors from bugs where org_id = 1 Bugs id serial name varchar org_id integer Notes id serial name varchar bug_id integer org_id integer
  • 44. Так все же, о чем речь? Активное использование SQL Перенос функциональности и логики в SQL Почему Postgres? Оптимизация БД Тестирование производительности
  • 45. Тестирование производительности Во первых, создайте набор тестов, измеряющих производительность самых популярных страниц. Например: Benchmark Burndown 120 0.70 ± 0.00 Benchmark Inc. Burndown 120 0.92 ± 0.01 Benchmark Sprint 20 x (1+5) (C) 0.45 ± 0.00 Benchmark Issues 100 (C) 0.34 ± 0.00 Benchmark Prediction 120 0.56 ± 0.00 Benchmark Progress 120 0.23 ± 0.00 Benchmark Sprint 20 x (1+5) 0.93 ± 0.00 Benchmark Timeline 5x100 0.11 ± 0.00 Benchmark Signup 0.77 ± 0.00 Benchmark Export 0.20 ± 0.00 Benchmark Move Here 20/120 0.89 ± 0.00 Benchmark Order By User 0.98 ± 0.00 Benchmark Set Field (EP) 0.21 ± 0.00 Benchmark Task Create + Tag 0.23 ± 0.00 ...
  • 46. Тестирование производительности Еще один тип интеграционного тестирования: class RenderingTest < ActionController::IntegrationTest def test_sprint_rendering login_with users (:user), &quot;user&quot; benchmark :title => &quot;Sprint 20 x (1+5) (C)&quot;, :route => &quot;projects/1/sprints/3/show&quot;, :assert_template => &quot;tasks/index&quot; end end Benchmark Sprint 20 x (1+5) (C) 0.45 ± 0.00
  • 47. Тестирование производительности Еще один тип интеграционного тестирования: def benchmark (options = {}) (0..100). each do |i| GC. start pid = fork do begin out = File. open (&quot;values&quot;, &quot;a&quot;) ActiveRecord::Base. transaction do elapsed_time = Benchmark:: realtime do request_method = options[:post] ? :post : :get send (request_method, options[:route]) end out. puts elapsed_time if i > 0 out. close raise CustomTransactionError end rescue CustomTransactionError exit end end Process:: waitpid pid ActiveRecord::Base. connection . reconnect ! end values = File. read (&quot;values&quot;) print &quot;#{ mean (values).to_02f} ± #{ sigma (values).to_02f}&quot; end
  • 48. Тестирование производительности Осторожно! Потеря 10мс в тесте зачастую только кажется безобидной Потому что может случиться так, что эти 10мс уходят на случайно добавленный SQL запрос
  • 49. Тестирование производительности Тесты запросов def test_queries queries = track_queries do get :index end assert_equal queries, [ &quot;Foo Load&quot;, &quot;Bar Load&quot;, &quot;Moo Create&quot; ] end
  • 50. Тестирование производительности module ActiveSupport class BufferedLogger attr_reader :tracked_queries def tracking=(val) @tracked_queries = [] @tracking = val end def add_with_tracking(severity, message = nil, progname = nil, &block) @tracked_queries << $1 if @tracking && message =~ /3[56]1m(.* (Load|Create|Update|Destroy)) / @tracked_queries << $1 if @tracking && message =~ /3[56]1m(SQL) / add_without_tracking (severity, message, progname, &block) end alias_method_chain :add, :tracking end end class ActiveSupport::TestCase def track_queries(&block) RAILS_DEFAULT_LOGGER. tracking = true yield result = RAILS_DEFAULT_LOGGER. tracked_queries RAILS_DEFAULT_LOGGER. tracking = false result end end
  • 51. Интересно оптимизировать Rails? Приходите в нашу команду! http://www.acunote.com/pluron/jobs
  • 52. Cпасибо за внимание! Вопросы? Наш блог про Rails Performance: http://blog.pluron.com Александр Дымо Director of Engineering [email_address]