Мы уже говори­ли о том, что такое хай­лоад и какой он быва­ет, а теперь хотелось бы кос­нуть­ся дос­таточ­но боль­ной темы — сто­ит ли к нему готовить­ся молодым ком­пани­ям, которые толь­ко начина­ют раз­рабаты­вать свой про­дукт. И если сто­ит, то на каком эта­пе раз­работ­ки.

По­нят­ное дело, что всем раз­работ­чикам, как людям тех­ничес­ким, хочет­ся инте­рес­ных и слож­ных задач и архи­тек­тур, но на стар­те важен баланс тех­нологий и кли­ент­ских фич про­дук­та. С одной сто­роны, надо как мож­но быс­трее выкаты­вать новую фун­кци­ональ­ность, с дру­гой — не ока­зать­ся в точ­ке, ког­да ничего не работа­ет из‑за слож­ных наг­рузок и сде­лать ничего нель­зя. Как бы аван­тюрно это ни зву­чало — за пять лет работы мы поч­ти не видели, что­бы ком­пания дей­стви­тель­но ока­зыва­лась в такой ситу­ации, что сде­лать было уже ничего нель­зя (за ред­кими исклю­чени­ями). Зато видели мно­жес­тво слу­чаев, ког­да ком­пании тра­тили свое вре­мя на то, что­бы готовить­ся к жут­кой посеща­емос­ти, до которой в ито­ге баналь­но не дожива­ли.

Примерная схема работы начинающих стартаперов
При­мер­ная схе­ма работы начина­ющих стар­таперов

Как говари­вал дедуш­ка Кнут, преж­девре­мен­ная опти­миза­ция — корень всех зол. И в дан­ной статье мы хотим прой­тись по трем видам преж­девре­мен­ной опти­миза­ции, которые видим чаще все­го в веб‑раз­работ­ке. Это:

  • преж­девре­мен­ная опти­миза­ция работы с базой дан­ных;
  • преж­девре­мен­ная опти­миза­ция все­го при­ложе­ния в целом;
  • чрез­мерное увле­чение хип­стерски­ми тех­нологи­ями в надеж­де най­ти сереб­ряную пулю.
 

Преждевременная оптимизация работы с базой данных

Мы час­то видим, как стар­тапы ожи­дают, что с пер­вого же дня основная наг­рузка ляжет на базу дан­ных, которая не смо­жет спра­вить­ся с рас­тущим объ­емом зап­росов и положит про­ект. В одной дру­жес­твен­ной нам ком­пании, которой, к сожале­нию, теперь уже нет, раз­работ­чики исхо­дили из того, что гигант­ское количес­тво дан­ных будет уже на стар­те. И раз­работ­ку про­екта начали с пла­ниро­вания архи­тек­туры, которая дала бы воз­можность лег­ко и прак­тично балан­сировать дан­ные меж­ду любым количес­твом сер­веров. При этом схе­ма учи­тыва­ла воз­можность добав­ления новой шар­ды базы дан­ных на лету.

Раз­работ­чики воз­вра­щались к этой под­систе­ме нес­коль­ко раз на про­тяже­нии двух лет — она не была пол­ностью готова, а раз­работ­чики, свя­зан­ные с про­дук­товой частью, регуляр­но меняли схе­мы БД. В ито­ге про­ект был зак­рыт, а силы, которые мог­ли бы быть бро­шены на раз­работ­ку про­дук­товой час­ти, были пот­рачены на соз­дание под­систе­мы, которую никог­да не приш­лось исполь­зовать.

На деле — шар­динг необ­ходим как решение двух проб­лем:

  1. Опе­рации с БД в усло­виях огромно­го объ­ема дан­ных.
  2. Мас­шта­биро­вание наг­рузки на дис­ковую запись.

От­дель­но хотелось бы отме­тить: в 99 из 100 слу­чаев (и в усло­виях широкой дос­тупнос­ти SSD в наши дни) задачи мас­шта­биро­вания за­писи воз­ника­ют далеко не сра­зу. Что­бы кон­тент соз­давал­ся, поль­зовате­ля надо заин­тересо­вать. При этом боль­шие объ­емы дан­ных в вашей сис­теме так­же не ока­жут­ся вне­зап­но и сра­зу — поч­ти навер­няка у вас будет вре­мя на модифи­кацию архи­тек­туры в про­цес­се работы сис­темы так, что­бы она мог­ла мас­шта­биро­вать­ся по записи.

