Как стать автором
Обновить

Комментарии 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. Естественно, что в течении этого времени сервер не работает.

Ну т.е. имеется в виду не «не позволит», а «будет очень не эффективно»
Да, работать будет, но почти все время будет в коме.
НЛО прилетело и опубликовало эту надпись здесь
К сожалению, сам не проверял, но люди использующие G1 в 1.6 на 8гб хипа говорил, что не так уж и быстрее нежели CMS. На 1.7 ситуация была куда лучше.
НЛО прилетело и опубликовало эту надпись здесь
Опять, же, 1.6 или 1.7? Сейчас, насколько я знаю, они на 1.7 и как раз с G1.
НЛО прилетело и опубликовало эту надпись здесь
Ну значит они там что-то накосячили со своими измерениями.
пробовали. с включенным G1 jvm работает до креша где то 5 минут. пока решили экспериментов не продолжать и подождать более стабильной версии.
НЛО прилетело и опубликовало эту надпись здесь
Креша от чего? От 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 — не пробовали вообще после того, как узнали цену
НЛО прилетело и опубликовало эту надпись здесь
падает чаще.
НЛО прилетело и опубликовало эту надпись здесь
поскольку выигрыша по скорости не было — то и не разбирались с этим.
Единичный случай у меня был, когда 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 начнет сбор мусора, все запросы перенаправлялись на репилку, сервер перегружался, запросы снова с реплики на основной сервер перенаправлялись.

Если кто-то вспомнит источник, буду благодарен, поскольку слышал это от третего источника. Так сразу не удалось нагуглить.
НЛО прилетело и опубликовало эту надпись здесь
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?
ответил в предыдущем комменте
НЛО прилетело и опубликовало эту надпись здесь
Да кстати возник такой же вопрос, исходя из CAP теоремы, хранить данные вместе может заставить только требование согласованность данных и доступности.
это не совсем удобно — в 8 раз больше соединений, усложняется конфигурация и администрирование. плюс 2 jvm на одном боксе работают хуже чем одна, но под двойной нагрузкой.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий