Pull to refresh

Comments 81

Я, видимо, попал в момент между публикацией статьи и заливкой исходников.
UFO just landed and posted this here
Это приводит к тому, что серверная апликуха просто “умирает” на 10 секунд. Более того, предсказать момент “клинической смерти” практически невозможно.

Вообще-то, можно. И в вашем случае, как раз и нужно.
Что с того, что им можно немного управлять и немного мониторить. Ну это ни как не устраняет неэффективность дефолтного сборщика мусора и менеджера памяти в целом для задач с большими данными.
Там как раз пример есть. Поднимается два инстанса приложения. GC паузы мониторятся. Как только на каком-то инстансе запускается сборка мусора, он исключается из балансировщика и спокойно себе собирает мусор пока запросы обрабатывает второй инстанс.
у меня в много objects- они все volatile, етот state стрoился 2 недели, о каком переклучении процесса идет реч? реконструироват 500.000.000 objects for 2 weeks just to collect 50 Mb of trash?
Ну это же ужасно, в 2 раза больше памяти, а вдруг будет наложение, так что 3 раза хранить. А потом когда разлипнет процесс, то что, эту память нужно синхронизировать между процессами, она же изменится.
то что мы сделали основано исклучително на ПРАКТИЧЕСКОМ опыте.
Перемещать десятки миллионов записеи в раме очень медленно. Между машинами — нереално.
Я молчу о сотнях миллионов записеи. А если записи связани друг с другом — то ето вообше коллапс.
It takes DAYS to move billions of rows between nodes
Вот этот подход с несколькими процессами подходит только для кеша в stateless приложениях, когда кеш прост и пассивен, а работать с живой моделью предметной области, то переключение неизбежно порвет ее консистентность.
Да, простите, в вашем случае не разобрался. Просто глаз зацепился за невозможность сосуществования со сборками мусора.
Просто в статье написано кэш, а это подразумевает, что его можно потерять и восстановить. Тут скорее модель, основная копия объектов, где они живут как в бездисковой СУБД.
Перемещать десятки миллионов записеи в раме очень медленно. Между машинами — нереално.

Ну так вы сделайте через event sourcing с бродкастом событий между инстансами через какой-нибудь EventStore, будет постоянно живая полная реплика, проблему-то нашли.
Что вы имеете ввиду? Если я правильно понял, то можно пробрасывать данные между процессами IIS?
Вы так это описываете, как будто пишете такие системы по три раза перед завтраком.
Ну так суть в том, что нет ничего сложного в поддержке синхронизированного состояния между инстансами в кластере, все необходимые инструменты давно доступны.
а как же моногофазовый пахос, или на многих нодах будет тогда мусор?
вы поймите — локалный рам всегда быстрее чем network, даже 40 Гбит. + координация ИП ядра на послки пакетов, DMA, IRQ

Загрузите код и сделайте тест
Горизонтальное масштабирование. Нагрузка делится равномерно на несколько инстансов. К тому же, получаем бОльшую отказоустойчивость (если какой-то инстанс выйдет из строя, его заменят другие). Аппаратные ресурсы при этом, обычно, разумная жертва в угоду производительности.
Синхронизация это уже отдельная задача архитектуры. Полезно почитать.
это ужe на 1м ноде. в кластере у нас сотни миллиардов объектов.
как раз это и позволиает имет каш хит rate > 25% очен дешево ЛОКАЛьНО
попробуите хранить устоичиво 500.000.000 objects таких:
person
{
GDID key, //this is 12 bytes
GDID user, //12 bytes
string msg,
DateTime ts,
GDID? ref //1 or 13 bytes
}
и посмотрите что будет с вашим сервером
Массивы структур использовать не пробовали? С указанием строк как оффсета в массиве char-ов. Получилось бы, внезапно, то же самое, но куда эффективнее как с точки зрения отсутствия расходов на сериализацию, так и в плане GC pressure (не нужно собирать десериализованные экземпляры).
а что если НЕИЗВЕСТНО какие типы данних надо хранить? например — в ДБ каше мы храним бизнесс данные — какои их тип — неизвестно.
это может быть листы сложных объектов, транзакции. Хранить все во флат byte[] — это значит не исползоват C# objects как таковои.
Pile — ето БАЗА ДАННИХ/warehouse of objects в памяти
Ну так сделайте обобщённые классы пулов на generic-типах, пока типы будут соответствовать набору правил (нет ссылок на reference-типы), всё будет работать на отлично.
Кодогенерация в рантайме?
А как работать с массивом char-ов? Имеется ввиду создавать объект string перед использованием?

