#71: Как работает IMDG Apache Ignite
Александр Пахомов и Станислав Лукьянов разбирают in-memory data grid снизу вверх: от локальной HashMap до распределённого хранилища с логическими шардами, репликами, консенсусом и SQL. На примере Apache Ignite они показывают, как коллокация данных и вычислений убирает сетевые пересылки, зачем Java-системы ушли в off-heap и где остаются проблемы GC, JIT, безопасности и эксплуатации. В финале формулируют критерии выбора такой технологии и обсуждают её возможное будущее в памяти агентов и LLM KV-кэшах.
Главное
- In-memory data grid — не просто кэш: система объединяет память нескольких машин и строит API так, чтобы минимизировать I/O, сетевые пересылки и движение данных между диском и памятью.
- Ключ сначала отображается в логический шард, а шард — в физический узел; consistent и rendezvous hashing позволяют менять состав кластера, не перекладывая заново весь набор данных.
- Репликация требует явного выбора между доступностью и консистентностью: Ignite 2 предлагал режимы Primary Sync и Full Sync, а Ignite 3 использует Raft и подтверждает запись большинством реплик.
- SQL поверх распределённого key-value-хранилища становится эффективным, когда связанные данные коллоцированы: partition pruning и локальные join'ы убирают shuffle между узлами.
- Collocated compute переносит Java-бизнес-логику к данным и сводит операцию к одному сетевому переходу, но повышает требования к безопасности, изоляции, multi-tenancy и квалификации команды.
- Переход в off-heap решал сразу три задачи: снижал давление на Garbage Collector, давал предсказуемый бинарный layout для индексов и SQL и позволял сохранять страницы на диск.
- Распределённое хранилище оправдано при сочетании низкой latency и большого параллелизма; прежде чем вводить его сложность, стоит проверить scale-up, кэш и репликацию обычной базы.
- Идеи data grid могут вернуться в AI-инфраструктуре как масштабируемый слой памяти агентов, семантических кэшей и распределённого хранения LLM KV-кэшей рядом с инференсом.
В выпуске
- Станислав Лукьянов — Инженер распределённых систем и developer-facing продуктов (около девяти лет в распределённых системах, тринадцать — в продуктах для разработчиков). Коммитер Apache Ignite, работает над GridGain (теперь часть MariaDB); ранее — Java Platform в Oracle. Живёт в Нью-Йорке.
Ссылки
Расшифровка
[00:00] Александр: Всем привет! В эфире подкаст «Тысяча фичей». В гостях Стас Лукьянов. Я думаю, что слушатели подкаста знакомы с предыдущим выпуском, где мы почти три часа разгоняли про AI-программирование с Клодом и все такое. Очень нам было интересно. И это второй заход со Стасом наш. Когда мы уже к оригинальной теме все-таки решили подойти, мы будем сегодня говорить про распределенные кэши, in-memory data grid, в частности, много будем говорить про Apache Ignite. Два коммитера Apache Ignite будут про это разговаривать. Я буду в роли уточки, поэтому мои вопросы будут максимально простыми, тупыми и такими, которые могли возникнуть у слушателей подкаста, так что все ок. Но и жести будет сполна. Я думаю, мы тут зарубимся по-технически. Нормально вспомним, что подкаст «Тысяча фичей» — это подкаст про базы данных, а сегодня мы говорим отчасти про базы данных. Стас, привет!
[00:55] Станислав: Привет, Саша!
[01:09] Александр: Говоря про Apache Ignite, я бы такой вопрос задал. Вот моя карьера разработческая, она так или иначе с Java связана на протяжении уже там 9-10 лет. И вот когда я начинал разрабатывать на Java, такой вот Apache Ignite проект был довольно на слуху. Я его использовал, я поверх него строил какие-то там комплексы, которые были в том числе для разработки. Продукты эти или движок на Groovy мы писали, чтобы вот in-memory что-то процессить. Было весело. И это вообще-то довольно такой был феномен в плане изначально рожденный, наверное, в России все-таки open-source серьезный проект, написанный на Java. Всегда было интересно за ним наблюдать. И я думаю, ты сильно ранее познакомился с ним, чем я. Поэтому вот если бы тебе было несложно, расскажи какую-то историческую справку, экскурс. Вот каков такой феномен Apache Ignite? Почему он стал популярен?
[01:59] Станислав: Я не знаю, насколько раньше я, чем ты познакомился с Apache Ignite, но может быть немного раньше. Я помню, что мой, наверное, первый контакт с экосистемой, это было такое знаменитое выступление Германа Оскаровича Грефа на, по-моему, МЭФе, где он толкнул абсолютно лютый, ну вот так вот, если смотреть назад, спич, про то, что все эти ваши Oracle и Microsoft скоро сдохнут. Oracle, там, это система для динозавров. А мы, короче, вот банканули на такую компанию GridGain, которая там вендор Apache Ignite. И вот мы, короче, будем переписывать на Apache Ignite, короче, весь банк. Вот. И действительно был такой достаточно знаменитый даже уже не в узких кругах, мне кажется, такой. Такой проект, да, который там частично получился, частично нет, но действительно вот такая ставка, которая была сделана, ну, достаточно публично и достаточно широко была видна внутри СНГ с одной стороны. С другой стороны есть вот другое имя, которое я здесь, наверное, наимдропну. Это Виктор Гамов, известный многим под ником Gamussa, developer advocate. Я не помню, где он сейчас. Он сейчас, наверное, в Confluent. А тогда он был одним из хостов знаменитого подкаста Разбор полетов, знаменитого в том числе среди джава-разработчиков. И он же был DevRel-инженером компании Hazelcast или девелопер-евангелистом компании Hazelcast, но говорил про Hazelcast. И вот мне кажется, что вот в то время вот это как бы вынесло тему in-memory data grid, которая, в принципе, там в 2010-х была достаточно на устах. И вот это очень глубоко посадило в головы именно русскоязычных джава-разработчиков. И я был в их числе. И я в скором времени после этого я уже начал заниматься этим профессионально и начал профессионально разрабатывать уже Apache Ignite, присоединившись как раз к компании GridGain. Ну, что еще можно сказать из тех времен? Ну, наверное, in-memory data grid виделись как одна из таких больших технологий, которая позволит нам делать бигдату. То есть вот до того, как мы делали AI, и для того, чтобы мы сегодня могли делать AI, мы должны были в десятых научиться обрабатывать, обрабатывать большие объемы данных. И одна из вещей, которая должна была с этим помочь, это должны были быть распределенные системы и in-memory системы. И вот как раз in-memory data grid, и в частности Apache Ignite, с этим мы помогали. Ну и последнее, почему именно Apache Ignite? Почему не Hazelcast, например, стал настолько широко известным, настолько широко используемым? Ну, потому что, ну, не секрет, что много, русскоязычных разработчиков у Apache Ignite, и это позволило, в общем, Apache Ignite вот так появиться среди СНГ-экосистемы.
[05:42] Александр: Два хот-тейка услышал, я бы их просто повторил, не развивая, а просто, как это, запечатлеть. Apache Ignite должен сказать спасибо за популярность Грефу и Гамову. Это то, что я услышал.
[05:55] Станислав: В моем мире 100%. В моем мире 100%. Спасибо.
[05:59] Александр: И второе, это то, что AI стал крутым благодаря Apache Ignite.
[06:04] Станислав: Давай скажем, конечно, что это так.
[06:07] Александр: AI стал крутым благодаря Big Data. Вот, Big Data случилась, в частности, благодаря Apache Ignite, конечно, да. Да, вот в мире Big Data, кстати, я познакомился вот с этим продуктом, когда тоже начал работать в Big Data. Мы вот огромное количество данных, что-то там в телекоме, обрабатывали, вот, из HDFS читали, потом их надо было куда-то как-то складывать, какие-то промежуточные результаты, вот эти ETL-джабы, Extract Transform Load и все такое. И действительно, вот тогда идея, что это можно обрабатывать в оперативной памяти, она была довольно свежа, то есть диски были медленные, SSD не так были распространены на серверах, S3 тогда еще не был таким популярным, и по сути это был сервера, ну вот Big Data, это что, это HDFS, поверх дисков с крутящимися магнитными головками, медленное там чтение записи, никакого рандомного доступа, и ты просто максимально считывал сколько мог и пытался с этим что-то сделать. И вот когда ты с этим пытался что-то сделать, минимально записывая на диск, то логично было, что можно класть что-то в оперативную память. А класть хотелось много. А как класть много в оперативную память? Ну вот нужно какое-то решение, потому что в оперативную память одной тачки это не помещается, то есть нельзя написать программу, которая обработает там все, что нужно. Мы про это поговорим, то есть, слушатели, я надеюсь, что в конце этого выпуска у вас сложится понимание, как у инженеров, как у архитекторов, системных мыслителей, сложится понимание системы такой вот распределенного кэша, представителем которой является Apache Ignite, и дальше это поможет вам в том числе ориентироваться вообще и строить, может быть, подобные системы. Возможно, некоторые из вас подумают о том, что где бы можно было это использовать, или нет. Про все про это мы сегодня поговорим. Наверное, мне бы хотелось начать с такого. Мы исторический экскурс сделали, а теперь хотелось бы немного посравнивать, что это за продукт такой, но верхнеуровнево хотелось бы понять, посравнивать с другими, понятное дело, базами данных или движками, но прежде, наверное, стоит вообще сложить какое-то представление у слушателей, которые первый раз слышат слово in-memory data grid, а что это вообще такое? Вот человек пишет на Rust, например, пять лет, первый раз слышит это слово. Вот как бы мы объяснили, что это за зверь такой?
[08:35] Станислав: Главная, наверное, родовая травма in-memory data grid в том, что их достаточно сложно определить. Каноническое определение, которое дал им, по-моему, какой-то Gartner, то есть это все in-memory data grid как концепция, она пришла скорее как бы не из именно вот описания, оно пришло скорее не от технорей, а оно пришло скорее вот из тех, кто, вот от вот этих чуваков в костюмах, которые разговаривают с другими чуваками в костюмах и там рисуют интерпрайз с архитектурой большого банка, да, и вот они описывали in-memory data grid как систему, которая позволяет запулить оперативную память нескольких машин вместе, чтобы приложение могло использовать одновременно оперативную память, многих машин, то есть там не было речи про, а как мы процессим данные, не было речи там про какое-то ускорение баз данных, то есть там фокус был именно на том, что это приложения, которые получают много памяти, которая, которая с разных машин приходит, вот на, которая вот пулится на уровне вот этого, которая пулится на уровне in-memory data grid, что это означает для нас практически, это тоже менялось за годы, мы, наверное, поговорим немножко про историю, 10 лет назад можно было говорить про то, что in-memory data grid это то, что дает вам распределенно хранить данные, сегодня у нас там распределенных баз данных всяких разных пруд пруди и не только NoSQL, и не только in-memory data grid, хочешь распределенный Postgres, возьми YugabyteDB, и будет у тебя распределенный Postgres, если есть какое-то одно ключевое, какая-то одна ключевая особенность in-memory data grid, то я бы сказал, что in-memory data grid это единственная технология, которая максимально бросает все свои ресурсы, все свои, весь свой дизайн системы, API, всего, на то, чтобы минимизировать I/O, на то, чтобы минимизировать передачу данных по сети, на то, чтобы минимизировать передачу данных с диска в память, для того, чтобы максимально все делать в памяти одной машины, это такая причина существования in-memory data grid, особенно сегодня. Поговорим про то, как они это делают, там есть много разных кусков этой технологии, но есть еще, скажем так, вторичные признаки, которые, ну, исторически, наверное, обусловлены, это то, что почти все in-memory data grid это java-based технология, так получилось, единственный. Вот это тот самый, знаешь, случай, когда так исторически сложилось, это лучше и не скажешь. Да, если бы там переписывать вот сегодня, в 2026 с Клодом, если переписывать там какой-нибудь Apache Ignite, написал бы я его на Java или на Rust, наверное, я написал бы его на Rust. Да, но, есть и причины, почему он, ну, почему java это хороший выбор там, это в частности, потому что, ну, in-memory data grid по определению, он должен быть расширением вашего приложения, и если ваши интерпрайзные приложения это микросервисы, написанные на Java, то и расширение вашего приложения, да, с которым оно нативно разговаривает, оно тоже может быть на Java, там многие вещи становятся удобными, и на самом деле это одна из причин, почему почему так исторически сложилось, то есть это java технологии, это изначально key-value технологии, то есть сервера, хранящие данные, да, их основной интерфейс, это чаще всего key-value, SQL там иногда доступен, иногда недоступен, там в Ignite есть полноценный SQL, там в каком-нибудь Oracle Coherence, например, там SQL полноценного нет, но есть что-то, но есть там что-то похожее, в каких-то системах это он нативный, в каких-то системах это такая нашлёпка, которая, ну, не очень хорошо прикручена, в целом, вот такие java key-value распределенные сервера хранения, системы хранения данных, вот, которые дают много всяких высокоуровневых API для того, чтобы данные обрабатывать эффективно в памяти прямо на сервере, а не только вытягивать их на клиента, вот так, наверное, можно это суммарно определить.
[13:09] Александр: Слушай, мне вот понятно в целом key-value, понятно, как бы я это представляю так, своими словами, это, ну, условно бесконечная key-value хранилка для моего java приложения, вот если прям совсем на пальцах, это значит, что я могу туда дофига класть и что-то читать оттуда могу, дофига, наверное, не могу, потому что, ну, я как инженер уже понимаю, ну, у меня же оперативная память есть, я же это считываю куда-то себе в heap, соответственно, я могу читать, но не бесконечно, я могу читать столько, сколько я способен прочитать, и вот здесь как раз мне становится понятным, а почему ты говоришь про обработку данных на сервере, то есть, я-то читаю, как клиент, для чего? Чтобы обработать, но навряд ли я это всё на веб там отдаю, навряд ли, то есть, я как-то хочу обработать, отфильтровать, может быть, сгруппировать, там word count какой-то посчитать, короче, аналитику сделать или агрегацию, статистику, и для этого у меня есть какой-то код, и вот этот код, потом мы поговорим, что не обязательно это у меня на клиенте исполнять, но ещё, что я вот вспомнил, когда ты про это всё говорил, что мне очень долго концепция укладывалась в голове, когда я познакомился с подобным, что это же ещё может быть частью твоего приложения, то есть, это такая неочевидная вещь, которую вот, если сразу не объяснить, то в голове по-другому строится архитектура, что, ну, как вот мы сейчас с базами данных привыкли все пользоваться, большинство так пользуются, но есть где-то базы данных, на каком-то там сервере, на каком-то клауде, распределённый, нераспределённый, уже и неважно, есть просто connect любой, и мы клиент, и мы подключаемся, а приложение бежит где-то в другом месте, на другом поде в Kubernetes, у меня на компьютере, то есть, это два разных приложения с точки зрения операционной системы. Мой клиент, backend, и база данных, которая хранит данные, по крайней мере, в одних из модов, в которых можно работать, а патч Ignite’ом, это embedded mode, то есть, это то, когда серверный процесс, базы данных становятся, ну, или вот хранилище, становятся частью вашего приложения. Я вот первый раз эту архитектуру увидел у одноклассников, вот, они тоже часто про это говорили на хайлоде, в докладах, что у них там, по-моему, даже большая часть backend написана так, что у них данные хранятся в оперативной памяти и самих приложений, грубо говоря, и эти приложения образуют, как бы, кластер, и у тебя, как бы, гибридная такая модель, у тебя и код backend, и код базы данных, он, как бы, в одном процессе работает. У этого тоже есть свои плюсы и минусы, но я вот это хотел подсветить, что, если такое, тут что-то хочешь добавить еще про это?
[15:55] Станислав: Да, здесь вот тут embedded mode и, ну, вообще, вот этот вот стиль написания приложения, когда мы в принципе говорим, что, а че, у нас распределенная база, у нас есть распределенный какой-то сервис, и у нас есть наша распределенная база данных. И наша распределенная база данных, она написана на Java, она вообще хранит данные наши прямо на heap, и так, а че бы нам не засунуть нашу базу, наши вот эти узлы базы, прямо внутрь нашего распределенного сервиса, и тогда у нас, как бы, такой полустейтфул сервис получается, но нам вообще не надо никуда никогда не ходить, в какую базу, у нас нет для большого количества операций, у нас нет вообще никакой сети, это мне всегда казалось очень классной для определенного класса проблем, очень классная архитектура, но при этом она идет немножко против шерсти, ну, какой-то сегодняшней вот мудрости, да, разработчикской, что вот люди знают, что нет, у меня должны быть микросервисы, они должны быть, например, стейтлесс желательно, они должны там очень легко, очень легко скалироваться, у меня каждый сервис должен там делать ровно одну задачу, а здесь мы наоборот объединяем вещи друг с другом, да, то есть мы объединяем там дата слой какой-то, и слой, и слой приложения, как бы, хорошо ли это, иногда хорошо, мне вот, например, такая архитектура очень нравится для каких-то сервисов, у которых данные, это не ваши бизнес данные, а там, например, какие-нибудь микросервисы, отвечающие за аутентификацию, и вот они, например, кэш-сессии хранят прям локально, и тогда у нас вот эти сервисы, им вообще не надо ходить ни в какую базу, нам не нужно, или там, ни в какой там распределенный кэш отдельный, нам никак не нужно их отдельно менеджить, мы очень легко их дипло. мы их очень легко диплоим, мы их очень легко скалируем, но я понимаю, почему у этой архитектуры также есть и противники, ее действительно, ну, ваш сервис становится более тяжелым, да, теперь в нем, теперь прямо в сервисе хранятся какие-то данные, даже если это только в инмемори, но об этом теперь нужно, об этом теперь нужно думать. Но в этом, наверное, вот в этой архитектуре, если так подумать, это вот самый вот чистый способ использования in-memory data grid, вот прямо вот как в определении в их написано, что вот это, у приложения становится бесконечная память, вот так вот она и становится бесконечная, что мы много инстансов приложения, мы вот так вот вместе объединяем, и у нас там из 50 объединенных машин получается просто x50 трэдов, да, они как бы, это просто такое гигантское мультитрэдное приложение с гигантской памятью, и все это работает вместе. Это прикольная архитектура, но надо, да, надо понимать, где можно ее использовать.
[19:05] Александр: Вопрос прям дилетантский. Вот ты говоришь память, память, память. Я как разработчик баз данных, хотя и играющий роль утки здесь, ну, сразу у меня возникает вопрос, а что такое память? То есть, это, ну, вот память может быть разная, вот я взял байтовый массив у себя, указатель на байтовый массив в своем C++ приложении, вот это звездочка там, char ptr, не знаю, и все, вот это память. Или, например, память это коннекция к JDBC, например, коннектор. То есть, что такое, с точки зрения вот нашей дискуссии, память?
[19:40] Станислав: Да, здесь, наверное, надо уже пойти в разговор о том, что in-memory data grid собой представляет, для, собственно, для приложения, да, для разработчиков, то есть, какой аппетта я вижу. И здесь, мне кажется, проще всего это понять, если начать вот с самого, вот с самого базового случая. Есть у меня, вот я джава-разработчик, у меня есть HashMap. Мне вот, мне просто нужен, мне просто нужен HashMap. Ну, что у меня есть в HashMap? У меня есть get, put, ну, вот такой вот key-value интерфейс, там, какой-нибудь putIfAbsent, да, все, что у меня есть. Пауза, я,
[20:20] Александр: когда про структуры, вот такие данных, думаю, я всегда говорю, а почему HashMap, а не TreeMap, например? Здесь я просто прояснить бы хотел, потому что все-таки кажется, что слово хеш здесь имеет большое значение. И мы не можем абстрагироваться выше, то есть мы не можем сказать, что это абстрактная какая-то мапа, это все-таки HashMap.
[20:42] Станислав: А, кстати, вот, я понимаю, да, я понимаю, к чему ты ведешь, нам потом понадобится хеш, потому что мы, поднимаясь от уровня того, что у нас есть HashMap, когда мы превратим ее в распределенную HashMap, мы увидим, что нам очень поможет уметь хешировать ключи. Вот, но, кстати, если сравнить TreeMap с HashMap, ну, как бы, что главное, какое главное свойство у TreeMap? То, что у нее ключи сортированные. Ну и на самом деле, когда мы пойдем еще дальше, мы увидим, что ключи тоже бывают сортированные в in-memory data grid, и на самом деле им нужно быть сортированными, потому что в какой-то момент ты захочешь не только key-value, ты захочешь еще и range запроса, но это мы забегаем вперед. Да, это мы
[21:26] Александр: вперед забегаем, я бы тут вот с точки зрения API еще бы подсветил, что все-таки, когда я думаю про API, HashMap и TreeMap, это, кстати, не самая очевидная штука, она ко мне как-то вот с тем, как я начал разрабатывать баз данных и больше про это думать, и подкаст про это сделал, я немного начал реально осознавать, что, вообще говоря, это хоть и с виду разные структуры данных, но, по сути, они максимально разные, и что удивляет, что у тебя требования к ключу концептуально должно быть разное в HashMap и в TreeMap. В HashMap у тебя требование, чтобы у тебя был equals и хешкод, и это как бы одно семейство данных и ключей, а другое семейство это в TreeMap, когда тебе нужно компаратор объявлять, и у тебя должно быть отношение порядка между ключами, и это, вообще говоря, не всегда возможно, то есть это, ну, между стрингами, например, да, у нас есть отношение порядка и там хеш, но, вообще говоря, есть такие данные, какие-нибудь там, ну, вот сейчас прям так, какие-нибудь, может быть, измерения рандомные с разных приборов, или что-то такое, где отношение порядка между ключами, вообще говоря, оно, ну, не определяется, по сути, данных, которые под ним лежат, и тогда, а что такое порядок? А TreeMap нужен порядок, потому что там, ну, собственно, это дерево поиска.
[22:44] Станислав: Именно так, именно так, да, и это, на самом деле, то есть, когда мы возвращаемся к тому, а это же какая-то структура данных, с которой работает приложение, на самом деле, здесь вот разница между HashMap и TreeMap, она, оно в том, а с точки зрения моего домена, ну, домена моих данных, с точки зрения тех ключей, которые у меня есть, а какие операции у меня вообще возможны? У меня операция пофильтровать по какому-то диапазону ключей у меня вообще возможно, у меня бывают диапазоны ключей, или у меня ключи бывают только вот индивидуальные? И, ну, там можно, на самом деле, легко увидеть, что вообще key-value модель, она работает отлично, когда у нас именно обращение к одному ключу, да, то есть, когда это вот такой HashMap, точечные запросы по отдельному ключу, например, там, session-id, ну, не бывает, нет понятия range session-id, session-id, они, не, мы можем их, конечно, отсортировать как-нибудь, но это бесполезно, да, то есть, то есть, у айдишников нет хронологического порядка, как бы, да, у них есть рандомные просто идентификаторы, да, да, да, да, да, ну, обычно сейчас там придут люди и скажут, что у меня Snowflake id, они там у меня, как-нибудь, не, пожалуйста, да, то есть, да, мы понимаем, да, мы, можно чего угодно сделать, да, но идея в том, что обычно вот такие объекты, они, как бы, не, они не сортируются, с другой стороны, в каких-то приложениях у нас там id это какие-нибудь либо порядковые номера, да, которые отсортированы, либо может быть у нас id это какие-нибудь там, иногда бывают даты, какие-нибудь таймстампы, что-то такое, и в этом случае нам действительно может захотеться сделать по ним range-запрос, это как раз та территория, когда мы потихонечку, потихонечку начинаем выходить за территорию, с территории key-value на территорию каких-то более сложных запросов, и быстро-быстро мы придем в SQL, на самом деле, когда мы туда идем, что, ну, мы начали с того, что in-memory data grid, они фундаментально про key-value, но я здесь же сразу скажу, что in-memory data grid поддерживают и более сложные query, и многие из них поддерживают SQL или какой, или хотя бы какой-нибудь subset SQL, то есть они как бы могут работать и в технологии TreeMap, да, но как бы фундаментально, наверное, они начинаются с того, что у них есть HashMap. Вернемся, наверное, к, да, вот к тому, что у меня есть вот HashMap, и я ее хочу очень большую, вот у меня есть очень много данных, которым я хочу вращаться по ключу, мне надо хранить там, ну, много данных, это больше, чем у меня, это больше, чем у меня помещается в память одной машины, и опять же, это, забегая вперед, мы, наверное, поговорим про то, а что же с in-memory data grid сегодня, но вот там, если отмотать на, там, 10-15 лет назад, много данных, это было там, ну, 10 гигабайт в памяти, это было много данных, потому что, ну, больше, там, 30 гигабайт, мы с G1 GC в Java не хранили в памяти обычно, вот, сегодня, конечно, я могу засунуть там, там, какой-нибудь терабайт памяти на одну машину, подключить туда хороший garbage-коллектор, и все у меня будет хорошо, поэтому, что такое много, и в каком, в какой момент нам нужно идти в распределенную систему, оно, ну, сместилось за годы, и мы можем, мы можем дальше про это поговорить, но давайте пока что скажем, что, ну, реалистично, как бы, ну, вот, какие вам машины доступны, особенно, если вы не в облаке, а если вы в private cloud, ну, вот, какие машины вам предоставляет, там, ваш IT на работе, ну, наверное, там, у вас, там, 100 гигабайт в память на одну машину вы, там, положите, а больше, наверное, вы складывать не захотите, если вы хотите стейтлесс приложение, да, вы ж не хотите микросервер, чтобы у вас каждый микросервис имел 100 гигабайт, да, ну, тогда вам уже, ну, да, да, да, да, да, тогда вам, наверное, хочется иметь маленькие микросервисы, и где-то там, вот, удаленный, вот, этот, вот, сервер, у которого будет много, у которого будет много памяти, ну, в общем, в какой-то момент мы говорим, что у нас слишком много, слишком большая HashMap, чтоб она жила прямо, прямо в нашем приложении, да, мы хотим ее куда-то вынести, и она, вообще, слишком большая, чтобы жить на одной машине, мы хотим, чтобы эта HashMap, она как-то, вот, хранилась распределенно на нескольких машинах.
[27:32] Александр: То есть мы хотим порезать ее на части, как бы, да, и разослать каждый кусочек на какую-то машину, и получить какой-то API, как будто бы мы про эти части пока не знаем, ну, или не хотим думать.
[27:42] Станислав: Именно так. Это очень старая проблема, это то, что называется DHT, Distributed Hash Table, есть много пейперов, которые объясняют, как это делать. Обычно это сводится к тому, что у нас есть, и как раз как бы почему нам, почему важно, что у нас здесь есть хэш? Обычно все сводится к тому, что у нас есть какие-то ключи, мы эти ключи каким-то образом хэшируем, в соответствии с этими хэшами мы группируем в. иногда это называется шарды, иногда это называется партиции. Давайте для нашего разговора сегодня называть это шардами, потому что партиции. наверное, партиции здесь более правильное научное слово, но партиции есть еще в традиционных базах данных, они там означают немножко другое. Давайте называть это шардами. Мы группируем наши ключи в шарды, и эти шарды раскидываем по нашим серверам. Шардов у нас обычно там в разных системах немножко по-разному, но нам скорее всего потребуется как минимум столько шардов, сколько у нас суммарно есть процессоров, то есть сколько у нас суммарно есть ядер в нашей системе, то есть если у нас там 10 серверов и у каждого по 8 ядер, то наверное мы захотим там как минимум 80 шардов. Если мы. там разные системы делают там больше, да, там Apache Ignite 2 знаменитый использует там 1024 по умолчанию, сейчас поговорим там почему, Hazelcast использует, по-моему, там какое-то магическое число, что-то типа 271, тоже можем там поговорить почему, но там короче, есть какие-то вот такие магические числа, с которыми. дефолтные для in-memory data grid, с которыми они там. количество шардов, которые они используют. И, соответственно, так как у нас хэш-функция определена, функция, которая превращает хэш вашего ключа в номер шарда, она как бы определена, и мы всегда знаем, какой ключ на каком шарде будет лежать. Что нам это дает? Нам это дает то, что мы можем предсказуемым образом раскидать ключи по распределенным серверам, и это дает нам еще один бонус, про который мы еще не говорили, то, что если у меня не один инстанс моего микросервиса, а их много, у них теперь как будто бы общая вот эта вот хэш-мапка, да, потому что эта хэш-мапа, она лежит там где-то отдельно на этих серверах, и каждый из микросервисов знает на какой шард, на какой инстанс вот этого гряда ему надо пойти для того, чтобы найти каждый ключ, да. Это вот самая такая основа основ распределенного key-value-сервиса.
[30:34] Александр: Ну, здесь, смотри, я бы подсветил тот факт, что когда мы говорим шард, мы больше все-таки, наверное, говорим про логический шард, то есть…
[30:42] Станислав: Логический шард.
[30:44] Александр: Да, сущность логическая, мы отдельно должны, ну, условно, мы можем однозначно отобразить пространство ключей на пространство шардов, а дальше по каждому из пространства шардов мы дополнительно, обладая какой-то мета-информацией, можем отобразить его на пространстве серверов. То есть это вот как бы в два шага мы идем, то есть как узнать, на какой сервер мне идти, ну, вот сначала берешь шард, а шард мапишь на сервер. И здесь такое, что, типа, шард — это не сервер, потому что, ну, понятная проблема, что сервер может перезагрузиться банально, вот, или выйти из строя, вот. И тогда, как бы, что делать с данными, вот, какие техники в этот момент используются, чтобы данные не потерять, потому что распределенная система, она должна какие-то гарантии, там, отказоустойчивости предоставлять, какие-то договоренности должны быть, вот.
[31:41] Станислав: Во! Ты отличные два топика вот следующих поднял, я бы сначала, перед тем, как говорить про отказоустойчивость, я бы, наверное, закончил про шардирование, да. То есть мы начали с того, что сделали какой-то, ну, мы перепрыгнули этот момент, но понятно, что у нас появился там какой-то клиент-серверный протокол сетевой, да, по которому клиенты общаются с вот этим, с вот этими key-value хранилищами распределенными. У нас есть какой-то алгоритм шардирования, по которому мы превращаем ключ в номер вот этого логического шарда. Ты правильно затронул то, что нам теперь эти логические шарды надо распределить по физическим серверам. И вот здесь как раз появляется вот эта вот магия, которая немножко разная во всех системах. Есть системы, в которых маппинг шарда на физическую ноду прибит гвоздями. Вот, например, есть еще одна такая популярная в СНГ система, которая называется Tarantul, в которой сейчас, насколько я помню, ну, уже достаточно давно, там тоже есть эластичное вот это вот шардирование, и там шарды сами находят, где им сохраниться, но насколько я помню, в первых версиях там надо было гвоздями прибить, что вот шард номер один идет на сервер номер один, там шард номер два идет на сервер номер два. Какие-то похожие штуки, насколько я помню, тоже были в старых версиях Redis, которые не in-memory data grid, мы, кстати, не поговорили про то, как бы какие у нас вообще технологии, давай про это, наверное, в следующем пункте. Ну, вот, например, Redis, который не in-memory data grid, а скорее более простая технология in-memory кэша, да, но тоже фундаментально key-value распределенное хранилище. Вот там в Redis тоже когда-то надо было прибивать гвоздями, там, руками определять, какие ключи куда ходят. В in-memory data grid’ах это обычно эластично, то есть пользователю, программисту не надо про это думать, определил там какой-то пространство ключей, раздал системе сервера, система сама там как-то раскидала эти шарды, партиции по серверам. Как она это делает? Она это делает с помощью какой-то функции хешинга, обычно это либо какой-то consistent hashing, либо rendezvous hashing, не будем, наверное, углубляться в детали этих алгоритмов, все могут про них много прочитать, про consistent hashing, мне кажется, написано в каждой книге про system design, в rendezvous hashing это такое, в Apache Ignite как раз он использует, он традиционно используется, это такая более specialty штука, но главное, чего пытается достичь вот этот хешинг алгоритм, он пытается взять вот это наше фиксированное количество наших шардов и более-менее равномерно распределить их между серверами, плюс распределить их таким образом, чтобы если у нас меняется количество серверов, то есть сегодня у меня там 5 машин, а завтра у меня 10 машин, чтобы у меня распределение шардов между серверами не сильно менялось, вот это, например, то, как раз, что делает rendezvous hashing в Apache Ignite, когда я скалируюсь с 5 серверов до 10, алгоритм вот этого хеширования, он делает так, чтобы между старыми 5 серверами у нас трафика не было, то есть чтобы они старые 5 отдали какие-то шарды новым 5, но не обменивались информацией между собой, то есть мы как бы оптимизируем вот эту процедуру, скалирование вверх.
[35:19] Александр: Ну, это интересная, кстати, тема, которую можно подраскрыть, потому что пока ты не занимаешься написанием подобных систем, с подобными проблемами редко сталкиваются разработчики, что есть хеш и хеш, да, казалось бы, что плохого в алгоритме хеширования, не знаю, там, в самом простом, не знаю, берем длину строки и умножаем на 32, грубо говоря, помимо того, что у него распределение будет плохое, но допустим, у нас все строки разных длин, вот, и мы просто вот берем длину строки за хеш, наивный очень алгоритм хеширования, но проблема в нем, какая будет, если ты разрабатываешь систему, которая хранит большое количество данных, распределенную, вот, с которой, как бы, большинство разработчиков сталкивается, с тем, что когда настанет момент, а он настанет, что у тебя одна нода или несколько нод выйдет из строя, выйдет из кластера, или добавится несколько новых нод, то в чем проблема? Вот у тебя есть пять нод, да, вот прям рисуем себе картинку, пять нод, мы равномерно нашим алгоритмом хеширования string-lens распределили между этими по, там, остатку отделения на пять серверами данные, у нас вот прям равномерненько на каждом, там, по 10 гигабайт лежит, а лучше давай по 100, чтобы, вот, понять, что это не так уж и, ну, операция сложная, вот по 100 гигабайт данных у нас лежит на каждом сервере, они распределены, и дальше происходит следующее, один из серверов, допустим, выходит из строя, или нет, давай, нет, давай добавим, чтобы попроще чуть было, добавляем еще пять серверов новых, у нас стало 10, вот как теперь наша система выглядит, для то, что у нас на пяти серверах старых есть по 100 гигабайт данных, а на пяти серверах новых по нулю, и, как бы, это не сбалансированная система, то есть мы не для этого вводили новые ноды, ну, изначально так, чтобы данные продолжать хранить на пяти, ну, как минимум, у нас теперь что происходит, мы теперь остаток отделения будем брать не на пять, а на десять, и вот здесь возникает первый челлендж, если мы берем остаток отделения на десять, то мы половину ключей будем продалбывать, потому что если мы пойдем читать новый, старый ключ, который мы положили, используя остаток отделения на пять от нашей прежней топологии, а теперь идем и берем его читаем, то есть мы сделали put, между этим, после этого put произошло то, что добавили пять новых серверов, у нас сервер стал, кластер стал из десяти нод, мы делаем теперь get, и вот этот get, если пойдет, остаток отделения на десять возьмет, а у нас вдруг там семерка остается, да, и мы как бы не на вторую ноду пойдем, а на седьмую теперь, и там не окажется этого ключа, то есть мы ноды ввели, а данные, как бы, ну, условно, логически, потеряли, то есть потому что мы в поиске их учли, что мы теперь делим на десять, но не переместили их физически, и это вот второе, что нам надо сделать, помимо того, что мы делим на новое количество нот, мы теперь должны еще и как-то со старых нот, ну, то есть сделать ребалансировку, так, наверное, называемую, то есть мы данные должны пошафлить, то есть чтобы теперь новые клиенты ходили по новому алгоритму и находили эти данные, и здесь возникает челлендж, что мы не можем просто так взять все, перешафлить, потому что будет у нас нагрузка на сеть огромная внутри кластера, то есть мы, по сути, переложим очень много данных, то есть как будто бы у нас эти пятьсот гигабайт, мы заново сделаем insert, ну, вот, по новому алгоритму, при том, при всем, что там еще, наверное, какой-нибудь коммутативный, может быть, эффект кумулятивный, что когда это все начинают делать, оно еще тяжелее становится, забывая, вообще не думая о том, что там garbage collector в этот момент думает, и в целом, как себя свитчить, сетевой виде, короче, просто, допустим, этого всего нет, просто переместить данные тяжело, и как раз для этого, чтобы минимизировать количество пересылаемых данных во время ребалансировки, и созданы вот эти вот алгоритмы rendezvous hashing и consistent hashing. Вот я так своими словами переговорил, может быть, я что-то упустил. Стас, мне кажется, я немного сложновато своими словами это все пересказал, я думаю, что ты можешь попробовать чуть-чуть пересказать попроще, как ты это видишь, как ты это понимаешь, потому что, мне кажется, это важно, понять именно вот эту картинку. Почему нам нужны какие-то сложные вот эти алгоритмы распределенного хеширования, там, consistent hashing, rendezvous hashing, самый простой пример.
[39:49] Станислав: Давайте скажем, вот у нас есть 20 шардов, они лежат на четырех узлах, то есть там по пять шардов на узел. Они туда попали каким-то рандомом, да, то есть у нас есть, так как это все хеширование, да, там, у нас на первом узле может быть там пять шардов, там, номер один, номер пять, номер восемь, номер там четырнадцать, номер семнадцать, да, и так дальше, то есть они случайный какой-то набор. Мы добавляем еще несколько узлов, и нам на вот эти новые узлы нужно часть вот этих шардов переложить. И так как у нас, если, точнее, если у нас алгоритм хеширования, он, ну, рандомный, да, то у нас не только на новые узлы появятся, на новые узлы переедут старые шарды, но у нас и на старых узлах могут оказаться, вообще, им могут получиться вообще другие номера на них попасть. То есть, если мы каждый раз независимо пересчитываем от текущей топологии, от текущего набора узлов, мы заново генерируем вот эти рандомные шарды, которые на каждый узел пойдут, то мы будем с каждого сервера присылать данные на каждый, при каждом изменении топологии, да. А мы этого не хотим, да, ну, потому что нам оптимально. Нам не нужно пересылать данные там с сервера 1 на сервер 2, да. Нам нужно только с сервера 1 и с сервера 2 пересылать там данные на там сервер 5 и сервер 6, которые мы только добавили. То есть только со старых на новые пересылать. Мы не хотим между старыми пересылать какие-то данные. Грубо говоря, вот ради этого, во-первых, и ради равномерного распределения партиций, ради равномерного распределения шардов, во-вторых, И существуют вот эти вот более сложные алгоритмы consistent hashing, rendezvous hashing и некоторые другие. То есть все, что мы хотим, это минимизировать трафик, когда у нас топология меняется, когда у нас добавляются или уходят сервера. Все эти алгоритмы, они не без своих downside’ов каких-то. Например, там вот алгоритм, который в Apache Ignite традиционно используют в rendezvous hashing, он минимизирует трафик, когда меняется система, но он не гарантирует максимально равномерное распределение шардов. То есть вот он дефолтится в 1024 партий, 1024 шарда логических. Если у меня 10 узлов, ну, я должен на каждый получить примерно 100, примерно 102 партий, 100. Я не получу ровно 102 партий, на каждую, или даже давайте, чтобы нацело делилось. Вот, скажем, что у нас есть 1024 партий, 1024 логических шарда. У нас есть 16 узлов. В идеале мы должны на каждый узел получить ровно 64 шарда, потому что деление нацело. Вот, например, алгоритм, который используется в Apache Ignite, он не гарантирует вам, что у вас будет ровно 64. На каком-то будет 65. На каком-то будет 63. Там есть такое эмпирически найденное правило, что, ну, его на самом деле математически можно посчитать, что вот, если у нас количество шардов на два порядка больше, чем количество узлов, то вот Apache Ignite, он там примерно равномерно все делит, ну, и там будет там отклонение в 1-2%, оно, в общем, не повлияет на вас. Ваш workload. Если у вас там, например, 1024 шарда и 200 узлов, то у вас на какие-то узлы появятся там 4 партиции, на каких-то будет 6 партиций, у вас разница в 30% в нагрузке на узлы, у вас очень неравномерно пойдет нагрузка, и очень плохо будет загружена система. То есть вот такие там есть нюансы, это вот мы, мы на самом деле, мы еще в in-memory data grid даже не пришли. То, о чем мы сейчас говорим. Это такой классический, вот опять же, любую книжку, дистрибьютед хештейбл, дистрибьютед хештейбл, да, классический дистрибьютед кей-вэлью стор, вот пока что проблему. То есть уже вот на этом, вот на этом уровне мы нашли первый такой вот сложный кусок. Это алгоритм распределения данных, который будет дружелюбен к тому, что кластер меняется, становится больше, становится меньше. Дальше, Саша, ты говорил про то, что мы делаем с high availability, да. Здесь тоже, мы еще пока не приходим в in-memory data grid, это такая традиционная штука для любого распределенного сториджа. Чтоб у нас был high availability, нам нужно иметь несколько копий данных. Ну, чтобы если у нас одна, один узел упал, чтобы на каком-то другом узле эти данные тоже были. Даже если нам эти данные не нужны, ну, скажем, это там какой-нибудь session cache, да, ну, поверьте, если у вас там данные сессии, там какие-нибудь, я не знаю, корзина какая-нибудь в интернет-магазине, даже если это данные, которые вы в принципе можете потерять, если каждый раз, когда у вас сервер перезагружается, вы будете терять там корзину для какого-то процента пользователей, никто вам спасибо не скажет в бизнесе. Поэтому вам нужно какое-то распределенное бэкопирование. Тут, кто влезет, кто подрова, все системы используют какую-нибудь разную технологию, как получить несколько копий данных, которые друг с другом согласованы. Там разные версии Apache Ignite используют разные системы, у Hazelcast там разные системы в комьюнити-версии, в интерпрайз-версии. Многие из них, они такие немножко доморощенные, потому что опять же, in-memory data grid и технологии в основном, чуть более старые, поэтому они, например, кто что придумал, например, в начале десятых, вот так оно и работает. Сегодня дефолтным способом иметь одни и те же данные на разных узлах, то есть иметь несколько копий одного и того же состояния, это протоколы типа Raft или Paxos. Ну, чаще всего это просто протокол Raft, опять же, в интернете много про него можно прочитать, посмотреть всякие визуализации, он очень хорошо объяснен, и он, наверное, чаще всего сейчас используется чем что-либо во всех современных базах, и опять же, современные версии Apache Ignite на нем работают, и коммерческие версии Hazelcast, я знаю, на нем работают. Практически любые распределенные базы, которые вот уже позиционируют себя именно как базы данных, скорее всего, работают на чем-то типа Raft. Что такое Raft? Это, ну, опять же, не будем, наверное, углубляться, или, я не знаю, Саша, может, ты захочешь немножко копнуть, но.
[47:19] Александр: У меня на канале в Ютубе есть видео, где, по-моему, часа три Матклад объясняет Paxos. Он рисует, и, честно говоря, вот он, ну, вот с Paxos и с Raft такая штука, что вот пока ты, мне кажется, его не напишешь, ты глубинно и искренне до конца ты не поймешь, как он работает. Я не знаю, может, супермозги какие-то и понимают с первого раза, но либо это самообман, либо это реально супермозг, потому что, ну, всегда сложно. В подкасте на визуале это, ну, без визуала, точнее, мне кажется, это нерешаемая задача. Человечество еще не придумало аудиоверсии объяснения Paxos, чтобы всем было понятно. Поэтому, кому интересно, попробуйте. У меня на YouTube-канале «Тысяча фичей». Матклад объясняет Paxos. Посмотреть для общего развития, да. Но здесь больше, наверное, знаешь, мне интересно было бы, а какую задачу, какую задачу решает Raft в репликации? Именно, то есть, зачем он там нужен? То есть, распределенный консенсус, по сути, вот, если совсем, как детям, сказать, что нам надо договориться всем нодам об одной циферке. Вот по факту. И вот эта циферка, она зачем нужна? То есть, что должно быть консистентно? Вот, да, вот давай так. Не как консистентность на уровне алгоритма достигается, а зачем нам консистентность при high availability, когда мы делаем репликацию?
[48:43] Станислав: Вот если на пальцах, то, опять же, возвращаемся к нашему примеру, да, у нас есть какие-то микросервисы, у них есть вот эта одна огромная распределенная мапа, в которую они все хотят писать. Когда я пишу в какую-то мапу в своем приложении, ну, вот в локальную, в обычную, Java, я, в общем, знаю, что я, когда в нее написал, я после этого то, что я туда записал, прочитаю, оно никуда не потеряется. Вот в распределенной системе это, это очевидность, она становится гигантской проблемой, потому что я куда-то там записал, это на каком-то сервере, этих серверов у меня на самом деле там единицы, десятки, сотни, у меня есть сколько-то копий, вот, сколько-то копий вот этого значения, которое я там записал, записал и какой-то объект сессии туда, да, и вот у меня есть много-много копий, которые должны быть одинаковые. Если я записал, а потом какой-нибудь сервер упал, и я это обратно не прочитаю, ну, ну, у меня приложение неправильно будет работать, правильно, оно же не ожидает, что данные будут потеряны. Соответственно, мне нужен какой-то алгоритм, который убеждается, что у меня эти, то, что было записано, оно потом никуда не пропадет и будет, и будет также прочитано. Это вот совсем на пальцах. Есть еще один момент, про который надо, конечно, говорить, это знаменитая, наверное, многие слушатели знают CAP-теорема, consistency, availability, partition tolerance. Что она означает? Она говорит, что если у нас в системе существует network partition, то есть у нас система может, у нас может поломаться сеть, и у нас часть серверов перестанет видеть другую часть серверов. Вот у нас было две серверные стойки, и кто-то между ними перерезал кабель, там, ну, как это, уборщица, уборщица, понятно, выдернула кабель. И раз, раз уборщица выдернула кабель, значит, у нас вот теперь две серверные стойки друг с другом не разговаривают, и в этой ситуации CAP-теорема нам говорит, что мы должны выбрать, у нас система будет либо consistent, либо available. Consistent, это означает, что вот эти две серверные стойки, они все еще будут хранить одно и то же значение, да, и мы не сможем, скорее всего, записать новое, потому что, чтобы нам записать, они должны друг с другом поговорить и реплицировать данные друг с друга. Либо система у нас будет available, что означает, что да, две серверные стойки разговаривать друг с другом не могут, мы будем в них независимо писать, у них данные разъедутся, потому что мы один инстанс микросервиса написал в одну серверную стойку значение А, другой записал в другую серверную стойку значение Б, теперь у нас там два разных состояния одной и той же сессии, да, по одному и тому же айдишнику живет в двух половинках, и мы потом никогда это обратно не слепим. Вот Raft, он как раз помогает нам имплементировать, в частности, консистент поведение, то есть Raft как консенсус протокол, он говорит, что нам нужно, чтобы все или там больше половины членов нашего кластера приняло новое значение, чтобы оно считалось записанным. Соответственно, если у нас произошел split brain, если у нас произошел network partition, то мы в одну половину там записать не сможем, и Raft, соответственно, нам скажет, что нет, извините, пока что мы не можем записать новое значение, мы не available, но остаемся consistent. Есть другие протоколы, которые иногда это модификации Raft, такие сложные, иногда это совсем другие протоколы, которые вообще выбрасывают вот эту идею о консистенции, или частично ее выбрасывают и говорят, что не, мы, когда будет split brain, когда у нас будет вот это партиционирование в сети, мы будем продолжать работать, мы будем продолжать принимать записи, но понятно, что данные у вас разъедутся между разными кусочками сети, и после этого ну, вы сами там должны придумать, каким образом, ну, что вы будете с этим делать. Это вот такой второй, наверное, большой, сложный кусок, как нам реплицировать данные, и при этом, либо оставаться консистентными, либо каким-то образом сохранять availability, но потом что-то делать с неконсистентностью. Разные системы это решают по-разному, возвращаясь к тому, что у нас тут все-таки про in-memory data grid в первую очередь, in-memory data grid традиционно были high-availability-first системами, то есть они решали, что если у нас split brain, то скорее всего у нас данные разъедутся, и вы там как-нибудь сами. Потому что in-memory data grid, они были направлены в первую очередь на высокую производительность, на низкую латенси, именно поэтому мы все держим в памяти, и обычно в таких системах availability важнее, чем консистентность, то есть это не совсем база данных. В современных in-memory data grid, ну и в современных системах, если посмотреть, то там немножко другая ситуация, то есть там последняя версия Apache Ignite, она использует, она по умолчанию, consistent, а не available. Опять же, Hazelcast тоже там имеет только коммерческую решение, но тоже имеет консистентную систему. Всякие другие системы, например, там у Coherence, по-моему, нет ничего для consistency, насколько я помню, но он является частью экосистемы Oracle, и предполагается, что если тебе нужна консистентность, то ты всегда можешь пойти в сам Oracle, вместо того, чтобы идти в Coherence. То есть сегодня все понимают, что, ну, где-то в твоей платформе у тебя должен быть консистентный data storage. Даже если у тебя это не твой data grid, то значит ты должен дать какой-то другой data store, да, вот в твоей большой платформе, в твоем стеке, который будет консистентным. Вот это наша вторая такая большая, второй большой кусок.
[55:08] Александр: В наших вот этих сущностях, которых мы распределяем, мы распределяли нашу HashMap, мы там добавили шарды, да, добавили сервера, и потом мы немного, мне кажется, чуть-чуть перепрыгнули, не сказав, что у шардов, наверное, вот когда ноды могут выходить из строя, у них появляются реплики в каком-то виде, да? Вот мы, ну, то есть вообще про какую консистентность мы говорим, если у нас есть как бы ключ-значение, берешь ключ, идешь на ноду, читаешь данные. Что? Вот они лежат. О чем вообще, собственно, предмет дискуссии, а в том, что у нас на ноде, не на одной ноде-то данные лежат, если мы говорим про доступность. То есть вот это суть распределенных систем, что данные всегда в каком-то виде они дублируются на какие-то другие машины. То есть у нас никогда на одной машине в эксклюзивном виде, если мы там специально настройками не поигрались, данные не лежат. Иначе это, ну, как бы наивная версия распределенного мапы, а не продакшн, скажем так, серьезная система. То есть, чтобы данные не терять, их надо дублировать. Чтобы их дублировать, нам надо знать, а где лежат дубли. И вот как раз для того, и где самая актуальная версия, на каком из серверов самый актуальный дубль. Точнее, не дубль, а данные. Потому что теперь у нас может быть разъезжаться как? Вот мы записали там по ключу 1 значение А. Окей, потом по ключу 2 значение Б. Потом у нас эти значения А и Б продублировались на какие-то ноды. У нас там их стало в 3 раза больше, если у нас степень репликации 3. И вот теперь мы хотим прочитать значение А. На какой сервер мы пойдем? На тот, на который записывали, или на тот, на которых есть дубликаты, реплики так называемые? Мы, конечно, пойдем, наверное, на тот, на который записывали. А вдруг он перестал работать? И вот тут вопрос, а куда идти? Первый, ладно, прочитав-то мы куда-то сходили. А вот теперь мы пишем новое значение, и в этот момент сервер вернулся оригинальный. Вот как вот эти тонкости разруливать? Конечно, мы сейчас подкастик там не разруливаем нормально на пальцах, но вот ради этого и, мне кажется, существуют вот эти системы консенсуса. То есть консенсус существует, договориться. То есть где-то должна быть Oracle, не база данных, а вот какой-то такой вот сущность, к которой можно обратиться и всегда знать, что это правда. И вот Raft, многие базы данных используют ZooKeeper, например, просто для того, чтобы делегировать такие вещи. Упомянутый Витя Гамов, он был, по-моему, девелопер-адвокатом Kafka какое-то время, и вот Кавка огромное количество времени использовала ZooKeeper для того, чтобы как раз вот эти вещи там разруливать тонкости. А то есть, наверное, у конкретного продукта Apache Ignite есть разные способы. Вот мы, когда про Кассандру говорили, там есть много способов того, а когда считать значение записанным в систему. Вот тут тоже это про репликацию. Вот я иду и пишу значение на сервер. Допустим, у меня три реплики, да, то есть у меня есть primary реплика и две там secondary. Вот в момент, когда я записал на сервер на primary, я уже могу сказать, как система, что я значение записал? Или я должен дождаться записи и на другие сервера, сказать, что вот я записал на два сервера, теперь я точно эти данные не потеряю, и вот теперь-то запись клиент дорогой произошла. Вот с точки зрения настроек, какие есть способы здесь?
[58:42] Станислав: Вот здесь по-разному, в разных системах, да, правильно ты говоришь, в Ignite традиционно вот у нас как бы есть сейчас две большие версии Ignite 2 и Ignite 3, да, Ignite 3, который там на других совсем рельсах живет, и традиционно в Ignite 2 это было то, что называлось Primary Sync и Full Sync, то есть мы записали только на primary и решили, что раз на один сервер записали, ну, тогда уже считаем, что записали. Это ведет к понятным проблемам, то есть, понятно, латенсы ниже на запись такой ситуации, но это ведет к понятным проблемам, что как бы мы записали на primary, а primary тут же упал, да, и он, ну, на вторую свою копию, на вторую, третью, там, сколько у него дополнительных копий, он туда данные не реплицировал, соответственно, мы запись свою потеряли. Что может быть вполне, ну, разумно для определенного класса систем? Есть режим Full Sync, когда мы записываем сразу на все, то есть мы ждем, клиент ждет, пока все реплики не будут обновлены с новой записью, и только тогда мы считаем, что мы все закончили, все записано. Ignite 3, вообще, обычно системы на основе Raft, они по-другому себя ведут, там мы обычно ждем консенсуса, то есть консенсус обычно, ну, это majority узлов, большая часть копий, которые у нас есть, то есть если у нас есть 5 копий, то мы ждем, пока мы запишем на 3, если у нас 3 копии, мы ждем, пока мы запишем на 2. Здесь мы получаем, наверное, лучше из обоих миров, мы с одной стороны не теряем данные, если у нас упал один узел, то есть у нас данные уже четко записаны в систему, и с другой стороны мы, если у нас, например, там 5 узлов, и один из них четко затупил, нам не нужно ждать вот того, который затупил, чтобы завершить запись, чтобы завершить операцию. Он, когда развиснет, он вернется, он вот эти свои операции, которые произошли, пока он тупил, он их как-то догонит, но мы можем не терять доступность из-за того, что один узел отвалился. Ну, вот это, наверное, современный такой вот дефолт. Здесь если говорим уже про разные системы, мне вот нравится, как это решено в MongoDB на самом деле, которое совсем не InMemory, не DataGrid, но вот традиционно, наверное, потому что вот была вот эта NoSQL-волна, и там все должно было настраиваться с точки зрения консистентности. У них, насколько я помню, прям настройка есть, которая, по-моему, называется Write Concern, когда ты прямо указываешь, а сколько копий должно быть записано, прежде чем я завершу вот эту запись. Она вот даже, сам факт того, что она есть вот в API, мне вот как разработчику таких систем, мне вот нравится, потому что это как бы сразу пользователю показывает, что мы не обязаны ждать всех серверов, мы не обязаны ждать там больше одного, да, то есть ты сам решаешь, да, и ты сам можешь это выбирать на стороне клиента, насколько отказоустойчивой должна быть конкретная вот эта запись, которую ты делаешь сейчас. Кажется, это такой, ну, может быть, усложнение, API, но мне кажется, что оно вот в итоге делает систему прозрачнее и понятнее. Да, да,
[1:02:18] Александр: Окей. Ну, в общем-то, мы сейчас потихоньку уже начали говорить про другие системы, и здесь, наверное, можно продолжить, потому что систем огромное количество, вот, и в том даже не самих конкретных систем, а классов систем. Вот когда мы говорим про хранение данных, мы сейчас поговорили, наверное, про самый примитивный, самый вот первый способ, а как данные можно хранить, это key-value, потому что, ну, даже банально там, то есть, когда мы говорим про хранение данных, мы учим программирование, массив ассоциативный, это, наверное, первая структура данных, которую мы учим, по факту HashMap это тот же самый ассоциативный массив, просто у него индексация может быть по разным ключам, вот, и такая она, ну, то есть ассоциация все то же самое есть. Но если мы говорим про вообще хранение данных у нас в системах, то есть разные способы, как это все делать, помимо key-value, какие есть в целом альтернативы, или там даже, скорее всего, будет правильнее построить вопрос так, а каким системам key-value in-memory data grid являются альтернативой?
[1:03:27] Станислав: Вот, да, давай вот про это поговорим. На самом деле, вот то, о чем мы сейчас проговорили, да, опять же, мы еще не дошли до того, что делает in-memory data grid in-memory data grid. Мы пока про хранение. Да, то есть мы пока что проговорили, то есть, то есть, вот сумма того, чем мы сказали, то есть key-value, распределенное хранение, вероятно, какой-то консенсус, вероятно, какая-то репликация данных, какое-то там хэширование, но это то, что называется там распределенный key-value-store или распределенный кэш. То есть там, когда вы говорите, когда вы думаете там про Redis или про там какой-то шардированный сетап Memcached, вероятно, вы имеете в виду вот что-то такое. Поверх этого, что мы можем, ну, что мы можем таким, и, кстати, в основе всего, чем вы пользуетесь сегодня, наверное, кроме самых таких классических кондовых RDBMS, типа там Postgres, MySQL, MariaDB, там Oracle SQL Server, да, вот они, у них нет key-value внутри нигде. Если вы смотрите на современные, на in-memory data grid, то, кстати, у них вот в самом вот в самом сердце у них какой-то вот такой key-value-store, да, и все, что, все остальное, что вы видите, оно накручено сверху. Если вы смотрите на современный distributed SQL, какой-нибудь YugabyteDB или CockroachDB, там внутри на самом деле какой-то key-value-store, а все, что вы видите, это на самом деле накручено на него сверху, но внутри оно все равно каким-то образом делает key-value хранилище. То есть с точки зрения того, какие интерфейсы мы можем, интерфейсы доступ к данным мы можем предоставлять. Ну, здесь основное у нас что есть? У нас есть key-value, который адресация по ключу, и key-value считается как не структурированными данными, да, то есть key-value у нас, мы ничего не знаем про то, что там, про то, что внутри этих значений, внутри этих ключей. Для нас это просто обычно какие-то там строчки или какие-то binary блобы. Есть документные базы типа MongoDB, у которых структурированы структура, то, что называется полуструктурированность, semi-structured. Ну, что это означает? Это означает, что у нас, наверное, есть какая-то структура, но вообще мы разрешаем какие-то рандомные поля туда добавлять, и мы умеем залезать внутрь наших данных, то есть как, например, в MongoDB мы можем залезть там, у нас хранится, грубо говоря, JSON, но мы можем залезть внутрь этого JSON и что-то там в нем найти. Просто у нас вот эта схема, она не прибита гвоздями, она у нас плавает, плавающая, скорее всего. И есть у нас структурированные данные, и структурированные данные — это обычно SQL, правильно? И SQL, там мы уже, мы заранее знаем, какие у нас колонки, это обычно реляционная модель, и мы можем по разным колонкам делать какие-то запросы, делать какие-то сканы, и какие-то более высокого порядка операции. Вот эти все разные интерфейсы, они перетекают друг другу, скажем так. То есть, есть, в чем вот первое такое большое отличие in-memory data grid от, например, традиционных key-value кэшей, в том, что in-memory data grid, они как раз приносят вот эту возможность обращаться к данным не только по одному ключу, а они дают еще какую-то возможность делать query. В случае Apache Ignite это прям настоящий SQL, естественно, он там, во-первых, он распределенный, и мы получаем распределённый доступ сразу к данным на разных узлах, и обращаться с этим точно так же, как вы обращаетесь с Postgres, будет неправильно. You will have a bad time. То же самое, не знаю, про Hazelcast, у которого SQL, ну, немножко, наверное, попроще, но там с другим фокусом. Есть там какой-нибудь, например, Coherence, у которого нет SQL, но у него есть там свой query language, который на SQL чем-то похож, он там еще проще. Есть Redis, который, ну, конечно, он не data grid, но у него тоже есть там какая-то возможность делать запросы не только по первичному ключу, да, и там тоже делать какую-то фильтрацию. То есть система так или иначе эти возможности прикручивает. Какие-то системы прикручивают и доступ к документным данным, доступ к JSON, но здесь больше различий, там, скажем, в системах, где есть SQL, это обычно делается через SQL, ну, то есть мы делаем SQL-запрос, и внутри SQL-запроса используем там какие-то JSON-функции. Но некоторые системы там пытаются прикрутить и более нативный JSON-саппорт, чтобы там напрямую конкурировать с той же MongoDB. Что нам нужно для того, чтобы это работало? То есть, ну, key-value понятно, как работает, и документы, наверное, оставим в стороне, но что нам нужно для того, чтобы у нас работал, ну, вот, какой-то более сложный…
[1:08:41] Александр: Ну, я бы здесь добавил вот еще в классификации систем, потихоньку теряющий актуальность, но все еще являющийся довольно важным момент, что у нас все-таки есть разделение внутри SQL еще, как правило, на аналитические системы и на системы транзакционные. Вот, потому что там начинает от того, что вроде бы SQL-1, да, то есть интерфейс-1, запросы одинаковые, и данные ты вставляешь, ну, как бы одинаково, под капотом совершенно разные классы систем, и вот, кстати, движки там будут прям максимально разные, условно, ортогональные, колончатые, строчные способы хранения данных, про это тоже, наверное, стоит отметить. И вот к чему ближе все-таки по use case это in-memory data grid?
[1:09:27] Станислав: In-memory data grid они почти всегда это транзакционные системы. Транзакционные системы, вот, если вы откроете сайт любого, любой технологии, которая там связана с in-memory data grid, там будет написано слово аналитика. Когда in-memory data grid говорят, что они технология для аналитики, они не имеют в виду, что не нужен вам никакой spark, не нужен вам никакой там, я не знаю, никакой hive, никакой snowflake, приходите на нас и делайте нас. Нет, идея не в этом. Идея в том, что можно делать на таких системах то, что называется decision support. Мы спрашиваем, мы задаем системе какой-то вопрос, она нам отвечает, да, мы там делаем какой-то query, она нам отвечает, мы на основании этого принимаем в нашей системе какое-то решение. То есть мы используем, мы можем использовать in-memory data grid, они очень часто так используются, для того, чтобы больше читать из них данные, чем писать данные. То есть это достаточно частый use case. Но то, как мы будем данные читать, это не будет, мы там закинули один запрос, который у нас будет работать час и перелопатит нам, сделает там full scan всей системы, да, как это делается в настоящих аналитических системах, в настоящем OLAP. Нет, это скорее OLTP-системы, которые заточены под то, чтобы делать, либо принимать нагрузку на запись, либо принимать нагрузку на чтение, но такую более точечную, то есть большое количество параллельных относительно небольших запросов или операций. Опять же, в случае каких-то систем это будут операции через server-side compute, про который мы еще поговорим, да, который вот, наверное, самая такая киллер-фича in-memory data grid. Иногда это будет просто SQL, да, но SQL такой вот, который в распределенной системе таргетирует там какой-то небольшой кусочек. Чаще всего вот хороший, признак хорошей системы, которая хорошо заточена под распределенное выполнение его там под SQL в in-memory data grid, это когда у меня запросы зависят только от одного шарда. То есть, когда вот я знаю, что у меня весь запрос, он будет выполнен внутри одного шарда. Мы сейчас, кстати, поговорим про то, а что значит запрос внутри одного шарда, потому что это связано с тем, как данные нам надо распределять и хранить. И ты правильно сказал вот еще по поводу того, что да, у нас же есть разные storage engine, да, то есть там, где мы данные-то, где у нас данные-то физически хранятся, там, в row store или в column store, и вот про это тоже можем поговорить. Я думаю, может, давай сначала поговорим про то, как у нас, откуда у нас SQL появляется для того, чтобы вот этот вот вопрос как бы закрыть.
[1:12:27] Александр: Ну да, вот вообще говоря, ты так ловко начал говорить про SQL, что вот способ доступа, перед этим мы говорили, вообще говоря, там, про HashMap, и вот как бы где HashMap и где SQL. Ну, в какой момент вообще он появился и зачем он нужен? Таким как бы а-ля примитивным системам, как вот in-memory data grid.
[1:12:49] Станислав: А в какой-то момент мы просто, ну, в реальной жизни да, многим приложениям, опять же, если мы храним, например, сессии, да, нам, ну, не нужно делать какую-то, скорее всего, нам не нужно делать какую-то там агрегацию или фильтрацию там между разными сессиями. Но если храним, мы, например, ну, то есть вот там in-memory data grid, часто используется для какой-нибудь финансовой информации в банках, ну, или там для трейдинга. Вот есть у нас какая-то информация с рынка, вот прямо сейчас, как какие акции торгуются, и мы хотим поверх этого запускать какие-то квари, вот прямо сейчас смотреть там какие-то балансы, там, в случае с этими, в случае с деривативными рынками считать там все эти Greeks, как они там называют, эти, альфа-бета, гамма, плохо, плохо я знаю мою, финансовую аналитику. Вот в этом случае мы уже захотим в такой же точной системе мы захотим интерфейс, который немножко посложнее, поумнее, чем просто обращение по ключу. Ну и что мы хотим обычно? Мы хотим чаще всего получить какой-то range данных, когда данные у нас сортированы. Мы хотим обращаться к данным не только по основному ключу, но и по secondary key, то есть мы там хотим, например, если у нас это стоки какие-нибудь, ну какая-то финансовая информация, то мы хотим это искать по времени, когда произошел трейд, по тикеру, на котором трейд произошел, может быть, мы хотим сагрегировать что-то по цене и так далее. То есть мы хотим, как мы обычно делаем это в SQL-запросах, и все вот это нас приводит как раз, да, и в конце мы хотим также, а у нас есть разные какие-то коллекции, у меня там есть две какие-нибудь HashMap, и я хочу между ними получить какую-то комбинированную информацию, ну то есть я хочу сделать join, и вот эта вот необходимость, номер один, получить range каких-то данных, когда они у меня сортированы, номер два, сделать какую-то агрегацию поверх вот этого range, и номер три, сделать join, вот это то, что нам приносит необходимость иметь что-то похожее на SQL. Здесь уже in-memory data grid, они различаются между собой, это, наверное, то, что их отличает вот как раз от более, от таких более простых технологий, как простые in-memory кэши, типа вот там Redis, Memcached, да, в которых там в Redis это есть на базовом уровне, а в Memcached, по-моему, нет совсем. Вот в in-memory data grid обычно есть, точно есть какой-то способ делать запросы по диапазонам, скорее всего, есть какая-нибудь агрегация, в некоторых есть join, причем, ну, там, не во всех. Есть вопрос, есть ли join, например, распределенные между разными, разными шардами. Да, вот Apache Ignite долгое время был и до сих пор остается лидером как раз вот в этой области, именно потому, что он достаточно свободно дает делать разные SQL-запросы, ну, привычным образом, там нет каких-то совсем сложных штук, типа там рекурсивных запросов или window-функций, но там, грубо говоря, join с GROUP BY, там, ну, на нем сделать можно. То есть мы хотим к модели данных физической, которая вот у нас есть изначально, то есть у нас есть какие-то данные в памяти, нам нужно быстро их обрабатывать, нам нужно, чтобы все в памяти жило. Мы хотим, чтобы вот к этому у нас еще появился вот такой более богатый SQL-интерфейс. Как мы это, как мы его добавляем технически, ну, это значит, что у нас вот это наш грид, вот этот наш кластер серверов, там еще должен появиться, собственно, какой-то SQL-движок, и этот SQL-движок должен быть, он не просто должен иметь парсить SQL, а он еще, должен быть интегрирован с вот этим механизмом шардинга, который у нас есть. То есть SQL-движок знает, где какие данные лежат, знает, куда надо отправлять запрос, и если мы хотим не совсем простые запросы, когда вот есть совсем простые запросы, которые работают на одном шарде, то есть я там, я отправляю данные, и там каждый шард, как бы их обрабатывает, отправляю запросы, каждый шард его обрабатывает по отдельности. А мы обычно хотим все-таки сагрегировать между шардами тоже. Или, например, хотим join сделать. Да, потому что мы, я как пользователь, я не хочу там получать 50 ответов, да, от каждого шарда по одному, и там последнюю милю делать где-то уже на приложении. Я хочу, чтобы мне сервер отдал, как бы, готовый результат. И это означает, что вот этот SQL движок, он должен еще и понимать, что у него бывают частичные результаты от каждого узла, и потом он получает полный результат. И вот этот полный результат он должен отдать уже. Вот это то, что как раз вот здесь, это, наверное, то, что отличало in-memory data grid ну, скажем так, ранние, вот там, в десятых, и делает их сродни вот есть другие технологии типа in-memory databases, IMDB, есть просто распределенный SQL, распределенные базы данных типа, опять же, YugabyteDB, CockroachDB, или какой-нибудь там на Амазоне, какой-нибудь Авроры, например, да, которые предоставляют тоже, вот такой распределенный интерфейс, вот это все, вот все эти технологии, вот это вот умеют.
[1:18:18] Александр: Перейдем к тому, как это физически хранится, может быть, тогда, потому что мне кажется, что мы уже к этому подошли вплотную. Я подумал, как бы я реализовал агрегирующий запрос с GROUP BY, в этом случае, когда там распределенные данные по нескольким шардам с фильтрацией, то есть, как бы я группировку бы реализовал, я в этом просто как инженер улетел немножко, так, на минутку, начал такой, блин, это надо сначала там так сделать, а если там prefilter есть, то это. Короче, это к чему? К тому, что штука-то очень сложная, вот в плане, она максимально нетривиальная, потому что, по сути, это поддержка, ну, языка доступа к данным, который довольно сложный, к которому есть большие требования в том плане, что ну, есть уже пользователи, которые привыкли к тому, что джойны работают, к тому, что GROUP BY с HAVING работают, агрегирующие функции есть, оконные функции, ну, оконные, это для совсем прям аналитиков, но какой-то набор ожиданий к SQL есть, и когда мы говорим, что SQL работает поверх какой-то вот такой структуры, которая распределенная, причем изначально задизайненная не для того, чтобы SQL работать, а просто сама в себе, key-value и хранилка, а возникает огромное количество нюансов, которые делают вот эту задачу, поддержать SQL, довольно сложной, ну, прям очень сложной, и вносит ряд ограничений в то, какие операции работают, какие нет, с какими там условностями и так далее. То есть здесь как бы нужно понимать, что это не просто, знаешь, мы там API поддержали, и вот мы можем сказать, что мы там SQL SQL рознь, вот к чему я, есть всякие разные стандарты, но банальный SELECT * FROM table, то уж, точно работает, из фильтрации какой-то базовой. В том плане, что где ограничения-то у этого есть, вот распределенные джойны, наверное, это может быть одно из этих ограничений, или, например, вот в Apache Ignite даже этих ограничений нет. Ну что тут он не должен уметь делать?
[1:20:26] Станислав: Ну, ограничения, да, ограничения, они возникают в тот момент, вот всегда, вот смотрите на SQL-запрос, вот, и я делал это много-много-много раз с людьми, которые как раз приходили от, скажем так, локального SQL, да, single-node SQL к распределенному. Смотрим на запросы, думаем, я всегда вокруг какой-то одной сущности делаю запрос, да, я могу каким-то образом свой SQL написать так, или выполнить так, чтобы знать, что все мои данные лежат в одном месте, или мне всегда нужно будет данные забирать со всего кластера, да, со всех шардов моих, со всех узлов. И в тот момент, когда мне нужно делать это распределенно, когда мне нужно делать это со всех узлов, ну, скорее всего, такой вопрос, запрос будет выполнять сложнее, какие-то из них будут выполняться просто, ну, с оверхедом, ну, потому что распределенная система, нам теперь надо данные какие-то гонять по системе. Какие-то из них просто выполнять будет, ну, сложно.
[1:21:30] Александр: Вопрос. Я, как пользователь подобной системы, в которой есть как бы SQL-интерфейс поверх и и данные хранятся на многих нодах, и это как бы, знаешь, то, что данные хранятся на нескольких нодах, это как бы такой first-class citizen, понимание системы. То есть я как бы, в мою модель того, когда я работаю с системой, это входит. То есть я как пользователь, я не абстрагирован от того, что данные распределены, я про это знаю. То есть вот это такого класса системы. Это тоже, кстати, вот важный такой момент, потому что, например, по-моему, там Snowflake или что-то, они пытаются это скрывать. То есть это просто условно доступ к данным. Все-таки, когда я работаю с Apache Ignite и пишу SQL, я должен понимать, как они хранятся, просто чтобы выполнить этот запрос, даже иногда не то, чтобы эффективно, а просто чтобы выполнить. И здесь как бы это интересно, потому что модель думания про систему меняется. Но и в то же время, имея это понимание в голове, у меня какие-то есть дополнительные возможности того. Я реально могу как-то более эффективно выполнять запросы, потому что это знание, оно мне может что-то дать. И вот тут, конечно, интересно было бы подумать, а что оно мне может дать.
[1:22:47] Станислав: И это, на самом деле, нас подводит к, может быть, вот самому главному. Вот все, что мы говорили до этого момента, оно в основном, опять же, не специфично для in-memory data grid. То есть все, что мы говорили, оно такое, как бы где-то вообще распределенные системы, где-то распределенные SQL-системы. Но вот здесь я, говорил до этого, о том, что in-memory data grid отличаются тем, что они пытаются минимизировать I/O, минимизировать вывод настолько, насколько это возможно. И вот в выполнении запросов, как раз в обработке данных, это становится видно. Потому что, ну вот мы выполняем какое-то, нам надо сделать какое-то распределенное вычисление. Вот у нас есть куча наших данных, которые вот эти наши, одна или несколько хэшмапок, которые мы засунули в систему. Ну, скажем, вот join мы хотим сделать, да, между там двумя нашими коллекциями. Самый простой способ сделать этот join, это, ну, давайте затянем все данные на один узел, да, со всех все прочитаем, да, на один узел затянем, и там поджоиним как-то в памяти. Ну, может быть, там какую-то префильтрацию сделаем на локальных узлах, да, может быть, там нам поменьше надо будет затянуть, и все это сделаем. Ну, можно было бы сделать так, но тогда у нас есть bottleneck, а хватит ли нам памяти для того, чтобы сделать это всё на одной системе.
[1:24:18] Александр: Да, но тут логичный вопрос, как бы, вот ты данные распределил, а чтобы их считать, тебе их надо на одну ноду прислать. Да, да, да. Смысл распределения тогда.
[1:24:28] Станислав: Но на самом деле системы есть, которые плюс-минус так и работают. И, например, там, Hadoop-стек, так, ну, то есть, не совсем так, там, чаще происходит что-то, какой-то вариант репартишнинга, когда мы, там, не знаю, в каком-нибудь Spark’е, в Hive’е, да, то есть, как мы выполняем запросы. Но запрос приходит на кластер, данные читаются там с какого-нибудь DFS, но нам надо в какой-то момент сделать какую-нибудь группировку, или какую-нибудь там join, да, где там нужно каждый шарт с каждым другим, там, поджойнить. Ну, ничего страшного, мы просто данные попересылаем в памяти между серверами. Ну, и там, так, в несколько шагов, то есть, там, в несколько шагов их поджойним. То есть, там, в Spark’е, ну, мне кажется, что ты, наверное, больше даже специалист, чем я здесь, но мне кажется, что вот репартишнинг на Spark’овских пайплайнах, это такая достаточно, ну, достаточно знаменитый footgun. Правильно же я, наверное, говорю здесь, да?
[1:25:39] Александр: Да, footgun, это новояз.
[1:25:42] Станислав: Новояз, простите, да. Ну, короче.
[1:25:46] Александр: Тогда мы это называли по-другому, но это то, что…
[1:25:48] Станислав: Грабли мы это называли, наверное, да, по-русски?
[1:25:52] Александр: Не нравится, да, то есть, ну, по сути, да, пересылание данных между нодами, это то, что как можно решить задачу джойна, но крайне не хочется этого делать, это хочется избежать, потому что это тогда latency на просто ожидание запроса, если мы говорим не про аналитику, где в аналитике еще мы можем себе как-то допустить в каких-то случаях, что мы можем и на ночь запрос запустить, и пускай оно там решафлит сколько угодно, мне с утра отчет нужен. Иногда мы готовы себе позволить там пятиминутное окно, иногда и этого нет тоже, мы там реалтайм какие-то строим, дашборды, но мы не можем себе позволить этого, но когда мы говорим, что вот in-memory data grid чуть больше все-таки не про аналитику, а про вот такие вот транзакционные вещи, то решафлинг вот этот, он вообще становится почти непозволительным таким удовольствием в том классе систем, на которые претендует вот та, про какую мы говорим.
[1:26:47] Станислав: Именно так, именно так, и поэтому in-memory data grid они как раз отличаются тем, что они фокусируются на концепции коллокации, коллокации, там говорят о коллокации данных с данными и коллокация вычислений с данными. Что такое коллокация? Коллокация значит, что мы пытаемся одни вещи совместить с другими на одной физической машине или на логическом узле, то есть мы говорим, что коллокация данных с данными означает, что мы будем данные, которые имеют друг к другу отношение и которые обычно процессятся вместе, мы будем вместе хранить. Коллокация вычислений с данными означает, что мы постараемся вычисления над куском данных проводить там, где этот кусок данных живет, вместо того, чтобы эти данные вытягивать на другой сервер или на клиента. И вот эта концепция коллокации, вот эта вот идея, она как раз очень-очень-очень глубоко сидит именно в in-memory data grid и она там как раз она существует везде, то есть она существует не только там на уровне архитектуры, как она существует, например, в некоторых других системах также, но она существует также и на уровне API, она существует там на уровне дизайна схемы. Ты правильно сказал, что когда я пользуюсь вот in-memory data grid, моя модель коллокации, так называемая, она становится частью моей модели данных. То есть я когда придумываю, какие у меня там будут вот эти вот мапы, коллекции, таблицы, когда я вот их планирую, я сразу же думаю, каким образом я буду их коллоцировать. Что такое коллоцировать? Как я делаю это физически, технически в API? Обычно есть какой-то ключ. То есть это ключ, иногда он называется collocation key, иногда он называется affinity key, иногда он называется sharding key. В общем, это чаще всего часть primary key, то есть если у меня есть вот в key-value, в исходной нашей key-value системе у меня был какой-то объект ключа, у него есть там обычно несколько полей. Такой составной ключ как бы. Составной ключ, да. Из этих полей я выбираю как свой ключ коллокации, то это означает, что все записи, у которых значение ключа коллокации одинаковое, они будут всегда жить вместе. Причем это верно не только внутри одной коллекции, но внутри нескольких коллекций. Скажем, вот возвращаясь к примеру с трейдингом, у меня есть, скажем, какие-то там, ну, скажем, у меня есть система, которая считает данные в основном вокруг каких-то тикеров на бирже. То есть там считает, агрегирует данные вокруг транзакций по Apple, вокруг транзакций по Microsoft, вокруг транзакций там по SpaceX, да. И мы всегда, когда мы делаем какие-то запросы, всегда, когда приложение обращается к системе, оно всегда обращается к каким-то конкретным тикерам. Вот в такой системе, вот это идеальный для нас кейс, скорее всего, это идеальный кейс. Потому что тогда мы можем естественным образом выбрать вот этот тикер как ключ коллокации. И это значит, что все записи, которые у нас есть в системе, у них, скорее всего, есть поле с вот этим тикером. И это значит, что всегда, когда записи даже в разных таблицах, даже в разных вот этих наших мапках, они относятся к вот этому значению тикера, скажем так, к тикеру Apple, то они всегда будут жить вместе, они всегда будут храниться вместе в одном и том же шарде. Это значит, что когда мы выполняем запрос, мы можем посмотреть на запрос, если там есть фильтр по тикеру, есть фильтр по тикеру Apple, например, то мы можем найти где хранится тикер Apple, в каком шарде, пойти на этот узел, который хранит этот шар, и полностью запрос выполнить там локально. Нам вообще не нужно ходить на другие узлы, потому что мы знаем, это то, что называется partition pruning, когда мы можем выкинуть все остальные партиции, которые мы знаем, нам точно не нужны, выкинуть все остальные шарды, и сделать запрос только по одному маленькому кусочку, данных на одном сервере. И вот таким образом мы получаем как раз эффективность, то есть мы здесь весь запрос выполнили вообще без сети. То есть мы принесли, по сети мы отправили только запрос, на клиента мы вернули только ответ. Мы вообще ничего больше не пересылали ни между клиентом и сервером, ни между серверами. То есть это идеальный кейс. Когда это не работает? Очевидно, это не работает, если у нас, есть какие-то, например, мапки, таблицы, которые не привязаны к конкретному тикеру. Ну, то есть, когда нет у нас вот этого ключа коллокации. Чаще всего такие штуки, они, ну, во многих схемах в реальной жизни, это какие-нибудь там справочники, которые относительно небольшие сами по себе, и тогда решение здесь просто хранить больше копий такого справочника и хранить, его копию, да просто на всех узлах. Ну, тогда мы приходим, например, мы, нам надо, чтобы выполнить запрос, нам нужно, нужен шард с тикером Apple, и нам нужны какие-то справочники. Мы приходим на конкретную ноду с этим шардом, а справочники, мы говорим, а они у нас везде хранятся, они вообще, у каждого узла есть копия этого справочника, эти справочники там редко меняются, они маленькие, нам недорого их везде хранить. И тогда нам не нужно, мы их затягивать по сети никогда. Иногда бывает, что в запросе нет собственно ключа коллокации, то есть у нас нет какой-то такой фильтрации, да, и мы тогда в этом случае мы будем вынуждены сделать распределенный запрос по всем уже шардам, по всей системе, и вот в этом случае как раз запрос становится тяжелее, и там вот чем больше в этом запросе мы делаем не в рамках одного вот этого тикера, да, не в рамках одного, то есть мы агрегируем не по тикеру, а агрегируем по всей системе. Чем больше мы делаем агрегации по всей системе, тем тяжелее будет запрос для вот такого для распределенного SQL вот такого вот стиля, как он обычно работает вот в в data grid. И, соответственно, ну, если в нашей системе большая часть запросов вообще не collocation-friendly, так сказать, да, то есть не в дружелюбном локации, не имеет вот этих ключей, по которым мы можем отфильтровать шарды, ну, если у нас большая часть запросов ну, такие глобальные, ну, скорее всего, такая система будет хуже работать на in-memory data grid, и нам здесь нужно что-то другое, что-то вот может быть больше похожее как раз вот там на какой-нибудь Spark SQL, который как раз построен на идее, что мы вот это от пользователя прячем, да, вот эту вот сложность, и мы там сами где-нибудь на бэкэнде втихаря данные порепратиционируем, как нам нужно. Там есть и всякие другие сложности, в которые, я не знаю, может быть, не стоит сейчас слишком сильно углубляться, скажем там, а что если у меня по Apple и SpaceX происходит больше тtrade-off, чем по всем остальным тикерам, которые у меня есть в системе, вместе взятым, да, то есть что в этом случае делать? Ну, то есть там становится понятно, что вот эта вот модель, коллокация, она тоже не совсем работает, мы не можем хранить вот этих вот звездные, как это, звездные ключи, мы не можем хранить так же, как не звездные ключи. И всякие другие похожие проблемы там тоже возникнут, но общая идея примерно такая. И вот главное, что нужно понимать, идя вот в in-memory data grid и похожего стиля системы, например, кстати, вот среди распределенных баз, ну, CockroachDB, тоже по такому принципу реализован, то есть у него тоже, там тоже можно указать ключ партиционирования, и тоже, CockroachDB, исходит из того, что в хорошо затюненной системе ты правильно укажешь этот ключ, и будешь понимать, что твои запросы SQL, они на самом деле необычные, они распределенные SQL-запросы, и они будут как-то перформить настолько хорошо, насколько хорошо они коллоцируются в системе. Другие распространения, распределенные базы ведут себя по-другому, и наоборот, говорят, что мы от пользователя это вообще спрячем, и не будем под это оптимизироваться. Вот знать ваш распределенный SQL, как работает, тут очень важно, и правильным образом моделировать данные.
[1:36:24] Александр: Ох, да, я тут, конечно, как уточка, наверное, бы похлопал глазами и сказал бы, ну, пойдем дальше, вроде бы понятно, но с другой стороны, мне как инженеру, конечно, руки чешутся немножечко поглубже разобраться в этом феномене коллокации, потому что, ну, вообще говоря, вот когда я первый раз с этим познакомился как пользователь, для меня это стало как бы энейблером, да, то есть для меня некоторый класс запросов вообще не работал в распределенной системе, вот такой, потому что шафлинг, потому что, ну, вот это пересылка данных. И это был просто недоступный класс запросов, вот реально, я не мог поджойнить две коллекции, просто банально, потому что джойн делал огромное количество пересылок данных между нодами, и там чуть ли не до краш-системы могло дойти, потому что, ну, вот так. И когда я понял, что существует вот такая штука, как коллокация, и включил ключ, по которому я джойнил обе коллекции, и в той и в той коллекции, этот ключ присутствовал, и он был ключом соединения. Вот в том случае это тикер, да, вот тот самый, а название компании, и сделав его и в той, и в другой коллекции collocated by, да, то есть ключом коллокации, и потом поджойнив по нему эти две коллекции, запрос начал работать там миллисекунды. И я такой, а как раньше у меня система ложилась, а теперь это миллисекунды? Ну, потому что как раз вот этот трюк и понимание того, а почему это сработало, оно меня привело вообще к пониманию того, а как там, ну, как распределенная система работает. То есть это, по факту, это как бы интересная штука, потому что как только ты начинаешь задумываться, а почему раньше не работало, а сейчас работает, ты начинаешь себе визуализировать, а как бы я написал алгоритм распределенного джойна. И как только ты начинаешь об этом думать, ты понимаешь, что тебе нужно, ну, понимать, как это работает. Я просто думаю, вот в подкасте распределенный джойн объяснить хоть на 80% у меня получится или нет, скорее всего, нет, но часть этого того, что просто представьте, у нас есть так или иначе там две HashMap, вот, у нас была раньше одна HashMap, теперь две, и нам нужно все ключи в одной HashMap и которые есть представители этого ключа в другой HashMap положить в один бакет. По сути, это и есть джойн. Вот мы берем просто из одной мапы перекладываем в другую. И как бы вот этот процесс переложить, он говорит, что у нас должна быть одна HashMap-контейнер, то есть мы в какую-то должны складывать. И, как правило, это там первая таблица в запросе, вот, join с GROUP BY, когда мы делаем джойн. И, соответственно, мы в эту таблицу должны из другой переложить. И мы процессим строчка по строчку. Вот представьте, мы идем по таблице, хеш-таблице, говорим, вот у нас строчка с ключом Apple. Теперь мы хотим туда положить все данные, у которых тоже ключ Apple в другой таблице. Что для этого нужно сделать? Нужно их найти. Как их найти? Мне нужно сделать full scan. Ну, иначе я. То есть, а как я могу узнать, если это не оригинальный ключ? То есть, если это не primary key, а часть какая-то. И все. И так получается у меня n квадрат сложности. А если мы пересылаем, то это огромное количество данных, это почти невыполнимо. Я хочу сделать линейную сложность. Как мне это сделать? Мне нужно не сканировать таблицу по каждому ключу, а знать, куда пойти. И вот этот collocated by, он как раз мне говорит, что данные с этим полем лежат вот здесь. И помимо того, вот здесь это та же самая нода, на которой ты исполняешься. То есть, вот эта коллокация, она говорит, что все данные по Apple будут лежать на той же самой ноде, и мне на другие ноды ходить не нужно будет при выполнении таких запросов. Я буду все локально выполнять. И вот как только я это немножко начал понимать, я понял, а, так вот, зачем такие системы существуют. И вот то, что ты сейчас рассказывал, это ровно оно. Что вот что мы приносим для того, чтобы выполнять такие запросы. Стало реальным. А если я не запутал слушателей, не заставил их закрыть подкаст этих оставшихся двух энтузиастов, то мы можем перейти к еще одной киллер-фиче подобных систем, наверное, поговорить про execution. Ведь не только про выполнение запросов, как таковых, вот прям типа селект уровня эти системы существуют, но еще что-то там можно делать.
[1:40:59] Станислав: Эта штука, которая появилась до еще SQL в этих системах, и то, откуда эти системы вышли, это как раз выполнение нативного compute. То есть я, когда говорю, что эти системы, у них вот смысл существования, это минимизировать вот вывод. А как можно минимизировать, вот максимально избавиться от пересылки данных между бизнес-логикой, которая лежит в приложении, и сервером, в котором лежат данные. Радикальный способ — это положить бизнес-логику прямо в сервер, где данные лежат. И вот это то, что называется распределенный compute, distributed compute, или Apache Ignite это называют, compute grid. По-разному это называют разные, и иногда это называют collocated compute в разных этих системах. Общая идея здесь такая, что мы берем наш код, который фактически кусок нашего приложения, который обычно…
[1:42:01] Александр: Ну, Java-код, если мы говорим про Apache, да? Java код прямо,
[1:42:04] Станислав: Да, да, да. Ну, и почти во всех data grid это в первую очередь будет про Java код. Ignite может это делать с разными языками, там можно и на плюсах, и на.NET это сделать, но чаще всего это Java. Мы берем кусок кода на Java, который был вот к нашим кодам приложения, скорее всего. Что он делал? Он принял какой-то пользовательский запрос, сходил в базу, что-то прочитал, что-то там подумал, что-то записал в базу, что-то еще прочитал, что-то еще подумал, и вот так вот несколько раз там в несколько round trip с базой и какой-то бизнес-логикой между этими round trip. Как это меняется с Colocated Compute? С Colocated Compute это меняется радикально. Мы один раз делаем запрос к нашему гриду, говорим, выполни вот эту вот бизнес-операцию вот с этим кодом. И вот весь этот код, который со всей его бизнес-логикой, множественными доступами к данным, все это выполняется прямо на стороне на стороне data grid, прямо там, где данные хранятся. Логика здесь точно такая же, как то, о чем мы говорили про SQL. То есть также у нас есть коллокация данных, мы знаем, какие данные где лежат. Чаще всего такие запросы, такие вот compute-операции, они тоже ассоциированы с каким-то конкретным коллокационным ключом. То есть как в нашем предыдущем примере, с конкретным тикером. То есть мы хотим посчитать или там посчитать и записать что-то про тикер Apple. Мы вот это все берем и отправляем вот это вычисление прямо на сервер. У меня есть такая максима, которую я люблю повторять, особенно новым пользователям этих технологий или там на конференциях, когда говорим про in-memory data grid, что если вы используете in-memory data grid и у вас есть больше одного round trip между клиентом и сервером, на бизнес-транзакцию, то вы делаете что-то неправильно. Ну или по крайней мере вы не используете вашу технологию еще на полную мощность. То есть эта технология сделана для того, чтобы на один пользовательский какой-то запрос, на одну бизнес-операцию, на один ивент, мы один раз пришли на data grid, в идеале пришли на конкретный узел этого data grid, там что-то почитали, что-то пописали, собрали все в кучу и вернули пользователю один ответ из этого вычисления, или там одно подтверждение из этого вычисления, все. То есть мы, сети у нас вообще нет, у нас один сетевой hop с клиента прямо на нужный сервер, потому что про это мы, кстати, тоже не говорили, но data grid магические, и там клиент знает, на каком сервере данные хранятся. То есть не только сервера друг про друга знают, но даже клиент знает, на каком сервере. Не будем углубляться в магию, она там бывает разная всякая, но клиент какой-то магией знает, на какой сервер пойти, и обычно у клиента есть со всеми серверами прямой доступ, то есть он к каждому серверу держит открытое соединение и может прямо туда пойти, прислать ему этот computation, этот computation там производится, возвращает результат. Внутри этого computation у нас есть доступ просто к полному нашему API. API это обычно, мы, кстати, да, мы не договорились, говорили тогда про API, а что у нас там есть? API это обычно не JDBC, да, не как вот в обычных базах, а там какой-то кастомный API, ну, примерно как вот наверное слушатели может быть более знакомы, скажем, с Redis, да, чем с вот этни с in-memory data grid. Ну, вот как у Redis есть там свой собственный API, да, и там свои собственные клиенты. Ну, вот здесь что-то похоже. У каждого in-memory data grid, да, он будет немножко свой map API, и нам полностью весь этот API доступен внутри нашей compute, нашей compute jobs, как они называются, да, нашего вычисления, и мы там просто пишем приложение точно так же, как мы писали бы его на клиенте, просто оно выполняется на сервере. Там даже есть такие более магические фичи, скажем, вот у Apache Ignite есть то, что называется peer class loading, он же zero-deploy, это такое более маркетинговое название, когда мы просто берем кусочек приложения, запаковываем его в лямбду, просто компилируем этот код, запускаем. Клиент прямо динамически во время выполнения запаковывает эту лямбду, отправляет ее на сервер, сервер сам сходит на клиента и попросит у клиента class-файл, попросит у клиента байт-код, все это загрузит динамически, все это запустит, можно настроить какие-то сандбоксы для этого, и вот так вот все прямо волшебным образом исполнится. Также можно делать распределенные вычисления, то есть так же, как мы говорили про распределённый SQL, compute тоже можно делать его распределенным, скажем, мне нужно послать ну, такой самый частый кейс, мне нужно пойти и ну, может быть, даже в несколько шагов, то есть мне надо пойти распределенно сделать какое-то вычисление на всех машинах, получить результат, потом на его основании пойти сделать еще какое-то распределенное вычисление на всех машинах, получить результат, и так может быть в несколько, ну, в несколько вот таких вот шагов. Это все тоже можно сделать, для этого есть интерфейс, да, для этого есть API, который позволяет не просто вот одну джабу так отправить, а отправить вот такую сразу долгоживущий вот такой вот таск, который будет порождать много-много-много джабов, и их между собой соединять, и в итоге пользователю отдавать финальный результат.
[1:48:08] Александр: Я чувствую, я чувствую вопрос уже вот слушатели, вот те трое, которые остались, вот у них, у двоих точно, прям вот горит, так это же хранимые процедуры. Чем это отличается от хранимок? Объясни.
[1:48:22] Станислав: Оно отличается, все, вот все, о чем мы говорим, это, кстати, может быть дисклеймер, который надо было сделать вот в первые три минуты, все, о чем мы говорим, можно сделать из, из Postgres и, не знаю, там, из Postgres и PgBouncer’а, да, вы всегда, если вы очень хорошо знаете Postgres, вы всю эту, весь этот подкаст сидите и думаете, так я же могу это сделать, и так вот, там, такими-то, такими-то инструментами, да, наверняка, главное отличие здесь это в том, а какое количество вещей мне придется делать, как бы, do it yourself, да, то есть, что мне придется делать самому и что мне предоставляет платформа. in-memory data grid, ну, вот давайте возьмем там Apache Ignite, да, он сразу мне предоставляет богатый compute интерфейс, который написан на нормальном языке, не SQL процедуры, да, динамическая регистрация, регистрация и дерегистрация, обновление и версионирование, вот этих compute workloads, то есть, я, у меня, я задеплоил новую версию своего приложения, оно пошло, и прям динамически, прям вот изнутри приложения, если у него есть такие permissions, да, оно пошло, и задеплоило свой код на сервер. Есть вот эта вот магия, про которую я сказал, которая вот как peer class loading, которая, там вообще не надо ничего деплоить на сервер заранее, там сервер сам попросит у клиента классы, которых ему не хватает для того, чтобы что-то выполнить, то есть, все это приходит из коробки. А, ну и, конечно, само вот это вот, сама концепция коллокации данных с данными и вычислений с данными, то есть, то, что я когда запускаю, то есть, вот в Apache Ignite, например, есть такая штука, она называется affinity call, или там в последней версии execute collocated, когда я запускаю вот этот вот compute, я прям передаю, ну вот в нашем предыдущем примере, я прям передаю в API, тикер, вот я говорю, запусти вот эту вот джабу на тикере Apple, и оно само внутри находит, мне не надо там идти к какому-то сервису, спрашивать, где хранятся данные про Apple, туда отправлять на этот конкретный сервер, оно все само найдет, где Apple хранится, если Apple куда-то приедет, там оно все это правильно зафейловерит, найдет всегда, где лежат нужные данные, все это работает, все это работает из коробки. Здесь есть очевидный trade-off, а может быть даже два. Они оба связаны так или иначе с security и multi-tenancy. То есть первое, а что если какой-нибудь хакер получит к этому доступ и задеплоит туда какой-нибудь плохой compute? И второй, это, а что с multi-tenancy? Что если у меня там есть два приложения, и одно из них с багами, и оно какой-нибудь плохой compute туда закинуло, который, не знаю, выжрал мне всю память, и не знаю, крашит мне сервера. Тут на самом деле нечего сказать, кроме того, что это trade-off. Есть инструменты, есть API, которые позволяют вам обложиться какими-то permission, обложиться какими-то сандбоксами, то есть как каким-то образом ограничить то, что будет выполняться. Но в конечном счете вы даете выполнять как бы Java-код на стороне сервера. Этот Java-код контролируется приложением. Если вы приложению разрешили выполнять этот Java-код, то теперь это доверенное приложение, оно может много чего получить, там, не знаю, сделать какой-нибудь reflection, получить это на стороне сервера. То есть это похожие проблемы на те, которые у нас были, например, во времена application серверов в Java, когда там у нас один application сервер, много applications, они вроде как изолированы, и один плохой application может получить доступ к другим через reflection. Ну как это решали? Ну были security managers, security manager теперь там есть какие-то другие соображения для этого. То есть там на, ну в случае с in-memory data grid, можно там форкаться в какой-нибудь соседний процесс, чтобы по крайней мере внутри того же процесса не было доступа у этого compute к остальному серверу. То есть можно это решать, но, конечно, да, то есть здесь есть определенные риски, здесь есть определенные трейд-оффы.
[1:53:08] Александр: Знаешь, что хочется сказать, что вот для работы с такой системой, если вот как бы, ну и что, so what, то что квалификация инженеров должна быть в среднем повыше, чем, например, для работы с тем же самым там Postgres. Просто даже не потому что, ну и потому что надо понимать больше, но и ответственности больше. Вот я как пользователь вот подобного in-memory data grid, имея возможность, и возможно у меня какая-то задача будет там, напиши-ка ты свою там compute job, которая делает MapReduce, там word-count какой-нибудь. И вот я такой пошел, на Java это все написал, и если я, ну, квалификации недостаточной, то я могу, например, сделать такое, что у меня будет мой там цикл написанный, какой-то внутренний с логикой моей бизнеса, очень много мусора производить, или вообще утечку памяти напишу у себя там какую-то такую, которая просто GC pressure, то есть нагрузку на garbage-коллектор повысит. Сильно, неоправданно, не нужно. И написав такой код, задеплоив его и начав его исполнять на сервере in-memory data grid, где лежат данные, и где есть другие пользователи, где есть другие compute jobs, где garbage-коллектор бедный, и так еле справляется. И я тут еще со своим циклом. Я могу, ну, просто добить, и добить настолько, что у меня кластер может развалиться. Поэтому ответственность на мне больше, что ведет за собой выше, более высокие требования к квалификации, к моей, как к инженеру, который с такой системой работает. Я думаю, это вот такое следствие тоже вот этой вот security-штуки.
[1:54:41] Станислав: Сто процентов. И это, кстати, к началу нашего разговора. А почему в России-то Apache Ignite был так, ну, среди, даже не в России, а, надо сказать, среди русскоязычных инженеров, да? Потому что остальные не вывезли просто. Ну, откровенно говоря, я, кстати, верю в это абсолютно, потому что мне кажется, что, русскоязычная среда была всегда таким, такой комбинацией из того, что с одной стороны у нас стала очень популярна Java, может быть, даже больше, чем вот в мире, но при этом на ней делали какие-то сложные штуки. То есть не только CRUD-сервисы в банках, а, блин, сложные CRUD-сервисы в банках. И средний уровень квалификации, ты прав, он должен быть выше, и там, где, ну, средний уровень выше, как бы, ну, вот в среднем по больнице, такие технологии окажутся более доступны. Я вообще вот так скажу, что очень многие клиенты, с которыми я встречался по работе, которые имплементировали системы на in-memory data grid, очень многие из них в прошлом писали что-то такое сами, скажем, там, какой-нибудь телеком, который в конце 90-х, в начале нулевых они там писали свой собственный Data Grid или там свой собственный in-memory cache, потому что, ну, это то, как тогда делали вещи. Сейчас они, не знаю, там, обновляются на 5G-систему, и там под 5G-систему они уже не тащат вот это, то, что они написали 20 лет назад, а они берут что-то, что доступно, ну, готовое с полки, да, и вот они приходят, например, там, в Apache Ignite, в GridGain, с этим. Это действительно вопрос квалификации. Я слышал мнение, что, ну, должен быть хотя бы один PhD в организации для того, чтобы организация могла использовать такие технологии. Я не думаю, что это прямо так. Наверное, недалеко от этого.
[1:56:51] Александр: И тоже всем как слушателям, если вы когда-то обнаружите себя имплементирующими что-то около data grid, дейтабейзов, такие технологии, прочитать Database Internals, она же известная как книжка с Камбалой, сильно поможет. Она читается, если ее вот так вот пролистывать, она читается за один, может быть, два вечера, да, если там не вчитываться в каждый параграф, но чтобы в общем понять, вот, как бы структуры, которые там есть, и проблемы, которые возникают в этой системе, это, ну, это, наверное, наверняка поможет довольно сильно.
[1:57:35] Станислав: Мы, на самом деле, мы не покрыли один еще кусок, такой большой, в in-memory data grid, а это как раз мы все вокруг да около ходим, а это про Java heap и про что там происходит с Garbage Collection.
[1:57:51] Александр: Ну, мы уже вплотную подошли, как бы пространства для маневров все меньше, и у меня среди слушателей довольно много, я думаю, ребят, которые пишут на плюсах, на Rust, я много делал и контента про раст, и, я, кстати, свою базу данных на YouTube, по-моему, что-то там около 50 стримов я на Rust писал. Вот, поэтому ребят, которые не знают о существовании Garbage Collector и не понимают, зачем он вообще нужен, когда рантайм без него, у них может вполне себе понятный вопрос возникнуть в голове. Как раз такая система сложная, зачем в нее еще вносить, как бы, сложности и новые сущности, помимо всех прочих, так еще и Garbage Collector, потому что, ну, наверняка, с ним есть какие-то проблемы. Давай про это поговорим, потому что на самом деле это такое, как бы, не то чтобы всем знакомая, знакомый класс проблем с системами, когда ты пишешь на Java, как там, когда GC-паузы становятся, ну, реальной проблемой. Вот когда ты пишешь бэкэнды, ставшие там уже какой-то часть, как мы в прошлом подкасте говорили, комодити, да, вот это rest пишешь, CRUD’ы шлёпаешь, ну, навряд ли ты вообще думаешь о Garbage Collector, и это хорошо, вот когда пользователь языка программирования не думает о существовании Garbage Collector, это как вот хороший менеджер, а его существование никто не должен знать, вот он отлично менеджит heap, но когда ты пишешь сложные штуки, подобные вот in-memory compute grid на Java, почему-то, да, мы вот сейчас поговорим, почему, но прежде всего, да, ты начинаешь думать о том, а что такое GC, как он работает, и какие последствия, какие приколы могут возникнуть с ним, ведь данных-то много, да, в основном, какая проблема, что данных, с которыми работает приложение, становится огромное количество, и вообще, строго говоря, ну уж точно старые GC, они точно не были задизайнены под такой workload, они все-таки больше были вот такие бизнес-Java-приложения, которые данные какие-то создают, и потом их можно там почистить, убрать, вот запрос пользователя обработал и убрал, но когда у тебя это база данных, и данные лежат, а если они еще лежат в heap, то GC про них не знает, в API и GC нету это данные там, таблицы, пожалуйста, не чистите их, он их будет обходить, ну так или иначе, там, особенно в предыдущих поколениях, короче, много приколов с GC, вот давай про это поговорим, какая самая страшная проблема у тебя на практике случалась из-за garbage collector, из-за его существования?
[2:00:21] Станислав: У меня есть два замечательных проекта, которые мы делали, которые были такие технические proof of concept, технические proof of technologies, один из них, они оба были около GC, и сейчас вот люди поймут, как бы, они как раз очень хорошо иллюстрируют вот этот разрыв между тем, какие системы in-memory data grid должны саппортить, против, как бы, а что делать с garbage collection, одна из таких систем, мы строили систему обработки, карточных транзакций, а карточные транзакции, это вообще мы вот еще не говорили про бизнес юзкейсы какие-то для in-memory data grid, но вот карточная обработка транзакций, это прямо вот такой хрестоматийный пример, все про него всегда говорят, вот fraud detection, там особенно любят вот когда нужно там in-memory перелопатить много данных, но относительно много, с большой параллельностью, но самое главное то, что там, вы когда карточную транзакцию используете, то она будет, если вы карточки проводите в магазине, транзакция должна подтвердиться или отклониться за секунду, и на одну вот эту операцию, например, на fraud detection, там бюджет, например, 20 миллисекунд, и вот нам нужно в 20 миллисекунд, в четырех девятках всегда успевать это делать, а делать это по сети, делать это с диском, это как бы не вариант, да, и вот здесь нам нужно, чтобы все данные у нас были под рукой, чтобы мы могли здесь с этим работать, как раз коллоцированные данные от одной вот этой карточки, чтобы они все под рукой лежали. Вот. И нам нужно было достичь как раз четырех, достичь четырех девяток, там, до, по-моему, 20 миллисекунд. Это был очень болезненный POC, мы довольно долго пытались докрутить еще во времена G1 GC, то есть никакой Shenandoah не было, никакого ZGC не было в Java. Точнее, они вот только-только-только обещались. Вот. Мы пытались это докручивать с G1 GC, и мы как-то там докрутили три девятки латенса, то есть, ну, 99.9% запросов у нас были там до 20 миллисекунд, но четвертая девятка, она улетала там в какие-то секунды. И в итоге часть работы мы, да, сделали оптимизацией как раз вот вокруг коллокации, оптимизацией на своей стороне, но в итоге спасло нас то, что мы пришли в Азул, и мы вместе с ними, даже до сих пор на YouTube, ну, где-то в интернете там есть какие-то старые блоги из той компании, мы тогда запартнерились с ними и сделали это на Zing, на как называется, C4, да, C4 Garbage Collector из Азул Zing, который был первым pauseless Garbage Collector, и вот с ним как раз у нас все, с ним как раз все заколосилось. Здесь как раз такой, как это, пример, когда, ну, ну что, принесли нормальный Garbage Collector, стало, ну, стало хорошо. Другой был Proof of Concept тоже, но там он был совсем смешной, когда нам нужно было делать что-то, ну, внутри, там, не буду вдаваться в подробности, но какую-то систему, не совсем High Frequency Trading, но вот уже как бы внутри, trading-систем, когда требования к латенсе на одну операцию были какие-то там, по-моему, там было что-то типа 100 микросекунд, или там что-то типа 70 микросекунд, и мы довольно долго это крутили, и я помню, как я там обходил все наши какие-то слои, весь сетевой overhead для того, чтобы максимально быстро доставлять там какие-то сообщения, мы прикручивали, специальные карты, специальные сетевые карты, короче, которые работают в юзерспейсе, не уходят в kernel, и за счет этого у тебя нет там латенса спайка на переходе в kernel и обратно, это все было замечательно, но лучше, чем 95% латенса мы все равно показать не могли, потому что это все-таки Java, но, конечно, да, если вам нужно там меньше 100 микросекунд в высокой персентиле латенса, но это не сюда, то есть мы как бы изначально это понимали, но, ну, то есть вот мы увидели, что там 99% персентиле, она уже у нас ну, куда-то там в миллисекунды уже, естественно, улетала, даже с хорошим, там, хорошо потюненным garbage collector. Ну, вот такие вот, то есть на дальнем рубеже, скажем так, на дальнем краю вот этих low latency, ultra low latency use cases, garbage collection становится прямо, дальним фактором. Естественно, помимо этого, был еще там вагон и маленькая тележка кейсов, когда ну, просто пользователи приходят, запускают там, не знаю, какой-нибудь незатюненный SQL-запрос, этот SQL-запрос сжирает у них всю память, все зависает в бесконечном garbage collection, в, вот тут, чтобы у всех свело old school, в G1 еще была такая настройка, максимальная garbage collection пауза, которая дефолтилась, 200 миллисекунд. Ну, то есть, это не ограничение, это таргет. И он сам себя под эту паузу тюнит. Ну, люди, как бы, они знают, что надо тюнить ГЦ, поэтому, когда им надо было, чтобы он работал лучше, они ему просто ставили там максимальная пауза, там, типа, 5 миллисекунд. И возмущались.
[2:06:18] Александр: Надо меньше поставить лучше, то есть, да.
[2:06:21] Станислав: И в итоге у них просто подвисал окончательно там, garbage collection, потому что, естественно, он пытался, как мог, сделать минимальные паузы, накапливал там, ну, какой-то баглог, и в итоге улетал там в full GC просто на минуты. То есть, обычно я это чинил тем, что я приходил, и вместо 200 миллисекунд я им ставил там, не знаю, там, 2 секунды максимальную паузу. Ну, и у них, да, у них появлялись лаги по 2 секунды регулярные, но, в целом, система жила нормально, просто, ну, с периодическим вот таким лагом. Это такая очень интуитивный трюк, который больше уже, наверное, не нужно вам знать, потому что пользуйтесь новыми джавами, пользуйтесь ZGC, и все у вас будет нормально. Я не думаю, что сейчас кто-то уже пользуется вот таким такой глубокой настройкой на G1. Возвращаясь, да, возвращаясь к in-memory data grid, к heap и к их отношениям с heap, я, наверное, хочу здесь опять пройти вот весь вот этот путь, который мы проговорили, от того, что, а у нас просто HashMap, мы сделали ее какую-то распределенную, и мы наше приложение перенесли вот в этом compute, в этой compute job перенесли из клиента прямо на сервер, а потом мы захотели SQL, а потом мы захотели еще что-то. И вот сейчас поговорим про то, а что еще мы захотели. Вот в стародавние времена, когда это был ну, просто вот такой распределенный compute, не было еще там никаких SQL особо, не было там особо запросов, то есть это просто такая вот распределенная HashMap, и server-side-выполнение Java. В такой ситуации вы хотите Java-объекты. Вы никаких там. У вас никаких таблиц еще нет. У вас никаких запросов внутри этих Java-объектов еще нет. Все, что вы хотите, это просто HashMap, который выглядит и крякает как HashMap. Это означает, что вы, в общем, хотите Java-объекты держать прямо вот на heap несериализованными. Вы хотите, чтобы когда вы получаете этот объект, вы вот прямо его хотите держать. Вы, скорее всего, даже хотите его как-то мутировать и не записывать обратно в HashMap, а чтобы вот вы в объекте поменяли поле, и он там автоматически как бы вот в хранилище уже поменялся, потому что это не копия объекта, это тот же самый объект. Примерно так старые data grid в стародавние времена и жили. То есть это буквально семантика HashMap просто каким-то образом распределенной. Потом появилось, наверное, скажем, три фактора, которые все привели в одну точку. Фактор первый — это garbage collection, про который мы уже сказали. Если мне нужно хранить много таких объектов, и мне нужно постоянно их записывать, читать новые, ну, я умру от garbage collection просто. И я не могу хранить слишком много на одном. То есть там, ну, реалистичное ограничение было там, ну, типа, ну, 16 гигабайт ты можешь хранить так на одном узле, да, там, с G1. То есть, ну, G1 там, чтобы, опять же, для слушающих на Java-разработчиков, вы хотите, чтобы у вас были compressed object headers, object pointerss, для того, чтобы были compressed object pointers, вам нужно не больше 31 гигабайта heap. Реалистично вы хотите еще меньше, потому что, ну, хранить данных меньше, чем 31 гиг, потому что, ну, у вас там overhead какой-то, ну, вот такое вот правило большого пальца было, ну, гигабайт 16 вот так вот на одну ноду. Если хотите больше, ну, делайте больше узлов. И, соответственно, кластера получались большими, что тоже не очень удобно. Это первая загвоздка. Вторая загвоздка это, вообще-то, мы хотим SQL поверх этого. Мы хотим делать запросы по вторичным полям, мы хотим вторичные индексы, как в базах данных, когда, вот, у меня есть первичный, да, первичный индекс, да, первичные ключи. Но еще я хочу там, не знаю, делать какой-нибудь запрос, ну, вот, если у меня, да, трейдинг какой-нибудь, я хочу найти там все трейды по цене выше какой-то, да. Как мне это делать быстро? Ну, мне нужен какой-то вторичный индекс, который, дополнительная информация, которая индексирует конкретно вот это вот поле. И третий момент. Это все, конечно, in-memory технологии, это круто, но если я рестартанул узел, я же не хочу перезаливать все данные в этот узел по сети, я же хочу, чтобы он у меня поднялся, ну, желательно, особенно если у меня там какое-нибудь maintenance окно. Я хочу, чтобы он у меня поднялся, и чтобы у меня данные были прямо там же, да, чтобы они не, чтобы мне их не перезагружать. А значит, они должны каким-то образом еще и на диск попасть. То есть, да, это in-memory технологии, да, я хочу in-memory, но вообще-то я хочу и диск тоже. Я хочу, чтобы у меня было все в памяти, но еще я хочу, чтобы все на диске. Можно мне вот
[2:11:29] Александр: Лучший из двух миров, пожалуйста? Лучший из двух миров.
[2:11:32] Станислав: Да, да, пожалуйста, спасибо. Вот эти три причины, то, что garbage collection нас убивает, то, что нам нужны, то, что нам нужно залезать внутрь объектов. Что значит внутрь объектов? Нам нужна какая-то, нам нужно, чтобы они как-то предсказуемый лейаут у них какой-то был физический, чтобы мы могли к полям этих объектов как-то эффективно обращаться. Нам их надо как каким-то образом десерилизовывать и писать на диск. Все это привело в сумме к тому, что data grid начали массово уходить в какие-то варианты off-heap storage. То есть даже оставаясь Java технологиями, и при том, что вся обработка данных происходит на heap, мы данные хотим хранить не на heap. Тут опять же были разные подходы и разные data grid подошли к этому по-разному. Apache Ignite пошел дальше всех, фактически превратившись из in-memory грида, вот за годы превратившись в полноценную базу. Потому что Apache Ignite просто сказал, что а мы вообще не будем хранить данные на heap тогда, и мы просто заменим heap storage off-heap storage, а off-heap storage у нас будет B+tree, не углубляемся в то, что такое B+tree, Database Internals, книжка с камбалой.
[2:12:53] Александр: Второй сезон подкаста «Тысяча фичей» B+tree. B+tree и LSM-tree разобраны в отдельных выпусках, так что вот вам ссылочки.
[2:13:02] Станислав: Это замечательно, это еще лучше. Но книжку все равно прочитайте. Сделаем B+tree, а B+tree, оно одновременно и дает нам хранить данные эффективно на heap. B+tree, оно как бы, оно же бинарное, оно же дерево, и оно же дает нам, это дерево поиска, оно дает нам как раз искать ключи, и сразу у нас и индексы будут B плюс деревьями, и сами данные будут в B плюс дереве лежать. Так как данные там лежат в B плюс дереве постранично, мы эти странички можем очень эффективно писать на диск. И вот таким образом мы как бы все три. А, и формат записи внутри этого B плюс дерева, так как у нас больше не Java-объекты, мы придумываем свой собственный формат. В Ignite он называется в более старых версиях Binary Object, более новых Binary Tuple, просто кастомный бинарный формат, который, ну, кастомное представление данных, в котором мы знаем, где у нас лежат какие поля, мы знаем схему, и если у нас там какой-нибудь SQL-запрос, которому нужны отдельные поля этих объектов, мы можем, не десериализуя объект в Java-объект, да, мы можем просто посмотреть на него прямо на байтике просто посмотреть в странице на off-heap и вытащить нужные поля. То есть таким образом мы пришли к off-heap storage. Опять же, там, другие технологии делают это немножко по-другому, Coherence, по-моему, немножко попроще, у Hazelcast, мне кажется, у них несколько поколений вообще, там несколько разных этих storage. По моему мнению, ну, я biased, да, но по моему мнению никто не подошел среди in-memory data grid так близко к полноценной базе, да, как это сделал Apache Ignite и GridGain, они все так или иначе с heap либо ушли совсем, либо к heap добавили еще опцию off-heap хранения. И там же есть еще опции, где-то можно, где-то ты обязан хранить полностью копию, то есть то, что хранится на диске, хранится и в памяти. Опять же, Apache Ignite позволяет хранить на диске вообще больше данных, чем в памяти. То есть это как бы in-memory data grid, но вообще-то уже и не совсем, потому что если я могу просто хранить, много данных на диске и мало хранить данных в памяти, то как бы.
[2:15:30] Александр: Это not only in-memory data grid.
[2:15:32] Станислав: Да, да, да, то есть это уже становится как бы мало чем отличимо от там распределенных баз данных, то есть там на уровне API у нас все еще есть всякие вот эти data-grid API, типа вот там compute, коллокация, но на уровне хранения это уже в общем очень сильно похоже на традиционные на традиционные базы. Отличие, наверное, в том, что ну все равно data grid системы все еще написаны исходя из того, что чаще всего у нас все данные в памяти. Мы оптимизируемся под то, что все данные будут в памяти. Мы оптимизируемся под то, что у нас коллоцированный доступ. Традиционные базы все-таки написаны исходя из того, что у нас много данных на диске, маленький in-memory cache, мы там может быть будем пользоваться им не так эффективно, но зато как бы читать с диска мы будем эффективнее. Я на самом деле вижу это регулярно на каких-то бенчмарках. Нет какой-то одной причины, почему, но обычно получается так, что если дать очень много данных, то есть вроде как одни хранят на off-heap и на диске, и другие хранят на off-heap и на диске, но получается так, что если дать очень много памяти data grid и дать очень много памяти там Postgres, то ну data grid как-то быстрее, ну как-то получается быстрее. Хотя там наверняка придут специалисты и в одной, и в другой технологии и там вероятно затюнят, если там затюнить все по максимуму, то наверное может перформанс там и сравняется. То есть в современных data grid проблема Java heap’а и garbage collection’а, она в основном ушла. Нам все еще нужен Java heap для того, чтобы ну собственно выполнять наше вычисление, то есть тот java код, который вы задеплоили, он будет выполняться на heap’е. Тот SQL, который у вас там выполняется, скорее всего там SQL-движок, скорее всего наверняка SQL-движок, который написан на Java.
[2:17:37] Александр: Не, ну даже сама агрегация данных, то есть мы же должны там ладно фильтрацию мы можем сделать не перетащив данные на heap, но если мы уже делаем, ну то есть если у нас есть какой-то промежуточный результат, мы же должны его где-то держать, но мы скорее всего держим на heap’е, то есть все равно там будет какой-то heap'
[2:17:56] Станислав: Но размер heap’а больше не определяется количеством данных, которые у вас есть, а он определяется скорее тем, какой compute вы пускаете. Ну вот так вот в современных data grid, в современных системах. Это на самом деле приняло еще один очень интересный оборот, ну вот в последние годы, потому что в последние годы, когда в java появились современные garbage коллекторы, у которых нет проблемы с паузами, которые там ZGC, по крайней мере маркетинг, говорит, что стабильно он держит single digit milliseconds паузы, или там меньше одной миллисекунды, и действительно на нем может делать очень low latency системы. Если так, а действительно ли нам все еще нужны эти off heap хранилища? И на самом деле это открытый вопрос, вроде как, ну, off heap хранилища уже сделаны, и, наверное, у них нет какого-то большого, ну, какого-то большого даунсайда, но я встречал некоторые приложения, на которые смотришь и думаешь, что вот вот есть там Apache Ignite версии 1, да, в котором был еще on heap storage, а есть там версии 2 и 3, где storage уже off heap, и вот смотришь на некоторые приложения и думаешь, вот возможно, возможно, вот это там одно из там тысячи приложений, которому может быть даже и на Apache Ignite 1 было бы хорошо, если бы это было. Если бы вовсе еще кто-то поддерживал. Так что, может быть, в будущем нужно будет делать или продолжать поддерживать такие гибридные системы, которые имеют и в on heap, и в off heap.
[2:19:38] Александр: Слушай, ну это вот одно из таких, наверное, сильных сторон Java, как платформы, как экосистемы, которые постулируют, что вот мол, ваше написанное приложение там 10 лет назад, просто обновляйте Java, вот, и оно будет как минимум не хуже, а то и лучше, быстрее перформировать, и вот это как раз тот случай, да, просто обнови Java, да, поставь себе новый ГЦ, и вдруг все заработает.
[2:20:04] Станислав: Ну, с другой стороны, тоже, знаешь, вроде как это так, но если посмотреть на технологии, вот как раз такие бигдата технологий 2010-х, они практически все написаны на Java. Это было время Java, самое популярное, и опять же язык был популярный, и технологии как-то вот все были, ну, в каком-то, все были как бы друг с другом связаны, то есть огромное количество вот этих бигдат технологий, они делались там в Apache Software Foundation, то есть там, что Apache Kafka, что Apache Cassandra, что я не знаю, что упоминают, что Ignite, что ZooKeeper, там, не знаю, Lucene какое-нибудь, да, все они, там же, все они появились примерно в одно время, все они появились где-то на Java. Сегодня у всех этих технологий, не только у in-memory data grid, у всех у них, примерно одна и та же проблема. Они начали с того, что все делали на heap, поняли, что на heap это делать нельзя, и ушли в off-heap, а в off-heap на Java уйти легально было нельзя, был только sun.misc.Unsafe класс, который дает тебе доступ к off-heap, но он даже называется Unsafe, и это даже не фича языка, как в некоторых языках есть, там, в Rust, по-моему, есть Unsafe, да, а здесь, это просто, ну, это просто escape hatch такой неподдерживаемый, полузадокументированный. Разработчики Java всегда говорили, мы его когда-нибудь заберем, но выбора никакого не было, приходилось использовать Unsafe, а иначе, ну, а иначе как? Иначе переписывать, просто уходить с Java. И в итоге сейчас Unsafe наконец-то заменили, наконец-то пришел Project Panama в Java, пришел Foreign Function & Memory API, пришел, пришел, Foreign Memory, теперь на новых версиях Java мы, по идее, должны все переписать, все наши системы, и заменить вот этот Unsafe, off-heap, мы должны теперь заменить на поддерживаемые API, но мы же должны поддерживать и старые версии Java, в которых этого нового API нет, и это достаточно сложный такой момент, то есть я ожидаю, что в следующие, вот, ну, там, несколько лет, наверное, будет такой сложный переход, когда Unsafe начнут потихонечку, отбирать, и вендорам и мейнтейнерам этих технологий, то есть коммерческим вендорам и open-source мейнтейнерам станет немножко сложно поддерживать, потому что тебе фактически придется поддерживать две кодовые базы, на Unsafe для старых Java, и на вот этом новом API, поэтому здесь Java, да, один раз написал, и типа все стало со временем лучше, но с другой стороны они как бы одни вещи улучшили, а другие вещи они потихонечку отбирают. Конечных пользователей это никак не заэффектит, конечно, и мы все найдем путь и каким-то образом поддержим все, что надо, все, что надо поддержать, и если что, будем, не знаю, писать письма в Oracle, прося их продлить нам еще на пару лет поддержку Unsafe, но это такой немножко заковыристый момент прямо сейчас.
[2:23:22] Александр: Да, у меня про все вот это экосистему, знаешь, какие мысли такие, может быть, кстати, они тебя посещали, а может быть, они будут для тебя относительно новые, что вот у меня был выпуск подкаста про ClickHouse, про то, как там написано JIT, там есть часть запросов, которые вот прям пользовательский запрос может скомпилироваться и выполняться лучше. То есть это не интерпретация SQL, да, а его компиляция в C++, вот, а потом машинный код прям здесь на ходу. А в Java это встроенная фича, JIT с нами уже давно. И вот здесь вопрос, а наблюдал ли ты как-то в своей карьере, когда вот наличие JIT в Java и написание такого продукта, вот, которым есть тяжелый compute, execution пользовательский, оно, ты такой говорил, вау, как хорошо, что здесь есть JIT, вот благодаря ему там эти запросы работают сильно быстрее. Или это совсем не чувствовалось, не наблюдалось?
[2:24:26] Станислав: Честно говоря, мне кажется, что нет. Я вот сейчас пытаюсь вспомнить такие кейсы, и вот так сходу не могу, потому что, ну, все-таки JIT, он помогает, да, он помогает там с какими-то hot path, но вот, например, когда мы говорим про JIT-компиляцию запросов, она же все-таки происходит там на уровне выше, то есть она происходит так, что мы фактически создаем, насколько я, ну, понимаю JIT в базах данных, которые вот так вот продвинуты оптимизировать запросы, мы фактически создаем параллельный путь выполнения, то есть у нас есть наш generic код там с бранчами, да, со всем, и у нас есть вот такой вот супероптимизированный код для вот этого конкретного запроса, где мы делаем только то, что нам надо для этого конкретного запроса. Когда Java, ну, JVM выполняет какой-то код, ну, то есть там же нет магии, да, то есть там, ну, у нас есть C2, что делает C2, ну, он просто смотрит, какие методы чаще выполняется, какие методы горячее, какие нет, и там inline методы, которые более горячие. С точки зрения Java JIT, он просто увидит, что до всех code path у нас выполняются, он же не знает, что у нас есть там вот такой-то запрос, и вот если у нас такой-то запрос, то мы всегда идем вот по такому code path, а если у нас запрос другой формы, то мы всегда идем вот по вот этому второму code path. И вот под эти вот две формы запросов мы могли бы сделать там отдельно скомпилировать куски кода, да, из от JIT, чтобы их всегда выполнять. Тут не получится это сделать на уровне компилятора, наверное. То есть какой-то, наверное, какой-то Очень умный компилятор так сделать бы мог. Наверное, скорее нет. Может быть, какие-то вещи, какие-то вещи на уровне уже пользовательского compute. Ну, как раз, когда мы говорим про Compute Grid. Да, вот пользователь, вот он пишет как на Java, вот он пишет так, как привык. Он его слишком сильно не оптимизирует. Да, опять же, нам не нужно думать про какие-то микрооптимизации, потому что все микрооптимизации за нас сделает Java. Ну, наверное, вот эта вот простота, она действительно сохраняется, но она сохраняется главным образом из-за того, что, ну, просто Java язык такой, все, что хорошо и все, что плохо в Java, мы берем и переносим просто с клиента на сервер в нашем collocated compute в этой модели. Нам не нужно. Мы не вводим каких-то дополнительных эффективностей здесь. Вот. Может быть, это как раз разница между, вот, когда ты сравниваешь, вот, если я могу сделать одну и ту же вещь, ну, или какую-то похожую вещь, я могу написать SQL-запрос, или я могу написать Java. Да, ну, можно, наверное, представить ситуации, когда, написав это на Java, я выиграю просто потому, что мне там. Где-то мне JIT поможет, что-нибудь ускорит на сервере, там, парсинга какого-нибудь не будет, и за счет этого вот нативный Java, нативный Java, вот, будет немножко побыстрее. Но чаще, опять же, помним, что это же все распределенное выполнение. То есть, ну, ты здесь выиграл какие-нибудь там, не знаю, там, десятки наносекунд, тут выиграл какие-то десятки наносекунд, а потом ты послал этот запрос по сети, и у тебя там латенсы этого запроса, латенсы просто пересылки этого запроса и вернуть ответ, это там миллисекунда. И все вот такие вот совсем микрооптимизации, они немножко теряются, возможно.
[2:28:24] Александр: Ну, да. Ты прав, просто мне было, конечно, так как-то вспомнилось, да, еще вот такая отсылочка, ребят. Если вам интересно понять про то, как продвинутые оптимизаторы запросов вот с JIT-ом встроенным работают в современных баз данных, таких как ClickHouse, тоже, пожалуйста. У меня, кстати, на сайте подкаста apakhmv.xyz, ссылочка всегда есть, можно строку поиска найти и просто там написать слово JIT, и вам выдаст все подкасты, в которых оно упоминалось. Вот, строка называется «Саша говорил». Так что можно поэтому искать. Кстати, я никогда не говорил про этот сайт, подкаст, вот сегодня первое упоминание. Да, мы очень хорошо прошлись про, собственно, про in-memory data grid, про Java хорошо поговорили, про. В целом, мне кажется, вот у людей, которые не были знакомы с подобными технологиями, сложилась некоторая картинка. Мы, конечно, оба, как бы, довольно сильно погруженных в эту технологию человека и могли что-то, может быть, где-то не до конца дообъяснить. Если есть какие-то вопросы, ребят, всегда пишите в подкасте, там, в телеграм-канале подкаст или в ютубе вот комментарий оставляйте. Я тут уж точно приду, отвечу. Мне бы хотелось вот такой вопрос задать, уже как бы чуть-чуть вынуров из конкретной технологии, посмотрев вокруг и поняв, вот мне сейчас, как инженеру рядовому, вот я пишу, ну, серьезные вещи сейчас с Клодом, там каждый инженер может в целом отвечать за целое направление почти. У него есть достаточно компетенции. Вот он выбирает себе хранилище данных для своего какого-то области ответственности. Вот в каких случаях ему стоит приглядеться к подобным технологиям, про которые мы сегодня говорим, а в каких нет?
[2:30:10] Станислав: Я тут начну, наверное, с хот-тейка. Десять раз подумайте, прежде чем идти в in-memory data grid, in-memory базы данных, распределенные базы данных, и даже распределенные кэши, хотя, казалось бы, Redis уже такой хаус-холд нейм, и за Redis рука тянется в любой непонятной ситуации. Подумайте десять раз. Я много лет занимаюсь инсталляцией этих штук в разные-разные-разные окружения, в разные команды, в сильные технические команды, слабые команды, в mission-critical проекты, в какие-то менее важные проекты. И любая технология, она должна быть соразмерна, ее сложность должна быть соразмерна, как бы, value, который вы из нее получаете. Распределенные системы — это сложные технологии. Мы с Сашей сегодня говорили о том, что средний уровень команд, которые эффективно используют in-memory data grid, обычно довольно высокий. У вас должна быть соответствующая задача и должна быть соответствующая команда и соответствующая экспертиза, чтобы, ну вот, чтобы вот в это лезть. Очень часто вот такие. Я начну, да, я начну с того, когда не использовать, да, а потом поговорим про то, когда вот такой формат технологии подходит. Такие самые простые ошибки, которые вот я вижу, это то, что мне нужен там. Моя база не справляется, мне нужно поставить in-memory data grid, как бы, во-первых. А вы можете сделать вашу базу больше? Вы можете сделать scale-up? Нет ничего, плохого в scale-up. Если у вас как бы база крутится на 10 гигабайтах памяти, возможно, вам надо дать ей побольше памяти. То есть часто видно, как люди просто, ну там, in-memory cache не включают в базе. Во всех базах есть in-memory cache. Там база, она тоже умеет пользоваться памятью. Может быть, не так эффективно, но она ускорит ваши запросы. Дайте ей, там, дайте вашей базе побольше памяти, убедитесь, что она там правильно затюнена. Если вам нужен high availability, поставьте вторую копию вашей базы. Там во всех базах есть как минимум как минимум active-passive копирование, да, когда вы получаете там read-only реплику, и которую можно там даже сделать синхронной, да, чтобы у вас там durability данных было. Там все эти решения, все эти решения есть. in-memory data grid, конкретно in-memory data grid, это технологии, которые вот мантра Apache Ignite всегда была speed and scale. Это скорость и масштабируемость. Вы идете в эту технологию, когда вам нужно быстрее, и быстрее чаще всего означает латенция. То есть вы чаще всего хотите латенции не в десятки миллисекунд или сотни миллисекунд, а в единицы миллисекунд. У вас есть там какие-нибудь запросы, которые у вас затаскивают данные из диска, и вам нужно эти данные затаскивать там, ну и вам нужно эти запросы выполнять там за пару секунд, а они выполняются у вас в текущем времени. 30 секунд. Вот вы вот за такими вещами берете in-memory data grid с точки зрения скорости. С точки зрения масштабируемости, чаще всего это про количество параллельных запросов. То есть вы говорите, что у меня есть там сегодня тысяча пользователей, мне нужно поддержать 10 тысяч параллельных пользователей через полгода. Как мне это сделать? Если вы эти 10 тысяч пользователей, если всех пустите на свою обычную базу, то, возможно, они вам эту базу положат. Вы говорите, окей, я тогда перенесу часть этой нагрузки с базы на in-memory cache или in-memory data grid. А конкретно in-memory data grid очень хорошо работают как раз когда вам нужно делать вещи быстро с очень низкой латенси и при этом поддерживать вот эту, большую масштабируемость. То есть вы всегда сможете в этот in-memory data grid добавить еще узлов, добавить еще CPU, добавить еще памяти и больше ивентов, больше запросов обработать в параллель. Опять же, если вы видите, что у вас сейчас источник латенси, это, например, множественные запросы к базе, ну и у вас просто бизнес-логика такая, что там, ну вот вам действительно либо хранимые процедуры писать на базе, и теперь у вас база становится как бы частью вашего приложения, а база у вас, скорее всего, шарена, и вам, ну это просто операционно будет сложно делать. Либо вы можете воспользоваться in-memory data grid и воспользоваться распределенным compute и запаковать ваши вот эти сложные бизнес-операции в распределенный compute и выполнять эти операции за один сетевой hop. Такие вещи хорошо работают. Хорошо работают, вот, кстати, самая вот горячая область была до того, как появился чат GPT, это был стрим-процессинг. Все системы начали переходить там с бач-аналитики на стриминг, и выяснилось, что для стриминга, для того, чтобы обрабатывать ивенты прямо вот онлайн, да, у вас есть Kafka, и Kafka может их перетащить из точки в точку, но вам нужно делать какую-то агрегацию, вам нужно дополнять ваши ивенты, ваши стримы с какими-то, ну, с какими-то данными предыдущими, которые у вас уже хранятся. То есть там есть технологии типа Apache Flink, которые делают часть из этого, но они не очень хорошо, они не всегда подходят, да, они лучше подходят, когда нужно именно обработать стрим, меньше, когда нужно его агрегировать и там заджойнить с имеющимися данными. Вот для этого очень хорошо подходят и in-memory data grid тоже, то есть когда вам нужно обрабатывать какие-то потоки данных. И мы, кстати, вот в эти, в более высокоуровневые API in-memory data grid не пошли, но тоже в in-memory data grid на самом деле очень часто бывают API, мне кажется, во всех in-memory data grid есть всякие разные in-memory дата коллекции, типа там map, list, вот буквально map, буквально list, буквально какие-нибудь там распределенные, распределенные mutexы вы там можете получить, и также там есть примитивы для обработки стримов, как там, например, то, что называется continuous query, когда вы можете подписаться на какие-то события в изменении данных, когда кто-то в эту табличку пишет, а другое приложение читает все изменения из этой таблички и автоматически на них как-то реагирует, что-то похожее на триггеры в базах данных. То есть вот такую вот сложную обработку действительно хорошо делать на in-memory data grid.
[2:37:21] Александр: Здесь, наверное, нужно поговорить еще про identity in-memory data grid, то есть где они сейчас-то все находятся, и поговорить, может быть, мы так и не, кстати, не произнесли все названия, да, все названия связанных технологий, давай, может быть, поговорим про это сейчас. Когда мы говорим in-memory data grid, вот к концу третьего часа подкаста мы до этого дошли, мы говорим про какие технологии?
[2:37:45] Станислав: Мы говорим в первую очередь Apache Ignite, GridGain, Hazelcast, Oracle Coherence, GigaSpaces, Infinispan, который сейчас, по-моему, живет уже только как часть JBoss сервера, но все еще жив как технология. Вот это все in-memory data grid. Что я лично не отношу к in-memory data grid, это кэши типа Redis и Memcached, это in-memory базы данных или memory-центричные базы данных, типа SingleStore, бывший MemSQL, или Aerospike. Это не NoSQL базы, которые тоже часто сфокусируют на скорости, типа там Cassandra какой-нибудь. Это не распределенный SQL, типа YugabyteDB и CockroachDB. Но все вот эти технологии, которые я назвал, они чем-то похожи на in-memory data grid, или in-memory data grid похожи на них. То есть так же, как в CockroachDB, в Apache Ignite есть распределенный SQL. Да? Так же, как, например, в SingleStore Apache Ignite может, ну или сфокусирован на том, чтобы хранить данные в памяти и быстро их в памяти обрабатывать. Да? Так же, как in-memory кэши, in-memory data grid предоставляют key-value доступ, и вот это распределенное in-memory хранилище простое. Во многом из-за этого, из-за того, что, что появилось так много разных систем около in-memory data grid, и возник вот, ну, какой-то такой кризис идентичности у этих технологий. Если вы вот сейчас посмотрите на сайт Ignite, Hazelcast, даже GigaSpaces, никто из них не называет себя in-memory data grid. Это название, оно, наверное, осталось, ну, во многом в прошлом сейчас. Это все еще термин, который понимается в индустрии, но, ну, это не так. Ни одна из технологий больше не является то, что называется, особенно коммерчески pure play in-memory data grid. То есть ни одна технология не делает только это. Ignite ушел больше в сторону базы данных и стал такой более SQL-native. GridGain, как коммерческая версия, в итоге был куплен MariaDB и сейчас является частью MariaDB платформы. GemFire, еще один, который я не назвал. GemFire был куплен. Много. кем несколько раз и сейчас является частью платформы VMware. Coherence идет как довесок к Oracle, то есть тоже не является как бы вещью в себе. Наверное, последняя технология, которая просто продает in-memory data grid, это Hazelcast, но даже Hazelcast не называет себя in-memory data grid уже много лет и ушел больше в стрим-процессинг. И вот в текущих версиях там, мне кажется, от стрим-процессинга уже больше, чем от in-memory data grid. Это как раз потому, что вот эта область, она немножко сжимается со всех сторон, и оказывается, что когда у тебя есть одна конкретная задача, скорее всего, у тебя есть какое-то другое смейство технологий, которые либо тоже подходят, либо подходят лучше, либо, может быть, подходят даже хуже, чем in-memory data grid, но ты там понимаешь его лучше, скажем, может быть, Redis технология хуже для твоей задачи, и я бы сказал, что практически во всех случаях проще воспользоваться Ignite, чем Redis. Но возможно, вы уже пользуетесь Redis, или вы уже знаете Redis, или у вас есть коллега, который знает Redis, поэтому вам проще взять Redis в этот момент. Но когда у вас возникает какая-то проблема, которой нужно сразу, несколько разных вещей, то есть мне нужно сразу вот и низкую латенси, и SQL получить, где мне это взять? Ну вот только in-memory data grid, или то, что было in-memory data grid, и вот сейчас нашли какую-то вот эту, нашли какую-то новую новую идентичность.
[2:42:08] Александр: Ну и, наверное, завершая наш разговор, я бы вот такой бы вопрос задал. В современном мире у нас, у нас появился, появились новые, новая категория пользователей, да, это собственно агенты, и мы про них с тобой много говорили. Вот в этом разрезе ожидаешь ли ты какой-то трансформации подобных систем, как Apache Ignite с приходом агентов, могут ли они быть полезны или бесполезны, могут ли они быть трансформированы как-то под новые юзкейсы, под новых пользователей?
[2:42:49] Станислав: Отличный вопрос, да. Я на самом деле вижу здесь очень большое поле для развития. Опять же, начну с того, что мне кажется скорее тупиковым путем. Мне не кажется, что можно просто поставить MCP сервер перед in-memory data grid, как мы делаем это перед базами, и сказать агент, ну вот, ходи как приложение, короче, в in-memory data grid. Это по двум причинам. Во-первых, агентам не нужна такая офигенно низкая латенси на доступ, ну потому что они сами тупо медленные, и как бы от того, что я затянул данные за одну миллисекунду из Apache Ignite, или я затянул за 20 миллисекунд из Postgres, ну для конечного пользователя ничего не изменится, потому что агент там будет думать полминуты все равно над этими данными, какая разница. Плюс in-memory data grid, как мы говорили, там есть все-таки моменты, что, а что там с Multi-Tenancy, а это все-таки такие сложные дорогие системы, и там SQL есть, да, но он там не такой полный, как в традиционных базах данных или в аналитических системах, поэтому давать просто агентам, как обезьяне с гранатой, давать MCP, разрешать там пускать любые запросы в грид, ну это скорее всего не полетит. Что полетит? Я очень верю в то, что, агентские приложения, им нужны такие же точно примитивы, как нужны старым FullStack-приложениям. Старые FullStack-приложения использовали in-memory data grid, вот с чего мы начали, как сешенкаши, как как расширение собственной памяти, как какой-то слой оптимизации доступа к данным из базы. Что у нас есть здесь для агентов? У нас есть agentic memory, agentic memory, у нас есть всякие семантические кэши, у нас есть доступ оптимизации к LLM, все это очень хорошо ложится как раз на концепцию in-memory data grid. Вряд ли in-memory data grid это хорошая штука для того, чтобы сервить бизнес-данные для агента, по крайней мере в текущем его виде. Может быть, когда агенты сами станут побыстрее, им понадобится латенси побольше, тогда да. А как агентская память, как вот этот вот шареный слой, у которого, может быть, могут быть пониже гарантии по консистенции, может быть, у него могут быть пониже там гарантии по дюрабилити, но он должен не создавать нагрузку на мою обычную базу, он должен работать быстро, не создавать никакого дополнительного латенси для агента. Мы должны иметь возможность скалировать наших агентов сколько угодно, и вот этот вот слой agentic memory, он должен поддерживать любое количество конкурентных каких-то запросов, вызовов. Вот в этом я вижу очень большой такой, очень большой потенциал. И последнее, что здесь скажу, но это такая футуристическая немножко штука, которую я вижу пока что больше в академии, чем в реальном применении. На стороне агентов, не на стороне агентских приложений, а на стороне инференса моделек, есть такие штуки, которые называются key-value, кэши. key-value в этом случае имеет немножко другое значение, не тот key-value, о котором мы говорим для бизнес-данных, а key-value — это такие матрицы, которые внутри LLM у себя считает, и, не вдаваясь в подробности, когда вы задаете новые и новые вопросы в одном и том же LLM-чате, у вас вот эти вот старые посчитанные key-value матрицы, их можно просто переиспользовать, потому что они будут повторяться заново и заново и заново. Для того, чтобы оптимизировать время на GPU, сейчас инференс-системы, они пытаются дать больше памяти на стороне GPU, на стороне инференса, и сохранять вот эти один раз посчитанные key-value матрицы, сохранять куда-нибудь, сохранять в какую-нибудь локальную память. И есть сейчас вот такое вот решение, что, а давайте мы возьмем in-memory кэши, in-memory data grid, ну, сейчас там больше на стороне in-memory кэшей смотрится там Redis, Valkey, и будем их использовать как распределенное хранилище для вот этих вот предвычисленных матриц, но вообще не на стороне аппликаций, а на стороне инференса, вот прямо рядом с GPU. И там много оптимизации, сейчас я вижу, делается как раз вокруг этого. Мне кажется, что в итоге мы можем прийти на стороне вот этого матана и обсчета матриц в GPU, мы можем прийти примерно к тому, к чему мы пришли на стороне приложений, когда у нас есть просто много, очень много данных, и мы хотим иметь к ним быстрый доступ, и нам снова нужны in-memory кэши, in-memory data grid, просто мы вот эту вот сложность выпнули куда-то вот на уровень ниже внутри инфраструктуры. Вот это будет очень интересно за этим наблюдать следующие там 2-3 года. Сейчас очень много ресерча на эту тему видно.
[2:48:31] Александр: Ну да, такая как бы спираль получается. Мы пришли в ту же точку, но с другими технологиями и на другом, может быть, каком-то уровне, в другом месте, но точка, да, вот это вот развитие как бы наблюдать в целом довольно интересно, и оно такое, знаешь, все, что мы, как индустрия, прошли там за последние 40 лет, мы сейчас там за 5 лет с агентами еще раз пройдем, сделаем это более эффективно, быстро там, но по сути тот же самый такой кружок проходим, это интересно. Слушай, мне кажется, мы хорошо поговорили. Есть ли тебе что-то, что ты вот прям хотел сказать и не сказал про in-memory data grid, про что мы не упомянули совсем?
[2:49:14] Станислав: Нет, я думаю, мы очень хорошо все, очень хорошо все открыли. Читайте книжки про распределенные базы данных, поймете распределенные базы данных, поймете распределенные data grid. И, ну, вот как напутствие про data grid, просто хочу сказать, что при том, что мы сказали, что да, это такие сложные технологии, туда лучше не соваться и не суйтесь туда, если они вам очень не нужны, но бояться их тоже не стоит, и после того, как вы поймете, как они работают, особенно поймете вот эти специфику коллокации и специфику вот этого распределенного compute, распределенных вычислений, коллоцированных вычислений, вещи становятся очень простыми, и даже в других областях знаний, особенно в инфраструктуре, в бигдате, в AI, вещи резко упрощаются, глаза открываются.
[2:50:12] Александр: Хорошо, спасибо тебе большое, Стас. Ну и напоследок, на каком бы языке программирования ты сейчас писал Apache Ignite?
[2:50:19] Станислав: Я бы писал на языке программирования agents.md. Но, если серьезно, то я бы, конечно, делал это на Клоде, а Клод отлично пишет на Rust, я бы, наверное, написал, ядро бы я писал на Rust, а вот всю обвязку надо все равно писать. Самое главное сделать хорошие API, а API придется все равно делать на каждом языке свои. И самое красивое будет на Java.
[2:50:52] Александр: Ну, все, на этом спасибо большое тебе, Стас, и пока.
[2:50:56] Станислав: Спасибо, Саша.
[2:51:00] Александр: Спасибо.