Что может это­му помешать и что дей­стви­тель­но сто­ит сде­лать в начале? Мы ска­жем кра­моль­ную вещь: сле­дует быть край­не осто­рож­ным с исполь­зовани­ем любых абс­трак­ций дос­тупа к базе дан­ных. Да, ORM — это кле­во и удоб­но, но, ког­да речь зай­дет о том, что зап­рос надо рас­кидать по двум раз­ным мес­там, — вам при­дет­ся докопать­ся до самых глу­бин исполь­зуемо­го ORM, что­бы понять, как это реали­зовать. Мы час­то видим, как, казалось бы, прос­тая задача модифи­кации в усло­виях генери­рова­ния SQL-зап­росов прев­раща­ется в нас­тоящий ад.

Вто­рая опти­миза­ция с точ­ки зре­ния прог­раммис­та, которую мож­но сде­лать, — это сра­зу пре­дус­мотреть, что сер­веров может быть нес­коль­ко, и в момент выбора дан­ных допус­тить то, что SQL-зап­рос может быть выпол­нен на одном из нес­коль­ких сер­веров. То есть: у тебя есть объ­ект дос­тупа к БД, и у тебя есть SQL-зап­рос, который ты хочешь выпол­нить. По‑хороше­му, в том мес­те, где ты выпол­няешь этот зап­рос к сер­веру, ты дол­жен иметь воз­можность непос­редс­твен­но выб­рать сер­вер, к которо­му обра­щаешь­ся.

И еще нем­ного про ORM: в усло­виях высоких наг­рузок не может получить­ся нор­маль­ного раз­деления сфер вли­яния — прог­раммист прог­рамми­рует, а адми­нис­тра­тор БД адми­нис­три­рует БД. По сути, работа прог­раммис­та дол­жна уже исхо­дить из спе­цифи­ки работы с БД, пос­коль­ку даже неболь­шие модифи­кации (фор­сирова­ние исполь­зования индексов, изме­нение зап­роса под спе­цифи­ку опти­миза­тора базы дан­ных) могут дать огромный при­рост про­изво­дитель­нос­ти. Прос­тота и дос­тупность ORM изо­лиру­ют всю эту спе­цифи­ку от раз­работ­чика и дают шанс сге­нери­ровать безум­ный зап­рос, который для прог­раммис­та будет выг­лядеть впол­не нор­маль­ным.

На одном из про­ектов — сай­те с воп­росами и отве­тами, написан­ном на джан­ге, — мы видели, как спи­сок воп­росов на про­тяже­нии какого‑то количес­тва кода перед его получе­нием был огра­ничен рядом кри­тери­ев. С точ­ки зре­ния прог­раммис­та все выг­лядело более‑менее ОК — вре­мя от вре­мени к объ­екту спис­ка прос­то допол­нялся новый кри­терий. В ито­ге же ORM джан­ги генери­ровал зап­рос на 25 group by, которые экспо­нен­циаль­но соз­давали наг­рузку на базу по мере рос­та объ­ема обра­баты­ваемых дан­ных. И если бы зап­рос был в виде прос­того SQL’я — еще оста­вал­ся шанс подумать, как опти­мизи­ровать про­цеду­ру, но в этом слу­чае все было силь­но усложне­но.

На­пос­ледок про часть с опти­миза­цией БД. Как мы уже ска­зали, мы чаще видим наг­рузку на чте­ние, чем на запись. Орга­низо­вать боль­шую наг­рузку на запись — это отдель­ный успех :). А наг­рузку на чте­ние мож­но балан­сировать вооб­ще без осо­бых изме­нений со сто­роны кода. Если мы готовы к тому, что во вре­мя выпол­нения зап­роса мы можем выб­рать сер­вер, то, если нам надо балан­сировать чте­ние, мы можем прос­то соз­дать n-ное количес­тво slave-сер­веров. А при выпол­нении селек­та ран­домно выбирать один из сер­веров, на котором уже и выпол­нить этот селект. А запись про­изво­дить толь­ко в один отдель­ный master.

Почти идеальная схема работы с базой
Поч­ти иде­аль­ная схе­ма работы с базой
 

Преждевременная оптимизация всего приложения в целом

Эта исто­рия начина­ется так же: одни наши друзья ожи­дали сто мил­лионов поль­зовате­лей. Пос­лушали док­лад про то, как все устро­ено в Яндексе, и захоте­ли сде­лать у себя так же. Решили, что nginx будет собирать веб‑стра­ницы по шаб­лонам в XSLT/XML, опи­сыва­ющим общую струк­туру ком­понент на стра­нице. При зап­росе nginx пар­сит файл, видит, какие исполь­зуют­ся ком­понен­ты, и по каж­дому ком­понен­ту обра­щает­ся на бэкенд за его отренде­рен­ной вер­сией, переда­вая иден­тифика­тор сес­сии, что­бы сох­ранить сос­тояние. А соот­ветс­твен­но, бэкенд всей плат­формы понима­ет, как получать такие зап­росы и генери­ровать вывод ком­понен­та.

