company_banner

Как мы избавились от пауз GC с помощью собственного java off-heap storage решения

    Привет, Хабр!

    Некоторые системы просто не могут давать адекватный отклик без кэширования данных. Причем рано или поздно они могут наткнуться на проблему, что данных, которые хотелось бы кэшировать, становиться все больше и больше. Если ваша система написана на java, то это приводит к неизбежным паузам GC. Когда-то Одноклассники тоже столкнулись с этой проблемой. Мы не хотели ограничивать себя в размере кэшируемых данных, но в то же время понимали, что GC нам просто не позволит иметь Heap требуемого нам объема. С другой стороны, мы хотели продолжать писать на java. В этом топике мы опишем, как решили эту проблему для себя со всем плюсами и минусами нашего подхода, а также опытом использования. Надеемся, что наш подход заинтересует тех, кому приходится бороться с паузами GC.



    Обоснование выбранного решения


    В обзорной статье про архитектуру проекта мы уже упоминали, что для кэширования, а так же хранения некоторых данных, используем фреймворк собственной разработки. Он написан полностью на Java, но данные хранит вне кучи (off-heap). У вас наверняка возник вопрос: зачем нам понадобился такой нестандартный подход с off-heap, и почему мы не использовали готовое решение?

    Очевидно, что самое эффективное хранилище (в частности кэш) — то, которое всегда находится в оперативной памяти и доступно процессу приложения без какого-либо сетевого взаимодействия. Для приложений, написанных на java, вполне разумным является реализация хранилища тоже на языке программирования java, так как пропадает проблема интеграции. К тому же любой программист, работающий над системой, может легко разобраться в тонкостях его работы, посмотрев исходники. Но так как при большом heap практически любая java программа начинает страдать от пауз GC, то при суммарном размере данных в несколько гигабайт и выше, система становится недееспособной (если только весь Heap это не просто один большой массив байтов). Настройка GC — задача не из тривиальных. Если же ваш heap уже содержит десятки гигабайт, то настроить GС, чтобы паузы были адекватны, для приложения, с которым взаимодействует пользователь, практически нереально. А так как большая оперативная память становится все более и более доступной, то подход с java off-heap кэшом приобретает все больше и больше популярности.

    Когда-то давно данный подход действительно можно было посчитать нестандартым. Но, посмотрите, сейчас все больше продуктов появляется и развивается в этом направлении. Около полутора лет назад на рынке появилось первое решение такого рода: BigMemory от Terracota. Полгода назад Hazelcast объявил о том, что они выложили на alpha-тестирование продукт под названием Elastic Memory. Но, к сожалению, он до сих пор в beta стадии, да к тому же, как и BigMemory, будет платным. Cassandra около полугода назад начала использовать кэширование записей вне кучи (off-heap row cache). Но это кэш для внутренних целей, который не так просто выковырять, чтобы использовать у себя. Крепкого open-source продукта в этой области попросту пока нет. Однако стоит заметить, что осенью в инкубатор apache попал проект DirectMemory. Правда, когда ждать готового продукта и когда он себя зарекомендует в крупных проектах, вопрос открытый. Мы же начали использовать свое решение больше четырех лет назад, ничего готового, как вы видите, в то время попросту не было.

    Реализация


    Общение java приложения с памятью вне кучи может быть реализовано несколькими способами. Одноклассники в свое время выбрали подход, основанный на использовании класса sun.misc.Unsafe, который входит в приватный пакет HotSpot, а сейчас и OpenJDK. Данный класс позволяет работать напрямую с памятью, без явного использования JNI, что оставляет наше решение кроссплатформенным, и мы можем запускать одни и те же бинарники как на основной системе, так как и на компьютере разработчика, причем не важно — сидит ли разработчик под Windows, Ubuntu или Mac. Чтобы не писать сложный алгоритм управления памятью, как, например, решили сделать в DirectMemory, мы для каждого объекта, который хотим положить в хранилище или удалить из него, выделяем или освобождаем память отдельно. Пусть управлением памятью занимается операционная система, у неё это очень хорошо получается. Именно по причине того, что нам нужно часто выделять и освобождать память, мы не используем стандартный класс java.nio.ByteBuffer, который находится в составе JDK, имеет открытое API и позволяет работать с памятью вне кучи. Проблема с ByteBuffer в нашем случае в том, что он создает дополнительный мусор и не позволяет напрямую освобождать память, а делает это на основе фантомных ссылок. Последнее ведет к тому, что, выделив много памяти вне кучи, она не может быть освобождена, пока не сработает GC, даже если объекты ByteBuffer, выделившие эту память, уже не используются. Хотя для решения этой проблемы есть флаг -XX:MaxDirectMemorySize=, который инициирует сборку мусора при достижении выделенной памяти вне кучи заданного значения, все равно большое количество фантомных ссылок может негативно повлиять на GC. Данное поведение ByteBuffer скорее всего объясняется тем, что он проектировался для выделения больших объемов памяти и их переиспользования. Мы же используем противоположный паттерн работы. Конечно, отказ от ByteBuffer заставил нас делать некоторые вещи самостоятельно, как то, например, заботиться об byte order.

    У пытливого читателя может возникнуть вопрос: не стесняемся ли мы использовать приватные механизмы HotSpot (sun.misc.Unsafe) для нашего функционала, ведь они могут поменяться или исчезнуть в один прекрасный момент. Нет, нисколько. Ведь эта самая HotSpot новой версии не окажется у нас в рабочих версиях неожиданно, т.е. мы будем к этому готовы. Все, что мы используем из Unsafe, использует уже упомянутый ByteBuffer. Т.е. если в очередной версии что-то изменится, нам будет достаточно заглянуть в исходники и сделать такие же изменения в нашем фреймворке, а точнее всего в одном классе, отвечающем за выделение/освобождение памяти и сохранение данных.

    Возможности нашего фреймворка


    Нет ничего сложного в том, чтобы сериализовать объект в массив байтов и поместить его вне кучи. Чем же еще нам, в Одноклассниках, помогает фреймворк?

    Гранулированность апдейтов. Для того, чтобы положить объект в наше хранилище, необходимо описать его структуру, указав, как надо сохранять каждое поле. Наш фреймворк позволяет сохранять структуру объекта, состоящего из любых примитивов, массивов, коллекций и составных объектов. Каждому полю объекта должен быть поставлен в соответствие наш библиотечный алгоритм описания сохранения и обновления. Есть возможность создать новый или расширить существующий, если вдруг появится такая потребность. Данный декларативный подход позволяет делать точечные обновления полей, а не зачитывать объект целиком, десериализовать его, делать обновление, сериализовать и сохранять обратно. Так же можно делать различные view объектов в хранилище. Так, если какому-то сервису не нужны все данные хранимого объекта, он может запросить и считать только часть данного объекта. Еще это сильно помогает при фильтрации. Например, когда вы хотите вытащить объекты по списку идентификаторов, но только те, которые удовлетворяют какому-нибудь критерию.

    Read-through. Некоторые из наших хранилищ помогают снять нагрузку с базы, т.е. используются в качестве кэшей. Для такого случая у нас есть опция read-through. Если в кэше нет данных, которые запрашивает клиент, то мы идем в БД и читаем их. Здесь у нас есть еще некоторые хитрости, например, если придет два одинаковых запроса одновременно, то в БД мы пойдем только один раз, а второй запрос просто дождется результата первого. Это очень помогает в случае холодного старта. Или если клиент постоянно запрашивает данные, которых в кэше нет, то через какое-то количество попыток мы перестаем обращаться к БД за этими данными.

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

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

    Клиентская библиотека. Если хранилище становится очень большим и не помещается полностью на наше commodity железо, то мы разносим его по нескольким машинам и используем шардинг по ключу. Причем делаем мы это не из-за возрастающих пауз GC, а именно из-за ограничения железа. Конечно, тут теряется вся прелесть in-process кэша и появляется сетевое взаимодействие, но тут уж ничего не поделаешь. Зато остаётся наш старый знакомый код, целиком написанный на нашей любимой Java. К тому же какая-то логика может тоже быть легко вынесена на эти отдельные боксы, так как CPU там все равно простаивает. Наша клиентская библиотека так же позволяет обновлять или получать только требуемые поля объектов из хранилища, что значительно уменьшает трафик. Для отказоустойчивости и масштабируемости, мы используем дублирование таким образом, что за каждую шарду у нас отвечает как минимум два хоста. Запросы на чтение идут по round-robin алгоритму, апдейты идут на все ноды.

    Недостатки и вынужденные компромиссы


    Конечно же, в нашем фреймворке есть и недостатки. Например, по причине того, что ключи мы храним все же в куче, в какой-то момент GC может начать сказываться на производительности приложения. Мы стараемся не использовать ключи больше Long, поэтому даже на наших объемах данных мы вполне удовлетворены текущими характеристиками.

    Как уже упоминалось, в отличие от некоторых существующих решений, мы отдельно аллоцируем память для каждого объекта. Хотя это и не может не сказываться на дефрагментации памяти, зато мы обошлись без сложного алгоритма управления памятью.

    Чтобы эффективно уметь делать гранулированные обновления, нам приходится отдельно аллоцировать память для каждого поля переменной длины (такое как строка или коллекция). Таким образом, для создания одного объекта иногда мы делаем несколько низкоуровневых вызовов.

    Использование внутри Одноклассников


    Используя вышеописанный фреймворк, мы храним профили пользователей, профили групп, метаинформацию по фотографиям, информацию по классам (like), статистику действий пользователя по отношению к своим друзьям и группам, куски ленты и кое-что еще. Например, распределенное хранилище статистики, на основе которой считаются веса пользователей для попадания в ленту, содержит около 300Гб данных. Максимальный же размер данных в памяти хранилища на одном боксе у нас достигает 90 Гб. Средняя нагрузка на 2ух процессорную железку с 4 мя ядрами на каждом в пике составляет около 20K запросов в секунду.

    Стоит особо отметить, что наше хранилище ни в коей мере не служит нам заменой RDBMS или чем-то вроде NoSQL решения. Чаще мы его используем просто в качестве кэша для базы данных. Возможно, в некоторых местах использования мы могли бы перейти на NoSQL вместо существующей связки кэш плюс RDBMS, но это требует сильного пересмотра бизнес-логики, которая может опираться на транзакционность, обеспечиваемую RDBMS.

    Таким образом, описанный фреймворк позволяет нам писать весь код на java, хранить в heap такой объем данных, который требует задача, не иметь головной боли с настройками GC, быстро внедрять хранение или кэширование новых данных.

    Нам будет также очень интересно узнать о вашем опыте решения проблем пауз GC на больших объемах heap.
    Одноклассники
    209,00
    Делимся экспертизой
    Поделиться публикацией

    Комментарии 81

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

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

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

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

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

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

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

                                      azul — не пробовали вообще после того, как узнали цену
                                      • НЛО прилетело и опубликовало эту надпись здесь
                                          0
                                          падает чаще.
                                          • НЛО прилетело и опубликовало эту надпись здесь
                                              0
                                              поскольку выигрыша по скорости не было — то и не разбирались с этим.
                                        0
                                        Единичный случай у меня был, когда CMS 12гб очищал как-то около одной минуты, к сожалению точных цифр не помню, но где-то в районе 50-70 секунд, и около 10 таки вычистил.

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

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

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

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

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

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

                                          И да, это вот

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                                                          А почему не стали использовать memcached?
                                                            0
                                                            ответил в предыдущем комменте
                                                          • НЛО прилетело и опубликовало эту надпись здесь
                                                              0
                                                              Да кстати возник такой же вопрос, исходя из CAP теоремы, хранить данные вместе может заставить только требование согласованность данных и доступности.
                                                                +1
                                                                это не совсем удобно — в 8 раз больше соединений, усложняется конфигурация и администрирование. плюс 2 jvm на одном боксе работают хуже чем одна, но под двойной нагрузкой.

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

                                                              Самое читаемое