Google App Engine – масштабируемые приложения

    Google App Engine позволяет легко создавать приложения, надежно работающие даже при большой нагрузке и с большими объемами данных. Но ещё проще создать программного монстра, который будет работать очень медленно или вовсе не работать, постоянно возвращая ошибку HTTP 500.

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

    Все ниже изложенное прежде всего касается приложений, написанных на Java, но большей частью должно быть справедливо и для приложений написанных на языке Python.


    Одна секунда


    Google App Engine позволяет каждому приложению обслуживать до 500 запросов в секунду, но только в том случае, если выполнение каждого запроса в среднем занимает не более одной секунды. В противном случае приложение, как неэффективное, будет ограничено, и уже при сравнительно небольшой нагрузке часть запросов начнет завершаться ошибкой HTTP 500.

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

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

    Тридцать секунд


    Хранилище данных в App Engine позволяет приложениям эффективно работать с огромным объемом данных благодаря своей распределенной архитектуре, но из-за неё же в среднем 1 из 3000 операций с хранилищем заканчивается тайм-аутом. Такие операции автоматически повторяются, но если после нескольких повторов операцию завершить так и не удалось, то генерируется DatastoreTimeoutException, при отсутствии обработчика которого приложение завершится ошибкой HTTP 500.

    Для быстрых запросов, обработка которых занимает меньше секунды, автоматические повторы уменьшают частоту появления DatastoreTimeoutException. Во время же выполнения «длинных» запросов, которые интенсивно работают с хранилищем, вероятность генерации исключения значительно выше. Часто такие запросы вообще завершаются DeadlineExceededException, потому что при многократном повторении неудачного «тяжелого» обращения к хранилищу приложение может исчерпать 30-секундный тайм-аут, отведенный на обработку запроса.

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

    Десять раз в секунду


    Все объекты в хранилище относятся к какой-нибудь группе объектов (entity group). К каждой группе принадлежат объекты, между которыми определено отношение зависимости. Объект, независящий от других объектов, принадлежит к группе, состоящей из него самого. Группы объектов помогают App Engine хранить связанные объекты вместе в распределенном хранилище.

    Распределенная архитектура хранилища позволяет распараллеливать операции с объектами, но только в том случае, если они принадлежат к разным группам. Для объектов, являющихся частью одной группы, поддерживается не более 10 операций записи в секунду.

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

    Проектируя приложение, нужно ясно представлять как объекты будут объединены в группы, и как часто будет модифицироваться каждая из них, в том числе с учетом роста числа пользователей приложения. Чтобы приложение хорошо масштабировалось необходимо придерживаться небольших групп объектов, а для часто обновляемых данных использовать технику шардинга (sharding).

    Резюме


    Google App Engine позволяет легко создавать приложения, надежно работающие даже при большой нагрузке и с большими объемами данных, но только в том случае если запросы к приложению выполняются быстро, обмен данными с хранилищем осуществляется небольшими порциями, а сами данные организованы в небольшие группы объектов.



    Статья написана Александром, который пока ещё не присутствует на Хабре.
    Александр на Хабре — windicted
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 26

      +12
      Статья конечно интересная, но не пробовал ли Александр публиковать ее в Песочницу?
        0
        Ждём появления Александра на Хабре с более подробными статьями. Инвайта к сожалению нету.
          0
          Я так понимаю эти ограничения действительны только для бесплатного апп енджина? Или для платного тоже?
            +1
            Задайте себе вопрос: а есть ли разница между платным и бесплатным, кроме накладываемых ограничений не количество запросов, объёмов БД и др.?
              0
              *на количество*
                0
                Да, как-то криво я задал вопрос. Был сонный :)
                +1
                Между платным и бесплатным app engine разница только количественная (да и то не всегда), а принципиальной разницы нет.
                  +1
                  Квоты в минуту у платного повыше, ну и не относящийся к статье Blobstore недоступен пока оплата не включена.

                  В целом же, в описании квот довольно ясно описаны ограничения, и путём нехитрых вычислений можно прикинуть, что 500 запросов в секунду могут быть обработаны при среднем времени CPU на запрос, составляющем 0,144с для платной версии, что довольно далеко от заявленной секунды.

                  В бесплатном варианте число запросов ограничено 123-мя в секунду и для его достижения среднее время запроса должно составлять 0,121с.
                    0
                    Спасибо. Я плохо задал вопрос, а получил хороший ответ )
                      0
                      От Александра: Под заголовком «одна секунда» речь идет о фактическом времени выполнения запроса, а не о затраченном процессорном времени. Как и многие вы введены в заблуждение русской версией документации, которая неактуальна. Одновременно с выходом SDK 1.3.3 поменялась система учета параллельных динамических запросов: code.google.com/intl/en/appengine/docs/java/runtime.html#Quotas_and_Limits

                      Приложение может обслуживать 500 (и даже больше) запросов при среднем фактическом времени выполнения каждого запроса не более секунды. Сколько при этом процессорного времени будет затрачено — роли не играет.
                        0
                        В английской версии те же самые 15 и 72 минуты CPU в минуту, поэтому русская версия была приведена в пример как актуальная, но более удобочитаемая. В приведённой Вами ссылке речь о том, что долгие запросы имеют меньший приоритет в самой системе, что может сказать на поведении приложения при большой нагрузке на соседние.

                        И если длинный запрос довольно просто разбить на несколько коротких с помощью task queue (да и Dashboard красноречиво сигналит обо всех долгих запросах), то под ожидаемую нагрузку в 500 запросов надо серьёзно оптимизировать код. И, на мой взгляд, достижение квот придёт быстрее — на многих запросах время CPU может превышать время запроса.
                          0
                          Я вас неправильно понял, вы говорите о квотах CPU в минуту. К счастью, их больше нет. Об этом Nick Johnson (из команды App Engine в Google) писал не раз, например, вот тут. А документация опять отстает.
                      +1
                      От Александра: Для обоих. В статье приведены цифры для приложений с включенным биллингом.
                      +6
                      В статье не хватает: 1. ссылок на документацию, где о фактах, изложеных в статье, можно почитать подробнее; 2. картинок, демонстрирующих control flow; 3. примеров кода.
                        +3
                        От Александра: С ссылками на документацию, увы, есть проблемы. Система развивается, выходят новые версии SDK, но документация имеет всего одну версию, которая по-тихому (без каких-либо анонсов) меняется. Например, одновременно с выходом GAE SDK 1.3.3 была изменена система
                        масштабирования приложений (1 секунда на обработку запроса). В Change Log об этом не было сказано ни слова, так же как и о том, что изменения кратко изложены в документации.

                        Кроме того, часто информация в документации размазана и неполна — знания буквально приходится собирать по крупинкам, регулярно выходя далеко за её рамки. Что-то узнаешь через google groups, что-то приходится проверять с помощью кода. В основном статься собрана как раз из таких знаний. Собрана без картинок и примеров кода. Ну что ж. Учту на будущее.
                      • UFO just landed and posted this here
                          –1
                          Принимайте лекарства от расстройства пищеварения.
                          –1
                          Слишком часто в статье встречается слово «ошибка», «Exception», слишком много «но». Хотелось бы реальных примеров от уважаемого Александра.

                          Напишите в личку если нужен инвайт для него.
                            +1
                            Что-то меня смущает фраза «Для объектов, являющихся частью одной группы, поддерживается не более 10 операций записи в секунду». Вопрос — в группу объединяются объекты или классы? Как работает механизм объединения в группы? Автоматически?

                            И поподробнее про sharding в контексте GAE, если есть возможность.
                              +5
                                0
                                Большое спасибо за ссылку
                                0
                                От Александра: В группы объединяются именно объекты. Обычно они объединяются автоматически, когда между ними появляется отношение зависимости (owned relationship). Про отношения очень хорошо написано в документации: code.google.com/appengine/docs/java/datastore/relationships.html
                                  +1
                                  От Александра: Про технику шардинга в GAE есть отличная статья:
                                  code.google.com/appengine/articles/sharding_counters.html
                                    0
                                    Спасибо за ссылки. Получил свой ответ, прочитав статью по ссылке выше. Получается что для того, чтобы увеличить количество операций записи можно использовать sharding, memcache или и то и другое. Но при их использовании сильно разрастается код (раз в 10 судя по примеру «sharding counters»). Хочется какой-нибудь стандартной библиотеки, которая помогла бы прикрутить sharding в виде «аспекта» (без влезания кода DAO в бизнес-объекты).
                                      0
                                      В принципе, оптимальное решение — зеркало хранилища данных в memcache, при котором само приложение работает с данными в memcache, а актуальность базы поддерживается работающим отдельно демоном. Правда, у этого решения есть ещё ряд недостатков: task queue ещё в стадии обкатки (практически не проблема), во время техобслуживания memcache не работает и приложение резко начнёт потреблять в разы больше ресурсов (правда, в эти страшные моменты подобные проблемы будут практически у всех приложений), и в виде универсальной библиотеки сделать это проблематично — всё равно придётся допиливать под конкретные модели данных
                                    0
                                    это parent модели. например- если комментарий будет иметь родителем «пост», то в одной транзакции можно будет и коммент добавить и, например, обновить счетчик комментариев у поста (денормализация)

                                  Only users with full accounts can post comments. Log in, please.