Comments 68
64ГБ памяти позволяют легко закешировать картинки средствами ОС при использовании sendfile. Почему такой вариант не подошел, он медленнее?
+1
А наружу торчит HTTP REST API? Если да — вы могли бы на таком же железе настроить nginx + memcached и оценить его производительность?
0
Скорее всего мемкеш будет самым медленным решением. Медленее ФС кеша, который медленее in-process кеша.
0
Nginx была идея использовать, но потребовалось бы дописать клиентский модуль для похода в хранилище, ведь все свое ПО разрабатываем на Java. А memcached даже не рассматривался, т.к. out-of-process и будет заведомо медленнее.
0
Latency возрастёт, но, возможно, при этом машина сможет обрабатывать больше запросов в секунду. Вы сейчас во что нибудь упираетесь или просто больше нагрузки сейчас нету?
0
У memcached нет причин обрабатывать больше запросов:
— аллокация у него медленнее (SLAB vs. pointer increment);
— поиск медленнее (hash + linear search vs. hash + binary search);
— копирование данных медленнее (socket i/o vs. memcpy).
Сейчас ни во что не упираемся — во время эксперимента весь трафик смайликов и подарочков прокачивался через один сервер. CPU при этом был загружен на 50%.
— аллокация у него медленнее (SLAB vs. pointer increment);
— поиск медленнее (hash + linear search vs. hash + binary search);
— копирование данных медленнее (socket i/o vs. memcpy).
Сейчас ни во что не упираемся — во время эксперимента весь трафик смайликов и подарочков прокачивался через один сервер. CPU при этом был загружен на 50%.
+1
То есть вы пришли к выводу, что горлышко бутылки — это запросы к [memcached|...], а не NFS?
0
в статье нет ошибки?
3000 Mb/s это точно 3 тысячи мегабайт в секунду?
3000 Mb/s это точно 3 тысячи мегабайт в секунду?
0
А sun.misc.Unsafe#allocateMemory и sun.misc.Unsafe#freeMemory не подошли бы? Вроде как принимают на вход long и возвращают адрес.
0
Зачем на каждый сегмент по локу?
Сколько потоков одноврменно пишут и читают?
Сколько потоков одноврменно пишут и читают?
0
Concurrency: одновременное использование кеша в сотне потоков;
10% из потоков пишут.
10% из потоков пишут.
0
а почему не сделать 256 локов, распределить их на все сегменты?
0
а почему не сделать 128 или 256 или N локов, распределить их на все сегменты?
0
В первой версии был единый лок на весь кеш — и он тут же стал узким местом — пришлось сегментировать. Всего порядка 100 потоков.
0
А сколько в сумме все картинки занимают (я понял, что больше 64Г — но насколько)?
+1
Всего смайликов c подарками ~1.5 TB (500 GB x 3 replication factor)
+1
В таком случае интересно, если их все положить на диск (ну и докладывать по мере поступления новых, конечно) и отдавать nginx-ом через sendfile как статику, насколько это будет медленнее?
0
Насколько медленнее — не замерял, но прежде всего с количеством запросов не справится. Сейчас смотрю на один из наших blob-storage серверов с 4мя дисками — он обрабатывает в пике около 2 тыс. запросов в секунду. При этом IO Utilization составляет 20%. Т.е. при 10 тыс. запросов сервер загнется на дисковом I/O. Наш же download обрабатывает 70 тыс. запросов, и при этом остается еще приличный запас.
0
Может, blob storage часто мимо дискового кэша промахивается? А в картинках-подарках, насколько я понял из статьи, вероятность кэш-попадания значительно выше. Если это так, то эксперимент-сравнение должно быть провести легко — достаточно поставит nginx на точно такую же машина, настроить его, а потом забомбить запросами к одному и тому же статическому файлу: если даст 70К rps, значит, все то же самое (чем забомбить с такой интенсивностью только вопрос).
0
А вот, кстати, и чем «забомбить» выложили:
roem.ru/2012/07/23/addednews51657/
roem.ru/2012/07/23/addednews51657/
0
всё что выше дискового кэша менее 60-150 картинок в секунду в идеальном случае помноженное на x 3 replication factor
0
Никогда супер нагруженными системами не занимался, всегда хватало стандартных средств. Но вещь интересная, плюс заметил у вас вакансию java developer — пошел готовить резюме. :)
0
Наверное глупый вопрос, но все таки, я правильно понимаю, что на реализациях JVM отличных от Oracle/Sun это не будет работать?
0
Молодцы!
Вы рушите мою светлую веру в превосходство nginx над любым велосипедом! Язычники!!!
Вы рушите мою светлую веру в превосходство nginx над любым велосипедом! Язычники!!!
0
Хорошее решение — чувствуется серьезный подход к делу. А комментарии к топику завистливые — всякий норовит докопаться к чему-нибудь.
0
А с доступом к файлу в памяти какое решение в итоге использовали — первый способ (который легальный, но полноценный) или второй (с использованием «тайных знаний»)?
0
Попробуйте уйти от традиционных файловых операций и посмотреть в сторону ZFS, которая умеет кэшировать часто востребованные данные в памяти, используя собственные алгоритмы кэширования и управления памятью в зависимости от потребностей приложений. Ожидается, что тандем «ZFS + Java приложение» будет использовать оперативную память гораздо эффективнее, чем традиционные механизмы ручного управления разделяемой памятью.
0
Любая файловая система делает это (кеширование в зависимости от потребностей приложений). Не могли бы вы пояснить, почему вы ожидаете, что ZFS тут будет лучше?
0
Потому что ZFS очень агрессивно кэширует запрашиваемые данные, в отличие от традиционных файловых систем. При этом память под кэш выделяется динамически, при этом автоматически занимается почти вся доступная память. Запускаемые приложения, естественно, тоже требуют своё, и ZFS в этом случае оперативно освобождает кэш — память, когда ZFS кэш разогрет, всегда занята на 100% под кэш и приложения, конечно, если не выставлены ограничения (через sysctl-переменные управления).
При использовании традиционных файловых систем на сервере всегда остаётся какой-либо незанятый кусок памяти. Его приходится утилизировать всякими способами — это даётся очень трудоёмко и сопряжено с малопроизводительным ручным трудом прикладных программистов. Зачем делать одно и то же по-новому на прикладном уровне, если автоматическое управление всей доступной памятью уже реализовано на уровне ZFS и операционной системы? Попробуйте и сравните оба подхода.
При использовании традиционных файловых систем на сервере всегда остаётся какой-либо незанятый кусок памяти. Его приходится утилизировать всякими способами — это даётся очень трудоёмко и сопряжено с малопроизводительным ручным трудом прикладных программистов. Зачем делать одно и то же по-новому на прикладном уровне, если автоматическое управление всей доступной памятью уже реализовано на уровне ZFS и операционной системы? Попробуйте и сравните оба подхода.
0
А вы можете к Apache Java Caching System патч прислать, чтобы эта система не хуже вашего подхода работала? Или патча не нужно, нужно просто правильно настроить?
0
ну не совсем ясно зачем тут content addressable storage. Все картинки у нас адресуются в хранилище однозначно по цифровому ИД. К тому же сомнительно что кто то из CAS сможет работать с такой скоростью — просто из-за более сложного и ненужного для данной задачи алгоритма.
Или вы предлагаете вместо blob storage использовать какой то CAS solution?
Ну а если хочется заниматься крутыми штуками — используйте e-mail job@odnoklassniki.ru по назначению ;-)
Или вы предлагаете вместо blob storage использовать какой то CAS solution?
Ну а если хочется заниматься крутыми штуками — используйте e-mail job@odnoklassniki.ru по назначению ;-)
0
мне вспомнился CAS по той причине, что он решает те же самые задачи:
— адаптирован к частным get/нечастым put
— производительный get по ид
— имеют большой собственный кеш и перечисленные требования по LRU, FIFO, Concurrency и Persistence решаются cas-коробкой
впрочем, отвечая на собственный вопрос, могу предположить, что стоимость цас-решения могла быть существенно выше предложенного.
и спасибо за адрес :)
— адаптирован к частным get/нечастым put
— производительный get по ид
— имеют большой собственный кеш и перечисленные требования по LRU, FIFO, Concurrency и Persistence решаются cas-коробкой
впрочем, отвечая на собственный вопрос, могу предположить, что стоимость цас-решения могла быть существенно выше предложенного.
и спасибо за адрес :)
0
Я вот не эксперт в низкоуровневых делах, а выясняли ли вы — обеспечивает ли архитектура x86 синхронизацию доступа к памяти? Я имею в виду следущее: вот вы засинхронизировали доступ потоков записи и чтения через самодельный ReadWriteLock, а реально ли после записи поток чтения получит консистентные данные в случае многоядерного процессора? То есть Java Memory Model это гарантирует, но ведь тут вы обращаетесь к памяти уже напрямую.
Реализовать ReadWriteLock через Semaphore видимо можно создав семафор со счетчиком равным числу потоков в пуле (N). При этом каждый поток чтения будет запрашивать по единичке, а поток записи — сразу N единиц. Так?
Реализовать ReadWriteLock через Semaphore видимо можно создав семафор со счетчиком равным числу потоков в пуле (N). При этом каждый поток чтения будет запрашивать по единичке, а поток записи — сразу N единиц. Так?
+1
Все так. С той лишь оговоркой, что семафор должен быть справедливым (fair), дабы избежать зависания (starvation) потока-писателя.
Примитивы синхронизации в Java, в том числе ReentrantReadWriteLock и Semaphore, при захвате и освобожении обеспечивают полный memory barrier, в частности, все записи памяти в одном потоке (неважно, чем они сделаны, записью в поле объекта или через Unsafe) будут видны в другом потоке.
Примитивы синхронизации в Java, в том числе ReentrantReadWriteLock и Semaphore, при захвате и освобожении обеспечивают полный memory barrier, в частности, все записи памяти в одном потоке (неважно, чем они сделаны, записью в поле объекта или через Unsafe) будут видны в другом потоке.
0
А вот вы упомянули 8-мь экземпляров Tomcat. Получается у вас кэш все равно находится в другом процессе (и в другой JVM) и вы все равно должны будете воспользоваться каким-либо механизмом IPC?
В таком случае единственное что отличает от MemCached например, то что вся умная логика по инвалидации и обработке спрятана в сам процесс кэша? Т.е. у меня сомнение — будет ли какой-либо выигрыш вашего подхода относительно MemCached например при измерении производительности в комплексе, а именно с учетом IPC вызовов?
Или я не понял насчет 8-ми томкэтов чего-либо?
В таком случае единственное что отличает от MemCached например, то что вся умная логика по инвалидации и обработке спрятана в сам процесс кэша? Т.е. у меня сомнение — будет ли какой-либо выигрыш вашего подхода относительно MemCached например при измерении производительности в комплексе, а именно с учетом IPC вызовов?
Или я не понял насчет 8-ми томкэтов чего-либо?
0
Не так. Раньше smile download сервис обслуживался 8-ю серверами с Tomcat. На время эксперимента весь трафик вместо этих серверов был направлен на один единственный сервер, про который идет речь в статье, и он выдержал с хорошим запасом. HTTP-сервер, blob storage клиент и кеш работают в одном Java-процессе.
0
Итак много букв:
1. Вы так активно бросились оптимизировать под процессор, что стало интересно: как ваши оптимизации сочетаются с тем что у вас всего 16 железных обработчиков потоков (без HT 8 штук)? Просто вы упомянули что у вас 100 потоков, и любопытно понять как они вмещаются в 16 исполнителей? Переключения вас не страшат или много потоков в ожидании висят?
2. Можно предположить что есть четыре базовых стратегии для ReadWriteLock: справедливая (по времени прихода) без разделения на read/write, приоритет на read, приоритет на write, случайная. Судя по вашим заявлениям у вас обычная справедливая стратегия. Почему так? Вы проверяли другие варианты? Может быть приоритет на read повысит производительность?
3. Судя по всему у вас у каждой картинки есть уникальный числовой key. Вы его генератором фигачите? Просто меня смутила немного функция определения сегмента. Она или слишком простая, или вы для упрощения не стали приводить полную функцию. К примеру можно придти к выводу что возможно у вас перекос по нагрузке на сегменты, а часть сегментов занимает память и не используется (не исключаю что на ваших объемах таких проблем нет, но вдруг). У вас накапливается статистика по оптимальности функции определения сегмента по ключу?
4. Еще был вопрос почему вы не используете inmemory базы данных? Было бы довольно любопытно сравнение их с вашим решением. Например почему бы не взять какое нибудь nosql решение? Также из-за предвзятости разработчиков решения, я бы доверил настройку других решений для сравнения, другим людям (которые специалисты в них). Надеюсь вы поступили именно так, а то так эти цифры только для улучшения самолюбия ваших инженеров.
В целом спасибо за обзор. Было любопытно читать.
1. Вы так активно бросились оптимизировать под процессор, что стало интересно: как ваши оптимизации сочетаются с тем что у вас всего 16 железных обработчиков потоков (без HT 8 штук)? Просто вы упомянули что у вас 100 потоков, и любопытно понять как они вмещаются в 16 исполнителей? Переключения вас не страшат или много потоков в ожидании висят?
2. Можно предположить что есть четыре базовых стратегии для ReadWriteLock: справедливая (по времени прихода) без разделения на read/write, приоритет на read, приоритет на write, случайная. Судя по вашим заявлениям у вас обычная справедливая стратегия. Почему так? Вы проверяли другие варианты? Может быть приоритет на read повысит производительность?
3. Судя по всему у вас у каждой картинки есть уникальный числовой key. Вы его генератором фигачите? Просто меня смутила немного функция определения сегмента. Она или слишком простая, или вы для упрощения не стали приводить полную функцию. К примеру можно придти к выводу что возможно у вас перекос по нагрузке на сегменты, а часть сегментов занимает память и не используется (не исключаю что на ваших объемах таких проблем нет, но вдруг). У вас накапливается статистика по оптимальности функции определения сегмента по ключу?
4. Еще был вопрос почему вы не используете inmemory базы данных? Было бы довольно любопытно сравнение их с вашим решением. Например почему бы не взять какое нибудь nosql решение? Также из-за предвзятости разработчиков решения, я бы доверил настройку других решений для сравнения, другим людям (которые специалисты в них). Надеюсь вы поступили именно так, а то так эти цифры только для улучшения самолюбия ваших инженеров.
В целом спасибо за обзор. Было любопытно читать.
+1
1. Потоки ждут в epoll_wait(). Некоторые висят на запросе в хранилище. С трафиком справилось бы и меньшее число потоков, но так мы уменьшаем максимальное время отклика. И 100 потоков — это не 10 тысяч, накладные расходы на переключение незаметны.
2. Справедливая, чтобы не могла возникнуть ситуация «голодания» записывающего потока. Сравнивали производительность со случайной стратегией — никакой разницы.
3. Генератор простой, и хеш простой. Про любой хеш-функции будут перекосы из-за того, что одни картинки более популярны, другие менее. Но это нестрашно: время полного обновления сегментов составляет несколько часов. Память в кеше используется в среднем на 99.4%.
4. Очень даже используем, но здесь не та ситуация. Напомню одно из требований к кешу — in-proccess. Сравнить с Memcached или Tarantool/Box можно, но смысл? См. habrahabr.ru/company/odnoklassniki/blog/148139/#comment_5000546
2. Справедливая, чтобы не могла возникнуть ситуация «голодания» записывающего потока. Сравнивали производительность со случайной стратегией — никакой разницы.
3. Генератор простой, и хеш простой. Про любой хеш-функции будут перекосы из-за того, что одни картинки более популярны, другие менее. Но это нестрашно: время полного обновления сегментов составляет несколько часов. Память в кеше используется в среднем на 99.4%.
4. Очень даже используем, но здесь не та ситуация. Напомню одно из требований к кешу — in-proccess. Сравнить с Memcached или Tarantool/Box можно, но смысл? См. habrahabr.ru/company/odnoklassniki/blog/148139/#comment_5000546
+2
1. Кстати, а у вас есть данные сколько времени в среднем отрабатывает запрос на получение данных из хранилища? И среднее время обработки запроса при попадании в кэш? Просто мне довольно интересна тема оптимального выбора количества потоков. Может быть вы посоветуете почитать что-нибудь на эту тему?
2. Возможно для оптимизации можно реализовать свою стратегию ReadWriteLock с группировкой писателей в кэш (с некоторым таймаутом для писателей в кэш). Например при накоплении 5-10 писателей запрещать write и отрабатывать пакетную запись в кэш. При этом можно сэкономить на числе перестановок (переупорядочивания) в для ключей.
4. Согласен, вам не особо требуются дополнительные плюшки внешних хранилищ. К примеру, в очень крайнем случае вы кэш пересоберете заново. Распределенность кэширования скорее всего ухудшит производительность.
2. Возможно для оптимизации можно реализовать свою стратегию ReadWriteLock с группировкой писателей в кэш (с некоторым таймаутом для писателей в кэш). Например при накоплении 5-10 писателей запрещать write и отрабатывать пакетную запись в кэш. При этом можно сэкономить на числе перестановок (переупорядочивания) в для ключей.
4. Согласен, вам не особо требуются дополнительные плюшки внешних хранилищ. К примеру, в очень крайнем случае вы кэш пересоберете заново. Распределенность кэширования скорее всего ухудшит производительность.
0
Интересная статья.
Вопрос: какая стратегия все-таки используется для замещения картинок при наполнении сегмента — FIFO или LRU? И как механизм реализован? Вот эта фраза не очень понятна:
если сегмент заполнен, линейным поиском по массиву ссылок находятся и удаляются из индекса ключи, чьи данные будут перезаписаны очередным блоком;
наверное подразумевается все-таки FIFO с указателем на текущую позицию для вставки, который при достижении конца сегмента сбрасывается на начало?
И если это FIFO, то не логичнее ли реализовать LRU, который позволил бы держать в кэше наиболее востребованные картинки?
Вопрос: какая стратегия все-таки используется для замещения картинок при наполнении сегмента — FIFO или LRU? И как механизм реализован? Вот эта фраза не очень понятна:
если сегмент заполнен, линейным поиском по массиву ссылок находятся и удаляются из индекса ключи, чьи данные будут перезаписаны очередным блоком;
наверное подразумевается все-таки FIFO с указателем на текущую позицию для вставки, который при достижении конца сегмента сбрасывается на начало?
И если это FIFO, то не логичнее ли реализовать LRU, который позволил бы держать в кэше наиболее востребованные картинки?
0
Используется FIFO по принципу циклического буфера. Полное обновление сегмента происходит в течение 8-12 часов, т.е. время жизни объекта в кеше составляет не менее 8 часов. При таких показателях FIFO полностью устраивает, в то время как честная реализация LRU значительно усложнила бы логику распределения памяти.
Вот эта фраза не очень понятна:Старые данные (картинки) вытеснять из кеша не надо, они и так перезаписываются поверх. Но нужно удалять их id из индекса. Для этого пробегаем по порядку весь индекс, удаляя те ключи, чьи значения попадают в перезаписываемую область.
если сегмент заполнен, линейным поиском по массиву ссылок находятся и удаляются из индекса ключи, чьи данные будут перезаписаны очередным блоком
+1
Может я чего-то не понимаю, но у меня вызывает сомнение обоснованность главного требования о том, что кеш должен быть in-process.
Вы пишете, что нужно обрабатывать 70 тыс. запросов в секунду, 3000 Мбит/с, но это ведь совсем не много. Только что поставил простой эксперимент на своем десктопе — померял пропускную способность http-сервера nxweb на файле размером 10Кбайт. Получилось 340 тыс. запросов в секунду и почти 3.5 Гбайт/с. Запас более чем десятикратный. Причем это далеко не два Xeon'а, а всего лишь i7-2600 (3.4 ГГц).
Другое дело, что Java в принципе медленнее C/C++. Банальный «hello, world» через Tomcat на моем же десктопе дает лишь 30-50 тыс. запросов в секунду. А если она еще к memcache начнет обращаться, то будет совсем нехорошо.
Вы пишете, что нужно обрабатывать 70 тыс. запросов в секунду, 3000 Мбит/с, но это ведь совсем не много. Только что поставил простой эксперимент на своем десктопе — померял пропускную способность http-сервера nxweb на файле размером 10Кбайт. Получилось 340 тыс. запросов в секунду и почти 3.5 Гбайт/с. Запас более чем десятикратный. Причем это далеко не два Xeon'а, а всего лишь i7-2600 (3.4 ГГц).
Другое дело, что Java в принципе медленнее C/C++. Банальный «hello, world» через Tomcat на моем же десктопе дает лишь 30-50 тыс. запросов в секунду. А если она еще к memcache начнет обращаться, то будет совсем нехорошо.
0
померял пропускную способность http-сервера nxweb на файле размером 10Кбайт
Что этот пример показывает? Где здесь out-of-process?
0
Ну, командная строка (точнее утилита измерения числа запросов в секунду) — она по отношению к http-серверу, как бы, не in-process, но работает достаточно быстро.
Показывает, что вызов чего-то быстрого out-of-process не должен стать узким местом при указанной частоте запросов и пропускной способности. Сокет на том же хосте работает достаточно быстро.
Показывает, что вызов чего-то быстрого out-of-process не должен стать узким местом при указанной частоте запросов и пропускной способности. Сокет на том же хосте работает достаточно быстро.
0
Утилита в данном случае — генератор пользовательских запросов. Чтобы тест был релевантным, нужна еще одна прослойка. Согласитесь, если точки A и B обмениваются данными со скоростью 340 тыс. rps, а B и C тоже со скоростью 340 rps, то вовсе не факт, что A и C через B тоже будут обмениваться с такой же скоростью. Про memcached уже писал.
0
Согласен, конечно. Смысл в другом. Мой тест показывает, что издержки на out-of-process могут составлять (на моем компьютере) лишь порядка 3 мкс. Понятно, что это может оказаться не совсем так на практике, надо проверять, но я веду к другому. Не стоит принимать за аксиому то, что кеш обязан быть in-process, чтобы справиться с указанной нагрузкой.
0
А в целом, спасибо за интересную статью. Давно пишу на Java, но мне бы и в голову не пришло решать на ней такие задачи. Вы расширили мой кругозор, за что премного благодарен.
0
А еще ведь можно запустить какой-нибудь Varnish в режиме malloc (чтобы кешировал в ОЗУ), а позади него поставить tomcat, который будет в случае непопадания в кеш подтягивать недостающий контент из хранилища. Не улучшит ли это результат? Причем значительно, так как большую часть запросов будет отрабать C-код, а не Java.
0
Мы, значит, делаем все возможное, чтобы по-максимуму использовать Java-решения, а вы нам тут C настоятельно советуете?.. Шучу, ничего не имею против. За Varnish спасибо. Впрочем, любое стандартное решение в чистом виде нам вряд ли подойдет, так как в любом случае нужна некоторая портало-специфичная логика, например, фильтрация по HTTP-заголовкам, проверка security-токенов в запросах и т.п. Malloc в чистом виде опять же не подойдет — т.к. при рестарте все теряется.
На самом деле, медленность Java по сравнению с C сильно преувеличивается. И, в частности, этой статьей я хотел показать, что на Java можно писать высокопроизводительные приложения, не уступающие программам на C.
На самом деле, медленность Java по сравнению с C сильно преувеличивается. И, в частности, этой статьей я хотел показать, что на Java можно писать высокопроизводительные приложения, не уступающие программам на C.
0
Так как я достаточно давно пишу не только на Java, но и на C, могу сказать по личному опыту, что разница в скорости обычно измеряется коэффициентом от 2 до 10. Наверное что-то можно отыграть с помощью более эффективных алгоритмов, но если переложить те же алгоритмы на C, все равно получится в несколько раз быстрее. Хотя, опять же, это обобщение, а занчит, со всемы вытекающими оговорками.
И меня обычно тоже не устраивают стандартные решения. Сам я Varnish так ни разу в деле и не попробовал. Но по идее он должен помочь.
И меня обычно тоже не устраивают стандартные решения. Сам я Varnish так ни разу в деле и не попробовал. Но по идее он должен помочь.
0
Sign up to leave a comment.
Использование разделяемой памяти в Java и off-heap кеширование