TGViewer
Channel Public Channel
EvApps

EvApps

@evapps_team

IT-aутстафферы из Тулы💚
https://evapps.ru/

Здесь пишем про веб- и мобильную разработку

▶️ Наш чат для системных аналитиков: https://t.me/pro_sa_evapps

▶️ Посмотреть, как мы живём: https://vk.com/evapps
Subscribers
199
Photos
1.2K
Videos
52
Links
253

Showing posts older than #1503 · Back to latest

Older Posts 20 shown
Post #1502 148
🎨CSS функции в 2026: насколько далеко они зашли

Когда-то в CSS было всего две функции:

- calc()
- rgb()
И даже calc() многие использовали с осторожностью

Сегодня в CSS появились новые функции:
- anchor()
- anchor-size()
- exp()
- hypot()
- sign()
- shape()
- xywh()
- image-set()
CSS постепенно получает всё больше возможностей для работы с layout, вычислениями и адаптивностью

Разберём несколько интересных функций.
⚓️1. anchor() — позиционирование относительно элемента
Раньше для позиционирования tooltip или popover часто использовали JavaScript:
getBoundingClientRect()
+ scroll offsets
+ ResizeObserver

Теперь часть подобных задач можно решить средствами CSS
.tooltip {
position-anchor: --btn;
left: anchor(left);
top: anchor(bottom);
}

CSS может позиционировать элемент относительно другого элемента-якоря
Это упрощает реализацию tooltip, popover и похожих интерфейсных элементов

📏2. anchor-size() — размеры относительно другого элемента
Теперь элемент может брать размер от другого элемента, а не только от родителя или viewport
.popover {
width: anchor-size(width);
}

Это полезно для:
- Dropdown меню
- Popover
- Контекстных панелей
- Floating UI
Например, когда ширина выпадающего меню должна совпадать с шириной кнопки

📈3. exp() — экспонента в CSS
CSS получил функцию для экспоненциальных вычислений
width: calc(exp(2) * 10px);

Это может быть полезно для:
- Систем масштабирования
- Типографических шкал
- Плавных кривых анимации
Пример:
font-size: calc(1rem * exp(var(--scale)));

📐4. hypot() — вычисление расстояния
Функция возвращает длину гипотенузы по теореме Пифагора
width: hypot(30px, 40px);

Она может использоваться, например, при вычислениях расстояния или диагонали
Иногда применяется в сложных layout или анимациях:
transform: translate(
hypot(10px, 20px)
);

➕➖5. sign() — работа со знаком числа
Функция возвращает:
-1
0
1
в зависимости от значения
Пример:
opacity: calc(sign(var(--value)) * 1);

Это позволяет строить простые условные зависимости через CSS-вычисления

🖼6. image-set() — изображения для разных плотностей экранов
Позволяет указывать несколько версий изображения
background-image: image-set(
"image.png" 1x,
"image@2x.png" 2x
);

Браузер сам выберет подходящую версию в зависимости от плотности пикселей экрана
Плюсы:
- Оптимальная загрузка
- Экономия трафика
- Более чёткое отображение на retina-экранах

🔺7. shape() — работа с геометрией
Функция позволяет задавать геометрические формы для clipping
clip-path: shape(
from 0 0,
line to 100% 0,
line to 50% 100%,
close
);

Это упрощает создание нестандартных форм без SVG

📦8. xywh() — понятный синтаксис
Функция задаёт прямоугольник через координаты и размеры
Раньше:
clip-path: inset(10px 20px 30px 40px);

Теперь:
clip-path: xywh(10px 20px 200px 100px);

x, y, width, height
Такой синтаксис легче читать и поддерживать

🧩Итог
Современные функции CSS позволяют решать больше задач прямо на уровне стилей
Многие задачи интерфейса, которые раньше решались с помощью JavaScript, теперь иногда можно реализовать напрямую в CSS.

💬 Как вы относитесь к тому, что часть задач интерфейса постепенно переходит из JavaScript в CSS?
Это упрощает разработку или наоборот усложняет поддержку?


#css #webdev #frontend #webdevelopment #dev #programming
Post #1501 210
🤖 AI убил рынок джунов?
Сейчас много разговоров о том, что AI убил рынок джунов.
Статистика вакансий действительно выглядит пугающе.

Но проблема немного в другом.
Роль junior-разработчика не умерла — просто сломалась карьерная лестница.

Раньше путь выглядел довольно понятно:

junior делал скучную работу — писал тесты, фиксил мелкие баги, делал CRUD, конвертировал схемы.
Для сеньоров это была рутина.
Для джунов — лучший способ понять, как на самом деле работают системы.
Через эту “скучную работу” постепенно появлялось главное:
интуиция — как ломаются системы, где появляются баги, как течёт data flow.
Сегодня эту работу делает AI.
Boilerplate, тесты, схемы, простые функции — всё это модель генерирует быстрее и дешевле.
То есть нижняя ступенька лестницы просто исчезла.

В результате рынок начинает выглядеть как странная “гантеля”:
• На одном конце — супер-сеньоры, которые с AI работают в 10 раз быстрее
• На другом — люди, которые умеют писать промпты
А моста между ними почти нет
И именно это сейчас ломает карьерный pipeline.
Но из этого не следует, что роль junior исчезла.Скорее она трансформировалась.

Новый junior — это не кодер, а аудитор
AI может генерировать код.
Но кто проверяет, что он правильный?
Именно здесь появляется новая роль.
Новый ключевой навык джуна — verification.

То есть способность:
— Читать код, который написал AI
— Находить архитектурные проблемы
— Замечать баги, которые проходят тесты
— Понимать, где AI ошибается
Проблема в том, что проверить можно только то, что понимаешь.
А значит от джунов теперь требуют почти сеньорские навыки анализа —но без нормального пути, как до них дойти.
Раньше обучение происходило через практику.Теперь эту практику всё чаще делает AI.

🔎Исследования это подтверждают
В эксперименте Anthropic джуны с AI выполняли задачи быстрее, но хуже понимали код.
Разница составила –17% по тестам на знание.
Разница не в инструменте.
А в том, как его использовали:
— одни делегировали задачу AI
— другие задавали вопросы и пытались понять решение
Те, кто пытался разобраться, учились.
Те, кто копировал ответ — нет.

🧠Почему это происходит

Во многом мы начинаем понимать идею именно в процессе её формулирования.
Когда этот процесс полностью отдаётся AI, результат появляется — но мышление может так и не сформироваться.
Если junior делегирует решение модели, он получает функцию.
Но пропускает сам процесс рассуждения, который позволил бы заметить, почему это решение может сломаться

⚠️Плохие ответы на Stack Overflow, странные баги, неожиданные проблемы —всё это заставляло сомневаться и проверять решения.
Иногда ответ выглядел правильным, но через пару часов становилось понятно, что он ломает половину системы.
Так появлялось настоящее понимание.
AI убирает этот опыт.
Он всегда отвечает уверенно и выглядит правым.
Поэтому легко просто скопировать решение —
и не разбираться, почему оно работает.

🧑‍💻В 2026 портфолио джуна — это уже не todo-app.

AI делает такой проект за 30 секунд.
Гораздо ценнее показать мышление и суждение:
— “Вот код, который предложил AI — и почему я его отклонил”— “Вот баг, который AI пропустил”
— “Вот как я проверил архитектурное решение”
— “Вот почему чистое решение AI ломается на масштабе”
Сегодня ценится не столько генерация кода, сколько способность разобраться, где он неправильный.
Не генерация. Суждение.

❓В Если компании перестанут нанимать джунов, потому что “AI дешевле” —откуда возьмутся сеньоры через 5–10 лет?
Пока хорошего ответа ни у кого нет.
Но компании, которые смогут заново построить эту лестницу, получат огромный кадровый запас.

А как вы думаете?
#ai #webdev #career #junior #programming #discussion
  • 🔥 5
Post #1500 166
🌐Браузер сегодня — это уже не просто «рендер HTML».
Проблема в том, что мы часто по привычке тянем библиотеки… даже когда нужный инструмент уже встроен:
1) Structured Clone API 🧬
Раньше вопрос «как правильно скопировать объект?» был классикой.
Кто-то вспоминал про ссылки, кто-то — про Object.assign, кто-то — про JSON, а кто-то начинал гуглить 😉
Сегодня всё стало проще:
const copy = structuredClone(original);

Почему это удобно:
- Работает с Map, Set, Date, Blob, File, ArrayBuffer
- Корректно обрабатывает циклические ссылки
- Поддерживается всеми современными браузерами

2) Performance API ⏱️
Мы часто спорим об оптимизациях, но редко честно измеряем результат.
А между тем, браузер уже даёт простой инструмент:
performance.mark("start");
// код
performance.mark("end");
performance.measure("test", "start", "end");
console.log(performance.getEntriesByName("test"));

Это полезно для:
- Микро-бенчмарков
- Сравнения разных реализаций
- Проверки, имеет ли смысл Worker или WASM
Работает стабильно во всех современных браузерах.

3) Page Visibility API 👀
Этот API сообщает, активна ли вкладка.
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
video.pause();
}
});

Реальный мир выглядит так:
Пользователь открыл ваше приложение — и ушёл в другую вкладку на полчаса.
Или вообще забыл вернуться.
Можно:
- Cтавить видео и анимации на паузу
- Останавливать polling
- Снижать нагрузку на CPU
И серверу станет легче жить.

4) ResizeObserver 📐
Наконец-то можно следить за размером элемента, а не только окна.
const ro = new ResizeObserver(entries => {
for (const entry of entries) {
console.log(entry.contentRect.width);
}
});
ro.observe(element);

Если вы делали адаптивные компоненты или графики, вы точно писали костыли под resize.

5) IntersectionObserver 📍
Этот API отвечает на вопрос: попал ли элемент в область видимости?
const io = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
console.log("Элемент виден");
}
});
});
io.observe(element);

Идеально подходит для:
- Lazy loading
- Infinite scroll
- Анимаций при скролле
Любой, кто реализовывал infinite scroll вручную, знает, насколько это «весёлое» занятие 😄

6) AbortController 🛑
Чаще всего его используют с fetch, но на самом деле он подходит для любых отменяемых операций.
const controller = new AbortController();
fetch(url, { signal: controller.signal });
// позже
controller.abort();

Плюсы:
- Можно отменять несколько операций
- Подходит для fetch, стримов, обработчиков событий и т.д.

7) Idle Detection API 🧍
Если Page Visibility говорит о вкладке, то Idle Detection говорит активен ли пользователь за устройством
const detector = new IdleDetector();
await detector.start();
detector.addEventListener("change", () => {
console.log(detector.userState);
});

Пользователь может держать вкладку открытой, но в реальности — уйти на созвон.
Примеры применения:
- Авто-выход из аккаунта
- Статус «away»
- Оптимизация фоновых задач
Поддержка в основном в Chromium и требует разрешения.

8) BroadcastChannel API 📡
Простой способ общаться между вкладками одного сайта.
const channel = new BroadcastChannel("app");
channel.postMessage("logout");
channel.onmessage = e => {
console.log(e.data);
};

Подходит для:
- Cинхронизации логаута
- Cостояния авторизации
Особенно актуально, когда у пользователя открыты пара вкладок.

9) Web Locks API🔐
Нужен, чтобы не делать одну и ту же работу в нескольких вкладках.
navigator.locks.request("data-lock", async () => {
await fetchData();
});

Например:
- Только одна вкладка опрашивает сервер
- Меньше лишних запросов

10) File System Access API 📁
Да, браузер умеет работать с файлами напрямую.
const [fileHandle] = await window.showOpenFilePicker();
const file = await fileHandle.getFile();

Это открывает дорогу для:
- Веб-редакторов
- Инструментов импорта/экспорта
Поддержка в основном у Chromium-браузеров.

А какими вы уже пользовались? 😊

#web #frontend #javascript #dev
Post #1499 153
В прошлом посте я писал, что prompt engineering не исправит архитектуру.💻
И сразу получил ожидаемый ответ:
«Окей, но умение писать промпты — не менее важная часть работы с AI‑системами».

И… да.
Как и умение писать SQL, который компенсирует плохо спроектированную базу.
Полезно? Конечно.
Но это не отменяет того, что проблема — глубже.

Для тех, кто хочет разобраться в том, как писать запросы лучше, есть книга Prompt Engineering (Lee Boonstra, 2025). 

Давайте разберёмся, что в книге действительно полезно, а что стоит читать с поправкой на реальность.

Что в книге действительно ценно🧩
Книга честно проговаривает то, что хайп вокруг AI обычно замалчивает:
LLM — не разум. Это предсказательная машина.  
Она угадывает следующий токен. Вежливо. Последовательно. Без понимания.

Всё остальное — «мышление», «рассуждение», «принятие решений» — это надстройки, которые мы сами прикручиваем.

Техники, описанные в книге:
- Chain of Thought
- Step‑back prompting
- Self‑consistency
- ReAct
- JSON‑схемы
- Ограничения вывода

Это не магия. Это механизмы контроля.

Если вы когда‑то:
- Оборачивали нестабильный API ретраями,
- Добавляли идемпотентность,
- Заставляли ответ соответствовать схеме, чтобы он не ломал систему,

то вы уже занимались prompt engineering.
Просто не называли это так.

Ключевая мысль книги⚙️
Prompt engineering — это middleware для вероятностных систем.

Все техники в книге решают одни и те же проблемы:
- Недетерминизм,
- Отсутствие структуры,
- Отсутствие контрактов,
- Непредсказуемые ретраи,
- Побочные эффекты.

Классические проблемы распределённых систем.

Только вместо логов — абзацы.
Вместо stack trace — уверенность.

Когда промпт‑инжиниринг действительно уместен🎯
Он отлично работает, когда:
- Задача по природе размытая (язык, суммирование, классификация),
- Цена ошибки низкая,
- Вывод носит рекомендательный характер,
- Можно безопасно ретраить,
- Никто не притворяется, что система детерминирована.

Но если вы используете его, чтобы:
- Применять бизнес‑правила,
- Принимать финансовые решения,
- Менять состояние продакшена,
- Заменять доменную логику,

Вы сталкиваетесь не с проблемами модели, а с техническим долгом, который наконец стал заметен.

Главный урок книги ( о котором явно не говорят)📘
Лучшие промпты в книге имеют общие черты:
- Чёткие входные форматы,
- Явные схемы,
- Узкие обязанности,
- Детерминированные ожидания,
- Скучные, предсказуемые ответы.

Настоящий урок — не:
Стань гуру промптов
а:
«Твоя система наконец должна иметь границы. И AI больше не позволит тебе их игнорировать».

Вывод🧠
Книгу действительно стоит прочитать — не как набор трюков и не как быстрый способ «прокачать» навыки.
Она полезна, если смотреть на неё глазами инженера, который разбирается с тем, как ведут себя вероятностные системы

Prompt engineering не исправит архитектуру.
Он делает важную вещь:
Помогает увидеть те проблемы, которые раньше было легко не замечать.
Возможно, именно поэтому создаётся ощущение, что это такой мощный инструмент.
  • ❤ 2
Post #1498 156
🤖Сегодня большинство цифровых продуктов рано или поздно получают AI-функциональность

AI-агенты, NLP, автоматические решения — постепенно становятся привычной частью продуктовой разработки.

И вместе с этим появился новый рефлекс:
- Ответ получился странным — переписываем промпт
- Модель ошиблась — добавляем больше контекста.
- Поведение непредсказуемо — промпт недостаточно точный.

Со временем складывается впечатление, что качество работы AI зависит от prompt engineering. На практике это не так.

🔍Что происходит на самом деле

AI редко существует сам по себе. Чаще он встроен в продукт:
Получает данные из backend-сервисов, использует API, опирается на модели данных и выполняет действия внутри системы.
Здесь и возникает главная проблема.
Когда AI начинает работать плохо, команда пытается исправить поведение модели, и промпт превращается в инструмент компенсации архитектурных пробелов.

🛠 Почему всё сводится к промптам
Потому что их изменить проще.
Не нужно наводить порядок в данных, вводить единый источник правды или пересобирать бизнес-логику.
Достаточно добавить инструкции вроде «если данные неполные — сделай разумное предположение» или «если значения конфликтуют — выбери логичное», и вроде бы начинает работать лучше.
Но часть архитектурных решений просто переносится внутрь запроса к модели.

⚡️Важно понимать:
AI не знает, как устроен продукт.
Он видит только ту картину мира, которую открыли — кусок данных, ограниченный контекст, описание правил.
Если бизнес-логика не формализована, модель будет угадывать.
Если не определила источник истины, будет интерпретировать.

Когда AI начинает не только отвечать, но и выполнять действия, ситуация становится ещё хуже.
AI-агент — это распределённая система с вероятностным компонентом: запросы выполняются асинхронно, состояние восстанавливается из контекста, операции повторяются, ошибки сложно воспроизводятся.
Если архитектура продукта не рассчитана на такие сценарии, появляются знакомые эффекты: действия выполняются дважды, решения принимаются на неполных данных, система уверенно делает неправильные шаги.
И снова кажется, что проблема в AI, хотя он лишь усиливает системные слабости.

Ограничения, проверки и дополнительные инструкции - помогают снизить количество ошибок.
Но чаще это выглядит так: ещё больше правил в промпте, дополнительные проверки после ответа, просьбы к модели уточнять действия у пользователя.
Снижаются симптомы, но не устраняется причина — отсутствие чёткой структуры взаимодействия между AI и продуктом.

🕵️Непопулярная правда
Со временем становится заметна одна неприятная закономерность: AI не упрощает архитектуру продукта и не компенсирует слабые места.
Наоборот, делает их очевидными.
Когда архитектура изначально спроектирована аккуратно — с понятными границами, согласованными данными и формализованной бизнес-логикой — AI внутри неё ведёт себя спокойно и предсказуемо.

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

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

🚀В итоге
Prompt engineering остаётся важным инструментом работы с AI
Но не исправляет продуктовую архитектуру, AI ускоряет момент, когда архитектурные проблемы становятся заметны и раньше, чем это происходило в системах без AI.

#AI #web #development #NLP #architecture #tech
  • 🔥 2
Post #1497 176
Мы вездесущи😈
В том смысле, что теперь ты можешь смотреть и слушать наш подкаст ITToLк там, где тебе удобно:

✅ в Яндекс Музыке: clck.ru/3RiYL9
✅ в VK Видео: clck.ru/3RidoV
✅ в VK Подкастах: clck.ru/3RiduC

Делись в комментариях, какие еще подкасты про IT и на каких площадках ты слушаешь. Мы доберемся и туда😉
  • 🔥 2
Post #1496 212
А на досуге можно глянуть новый эпизод нашего подкаста - в нем ребята поговорили с совладельцем EvApps Русланом Ишмухамедовым - предпринимателем, яхтсменом и не выспавшимся отцом - о бизнесе, путешествиях, воспитании детей и будущем аутстаффинга😉

Смотреть в VK Видео
  • 🔥 2
Post #1495 166
🗄 Шаг 2: таблицы в ClickHouse
ClickHouse умеет читать сообщения напрямую из Kafka через Kafka Engine.
Для этого настраивается цепочка из трёх таблиц и одного представления.

📥 Kafka-таблица
Описывает структуру сообщений и Kafka-топик, из которого будут читаться данные.
CREATE TABLE default.kafka_orders
(
    `id` Int32,
    `status` String,
    `price` String,
    `__deleted` Nullable(String)
)
ENGINE = Kafka('broker:9092', 'inventory.orders', 'clickhouse', 'AvroConfluent')
SETTINGS format_avro_schema_registry_url = 'http://schema-registry:8081';


🔁Материализатор данных из Kafka
Kafka-таблица читает сообщения только один раз — смещения коммитаются в consumer group.
Поэтому каждую запись нужно сразу перекладывать в постоянную таблицу.
CREATE MATERIALIZED VIEW default.consumer__orders
TO default.stream_orders
(
    `id` Int32,
    `status` String,
    `price` String,
    `__deleted` Nullable(String)
) AS
SELECT
    id,
    status,
    price,
    __deleted
FROM default.kafka_orders;


🧱 Основная таблица
Хранит все версии строк и пометки об удалении.
Для корректной замены старых записей используется ReplacingMergeTree.
CREATE TABLE default.stream_orders
(
    `id` Int32,
    `status` String,
    `price` String,
    `__deleted` String
)
ENGINE = ReplacingMergeTree
ORDER BY (id, price)
SETTINGS index_granularity = 8192;


👀 Витрина данных
Скрывает удалённые строки и возвращает только актуальное состояние данных.
CREATE VIEW default.orders
(
    `id` Int32,
    `status` String,
    `price` String
) AS
SELECT
    id,
    status,
    price
FROM default.stream_orders
FINAL
WHERE __deleted = 'false';

Важно:

постоянное использование FINAL дорого по ресурсам.
В production лучше:
1. Агрегации, - last value
2. Фоновые merge, - ожидание схлопывания данных
3. Материализованные витрины, - предрасчитанные представления

✅ Заключение
Мы собрали полноценный конвейер синхронизации между MySQL и ClickHouse через CDC.
Ключевые элементы:
1. Debezium, - читает binlog MySQL
2. Kafka, - гарантирует доставку и порядок событий
3. Kafka Engine, - потоковая загрузка в ClickHouse
4. ReplacingMergeTree, - устранение дубликатов
5. Поле __deleted, - корректная обработка удалений

В результате получается аналитическая копия боевой OLTP-базы:
MySQL продолжает обслуживать транзакции,ClickHouse — тяжёлую аналитику и отчёты,
оба без взаимных блокировок и деградации производительности.

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

Ещё услышимся 👋
#ClickHouse #MySQL #CDC #Debezium #Kafka #DataSync #OLTP #OLAP #DataEngineering #АналитикаДанных
Post #1494 131
⚙️ Реализация: шаг 1 — настраиваем CDC через Debezium

