Комментарии 41
Вы совершенно точно зря на себя наговариваете, всё получится!
шучу, открывай
а я наоборот...
обратите внимание, что весь профайлинг шел средствами языка (трейсинг и профайлинг), все генериурется "почти" стандартными средствами
плюс втягивает шаг за шагом к пониманию работы железа... описанная задача не рядовая, а на условном хайлоаде большого продукта, не многим это нужно, хотя описанные шаги очень показательные
от прочтения статьи испытал кайф от выбора языка
А почему не использовали какой нибудь Memcached/Redis? Вероятно это было бы гораздо лучшее решение в плане расхода памяти вцелом т.к. кеш не дублировался бы между всеми экземплярами сервиса. Скорее всего производительность также была бы выше т.к. не было бы необходимости в сборке мусора.
Когда строгая консистентность не требуется — мемори-кеширование выгоднее
2) Подойдет ли для решения вашей проблемы продукт типа tarantool, где бизнес логику можно прямо внутри писать?
2. Не подойдет, но тем не менее, его мы используем в других задачах. Не подойдет потому, что нас придется тогда обучать всех разработчиков работе с tarantool, когда у нас все разработчики уже знают Go. Это первый момент. Второй момент — опять же загрузка сети.
Второй момент — сервис запущен в нескольких экземплярах на физически-разных машинах (повышаем отказоустойчивость). Отсюда только одна реплика сервиса работает без сетевого оверхеда.
Третий момент k8s — он базово не гарантирует что после раскатки новой версии сервис не окажется на другой машине (на самом деле почти наверняка окажется).
2) Вероятно вы предлагаете использовать tarantool как application вместо GO. Tarantool не сильно cloud native. Готовить его так, чтобы ты мог в любой момент изменить количество реплик сервиса, и не терять стейт при перевыкатке или disaster крайне не просто. Ну и в итоге — мы получим все то-же inmemory хранилище как и в Go. Просто несколько более оптимизированное.
По поводу пункта 1. Под в k8s может содержать более одного контейнера и при желании можно написать деплоймент так чтобы рядом с каждым экземпляром сервиса поднимался контейнер с кешом.
Но вцелом мне все таки интереснее мой первоначальный вопрос:
Есть ли статистика hit/miss и как долго отдельное значение живет в кеше?
Условный пример:
У вас есть 3 экземпляра сервиса и у каждого независимый кеш. В ходе работы какого-то бизнес процесса происходит 3 обращения к вашему сервису для получения значения. В дальнейшем в течении длительного времени это значение не используется. Для данного примера это означает что кеш абсолютно неээфективен т.к. изза лоад балансера запросы попадут на разные сервисы и во всех 3-х случаях это вызовет дорогую операцию вычисления значения.
Но тогда мы получим независимые кеши на каждой реплике а это не будет принципиально отличаться от memory кеширования в самом GO. Только больше CPU потратим.
Есть ли статистика hit/miss
Вы правы, говоря что каждый из 3-х реплик будет иметь независимый кеш. И что hit\miss считать нужно, дабы понимать эффективность всей этой истории.
И у нас она есть, и мы ее мониторим.
Но в целом — статья про работу планировщика в GO и нюансы профилирования, а не про
Само профилирование не то чтобы сильно сложная тема. В Idea в несколько кнопок делается и рисует схемы буквально как у вас в стать. И это уже было много много лет назад. А решение проблемы по сути просто одно библиотеку на другому поменяли, в принципе могли бы и сразу посмотреть в коде библиотек на способ хранения.
Но лишний раз память освежить не помешает, так что за статью – спасибо!
Не всегда. Чаще всего наоборот in-memory предпочтительнее.
В случае с редис/мемкеш — добавляется оверхед на коннект-маршаллинг-анмаршаллинг-сеть
Плюс если говорить про ресурсы — то сеть чаще всего дороже как ресурс чем память на отдельный железках. При масштабировании упереться в сеть гораздо проще.
Да нужно учитывать минусы in-memory — дублирование кешей. Разные поды могут содержать разные по актуальности данные. Обновление протухших кешей повлечет за собой N запросов а не 1 в случае с редисом например.
> Скорее всего производительность также была бы выше
вот тут в корне не согласен. производительность ин-мемори в подавляющем большинстве случаев будет намного выше. Отсутствие сетевого лага. маршаллинга и анмаршаллинга.
П.С. конкретно в Авито утилизация сети достигает порой 70-80%.
производительность ин-мемори в подавляющем большинстве случаев будет намного выше.
Зависит от конкретной задачи. Здесь как видим внезапно выяснилось что кеш работает настолько ужасно что даже сборщик мусора не справляется..
Автор не предоставил еще одну ключевую метрику без которой сложно о чем то судить:
Какой вообще общий процент кеш хит/мисс? Я подозреваю что если сборщик мусора постоянно занят уборкой вытесненных значений то кеш вообще не эффективен и просто тасует данные туда-сюда. Это также косвенно следует из того что время ответа сервиса упало весьма незначительно (в 1,8 раз) для эффективного кеша можно было бы ожидать улучшения на порядок и более.
И чудовищная неэффективность — чисто из-за того что кешируем много маленьких объектов, и порождаем тонну указателей
Первое, что приходит в голову, — добавить кэш для того, чтобы не ходить лишний раз в сервис.
Неееет! Первое что должно приходить в голову — какого этот сервис не может за 300мс выполнить свою работу? И только после ответа на этот вопрос — кеши и ещё ещё что-то.
Вообще непонятно зачем все эти сложности? Есть юзер, у него есть верифицированный номер телефона. Есть объявление, у него тоже есть номер телефона, который либо anonymous, либо call tracking, либо берётся из данных юзера. Система решает этот вопрос в момент создания объявления. Есть отдельный сервис — пул телефонов, который связан с АТС и выдаёт номер нужного типа, в момент создания объявления. Больше к нему никто не обращается, нагрузки особой на него нет. Кажется все. И тут не видно всех тех сложностей, о которых пишут в статье, типа сервисы работы с телефонами тормозят, а их ещё и несколько штук…
Может я чего-то не понимаю, но архитектура выглядит криво.
Неееет! Первое что должно приходить в голову — какого этот сервис не может за 300мс выполнить свою работу? И только после ответа на этот вопрос — кеши и ещё ещё что-то.
Это было известно с самого начала, но специально не упомянуто тут, т.к. выходит за рамки статьи. Сервис медленный, т.к. сделан на php и работает с внешними сервисами, которые выделяют номера. За тот сервис отвечала другая команда, но в процессе оптимизации phones-gateway, о котором и рассказано в статье, наша команда также оптимизировала тот сервис, но переписывать его на Go было не в рамках нашего пула задач, и сильно оптимизировать мы не смогли, сейчас ситуация с ним значительно лучше, т.к. его переписали и оптимизировали.
Вообще непонятно зачем все эти сложности? Есть юзер, у него есть верифицированный номер телефона. Есть объявление, у него тоже есть номер телефона, который либо anonymous, либо call tracking, либо берётся из данных юзера. Система решает этот вопрос в момент создания объявления. Есть отдельный сервис — пул телефонов, который связан с АТС и выдаёт номер нужного типа, в момент создания объявления.
Тут у нас уже появляются требования Роскомнадзора и других органов. Номер телефона пользователя является персональными данными пользователя и хранится в отдельной таблице от объявления в виде зашифрованных данных и получение этих данных происходит через специальный сервис-хранитель перс. данных. Более того, в вашей модели не учтен момент, что пользователь может захотеть поменять номер телефона и поменять его так, чтобы он поменялся сразу на всех объявлениях.
Больше к нему никто не обращается, нагрузки особой на него нет. Кажется все. И тут не видно всех тех сложностей, о которых пишут в статье, типа сервисы работы с телефонами тормозят, а их ещё и несколько штук…
Тут еще дело в том, что этими сервисами заведуют разные команды, которые решают разные бизнес задачи, поэтому есть дифференциация на разные типы номеров для переадресации, и они преследуют разные цели. Также, реальный номер телефона может потребоваться получить в админке или еще в каких-то сервисах, по рассылке СМС к примеру. Поэтому архитектура на деле оправдана и обусловлена требованиями бизнеса.
cache []int64 — не массив, а слайс.
cache [1024]int64 — это уже массив, иммутабельный.
Из-за этой мелочи читать статью становиться очень проблематично…
неплохо расписано про работу с профилировкой
sql задач вроде не стоит. Но на пустом месте ловим io нагрузку+сериализация — десериализация.
delete from tbl where expired_at<...
Если не нравиться sqlite, то это может быть любая доступная встраиваемая бд или просто голый движок хранилища.
Но кажется вся идея k8s как раз про то, чтобы достаточно плотно напихивать сервисы в кластер.
А прибить к сервису пачку ядер, которые и не будут утилизироваться в 50% времени, и все-равно не решить задачу из статьи…
Но кажется вся идея k8s как раз про то, чтобы достаточно плотно напихивать сервисы в кластер.
Всё же больше про управление, масштабирование и отказоустойчивость. Напихивание — уже по желанию.
А прибить к сервису пачку ядер, которые и не будут утилизироваться в 50% времени
Чтобы избежать этого, имеет смысл использовать HPA. В случае же заданий (job) подобной проблемы не стоит вовсе.
и все-равно не решить задачу из статьи…
Никто этого и не обещал.
все вместе будет требовать определенного тюнинга каждого сервиса.
и доп тюнинга по мере развития проекта. ну и всегда есть шанс словить маркетинговую акцию и тупить пока развернуться до ядра.
В общем — это полезная штука, но использовать надо очень даже с умом.
Завис немного в самом начале на первой реализации обновления массива
if c.Has(userId) && status == 6 {
c.Delete(userId)
}
if !c.Has(userId) {
c.Add(userId)
}
разве в случае удаления мы не будем всегда возвращать удалённое значение обратно т.к. !c.Has(userId) после удаления будет true?
Все рекомендации из The Twelve Factor App — действительно толковые.
Но не стоит их воспринимать и следовать им с библейской точностью.
Кеш, липкие сессии и прочее в среднем нарушают ряд факторов, это верно.
И за нарушение надо платить. К примеру меньшей консистентностью. Но если для бизнес-задачи оно не критично, но позволяет повысить надежность в разы, снизя утилизацию ресурсов…
Почему-бы и нет?
Как вы выкатываете новые версии вашей программы? Если кеши хранятся in-memory, то выходит что при деплое новой версии все хеши обнуляются. Те на момент деплоя аппы у вас на сайте кнопка показать телефон начинает подтормаживать. Для вас это нормально?
Сначала поднимается одна новая реплика. Она прогревает кое-какой кеш. И только после этого она помечается как Redy, и заменяет одну старую.
Ничего в процессе не подтормаживает.
Оптимизация микросервиса на Go на живом примере