Хабрахабр

noBackend, или Как выжить в эпоху толстеющих клиентов

Название статьи не стоит понимать буквально: backend никуда не делся, просто фокус разработки — особенно на начальном этапе развития нового проекта — сильно смещается в сторону «клиентской части». Появляется большой соблазн взять что-то понятное для хранения данных и уже «обвязанное» REST API, максимально отказаться от PHP/Python/Ruby/Java/etc, писать 80% кода «на стороне клиента», минимально заботясь о возне «на стороне сервера».

Эта статья основана на докладе Николая Самохвалова, который, в свою очередь, обобщил опыт ряда проектов, написанных на React, React Native и Swift и переходящих на парадигму noBackend за счёт PostgreSQL+PostgREST.

В конце, вы найдете список must-check-вопросов для работы с noBackend-подходом, а, если ваш Postgres-опыт позволяет, то сразу после прочтения вы можете приступить к разворачиванию безопасного, высокопроизводительного и годного для быстрого развития REST API.

Далее — расшифровка доклада Николая на Backend Conf, рассчитанного и на бэкенд, и на фронтенд разработчиков. О спикере: Николай Самохвалов больше десяти лет работает с PostgreSQL, является со-организатором российского сообщества RuPostgres.org и в данный момент помогает различным компаниям оптимизировать, масштабировать и автоматизировать процессы, связанные с эксплуатацией PostgreSQL.

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

Эпоха толстеющих клиентов

Давайте, сначала определим, что же такое толстеющий клиент и что за эпоха такая.

Знали ли вы что Ruby появился в один год с PHP, Java и JavaScript?
Любопытный факт.

Естественно, это JavaScript. Эти четыре утенка появились в один год и, на самом деле, понятно кто из них гадкий, да?

Вначале его даже назвали иначе, а потом, когда прикручивали Java, заодно решили и переименовать, и вот уже 23 года некоторые новички путают эти языки. Он был «на коленке» написан за 10 дней сотрудником, который, в рамках подготовки нового браузера Netscape, написал скриптовый язык. Далее многие годы JavaScript выполнял второстепенную роль и был для того, чтобы сделать какую-нибудь анимацию или посчитать какую-нибудь реакцию в браузере.

0, появился GMail, Google Maps и прочее, и JavaScript конечно же обрел большее значение — клиенты стали толстеть. Но постепенно, где-то с 2003-2004, началась шумиха вокруг WEB 2. Например, есть React Ecosystem для браузера и React Native для мобильных устройств. А сейчас у нас есть single-page applications и огромное количество фрэймворков на JS. В один день и на соседних улицах Сан-Франциско проходило по несколько митапов по React с аудиторией несколько сотен человек. В Силиконовой Долине React — это огромное направление, которое вовлекло огромное количество людей.

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

Ниже график из исследования RedMonk.com за первый квартал 2018 года.

Мы видим, что JavaScript побеждает в обеих номинациях,, но другие 3 утенка тоже совсем рядом: Java, вообще там приклеилась, PHP, Ruby тоже очень близко, то есть это 4 языка, которые родились в один год 23 года назад. По OY — количество тегов в вопросах StackOverflow, а по OX — количество проектов в GitHub. Практически все другие исследования тоже подтверждают, что JavaScript опережает все языки программирования.

видел, что если SQL на GitHub немножко по-другому померять, то он даже JavaScript опередит, потому что он есть в очень многих проектах, несмотря на шумиху вокруг noSQL. Я немного беспокоюсь за SQL, т.к. Но понятно, что JavaScript это такой язык, который игнорировать невозможно.

Уже четыре года назад произошло опережение мобильных устройств над десктопом. И второй аспект толстеющих клиентов. Еще в 2015 году Google сообщила, что их поиском пользуются больше на мобильных устройствах, чем на компьютерах.

Эти два аспекта приводят нас к идее, что когда мы стартуем новый проект, то нам вообще не хочется думать про бэкенд.

Конечно, если у нас много пользователей и они как-то взаимодействуют, то нам где-то нужно хранить эти данные, естественно, на сервере и как-то с этим работать. Идея такая — давайте мы вообще не будем думать про Backend. Это просто должно быть: эффективно, надежно, безопасно.

Еще важный аспект, что если у вас не новый проект, а долгоиграющий, у которого есть сайт, приложение для iOS, Android, может быть, для smartTV и даже часов, то, естественно, вам требуется Backend и универсальный АPI.

Весь зоопарк должен унифицировано взаимодействовать и либо вы используете REST API, либо GraphQL из React Ecosystem — в любом случае, вам нужно что-то такое иметь.

Как выжить

Первый вариант. Облака

Давайте, у нас вообще не будет серверов, будем целиком жить в облаках. К сожалению, такие (специализированные облачные сервисы, предлагающие разворачивание API в облаке с минимум усилий) проекты онлайн не выжили. Два ярких представителя: StackMob.com и Parse.

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

У них есть некоторые элементы noBackend-подхода, это Lambda и сервис для аутентификации/авторизации Cognito. В целом, мысль такая: если уж облака, то давайте более надежные, понятные, и которые завтра никуда не денутся, решения — это Amazon и Google.

Если же вам сильно хочется совсем noBackend для API или чего-то ещё, то вот ссылка на специализированные решения для того, чтобы своих Backend-серверов не иметь вообще, а писать сразу клиентский код.

Второй вариант. PostgREST

А теперь я говорю, а давайте все-таки будем делать по-серьезному. И тут — PostgREST. Несмотря на то, что я всячески призываю его использовать, статья будет достаточно общая.

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

Сила PostgREST

PostgREST написан на Haskell и распространятся по очень либеральной лицензии, активно развивается, а в gitter-чате можно получить поддержку. В настояший момент PostgREST дозрел до отличного состояния и «обкатан» во многих проектах..

И тут же получим API-endpoint / person, у которого можно использовать параметры для фильтрации, постраничной навигации, сортировки, соединения с другими таблицами. На слайде выше показано, как это можно запустить: v1-схема — это схема, в которой, живет первая версия нашего API, мы можем быстренько создать табличку, либо, если у нас уже есть какие-то таблички, то можем собрать «виртуальную табличку» — представление (view).

Поддерживается четыре метода: GET автоматически транслируется в SELECT для SQL, POST — в INSERT, PATCH — в UPDATE, DELETE — в DELETE.

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

Но вы можете использовать также и другие языки, в том числе PL/Python или plv8 (это JavaScript, но такой, который не может общаться с внешним миром). PL/pgSQL — основной язык для хранимых процедур и для него есть дебаггеры. Хранимые процедуры могут вызываться с помощью POST /rpc/procedure_name (тогда в теле передаем именованные параметры) или GET /rpc/procedure_name (тогда можно использовать только GET-параметры, что влечет соответствующие ограничения). То есть много разных возможностей, в том числе язык для аналитики и статистики PL/R.

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

Качество

Начнем с первого урока, который мне пришлось выучить в нескольких проектах.

Кстати, PostgreSQL — это не обязательно чисто реляционная СУБД, вы можете и JSON там хранить, некоторые так и делают даже в платежных системах, такой своеобразный NoSQL.

Когда вы начинаете строить API, надо обложиться тестами практически сразу, обязательно. И, если вы еще не используете (хотя я думаю, большинство использует) инструменты Continuous Integration, то тут обязательно нужно это сделать и обложить тестами, особенно «плохими тестами», чтобы ничего не открыть из того, что нельзя открывать наружу. Одно дело, если вы сделали суперюзеров в базе данных и ваши рубисты это используют, другое дело — все это торчит наружу и можно сделать что-нибудь нехорошее. Какой-нибудь хакер рано или поздно придет, у меня такие случаи встречались много раз.

Незадолго перед докладом (2016 год) @backendsecret проводили опрос в Твиттере, и я вообще-то ожидал, что половина будет без Continuous Integration, но на самом деле уже все было довольно круто.

Если вы в первом пункте на слайде выше, то не надо так делать, это «лузерский» подход.

REST API

Не все знают, что можно прямо в Firefox запрос, который был сделан, просто поменять там есть Edit and Resend, есть cURL, есть консольная утилита HTTPie, в которой более удобный output.

Если вы работаете с API, то вам он очень пригодится. Но главный наш инструмент — это расширение Postman для Google Chrome. Т.е. у вас есть dev-сервер, staging-сервер, production-сервер — там разные хосты, там разные логины, пароли, все это можно завести как окружение, выгрузить в файлики и дальше, с помощью newman, который как бы добавка к Postman, вызвать из консоли. Он решает ряд проблем: вы можете накидать кучу запросов, сохранить их, сделать их абстрагированными от окружения. И разместить в вашем CI для того, чтобы тесты API выполнялись автоматически при каждом изменении в проекте.