Почти любая современная СУБД ведёт журнал изменений, куда сначала записываются все операции, а уже потом они применяются к данным.
Это механизм Write Ahead Log 📝
В MySQL таким журналом является binlog.
Если читать этот журнал, интерпретировать изменения и передавать их в другую систему, мы фактически реализуем подход Change Data Capture (CDC) 🔄

Почему CDC удобен для синхронизации:
- Работает потоково, почти без задержек
- Обеспечивает конечную согласованность
- Не требует тяжёлых батч-процедур
- Сохраняет порядок изменений
- Масштабируется под высокую нагрузку

Для работы с binlog чаще всего используют Debezium. Он подключается к MySQL, отслеживает изменения и публикует их в Kafka через Kafka Connect.
Дальше эти события может забирать ClickHouse 🧩

Ниже — только те настройки Debezium, которые важны именно для корректной работы с ClickHouse.

1. Оставляем только актуальное состояние записи
По умолчанию Debezium отправляет событие в таком виде:
- Cостояние до изменения
- Cостояние после изменения
Плюс при удалении формируется «пустое» сообщение

Для Kafka это нормально, а вот для ClickHouse — неудобно: таблицы Kafka Engine ожидают плоскую структуру.

Поэтому включаем трансформацию:
"transforms": "unwrap",
"transforms.unwrap.type": "io.debezium.transforms.ExtractNewRecordState"


Что это даёт:
- Для INSERT и UPDATE остаётся только новое состояние строки
- Поле before отбрасывается
- Структура сообщения упрощается

2. Корректно обрабатываем удаления

После упрощения структуры Debezium перестаёт передавать удаления. Чтобы это исправить, добавляем:
"transforms.unwrap.delete.handling.mode": "rewrite"


Теперь:
- Удалённые записи не пропадают
- В сообщении появляется поле __deleted = true
- Остальные операции получают __deleted = false

Это позволяет:
- Хранить историю изменений
- Фильтровать удалённые строки уже на стороне ClickHouse (через view)

3. Проблема обновлений неключевых полей
В нашем примере:
- В MySQL первичный ключ — id
- В ClickHouse таблица отсортирована по (id, status)
Если обновляется поле status, ClickHouse видит новую комбинацию ключей и создаёт ещё одну строку → появляются дубликаты 😬

Чтобы этого избежать, Debezium нужно явно сказать, какие поля считать идентификатором записи при обновлениях.

Настройка:
"message.key.columns": "inventory.orders:id;inventory.orders:status"


Теперь при изменении этих полей:

- Сначала генерируется событие удаления старой версии
- Затем событие вставки новой
- ClickHouse корректно «заменяет» строку через ReplacingMergeTree

Итоговая конфигурация Debezium
{
  "name": "mysql-connector",
  "config": {
    "connector.class": "io.debezium.connector.mysql.MySqlConnector",
    "database.hostname": "mysql",
    "database.port": "3306",
    "database.user": "root",
    "database.password": "mypassword",
    "database.server.id": "2",
    "database.server.name": "dbz.inventory.v2",

    "database.include.list": "inventory",
    "table.include.list": "inventory.orders",

    "message.key.columns": "inventory.orders:id;inventory.orders:status",

    "schema.history.internal.kafka.bootstrap.servers": "broker:9092",
    "schema.history.internal.kafka.topic": "dbz.inventory.history.v2",

    "snapshot.mode": "schema_only",
    "topic.prefix": "dbz.inventory.v2",

    "transforms": "unwrap",
    "transforms.unwrap.type": "io.debezium.transforms.ExtractNewRecordState",
    "transforms.unwrap.delete.handling.mode": "rewrite"
  }
}

⚠️ Важный момент: как выбирать message.key.columns

После задания message.key.columns:
- Эти поля используются как ключ сообщения в Kafka
- По ним распределяются данные по партициям
- Нарушенный порядок событий = риск рассинхронизации в ClickHouse

Практическое правило:
1️⃣ Определите ключ сортировки таблицы в ClickHouse
2️⃣ Поймите, из каких колонок источника он формируется
3️⃣ Объедините все эти колонки
4️⃣ Укажите их в message.key.columns
5️⃣ Убедитесь, что они входят в ORDER BY в ClickHouse

#ClickHouse #MySQL #CDC #Debezium #Kafka #DataSync #OLTP #OLAP #DataEngineering #АналитикаДанных
Post #1493 152
Предположим, у вас уже есть транзакционная база данных, обслуживающая основную работу приложения (OLTP-нагрузка).
Со временем появляется новая задача — строить сложные отчёты, считать метрики, анализировать поведение клиентов.
Для этого вы поднимаете отдельное аналитическое хранилище, например ClickHouse.

Дальше возникает логичный вопрос: как организовать стабильную и быструю передачу данных между этими системами?
И какие подводные камни ждут при попытке связать транзакционную и аналитическую базы?

Подобные сценарии встречаются практически в каждом продукте, где есть серьёзная работа с данными.
Сегодня для этого часто применяют подход Change Data Capture (CDC) и стриминговые платформы вроде Apache Kafka.
Они позволяют передавать изменения не батчами, а потоком, почти в реальном времени.
Но когда источник — классическая OLTP-БД (например, MySQL), а приёмник — колоночное OLAP-хранилище (ClickHouse), появляются особенности, которые нельзя игнорировать: разные модели данных, подходы к обновлениям, удалению строк и консистентности.
В этом завершающей линейке постов разберём один из практических вариантов реализации репликации изменений из MySQL в ClickHouse.

Несмотря на конкретный стек, описанный подход легко адаптируется под другие комбинации технологий.

Общая схема решения

Используется событийная архитектура:
1.MySQL фиксирует изменения в бинарном логе
2.Debezium превращает их в поток событий
3.Apache Kafka выступает транспортным слоем
4.ClickHouse читает события и сохраняет данные у себя
5. Аналитическая база постепенно синхронизируется с источником

MySQL → Debezium → Kafka → ClickHouse

Пусть в MySQL хранится таблица заказов интернет-магазина:
CREATE TABLE `orders` (
  `order_id` int NOT NULL AUTO_INCREMENT,
  `customer_id` int NOT NULL,
  `status` enum('new','paid','shipped','cancelled','completed') NOT NULL,
  `total_amount` decimal(10,2) NOT NULL,
  `currency` char(3) NOT NULL,
  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` datetime DEFAULT NULL,
  `delivery_city` varchar(100) DEFAULT NULL,
  `payment_method` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`order_id`),
  KEY `idx_customer` (`customer_id`),
  KEY `idx_created_at` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Что происходит с данными:
📦 постоянно создаются новые заказы (INSERT)
🔄 меняется статус и сумма (UPDATE)
❌ часть заказов отменяется и удаляется (DELETE)
⚡️ высокая частота операций в рабочее время

Наша цель:
Нужно без потерь передавать все изменения по заказам в ClickHouse и использовать их для:
- аналитики продаж
- расчёта конверсий
- финансовых отчётов
- мониторинга бизнес-метрик

Для этого:
применяется Debezium 2.1 как CDC-инструмент

В следующем посте будем глубже погружаться в нюансы реализации.
Следите за обновлениями! 🚀

#ClickHouse #MySQL #CDC #Debezium #Kafka #DataSync #OLTP #OLAP #DataEngineering #АналитикаДанных
Post #1491 141
Всем привет! 👋
Прежде чем продолжить сравнивать базы - вернемся к бенчмаркам разработчиков Clickhouse

Эти тесты охватывают наиболее распространённые сценарии работы с данными:
- Анализ кликов и веб-трафика
- Веб-аналитика и пользовательское поведение
- Обработка машинно-генерируемых данных
- Работа со структурированными логами и событиями
- Ad-hoc аналитика и дашборды реального времени

Методология сравнения🧪
Для объективного сопоставления ClickHouse и MySQL мы взяли 10 миллионов строк из этого датасета и отобрали запросы, которые наиболее наглядно демонстрируют разницу между двумя подходами.
Хотя бенчмарк и ограничен этими двумя базами данных, вы можете обобщить концепцию на другие строчные и колоночные СУБД.

Процесс тестирования⚙️
- Создаётся база данных
- Создаётся таблица с определённым DDL
- Данные (hits.tsv) загружаются в таблицу, и измеряется время загрузки
- Выполняются запросы, и измеряется время выполнения каждого запроса

Тестовые запросы:📋
1. SELECT COUNT(*) FROM analytics_events; [OLAP]
2. SELECT SUM(event_value), COUNT(*), AVG(processing_time) FROM analytics_events; [OLAP]
3. Сложный GROUP BY с фильтрами по дате и категории события [OLAP]
4. Агрегация по 2 полям с сортировкой по частоте [OLAP]
5. Точечный SELECT по ID пользователя [OLTP]
6. SELECT нескольких полей по ID транзакции [OLTP]
7. UPDATE одной строки в таблице пользователей [OLTP]

1. Загрузка данных⬇️
• ClickHouse: 65 секунд
• MySQL: 11 минут 35 секунд
• Соотношение: MySQL медленнее в 10.7 раза

ClickHouse загружает данные быстрее благодаря LSM-деревьям и разреженным индексам, но работает эффективнее с пакетными вставками, а не с отдельными строками — он создаёт неизменяемые блоки данных и не оптимизирован для частых точечных изменений..

2. Размер таблицы на диске💾
ClickHouse: 1.3 GiB
MySQL: 6.32 GiB
Соотношение: MySQL занимает в 4.86 раза больше места
Колоночная структура обеспечивает возможность сжатия данных, что недоступно в строчных базах данных.

3. Выполнение запросов на чтение📖
Запрос 1: SELECT COUNT(*) FROM analytics_events;
ClickHouse: 0.005 сек
MySQL: 7.79 сек
Соотношение: ×1558 - ClickHouse быстрее

Запрос 2: SELECT SUM(event_value), COUNT(*), AVG(processing_time) FROM analytics_events;
ClickHouse: 0.030 сек
MySQL: 16.0 сек
Соотношение: ×533 - ClickHouse быстрее

Запрос 3: Сложный GROUP BY с фильтрами по дате
ClickHouse: 0.193 сек
MySQL: 4.35 сек
Соотношение: ×22.5 - ClickHouse быстрее

Запрос 4: Агрегация по двум полям с сортировкой
ClickHouse: 2.600 сек
MySQL: 180.93 сек (≈3 минуты)
Соотношение: ×69.6 - ClickHouse быстрее

Запрос 5: Точечный SELECT по конкретным ID
ClickHouse: 0.01 сек
MySQL: <0.001 сек
Для точечных запросов MySQL показывает лучшее время

Запрос 6: SELECT нескольких полей по конкретным ID
ClickHouse: 0.011 сек
MySQL: <0.001 сек
Для точечных запросов MySQL показывает лучшее время

Разреженные индексы и колоночная структура ClickHouse превзошли MySQL во всех OLAP-запросах (номера 1-4). Вот почему BI-аналитики и аналитики данных были бы более чем довольны ClickHouse для своих ежедневных отчетов.
Однако MySQL выигрывает битву, когда речь идет об OLTP-запросах (номера 5 и 6). B-деревья лучше для точечных запросов, где требуются короткие транзакции с небольшим количеством строк.

4. Выполнение обновлений🔄
Для запроса на обновление в ClickHouse нужно выполнить другой запрос, он не поддерживает обновления в традиционном смысле, будем использовать ALTER:
Кроме того, ClickHouse применяет обновление асинхронно. Чтобы получить результат немедленно, нужно выполнить команду оптимизации
ALTER TABLE user_sessions UPDATE is_active = 0 WHERE user_id = 12345 AND session_date = '2024-01-15' AND session_token = 'abc123' AND device_id = 'device001';
OPTIMIZE TABLE user_sessions FINAL;

Запрос: 7
ClickHouse: 26 сек
MySQL: <0.001 сек
ClickHouse снова проигрывает в реальных обновлениях (и аналогично удалениях) по сравнению с MySQL.

Ну и напоследок у нас остается тема - применение CDC из MySQL в ClickHouse
Ждите апдейтов :)👀

#ClickHouse #БазыДанных #Производительность #Сравнение #Бенчмарки #Оптимизация #MySQL
Post #1490 154
ClickHouse vs MySQL: Когда что выбирать? 🤔
Ни одна БД не идеальна для всех задач. Нельзя ожидать, что одна СУБД будет максимально производительна для каждого запроса.
Ключевой навык разработчика — понимать сильные и слабые стороны разных инструментов и уметь выбирать подходящий инструмент для каждой задачи. 🛠

В следующем посте мы сравним ClickHouse как представителя OLAP-баз данных и MySQL как представителя OLTP.
Это поможет принимать лучшие решения при проектировании систем. Перед тем как переходить к сравнению, разберемся с основными понятиями. 📚

OLTP - Online Transaction Processing 💳
Это обработка транзакций в реальном времени.
Используется для повседневных операций: обработка заказов, обновление информации о клиентах, финансовые операции.
Оптимизирован для коротких транзакций с минимальным временем отклика.
Ключевые требования — точность данных и их согласованность.

OLAP - Online Analytical Processing 📊
Это аналитическая обработка данных.
Используется для анализа больших объёмов информации, выявления тенденций и закономерностей.
Оптимизирован для сложных аналитических запросов, которые обрабатывают миллионы строк.
Позволяет получать insights, недоступные при использовании традиционных отчётных инструментов.

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

ClickHouse 🚀
Это колоночная OLAP-СУБД с открытым исходным кодом, разработанная в Yandex.
Создана для обеспечения высокой производительности аналитических запросов.
Использует SQL-подобный язык запросов и поддерживает различные типы данных, включая целые числа, строки, даты и числа с плавающей точкой.
Предлагает такие функции как кластеризация, распределённая обработка запросов и отказоустойчивость.
Также поддерживает репликацию и шардирование данных.

В следующем посте проведём детальное сравнение производительности ClickHouse и MySQL на примерах и покажем, в каких сценариях эти базы данных показывают себя наилучшим образом. 🎯
Post #1489 197
Друзья, с наступающим 2026 годом! 🚀

Еще один год кода, багов, бессонных деплоев и триумфальных "все работает!" позади. Год, в котором требования менялись быстрее, чем кеш, а в продакшене всегда находился тот самый крайний случай. Но мы выдержали нагрузку, отрефакторили хаос и выкатили фичи - потому что наше дело именно такое.

Так пусть же в новом году:

🔥 Мержи проходят без конфликтов, а код-ревью длится не дольше чашки кофе.

🔥 Продакшен-баги обходят ваши сервисы десятой дорогой, а если и появляются — то в понедельник утром.

🔥 Тесты покрывают все, что нужно, и никогда не падают из-за кривых моков.

🔥 Документация существует (!), будет актуальной и в ней можно будет найти ответы.

🔥 Технический долг будет-таки закрыт, а не станет легаси, о котором все боятся вспоминать.

Желаем вам в 2026-м легких задач, быстрых компиляций, стабильных зависимостей и того чувства, когда после долгой работы код компилируется и запускается с первого раза. Пусть баланс между "багом" и "фичей" всегда будет в вашу пользу!

Отдыхайте, набирайтесь сил и вдохновения. Новый год готовит нам свежие вызовы, крутые технологии и гору интересной работы. И мы с вами точно со всем справимся - потому что лучшая команда разработчиков собрана именно здесь. 💻❤️

С Новым годом! 🎄✨
  • 🔥 2
  • 🎉 1
Post #1488 160
Всем привет!
Сегодня углубимся в детали ключей, партиций и дополнительных индексов — всё, что нужно для максимальной производительности.

Order Key vs Primary Key
Ранее я упоминал: если не указать PRIMARY KEY явно, ClickHouse использует ключи сортировки (ORDER BY) как первичные ключи.
Но вы можете задать PRIMARY KEY отдельно — он должен быть подмножеством ORDER BY.
CREATE TABLE ecommerce_events
(
    `customer_id` UInt32,
    `action_type` String,
    `event_date` Date,
    `product_id` UInt32,
    `session_token` String
)
ENGINE = MergeTree
PRIMARY KEY (event_date, customer_id)  -- Для индекса
ORDER BY (event_date, customer_id, action_type, product_id);  -- Для сортировки

Что здесь происходит:
— event_date и customer_id — используются и для индекса, и для сортировки
— action_type и product_id — только для сортировки (помогают в ORDER BY запросах)
— session_token — вообще не участвует в сортировке
Зачем это нужно? 
Если вы часто используете ORDER BY в запросах, включение этих столбцов в ORDER BY таблицы ускоряет выполнение — ClickHouse не будет тратить время на дополнительную сортировку.

Partition Key — разбиваем данные
Партиционирование в ClickHouse — это логическое разделение данных на части. По умолчанию все данные в одной партиции, но вы можете изменить это:
CREATE TABLE server_logs_partitioned
(
`server_id` UInt16,
`log_message` String,
`log_timestamp` DateTime
)
ENGINE = MergeTree
PARTITION BY toDate(log_timestamp)
ORDER BY (log_timestamp, server_id);

Что даёт партиционирование:
— Быстрое удаление старых данных — можно удалить целую партицию
— Оптимизация запросов — ClickHouse читает только нужные партиции
— Управление данными — перемещение, копирование партиций
Важно: 
Партиционирование — не для ускорения запросов!

Skip Index — когда ORDER BY не помогает:
Что делать, если нужно искать по столбцу, которого нет в ORDER BY? Например, найти все логи с определённым типом ошибки:
-- Добавляем ngram bloom filter для поиска подстрок
ALTER TABLE server_logs
ADD INDEX error_idx log_message
TYPE ngrambf_v1(3, 256, 2, 0) GRANULARITY 2;

-- Применяем индекс к существующим данным
ALTER TABLE server_logs
MATERIALIZE INDEX error_idx;

Типы Skip Index:
— bloom_filter — для точного совпадения строк
— minmax — для диапазонов (даты, числа)
— ngrambf_v1 — для поиска подстрок
— tokenbf_v1 — для поиска отдельных слов

Case Study: оптимизация метрик IoT-устройств

Допустим, у нас есть данные с 50K IoT-устройств. Мы хотим:
1. Быстро искать метрики по времени и device_id
2. Фильтровать по типу сенсора
3. Искать по статусу ошибки
CREATE TABLE iot_metrics
(
`device_id` UInt32,
`sensor_type` String,
`metric_time` DateTime,
`value` Float32,
`error_flag` UInt8
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(metric_time) -- Для архивации старых данных
PRIMARY KEY (metric_time, device_id) -- Основные фильтры
ORDER BY (metric_time, device_id, sensor_type) -- Сортировка + фильтры
SETTINGS index_granularity = 8192;

-- Добавляем skip index для поиска ошибок
ALTER TABLE iot_metrics
ADD INDEX error_sk error_flag TYPE minmax GRANULARITY 1;

Результат:
— По времени+устройству — мгновенно (первичный ключ)
— По типу сенсора — быстро (часть ORDER BY)
— По ошибкам — эффективно (skip index)
— Архивация данных — DROP PARTITION для старых месяцев

Главные правила проектирования:
1.ORDER BY — сначала столбцы для самых частых фильтров
2.PRIMARY KEY — подмножество ORDER BY (обычно первые 2-3 столбца)
3.PARTITION BY — только для управления данными, не для скорости
4.Skip Index — для столбцов вне ORDER BY, но с фильтрами

Что дальше?
В следующем посте мы проведём детальное сравнение производительности ClickHouse и MySQL на практических примерах

#ClickHouse #БазыДанных #Производительность #Индексы #Партиционирование #Оптимизация #MySQL
  • 👍 3
Post #1487 146
Продолжаем серию постов про ClickHouse! Ранее мы разобрали основные движки таблиц.
Сегодня углубимся в сердце производительности ClickHouse — ключи и индексы.

Важное уточнение: всё, о чём поговорим сегодня, работает только для семейства движков MergeTree.

Первичный ключ (Primary Key)
Индексы ClickHouse основаны на разреженном индексировании (Sparse Indexing) — альтернативе B-деревьям, используемым традиционными СУБД.
В B-деревьях индексируется каждая строка, что хорошо подходит для точечных запросов (point queries), характерных для OLTP-задач.
Однако это приводит к низкой скорости вставки больших объемов данных и высокому потреблению памяти и дискового пространства.

Напротив, разреженный индекс разбивает данные на несколько частей, каждая из которых группируется в фиксированные порции — гранулы.
ClickHouse создает индекс для каждой гранулы (группы данных), а не для каждой строки, отсюда и название "разреженный индекс".
При запросе с фильтром по первичным ключам ClickHouse находит соответствующие гранулы и загружает их параллельно в память.
Кроме того, данные хранятся в столбцах в нескольких файлах, что позволяет их сжимать и значительно экономить место на диске.

Для наглядности создадим таблицу пользовательских логов и вставим в нее данные:
CREATE TABLE user_access_logs
(
user_id UInt32,
page_url String,
access_time DateTime,
ip_address String,
session_duration UInt32
)
ENGINE = MergeTree
ORDER BY (user_id, access_time);

INSERT INTO user_access_logs
SELECT
number % 5000 as user_id,
concat('https://site.com/page', toString(rand() % 100)) as page_url,
now() - (rand() % 2592000) as access_time,
concat('192.168.', toString(rand() % 255), '.', toString(rand() % 255)) as ip_address,
rand() % 300 as session_duration
FROM numbers(1000000);

Важно: Если отдельно не указать первичные ключи, ClickHouse использует ключи сортировки (ORDER BY) в качестве первичных ключей. В этой таблице user_id и access_time будут первичными ключами.
При каждой вставке данных они будут сортироваться сначала по user_id, затем по access_time.

Фильтрация по первому первичному ключу
Посмотрим, что происходит при фильтрации по user_id (первый ключ):
EXPLAIN indexes=1
SELECT * FROM user_access_logs WHERE user_id = 100;

Результат анализа индексов: Система определила user_id как первичный ключ и исключила большинство гранул с его помощью!

Фильтрация по второму первичному ключу

Теперь попробуем фильтрацию по access_time (второй ключ):
EXPLAIN indexes=1
SELECT * FROM user_access_logs
WHERE access_time >= '2024-01-15 00:00:00'
AND access_time < '2024-01-16 00:00:00';

Результат анализа индексов: База данных определила access_time как первичный ключ, но не смогла эффективно исключить гранулы. Почему?
Потому что ClickHouse использует бинарный поиск только для первого ключа, а для остальных ключей — общий исключающий поиск, который гораздо менее эффективен.

Решение: правильный порядок ключей
Если мы поменяем порядок ключей в ORDER BY, поместив access_time на первое место (так как временные метки часто используются для диапазонных запросов), то получим лучшие результаты:
CREATE TABLE user_access_logs_optimized
(
`user_id` UInt32,
`page_url` String,
`access_time` DateTime,
`ip_address` String,
`session_duration` UInt32
)
ENGINE = MergeTree
ORDER BY (toStartOfDay(access_time), user_id, access_time);

Теперь при фильтрации по user_id (который стал вторым ключом):
EXPLAIN indexes=1
SELECT * FROM user_access_logs_optimized WHERE user_id = 100;

Результат: ClickHouse всё равно сможет эффективно фильтровать данные, используя комбинированную стратегию поиска.

Ключевое правило - всегда старайтесь упорядочивать первичные ключи от низкой к высокой кардинальности:
— Сначала ключи с малым количеством уникальных значений
— Затем ключи с большим количеством уникальных значений
Это обеспечит максимальную эффективность индексов для различных типов запросов.

#ClickHouse #БазыДанных #Аналитика #Индексы #Производительность #Оптимизация #OLAP
Post #1486 363
А тем временем в новом эпизоде ITToLкового подкаста наши бессменные ведущие вывели на чистую воду человека, который привык оставаться за кадром (но у истоков💪) всех наших PR-активностей😎

Поговорили с CMO EvApps Юлией Резниченко о том, как продвигать IT-компанию, сколько дают "на маркетинг" и причем здесь тульские пряники😉

🚀Что выяснили?
🔗 Смотри скорее по ссылке: https://vkvideo.ru/video-78780379_456239303
VK Видео IT ToLк by EvApps. Маркетинг, сексизм и пряники Расспросили нашего CMO Юлию Резниченко, откуда она берет такие красивые показатели, что работает, а что не работает в маркетинге и PR IT-компаний - и причем здесь, собственно, пряники. "Ссылочки в описании", как и обещали: ✅ Это наш официальный ВК (https…
  • 👍 1
Post #1485 147
Продолжаем разбор движков ClickHouse!
3. CollapsingMergeTree (для контролируемых изменений)
Этот движок позволяет явно управлять обновлениями и удалениями через специальный столбец-признак (sign):
sign = 1 — добавить/актуальная версия строки
sign = -1 — удалить/старая версия строки

Пример таблицы для отслеживания статусов заказов:
CREATE TABLE order_statuses
(
`order_id` Int32,
`status` String,
`updated_at` DateTime,
`sign` Int8
)
ENGINE = CollapsingMergeTree(sign)
ORDER BY (order_id, updated_at);

Как работает изменение статуса:
-- Первоначальный статус
INSERT INTO order_statuses VALUES (5001, 'pending', '2024-01-15 10:00:00', 1);

-- Обновление статуса: удаляем старый, добавляем новый
INSERT INTO order_statuses VALUES
(5001, 'pending', '2024-01-15 10:00:00', -1),
(5001, 'shipped', '2024-01-15 14:00:00', 1);

Важно: 
Как и в ReplacingMergeTree, схлопывание происходит в фоне. Для немедленного результата используйте FINAL.

Особенности:
CollapsingMergeTree позволяет более контролируемо обрабатывать обновления и удаления.
Например, вы можете обновить ключи сортировки, вставив старую строку с sign=-1 и новую строку с новыми ключами сортировки и sign=1.

4. AggregatingMergeTree
Этот движок автоматически вычисляет агрегаты при вставке данных, значительно ускоряя аналитические запросы.
Пример — дневная статистика пользователей:
-- Исходная таблица с активностью
CREATE TABLE user_activity
(
`user_id` Int32,
`action` String,
`duration` UInt32,
`event_date` Date
) ENGINE = MergeTree
ORDER BY (user_id, event_date);

-- Материализованное представление с агрегатами
CREATE MATERIALIZED VIEW user_daily_stats
ENGINE = AggregatingMergeTree()
ORDER BY (user_id, event_date)
AS SELECT
user_id,
event_date,
countState() as action_count,
sumState(duration) as total_duration,
uniqState(action) as unique_actions
FROM user_activity
GROUP BY user_id, event_date;

Как использовать агрегированные данные:
-- Вставка детальных данных
INSERT INTO user_activity VALUES
(1001, 'login', 120, '2024-01-15'),
(1001, 'view', 300, '2024-01-15'),
(1001, 'purchase', 60, '2024-01-15');

-- Получение агрегированных результатов
SELECT
user_id,
event_date,
countMerge(action_count) as actions,
sumMerge(total_duration) as total_time,
uniqMerge(unique_actions) as different_actions
FROM user_daily_stats
WHERE user_id = 1001
GROUP BY user_id, event_date;

Этот движок помогает сократить время отклика на сложные, фиксированные аналитические запросы, рассчитывая их во время записи.

Семейство Log: минималистичное хранение
Эти движки максимально просты и быстры для записи, но не имеют индексов.
TinyLog — для временных данных:
CREATE TABLE temp_metrics
(
`metric_id` UUID,
`value` Float64,
`collected_at` DateTime
) ENGINE = TinyLog;

Применение: Промежуточные данные ETL, кэши, временные логи.

Семейство Integration: работа с внешними системами
MySQL Engine — доступ:
CREATE TABLE remote_products
(
`id` Int32,
`title` String,
`category` String
)
ENGINE = MySQL('mysql-server:2206', 'shop', 'products', 'admin', 'password');

Теперь можно выполнять запросы к MySQL через ClickHouse:
SELECT * FROM remote_products WHERE category = 'electronics';

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

Ну и будем продолжать тему Clickhouse в следующих постах, дальше интересней :)

#ClickHouse #БазыДанных #Аналитика #MergeTree #CollapsingMergeTree #AggregatingMergeTree
Post #1484 157
В этой части мы поговорим о движке таблиц ClickHouse.
Как и в любой другой базе данных, ClickHouse использует движки для определения методов хранения, репликации и работы с параллельными запросами для таблиц.
У каждого движка есть свои плюсы и минусы, и выбирать их следует исходя из ваших задач.
Более того, движки сгруппированы в семейства, объединённые общими ключевыми характеристиками.

Итак, начнём с первого и самого популярного семейства:
Семейство MergeTree
Это основной и наиболее мощный движок ClickHouse. Если вы создаете таблицу и не знаете, что выбрать — начинайте с MergeTree или его модификаций.
Основная идея — оптимизация для интенсивной записи данных (INSERT).
Под капотом используется структура LSM-дерево (Log-Structured Merge-Tree).
В отличие от B-деревьев в классических базах данных (MySQL, PostgreSQL), LSM сначала буферизирует записи в памяти, а затем крупными, отсортированными "пакетами" записывает на диск.
Это дает огромный выигрыш в скорости вставки и уменьшает фрагментацию.
Теперь рассмотрим ключевых представителей семейства.

1. MergeTree (базовый)Пример создания таблицы:
CREATE TABLE users
(
`user_id` Int32,
`name` String,
`age` Int32,
`city` String
)
ENGINE = MergeTree
PRIMARY KEY (user_id, city)
ORDER BY (user_id, city, name)

Как работает?
Данные разбиваются на части (parts) и сортируются по ORDER BY.

Каждая часть делится на гранулы (блоки данных).

Для гранул создаются засечки (marks) — "отметки" по первичному ключу. Это разреженный индекс.

При запросе с условием по первичному ключу ClickHouse быстро находит нужные гранулы через бинарный поиск по засечкам и загружает только их.Правило: Первичный ключ (PRIMARY KEY) должен быть префиксом или совпадать с ключом сортировки (ORDER BY). Если PRIMARY KEY не указан, вместо него используется ORDER BY.

2. ReplacingMergeTree (для дедупликации)
DDL
В этом движке строки с одинаковыми ключами сортировки заменяются последней вставленной строкой.
Рассмотрим пример:
CREATE TABLE user_sessions
(
`user_id` Int32,
`session_id` String,
`status` String,
`last_activity` DateTime
)
ENGINE = ReplacingMergeTree
ORDER BY (user_id, session_id);

Предположим, вы вставляете строку в эту таблицу:
INSERT INTO user_sessions VALUES (101, 's1', 'active', '2024-01-15 10:00:00');

Теперь вставим другую строку с теми же ключами сортировки:
INSERT INTO user_sessions VALUES (101, 's1', 'inactive', '2024-01-15 11:30:00');

Теперь последняя строка заменит предыдущую. Обратите внимание: если вы выполните выборку, то можете увидеть обе строки:
Это происходит потому, что ClickHouse выполняет процесс замены во время слияния частей (merge), которое происходит в фоновом режиме асинхронно, а не мгновенно. Чтобы сразу увидеть финальный результат, вы можете использовать модификатор FINAL:
SELECT * from user_sessions FINAL WHERE user_id=101;

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

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

В следующей части мы продолжим разбор семейства MergeTree и рассмотрим:
CollapsingMergeTree — для контролируемых обновлений и удалений
AggregatingMergeTree — для предварительной агрегации данных

А также затронем более легковесные семейства Log и Integration для работы с внешними системами.

#ClickHouse #БазыДанных #Аналитика #MergeTree #Оптимизация
Post #1483 170
А есть ли здесь представители компаний-аутстафферов, которые одним глазком наблюдают за нашей бурной деятельностью?😎

На всякий случай делимся новостью - мы запустили конкурс на предоставление услуг IT-разработчиков в аутстафф.

У нас большой штат собственных аналитиков и разработчиков, но иногда клиентам нужна команда еще больше - или требуется специфичный стек. Тогда мы идем к нашим любимым партнерам, список которых все время пополняется надежными поставщиками IT-услуг💪

⚡️ Так что, если здесь среди разрабов затесался поставщик IT-ресурсов, который давно хотел поработать с нами - есть отличная возможность принять участие в открытом отборе партнеров на 2026 год.

📄 Подача заявок, а также подробные условия тендера и требования - на нашей площадке: https://business.roseltorg.ru/lk/orders/all/94251

⏰ Принимаем заявки до 18:00 19 декабря - успевайте, возможно, мы ищем именно вас😉
  • 🔥 1
Post #1482 127
#️⃣ClickHouse (Часть 1)
🔥 Запускаем серию постов про ClickHouse — одну из самых быстрых колонночных баз для аналитики.
Её придумали в Яндексе, а сейчас используют Facebook, Uber и многие другие компании, когда нужно крутить миллиарды строк за секунды.

Что такое ClickHouse:
Это open‑source СУБД, заточенная под OLAP.
Работает с SQL‑подобным языком, умеет шардинг, репликацию, распределённые запросы и масштабирование без единой точки отказа.

Чем она крута:
🗂 Колонночное хранение
В отличие от классических СУБД, где данные лежат построчно, ClickHouse хранит их по столбцам.
Это даёт сразу несколько преимуществ:
📑Сжатие: каждый столбец хранится в отдельном файле и может быть отсортирован. Благодаря этому алгоритмы компрессии (zstd, LZ4) работают эффективнее, и таблицы занимают в десятки раз меньше места.
⚡️Быстрые аналитические запросы: для range‑запросов система обращается только к нужным столбцам, а не ко всей таблице. Если столбцы отсортированы (sort keys), поиск и агрегации выполняются значительно быстрее.
🖥 Параллельная обработка: при работе с большими объёмами данных ClickHouse умеет распараллеливать операции на многоядерных процессорах, ускоряя загрузку и вычисления.

📈 Масштабируемость
ClickHouse отлично масштабируется как «вверх», так и «вширь»:
🔀 Горизонтально — добавляем новые шарды и реплики, распределяем нагрузку между узлами.
🏢 Между дата‑центрами — поддерживается асинхронная multi‑master репликация, все узлы равноправны, нет единой точки отказа.
💡 Вертикально — можно увеличивать ресурсы отдельного сервера (CPU, RAM, диски), и ClickHouse будет использовать их максимально эффективно.

Но есть нюансы:
- Нет полноценного UPDATE/DELETE ClickHouse не рассчитан на частые модификации данных.
Такие операции выполняются медленно и неэффективно, поэтому для сценариев с постоянными изменениями таблиц он не лучший выбор.
- OLTP‑запросы не его сильная сторона
Если нужны точечные запросы (например, быстро достать одну строку по ключу), классические реляционные базы вроде MySQL или PostgreSQL справятся заметно лучше. 

Аналоги:
- Druid
- ElasticSearch
- SingleStore
- Snowflake
- TimescaleDB
У каждого свои плюсы, но ClickHouse — топ именно для аналитики.

Установка
В этом гайде я показываю только вариант через Docker — самый быстрый способ «поднять» ClickHouse без лишних танцев с бубном.
За другими способами и нюансами - как и всегда, лучше обратиться к официальной доке

Загрузка образа
docker pull clickhouse/clickhouse-server


Запуск:
docker run -d --name some-clickhouse-server --ulimit nofile=262144:262144 clickhouse/clickhouse-server


Подключение к нему из нативного клиента

docker run -it --rm --network=container:some-clickhouse-server --entrypoint clickhouse-client clickhouse/clickhouse-server
# ИЛИ \{#or}
docker exec -it some-clickhouse-server clickhouse-client


Первые шаги
CREATE DATABASE ecommerce;

CREATE TABLE ecommerce.users
(
UserID UUID,
Username String,
Email String,
RegistrationDate Date,
LastLogin DateTime64(3, 'UTC'),
Age UInt8,
Salary Decimal(10, 2),
IsPremium Bool,
Settings JSON,
Tags Array(String),
Metadata Map(String, String)
)
ENGINE = MergeTree
PRIMARY KEY (UserID, RegistrationDate)
ORDER BY (UserID, RegistrationDate, Username)
PARTITION BY toYYYYMM(RegistrationDate);


➕ Вставка данных
INSERT INTO ecommerce.users VALUES
(
generateUUIDv4(), -- Автоматическая генерация UUID
'anna_sidorova',
'anna.s@company.com',
'2024-02-20',
'2024-03-19 09:15:30.500',
32,
62000.00,
false,
'{"theme": "light", "email_notifications": false}',
['new_user'],
map('city', 'Saint Petersburg', 'department', 'Sales')
);


🔍 Чтение данных
SELECT * FROM ecommerce.users;


📌 Мы разобрали основы ClickHouse и базовый сетап.
Впереди — движки таблиц, ключи, индексы и сравнение с MySQL.
Следите за апдейтами!

#clickhouse #database #tutorial
Older posts →
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →