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

1. Думайте своей головой и проверяйте факты


Это самое важное. Для вас не должно существовать безусловных авторитетов. Если кто-либо говорит полную чушь, или говорит что-то, что противоречит вашей практике — не прислушивайтесь к таким советам, и не важно, насколько этот человек известный и уважаемый. Если вы разрабатываете большую систему, и она не будет работать хорошо, то спрашивать будут с вас и в данном случае «мы следовали лучшим мировым практикам» оправданием не является. Умение применять нужные технологии в нужном месте и делает вас ценным специалистом, а не слепое следование чьим-то советам — для этого как раз квалификации не требуется.

2. Простое лучше сложного и «правильного»


Системы, которые вы разрабатываете, должны быть понятны прежде всего вам и вашим коллегам, а не «сферическому коню в вакууме». Например, если готовая система, на которую вы хотите перейти всей командой, не прозрачна и не понятна всем участникам, то вряд ли можно надеяться на то, что люди смогут эффективно её использовать. Как правило, под нагрузкой всплывают разного рода проблемы даже в самых лучших и отлаженных системах, а времени на устранение проблем обычно мало, поскольку в этот момент страдает большое количество пользователей. Без хорошего понимания, как всё работает, у вас мало шансов решить проблему быстро. Можно вспомнить, к примеру, как лежали «одноклассники» в течение нескольких дней — в хорошо спроектированной и продуманной системе даже серьезные сбои устраняются за несколько часов, а не суток.

3. Не забывайте про мониторинг


Обычно никто не забывает о том, что нужно тестировать изменения перед тем, как их выкладывать. Но намного реже люди задумываются о том, что происходит после деплоя, и как себя система ведет в продакшене. Но на самом деле мониторинг — это логичное продолжение QA для кода. У вашей инфраструктуры тоже должен быть Quality Assurance — вы должны видеть проблемы заранее, а если они все-таки возникли, то иметь возможность быстро диагностировать причины. Без мониторинга этого сделать попросту невозможно.

4. Пишите постмортемы (у каждой поломки есть стоимость)


Когда что-то важное упало — напишите постмортем, в котором будет описано:
  • что случилось
  • почему это случилось
  • сколько пользователей пострадало (если это возможно посчитать, то и сколько денег компания потеряла)
  • что нужно системно поменять, чтобы такого больше не повторилось (варианты вроде «быть внимательнее» на практике не работают)


Зачастую, уже при подготовке к написанию постмортема, можно сделать вывод, что на самом деле ничего страшного не произошло. То есть, устранение причин, вызвавших инцидент, обойдется компании дороже, чем оставить всё «как есть». Не стоит забывать и о таком варианте — то есть, иногда что-то будет ломаться, и это нормально, если оно находится в допустимых границах (для каждой компании эти границы свои).

5. Производительность — это фича


Несмотря на то, что формального определения «высоконагруженного проекта» не существует, обычно все более-менее понимают, что имеется в виду. Одной из особенностей приложений под хорошей нагрузкой является то, что зачастую намного проще оптимизировать какой-то кусок кода, чем разнести логику на несколько серверов. То есть, при разработке высоконагруженных систем часто приходится заниматься оптимизацией и полезно уметь это делать. Если оптимизируется не только пропускная способность, но и время отклика, то вы заодно улучшаете user experience. Win-win.

6. Двухфазный коммит — это сложно, но неизбежно


Если только ваша система не помещается полностью на один сервер, вы неизбежно будете сталкиваться с необходимостью делать т.н. «двухфазный коммит» — необходимость атомарно обновить данные в нескольких местах, например в базе данных и в сервисе. Двухфазный коммит на практике — это миф. Атомарно обновить что-либо больше, чем на одном сервере невозможно. Есть разные способы, как можно достигать консистентности в таких случаях, но самым распространненым являются очереди — вы добавляете в одной транзакции данные и запись в отдельную таблицу — очередь на обновление сервиса. Поскольку это транзакция на одном сервере, то она либо пройдет целиком, либо не пройдет вовсе. Соответственно, даже если обновить данные в сервисе сразу не удалось, в конечном итоге данные будут согласованы.

7. Постраничная навигация — сложная задача