Пока писал вопрос, возникла мысль как бы я начал решать это — создать структуру, которая в конструктор принимает строку, кладет ее в массив, и имеет набор всех часто используемых методов, типа сравнения, ну и метод ToString само собой. И класть ее в исходную структуру, где нужна «строка».

Еще не представляю как делать освобождение таких строк, если исходная структура больше не нужна, и ее можно удалить из массива или перезатереь другой, то как можно узнать, что больше никто не держит копию этой структуры? В сценарии «загрузить большой файл в память и с ним работать» это конечно неактуально, но наверняка найдутся другие сценарии. Разве что заботиться не об удалении единичных экземпяров, а об дропе некого большого блока «устаревшего кэша».
А почему не хранить такое в какой-нибудь редиске или другой No-SQL? Зачем вообще нужно хранить много данных в процессе?
Если нужно выполнять алгоритм на большом толстом графе, то к вашей редиске нужно будет несколько десятков, а то и сотен обращений на запрос. Дороговато выходит.
Так для этих целей графовые СУБД есть. Хотя я понял о чём вы, это как раз о том случае, когда ты сам реализуешь СУБД.
Т.е. сначала одни люди прикрутили абстракцию, избавляющую от управления памятью, затем другие люди поверх прикрутили абстракцию, эмулирующую управление памятью…

не, я понимаю зачем это все, но чувство парадоксальности происходящего не оставляет.
деиствительно смешно, но нам ето надо чтобы разгрузить сервера и кашать реално 500.000.000 комментариев в памяти.
это очень нужная вешь — она работает в разы быстрее чем мемкаш out-of-process
Философски. А ведь можно было просто использовать Си++. Собрать 64-битное приложение и вперёд. :)
Это видимо тот интересный переходный момент, когда преимущества языка превращаются в ограничения, которые мучительно преодолеваются.
да можно было просто хранить объекты .net в unmanaged памяти и спокойненько к ним обращаться и они не были бы подхвачены GС — было бы желание…
I think you are missing the point. The point is to use EXISTING objects that exist for absolutely different purposes, i.e. business logic, not DTO.
Why would I create 1000s of DTO objects in an unmanaged heap just to cache data. I already have 100s of business types with complex logic,
some with visitor pattern and double-dispatch etc… (i.e. friend-friend) interaction.
Pile allows me to use polymorphism and full features of C#, while not loading GC at all
Да какая разница что хранить? Хранятся только данные, методы никак к данным не относятся (не зря же туда this передают?)). Структура .net объектов в памяти ниразу не секретная — переместить данные в неуправляемую кучу и поднастроить ссылки дело нехитрое, более того это уже даже на конференциях показывают — например как шарить .net объекты (как объекты) между процессами в shared memory…
Первый раз об этом слышу, плз дайте что нибудь почитать или поглядеть…
На CLRium-ах было несколько раз. Единственное, код становится ни разу не переносимым, на Mono уже не запустить.
Pile — это 100% managed solution, именно поэтому он переносим легко межды платформами и применим к любым бизнес типам.
То что предлагаете вы — абсолютно не портабельно и делает невозможным работу с objects в стандартной парадигме работы в C#.
How do you access a «property» of a byte buffer via a pointer? Of course its all the same processor opcodes at the end of the day.
It would be easier to use C++ and forget about C# in your case.
How do you access a «property» of a byte buffer via a pointer? Of course its all the same processor opcodes at the end of the day.
В том и суть, что весь остальной код думает, что это обычный объект и работает с ним по обычной же ссылке на объект.
Просто автор не знал про наличие в языке value-типов, специально для таких штук предназначенных.
а как хранить ссылки в inside value type? на что ссылатся? на другои валуе type? это как, ану расскажи?
Делаете структурку типа
struct Link<T>
{
     int PoolId; //id массива
     int ObjectId; //по факту индекс в массиве
     //Тут всякая всячина с реализацией Equals, == и т. п.
}
её храните в своих «объектах» вместо настоящей ссылки и потом резолвите. К ней, соответственно, extension-метод
static T Resolve<T>(this Link<T> link)
{
//тут обрабатываете логику пулов
}