Безопасность

Мало кто любит думать про безопасность, но делать это нужно. Например, если у нас есть табличка и мы просто создаем вьюшку в PostgREST как «SELECT * FROM эта табличка», то мы создаем себе проблему безопасности. Потому что, если у юзера, под которым действует наш API, есть права, то кто угодно может туда все что угодно заинсертить, включая, скажем, чужие user_id, то есть это вообще бардак полнейший.

Если вы используете утилиты для миграции изменений DDL, то, например, в Sqitch есть возможность тестировать изменения, то есть их можно верифицировать с помощью migration-test. В таком случае необходимо разобраться с правами реляционной базы данных и давать только те права, которые нужно. И в частности, можно назначать привилегии, но рекомендую вам такой трюк (на него ведет стрелочка на слайде выше): если случится деление на ноль, определится ошибка и это поможет проверить, что у юзера не появилась привилегия на эту таблицу.

Если вы пишете тест в Postman, он автоматически выгружается, и дальше newman’ом все автоматически крутится и проверяется. С точки зрения API мы должны проверять, что API отвечает соответствующим кодом (в случае PostgREST чаще всего код будет 400). Это нужно делать обязательно, и обязательно заранее подумать, где какие двери есть и как их закрыть.

На самом деле, в вопросе безопасности может быть три уровня проблем, поговорим о каждом отдельно.

Анонимные запросы

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

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

Права на столбцы

О чем это? Если вы делаете SELECT* из таблицы «пользователь», то вы, как минимум, «светите» хэш паролей (я надеюсь, что вы храните их в хэшированном виде). Но вы «светите» и email — такого делать нельзя.

Это делается очевидным образом: при создании этой вьюшки вы перечисляете, какие поля можно читать, а не делаете «SELECT *». Если вы даете другим пользователям смотреть на список пользователей, вам как минимум стоит позаботиться о том, чтобы отсечь те столбцы, которые нельзя «светить». Но вы можете убрать право читать на какие-то отдельные столбцы. В PostgRESTе очень давно есть возможность показать таблицу анониму, если вдруг вам приспичило, и он сможет ее читать. Например, очевидно, что пользователь не должен иметь право поменять свой ID. Также можно поступить с INSERT и UPDATE и сделать это очень гибко. Права на столбцы помогают нам защитить данные, которые человек не должен иметь право менять.

Запрет доступа к «чужим» строкам

Это такой более сложный для визирования, но очень важный аспект — нельзя давать изменять чужие строчки. Т.е. если мы дали возможность апдейтить вьюшку, то по сути любой пользователь по умолчанию сможет проапдейтить и чужие строчки через API — и это беда.

А как ее закрыть? Это еще одна дверь, закрытость которой надо автоматически проверять. Если у вас более старая версия, то пишем хранимые процедуры: PostgREST делает переменную сессии (claims. Если у вас самый современный PostgREST, то используйте Row-Level Security — это наиболее предпочтительный способ. XXXXX), и мы знаем кто именно выполняет эту процедуру и можем все проверить.

Производительность

Начнем с типичного примера, допустим, у вас есть база пользователей и коллекции с постами, и допустим, что пользователей у вас миллионы и миллион блогов, и плюс еще связи между пользователями и коллекциями. На слайде ниже я отметил 4 таблицы: person, post, collection, person2collection — типичная модель любой social media и стандартная задача.

Конечно, гиганты, например, Инстаграм и Твиттер показывают нам новости не в хронологическом порядке, но в нашем примере мы будем строить хронологический порядок показов.

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

Например, выше я привел row_to_json, и тогда PostgREST вернет вам json, в который будет вложен json с информацией об авторах и коллекциях. Второй более в стиле PostgREST: написать JOIN из 4-х таблиц и дальше это все «переварить».

Но первый запрос намного хуже, потому что там 3 запроса, 3 API вызова. Но оба эти запроса плохие в плане производительности. Но совсем другое дело, когда вы делаете это через API. Когда вы пишете на Ruby/PHP/Python, то вы можете не сильно об этом беспокоиться, в некоторых СУБД удобнее сделать три быстрых и коротких SQL-запроса вместо одного. Их могло бы быть 200, а теперь 600. Представьте, что человек из другой страны, и round-trip time может быть 100-200 миллисекунд, и вот у вас уже появляются 600 лишних миллисекунд. То есть, первый вариант нужно отмести сразу — мы должны стараться все делать одним запросом, если, конечно, это возможно

Итак, первый под-урок здесь в том, что в первую очередь мы должны думать про сетевую сложность.

На слайде она сделана с помощью WHERE и использования предыдущего ID.
Кстати, если вы работаете с реляционной базой, запомните, что ни в коем случае не надо использовать OFFSET для постраничной навигации.

Если у вас большие объемы данных, то вы стопудово получите проблемы и со вторым методом, потому что он будет работать секунды, а то и десятки секунд.

Изучив ее, вы сможете освоить джедайские техники, такие как: работа с рекурсивными запросами, с массивами, сворачивание-разворачивание строки и, в том числе, подход очень экономно считывания данных Loose IndexScan. С этим столкнулось много проектов, в том числе несколько моих, и меня очень выручил известный эксперт по PostgREST, проживающий в Австралии, — Максим Богук, очень рекомендую эту презентацию его доклада на PGday.

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

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

При использовании PostgREST у вас огромное количество инструментов, чтобы увеличить производительность, вы можете использовать силу.

Масштабируемость

Первый уровень: понятно, что у нас нет сессии, RESTful подход и PostgREST можно поставить на большое количество машин и он будет спокойно работать.

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

То есть это, конечно, не ORM, а такой JSON-Relational Mapping.
Обратите внимание, вся эта тема очень похожа на Object-Relational Mapping.

В данном случае, когда вы имеете GET, то 100% это только читающая транзакция SELECT (если только это не SELECT /rpc/procedure_name, о котором упоминалось выше). Поэтому ngnix несложно настроить конфигурацию, чтобы он часть трафика направил на другой хост. Все в руках админа, который занимается ngnix. Те, кому это все пока не нужно, могут быть уверены, что в будущем вы сможете масштабировать свой проект.

Сходу — пока никак, но в Poctgres-community над этим активно работают. Вопрос: как масштабировать master?

Когда вам нужно вскопать поле, что вы предпочтете: 100 гастарбайтеров с лопатами или трактор? Еще есть философский аспект: вся эта шумиха вокруг WebScale. По сути, это трактор, который каждый год улучшают, каждый год выпускают новую версию, и он реально очень продвинутый. Сейчас PostgreSQL приближается к тому, чтобы производить на одном сервере миллион транзакций в секунду.

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

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

Что не стоит делать внутри?

Ответ очевиден! Если вы запрашиваете внешний сервер, не стоит делать этого прямо в PostgREST, потому что это займет непредсказуемое время, и не стоит удерживать бэкенд Posgres на master непонятное время.

Или реализовать очередь прямо в БД, используя очень эффективную обработку во много потоков с помощью «SELECT… FOR UPDATE SKIP LOCKED». Стоит пользоваться методами LISTEN/NOTIFY — это старая, уже давно отлаженная, штука.

Вы можете сделать скрипт на Node.js или RUBY или, на чем угодно, этот демон подпишется на события в PostgREST, а дальше хранимая процедура просто отправит сообщение в этом событии, а ваш демон его подхватит.

Для этого есть ряд решений, это тема довольно изъезженная, просто приведу несколько ссылок: Если вам хочется, чтобы события, которые вдруг какое-то время никто не слушал, не пропали, то придется поменять все системы очередей.

И наконец, обещанная памятка. Это список всего, что мы сегодня обсудили. Если вы начинаете новый проект или переделываете старый на использование внутреннего API — проходитесь по этим пунктам и будет вам счастье.

Контакты и ссылки:

Email: ru@postgresql.org
Сайт: http://PostgreSQL.support
Твиттер: https://twitter.com/postgresmen
YouTube: https://youtube.com/c/RuPostgres
Митапы: http://RuPostgres.org

На ближайшей Backend Conf Николай продолжит нести Postgres в массы, в частности, обещает презентовать новый инструмент полуавтоматического поиска узких мест по производительности, не требующего глубокого знания внутренних компонентов.

Не забудьте и о Highload++ Siberia, до летней конференции разработчиков высоконагруженных проектов менее двух месяцев — самое время бронировать билеты.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть