Pull to refresh

Comments 81

А почему именно java кстати? Исторически сложилось, или какие-то объективные причины?
какой же Одноклассники «Ынтерпрайз»?
Ну раз уж вы сослались на мою заметку, то да, в этом смысле Одноклассники — это Enterprise, потому что:

— большая команда, работающая над проектов в течении долгого времени, соотв. требования к поддержке и процессам
— большое количество (я предполагаю) исходного кода, который необходимо пересматривать, рефакторить и оптимизировать
— высокие нагрузки
Это называется high-load проект, а под enterprize я привык понимать более узкую, или, скажем, специфичную сферу (например работа в гетерогенных средах, специфичные бизнес процессы, интеграция с большим количеством сторонних продуктов, и т.п.).
Соглашусь. Мне следовало наверное сказать — «В любых крупных и долгоживущих проектах».
Да high-load практически весь тоже на ней: Twitter/LinkedIn/Amazon/Google/eBay/Facebook — всё это имеет бекенд на Java/C++. Даже наши Mail.ru/Yandex. Больше то не на чем. Front-end ещё можно на Ruby/PHP/Python встретить, хотя и тот по-тихоньку на JavaScript переползает.
>>что GC нам просто не позволит иметь Heap требуемого нам объема
А можно немного подробнее? Не пойму как это не позволит… Имеется в виду ограничения на размер Heap или что?
при больших размерах хипа (>4gb) и большого количества мелких объектов в старом поколении (что и происходит в кеше бизнес объектов ), эффективность работы GC падает. Часто до такой степени, что он не успевает подбирать мусор.
Что в свою очередб приводит к отказу concurrent gc, и паузам сборки мусора. Причем эти паузы могут быть достаточно продолжительны — по 30-60 секунд на 10-16Gb heap и вплоть до нескольких минут6 если размер хипа приближается к 90 gb. Естественно, что в течении этого времени сервер не работает.

Ну т.е. имеется в виду не «не позволит», а «будет очень не эффективно»
Да, работать будет, но почти все время будет в коме.
UFO just landed and posted this here
К сожалению, сам не проверял, но люди использующие G1 в 1.6 на 8гб хипа говорил, что не так уж и быстрее нежели CMS. На 1.7 ситуация была куда лучше.
UFO just landed and posted this here
Опять, же, 1.6 или 1.7? Сейчас, насколько я знаю, они на 1.7 и как раз с G1.
UFO just landed and posted this here
Ну значит они там что-то накосячили со своими измерениями.
пробовали. с включенным G1 jvm работает до креша где то 5 минут. пока решили экспериментов не продолжать и подождать более стабильной версии.
UFO just landed and posted this here
Креша от чего? От OOME? Или вообще? Это ничего, что у других с G1 машина часами гоняется?
Часами — это мало, многда надо что бы месяцами все работало
и шо вы такой нервный ж-)? креш был на сегфолт. на самом деле, скорее всего это зависит от паттерна нагрузки и потребления памяти. наверно у вас паттерн удачный для G1, на багу не напоролись у нас — чуть другой — напоролись.

Там дальше написано:
настроить GС, чтобы паузы были адекватны, для приложения, с которым взаимодействует пользователь, практически нереально.

Плюс, на сколько я помню, еще и concurrent mark and sweep начинает лажать и тупить серьезно на хипах больше 8Гб.
А вы не хотите им поделиться с общественностью, как это сделали например c Disruptor?

PS А вообще, спасибо за описание. По-моему самый нормальный корпоративный блог на Хабре.
Если поймем, что это будет много кому интересно, то почему бы и нет.
Ну во всяком случае его можно будет потрогать и «аппетит придет во время еды».
А azul, который обещает не делать stop the world, не рассматривался? Мне вот интересно действительно ли он может помочь на реальном большом проекте.
да и JRocket тоже что-то гарантирует в плане задержек, в общем интересна мотивация написания кучи кода, вместо смены jvm.
C JRocket, к сожалению, опыта нет, но на JavaOne в прошлом году говорили, что на больших Heap он от пауз все равно не избавит.
мотивация в том, что даже на текущий момент ява не может хранить и эффективно управлять хипом больших размеров. В JRockit используются принципиально те же алгоритмы управления памятью, что и в sun jvm, поэтому качественно ничего от смены jvm не изменится.

гарантии задержек работают только в том случае, если GC успевает чистить мусор. Если же нет — то все равно будет старый добрый stop-the-world gc.
а циферок и примеров вы нигде не видели? а то одни маркетинговые обещания и никакого хардкора.
с азулом аналогично
не совсем понял какие циферки вы имеете в виду?
Практика применения на реальных проектах. Например, инстанс при 1000 активных пользователях использовал в среднем 25 гб памяти. При этом он делал stop the world раз в пол часа на ~X секунд (далее куча цифр и графиков).
Мы взяли azul (или jrocket) и у нас стало все хорошо(или стало еще хуже), далее те же цифры и графики, но с новыми значениями.
ну я могу только про jrockit сказать, что когда мы его пробовали — никаких изменений по сравнению с sum jvm в пределах статистической погрешности не было. но была менее стабильная работа самой jvm, поэтому решили более с jrockit не продолжать.

azul — не пробовали вообще после того, как узнали цену
UFO just landed and posted this here
UFO just landed and posted this here
поскольку выигрыша по скорости не было — то и не разбирались с этим.
Единичный случай у меня был, когда CMS 12гб очищал как-то около одной минуты, к сожалению точных цифр не помню, но где-то в районе 50-70 секунд, и около 10 таки вычистил.

На 3-4гб значения были гораздо меньше, макс вроде бы около 3-х секунд было.
Когда писался описанный фреймворк, azul еще не предлагал своего решения в этом плане. К тому же он платный и требует другую jvm. Текущее решение вполне удовлетворяет, поэтому в данный момент не рассматриваем.
Azul — это интересная многообещающая технология. К сожалению у нее есть существенный минус — ну очень высокая цена. Поэтому область ее применения пока ограничена финансовым рынком.
Может, кто-нибудь напишет пост про него? Мне интересно, как можно вообще поверх JVM написать фреймворк, гарантирующие отсутствие задержек больше чем скажем X миллисекунд, если только они не переписали/серьезно пропатчиkи GC внутри hotspot.
как мне известно, Азул — это не надстройка, а наоборот, полностью своя JVM с менеджментом памяти и GC собственной разработки.
а патчили они кернел линукса чтобы позволить jvm более близко работать с железом для реализации read & write barriers, которые он и использует для организации доступа к перемещенным при компактировании хипа областям памяти
Хотя это и не может не сказываться на дефрагментации памяти, зато мы обошлись без сложного алгоритма управления памятью.

А вы не делали измерений насколько высока степень фрагментации?
По идее, для приложения такого уровня используя примитивную схему malloc-free фрагментация памяти может нести катастрофический характер. Зависит от однородности объектов, конечно, но все равно «энтропия растет».

Кроме того интересна производительность этого фрейворка. Если я правильно понял, то освобождение и выделение памяти происходит пообъектно. Тогда, имея многогигабайтный off-heap, получаются миллионы вызовов ОС для работы с памятью, что обычно нельзя не заметить (выделение одного и того же объема памяти одним куском или кучей маленьких по производительности может отличаться на порядки).
Ну у нас все же в основном операции чтения и реже апдейта поля, при котором с большой долей вероятности не надо будет malloc-free делать.
Степень фрагментации в большинстве случаев небольшая — так как объекты статистически одного размера. Был один случай, когда размеры были сильно разные — тогда приходилось делать выравнивание по размеру.

производительность самого фрейворка достаточна для того чтобы ее не замечать. поскольку кеш сетевой, типа memcached, то большинство тормозов происходит при пересылке данных по сети.
А кстати, memcached не пробовали при выборе решения?
Welcome to Habrahabr 12 лет, Москва

Ооок :)
надо было писать печатными буквами, разборчиво. ж-)
У вас не очень понятно из описания, что именно вы храните вне кучи? Модельки с данными и коллекциями? Как хранятся связанные записи?

