-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.xml
1869 lines (1348 loc) · 146 KB
/
index.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Инфраструктура ИТ on Инфраструктура ИТ</title>
<link>https://vitalyzhakov.github.io/</link>
<description>Recent content in Инфраструктура ИТ on Инфраструктура ИТ</description>
<generator>Hugo -- gohugo.io</generator>
<language>ru-ru</language>
<copyright>&copy; 2018</copyright>
<lastBuildDate>Wed, 20 Apr 2016 00:00:00 +0000</lastBuildDate>
<atom:link href="/" rel="self" type="application/rss+xml" />
<item>
<title>Генерация паспорта устройства</title>
<link>https://vitalyzhakov.github.io/post/device-passport/</link>
<pubDate>Mon, 13 Jun 2022 00:10:06 +0500</pubDate>
<guid>https://vitalyzhakov.github.io/post/device-passport/</guid>
<description>
<p>Часто в комплекте с устройствами поставляются их паспорта, которые содержат техническую спецификацию.
На производстве выпускается несколько модификаций одного и того же устройства.
Как сейчас - паспорта создаются путём копирования похожего и внесения в него изменений, что приводит к человеческим ошибкам.</p>
<h2 id="требования">Требования</h2>
<p>Основные параметры шаблона зависят от маркировки.
Маркировка - это строка вида DSA XXXX DS XX XX XX, где каждый символ - выбор характеристики из словаря.</p>
<p>Кроме маркировки нужно указать точные характеристики, не вынесенные в маркировку (в том числе серийный номер).</p>
<h3 id="пользователи">Пользователи</h3>
<p>Поддержка двух групп пользователей:</p>
<ul>
<li>оператор - доступ для генерации паспорта;</li>
<li>редактор - внесение изменений в шаблон паспорта или список параметров.</li>
</ul>
<h2 id="c4-диаграммы">C4 диаграммы</h2>
<h2 id="решение">Решение</h2>
<p>Для каждого типа оборудования создать xml-спецификацию</p>
<pre><code class="language-xml">&lt;?xml
&lt;head&gt;
&lt;title&gt;
Спецификация для паспорта DSA
&lt;/title&gt;
&lt;/head&gt;
&lt;template path=&quot;templates/dsa.latex&quot;&gt;
&lt;/template&gt;
&lt;form&gt;
&lt;mark&gt;
&lt;static&gt;DSA-&lt;static&gt;
&lt;select name=&quot;1&quot;&gt;
&lt;label&gt;Количество входов&lt;/label&gt;
&lt;option value=&quot;1&quot;&gt;1&lt;/option&gt;
&lt;option value=&quot;2&quot;&gt;2&lt;/option&gt;
&lt;/select&gt;
&lt;select name=&quot;2&quot;&gt;
&lt;label&gt;Измеряемая величина&lt;/label&gt;
&lt;option value=&quot;1&quot;&gt;Измерение зазора в плоскости&lt;/option&gt;
&lt;option value=&quot;2&quot;&gt;Максимальное значение в области зазора&lt;/option&gt;
&lt;/select&gt;
&lt;select name=&quot;3&quot;&gt;
&lt;label&gt;Температурный диапазон&lt;/name&gt;
&lt;option value=&quot;1&quot;&gt;4-10 С&lt;/option&gt;
&lt;option value=&quot;2&quot;&gt;-20 +50 С&lt;/option&gt;
&lt;/select&gt;
&lt;/mark&gt;
&lt;modification&gt;
&lt;select name=&quot;4&quot;&gt;
&lt;label&gt;Диапазон измерений&lt;/label&gt;
&lt;option value=&quot;5-20 mA&quot;&gt;5-20 mA&lt;/option&gt;
&lt;option value=&quot;1-18 mA&quot;&gt;1-18 mA&lt;/option&gt;
&lt;/select&gt;
&lt;select name=&quot;7&quot;&gt;
&lt;label&gt;Температурный диапазон&lt;/label&gt;
&lt;option value=&quot;1&quot;&gt;4-10 С&lt;/option&gt;
&lt;option value=&quot;2&quot;&gt;-20 +50 С&lt;/option&gt;
&lt;/select&gt;
&lt;/modification&gt;
&lt;label form=&quot;8&quot;&gt;Серийный номер&lt;/label&gt;
&lt;input name=&quot;8&quot;&gt;&lt;/input&gt;
&lt;/form&gt;
?&gt;
</code></pre>
<p>Здесь id - наименование параметра, передаваемого в шаблон.</p>
</description>
</item>
<item>
<title>Масштабирование от нуля до миллиона пользователей</title>
<link>https://vitalyzhakov.github.io/post/scaling-from-zero-to-millions-of-users/</link>
<pubDate>Wed, 10 Nov 2021 00:10:06 +0500</pubDate>
<guid>https://vitalyzhakov.github.io/post/scaling-from-zero-to-millions-of-users/</guid>
<description>
<h2 id="настройка-одного-сервера">Настройка одного сервера</h2>
<p>Путь в тысячу миль начинается с первого шага и в проектировании сложных систем аналогично.
Чтобы начать с чего-то простого, всегда запускаются на одном сервере.</p>
<p>Рисунок 1-1 демонстрирует одиночный сервер, в котором всё запущено на 1 сервере: веб приложение, база данных, кеш и т.д.</p>
<p><img src="https://vitalyzhakov.github.io/images/scaling-from-zero-to-millions-of-users/figure1-1.png" alt="Рисунок 1-1" title="Рисунок 1-1" /></p>
<p>Для того, чтобы понять конфигурацию, полезно исследовать поток запроса и источники траффика.
Давайте для начала посмотрим на движение запроса.</p>
<p><img src="https://vitalyzhakov.github.io/images/scaling-from-zero-to-millions-of-users/figure1-2.png" alt="Рисунок 1-2" title="Рисунок 1-2" /></p>
<ol>
<li>Пользователи получают доступ к вебсайтам через доменные имена, такие как api.mysite.com.
Обычно Системы доменных имён (DNS) - это платный сервис, предоставляемый сторонними организациями и не обслуживаемый нашими серверами.</li>
<li>Адрес интернет протокола (IP) возвращается в браузер или мобильное приложение. В примере возвращается адрес 15.125.23.214.</li>
<li>Как только IP адрес получен, запрос Hypertext Transfer Protocol (HTTP) отправляется напрямую к вашему веб-серверу.</li>
<li>Веб-сервер возвращает HTML-страницу или JSON-ответ. В общем случае может вернуть в теле ответа также изображения, видео и другие типы содержимого.</li>
</ol>
<p>Далее, давайте проверим источники траффика. Траффик к нашему вебсерверу приходит из двух источников:
web и мобильное приложения.</p>
<ul>
<li>Web приложение: использует комбинацию серверных языков (Java, Python, etc.) для обработки
бизнес-логики, хранения и т.д. и клиентских языков (HTML и JavaScript) для представления.</li>
<li>Мобильное приложение: протокол HTTP - протокол коммуникаций между мобильным приложением и веб-сервером.
JavaScript Object Notation (JSON) - общеиспользуемый формат для передачи данных из-за его простоты.
Пример API ответа в формате JSON представлен ниже:</li>
</ul>
<p>GET /users/12 – Retrieve user object for id = 12
<img src="https://vitalyzhakov.github.io/images/scaling-from-zero-to-millions-of-users/json-response-example.png" alt="JSON response example" title="JSON response example" /></p>
<h2 id="база-данных">База данных</h2>
<p>С ростом базы данных один сервер может оказаться недостаточным, нам понадобится несколько серверов:
один для обработки web/mobile запросов, другой для базы данных (Рисунок 1-3).
Разделение web/mobile траффика (уровень web) и базы данных (уровень данных) позволяет масштабировать их независимо.</p>
<p><img src="https://vitalyzhakov.github.io/images/scaling-from-zero-to-millions-of-users/figure1-3.png" alt="Рисунок 1-3" title="Рисунок 1-3" /></p>
<h2 id="какую-базу-данных-использовать">Какую базу данных использовать?</h2>
<p>Вы можете выбирать между традиционной реляционной базой и нереляционной базой. Давайте поймём, в чём разница.
Реляционная база данных также называется реляционной системой управления баз данных (RDBMS) или SQL базой данных.
Самые известные представители - PostgreSQL, MySQL, Oracle database, и другие.</p>
<p>Реляционная база данных представляет и хранит данные в таблицах и строках. Вы можете выполнять операции объединения используя язык запросов SQL
над разными таблицами базы данных.</p>
<p>Нереляционные базы данных также называют NoSQL базами данных.</p>
<p>Известные представители - CouchDB, Neo4j, Cassandra, HBase, Amazon DynamoDB, MongoDB, etc</p>
<p>Эти базы данных группируются в 4 категории:</p>
<ul>
<li>хранилища ключ-значение;</li>
<li>графовые хранилища;</li>
<li>колоночные хранилища;</li>
<li>документные хранилища.</li>
</ul>
<p>Операции объединения обычно не поддерживаются в нереляционных базах данных.</p>
<p>Для большинства разработчиков реляционная база данных - лучший выбор, потому что они существуют уже более 40 лет и исторически хорошо зарекомендовали себя.</p>
<p>Однако если реляционные базы данных
не подходят для ваших конкретных случаев использования, важно исследовать, выходя за рамки реляционных
базы данных. Нереляционные базы данных могут быть правильным выбором, если:</p>
<ul>
<li>вашему приложению нужны очень низкие задержки;</li>
<li>ваши данные не структурированные или у вас нет реляционных данных;</li>
<li>вам достаточно сериализовывать и десериализовывать данные (JSON, XML, YAML и др.);</li>
<li>вам нужно сохранять большие объёмы данных.</li>
</ul>
<h2 id="вертикальное-и-горизонтальное-масштабирование">Вертикальное и горизонтальное масштабирование</h2>
<p>Вертикальное масштабирование, именуемое также &ldquo;масштабирование вверх&rdquo; означает процесс добавления мощности (ядра, память) на ваши сервера.
Горизонтальное масштабирование, именуемое также &ldquo;масштабирование в ширину&rdquo; позволяет масштабироваться путём добавления большего количества серверов в пул ресурсов.
Когда траффик небольшой, вертикальное масштабирование - отличный вариант и простота вертикального масштабирования - это его главное преимущество.
К сожалению, оно имеет серьёзные ограничения:
* вертикальное масштабирование имеет жёсткий лимит. Невозможно бесконечно добавлять CPU и память к одному серверу;
* Вертикальное масштабирование не имеет аварийного переключения и резервирования. Если один сервер упал, вебсайт / приложение упадёт целиком.</p>
<p>Горизонтальное масштабирование более желаемо для больших систем из-за ограничений вертикального.
В предыдущей варианте пользователи соединялись с веб-сервером напрямую. Пользователи не смогут достучаться до вебсайта, если вебсервер будет оффлайн.</p>
<p>В другом сценарии, если много пользователей придут на вебсервер одновременно и достигнут лимита нагрузки, пользователи обычно получают ответ медленнее или не получают его совсем. Балансировщик нагрузки - лучшее решение для этой проблемы.</p>
<h2 id="балансировщик-нагрузки">Балансировщик нагрузки</h2>
<p>Балансировщик нагрузки распределяет входящий трафик по веб серверам, которые определены в балансируемом множестве.
Рисунок 1-4 показывает, как работает балансировщик нагрузки.</p>
<p><img src="https://vitalyzhakov.github.io/images/scaling-from-zero-to-millions-of-users/figure1-4.png" alt="Рисунок 1-4" title="Рисунок 1-4" /></p>
<p>Как показано на рисунке 1-4, пользователи соединяются с публичным IP балансировщика нагрузки напрямую.
В этой конфигурации веб сервера больше недоступны напрямую с клиентов.
В целях безопасности для коммуникации между серверами используются приватные IP.
Приватный IP - IP адрес, доступный только между серверами одной сети; таким образом, они недоступны через интернет.
Балансировщик общается с веб сервером через приватные IP.</p>
<p>На рисунке 1-4, после того как мы добавили балансировщик и второй веб сервер, мы успешно решили вопрос аварийного переключения и улучшили доступность web зоны:</p>
<ul>
<li>Если сервер 1 падает, весь траффик будет переадресован на сервере 2. Это снижает вероятность недоступности вебсайта. Мы также добавим второй сервер в пул
для балансировки нагрузки</li>
<li>если траффик вебсайта быстро расчёт, 2 сервер может быть недостаточно, чтобы обработать весь траффик, балансировщик нагрузки может помочь решить эту проблему изящно. Вам достаточно добавить больше серверов в пул и балансировщик начнёт отправлять запросы к ним.</li>
</ul>
<p>Сейчас web зона выглядит хорошо, что насчёт зоны данных? Текущий дизайн имеет одну базу, поэтому он не поддерживает аварийное переключение и избыточность.
Репликация баз данных - общепринятая техника для решения этой проблемы. Давайте посмотрим, как она работает.</p>
<h2 id="репликация-баз-данных">Репликация баз данных</h2>
<p>Цитата из википедии: &ldquo;репликация баз данных может быть использована в системах управления базами данных, обычно с отношением
primary/replica между источником (primary) и копиями (replica).</p>
<p>Primary база данных обычно поддерживает только операции записи. Replica берёт копию данных с primary и обслуживает запросы на чтение.
Все операции изменения данных, такие как insert, delete или update должны быть отправлены в primary базу.
Большинству приложений требуется намного больше операций чтения, чем записи.
Поэтому количество реплик в системе обычно больше, чем количество primary баз данных.
Рисунок 1-5 показывает primary базу данных с несколькими репликами.</p>
<p><img src="https://vitalyzhakov.github.io/images/scaling-from-zero-to-millions-of-users/figure1-5.png" alt="Рисунок 1-5" title="Рисунок 1-5" /></p>
<p>Преимущества репликации баз данных:</p>
<ul>
<li><p>Улучшение производительности: при использовании модели master-slave все операции записи и обновления происходят в master-узлах,
тогда как операции чтения распределены между репликами. Такая модель увеличивает производительность, поскольку
позволяет обрабатывать больше запросов одновременно.</p></li>
<li><p>Надёжность: Если какой-либо из серверов баз данных выходит из строя из-за катастрофы (например, пожара или землетрясения),
данные остаются доступными. Можно не волноваться о сохранности данных, поскольку они реплицированы в несколько мест.</p></li>
<li><p>Высокая доступность: При репликации данных, веб-сайт остаётся работающим, даже если база оффлайн, поскольку
есть возможность получить доступ к данным на другом сервере баз данных.</p></li>
</ul>
<p>В предыдущем разделе обсуждался вопрос, как балансировщик нагрузки помогает увеличить доступность системы.
Зададимся похожим вопросом и здесь: Что, если одна из баз данных отключится?
Архитектура, представленная на рисунке 1-5, может дать ответ:</p>
<ul>
<li><p>Если имеется только одна база-реплика, и она отключилась, операции чтения будут временно перенаправляться в master-базу.
Как только проблема будет найдена, новая реплика заменит неработающую. Если же имеется несколько баз-реплик,
запросы чтения будут перенаправлены на работающую копию, пока новая реплика не заменит неработающую.</p></li>
<li><p>Если вышел из строя master-сервер, одна из реплик будет повышена до нового master-сервера.
Все операции будут временно выполняться на этой базе, и сразу будет создана новая реплика взамен повышенной.
В боевой среде задача повышения базы до master-сервера осложняется тем, что в репликах могут быть устаревшие данные.
Потерянную информацию можно восстановить запуском скриптов восстановления данных. Также могут помочь такие методы репликации,
как мульти-master и круговая репликация, но они более сложные и останутся за границами этого обзора.</p></li>
</ul>
<p>Рисунок 1-6 показывает дизайн системы после добавления в неё балансировщика нагрузки и репликации баз данных.</p>
<p><img src="https://vitalyzhakov.github.io/images/scaling-from-zero-to-millions-of-users/figure1-6.png" alt="Рисунок 1-6" title="Рисунок 1-6" /></p>
<p>Давайте взглянем на дизайн</p>
<ul>
<li>пользователь получает IP адрес балансировщика нагрузки из DNS;</li>
<li>пользователь соединяется с балансировщиком нагрузки через этот IP адрес;</li>
<li>Http запрос направляется на сервер 1 или сервер 2;</li>
<li>веб сервер читает данные из базы slave;</li>
<li>веб-сервер направляет операции изменения данных на базу master. Это включает в себя операции insert, update и delete;</li>
</ul>
<p>Сейчас у вас есть уверенное понимание зон веб и данных. Настало время улучшить время загрузки / ответов.
Это может быть сделано добавлением кеширующего слоя или доставкой статического контента (JavaScript/CSS/картинки/видел файлы)
в сеть доставки контента (CDN).</p>
<h2 id="кеш">Кеш</h2>
<p>Кеш - временное хранилище результатов дорогих операций или часто запрашиваемых операций, таким образом что последовательные вызовы обслуживаются быстрее.
Как показано на рисунке 1-6, каждый раз, когда загружается новая веб страница, один или больше вызовов баз данных исполняется для получения данных.</p>
<p>На производительность приложения сильно влияет многократный вызов базы данных.
Кеш может смягчить эту проблему.</p>
<h2 id="зона-кеширования">Зона кеширования</h2>
<p>Зона кеширования - временный слой хранения данных, более быстрый, чем база данных.
Преимущества зоны кеширования включают лучшую производительность системы, возможность снизить нагрузку на базу данных,
возможность масштабировать кеш зону независимо. Рисунок 1-7 показывает возможную конфигурацию сервера кеширования:</p>
<p><img src="https://vitalyzhakov.github.io/images/scaling-from-zero-to-millions-of-users/figure1-7.png" alt="Рисунок 1-7" title="Рисунок 1-7" /></p>
<p>После получения запроса веб сервер сначала проверяет, доступен ли в кеше ответ. Если да, отправляет ответ клиенту.
Если нет, отправляет запрос в базу данных, сохраняет ответ в кеше и отправляет клиенту.
Эта стратегия кеширования называется &ldquo;сквозной кеш&rdquo;.
Другие стратегии кеширования зависят от типов данных, размера и моделей доступа.</p>
<p>Взаимодействие с серверами кеширования довольно простое, так как большинство кеширующих серверов предоставляет API для распространённых языков программирования.
Следующий кусок кода показывает типичное API Memcached:</p>
<pre><code>SECONDS = 1
cache.set ('myKey', 'myValue', 3600*SECONDS)
cache.get ('myKey')
</code></pre>
<p>Рекомендации по использованию кеша</p>
<ul>
<li>решите, когда использовать кеш. Общая рекомендация - использовать кеш, когда данные часто читаются, но меняются редко.
Поскольку кэшированные данные хранятся в энергозависимой памяти, кеш сервер - не идеальное место для хранения данных.
Например, при перезапуске кеш-сервера все данные в памяти будут потеряны.
Таким образом, важные данные должны быть сохранены в постоянном хранилище.</li>
<li>политика устаревания. Хорошая практика реализовать политику устаревания. Когда кешированные данные устаревают, они удаляются из кеша.
Когда нет политики устаревания, данные будут храниться в кеше постоянно.
Желательно не делать срок истечения слишком коротким, это может привести к частым обращениям к базе данных.
Между тем, желательно не увеличивать срок жизни слишком надолго, это может привести к тому, что данные устареют.</li>
<li>Согласованность: это включает поддержание хранилища данных и кеша в синхронном состоянии.
Несогласованность может случаться из-за того, что операции по изменению данных в хранилище и кеше не находятся в одной транзакции.
Когда речь идёт о масштабировании на несколько регионов, поддержка согласованности между хранилищем и кешами - настоящий челлендж.
Для подробностей можно ознакомиться с “Scaling Memcache at Facebook”.</li>
<li>Политика вытеснения: когда кеш заполнен, любые запросы на добавление элементов в кеш могут вызвать удаление существующих элементов.
Это называется вытеснением кеша.
Least-recently-used (LRU) - в первую очередь, вытесняется неиспользованный дольше всех. Другие политики вытеснения,
такие как Least Frequently Used (LFU) или First in First Out (FIFO) могут быть использованы для соответствия другим требованиям.</li>
</ul>
<h2 id="сеть-доставки-контента-cdn">Сеть доставки контента (CDN)</h2>
<p>CDN - сеть географически распределённых серверов, используемая чтобы доставить статический контент.
Сервера CDN кешируют статический контент, такой как изображения, видео, CSS, javascript файлы и т.д.
Кеширование динамического контента - относительно новый концепт и остаётся за рамками этой статьи.
Оно включает в себя кеширование HTML страниц, которое основано на пути запроса, переменных запроса, куках и заголовках запроса.</p>
<p>Пока фокусируемся на кешировании статического содержимого.</p>
<p>На рисунке показано, как устроен CDN устроен сверху: когда пользователь посещает вебсайт, CDN сервера, ближайшие к пользователю, доставляют статический контент.
Интуитивно понятно, чем дальше пользователи от серверов CDN, тем дольше грузится сайт.
Например, если CDN сервера в Москве, пользовали из Санкт-Петербурга получат контент быстрее, чем из Владивостока.
Рисунок 1-9 - хороший пример, который показывает как CDN помогает улучшить время загрузки.</p>
<p><img src="https://vitalyzhakov.github.io/images/scaling-from-zero-to-millions-of-users/figure1-9.png" alt="Рисунок 1-9" title="Рисунок 1-9" /></p>
<p>На рисунке 1-10 показан процесс работы с CDN</p>
<p><img src="https://vitalyzhakov.github.io/images/scaling-from-zero-to-millions-of-users/figure1-10.png" alt="Рисунок 1-10" title="Рисунок 1-10" /></p>
<ol>
<li>Пользователь A пытается получить image.png используя URL. URL предоставлен провайдером CDN.
Следующие 2 URL к изображениям - примеры для демонстрации, как выглядят на Amazon или Akami CND:</li>
</ol>
<ul>
<li><a href="https://mysite.cloudfront.net/logo.jpg" target="_blank">https://mysite.cloudfront.net/logo.jpg</a></li>
<li><a href="https://mysite.akamai.com/image-manager/img/logo.jpg" target="_blank">https://mysite.akamai.com/image-manager/img/logo.jpg</a></li>
</ul>
<ol>
<li>Если на CDN сервере нет image.png в кеше, CDN сервер запрашивает файл с источника, который должен быть веб сервером или онлайн хранилищем, похожим на Amazon S3.</li>
<li>Источник возвращает image.png CDN серверу, включая опциональные HTTP заголовок время жизни (TTL), который описывает, насколько долго кешируется изображение.</li>
<li>CDN кеширует изображение и возвращает его пользователю A. Изображение остаётся кешированным в СDN, пока не истечёт TTL.</li>
<li>Пользователь B отправляет запрос на то же изображение.</li>
<li>Изображение возвращается из кеша, пока TTL не истечёт.</li>
</ol>
<p>Рекомендации по использованию CDN.</p>
<ul>
<li>Стоимость: CDN запускаются внешними провайдерами и вы будете платить за доставку входящего и исходящего траффика в CDN.
Кеширование редко используемого контента не даёт значительных преимуществ, поэтому убедитесь, что не используете CDN для таких ресурсов.</li>
<li>установите подходящее время устаревания кеша: для контента, чувствительного ко времени, установка времени устаревания важна.
Время устаревания должно быть ни слишком маленьким, ни слишком большим.
Если оно будет слишком большим, контент больше не сможет быть свежим.
Если будет слишком маленьким, может вызывать постоянный перезапрос содержимого с источника.</li>
<li>запасной план к CDN: вы должны подумать, как ваш сайт / приложение справляется с отказом CDN.
Если случится временное отключение CDN, клиенты должны определить проблему и запросить ресурсы с источника.</li>
<li>Инвалидация файлов: вы можете удалить файл из CND до того как он устарее путём одной из следующих операций:
через API
используя версионирование объектов, например, добавляя в url номер версии image.png?v=2</li>
</ul>
<p>Рисунок 1-11 показывает дизайн после добавления CDN и кеша
<img src="https://vitalyzhakov.github.io/images/scaling-from-zero-to-millions-of-users/figure1-11.png" alt="Рисунок 1-11" title="Рисунок 1-11" /></p>
<ol>
<li>Статические ресурсы (JS, CSS, images, др.,) больше не доставляются веб-сервером. Они доставляются с CDN для лучшей производительности.</li>
<li>Загрузка базы данных облегчается за счет кеширования данных.</li>
</ol>
<h2 id="сервисы-без-сохранения-состояния-stateless">Сервисы без сохранения состояния (stateless)</h2>
<p>Сейчас настало время рассмотреть масштабирование веба горизонтально.
Для этого нам нужно переместить состояние (хранение пользовательских данных) из приложения.
Хорошая практика - это сохранение клиентских сессий во внешнем постоянном хранилище, например, реляционная база данных или NoSQL.
Каждое приложение в кластере должно иметь доступ к этим данным.</p>
<h2 id="stateful-архитектура">Stateful архитектура</h2>
<p>Statefull север и stateless имеют несколько ключевых различий.
Statefull server запоминает клиентские данные (state, состояние) от одного запроса к другому.
Stateless сервер не хранит информацию о состоянии.</p>
<p>Рисунок 1-12 показывает пример архитектуры без сохранения состояния.</p>
<p><img src="https://vitalyzhakov.github.io/images/scaling-from-zero-to-millions-of-users/figure1-12.png" alt="Рисунок 1-12" title="Рисунок 1-12" /></p>
<p>На рисунке 1-12 сессионные данные и изображение профиля пользователя A сохранены в сервере 1.
Для аутентификации пользователя A HTTP запросы должны быть отправлены на сервер 1. Если запрос будет отправлен на другой сервер,
например сервер 2, аутентификация не сработает, так как сервер 2 не содержит сессионные данные пользователя A.
Аналогично все HTTP запросы от пользователя B должны отправляться на сервер 2. Все запросы от пользователя C должны отправляться на сервер 3.</p>
<p>Проблема в том, что каждый запрос с одного клиента должен быть отправлен на его сервер.
Технически это может быть сделано с sticky sessions (&ldquo;липкими&rdquo; сессиями) в большинстве балансеров.
Тем не менее, это добавляет лишнюю нагрузку. Добавление или удаление серверов становится труднее.
Также тру</p>
<p>The issue is that every request from the same client must be routed to the same server. This
can be done with sticky sessions in most load balancers [10]; however, this adds the
overhead. Adding or removing servers is much more difficult with this approach. Также сложно справиться с отказами серверов.</p>
<h2 id="stateless-архитектура">Stateless архитектура</h2>
<p>Рисунок 1-13 показывает stateless архитектуру.</p>
<p><img src="https://vitalyzhakov.github.io/images/scaling-from-zero-to-millions-of-users/figure1-13.png" alt="Рисунок 1-13" title="Рисунок 1-13" /></p>
<p>В stateless архитектуре HTTP запросы от пользователей могут быть отправлены к любому веб-серверу, который получает состояние из общего хранилища данных.
Состояние сохраняется в общем пространстве и содержится отдельно от веб серверов.
Stateless система проще, более надёжна и масштабируема.</p>
<p>Рисунок 1-14 показывает обновлённый дизайн с приложениями без сохранения состояния.</p>
<p><img src="https://vitalyzhakov.github.io/images/scaling-from-zero-to-millions-of-users/figure1-14.png" alt="Рисунок 1-14" title="Рисунок 1-14" /></p>
<p>На рисунке 1-14 мы перемещаем сессионные данные из веб серверов и сохраняем их в постоянном хранилище.
Общие хранилища данных могут быть реляционными базами, Memcached / Redis, NoSQL и другими.
Хранилище NoSQL выбирается для упрощения масштабирования.
Автомасштабирование означает добавление или удаление серверов, основанное на показателях нагрузки.
После того как состояние перемещено из зоны приложений, автомасштабирование приложений достигается легко путём добавления или удаления серверов, основанное на загрузке по траффику.</p>
<p>Ваш вебсайт растёт быстро и привлекает значительное количество международных пользователей.
Чтобы улучшить доступность и предоставить лучший пользовательский опыт по всем географическим районам,
добавим поддержку нескольких центров обработки данных.</p>
<h2 id="data-centers">Data centers</h2>
<p>Рисунок 1-15 показывает пример настройки с двумя датацентрами. В нормальном режиме пользователи маршрутизированы средствами geoDNS
к ближайшему датацентру, с делением траффика x% в US-East и (100 - x)% в US-West. geoDNS - это DNS сервис, который разрешает доменные имена
в их IP адреса в соответствии с их местоположением.</p>
<p><img src="https://vitalyzhakov.github.io/images/scaling-from-zero-to-millions-of-users/figure1-15.png" alt="Рисунок 1-15" title="Рисунок 1-15" /></p>
<p>В случае значительного отказа датацентра, мы перенаправим весь траффик к живому датацентру.
На рисунке 1-16 датацентра 2 (US-West) оффлайн и 100% траффика маршрутизировано к здоровому дата центру 1 (US-East).</p>
<p><img src="https://vitalyzhakov.github.io/images/scaling-from-zero-to-millions-of-users/figure1-16.png" alt="Рисунок 1-16" title="Рисунок 1-16" /></p>
<p>Несколько технических вызовов должны быть решены для достижения развёртывания на несколько датацентров:</p>
<ul>
<li><p>Переадресация траффика: для перенаправления траффика в нужный датацентр нужны эффективные инструменты. GeoDNS может быть использован, но мы не можем оперативно управлять DNS на клиенте. Если у вас есть возможность, используйте BGP для анонса нескольких IP адресов.</p></li>
<li><p>Синхронизация данных: пользователи из разных регионов могут использовать разные локальные базы данных или кеши.
В случаях аварийного переключения траффик может быть перенаправлен в датацентр, в котором данные недоступны.
Общая стратегия - это репликация данных на несколько датацентров.</p></li>
<li><p>Тесты и развёртывание: с конфигурацией в несколько датацентров важно проверять ваше приложение с разных локаций.
Инструменты автоматического развёртывания жизненно необходимы для поддержки сервисов в консистентном состоянии во всех датацентрах.</p></li>
</ul>
<p>Для дальнейшего масштабирования системы нам нужно расцепить разные компоненты, чтобы масштабировать их независимо.
Очередь сообщений - ключевая стратегия, применяемая многими мировыми распределёнными системами для решения этих проблем.</p>
</description>
</item>
<item>
<title>Системный дизайн, простой уровень</title>
<link>https://vitalyzhakov.github.io/post/system-design-easy/</link>
<pubDate>Fri, 01 Oct 2021 00:10:06 +0500</pubDate>
<guid>https://vitalyzhakov.github.io/post/system-design-easy/</guid>
<description>
<p>Рассмотрим в качестве простого примера сервис, доставляющий статическую веб страницу.</p>
<p>Факторами оценки могут быть:</p>
<ul>
<li>SL доступности сервиса;</li>
<li>Время ответа на запрос клиента;</li>
</ul>
<p>В самом простом варианте клиенты размещены в Москве.</p>
<p>Ограничения</p>
<ul>
<li>SL виртуальной машины (SLвм) = 99.95</li>
</ul>
<p>Допущения</p>
<ul>
<li>мы в состоянии сделать сервис доставки страниц идеальным;</li>
<li>нагрузкой на сервис пока пренебрежём.</li>
</ul>
<p>Пусть сервис размещён в PRM.</p>
<p>Считаем факторы оценки</p>
<p>SL сервиса ~ SL nginx * SL вм, что примерно равно SL вм = 99.95</p>
<p>Время ответа = время генерации страницы (~1 msec) + время доставки до Москвы (~20 msec).
Итого 21 msec.</p>
<h2 id="география">География</h2>
<p>Усложним задачу.</p>
<p>30% клиентов в Москве,
40% клиентов в Перми,
30% клиентов в Омске.</p>
<p>Тогда время доставки будет выглядеть как взвешенная сумма</p>
<p>Время доставки = 0,3 * 20 * 0,4 * 5 * 0,3 * 30 = 17</p>
</description>
</item>
<item>
<title>Схема CI/CD для веб-разработчиков на основе open-source инструментов</title>
<link>https://vitalyzhakov.github.io/post/ci-cd/</link>
<pubDate>Mon, 22 Mar 2021 00:10:06 +0500</pubDate>
<guid>https://vitalyzhakov.github.io/post/ci-cd/</guid>
<description>
<div style="position: relative; padding-bottom: 56.25%; padding-top: 30px; height: 0; overflow: hidden;">
<iframe src="//www.youtube.com/embed/ODo3QieLvA8" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" allowfullscreen frameborder="0" title="YouTube Video"></iframe>
</div>
<p>Содержание</p>
<ol>
<li>Общая схема</li>
<li>Сервер dev с песочницами</li>
<li>Детализация разработки (создание ветки, непрерывный деплой и тестирование, создание merge на бота, апрув в master)</li>
<li>Прод с master-master репликацией, graylog, sentry, prometheus.</li>
</ol>
<p>Наверное, все видели стандартную восьмёрку, как выглядит CI/CD:</p>
<p><img src="https://vitalyzhakov.github.io/images/ci-cd/8.png" alt="Бесконечность CI/CD" title="Бесконечность CI/CD" /></p>
<p>Но обычно никто не рассказывает, как именно реализовать этот процесс.</p>
<p>Сегодня детализирую этот на примере разработки веб-сервисов.</p>
<p><img src="https://vitalyzhakov.github.io/images/ci-cd/common-scheme.png" alt="Общая схема" title="Общая схема" /></p>
<p>На схеме мы видим, как параллельно сдаются задачи (в виде коммитов) в master-ветку.</p>
<p>Далее принимается решение о релизе, он фиксируется, отправляется в <em>registry</em> (магазин приложений),
с <em>registry</em> готовое приложение попадает в прод.
Некоторое время за ним следим (в sentry) на наличие новых ошибок.
При возникновении потребности в откате - возвращаем на предыдущий тег из gitlab environments.</p>
<p>Далее поэтапно рассмотрим реализацию каждого шага.</p>
<p>Сердцем всей истории у нас является Gitlab и Gitab-CI.</p>
<p>Git flow показался нам слишком сложным для поддержки из одного репозитория одного проекта на продакшне.
<a href="https://guides.github.com/introduction/flow/index.html" target="_blank">Github flow</a> до слияния в master релизит изменения на пользователей (прод).
Параллельность работы нескольких разработчиков остаётся не раскрытой.</p>
<p>История с production/preproduction веткой в <a href="https://docs.gitlab.com/ee/topics/gitlab_flow.html" target="_blank">Gitlab flow</a> также кажется нам лишней, поэтому предлагаем вам следующую схему:</p>
<p><img src="https://vitalyzhakov.github.io/images/ci-cd/tank.png" alt="Flow" title="Flow" /></p>
<p>Схема поддерживает наличие нескольких разработчиков, работающих над проектом.
На схеме видно, что разработка фичей идёт в неблокирующем режиме.
На самом деле одного gitlab не достаточно, чтобы это работало. Грамотная архитектура проекта (кодовой базы) позволяет избежать конфликтов при разработке, что благоприятно влияет на TTM фичи. Также важно, чтобы сервис соответствовал архитектурным принципам (например, <a href="https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B8%D0%BD%D1%86%D0%B8%D0%BF_%D0%B5%D0%B4%D0%B8%D0%BD%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D0%BE%D0%B9_%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D0%BE%D1%81%D1%82%D0%B8" target="_blank">SRP</a>). Ответственность должна быть полностью инкапсулирована в сервис.</p>
<p>Из факта, что большинство ошибок возникают из-за внесения изменений, следует, что максимальное внимание нужно уделить именно изменению, а именно:</p>
<ul>
<li>ветка для разработки фичи максимально соответствовала проду (подход управления всем через код вам в помощь);</li>
<li>тестировать изменение;</li>
<li>проводить статический анализ и другие автоматические проверки максимально близко к изменению;</li>
<li>проводить code review;</li>
<li>по возможности наиболее строго относиться к ошибкам компилятора, интерпретаторов SQL-запросов и т.д.</li>
</ul>
<p>Если пускать в master нестабильные изменения, разработка параллельных фич может забрать их себе и при тестировании посчитать багом, привнесённым своей фичей. Всё это может сказаться отрицательно на TTM.
Поэтому важно <code>держать master стабильным</code>.</p>
<p>Процесс <code>code review</code> является затратным как с точки зрения фактических затрат времени, так и временного промежутка, который отдаляет фичу от master.
В большом коллективе важно, чтобы правила проведения review были приняты всей командой и были объективными.
Технические мероприятие для снижения длительности:</p>
<ul>
<li>автоматизация проведения проверок CI вместо человека;</li>
<li>автоматическое распределение review между проверяющими;</li>
<li>автоматическая очередь мерждей после прохождения всех стадий контроля.</li>
</ul>
<h2 id="dev-сервер-с-песочницами-под-каждую-ветку">Dev-сервер с песочницами под каждую ветку</h2>
<h3 id="в-начале-были-виртуалки-на-сервис">В начале были виртуалки на сервис</h3>
<ul>
<li>отдел, разрабатывающий код (Dev);</li>
<li>отдел, поддерживающий прод (Ops);</li>
<li>Ops предоставляли Dev виртуалку - копию прода. Но как только 2 разработчика начинали дорабатывать один сервис
через SFTP, они перезаписывали код друг друга;</li>
<li>на проде возможно обновить только код, системный софт - невозможно (из-за трудностей с тестированием и параллельного внесения изменений).</li>
</ul>
<h3 id="затем-виртуалки-с-пушем-через-svn">Затем виртуалки с пушем через SVN</h3>
<ul>
<li>они всегда были заняты;</li>
<li>они всегда были сломаны;</li>
<li>часто не соответствовали продакшну;</li>
<li>новая виртуалка ~ 2 недели ожидания.</li>
<li>копирование файлов вручную на прод приводило к багам;</li>
</ul>
<h3 id="теперь-у-нас-dev-сервер-с-песочницами-под-каждую-ветку">Теперь у нас Dev-сервер с песочницами под каждую ветку</h3>
<p>Основная фишка - предоставление песочницы под задачу со всеми нужными контейнерами для разработки быстро (в идеале в течение 1 минуты после запроса).
Сервис в песочнице должен максимально повторять продакшн.</p>
<p>Понятно, что железо стоит денег, поэтому старые песочницы ночью удаляются в автоматическом режиме.
Для увеличения порядка ветки, которые не прошли проверку временем, по истечению таймаута нужно удалять.
Иначе многие будут хотеть воскресить старую разработку, которая не актуальна не только по качеству кода, но и догнать master будет стоить значительного времени.</p>
<p>Без этой операции при большом количестве параллельных задач можно погрязнуть в хаосе.</p>
<p><img src="https://vitalyzhakov.github.io/images/ci-cd/devScheme-docker.png" alt="Схема работы dev-сервера" title="Схема работы dev-сервера" />
<img src="https://vitalyzhakov.github.io/images/ci-cd/gitlabRunner.png" alt="Схема работы dev-сервера" title="Схема работы dev-сервера" /></p>
<p>На этой картинке показано, что пользователи сервиса заходят через nginx на конкретную песочницу,
которая регистрируется в nginx во время создания с помощью consul и consul-template.</p>
<p>Задания исполняются с помощью gitlab-ci</p>
<p>Первое задание - сборка образа</p>
<pre><code class="language-bash">Build Dev Image:
stage: build_dev_image
- docker build --tag ${IMAGE_ADDRESS} .
</code></pre>
<p>Второе задание - запуск сервиса</p>
<pre><code class="language-bash">Deploy dev:
stage: deploy_dev
script:
# Для веб-сервисов нужен FQDN
- export FQDN=task-123.project.team.dev
- docker-compose up -d
- consul-template -config consul.conf
</code></pre>
<p>Пример содержимого файла docker-compose</p>
<pre><code class="language-yaml">version: '2.3'
services:
web:
image: ${IMAGE_ADDRESS}
container_name: ${COMPOSE_PROJECT_NAME}
depends_on:
- mongo
network_mode: &quot;bridge&quot;
environment:
SERVICE_NAME: ${COMPOSE_PROJECT_NAME}
</code></pre>
<h2 id="4-прод-с-master-master-репликацией-graylog-sentry-prometheus">4. Прод с master-master репликацией, graylog, sentry, prometheus</h2>
<p><img src="https://vitalyzhakov.github.io/images/ci-cd/prod.png" alt="Prod" title="Prod" /></p>
<p>На продакшне у нас развёрнуты kubernetes-кластера в двух географически распределённых ЦОДах.
Kubernetes сейчас стандарт де-факто. DB, файлы реплицируются в обе стороны.
Поддерживает IaaC (Infrastructure as a Code), что позволяет прямо в ветке внести изменения,
которые с релизом уйдут на прод. Сервисы ограничены лимитами k8s, что исключает негативное воздействие одного сервиса на другой.</p>
<p>Географическое active-active распределение помогает нам снизить сетевые задержки до клиентов и
позволяет иметь горячий бекап, куда можно переключиться при проведении работ или аварии на одном из кластеров.</p>
<p>С другой стороны, это достаточно дорого обходится нам (приходится писать софт соответствующим образом).</p>
<p>Не всем это нужно, вполне можно обойтись одним кластером.</p>
<p>Контейнеры созданы по методологии 12-факторного приложения, один из пунктов которого гласит - пишите логи в стандартный вывод.
Затем они подхватываются и отправляются в graylog (можно с таким же успехом взять ELK), где можно посмотреть логи.</p>
<p>Ошибки пишутся в sentry, при релизах просматриваются на предмет появления новых.
Если в случае с graylog при ограниченном бюджете на железо ещё можно подумать о его необходимости,
то для sentry не нужно много ресурсов, если вы будете своевременно устранять ошибки, которые записали.
Это скажется благоприятно как на качестве приложения, так и на затратах железа на sentry.
Ещё раз повторюсь, не так важно писать ошибки в sentry, как важно их разбирать и устранять.</p>
<p>Метрики приложений отправляются в prometheus, который в состоянии отреагировать на стандартные проблемы с сервисами. Резерв при пороговых триггерах - простой способ узнать о надвигающемся инциденте заранее и предотвратить его.</p>
</description>
</item>
<item>
<title>Исключения и библиотеки</title>
<link>https://vitalyzhakov.github.io/post/exception-in-libs/</link>
<pubDate>Thu, 11 Feb 2021 00:00:00 +0000</pubDate>
<guid>https://vitalyzhakov.github.io/post/exception-in-libs/</guid>
<description><p>При создании библиотек часто возникает вопрос,
прокидывать исключение наружу или ловить его в библиотеке?</p>
<p>Ответ прост)</p>
<p>Если библиотека знает, как поступить с исключением, стоит обработать его в библиотеке.</p>
<p>Если библиотека не знает, как поступить с исключением, стоит отдать обработку на уровень выше (в другую библиотеку или приложение).</p>
</description>
</item>
<item>
<title>Исправляем ошибку fxp/composer-asset-plugin incompatible после обновления до composer 2</title>
<link>https://vitalyzhakov.github.io/post/composer-2-fxp-asset-plugin/</link>
<pubDate>Sat, 02 Jan 2021 00:10:06 +0500</pubDate>
<guid>https://vitalyzhakov.github.io/post/composer-2-fxp-asset-plugin/</guid>
<description>
<p>После обновления менеджера пакетов PHP composer до версии 2 появляется ошибка:</p>
<pre><code class="language-bash"> [RuntimeException]
No composer.json present in the current directory, this may be the cause of the following exception.
[InvalidArgumentException]
Package fxp/composer-asset-plugin at version has a PHP requirement incompatible with your PHP version, PHP extensions and Composer version
</code></pre>
<p>Происходит из-за несовместимости fxp/composer-asset-plugin и composer версии 2.</p>
<h2 id="решение">Решение</h2>
<p>Удаляем старый плагин</p>
<pre><code class="language-bash">composer global remove fxp/composer-asset-plugin
</code></pre>
<p>Если появляется ошибка, связанная с папками bower или npm,
добавляем в config.php приложения алиасы до соответствующих папок</p>
<p>Пример ошибки</p>
<pre><code class="language-bash">mito/yii2-sentry[1.0.0, ..., 1.0.2] require bower-asset/raven-js
</code></pre>
<pre><code class="language-php">'modules' =&gt; [ ... ],
'aliases' =&gt; [
'@bower' =&gt; '@vendor/bower-asset',
'@npm' =&gt; '@vendor/npm-asset',
],
</code></pre>
<p>В composer.json добавляем</p>
<pre><code class="language-json">&quot;repositories&quot;: [
{
&quot;type&quot;: &quot;composer&quot;,
&quot;url&quot;: &quot;https://asset-packagist.org&quot;
}
]
</code></pre>
<p>Удаляем остатки fxp-asset (если есть):</p>
<pre><code class="language-json">&quot;config&quot;: {
&quot;fxp-asset&quot;: {
&quot;installer-paths&quot;: {
&quot;npm-asset-library&quot;: &quot;vendor/npm&quot;,
&quot;bower-asset-library&quot;: &quot;vendor/bower&quot;
}
}
}
</code></pre>
</description>
</item>
<item>
<title>Оптимизация обращений к базе данных</title>
<link>https://vitalyzhakov.github.io/post/db-optimization/</link>
<pubDate>Fri, 25 Dec 2020 00:10:06 +0500</pubDate>
<guid>https://vitalyzhakov.github.io/post/db-optimization/</guid>
<description>
<p>При разборе времени генерации ответа от сервера иногда узким местом может быть обращение к СУБД.
Оптимизацию времени доступа к СУБД можно разделить на несколько видов:</p>
<h2 id="использование-параметризованных-запросов">Использование параметризованных запросов</h2>
<p>Некоторые программисты совершают ошибку, вписывая параметры (например, идентификаторы) запроса сразу в запрос.
Первым шагом СУБД пытается проанализировать запрос. Если параметры приходят в запросе, в кеш запрос не складывается.
Но если параметры прикладывать к запросу, структура запроса не меняется.
Значит, при следующем запросе, есть вероятность нахождения проанализированной структуры в кеше, что позволит ускорить выполнение.</p>
<h2 id="размещение-приложения-ближе-к-субд">Размещение приложения &ldquo;ближе&rdquo; к СУБД</h2>
<p>Улучшение сетевой связности может положительно сказаться на производительности приложения в целом,
при этом вам не обязательно вносить изменения в код.</p>
<h2 id="использование-постоянных-соединений">Использование постоянных соединений</h2>
<p>Обычно поднятие соединений занимает значительное количество ресурсов СУБД.
Для исполнения одних и тех же запросов не обязательно поднимать каждый раз новое соединение, можно переиспользовать.
Например, в php это можно сделать одним атрибутом соединения <a href="https://www.php.net/manual/ru/features.persistent-connections.php" target="_blank">https://www.php.net/manual/ru/features.persistent-connections.php</a></p>
<h2 id="написание-хранимых-процедур">Написание хранимых процедур</h2>
<p>Порог входа для написания хранимых процедур достаточно высок.
Но если проект высоко нагружен и все остальные способы оптимизации вы уже перебрали, стоит попробовать.</p>
<p>Несколько запросов, зависящих друг от друга, можно объединить таким способом и
снизить затраты на сетевой обмен между сервером приложений и сервером баз данных.</p>
<h2 id="оптимизация-запросов-и-расстановка-индексов">Оптимизация запросов и расстановка индексов</h2>
<p>Здесь подразумевается использование инструментов <code>MySQL SLOW LOG</code>, <code>explain</code>.</p>
<h3 id="убираем-лишние-таблицы-данные-из-запросов">Убираем лишние таблицы / данные из запросов</h3>
<p>Стоит свежим взглядом осмотреть запросы не предмет неиспользуемых таблиц/полей в запросах.
Это может дать значительный эффект в проект, в который частно вносятся изменения, но при этом рефакторинга
&ldquo;на свежую голову&rdquo; давно не было.</p>
<h3 id="расстановка-индексов">Расстановка индексов</h3>
<p>Индексы добавляют размера базе данных, но позволяют ускорить запросы.
Стоит поискать баланс с помощью <code>EXPLAIN</code>.</p>
<h2 id="денормализация-и-объединение-запросов">Денормализация и объединение запросов</h2>
<p>Этот вариант требует значительных затрат и модификации.
В большинстве систем данные нормализованы для поддержания целостности.
Но с точки зрения оптимизации времени выборка из одной таблицы обходится дешевле, чем связка нескольких и выборка из связки.</p>
<h2 id="поиск-аномалий">Поиск аномалий</h2>
<p>Иногда стоит взглянуть на полный лог SQL-запросов, чтобы понять действительное положение вещей.
Может казаться, что узкое место - одно, а на самом деле - другое (например, приложение генерит много UPDATE, хотя не должно).
В случае с docker-compose можно сделать так:</p>
<pre><code class="language-yaml">version: '2.3'
services:
mysql:
image: 'percona:8.0'
command:
- --general-log=1
- --general-log-file=/tmp/general.log
</code></pre>
<p>Далее просмотреть содержимое файла <code>/tmp/general.log</code>.</p>
</description>
</item>
<item>
<title>Устранение ошибок SQL запросов в режиме ONLY_FULL_GROUP_BY</title>
<link>https://vitalyzhakov.github.io/post/mysql-only-full-group-by/</link>
<pubDate>Wed, 16 Sep 2020 00:10:06 +0500</pubDate>
<guid>https://vitalyzhakov.github.io/post/mysql-only-full-group-by/</guid>
<description>
<blockquote>
<p>“Что случилось с моим приложением? Я установил новую версию MySQL. Запросы, что выполнялись на старой версии теперь падают с кучей ошибок.&rdquo;</p>
</blockquote>
<p>Многие программисты сталкиваются с этим вопросом при обновлении до версий 5.7 или 8.
В этой статье мы рассмотрим один из самых частых кейсов и его решение.</p>
<p>Мы говорим об этой ошибке</p>
<pre><code class="language-SQL">ERROR 1055 (42000): Expression #2 of SELECT list is not in GROUP BY clause
and contains nonaggregated column 'test.web_log.user_id' which is not functionally
dependent on columns in GROUP BY clause; this is incompatible
with sql_mode=only_full_group_by
</code></pre>
<p>Видели ли вы когда-либо её?</p>
<h2 id="sql-mode">SQL_MODE</h2>
<p>Для начала разрешите мне представить концепцию <em>SQL_MODE</em>.</p>
<p>MySQL может работать используя разные SQL режимы, которые влияют
на синтаксис запросов и валидацию.
В зависимости от установленного значения переменной sql_mode
запрос может быть валидным и выполняться или может получить
ошибку валидации и не может быть выполнен.</p>
<p>Старейшие версии MySQL научили пользователей писать запросы, которые
семантически корректны, потому что разработаны для работы в “прощающем режиме”.
Пользователи могли писать любой синтаксически правильный запрос независимо от
соответствия SQL стандарту или сематических правил.</p>
<p>Это была плохая привычка, которая была исправлена введением sql_mode, чтобы настроить MySQL
работать более строгим способом для проверки запросов.</p>
<p>Некоторые пользователи не знают об этой функции, потому что значение по умолчанию не было таким строгим. Начиная с версии 5.7, значение по умолчанию является более строгим, и по этой причине у некоторых пользователей возникают проблемы с неожиданными ошибками запросов после перехода на 5.7 или 8.0.</p>
<p>Переменная <em>sql_mode</em> может быть установлена в файле конфигурации (/etc/my.cnf) или
может быть изменена во время выполнения.
Область действия переменной может быть GLOBAL или SESSION, поэтому может измениться
в соответствии с целью для любого отдельного соединения.</p>
<p>Переменная sql_mode может иметь несколько значений, разделённых запятой, для настройки различных поведений.
Например, вы можете проинструктировать MySQL как обращаться с датами с нулями, как <code>0000-00-00</code>,
чтобы дата считалась действительной или нет.</p>
<p>В &ldquo;прощающем режиме&rdquo; (или если переменная sql_mode пуста), вы можете вставить такое значение без проблем.</p>
<pre><code class="language-SQL"># установка sql в &quot;прощающий режим&quot;
mysql&gt; set session sql_mode='';
Query OK, 0 rows affected (0.00 sec)
mysql&gt; create table t1( mydate date );
Query OK, 0 rows affected (0.05 sec)
mysql&gt; insert into t1 values('0000-00-00');
Query OK, 1 row affected (0.00 sec)
mysql&gt; select * from t1;
+------------+
| mydate |
+------------+
| 0000-00-00 |
+------------+
1 row in set (0.00 sec)
</code></pre>
<p>Но это не правильное поведение, как заявлено в режиме TRADITIONAL.
Как хорошие программисты знают, что нужно проверять даты
в коде приложения, чтобы избежать некорректных данных или некорректных результатов.</p>
<p>Далее мы показываем, как динамически проинструктировать MySQL вести себя в traditional режиме,
чтобы выбросить исключений вместо замалчивания ошибки:</p>
<pre><code class="language-SQL">mysql&gt; set session sql_mode='TRADITIONAL';
Query OK, 0 rows affected (0.00 sec)
mysql&gt; insert into t1 values('0000-00-00');
ERROR 1292 (22007): Incorrect date value: '0000-00-00' for column 'mydate' at row 1
</code></pre>
<p>Существует множество режимов, которые вы можете использовать.
Покрытие всех режимов - не цель данной статьи, поэтому обратитесь
к <a href="https://dev.mysql.com/doc/refman/8.0/en/sql-mode.html" target="_blank">официальной документации</a>
за подробностями и примерами.</p>
<h2 id="проблема-only-full-group-by">Проблема ONLY_FULL_GROUP_BY</h2>
<p>Давайте сосредоточимся на самом частом кейсе ошибок миграции с 5.7 на 8.0.
Как уже было сказано, в 5.7 более строгий режим, чем в 5.6, в 8.0 более строгий, чем в 5.7.</p>
<p>Это работает, если вы обновляете MySQL, копируя старый файл my.cnf,
который не имеет специфичных настроек для переменной sql_mode. Итак, имейте в виду.</p>
<p>Давайте создадим простую таблицу для хранения кликов на вебстраницах нашего сайта.
Мы будем записывать название страницы и ID зарегистрированного пользователя.</p>
<pre><code class="language-SQL">mysql&gt; create table web_log ( id int auto_increment primary key, page_url varchar(100), user_id int, ts timestamp);
Query OK, 0 rows affected (0.03 sec)
mysql&gt; insert into web_log(page_url,user_id,ts) values('/index.html',1,'2019-04-17 12:21:32'),
-&gt; ('/index.html',2,'2019-04-17 12:21:35'),('/news.php',1,'2019-04-17 12:22:11'),('/store_offers.php',3,'2019-04-17 12:22:41'),
-&gt; ('/store_offers.php',2,'2019-04-17 12:23:04'),('/faq.html',1,'2019-04-17 12:23:22'),('/index.html',3,'2019-04-17 12:32:25'),
-&gt; ('/news.php',2,'2019-04-17 12:32:38');
Query OK, 7 rows affected (0.01 sec)
Records: 7 Duplicates: 0 Warnings: 0
mysql&gt; select * from web_log;