Авито
интерьер офиса
полиграфия
IT
ОБЩАЯ ПЛОЩАДЬ 4 200 кв.м.
АДРЕС ул. Лесная, 7/9, БЦ Белый
ДИЗАЙН 2015 – 2016
СТРОИТЕЛЬСТВО 2016
Над дизайн-проектом нового офиса Avito Yacht работали архитекторы архитектурного бюро ABD. Часть площадей на 10-м этаже отведена под рабочее пространство в формате open-space. Рабочие зоны, каждая из которых выполнена в голубых, красных, желтых и зеленых тонах, разделены стеклянными и мобильными флипчартными стенками, а также двухъярусными мягкими выдвижными ящиками, напоминающими трюм корабля. Для отдыха и общения персонала предусмотрены лаунж-зоны и укромные рабочие места. Для деловых встреч и конференций предусмотрены небольшие переговорные комнаты, названные по именам знаменитых островов (Врангеля, Котлина и др.).
15-й этаж многофункционального здания. Здесь есть столовая с просторным обеденным залом, конференц-залы, конференц-залы и несколько рабочих помещений.
Основная идея интерьера новой части офиса Avito родилась легко: коллектив Avito молодой, все увлечены спортом и путешествиями, поэтому тема интерьера была очевидна – Яхтинг.
Идея яхты вдохновила на множество оригинальных дизайнерских решений, причем все они носят не только декоративный характер, но и функциональны. Выигрышной идеей стало, например, использование солнцезащитной пленки, которая используется для смотровых иллюминаторов яхт, для матирования стеклянных стен. Через светоотражающую пленку снаружи практически не видно, что происходит внутри конференц-зала, а изнутри все видно прекрасно. Характерный для корпусов яхт белый глянец наносится маркерной краской в канцелярии. В результате можно рисовать маркером на большинстве стен.
Офис спланирован по принципу деятельностного проектирования. Это означает, что для всех рабочих ситуаций и процессов, будь то интенсивная консультация проектной группы, целенаправленная работа одного сотрудника или конфиденциальная беседа тет-а-тет или что-то еще, архитекторы из ABD architects предусмотрели соответствующую зону и созданы удобные условия. Большое внимание уделялось командной работе: планировочные и мебельные решения способствуют общению и сотрудничеству сотрудников. Имеется множество удобных и разнообразных внутренних зон для групп от двух-трех до двенадцати человек, что примерно соответствует количеству проектных бригад. Все конференц-залы и открытые зоны общения оформлены по-разному, ни одна из них не похожа на другую. Здесь есть гамак от двухкорпусного корабля, спасательная скамейка-кольцо для мозгового штурма, трюм, каюта подводной лодки и переговорная «спина боцмана» со стенами цвета загара, украшенными морскими татуировками, и многое другое.
Девиз Авито «Усердно работай – усердно играй» воплощен в жизнь буквально – помимо эффективных рабочих зон, здесь есть просторный тренажерный зал с раздевалкой и душевыми, бильярдная, уютная комнатка с массажным креслом, игровая комната с кикером и игровой приставкой, импровизированный скалодром, серьезно оборудованные «кухни» с напитками и закусками и просторная полноценная столовая, обслуживающая всю большую команду. В рабочей зоне есть несколько капсул для сна, где сотрудники Авито могут отдохнуть или поработать в тишине. Вы можете лежать или сидеть внутри. Есть мягкий матрас, вентиляция и свет. Капсулы расположены в два яруса и в духе морской тематики называются «торпедными аппаратами» — вход в каждую капсулу имеет характерную круглую форму.
Очень серьезное внимание в офисе уделяется акустике. Напряженные дискуссии, игра в бильярд, занятия в спортзале не должны мешать целенаправленной работе коллег. Таким образом, потенциальные источники шума локализуются планировочными средствами, связанными со специальными конструкциями полов и стен. В некоторых зонах в качестве звукопоглощающего материала используются настоящие морские канаты.
Конференц-центр расположен на одном из этажей: группа просторных переговорных комнат с мобильной мебелью и впечатляющим медиа-оборудованием позволяет проводить встречи, тренинги, презентации. Рядом с переговорными комнатами есть удобный зал, где можно пообщаться до и после встречи. Раздвижные перегородки позволяют объединить переговорные комнаты, холл и расположенную рядом столовую в одно огромное пространство, где можно проводить мероприятия на сотни гостей. Чтобы изменить зал столовой, вам не нужно выносить столы. Как и на яхте, специальные лебедки поднимают паруса, лебедки поднимают столы к потолку в столовой, освобождая место для вечеринок и других мероприятий.
Как работать с Postgres в Go. Использование Postgres из Голанга в… | Артемий Рябинков | AvitoTech
Когда приложение, использующее базу данных, демонстрирует какое-то неожиданное поведение, это разжигает священную войну между администраторами баз данных и разработчиками: администраторы баз данных кричат: « Ваше приложение приводит к сбою базы данных! », а разработчики кричат в ответ: « А ведь до этого все работало просто отлично! ». Хуже всего то, что администраторы баз данных и разработчики толком помочь друг другу не могут: первые не знают нюансов работы приложения и особенностей драйвера, вторые не знают всех темных уголков инфраструктуры.
Как вы могли догадаться: довольно часто недостаточно просто пролистать go-database-sql.org. Лучше вооружиться чужим опытом. Еще лучше, если опыт получен через боль и потерянные деньги.
Основы работы практически с любой базой данных SQL в Go можно найти на сайте go-database-sql.org. Если вы еще этого не читали — пожалуйста.
С моей точки зрения, главная сила Go — его простота. Например, эта простота проявляется в обычной практике написания запросов на чистом SQL (ORM не приветствуется). Это оказывается и благом, и источником дополнительных трудностей.
Итак, когда вы берете пакет database/sql из стандартной библиотеки, вы хотите расширить его интерфейсы. Как только это произойдет, загляните на github.com/jmoiron/sqlx. Я покажу вам несколько примеров того, как это расширение может облегчить вашу жизнь.
Использование StructScan позволяет избежать ручного сопоставления столбцов <->
Использование NamedQuery позволяет использовать поля структуры в качестве заполнителей в запросе.
Использование Get и Select позволяет устранить глупые циклы выборки строк базы данных.
database/sql — набор интерфейсов доступа к базе данных, а sqlx — их расширение. Для работы интерфейсов нужна реализация. Водители несут ответственность за реализацию.
Самые популярные драйвера:
- github.com/lib/pq — чистый драйвер Go Postgres для базы данных/sql. Долгое время этот драйвер был стандартным по умолчанию. В настоящее время она потеряла свою актуальность и не разрабатывается ее автором.
- github.com/jackc/pgx — драйвер и инструментарий PostgreSQL для Go. Сегодня этот инструмент является лучшим выбором.
github.com/jackc/pgx — вы действительно хотите использовать этот драйвер. Почему? Он активно развивается и поддерживается.
- Он может быть более производительным, если используется без интерфейсов базы данных /sql .
- Поддерживает более 60 специфических для Postgres типов (дополнительные, которые Postgres имеет в дополнение к стандартным типам SQL).
- Предоставляет возможность регистрировать все, что происходит внутри драйвера.
- pgx содержит удобочитаемые ошибки, а lib/pq вызывает панику. Если не поймать панику, программа рухнет. (Кроме того, не используйте паники в Go, как вы бы использовали исключения в других языках; они совершенно разные.)
- С помощью pgx у нас есть возможность настроить каждое соединение независимо.
- Поддерживает протокол логической репликации PostgreSQL.
Обычно для извлечения данных из базы данных можно написать следующий цикл:
Внутренне драйвер получает данные и накапливает их в буфере размером 4 КБ . Круговой обход сети и заполнение буфера происходит на строк. Вызов Next() . Если буфера размером 4 КБ недостаточно для завершения выборки, из сети извлекается следующий пакет данных. Чем больше сетевых отключений, тем медленнее становится скорость обработки. С другой стороны, поскольку ограничение буфера составляет 4 КБ, мы не будем использовать всю доступную память.
Но, конечно, мы хотели бы максимально увеличить емкость буфера, чтобы минимизировать количество сетевых вызовов и снизить задержку нашего сервиса. Итак, добавим такую опцию в драйвер и попробуем измерить ожидаемый прирост скорости с помощью синтетических тестов:
Очевидно, существенной разницы в скорости обработки нет. Но почему?
Как оказалось, мы ограничены размером буфера отправки данных внутри самого Postgres. Этот буфер имеет жестко заданный размер 8 КБ. Использование strace мы видим, что ОС возвращает 8192 байт в системном вызове чтения. tcpdump подтверждает это размерами пакетов.
Том Лейн ( один из основных разработчиков ядра Postgres) комментирует это так:
Традиционно, по крайней мере, таков был размер конвейерных буферов в Unix-машинах, так что в принципе это самое оптимальное размер фрагмента для отправки данных через сокет Unix.
Андрес Фройнд ( разработчик Postgres из EnterpriseDB ) считает, что буфер размером 8 КБ в настоящее время не является лучшей реализацией, и должны быть проведены тесты производительности с другими размерами и другими конфигурациями сокетов.
Кроме того, следует помнить, что у PgBouncer тоже есть буфер и его размер можно настроить параметром
Еще одна опасная особенность драйвера pgx ( v3 ): при каждом установленном соединении он отправляет запросы в базу данных для получения информации об идентификаторе объекта ( OID ).
Эти идентификаторы были добавлены в Postgres для уникальной идентификации внутренних объектов: строк, таблиц, функций и т. д.
Драйвер использует знания об OID, чтобы выяснить, как отображать данные из типов столбцов базы данных в примитивные типы Go. Для этой цели pgx внутренне использует следующую карту (ключ — имя типа, значение — идентификатор объекта )
Эта реализация заставляет драйвер отправлять 3 запроса для каждого подключения к базе данных, чтобы заполнить таблицу идентификаторами объектов.
Если база данных и приложение работают нормально, пул соединений Go позволяет избежать создания новых соединений с базой данных. Однако в случае малейшей деградации базы данных пул соединений истощается, и скорость соединений увеличивается экспоненциально. Запросы на получение OID довольно тяжелые, и в результате драйвер может привести базу данных в критическое состояние.
Вот момент, когда в одну из наших баз данных было залито таких OID-запросов:
15 транзакций в минуту в обычном режиме, до 6500 транзакций в минуту при снижении производительности БД.Что мы можем с этим поделать?
Прежде всего: устанавливает верхнюю границу размера пула соединений .
Для базы данных /sql это можно сделать с помощью функции DB.SetMaxOpenConns. Если пропустить интерфейсы database/sql и использовать pgx.ConnPool (пул соединений , реализованный самим драйвером ), то в ConnPoolConfig можно указать MaxConnections ( 5 по умолчанию ).
Кстати, при использовании pgx.ConnPool драйвер будет повторно использовать информацию о полученных OID и не будет извлекать OID из базы при каждом новом подключении.
Если вы все равно хотите использовать базу данных /sql , вы можете кэшировать информацию об OID самостоятельно.
Этот метод работает, но может быть опасен в следующем случае:
- Вы используете enum или доменные типы в Postgres;
- В случае сбоя мастера вы переключаете приложение на логическую (а не физическую) реплику.
В случае аварийного переключения в этих условиях кэшированные OID становятся недействительными. Но очистить их мы не можем, так как приложение не может знать момент аварийного переключения.
В мире Postgres потоковая репликация обычно используется для реализации высокой доступности, которая копирует экземпляры базы данных побитно, поэтому проблемы с кэшированием OID редко можно наблюдать в дикой природе. ( Но все же лучше сходить пообщаться с администраторами баз данных и уточнить, как именно работает режим ожидания в вашем конкретном случае ).
В следующей основной версии драйвера pgx (v4) запросы на получение OID будут удалены. Таким образом, новый драйвер v4 будет полагаться только на список OID, предопределенный в коде. Для пользовательских типов вам придется взять управление десериализацией в свои руки на стороне приложения: драйвер просто предоставит массивы байтов, полученные из БД.
Мониторинг и ведение журнала помогают предотвратить проблемы до сбоя базы данных.
база данных/sql предоставляет метод DB.Stats(). Возвращаемый моментальный снимок состояния дает вам представление о том, что происходит внутри драйвера.
Если вы используете пул в pgx напрямую, метод ConnPool.Stat() предоставит вам аналогичную информацию:
Ведение журнала не менее важно, и pgx позволяет нам это сделать. Драйвер принимает интерфейс Logger, и реализовав его, вы сможете получать все события, происходящие в драйвере.
Скорее всего, вам даже не придется реализовывать этот интерфейс самостоятельно. pgx из коробки имеет набор адаптеров для наиболее популярных логгеров, например: uber-go/zap, sirupsen/logrus, rs/zerolog.
Почти всегда при работе с Postgres вы будете использовать пул соединений, и это будет PgBouncer.
Вы можете освежить в памяти причины этого в этой блестящей статье brandur.org/postgres-connections. Вкратце, если количество клиентов превышает 100, производительность снижается. Это происходит из-за особенностей реализации на стороне Postgres: запуск выделенного процесса для каждого соединения, механизм создания моментальных снимков и использование общей памяти для взаимодействия — все эти факторы имеют значение.
Вот тест различных пулов соединений:
И тест пропускной способности с PgBouncer и без него.
В результате ваша инфраструктура будет выглядеть так:
Где Сервер — это процесс в нашем приложении, который обрабатывает запросы API. Этот процесс выполняется в Kubernetes . Отдельно на физическом аппаратном сервере стоит Postgres , перед ним PgBouncer в качестве пула соединений. PgBouncer 9Сам 0048 однопоточный, поэтому запускаем несколько инстансов, балансируя идущий к ним трафик с помощью HAProxy.
В результате сетевой обход нашего запроса состоит из следующего: пул соединений приложений → HAProxy → PgBouncer → Postgres.
PgBouncer может работать в 3-х режимах:
- Пул сессий — каждая сессия получает выделенное соединение и закрепляется за сессией на все время ее жизни.
- Объединение транзакций — соединение закреплено за отдельной транзакцией. Как только транзакция завершена, PgBouncer забирает соединение и отдает его другой транзакции. Этот режим позволяет очень эффективно использовать соединения.
- Пул операторов — устаревший режим. Он был создан только для поддержки PL/Proxy.
Вы можете взглянуть на следующую матрицу доступности функций. Обычно мы выбираем Пул транзакций , но он имеет ограничения при работе с Подготовленные отчеты .
Предположим, мы хотим подготовить запрос, а затем выполнить его. В какой-то момент мы запускаем транзакцию с нашим оператором PREPARE, а затем получаем идентификатор подготовленного оператора из базы данных.
После этого в любой другой момент запускаем другую транзакцию, в которой пытаемся выполнить подготовленный оператор по ID.
В режиме Transaction Pooling две транзакции могут выполняться в двух разных соединениях, но Оператор с идентификатором действителен только в пределах одного соединения . Мы получим ошибку « подготовленный оператор не существует », если попытаемся выполнить подготовленный оператор.
Самое смущающее, что при малой нагрузке при разработке и тестировании PgBouncer часто переиспользует одно и то же соединение и все работает корректно. Однако, как только мы разворачиваем приложение в продакшн, запросы начинают падать с ошибкой.
Теперь попробуйте найти какие-либо подготовленные операторы в этом коде:
Там его не увидишь! Подготовка оператора происходит неявно в методе Query() . При этом и подготовка, и выполнение запроса будут происходить в разных транзакциях, и мы получим все последствия, описанные выше.
Первый и самый простой вариант — перевести PgBouncer в режим Session pooling . В этом режиме за одну сессию выдается одно соединение, все транзакции стартуют в этом соединении и подготовленные запросы работают корректно. Но эффективность использования соединения оставляет желать лучшего. Поэтому мы не можем рассматривать этот вариант.
Второй вариант — создавать запросы на стороне клиента. Я не хочу этого делать по двум причинам:
- Потенциальные уязвимости SQL. Разработчик может не правильно очистить запросы или вообще забыть это сделать.
- Экранирование параметров запроса приходится каждый раз прописывать вручную.
Еще один вариант — явно обернуть каждый запрос в транзакцию. Ведь пока транзакция жива, PgBouncer не прерывает связь. Это работает, но помимо многословия в нашем коде мы также получаем довольно много сетевых вызовов: Begin, Prepare, Execute, Commit . Всего 4 сетевых вызова на один запрос. Задержка растет.
Однако мы хотим безопасности, комфорта и эффективности. И такой вариант существует! Вы можете явно указать драйверу, что хотите использовать режим Simple Query . В этом режиме никакой подготовки не будет и весь запрос пойдет в одном сетевом вызове. При этом драйвер сам экранирует параметры ( standard_conforming_strings следует активировать либо на уровне базы данных, либо при установлении соединения ).
Следующие проблемы связаны с отменой запроса на стороне приложения.
Посмотрите на этот код. Можете ли вы найти какие-либо подводные камни?
В Go есть механизм управления потоком выполнения программы — context.Context. В этом коде мы передаем драйверу ctx , чтобы при закрытии контекста драйвер отменил запрос на уровне базы данных.
В этот момент мы рассчитываем сэкономить ресурсы, отменив запросы, на которые никто больше не ожидает ответа. Однако при отмене запроса PgBouncer 1.7 отправляет в это соединение информацию о том, что соединение готово к использованию. Только после этого возвращает соединение в пул.
Такое поведение PgBouncer сбивает с толку драйвер, который мгновенно возвращает « ReadyForQuery» при отправке следующего запроса. В результате мы ловим множество неожиданных ошибок ReadyForQuery.
Начиная с версии PgBouncer 1.8 это поведение исправлено. Используйте последнюю версию PgBouncer .
И на самом деле, несмотря на то, что ошибки исчезнут, какое-то непонятное поведение все равно может происходить.
Изредка приложение может получить неожиданный ответ не на запрос, который оно отправило, а на «соседний» . Запросы должны совпадать по запрашиваемому порядку столбцов и типам.
Это происходит из-за обработки запросов на отмену на разных уровнях: на уровне пула водителей и пула вышибалы.
Но самое интересное то, как работает отмена запроса.
Чтобы отменить запрос, мы должны установить новое соединение с базой данных и отправить запрос на отмену. Для каждого соединения Postgres создает отдельный процесс. Отправляем команду на отмену текущего запроса в конкретном процессе, указав ID процесса (PID).
Тем не менее, пока команда отмены летит в базу данных, запрос, который мы хотим отменить, может завершиться нормально.
Postgres выполнит команду и отменит текущий запрос в указанном процессе. Но этот текущий запрос уже будет другим, а не тем, который мы хотели отменить в первую очередь.
Такое поведение Postgres при работе с PgBouncer делает безопаснее не отменять запрос на уровне драйвера. Вы можете установить функцию CustomCancel , которая не отменит запрос, даже если контекст. Используется контекст .