You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
1) Необходимо было обработать файл с расписанием автобусов. У нас уже была программа на ruby, которая умела делать нужную обработку. Она успешно работала на файлах небольшого размера, но для большого файла она работала слишком долго.
7
+
2) Страница отображения расписания автобусов тоже работала слишком долго. Пользователи жаловались на долгую загрузку страницы (расписание Таганрог/Владивосток загружалось 42 секунды (~2к записей)).
8
+
9
+
Я решила исправить обе проблемы, оптимизировав загрузку и отображение.
7
10
8
11
## Оптимизация импорта данных
9
12
10
-
напишем раннер для запуска / мониторинга времени / памяти выполнения скрипта импорта
13
+
## Формирование метрики
14
+
15
+
Конечная метрика: время выполнения импорта файла `fixtures/large.json` должно укладываться в 60 сек.
16
+
Для того, чтобы понимать, дают ли мои изменения положительный эффект на быстродействие программы я придумала использовать такую метрику:
17
+
1) обработка файла после внесенных оптимизаций должна быть меньше, чем до оптимизации
18
+
19
+
## Предварительная подготовка
20
+
21
+
1) Добавила в проект тесты
22
+
2) Написала раннер для профилирования / мониторинга времени / памяти выполнения скрипта импорта
11
23
24
+
## Ваша находка №1
12
25
1) время выполнения fixtures/small.json - 10.167809
13
26
2) воспользуемся rbspy для профилирования скрипта импорта
14
27
3) rbspy / rubyprof выдают много лишних данных при вызове таски, поэтому можно вынести импорт в отдельный класс и профилировать его
15
28
4) вынесла в класс DataLoader, перенесла тесты
16
29
17
30
Пока выносила обратила внимание что файл считывается в память целиком, также в задании упоминается про файл 1М который весит примерно 3ГБ, перепишем сразу на стриминг
18
-
добавила класс jsonStreamer - собирает объекты и возвращает по одному, какой то адекватный гем не нашла для такой обработки файла, теперь используется потоковая обработка файла
19
-
31
+
добавила класс JsonStreamer - собирает объекты и возвращает по одному. Гемы потоковой обработки выглядят сложновато для достаточно простой задачи, возможно они работают быстрее, но этим можно будет озаботиться попозже
20
32
21
-
1) время выполнения не поменялось
22
-
2) время выполнения fixtures/small.json - 10.473295
23
-
3) отчет stackfrof - главная точка роста:
33
+
## Ваша находка №2
34
+
1) время выполнения fixtures/small.json не поменялось - 10.473295
35
+
2) отчет stackfrof - главная точка роста:
24
36
в отчете видны 2 главные точки роста - update (41%) и find_or_create_by (25%)
25
-
4) что делать с update пока непонятно, а вот такое кол-во find_or_create_by можно заменить
26
-
5) Внесем правки в работу с городами, написано что в файле их не больше 100, попробуем собирать их в словать параллельно создавая
27
-
6) время выполнения fixtures/small.json - 8.523640
28
-
7) изменения в отчете профилировщика: find_or_create_by снизился до 18%
37
+
3) что делать с update пока непонятно, а вот такое кол-во find_or_create_by можно заменить
38
+
4) Внесем правки в работу с городами, написано что в файле их не больше 100, попробуем собирать их в словарь, параллельно создавая
39
+
5) время выполнения fixtures/small.json - 8.523640
40
+
6) изменения в отчете профилировщика: find_or_create_by снизился до 18%
29
41
42
+
## Ваша находка №3
30
43
1) также у нас собирается еще 2 стравочника - services, buses
31
44
2) заменим find_or_create_by и там
32
45
3) services: 6.919434, find_or_create_by 23%
33
46
4) buses: 5.319638, find_or_create_by больше нет
34
47
35
-
1) главная точка роста: update (64%)
48
+
## Ваша находка №4
49
+
1) отчет stackfrof - главная точка роста: update (64%)
36
50
2) обновляется автобус сервисами, это обновление в целом выглядит довольно бесполезно, потому что сервисы останутся от последнего trip в файле
37
51
3) поэтому обновление сервисами можно в принципе убрать, либо уточнить формат файла, должны ли они добавляться / обновляться или браться пересечение
38
-
4) убрали обновление заменив его на создание сервисов при создании автобуса
52
+
4) убрали обновление заменив его на создание сервисов при создании автобуса (добавила модель buses_service)
39
53
5) время: 3.025756
40
54
6) отчет профилировщика: update больше нет
41
55
42
-
1) главная точка роста: создание записей
43
-
2) обратимся к логам и посмотрим на кол-во обращений к базе
44
-
3) на файл example к базе было 10 (создание trip) + 2 (проверка существования автобуса + создание) + 4 (проверка городов + создание) + 2 (создание сревисов) = 18
45
-
4) можно воспользоваться стримингом из readme
46
-
5) время для small: 0.138599
47
-
6) medium: 0.510029
48
-
7) large: 3.958625
49
-
8) 1M: 38.381950
50
-
9) ну тут уже время упирается в стример, без каких либо преобразований он перебирает файл 1M за 34.973238
51
-
10) причем потребление памяти на 1М не превышает 8МБ
56
+
## Ваша находка №5
57
+
1) обратимся к логам и посмотрим на кол-во обращений к базе
58
+
2) на файл example (10 рейсов) к базе обращений было: 10 (создание trip) + 2 (проверка существования автобуса + создание) + 4 (проверка городов + создание) + 2 (создание сревисов) = 18
59
+
3) можно воспользоваться алгоритмом и стримингом из readme
60
+
4) время для small: 0.138599
61
+
5) medium: 0.510029
62
+
6) large: 3.958625
63
+
7) 1M: 38.381950
64
+
8) ну тут уже время упирается в стример, без каких либо преобразований он перебирает файл 1M за 34.973238
65
+
9) причем потребление памяти на 1М не превышает 8МБ
52
66
```
53
67
INITIAL MEMORY USAGE: 103 MB
54
68
MEMORY USAGE: 103 MB
@@ -61,4 +75,79 @@ MEMORY USAGE: 111 MB
61
75
FINAL MEMORY USAGE: 110 MB
62
76
```
63
77
78
+
## Оптимизация отображения расписания
79
+
80
+
## Формирование метрики
81
+
82
+
Конечная метрика: любая страница должна грузиться менее 0.3 секунды
83
+
Для того, чтобы понимать, дают ли мои изменения положительный эффект на быстродействие программы я придумала использовать такую метрику:
84
+
1) время рендеринга страницы после внесенных оптимизаций должно быть меньше, чем до оптимизации
85
+
86
+
## Оптимизация
87
+
88
+
Набор данных 1M. Для начала загрузим страницу http://localhost:3000/автобусы/Самара/Москва и посмотрим на логи
89
+
90
+
## Ваша находка №1
91
+
1) Completed 200 OK in 745ms (Views: 228.0ms | ActiveRecord: 487.9ms (120 queries, 0 cached) | GC: 33.0ms)
92
+
2) Выборка занимает бОльшую часть времени, начнем с нее. Также по логам видно, что для каждого trip делается отдельный запрос к базе чтобы достать автобус, а для него сервисы
93
+
3) Попробуем воспользоваться includes
94
+
4) Completed 200 OK in 157ms (Views: 55.8ms | ActiveRecord: 97.3ms (7 queries, 0 cached) | GC: 6.0ms)
95
+
5) Время загрузки уменьшилось и кол-во запросов тоже
96
+
6) Рендер тоже ускорился, потому что все вызовы были непосредствено из вьюхи
3) Главной точкой роста опять стал рендеринг, попробуем оптимизировать его еще лучше
130
+
4) Попробуем убрать отдельный рендер разделителя и сервисов
131
+
5) Воспользовалась spacer_template: Completed 200 OK in 276ms (Views: 217.5ms | ActiveRecord: 51.2ms (7 queries, 0 cached) | GC: 11.7ms)
132
+
6) Уберем рендер сервисов в partial trip. некрасиво, но это ускорит рендер
133
+
7) Completed 200 OK in 104ms (Views: 85.7ms | ActiveRecord: 14.4ms (7 queries, 0 cached) | GC: 5.2ms)
134
+
135
+
## Ваша находка №6
136
+
1) Проверим на большем кол-ве
137
+
2) самое большое кол-во рейсов - 4191 (Таганрог - Таганрог)
138
+
3) Completed 200 OK in 620ms (Views: 499.6ms | ActiveRecord: 127.1ms (7 queries, 1 cached) | GC: 55.3ms)
139
+
4) Время почти в 2 раза больше желательного
140
+
5) Посмотрим на это со стороны того, что за раз пользователю не надо видеть все 4к записей, можно добавить пагинацию
141
+
6) добавила пагинацию с дефолтным значением 100 записей (гем использовать не стала, потому что в текущем варианте это не сложно сделать)
142
+
7) одна страница: Completed 200 OK in 39ms (Views: 23.5ms | ActiveRecord: 10.0ms (7 queries, 0 cached) | GC: 0.0ms)
143
+
144
+
Можно закончить оптимизацию на этом, так как время выполнения укладывается в приемлемые рамки
145
+
146
+
Для красоты конечно лучше бы добавить турбо фреймы, чтобы полностью не перезагружать страницу при пагинации, но это уже не входят в текущую задачу
147
+
148
+
## Результаты
149
+
В результате проделанной оптимизации удалось ускорить импорт файла `fixtures/large.json` до 4 секунд и уложиться в метрику. Также удалось ускорить загрузку страницы Таганрог/Владивосток до ~300ms без пагинации, или ~50ms с пагинацией по 100 рейсов на странице
64
150
151
+
## Защита от регрессии производительности
152
+
1) для импорта данных был написан тест на проверку времени выполнения
153
+
2) для отображения расписания был бы написан тест на N+1 запрос, если бы rspec-sqlimit был совместим с rails 8.0.1
0 commit comments