Эшу быдлокодит
Как нормальные люди пишут тесты? Выявляют кейсы, которые надо протестировать, пилят в той или иной форме генератор тестовых данных, покрывают кейсы тестами, а потом гоняют их и радуются. Если структура данных сложная, а вникать в суть происходящего лень …
И сегодня, на эти же грабли, в этом же самом месте, два раза, хренак, хренак!
Да, я так и не написал генератор данных, поленился.
Да, я так и не написал генератор данных, поленился.
🔥2🤣2🤯1
Получил лвлап и кучу экспириенса по работе с тарантулом, что характерно - на ровном месте.
Рассмотрим кейс: есть некая запись, относящаяся к человеку (user_id, unsigned(UInt64)). У нее есть время создания (creation_time, integer (Int64)) и время закрытия (cancelled_at, integer (Int64)).
Нужно дёрнуть все записи, относящиеся к человеку, актуальные на определенный момент времени. То есть запрос вида:
И все было прекрасно, пока я не столкнулся с реальностью: на "жирном" пользователе, с 20к+ записей, запрос выполняется неадекватные 20+ секунд.
В тарантуле есть составные индексы, туда можно передавать запросы из нескольких частей. Но вот беда: запрос, объединяющий сравнения == <= и >= он выполнить не может.
Решил зайти с необычной стороны: сделать условием >= и построить следующий индекс:
Сравнение происходит не по частям индекса. В сравнении участвует весь массив целиком как единая сущность. Говорят, что логика сравнения где-то была описана, но с ходу страница документации не ищется и где она лежит никто не помнит.
В тарантуле есть специальный инструментарий для работы с пространственными данными. Казалось бы, при чём тут они?
Поднимемся по лестнице абстракций на пару ступеней. Представим себе запись о пользователе прямоугольником в системе координат (id пользователя/время). Одна точка прямоугольника - создание записи, имеет координаты (user_id, creation_time). Вторая - (user_id, cancelled_at).
Соответственно, проверка "существовала ли запись о пользователе в определенный момент времени"? Сводится к задаче о принадлежности точки прямоугольнику. Готовое решение для запроса по индексу - в наличии. При желании, можно добавить ещё измерений (до 20), но что-то пока не хочется.
Проблема решена? Фииигушки. Индекс не ест мои значения в Int64 и UInt64, ему подавай Float64. При ковертации "в лоб" хвостики id теряются, из-за ограничений точности Float64. Добавляем очередной велосипед.
Барабанная дробь... Стандартный инструмент для запросов к кластеру не поддерживает (и скорее всего не будет) запросы по этому типу индексов. Опускаемся на пару уровней абстракций ниже, берем апи маршрутизации и делаем небольшую велосипедную хранимку, позволяющую вызвать хранимку на инстансах с данными через инстанс-маршрутизатор.
А вот теперь - всё. Ну или мне так кажется:)
P.S. В монге решил эту же задачу за 25 (!) минут, запрос + индексы, всё работает с первого раза.
#tarantool
Рассмотрим кейс: есть некая запись, относящаяся к человеку (user_id, unsigned(UInt64)). У нее есть время создания (creation_time, integer (Int64)) и время закрытия (cancelled_at, integer (Int64)).
Нужно дёрнуть все записи, относящиеся к человеку, актуальные на определенный момент времени. То есть запрос вида:
Входящие параметры: id, time.
user_id == id && creation_time <= time && (cancelled_at == nil || cancelled_at >= time)
Во всех мануалах предлагается простое решение, сделать select по самому лучшему условию, а дальше с помощью специального синтаксиса перебрать результат и отфильтровать нужное. Использовал этот подход, решил задачу и пошел дальше.И все было прекрасно, пока я не столкнулся с реальностью: на "жирном" пользователе, с 20к+ записей, запрос выполняется неадекватные 20+ секунд.
В тарантуле есть составные индексы, туда можно передавать запросы из нескольких частей. Но вот беда: запрос, объединяющий сравнения == <= и >= он выполнить не может.
Решил зайти с необычной стороны: сделать условием >= и построить следующий индекс:
creation_time, user_id, - user_id, - cancellation_time
То есть добавить в индекс инвертированный id пользователя и инвертировать время закрытия. Тогда в теории все должно выцепляться одним условием ">=". Что могло пойти не так? Всё. Как оказалось, для составных условий используется логика обработки частей запроса, отличающаяся от той, что заложена в схожей ситуации в MongoDB или PostgreSQL.Сравнение происходит не по частям индекса. В сравнении участвует весь массив целиком как единая сущность. Говорят, что логика сравнения где-то была описана, но с ходу страница документации не ищется и где она лежит никто не помнит.
В тарантуле есть специальный инструментарий для работы с пространственными данными. Казалось бы, при чём тут они?
Поднимемся по лестнице абстракций на пару ступеней. Представим себе запись о пользователе прямоугольником в системе координат (id пользователя/время). Одна точка прямоугольника - создание записи, имеет координаты (user_id, creation_time). Вторая - (user_id, cancelled_at).
Соответственно, проверка "существовала ли запись о пользователе в определенный момент времени"? Сводится к задаче о принадлежности точки прямоугольнику. Готовое решение для запроса по индексу - в наличии. При желании, можно добавить ещё измерений (до 20), но что-то пока не хочется.
Барабанная дробь... Стандартный инструмент для запросов к кластеру не поддерживает (и скорее всего не будет) запросы по этому типу индексов. Опускаемся на пару уровней абстракций ниже, берем апи маршрутизации и делаем небольшую велосипедную хранимку, позволяющую вызвать хранимку на инстансах с данными через инстанс-маршрутизатор.
А вот теперь - всё. Ну или мне так кажется:)
#tarantool
🔥4🤡2
Эшу быдлокодит
Получил лвлап и кучу экспириенса по работе с тарантулом, что характерно - на ровном месте. Рассмотрим кейс: есть некая запись, относящаяся к человеку (user_id, unsigned(UInt64)). У нее есть время создания (creation_time, integer (Int64)) и время закрытия…
Решил добавить в индекс еще пару измерений. Теперь главное не пытаться представить, как выглядит запись в базе данных в качестве 4х мерного прямоугольника-параллелепипеда (термин из тарантуловской документации), где измерения - id пользователя, время, ранг записи в древовидной структуре и её тип.
Forwarded from Вехденская правда
CV_StolbovV_short.pdf
90.2 KB
А разнесите резюму по своим каналам, что ли.
👍1💩1
Эшу быдлокодит
И сегодня, на эти же грабли, в этом же самом месте, два раза, хренак, хренак! Да, я так и не написал генератор данных, поленился.
И в четвертый раз, на те же грабли, в том же месте!
Да, я пока ленюсь написать нормальный генератор данных.
Да, я пока ленюсь написать нормальный генератор данных.
🤡4🤣2
Эшу быдлокодит
И в четвертый раз, на те же грабли, в том же месте! Да, я пока ленюсь написать нормальный генератор данных.
И в пятый, надеюсь - в последний.
😁6🤡3
Пока монга выполняет роль хранилища-помойки, вида: положил кучку - забрал по id или паре простых запросов - она прекрасна и крайне дружелюбна.
Но стоит сделать шаг втемноту сторону каких-то статистических запросов - все перестает быть столь уютным.
Для человека, два года пользующегося монгой и казалось бы привычного к ней, первое составление запроса вида (дико извиняюсь за псевдо-sql):
Сначала, как обычно, пытаешься слепить запрос прямо в c#, используя синтаксический сахар, предоставляемый библиотекой-конектором.
После полутора дней мучений в с# я отправился в дефолтную графическую оболочку над базой, где часа за 2 в конструкторе составил нужный мне запрос.
Теперь я имею json в 30 строк, который при выполнении в монге выдает нужный мне результат. Осталось корректно перенести его в с#.
#mongodb
Но стоит сделать шаг в
Для человека, два года пользующегося монгой и казалось бы привычного к ней, первое составление запроса вида (дико извиняюсь за псевдо-sql):
select distinct field1, field2 from table where (пара условий) order by field2 limit 100 skip 100
становится увлекательным квестом на пару дней. То есть мне нужно взять данные без дублирования по одному полю из таблицы по условию, отсортировать и выдать из базы с пагинацией (постранично).Сначала, как обычно, пытаешься слепить запрос прямо в c#, используя синтаксический сахар, предоставляемый библиотекой-конектором.
После полутора дней мучений в с# я отправился в дефолтную графическую оболочку над базой, где часа за 2 в конструкторе составил нужный мне запрос.
Теперь я имею json в 30 строк, который при выполнении в монге выдает нужный мне результат. Осталось корректно перенести его в с#.
#mongodb
💩2
Эшу быдлокодит
Пока монга выполняет роль хранилища-помойки, вида: положил кучку - забрал по id или паре простых запросов - она прекрасна и крайне дружелюбна. Но стоит сделать шаг в темноту сторону каких-то статистических запросов - все перестает быть столь уютным. Для…
Вот примерный json, генерируемый запросом. Сравните по читаемости с псевдо-sql версией в прошлом посте.
Монга умеет в distinct запросы, но туда не завезли пагинации (постраничной отдачи), потому идём окольным путём 🤡
#mongodb
[{
$match: {
Field3: "qwerty123",
},
},
{
$group: {
_id: "$Field1",
id: {
$first: "$Field1",
},
Field2: {
$first: "$Field2",
},
},
},
{
$sort: {
Field2: -1,
},
},
{
$skip: 0,
},
{
$limit: 2,
},
{
$project: {
_id: 1,
},
},
]
group в данном случае используется для получения эффекта distinct из sql запроса.Монга умеет в distinct запросы, но туда не завезли пагинации (постраничной отдачи), потому идём окольным путём 🤡
#mongodb
А от шарповой версии запроса уже конкретно начинает течь кровь из глаз, при том, что простые запросы там прекрасны. В качестве примерна приведу получение всех записей, где определенное поле равно чему-то:
#mongodb
_mongoCollection.Find(record=>record.Field3=="qwerty123").ToList();
В примере используется трансляция в монговский формат внутришарпового языка запросов к коллекциям LINQ, потому всё максимально прозрачно.#mongodb
Примерно год назад я перетаскивал базу данных Палантира (чуть больше терабайта) с сервера на сервер. С помощью консольных утилит это было то ещё удовольствие, требующее как минимум стабильного соединения с сервером в течение многих часов.
В монге встроен механизм, который может быть использован в т.ч. для подобного переезда без отключения базы: создаём реплики на новых серверах, после чего постепенно отключаем старые инстансы и база перенесена.
В посгресе такого механизма "из коробки" нет. Но есть широко распространенное средство организации отказоустойчивых кластеров: Patroni. С год назад я к сожалению не осознавал, зачем оно мне.
Прилагаю статью с описанием процесса переноса продакшн базы без остановки доступа к данным. Кросивое.
Теперь надо бы для общего развития поднять кластер где-нибудь у себя.
#mongo
В монге встроен механизм, который может быть использован в т.ч. для подобного переезда без отключения базы: создаём реплики на новых серверах, после чего постепенно отключаем старые инстансы и база перенесена.
В посгресе такого механизма "из коробки" нет. Но есть широко распространенное средство организации отказоустойчивых кластеров: Patroni. С год назад я к сожалению не осознавал, зачем оно мне.
Прилагаю статью с описанием процесса переноса продакшн базы без остановки доступа к данным. Кросивое.
Теперь надо бы для общего развития поднять кластер где-нибудь у себя.
#mongo
В прошлом году итоги я подвёл в ноябре при запуске #палантир@eshu_coding, после него ничего особо интересного не было.
В этом году никаких проектов не под NDA, я в общем-то не делал. Если без подробностей, то вот краткие итоги личностного роста:
1. Научился готовить Tarantool, прошёлся по куче граблей, примерно понял, где он реально нужен, и главное, где не нужен. Пришлось поверхностно познакомиться и с механизмом построения кластеров: репликация, шардирование, вот это вот всё.
2. Нырнул в MongoDB на уровне, отличном от "положил как в помойку, достал по id/однострочному запросу". Многоступенчатые запросы, подписки на обновления коллекций, TTL, транзакции, materialised view. Впервые завел реплика-сет вместо отдельностоящего инстанса.
3. Построил пару обменников на RabbitMQ, пришло осознание проглоченного и применённого на коленке в 2021.
4. Влюбился в систему сбора метрик Prometheus. Если раскидать их по приложению и заодно подключить сбор с компонентов системы (баз данных и т.д.), получается крайне информативно. Дефолтный визуализатор так себе, но основные функции выполняет.
5. Познакомился с EKL стеком - сбор и визуализация логов. Особой любви не случилось, уж больно оно огромно и прожорливо. Можно кстати использовать ELK для построения красивых дашбордов по данным из Prometheus-а, но это мне пока не особо нужно.
6. Прогресс как шарписта у меня вышел так себе. Научился красиво описывать rest api с помощью Swagger, да в общем-то и всё. Ну ещё окончательно освоил разработку с использованием докера: приложение сразу запускается и отлаживается в контейнере, окружённое соседними сервисами. При прогоне тестов также активно использую песочницу, поднятую в docker-compose.
P.S. NoSQL я накушался досыта, 2023 - время вернуться к истокам - C# + PostgreSQL.
В этом году никаких проектов не под NDA, я в общем-то не делал. Если без подробностей, то вот краткие итоги личностного роста:
1. Научился готовить Tarantool, прошёлся по куче граблей, примерно понял, где он реально нужен, и главное, где не нужен. Пришлось поверхностно познакомиться и с механизмом построения кластеров: репликация, шардирование, вот это вот всё.
2. Нырнул в MongoDB на уровне, отличном от "положил как в помойку, достал по id/однострочному запросу". Многоступенчатые запросы, подписки на обновления коллекций, TTL, транзакции, materialised view. Впервые завел реплика-сет вместо отдельностоящего инстанса.
3. Построил пару обменников на RabbitMQ, пришло осознание проглоченного и применённого на коленке в 2021.
4. Влюбился в систему сбора метрик Prometheus. Если раскидать их по приложению и заодно подключить сбор с компонентов системы (баз данных и т.д.), получается крайне информативно. Дефолтный визуализатор так себе, но основные функции выполняет.
5. Познакомился с EKL стеком - сбор и визуализация логов. Особой любви не случилось, уж больно оно огромно и прожорливо. Можно кстати использовать ELK для построения красивых дашбордов по данным из Prometheus-а, но это мне пока не особо нужно.
6. Прогресс как шарписта у меня вышел так себе. Научился красиво описывать rest api с помощью Swagger, да в общем-то и всё. Ну ещё окончательно освоил разработку с использованием докера: приложение сразу запускается и отлаживается в контейнере, окружённое соседними сервисами. При прогоне тестов также активно использую песочницу, поднятую в docker-compose.
P.S. NoSQL я накушался досыта, 2023 - время вернуться к истокам - C# + PostgreSQL.
Telegram
Эшу быдлокодит
Палантир. Часть 24. Итоги, общая архитектура проекта.
#палантир@eshu_coding
По завершении ботов-агрегаторов проект я считаю завершенным, время описать что же получилось и подвести итоги.
На картинке:
DataFair - мастер сервер
DataLoader - сборщик
Пунтиром…
#палантир@eshu_coding
По завершении ботов-агрегаторов проект я считаю завершенным, время описать что же получилось и подвести итоги.
На картинке:
DataFair - мастер сервер
DataLoader - сборщик
Пунтиром…
👍9❤1
Ну чтож, надо посмешить богов, озвучить свои планы.
Чего хочу я в этом году:
1. Немного обмазаться фронтенд разработкой, но без фанатизма. Пока думаю о связке React + Typescript.
2. Попробовать на практике стандартную шарповую ORM: Entity Framework.
3. Углубиться в PostgreSQL, поиграться с уровнями изоляции транзакций, подтянуть оптимизацию запросов, возможно попробовать пособирать разные конфигурации кластеров на foreign table.
4. Прочитать несколько умных книжек, что-то типа:
a) Рихтер
b) Высоконагруженные приложения Фаулера
c) Ещё что-нибудь по архитектуре.
5. Начать подтягивать базу по Computer Science, мб копнуть алго задачи.
Вот такие планы, интересно, что исполнится к концу года:)
Чего хочу я в этом году:
1. Немного обмазаться фронтенд разработкой, но без фанатизма. Пока думаю о связке React + Typescript.
2. Попробовать на практике стандартную шарповую ORM: Entity Framework.
3. Углубиться в PostgreSQL, поиграться с уровнями изоляции транзакций, подтянуть оптимизацию запросов, возможно попробовать пособирать разные конфигурации кластеров на foreign table.
4. Прочитать несколько умных книжек, что-то типа:
a) Рихтер
b) Высоконагруженные приложения Фаулера
c) Ещё что-нибудь по архитектуре.
5. Начать подтягивать базу по Computer Science, мб копнуть алго задачи.
Вот такие планы, интересно, что исполнится к концу года:)
🔥7👍4👏2
Эшу быдлокодит
Ну чтож, надо посмешить богов, озвучить свои планы. Чего хочу я в этом году: 1. Немного обмазаться фронтенд разработкой, но без фанатизма. Пока думаю о связке React + Typescript. 2. Попробовать на практике стандартную шарповую ORM: Entity Framework. 3.…
Ну что же, фронтенд мой пока закончился на попытке нормально подружить Visual Studio и фронтенд разработку.
Я стремительно расширяю сознание в части строительства всякого интересного на базе MongoDB, скоро буду выходить на новый уровень в части работы с RabbitMQ.
PostgreSQL? Entity Framework? Фронтенд? Планирование? Не, это для лохов.
Я стремительно расширяю сознание в части строительства всякого интересного на базе MongoDB, скоро буду выходить на новый уровень в части работы с RabbitMQ.
PostgreSQL? Entity Framework? Фронтенд? Планирование? Не, это для лохов.
🔥3🤡2🤔1
Не прошло и 5 лет работы с с#, как я сподобился начать читать в режиме "от корки до корки" базовую книгу: Рихтера. Она несколько устарела: относится к версии языка от 2012 года. Но т.к. базовые концепции, лежащие в основе, с тех пор не поменялись, так что ознакомиться надо.
В начале (введение, общий обзор механики CLR, сборки проектов) ничего нового толком не было, я либо все знал, либо оно было мне уже не актуально. Но стоило дойти до конкретики - некоторые моменты, используемые мной годами, стали вставать на свои места.
Так, операцию сравнения - == между двумя экземплярами класса надо применять с осторожностью, результат зависит далеко не только от их содержимого.
При этом, если классы - это тип, полученный от другого объекта вызовом стандартного метода GetType - с ними такую операцию можно делать спокойно, теперь я знаю почему.
Во всех экземплярах классов хранится ссылка на объект-тип. GetType как раз ее и возвращает. Не то, чтобы что-то революционное, просто небольшой штрих в общей картине.
Далее здесь будет что-то типа конспекта, который буду писать по мере прочтения. Ну и будут новые теги:
#книги
#Рихтер
В начале (введение, общий обзор механики CLR, сборки проектов) ничего нового толком не было, я либо все знал, либо оно было мне уже не актуально. Но стоило дойти до конкретики - некоторые моменты, используемые мной годами, стали вставать на свои места.
Так, операцию сравнения - == между двумя экземплярами класса надо применять с осторожностью, результат зависит далеко не только от их содержимого.
При этом, если классы - это тип, полученный от другого объекта вызовом стандартного метода GetType - с ними такую операцию можно делать спокойно, теперь я знаю почему.
Во всех экземплярах классов хранится ссылка на объект-тип. GetType как раз ее и возвращает. Не то, чтобы что-то революционное, просто небольшой штрих в общей картине.
Далее здесь будет что-то типа конспекта, который буду писать по мере прочтения. Ну и будут новые теги:
#книги
#Рихтер
👍2
Практически осилил фундаментальную главу про типы. В целом - почти ничего нового, но пару занятных нюансов вычитал.
В с# есть разделение на типы - значимые (структуры, живут обычно в стеке) и ссылочные (обычные классы, живут в куче).
Для значимых сравнение между двумя экземплярами идёт по значению внутренностей, для классов - сравнивается эквивалентность ссылки.
Обычная практика - сделать структуру вида "координата", содержащую х, у и z и пользоваться ей.
Вычитал новое для себя, что дефолтные операторы сравнения значимых типов работают на базе рефлексии, что ни разу не быстро. Хочешь скорости - не забудь сам написать эти операторы.
Ещё из интересного - если выдавливать максимум производительности, то использовать штатные методы -ToString, GetType, GetHashCode для значимых типов стоит с осторожностью - легко наступить на грабли и засрать кучу мусором - во многих случаях сначала будет произведена операция упаковки (сама по себе не очень быстрая) значимого типа в кучу из стека, а потом уже вызван метод. Те из них, которые поддаются переопределению - переопределить. А GetType - использовать только по большой нужде. Кроме траты времени на упаковку мы нагружаем сборщик мусора, что в некоторых случаях даже печальнее.
Ну и бонусом, для совсем уж экономии на спичках - избегать виртуальных методов.
#книги
#рихтер
В с# есть разделение на типы - значимые (структуры, живут обычно в стеке) и ссылочные (обычные классы, живут в куче).
Для значимых сравнение между двумя экземплярами идёт по значению внутренностей, для классов - сравнивается эквивалентность ссылки.
Обычная практика - сделать структуру вида "координата", содержащую х, у и z и пользоваться ей.
Вычитал новое для себя, что дефолтные операторы сравнения значимых типов работают на базе рефлексии, что ни разу не быстро. Хочешь скорости - не забудь сам написать эти операторы.
Ещё из интересного - если выдавливать максимум производительности, то использовать штатные методы -ToString, GetType, GetHashCode для значимых типов стоит с осторожностью - легко наступить на грабли и засрать кучу мусором - во многих случаях сначала будет произведена операция упаковки (сама по себе не очень быстрая) значимого типа в кучу из стека, а потом уже вызван метод. Те из них, которые поддаются переопределению - переопределить. А GetType - использовать только по большой нужде. Кроме траты времени на упаковку мы нагружаем сборщик мусора, что в некоторых случаях даже печальнее.
Ну и бонусом, для совсем уж экономии на спичках - избегать виртуальных методов.
#книги
#рихтер
👍3
Сколько времени программисту нужно, чтобы разобраться с тем, как включить мультики с usb с телевизора?
15 минут и помощь системного аналитика, знающего бэкдор: направлять пульт строго в правый нижний угол телевизора.
15 минут и помощь системного аналитика, знающего бэкдор: направлять пульт строго в правый нижний угол телевизора.
😁5
Эшу быдлокодит
Практически осилил фундаментальную главу про типы. В целом - почти ничего нового, но пару занятных нюансов вычитал. В с# есть разделение на типы - значимые (структуры, живут обычно в стеке) и ссылочные (обычные классы, живут в куче). Для значимых сравнение…
Продолжаю читать про типы. Наткнулся на пару моментов, где я по незнанию лепил избыточный код:
1. Увидел как вызывать конструктор из другого конструктора. Чуть меньше копипасты:)
2. Узнал, что статический конструктор по умолчанию потокобезопасен. То есть все мои неэстетичные огородики с блокировками в таких конструкторах отправляются на помойку. Отлично.
#рихтер
#книги
1. Увидел как вызывать конструктор из другого конструктора. Чуть меньше копипасты:)
2. Узнал, что статический конструктор по умолчанию потокобезопасен. То есть все мои неэстетичные огородики с блокировками в таких конструкторах отправляются на помойку. Отлично.
#рихтер
#книги
👍4
Наткнулся на забавный вопрос к собеседованиям:
Как изменить в уже запущенной программе значение строковой константы?
Константа на то и константа, чтобы быть неизменной. Да ещё строки в c# неизменяемые, но есть обходной путь, правда череватый выстрелом даже не в ногу, а в живот.
В c# есть особый раздел языка: unsafe. По сути, это что-то вроде урезанного языка c++. И вот с помощью такой вставки можно получить указатель на место в памяти, где хранится эта константа инагадить записать туда свои байтики.
Потому родился встречный вопрос:
А у вас в кодовой базе такое практикуется?
Как изменить в уже запущенной программе значение строковой константы?
Константа на то и константа, чтобы быть неизменной. Да ещё строки в c# неизменяемые, но есть обходной путь, правда череватый выстрелом даже не в ногу, а в живот.
В c# есть особый раздел языка: unsafe. По сути, это что-то вроде урезанного языка c++. И вот с помощью такой вставки можно получить указатель на место в памяти, где хранится эта константа и
Потому родился встречный вопрос:
А у вас в кодовой базе такое практикуется?
🔥6