Pull to refresh
250.34
Rating
Конференции Олега Бунина (Онтико)
Конференции Олега Бунина

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

Конференции Олега Бунина (Онтико) corporate blogPostgreSQLSystem Analysis and DesignDatabase AdministrationData storage
Название статьи не стоит понимать буквально: backend никуда не делся, просто фокус разработки — особенно на начальном этапе развития нового проекта — сильно смещается в сторону «клиентской части». Появляется большой соблазн взять что-то понятное для хранения данных и уже «обвязанное» REST API, максимально отказаться от PHP/Python/Ruby/Java/etc, писать 80% кода «на стороне клиента», минимально заботясь о возне «на стороне сервера».

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

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



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

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


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


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

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



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



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

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

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

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



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

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

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



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



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

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



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

Как выжить


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


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



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

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

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

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


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



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

Сила PostgREST


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



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

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

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

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

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

Качество


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

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

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



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



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

REST API


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



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

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


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



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

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

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

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


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



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

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


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



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

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


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



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

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


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



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

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

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

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

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

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

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



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

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



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

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

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


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

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

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

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

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



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

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

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

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


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



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

Вы можете сделать скрипт на 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, до летней конференции разработчиков высоконагруженных проектов менее двух месяцев — самое время бронировать билеты.
Tags:nobackendpostgresqlpostgrestrest api
Hubs: Конференции Олега Бунина (Онтико) corporate blog PostgreSQL System Analysis and Design Database Administration Data storage
Total votes 50: ↑46 and ↓4+42
Views31K

Top of the last 24 hours

Information

Founded
Location
Россия
Website
www.ontico.ru
Employees
11–30 employees
Registered

Habr blog