В самом «объекте» храните ещё поле Self, чтобы на него можно было без геммороя ссылаться.

Итого: из оверхеда дополнительно тратим 8 байт памяти на объект для Self, ссылки как были 8 байт, так и остались.
The Pile concept allows you to work with CLR Object of ANY STRUCTURE.То что вы предлагаете — это какои-то edge-case for some particular «object».
>>ANY STRUCTURE
Ложь и провокация. Если я положу туда Stream со ссылкой на unmanaged-ресурс, то оно не будет сериализовано. И не edge-case, а просто другое представление структуры объекта.

И это, вы можете на каком-то одном языке писать? Раздражает.
Sorry, don' have ru-keyboard. my typing sucks.

You are absolutely correct. One can not put a stream with unmanaged handles in it.
But, like I have said, the concept is used for PRACTICAL cases which are as follows:
in tasks where one needs to store very many graph nodes resident in memory, AND retain the business purpose of every node,
instead of creation of DTOs that are only purposed for marshalling/storage, the Pile allows you to store «data with code».
It is a practical system, not a puristic one. So you are correct. you can't store delegates, unmanaged handles etc… but I have never needed it

You store in pile what you would have stored in Redis(ore memcache). Only faster. Why? because the data is already here, and serialization is orders of magnitude faster that JSON->string->redis.

Pile: [«any» object] -> bin ser -> byte[] pile(already alloced)
Memcache/Redis: [«any object»] -> JSON ser/bin ser->network/TCP-> memcache/redis server

One would think that serialization is very slow, I thought this when we started the project. The reality is different,
— we have not even expected to achieve such a good PRACTICAL result.

I hope this makes more sense now.
Тут идея в том, что в случае хранения всего в value-типах накладываются примерно те же самые ограничения (можно хранить только POCO), но за счёт дружественной к GC структуры данных можно избежать расходов на сериализацию/десериализацию, ненужного GC pressure (value-типы размещаются на стеке, а не в куче) и т. д.
При этом сами данные могут быть любые. За счёт использования generic-типов можно построить инфраструктуру, которая будет обрабатывать любой тип соответствующий требованиям.
То есть, получаем те же самые преимущества, но ещё эффективнее.

Я бы соорудил proof-of-concept, но для нормального сравнения производительности надо писать свой аналог malloc, а это слишком лениво делать перед праздниками.

А так идея довольно стара и активно мной использовалась ещё в школьные времена для программирования на QuickBasic, где не было объектов и ссылок, только структуры и массивы
Pile does not have to be local-only. It may be distributed. Also, there is Cache interface that adds key index on top of Pile. The index may be distributed as well. PilePointer has NodeID field. So it is not local-only solution, it may be a hybrid solution, where the most frequent data is kept in ram, some on the nearby server etc.