«Какая ещё сложная? Делаете обычный SELECT с LIMIT/OFFSET и всё, есть постраничная навигация!» — наверняка подумали вы. Вы правы, но этот подход хорошо работает только в случае, если данные не меняются (иначе будут возникать дубли и, наоборот, пропуски некоторых записей). Кроме этого, использование больших значений OFFSET обычно приводит к серьезной потере производительности, поскольку базе данных нужно буквально выбрать все запрошенные строки в количестве LIMIT+OFFSET, отбросить OFFSET штук и вернуть оставшийся LIMIT. Эта задача линейна по времени относительно значения OFFSET и при больших значениях эта конструкция обычно знатно тормозит.

В зависимости от задачи, решение этой задачи может быть различным, но оно практически никогда не является простым и однозначным. Например, можно использовать minId вместо номера страницы — вы просто пропускаете большинство записей по индексу. Или, если страницы возращаются в результате поиска, нужно уметь игнорировать новые изменения или где-то хранить снэпшот данных для соответствующего запроса.

8. (Ре)Шардинг — сложная задача


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

Если вы думаете, что проекты вроде CockroachDB представляют собой серебряную пулю, которая решит все ваши проблемы, то вы ошибаетесь. Если вы хотите получить от системы нормальную производительность, вам всё равно нужно хотя бы в общих чертах понимать, как шардирование происходит внутри базы данных, чтобы минимизировать общение между узлами (интенсивное общение между нодами — основная причина, по которой рост производительности редко бывает линейным при добавлении новых узлов).

9. Хороший код отличается от плохого тем, как обрабатываются ошибки


Вопреки распространенному мнению, хороший код определяется не только тем, насколько всё логично разложено по пакетам, можно ли этот код прочитать, следует ли он всем мыслимым и немыслимым style-гайдам, и так далее. Это всё важно, но пользователей системы (под пользователями здесь подразумеваются не только конечные пользователи сайта, но и системные администраторы, другие программисты, и т.д.) прежде всего волнует, чтобы всё работало так, как ожидается, а когда не работало, было понятно, что происходит.

На удивление много софта вообще не обрабатывают ошибки, или обрабатывает их в стиле «ой, что-то случилось», или вообще начинают DDoS'ить сами себя бесконечными ретраями. Обработка ошибок — это сложно, и адекватная реакция на них — тем более. Ошибки бывают самыми разными, и далеко не все из них требуют, к примеру, завершения программы. Например, если вы разрабатываете файловую систему и она совсем перестает работать (в том числе не дает удалить данные) при заполненности диска в 100%, то это — плохая файловая система. При этом, вы могли следовать всем «лучшим практикам» от известных людей и у вас миллион звезд на гитхабе — это ни о чём не говорит. Грубо говоря, shit happens, и даже у самых крутых людей место на серверах иногда неожиданно кончается, и вы должны уметь эту ситуацию.

На этом всё


Спасибо за то, что прочитали эту «статью». Интересно будет почитать ваше мнение, или ваши собственные советы, которые основаны на практическом опыте, пишите об этом в комментариях!

Комментарии (19)


  1. a-l-e-x
    07.12.2017 18:19

    Согласен.
    Я бы ещё Google SRE книжку посоветовал посмотреть. Там тоже очень много хорошего. Применяется везде, а не только для их платформы.


  1. alexkrash
    07.12.2017 20:36
    +1

    Когда что-то важное упало — напишите постмортем

    Как мне кажется, упущен пункт, что это знание должно быть расшарено с заинтересованными.


    1. a-l-e-x
      09.12.2017 13:05

      и буквально на днях пост был на эту тему:

      Getting the most out of shared postmortems — CRE life lessons
      goo.gl/ns5Ce2


  1. truezemez
    08.12.2017 08:23

    Спасибо за статью.


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

    Не уловил вашей мысли. Можете "на пальцах" объяснить?


    1. youROCK Автор
      08.12.2017 09:31

      Представим себе, что у нас, скажем, MySQL. Мы хотим обновить данные в таблице accounts и в сфинксе. Можно попытаться их обновить просто последовательно — сначала в базе обновляем, потом в сфинкс идем и надеемся на то, что у нас получится и там тоже обновить. Но сфинкс может находиться где-то на другом сервере и вообще быть недоступен в этот момент. Тогда мы останемся у разбитого корыта — в мускуле одни данные, а в сфинксе другие.

      Чтобы решить эту проблему, можно ввести очереди:
      — BEGIN;
      — update accounts…;
      — insert into sphinx_queue values (account_id);
      — COMMIT;

      То есть, вместо обновления сфинкса мы пишем в той же транзакции еще в одну таблицу-очередь. Потом, в отдельном фоновом процессе, мы эту очередь разгребаем и обновляем сфинкс. Если обновить сфинкс не удалось, то мы просто оставляем запись в таблице и пробуем снова через какое-то время.

      Более подробно об этом (и других проблемах шардинга) рассказывал Михаил Курмаев на хайлоаде: m.youtube.com/watch?v=Ygfwwd490mk


      1. truezemez
        08.12.2017 10:45

        Спасибо.


  1. Shersh
    08.12.2017 11:33

    А дайте ссылку на статью как делать свои continuation token'ы вместо offset и limit


    1. youROCK Автор
      08.12.2017 11:49

      https://habrahabr.ru/post/217521/ — это один из способов смягчить проблему, но не устранить её
      https://habrahabr.ru/post/44608/ — вот намного более старая статья, где есть намек на то, как можно решить проблему более правильно


      1. Shersh
        08.12.2017 11:52

        Там ведь нет про continuation token, но оптимизация интересная, спасибо


        1. youROCK Автор
          08.12.2017 11:56
          +1

          Я чуток отредактировал комментарий, добавив туда ещё одну ссылку :).
          Но идея не особо меняется — вы используете поле для сортировки, например, id, и в token вставляете последнее значение id, на котором остановились. Если поле не уникальное (например, timestamp в секундах), то в токен дополнительно засовываете значения id, которые вы уже видели с самым последним значением timestamp. Грубо говоря, если у вас строки в таблице идут вот так:


          id timestamp
          1 1
          2 2
          3 3
          4 4
          5 4 <- limit = 5, это последняя запись на текущей странице
          6 4
          7 5


          То для следующей страницы мы должны в итоге получить запрос вида SELECT… WHERE timestamp >= 4 AND id NOT IN(4,5)


  1. ptrue
    08.12.2017 13:17

    Спасибо за статью
    А вот что то практическое можете по этим пунктам посоветовать? =) про шардинг или вот про мониторинг, например?


    1. youROCK Автор
      08.12.2017 13:19

      Про шардинг — https://youtube.com/watch?v=Ygfwwd490mk, доклад Михаила Курмаева. Про мониторинг — вопрос хороший, сходу хороших докладов не припомню. Если речь идет о просто мониторинге серверов и немножко бизнес метрик, то не сочтите за рекламу, но я бы рекомендовал начать с того, что делают ребята из okmeter, например.


      1. tru_pablo
        08.12.2017 13:50
        +1

        Привет, раз уж позвали — мы тут есть.


  1. DreaMinder
    08.12.2017 14:03

    интересно узнать мнение автора о постройке такой системы в облаке. Например если использовать БД вендора которая сама масштабируется и повлиять не это нельзя.
    Понятно что оптимизация и тут понадобится, но я так понимаю уже с целью экономии денег?


    1. youROCK Автор
      08.12.2017 14:11

      Ничего не могу посоветовать, ибо с облачными базами не работал. Но из того, что я слышал от людей, которые их использовали, это обычно получается дорого и неуправляемо (как раз из-за отсутствия возможности что-то настроить «под себя»). То есть, оно годится в условиях сильно ограниченных людских ресурсов и слабо ограниченных финансовых ресурсов (enterprise?), и обычно это с хайлоадом плохо сочетается. Но понимать, как система работает, всё равно очень желательно, как раз для того, чтобы не тратить лишних денег (на больших объемах уменьшение расходов даже на 1% может выливаться в месячную зарплату одного работника).


  1. amarao
    08.12.2017 15:26

    В целом разумно, но первый совет — очень спорный.

    ов. Если кто-либо говорит полную чушь, или говорит что-то, что противоречит вашей практике — не прислушивайтесь к таким советам, и не важно, насколько этот человек известный и уважаемый.

    Прислушиваться нужно. Проверять нужно. Часто бывает, что человек несёт полную чушь, потому что он думает уже не о «твоей маленькой проблеме», а больших последствиях твоего метода исправления маленькой проблемы.

    Часто бывает, что «чушь» — это иная парадигма и иной комплект ценностей. Например, вам могут нести чушь про идемпотентность и вред неявных сайд-эффектов. Чушь? Чушь. Слушать? Ну..., как сказать, можно и не слушать.