И не проще выделять память большими пулами, каждый из которых разбит на N записей одинакового размера, запись на поля, где хранятся сырые значения полей (без объектов)? Плюс хеш-таблица, позволяющая найти запись по id или еще чему-нибудь. И соответственно, читать/модифицировать эти значения. Или это неподходящее решение?

И да, это вот

> Нам будет также очень интересно узнать о вашем опыте решения проблем пауз GC на больших объемах heap.

выглядит как троллинг :)
вне кучи хранятся модельки с данными и коллекциями, аватарки и маленькие фотки, списки дружб итп. бизнес объекты. связанные объекты хранятся в зависимости от необходимости. Если это master-detail, то тут же рядом с объеком и хранится, если association — то присто ид сущности и за сущностью или отдельный запрос делается или из кеша достается одним запросом. зависит от необходимости.

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

> выглядит как троллинг :)
нет, просто есть еще 1 способ, но мы о нем никому не расскажем ж-)
А аватарки — что, статику выгоднее тем же ява-монстром отдавать, или просто исторически сложилось, что они в базе хранятся?
аватарки — не совсем статика, они все таки меняются. хранятся они в хранилище, на дисках. раздаточные сервера их кешируют в памяти.

раздаются тем же томкатом, разницы никакой нет чем раздавать на самом деле — все равно упираются они в сетевой интерфейс.
А как часто наступали такие паузы за сутки и с каким минимальным интервалом?
Это было так давно, что вряд ли уже сможем поднять какую-то статистику. Сейчас при разработке любого функционала, мы сразу понимаем, в каком случае у нас могут возникнуть проблемы с GC и сразу делаем компонент с off-heap хранилищем, так что сейчас сложно сказать.
Просто мне вспомнилась история (не помню откуда я это слышал), что в какой-то из компаний когда детектилось, что GC начнет сбор мусора, все запросы перенаправлялись на репилку, сервер перегружался, запросы снова с реплики на основной сервер перенаправлялись.

Если кто-то вспомнит источник, буду благодарен, поскольку слышал это от третего источника. Так сразу не удалось нагуглить.
UFO just landed and posted this here
Oracle Coherence опять же совсем не дешевый и не open-source, что затрудняет с ним работу. К тому же, на сколько я знаю, off-heap конфигурация в Oracle Coherence появилась 2,5 года назад, нам же потребовалось решать проблемы с GC немного раньше.
да смотрели. из memory data grid это по моему мнению самый лучший продукт. к сожалению цена его также для наших размеров довольно высока, так как количество серверов изменяется сотнями. в нашем случае экономически эффективнее разработка собственного решения.
Какие вы скромные, у нас под несколько тысяч нод coherence — главное с Oracle договориться о скидке, на что они идут довольно охотно.
Если не трудно, расскажите, в чём была проблема с флагами
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
Они обычно создают нагрузку на процессор, но держат память в норме. Проверено на порядка 12-16ГБ оперативной памяти.
А какое у вас железо и сколько секунд паузы на минорных сборках и stop-the-world фазах CMS?
У нас все таки в памяти хранятся десятки гигабайт, используется commodity железо и CPU отдыхает.
да, у нас тоже есть сервера под CMS и 10-16 Г хипа, но тут больше зависит от количества объектов, а не от размера хипа. После некоторого предела ситуация начинает резко ухудшаться. В кешах бизнес объектов этот предел достигается быстро.

Да и по опыту могу сказать, что написать такое хранилище у меня недавно заняло с отладкой 2-3 дня. Настройка CMS под задачу тоже по времени сравнимо с этим. Но в итоге решение с Unsafe работает стабильнее и меньше подвержена внезапным поломкам в процессе эксплуатации.

Пишу диплом на такую тему.
Пробовал ByteBuffer, но отказался в пользу Unsafe из-за проблем с разметкой памяти. Мне показалось, что операционная система должна лучше справиться.

Возможность обновления и выборки отдельных полей объекта — отличная возможность, сам не заметил почему-то.

Расскажите поподробнее про ключи и выборки.

Еще интересует ваш подход к сериализации — вы используете заранее описанную логику для каждого класса? Или смотрите скелет класса в рантайме и по нему кладете данные (позволяет работать с произвольными объектами)?
Ключом может быть любой java объект. У нас это обычно лонги, они все же в хипе лежат, большие объекты в виде ключей, могут свести на нет все усилия освобождения heap. Как на картинке нарисовано, ключи лежат по сути в обычной хешмапе, а значение — адрес в памяти процесса вне хипа.

Для сериализации используем отдельно описанную логику. Эта логика так же позволяет делать выборку и апдейты отдельных полей по сдвигам от изначального адреса.
Насчет ключей, я интересовался, какой функциональный смысл они несут — ведь можно использовать обычные лонги, которые возвращает allocateMemory() и подобные методы.

Логика сериализации — имеется ввиду обычные путы/геты с оффсетом для каждого класса?
Просто есть возможность работать с произвольными классами, читая в рантайме карту класса. Под картой я имею ввиду бинарное представление. Но тут уже не будет удобного статического API для работы с полями.
ключи в кеше — это Ид записей в БД. Поэтому напрямую использовать адрес в памяти не получится. Кроме того этот адрес может измениться после например реаллокации памяти или рестарта кеша. Поэтому приходится иметь в памяти соответствние ИД -> адрес записи в памяти.

Про сериализацию — да, есть дерево компонентов, которые для класса описывают его сериализацию в память. Они в конечном итоге и занимаются пут по адресу с офсетом.
То есть, по сути, несколько лет назад искали удаленный и распределенный кеш (remote & distributed), который мог бы держать ноды большого размера. Локальные что in-memory, что off-heap кеши это не вариант для проектов такого масштаба, можно было не упоминать. Так зачем вообще Java для сторадж нод?
Тем, более, как правильно написано в статье, в Java на тот момент зрелых off-heap стотержей не было, а зрелых и опен-сорсных нет и до сих пор. Азул тоже не подходит по цене, и несколько лет назад он работал только на собственном железе, даже не на виртуалках.
Очевидным решением выглядело бы взять Memcached или что-то вроде того, да и не мучаться.

Действительно ли были нужны инкрементные апдейты и фильтрация? Как оно было-то, на самом деле, в плане мотивации внутренней разработки? )
Проект большой, в том числе есть задачи которые можно решать локальным off-heap кэшом. Выборочные апдейты, выборочное чтение и фильтрация действительно много используются. К тому же если есть логика тесно связанная с данными, почему бы ей не находиться в том же процессе на удаленных распределенных кэшах.
То есть действительно были такие требования изначально? То что эти фичи можно использовать, если уж они есть, это понятно.
Кстати, near-кеши на хипе клиентов во фреймворке есть?
Ну просто так мы ничего стараемся не делать. При первоначальной разработке этого решения мы рассматривали несколько вариантов, в том числе и мемкеш. Задачи поддержания консистентности на нем сильно усложняются, так как он не ведает, что хранит; повышенный трафик, так как приходится гонять бинарные массивы от него к клиентам и назад; необходимость разработки нового фейловер кластера; отсутствие сохранения данных между рестартами и, как следствие, проблема холодного старта — вот несколько причин, по которым он не подошел.

Да, неар кеши тоже есть, естественно. Профили пользователей, например, кешируются в том числе и не серверах приложений на короткий промежуток времени. Эти уже на хипе работают, так как объекты короткоживущие.

Отлично, спасибо за ответ. Еду дальше с вопросами.

Теперь интересует как все это хозяйство работает в распределенном режиме, т.е. что в статье было названо как шардинг. Судя по тому, что я понял, партиционирования данных, как это сделано в дата гридах, нет, есть пары нод, держащих одинаковые данные. В рамках пары данные одинаковые (апдейты идут на обе ноды), между парами распределяются. Плюс у вас чтение идет на обе ноды в паре, что хорошо и больше ни у кого нет, насколько я знаю (зато это порождает второй вопрос в списке ниже). То есть очень близко к тому, как это сделано в Terracotta Server Array, да и вообще это классическая схема. Вопросы:
— общаются ли ноды между собой по какой либо причине? поддерживаются ли соединения и если да, то для чего.
— как обеспечивается косистентность данных между нодами в паре. допустим, клиентское приложение записало данные на ноду A, но не записало в A'. предположим, сетевая проблема была. как обрабатываются такие ситуации?
— что происходит с данными, когда ноды 1) приходят в кластер, 2) уходят из кластера
— как роутятся запросы. видимо, за роутинг ответственна клиентская либа. тут интересно, 1) как определяется номер ноды, откуда читать данные по ключу в шардированном режиме. и 2) как идет запрос с фильтрацией — вытаскиевается все по ключам и затем за O(n) фильтруется, или есть распределенные индексы?

Пока все. я потом обязательно расскажу, с какой целью интересуюсь.
Поняли в общем правильно все.
Ответы ниже по пунктам:
1. Нет, соединения между самими серверами не поддерживаются.
2. Если апдейт с клиента не прошел по какой то причине — то измененные данные все равно попадут в кеш чуть позже через процесс синхронизации с Бд ( на картинке обозначен как synchronization). так что в итоге расхождение будет длиться не более 2 секунд
3. Когда нода стартует, она загружает данные из локального снапшота на диске. Затем запускается процесс синхронизации в БД, который подтягивает все изменения, прошедшие за время пока нода не работала. Ну а потом она собсно входит в кластер и клиенты начинают с ней работать.
4. Да, запросы рутит клиентская либа. Если запрос простой — по ид — то она, зная топологию кластера, определяет шард на основе ИД запрошенной записи и вызывает одну из реплик, по принципу взвешенного RR. Если же запрос не имеет определенного ид — то используется MapReduce — параллельно вызываются несколько шард, согласно маппера и потом результат сливается редюсером на клиенте. Фильтрация происходит на стороне инстанса кеша — на клиента приходят уже отфильтрованные данные. Индекс внутри инстанса кеша может использоваться, если это необходимо.
Олег, забыл я про эту вкладку. Заканчиваю с вопросами.

2. о как, то есть у вас AP система, в отличие от типичного дата грида.
3. тут я имел в виду немного другое — допустим, есть работающая продашкен система, и мы хотим ее капасити увеличить на ходу, т.е. добавить сторедж нод. как у вас это происходит. вопрос здорово связан со следующим.
4. ок, спасибо за описание. а как именно происходит меппинг ключа в шарду — консистент хешинг или какая-то статическая таблица?

К чему я всем этим интересовался. Как тут заметили где-то выше, сейчас на рынке нет ни одного нормального бесплатного off-heap стореджа. У вас, правда, есть ограничения — только value там храните плюс реализация не на основе public API. Но все равно отлично.

Не хотите ли вы это дело заопенсорсить — или кешинговую систему целиком, или отдельно выпилить off-heap? Мне кажется, было бы очень неплохо технологически пропиарить российскую компанию. Достаточно посмотреть как Terracotta продвигает свою BigMemory. Тут есть все варианты и международное признание получить.
2. Да, мы сейчас вообще в эту сторону двигаемся с максимально возможной скоростью. При таком количестве серверов выбора среди трех букв уже нет.

Про шарданг отвечу сначала. Все пространство ключей делится на 256 частей (берется младший байт ИД). Это число у нас называется «доменом». В системе кешей при конфигурации кластера описываются партиции интервалами доменов. Типа 0-15 => G1, 16-31 => G2 итп. Каждую партицию могут обслуживать несколько серверов, это тоже указывается в конфиге кластера. Все сервера в пределах 1 партиции являются полными репликами друг друга.

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

Если же необходимо разбить одну из партиций на 2, то делается в общем то же самое, только переконфигурация кластера посложнее будет.

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

Спасибо за статью, познавательно.

А почему не стали использовать memcached?
UFO just landed and posted this here
Да кстати возник такой же вопрос, исходя из CAP теоремы, хранить данные вместе может заставить только требование согласованность данных и доступности.
это не совсем удобно — в 8 раз больше соединений, усложняется конфигурация и администрирование. плюс 2 jvm на одном боксе работают хуже чем одна, но под двойной нагрузкой.
Sign up to leave a comment.