I encourage you to take a look at these:
github.com/aumcode/nfx/blob/master/Source/NFX/ApplicationModel/Pile/IPile.cs
github.com/aumcode/nfx/blob/master/Source/NFX/ApplicationModel/Pile/ICache.cs
Вам надо было брать Akka с шардингом и event sourcing, вот что.
Ага, а потом когда выясняется что эти структуры надо уметь удалять, начинаются пляски с аллокациями, списками свободных индексов, отдельные треды для компактификации… и элегантные шорты превращаются в Neo4j! В лучшем случае. А в худшем — в ту же самую управляемую кучу, только свелосипеженную с нуля. Ой…
Ну так предложенный автором Pile этих проблем не отменяет.
Да, не отменяет. Это, так, мысли в слух. Просто прикольно понимать, что managed языки, unmanaged языки и базы данных — это, на самом деле, отдельные области единого спектра.
На самом деле Pile и вышепредложенный способ имеют примерно ту же область применения, что и сверхбыстрый аллокатор, который не задумывается о необходимости освобождать память — когда она всегда освобождается одним единым куском на несколько гигабайт.
Звучит очень интересно, но идея до конца не ясна. Подскажите пожалуйста как это называется или ссылки на статьи или просто где можно про такое поподробнее почитать?
Если у вас даже сплошные value types все равно надо хранить строки. А строка, как ни крути — референс тип. Нy не быду же я свои строки делать?
Нy не быду же я свои строки делать?
А в чём проблема? Делаете под них точно такой же пул, только состоящий из большого массива char-ов. И храните оффсет в нём и длинну.
именно это и делает SlimSerializer, на котором стоит LocalPile, под капотом. Просто от вас ето скрыто и не нужно об этом думать когда пишеш бизнес логику
Скрытость под капотом даёт накладные расходы на сериализацию и GC-pressure. Вы с GC вроде как боролись, нет?
зачем вы тогда исползуете C#? C++ намного больше подходит для етих целей. статья была вообше не об этом.
мои GC расходы — это Gen0 а не Gen2 — вот и вся разница
зачем вы тогда исползуете C#?
Потому что он всеяден. Когда надо — можно аккуратно сделать такое вот эффективное с точки зрения GC решение. Ещё структуры очень активно используются в геймдеве (XNA, Unity3D), когда вообще нельзя создавать GC pressure.
я же много раз уже писал, что Pile предназначен для ПРЕДМЕТНОЙ области.
Если у вас есть уже десятки классов которые моделируют задачу. О каких структурах идет реч? В БОЛТ.дб уже есть около 200 таблиц в базе,
я их беру и храню в кеше который сидит на пиле — Это скрыто от меня, в этом весь бенефит.

Вот весь код:
что храним: github.com/aumcode/nfx/blob/master/Source/NFX/DataAccess/Distributed/Parcel.cs
как храним: github.com/aumcode/nfx/blob/master/Source/NFX/ApplicationModel/Pile/ICache.cs
blog.aumcode.com/2013/08/what-is-nfx-what-is-unistack.html
NFX это УНИСТАК — pile is less than 1% его features, невозможно просто взять и хранить + обрабатывать чтото в C++ — там совсем другая модель всего — значит опят надо все переписивать тепер на C++: logging, cluster process control, configuration, UDDI, networking stack.
В етом и весь смысл — не переписиват все осталное 25 раз. Обычно в таких случаях исползют мемкаш или редис — но ето намного менее еффективно чем держaть все прамо в рам. Например етот подход исползуется нами в граф-дб для нахождения друзеи.
Поменьше синтаксических ошибок пожалуйста, читать не приятно.
Но тема интересна.
Вы бы сами делали поменьше ошибок, а не другим советовали.
«Не приятно» должно писаться слитно в вашем предложении.
Вы ранее упоминали, что у вас нет периодически клавы с русской раскладкой. А в данном случае она у вас то появлялась, то исчезала, что-ли? Не то, чтобы придираюсь, просто любопытно.
не печатал по-русски где-то 23 года… заржавел
не пойму однако, почему это так важно для многих
Может быть, примерно потому что, если я влезу на Stackoverflow и начну писать на немецком, английском и французском одновременно, периодически разбавляя русским, то это будет выглядеть несколько странным? :) Понимаю, что аналогия несколько отличается, но частично совпадает :)
А вообще, с почином вас на Хабре! Пишите ещё. Хардкорные технические статьи всегда на пользу. Но, действительно, пожалуйста, если можно — поменьше сленга и выпендрёжа местами. Возможно, это потому что по-русски давно не писали. В общем, немного поскромнее, поподробнее и было бы вообще замечательно :)
Легче было переписать на языке без сборщика мусора
Кстати, очень похожим образом работал класс XPathDocument. Там после парсинга все XDM-дерево хранится именно вот так, отображением на большие массивы байт индексацией. И обход дерева через XPathIterator ходит по этим байтам (в которых хранятся и относительные сдвиги на соседние узлы по разным осям).
Сам пишу в основном на .NET. Но тут явно напрашивается С++. CPU Профайлер + _CRTDBG_MAP_ALLOC превращают кривонаписанные поделки в high-load ready приложения… И никаких больше «danGling» указателей
Вы лихо за 10 минут переписываете гигантские проекты которые доросли до hiload на плюсы?
Я предпочитаю потратить больше времени на разработку архитектуры, чем на кодописание и фишечки. Если проект вырос в high load из наколенной поделки, значит изначально не было понимания требований, переписывание тут ни при чем.
Sign up to leave a comment.

Articles