В ито­ге соз­дание это­го решения заняло боль­ше года, прив­лечен­ные C-раз­работ­чики зап­росили пре­доп­лату, но так и не сде­лали модуль, который бы реали­зовы­вал дан­ный фун­кци­онал в nginx. Впро­чем, он и не пот­ребовал­ся, так как пос­ле двух лет раз­работ­ки про­ект приш­лось свер­нуть.

В наш XXI век облачных тех­нологий, где CPU-ресур­сы мас­шта­биру­ются доволь­но дешево по вер­тикали, а балан­сиров­щики наг­рузки вклю­чают­ся за очень корот­кое вре­мя, соз­давать заранее какой‑то спе­цифи­чес­кий код для балан­сиров­ки при­ложе­ния, на наш взгляд, кажет­ся лиш­ней работой. Что вы дей­стви­тель­но можете сде­лать, так это пре­дус­мотреть, что­бы сес­сии поль­зовате­лей не были при­вяза­ны к кон­крет­ной веб‑машине, и пос­ле это­го прос­то кло­ниро­вать то количес­тво веб‑инстан­сов, которое вам нуж­но в ожи­дании опти­миза­ции кода.

Дру­гая край­ность, которую мы встре­чали в сво­ей прак­тике, — это усложне­ние работы со ста­тичес­ким содер­жимым, заг­ружа­емым поль­зовате­лями. В рас­чете на высокую наг­рузку раз­работ­чики соз­дают под­дер­жку клас­тера nginx-сер­веров с WebDAV-модулем. Каж­дый файл, заг­ружа­емый поль­зовате­лем, сна­чала ложил­ся на про­межу­точ­ный сер­вер, а сле­дом отправ­лялся на WebDAV-сер­вер, отку­да поз­же отда­вал­ся nginx’ом. Информа­ция о мес­те хра­нения это­го фай­ла сох­ранялась в базе. Реаль­ная наг­рузка на этот про­ект так и не приш­ла.

Дей­стви­тель­но серь­езной проб­лемой, на наш взгляд, для боль­шинс­тва про­ектов явля­ется лишь пол­ное отсутс­твие какого‑либо прин­ципа хра­нения фай­лов — ког­да все скла­дыва­ется в одну дирек­торию. И ког­да в один прек­расный день ты понима­ешь, что фай­лов ста­ло под 70 тысяч и ext4 не дает тебе записы­вать новые, ты либо экс­трен­но при­думы­ваешь более разум­ную схе­му рас­пре­деле­ния фай­лов, либо пере­езжа­ешь, к при­меру на XFS. Во вто­ром слу­чае тебя уже нич­то не спа­сет :).

Как мы пред­лага­ем решать этот воп­рос всем кли­ентам:

  1. Ес­ли есть раз­деление «горячих» и «холод­ных» дан­ных по вре­мени (све­жие фотог­рафии ВКон­такте про­лис­тыва­ют чаще, чем ста­рые), а поток залива­емых дан­ных при­мер­но оди­наков — при заг­рузке фай­лов хра­ни фай­лы в дирек­тории, соз­данной из даты/вре­мени момен­та заг­рузки. В зависи­мос­ти от количес­тва это может быть прос­то раз­биение по дням, а может быть раз­биение по часам. Опять же так будет про­ще уда­лять ста­рые фай­лы при необ­ходимос­ти.

Разделяем данные по времени...
Раз­деля­ем дан­ные по вре­мени...

  1. Ес­ли раз­деления дан­ных на све­жие и не очень нет, то мож­но выс­читывать два раз­личных циф­ровых хеша от име­ни фай­ла, а затем исполь­зовать их в некой струк­туре дирек­торий, внут­ри которой уже скла­дывать сам файл.
...либо же просто по хешу
...либо же прос­то по хешу

Но что делать, если мы боим­ся, что сер­вер не вывезет наг­рузки на чте­ние? На пер­вом эта­пе нас спа­сет lsyncd, который поможет рас­пре­делить ста­тику по нес­коль­ким отдель­ным сер­верам и тем самым рас­пре­делить наг­рузку на ее чте­ние. А так как ста­тика вез­де оди­нако­вая, то читать ее мож­но с любого сер­вера. При необ­ходимос­ти мож­но будет лег­ко добав­лять в схе­му допол­нитель­ные мощ­ности, пока прог­раммис­ты наконец не при­дума­ют более тон­кую схе­му. Но будь осто­рожен — пом­ни, что нет ничего более пос­тоян­ного, чем какой‑то вре­мен­ный кос­тыль. От них обя­затель­но нуж­но будет потом избавлять­ся.

 

Про хипстеров и серебряную пулю

Не сто­ит сло­мя голову бро­сать про­верен­ные вре­менем и мил­лиар­дами RPS’ов решения ради новых мод­ных фишечек, в надеж­де най­ти спа­сение от всех бед сра­зу. Зачас­тую ока­зыва­ется, что даже если какие‑то свер­хно­вые методи­ки и тех­нологии реша­ют какие‑то из тво­их текущих проб­лем, то при этом они добав­ляют изрядную долю новых, даже о потен­циаль­ной воз­можнос­ти которых ты мог никог­да не задумы­вать­ся. Взять, к при­меру, те же NoSQL базы дан­ных. В увле­чении NoSQL как панаце­ей от проб­лем с БД на самом деле есть очень мно­го под­водных кам­ней. Нап­ример, на боль­шинс­тве NoSQL-решений син­хро­низа­ция дан­ных на диск — это гло­баль­ный лок, во вре­мя которо­го работа с базой дан­ных край­не зат­рудни­тель­на. При исполь­зовании редиса, если находя­щиеся в нем дан­ные тебе нуж­ны, у тебя есть два выхода:

  1. Иметь на отдель­ной машине слейв, который будет пери­оди­чес­ки дам­пить дан­ные.
  2. От­клю­чить дамп на мас­тере, не делать слейв, пос­тоян­но молить­ся, что ничего не упа­дет.

По сути, две глав­ных проб­лемы всех новых тех­нологий сле­дуют одна из дру­гой:

  1. Сы­рость решения.
  2. От­сутс­твие базы зна­ний по сущес­тву­ющим проб­лемам.

Ес­ли у тебя есть проб­лема с MySQL и Postgres, ты можешь написать про нее в гуг­ле и поч­ти навер­няка най­дешь мил­лион людей, которые встре­чались с подоб­ной до тебя. Если же проб­лема свя­зана с какой‑то све­жей тех­нологи­ей, велик риск, что тебе при­дет­ся свя­зывать­ся с раз­работ­чиком и вмес­те с ним раз­бирать­ся, что к чему и почему она воз­никла. Мы сами однажды слиш­ком увлеклись подоб­ными вещами (и это про­изош­ло даже уже на отно­ситель­но извес­тном и дол­го раз­рабаты­ваемом Openstack’е) — вмес­то при­выч­ных методов вир­туали­зации хотелось иметь воз­можность управлять вир­туаль­ными машина­ми «как в ама­зоне». Для это­го мы выб­рали Openstack, с которым мучились в течение сле­дующе­го месяца. Основная при­чина стра­даний — сырость и необ­ходимость под­дер­жки. Для того что­бы запус­тить вир­туаль­ную машину в Openstack, работа­ют пять Python-демонов, вза­имо­дей­ствие меж­ду которы­ми идет через RabbitMQ, а код пос­тоян­но меня­ется. Понят­ное дело, что сло­мать­ся там может все что угод­но, и оно лома­ется.

 

Не перегибай

С дру­гой сто­роны, быть сов­сем уж рет­рогра­дом тоже опас­но для жиз­ни. Давай­те вспом­ним Perl-раз­работ­чиков (мы сов­сем не хотим оби­деть Perl-раз­работ­чиков), которые говори­ли, что Perl луч­ше чем PHP, Perl луч­ше Python, Perl луч­ше Ruby и так далее. И сей­час мы видим огромное количес­тво Perl-раз­работ­чиков, рынок для которых пос­тепен­но зак­рыва­ется. Как же быть? Глав­ное — най­ди кого‑нибудь, кто про­шел тер­нистый путь выб­ранной тобой тех­нологии до тебя :). У очень мно­гих людей есть мно­го сво­бод­ного вре­мени, а стар­тапы, которые выделя­ют по два года на вся­кие тех­нологи­чес­кие решения, всег­да будут сущес­тво­вать. Выб­рал хит­рую тех­нологию, про которую все пишут? Най­ди ребят, которые уже про­бова­ли и помогут.

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии