Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.

Подход Доктора Хауса в тестировании оптимизации запросов

1 249 vues

Publié le

Презентация доклада Сергея Михалева на конференции SQADays-14, Львов 8-9 ноября 2013

Publié dans : Formation
  • Soyez le premier à commenter

  • Soyez le premier à aimer ceci

Подход Доктора Хауса в тестировании оптимизации запросов

  1. 1. Подход Доктора Хауса в тестировании оптимизации запросов (третья серия) Автор: Михалев Сергей f1incode.com
  2. 2. У вас есть отличная команда Но не докторов, а программистов, которые занимаются оптимизацией вашего приложения.
  3. 3. Понедельник: и вдруг … С этого дня вы сами должны тестировать ваши оптимизации!
  4. 4. Понедельник: и вдруг … Теперь мы отвечаем за тестирование …
  5. 5. Что!? А как же отдел тестирования? “Отличное” начало рабочей недели.
  6. 6. У нас новый ПАЦИЕНТ А что тогда делать мне? Я буду оптимизировать Тогда я буду тестировать оптимизацию …
  7. 7. Ты Понедельник будешь тестировать функциональность изменений Но ведь это самое неинтересное … Разве? .. У нас новый ПАЦИЕНТ
  8. 8. Понедельник Я сделаю все так, чтобы это было интересно и этим можно было гордиться! У нас новый ПАЦИЕНТ
  9. 9. Наш пациент : предмет оптимизации Diagnosis Search Page Patient: Disease: Doctor: Search House DB Doctor Disease Dr. House Lumpus Ivanov Petr Loading 5 Records … / Pages 3 UI Patient Loading 123 … Petrov Ivan Dr. House Lumpus select * from ( select patient_name, doctor_name, disease_name, row_number() over(order by patient_name) as row_index from list_diagnosis() where doctor_name = 'House‘ ) d where row_index between 1 and 2 Запрос для постраничного вывода данных select count(*) from list_diagnosis() where doctor_name = 'House' Запрос общего количества строк
  10. 10. Уровни тестирования 1) Чем ниже уровень тестирования, тем оно Тестирование через UI a) меньше слоев вовлекает; b) более направленное; c) быстрее может выполняться. UI Приложение 2) Автоматическое Тестирование тестирование дает через API возможность перезапускать наши тесты, производя тем самым регрессионное База данных Тестирование тестирование. через DB
  11. 11. Основные вопросы Как функционально тестировать оптимизацию запросов на уровне базы данных? Как сделать тесты воспроизводимыми?
  12. 12. Требования к решению 1) Нашим тестом должен быть запрос. Это позволит тестировать на уровне базы. 2) Нужен запрос, который сравнит результаты функции до оптимизации с результатами этой же функции, но уже после оптимизации. 3) При этом сделает это для различных входных параметров. 4) И выведет все расхождения, если они есть. 5) Плюс будет достаточно быстрым.
  13. 13. Конец понедельника
  14. 14. Вторник Продолжаем готовиться к тестированию …
  15. 15. Вторник Можем даже устроить daily standup
  16. 16. Сравнение двух множеств множество мужчин из сериала (A) Форман Чейз Хаус множество умных врачей (B) Форман Чейз Кэмерон Хаус A except B B except A пустое множество Кэмерон Тогда чтобы сравнить эти два множества мы можем использовать следующую последовательность операций. (A except B) union (B except A) В результате пустое множество означает, что все элементы А есть во множестве B и все элементы B есть в А. Значит эти два множества состоят из одинаковых элементов.
  17. 17. Операции со множествами в SQL with source as ( select patient_name, desease_name, doctor_name from list_diagnosis_old() ), target as ( Исходная функция Оптимизированная функция select patient_name, desease_name, doctor_name from list_diagnosis_new() ) (select * from source except select * from target) union all (select * from target except select * from source) Операция сравнения
  18. 18. Функция с параметрами Наша функция зависит от параметра list_diagnosis_new(@hospital_id) Тогда нам нужно получить множество параметров, на котором мы будем тестировать нашу функцию. Например, таким образом select hospital_id from HOSPITAL Если параметров несколько select hospital_id, doctor_id from HOSPITAL cross join DOCTOR Или самим сформировать множество параметров select 1 as doctor_id union all select 3 union all select 4 union all select 6 Таким образом, мы может написать совершенно любой запрос для формирования множества параметров.
  19. 19. Функция с параметрами После этого нам нужно научиться вызывать нашу функцию для интересующего нас множества параметров. select p.hospital_id, patient_name, desease_name, doctor_name Выходное тестируемое from множество, включая ( параметры select hospital_id from HOSPITAL Множество ) p cross apply list_diagnosis(p.hospital_id) тестируемых параметров Тестируемая функция
  20. 20. Итоговый запрос with source as Множества параметров ( select hospital_id, patient_name, desease_name, f.doctor_name from HOSPITAL h cross apply list_diagnosis_old(hospital_id) f ), target as Тестируемые функции ( select hospital_id, patient_name, desease_name, f.doctor_name from HOSPITAL h cross apply list_diagnosis_new(hospital_id) f ) (select * from source except select * from target) union all (select * from target except select * from source) Операция сравнения
  21. 21. Чейз закончил оптимизацию, дело за нами. Вечер вторника Быстро, я пока еще полностью не готова.
  22. 22. Среда Я получил Вечер вторника оптимизацию в 5 раз!!!
  23. 23. Особенно программисты  Поэтому ВСЁ нужно перепроверять.
  24. 24. Начинаем тестировать
  25. 25. Экспресс анализ изменений Старая функция Новая функция Очевидно, что необходимо тестировать функциональность!
  26. 26. Скорость и покрытие 1) Чем больше объем данных. 2) Чем больше тестируемое множество параметров. 3) Чем медленнее функция. Тем, очевидно, дольше будет работать сравнение. Поэтому не стоит запускать сравнение сразу на полном объеме данных и с полным множеством параметров. Наращивайте скоуп тестирования постепенно.
  27. 27. Начинаем тестировать with source as ( select hospital_id, patient_name, desease_name, f.doctor_name from (select top 1 hospital_id from HOSPITAL order by hospital_id) cross apply list_diagnosis_old(hospital_id) f ), target as ( select hospital_id, patient_name, desease_name, f.doctor_name from (select top 1 hospital_id from HOSPITAL order by hospital_id) cross apply list_diagnosis_new(hospital_id) f ) (select * from source except select * from target) union all (select * from target except select * from source)
  28. 28. Увеличиваем скоуп with source as ( select hospital_id, patient_name, desease_name, f.doctor_name from (select top 100 hospital_id from HOSPITAL order by hospital_id cross apply list_diagnosis_old(hospital_id) f ), target as ( select hospital_id, patient_name, desease_name, f.doctor_name from (select top 100 hospital_id from HOSPITAL order by hospital_id cross apply list_diagnosis_new(hospital_id) f ) (select * from source except select * from target) union all (select * from target except select * from source)
  29. 29. Сравниваем на всем объеме with source as ( select hospital_id, patient_name, desease_name, f.doctor_name from HOSPITAL h cross apply list_diagnosis_old(h.hospital_id) f ), target as ( select hospital_id, patient_name, desease_name, f.doctor_name from HOSPITAL h cross apply list_diagnosis_new(h.hospital_id) f ) (select * from source except select * from target) union all (select * from target except select * from source)
  30. 30. Тестирование на всем объеме Даже, если мы протестировали на всем объеме имеющихся данных. Дает ли нам это гарантию, что мы действительно протестировали все и ошибок нет? НЕТ Потому что, тестирование происходит на уже существующих данных. И база может не содержать некоторых важных случаев. НО Если система существует продолжительное время. То именно для оптимизации запросов, это позволяет получить очень большую долю уверенности, что все хорошо.
  31. 31. Результаты тестирования И функционально работает правильно. Функция действительно стала быстрее. Ну … тогда выносим.
  32. 32. Выносим изменения
  33. 33. Четверг: после оптимизации у нас БАГ! Ну как же так, я же все проверила.
  34. 34. Описание бага BUG Количество строк в гриде не совпадает с подсчитанным в футоре. Diagnosis Search Page Patient: UI Disease: Doctor: Search House Doctor Disease Dr. House Lumpus Ivanov Petr Records 5 / Pages 3 DB Patient 123 Petrov Ivan Dr. House Lumpus select * from ( select patient_name, doctor_name, disease_name, row_number() over(order by patient_name) as row_index from list_diagnosis(@hospital_id) where doctor_name = 'House‘ ) d where row_index between 1 and 2 select count(*) from list_diagnosis(@hospital_id) where doctor_name = 'House'
  35. 35. Еще об операциях над множествами Результат запроса – это не множество. Понятие множества подразумевает, что в нем нет одинаковых элементов. Результат запроса(A) Форман Форман Чейз Результат запроса (B) A except B B except A Форман Чейз пустое множество пустое множество SQL операция except работает со множеством, т.е. удалит все дубликаты. По аналогии с union и union all в стандарте есть операция except all, но она не реализована в SQL Server-e.
  36. 36. except all – своими руками A Форман Форман Чейз A’ 1, Форман 2, Форман 1, Чейз B A except B B except A Форман Чейз пустое множество пустое множество B’ A’ except B’ B’ except A’ 2, Форман пустое множество 1, Форман 1, Чейз Таким образом мы добавили специальный идентификатор, который будет наращиваться только для одинаковых строк.
  37. 37. except all – своими руками select hospital_id, patient_name, desease_name, doctor_name, row_number() over ( partition by hospital_id, patient_name, desease_name, f.doctor_name order by hospital_id, patient_name, desease_name, f.doctor_name ) number from list_diagnosis_old(hospital_id) Функция row_number() нумерует строки исходя из сортировки и разбиения partition by отвечает за разбиения на подмножества order by отвечает за сортировку в подмножестве
  38. 38. Наш итоговый запрос with source as ( select hospital_id, patient_name, desease_name, doctor_name, row_number() over Исходная функция ( partition by hospital_id, patient_name, desease_name, f.doctor_name order by hospital_id, patient_name, desease_name, f.doctor_name ) number from HOSPITAL h cross apply list_diagnosis_old(hospital_id) ), target as Тестируемая функция, с ( ... такими же изменениями ) (select * from source except select * from target) union all (select * from target except select * from source)
  39. 39. Повторяем тестирование
  40. 40. Но результаты опять положительные … И проблема воспроизводиться только на продакшене.
  41. 41. Описание бага: еще разок BUG Количество строк в гриде не совпадает с подсчитанным в футоре. Т.е. отображается одна строка, а count(*) показывает, что их там должно быть пять. select * from ( select patient_name, doctor_name, disease_name, row_number() over(order by patient_name) as row_index from list_diagnosis(@hospital_id) where doctor_name = 'House‘ )d where row_index between 1 and 2 DB select count(*) from list_diagnosis(@hospital_id) where doctor_name = 'House' Т.е. одна и та же функция с одинаковыми фильтрами, но в разных контекстах возвращает разное количество строк.
  42. 42. Вечер четверга этого просто не может быть …
  43. 43. Вечер четверга но причина есть всегда!
  44. 44. Пятница: дифференциальный анализ
  45. 45. Дифференциальный анализ Симптомы: 1) На продакшене, результаты функции count(*) не совпадают с реальным количеством строк. 2) На локальных окружениях это проблема не воспроизводиться. Диагноз: 1) Возможно на это влияют какие-то настройки SQL Server-а? 2) Или в самой функции есть какие-то логические ошибки, которые почему-то проявляются только на продакшене.
  46. 46. Утро пятницы Я нашла причину! ночь прошла не зря …
  47. 47. Анализ функции select p.patient_name, desease_name, doctor_name from PATIENT p Достаем cross apply последний диагноз ( пациента. select top 1 desease_name, doctor_name from DIAGNOSIS g А если, по какой-то inner join DOCTOR d on g.doctor_id = d.doctor_id причине, есть 2 where g.patient_id = p.patient_id диагноза с одним order by g.create_time временем. ) d 1) В этом случае SQL Server не гарантирует какую строчку он в действительности достанет. 2) Это зависит от плана выполнения запроса, который, очевидно, был разным для двух использований функции. 3) Поэтому функция становиться недетерминированной, т.е. при одинаковых входных данных возвращать разные результаты.
  48. 48. Исправляем логическую ошибку select p.patient_name, desease_name, doctor_name from PATIENT p cross apply ( select top 1 desease_name, doctor_name from DIAGNOSIS g inner join DOCTOR d on g.doctor_id = d.doctor_id where g.patient_id = p.patient_id order by g.create_time ) d Очевидно, нужно заменить недетерминированный order by, например так: order by g.create_time, g.diagnosis_id или order by g.diagnosis_id
  49. 49. Начинаем тестировать
  50. 50. Выносим изменения
  51. 51. Анализ данного подхода 1) Тестирование производится на 1) Полное тестирование не уровне базы данных. всегда возможно. 2) Есть возможность 2) Даже при тестировании на протестировать изменения на всех данных – это не гарант очень большом объеме того, что ошибок нет. Так как: данных. • данных в базе может быть 3) Подход может использоваться: недостаточно для a) для функций, для запросов, для хорошего сравнения; сравнения целых баз данных • даже в старой функции b) в оптимизации, в ETL, для любых могут быть логические сравнений, где есть эталон. ошибки; Но в целом, если изменения производились только на уровне базы данных, то такой подход целесообразнее.
  52. 52. Подводим итоги недели 1) Разобрались с операциями над множествами, такими как except и union. 2) Реализовали except all – своими руками. 3) Разработали метод функционального тестирования запросов. 4) Функционально протестировали оптимизацию запроса на уровне базы данных. 5) Нашли очень замысловатую логическую ошибку и исправили старый функциональный баг.
  53. 53. Подход Доктора Хауса в тестировании Подводим итоги недели оптимизации запросов 1) Разобрались с операциями над 1) множествами, такими как except и Все врут, а значит всё нужно union. И даже реализовали except all. перепроверять. 2) Мы разработали метод 2) Разбираться в функционального тестирования проблеме до конца. 3) запросов. резать по Не боятся 3) Нашли оченьвсегда живому, но замысловатую логическую ошибку и исправили контролировать. 4) старый функциональный баг. Не сдаваться!!!

×