EvApps – Telegram
EvApps
203 subscribers
1.23K photos
50 videos
1 file
238 links
IT-aутстафферы из Тулы💚
https://evapps.ru/

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

▶️ Наш чат для системных аналитиков: https://news.1rj.ru/str/pro_sa_evapps

▶️ Посмотреть, как мы живём: https://vk.com/evapps
Download Telegram
Forwarded from Двач
This media is not supported in your browser
VIEW IN TELEGRAM
ChatGPT, когда в очередной раз выдал говёный код с ошибкой:
Всем привет🖐
Недавно, у нас возникла идея рассмотреть lazy loading не только как инструмент оптимизации, но и как возможный способ защиты чувствительного кода.

Lazy-loading — это способ загружать модули, компоненты или шаблоны только тогда, когда они действительно нужны, а не при первом запуске приложения.
В React и других фреймворках это реализуется через:
- разделение модулей,
- конфигурацию роутов,
- отложенную загрузку компонентов.

Преимущества стандартны:
⚡️ быстреее начальная загрузка
📉 меньше трафика
🚀 выше производительность

В React это реализуется через React.lazy() и Suspense:
const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
return (
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
);
}

Такое разбиение кода уменьшает размер бандла и повышает скорость загрузки.

Во время разбирательств, мы заметили странную вещь — почти ни один фреймворк не упоминает lazy-loading в контексте безопасности. Только как оптимизацию.

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

Lazy-loading позволяет решить это:
мы можем не загружать части приложения с чувствительной логикой до тех пор, пока пользователь не авторизован.

Вот пример с использованием OAuth2-библиотеки для клиентской аутентификации:
<AuthenticatedTemplate>
<p>Вход выполнен как: {user?.username}</p>
</AuthenticatedTemplate>

Вместо простого текста здесь может быть целый <ComplexApp /> — с десятками килобайт JS, потенциально раскрывающих уязвимости.

Решение: оборачивать такие блоки в <Suspense> и загружать их только после подтверждённой аутентификации.

Классическое SPA грузит всё и сразу, включая закрытый функционал.
Lazy-loading — загружает только то, что нужно конкретному пользователю в данный момент.

Это не «панацея» для безопасности, но важный слой защиты:
Чем меньше кода попадает в браузер, тем меньше поверхность атаки.

- Должны ли мы использовать lazy-loading не только ради скорости, но и ради защиты кода?
- Да. Определённо должны.

🧠 А вы что думаете? Встречали такой способ использования lazy-loading?

#webdev #security #architecture #frontend #development
3🔥3
Представьте: утро, обычный рабочий день.
Открываешь приложение — и что-то не так.
База не поднимается. Файлы повреждены. Или какой-то баг просто всё удалил.

В голове сразу мысль: «Ладно, не страшно — есть же бэкапы».
Но при попытке восстановить — тишина.
Архивы пустые, повреждённые или восстанавливаются бесконечно.

Горькая правда:
бэкап ничего не стоит, пока ты не попробовал его восстановить.

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

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

К счастью, это были тестовые данные. Но если бы прод?
Пользовательские данные, проект, репутация — всё под угрозой.

Часто живёшь в иллюзии, что раз бэкап есть — значит, всё под контролем.
На деле это копия, которая может не сработать в тот момент, когда она нужна.

Что важно бэкапить:
🧠 Код — GitHub, GitLab
🗄 Базу — Postgres, MySQL, Supabase, SQLite
🖼 Файлы пользователей — изображения, документы
🔐 Конфиги и секреты — .env, API-ключи, настройки деплоя

Главная ошибка:
ты не знаешь, работает ли бэкап, пока не попробуешь восстановление.
Без этого — он как кот Шрёдингера, запертый в коробке.

Как делают большие компании:
— GitLab ежедневно тестирует восстановление и следит за метриками
— Basecamp проводит «тесты катастроф» — симулирует исчезновение датацентра

Типовая схема:
• Делать бэкапы автоматически (ночные дампы, снапшоты)
• Восстанавливать в тестовую среду
• Проверять: база поднимается, файлы читаются, логин работает (плюс smoke-тесты)
• Отправлять алерты при сбоях
• Периодически симулировать сбой и измерять скорость восстановления

Что и как проверять:
💻 Код: Push в GitHub/GitLab (с 2FA) → клонировать в новую папку и собрать проект
🗄 База данных: pg_dump / .backup / PITR → восстановить в тестовую БД и проверить данные
🖼 Файлы пользователей: S3 / B2 / GCS → случайно скачать несколько файлов и открыть
🔐 Конфиги и секреты: 1Password / Bitwarden / VeraCrypt → запустить проект, используя только эти конфиги

💡 Для SQLite:
копирование живой .db может её повредить.
Используй:
sqlite3 mydb.db "VACUUM INTO backup.db"

Так создаётся чистая копия без ошибок.

Когда тестировать восстановление:
— Раз в месяц: локально проверить случайный бэкап
— Раз в квартал: полный тест — база + файлы + приложение
— Раз в год: симулировать потерю устройства и попробовать вернуть

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

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

Потому что бэкапы — это не паранойя.
Это способ быть уверенным, что завтра твой проект не исчезнет. ❤️

#backup #development #risk #code
7
🚀 Почему dev-серверы часто запускаются на localhost:3000 (и сейчас ещё на 5173)

Когда ты запускаешь сервер localhost:3000 или localhost:5173, это не случайность.
За этими портами — история веб-разработки: от Ruby on Rails и Java до Node.js и Vite.

💡 Что такое порт?
Порт — это номер, по которому на компьютере слушает сервис.

Представь, что твой компьютер — это здание, а порты — двери с номерами.
Когда ты заходишь на localhost:3000, ты буквально открываешь дверь № 3000, чтобы увидеть своё приложение.

Всего таких дверей 65 535:
🔹 0–1023 — системные (HTTP 80, HTTPS 443, SSH 22)
🔹 1024–49151 — пользовательские (3000, 8000, 8080 и др.)
🔹 49152–65535 — временные (используются ОС)

Так что 3000 — просто одно из множества возможных значений.

⚙️ Порт 3000 — путь от Rails к Node.js
В Ruby on Rails по умолчанию сервер разработки запускался на 3000.

Позже, с ростом Node.js и Express, в официальных примерах часто встречалась строка:
app.listen(3000, () => console.log('Server running on port 3000'));

Эта привычка закрепилась: её копировали в туториалах, курсах и шаблонах.
React, Next.js и другие фреймворки просто продолжили эту традицию.

Сегодня 3000 — неофициальный “Hello World” порт веб-разработки.

🐍 Порт 8000 — классика Python
Ещё до Node, Python-разработчики поднимали локальные HTTP-сервера командой:
python3 -m http.server

По умолчанию сервер слушал 8000 — безопасный номер, не требующий прав администратора.

Фреймворк Django тоже выбрал этот порт.
Со временем 8000 стал синонимом “запустил и проверил локально”.

☕️ Порт 8080 — наследие Java
В 90-х порты ниже 1024 требовали root-прав.
Java-разработчики из Apache Tomcat и Jetty придумали обход:
80 → 8080

Выглядит похоже, но работает без повышенных прав.
С тех пор 8080 стал стандартом для “серьёзных” серверов вроде Spring Boot.

⚡️ Порт 5173 — современный штрих от Vite
Когда появился Vite, авторы выбрали порт с пасхалкой:
51 = VI
73 = TE
👉 5173 = VITE 😎

Теперь каждый npm run dev открывает localhost:5173 — фирменный почерк нового поколения фронтенда.

🧠 Можно ли использовать другие порты?
Конечно!

Многие привыкают к 3000 и паникуют, увидев:
Error: Port 3000 already in use

Но свободных портов тысячи.
Попробуй:
npm run dev -- --port=42069
vite --port=13337

И никаких конфликтов 😄

🕰 Заключение

8080 — обход ограничений Java
8000 — прагматичный выбор Python
3000 — наследие Rails и Node.js
5173 — фирменный порт Vite

Эти цифры — не просто числа.
Это часть истории веб-разработки с 90-х годов.

Когда в следующий раз запустишь:
👉 http://localhost:3000/
вспомни — за этим числом десятилетия привычек и технологий.

Если порт занят — не убивай процесс.
Просто выбери другое число.
Сделай его своим фирменным портом 😉

Безопасный диапазон: 1024–49151
И когда будешь запускать сервер — делай это с уважением😁

А вы оставляете порт по умолчанию или меняете на свой?

#development #webdev #frontend #backend
👍5🔥1
🚀 Наши друзья из Intelsy проводят вебинар для тех, кто руководит ИТ и хочет навести порядок в хаосе 1С-проектов - а мы такое всецело поддерживаем!

Тема: Будни ИТ-директора: стабильность, быстродействие, борьба с 1С

Дата: 6 ноября в 11:00 по мск

Формат: открытый разговор без маркетинговых лозунгов

О чем поговорим:
– какие вопросы тянет на себе ИТ-директор;
– зачем нужны внешние спецы при собственном штате;
– к кому обращаться за ресурсами и как не ошибиться;
– как работает обращение к внешним ресурсам и что остается за кадром.

📍 Регистрация — по ссылке, без спама и рассылок.

Вебинар
Вебинар
Вебинар

🔥 Залетай, если хочешь меньше хаоса и больше стабильности в своих 1С-проектах.
🚀 Почему спор про Tailwind vs CSS до сих пор живет?

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

Сегодня Tailwind часто выбирают там, где важна скорость.
Командам проще двигаться, когда шрифты, отступы и цвета уже определены.
Разработчикам удобно — пишешь классы, видишь результат сразу.
Дизайн получается единообразным, а адаптивность решается прямо в разметке, без дополнительных медиа запросов.

Но обратная сторона тоже очевидна.
Разметка становится перегруженной.
В инструмент добавляется зависимость от сборки, конфигурации, среды.
Отладка усложняется: чтобы понять, почему элемент выглядит так, приходится разбираться в наборе utility-классов, а не открывать небольшой фрагмент CSS.

Пример привычного Tailwind-фрагмента:
<button
class="px-4 py-2 rounded-md bg-indigo-600 text-white font-medium
transition hover:bg-indigo-700 hover:-translate-y-1">
Button
</button>

Работает. Быстро. Но выглядит не всегда читаемо.

С другой стороны, классический CSS постепенно переживает второе дыхание .
Никаких дополнительных инструментов, чистая структура проекта, читаемость.
Современные возможности языка закрывают задачи, которые раньше требовали Tailwind или препроцессоры: container queries, nesting, cascade layers, :has(), переменные — всё это теперь доступно нативно.

Пример варианта на чистом CSS:
<button class="btn">Button</button>
.btn {
padding: .5rem 1rem;
background: #4f46e5;
color: #fff;
border-radius: .5rem;
font-weight: 500;
transition: .2s;
}
.btn:hover {
background: #4338ca;
transform: translateY(-4px);
}

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

🎯 И в итоге вопрос уже не звучит как «что лучше».
Tailwind — это про темп, единообразие и скорость вывода продукта.
CSS — про прозрачность, контроль и устойчивость без зависимостей.
В реальных командах в 2026-м всё выглядит проще:
часть задач удобно решать utility-классами, часть — обычными стилями.
Tailwind часто используют как систему отступов и цветов, а более сложные компоненты пишут вручную.
Настоящий сдвиг — в том, что разработчики наконец начали изучать сам CSS глубже. Tailwind помог ускорить работу, но современные возможности языка вернули интерес к фундаменту.
Скорость важна, но и понятный код никто не отменял.
Поэтому и Tailwind, и CSS останутся. Не как конкуренты — как инструменты для разных ситуаций.

А на чьей стороне ты, Tailwind или CSS

#frontend #web #development #clear_code #чистый_код
Всем привет!
Стартуем линейку постов про транзакции.

🔍 Зачем нужны транзакции и что такое ACID в MySQL + Laravel

Когда проект растёт, главное — не код, а данные.
Если данные кривые → бизнес страдает, деньги теряются, пользователи бесятся.
И вся эта история держится на одной штуке — транзакции.

🔥 Что такое транзакция
Транзакция — это группа операций, которая должна выполниться как одно целое.
Либо всё выполняется, либо ничего.
Представь последовательность:
- списать деньги
- создать заказ
- уменьшить остатки
Если один шаг упал → откатываем всё назад, как будто ничего не было.
Это и спасает от «битых» данных.

🎯 ACID — 4 принципа надёжных транзакций
A — Atomicity (атомарность)
Все изменения — как один выстрел.
Если внутри что-то упало → откат всей пачки.

Пример: списали деньги, но заказ не создался — Atomicity не даст оставить данные в полурежиме.

C — Consistency (согласованность)
Данные обязаны соблюдать правила БД:
- внешние ключи
- ограничения
- уникальность
- валидность значений

Если до транзакции данные были норм — после тоже должны быть.
Это защита от «сломал базу случайно».

I — Isolation (изолированность)
Параллельные запросы не должны портить друг другу картину мира.
Без Isolation возможны ситуации:
- два человека покупают последний товар одновременно
- два процесса списывают деньги по старому балансу
- кто-то читает недописанные данные

Изоляция не допускает такого.

D — Durability (надёжность)
Если транзакция зафиксировалась — данные не пропадут даже при:
- падении сервера
- сбое питания
- перезапуске MySQL

InnoDB пишет всё в журнал, чтобы восстановить изменения.

🧩 Итог
Транзакции — базовый предохранитель реальных продуктов.
Они не дают платежам списаться дважды, заказам зависнуть в «полусозданном» состоянии, а бронированиям ломать расписание.
Многошаговая операция либо проходит полностью, либо откатывается — и бизнес работает без сюрпризов.

Следите за серией постов — дальше будем развивать эту тему😊

#transactions #db #development #webdev #mysql #laravel
👍2
Media is too big
VIEW IN TELEGRAM
💥 Врываемся в твой понедельник с новым эпизодом подкаста IT ToLк!

На этот раз расспросили нашего CEO Альфреда Столярова о галерах об аутстаффинге - и всех самых страшных мифах про это дело 👀

➡️ Смотри скорее по ссылке
И снова всем привет)

Продолжаем линейку постов про транзакции.

🔧 Транзакции в MySQL: нюансы▶️ MySQL / InnoDB
Если хочешь, чтобы транзакции работали как надо, таблицы должны быть в InnoDB.

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

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

▶️ Уровень изоляции
В MySQL (InnoDB) уровень изоляции по умолчанию — REPEATABLE READ.
Он обеспечивает работу транзакции со стабильным снимком данных: все повторные SELECT внутри одной транзакции возвращают одну и ту же версию строк, даже если параллельные транзакции их уже изменили.

❗️Каждый уровень изоляции определяет, какие аномалии конкурентного доступа возможны:
- Dirty reads — чтение незакоммиченных данных другой транзакции.
- Non-repeatable reads — одна и та же строка возвращает разные значения в рамках одной транзакции.
- Phantom reads — при повторном запросе появляются новые строки, которых не было в первой выборке.

В InnoDB dirty reads исключены автоматически (READ UNCOMMITTED фактически не используется).
REPEATABLE READ блокирует non-repeatable reads, но фантомы сохраняются, что на нагруженных системах может приводить к гонкам в логике проверки и вставки данных.

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

▶️ DDL и транзакции: почему MySQL делает implicit commit

В MySQL операции изменения структуры данных (DDL) автоматически вызывают commit до и после выполнения.
Этот механизм встроен в движок и не зависит ни от приложения, ни от фреймворка.

Какие команды вызывают implicit commit:
- CREATE TABLE
- ALTER TABLE
- DROP TABLE / DROP INDEX
- TRUNCATE
- RENAME TABLE
- CREATE/DROP VIEW
- SET AUTOCOMMIT = 1

Как только выполняется любая из этих команд:
1.MySQL выполняет скрытый commit текущей транзакции.
2.Выполняет DDL.
3.Делает ещё один скрытый commit после DDL.

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

Пример сценария:
1. START TRANSACTION;
2. Вставляем данные.
3. Выполняем ALTER TABLE.
4. MySQL автоматически коммитит всё, что было до команды.
5. Попытка ROLLBACK уже не влияет на вставленные данные.

Это критичное поведение, которое разработчик обязан учитывать.
Laravel или любой ORM не могут перехватить или отменить implicit commit, потому что решение принимает сам MySQL на уровне протокола.

⚠️Последствия использования DDL внутри бизнес-операций:
- невозможность гарантировать атомарность операций
- риск частично записанных данных
- неконтролируемые состояния при сбоях
- нарушение инвариантов бизнес-логики

Поэтому DDL строго отделяют от обычных транзакций:
- применяют только через миграции
- выполняют в отдельные деплой-этапы
- не допускают вызовов в runtime

Это базовое правило эксплуатации MySQL в продакшене.

#transactions #db #development #webdev #mysql #DDL #innoDB
🔥1
Делимся важным из жизни наших системных аналитиков😍
Forwarded from Julia Reznichenko
Внимание, у нас важная новость - мы запустили пилотную программу по системному анализу! 🚀

И это не очередной «курс» на просторах интернета - это безопасное пространство, чтобы попробовать, ошибиться, разобраться и увидеть, как все устроено изнутри.

Мы сделали упор на практику и живую поддержку наставников.

Что будем делать?

🔹 Погрузимся в основы, которые реально работают в проектах.
🔹 Разберем живую систему и создадим настоящую документацию.
🔹 Проведем авторские мастер-классы - на них можно будет потрогать технологии руками и увидеть, как они работают изнутри.

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

Это пилот, и в будущем мы планируем набирать только маленькие группы до 5 человек. Нам важен результат и реальный опыт каждого 💪

Мы долго готовились, чтобы поделиться тем, что проверили годами практики!

Первый поток ведет лично наш руководитель отдела системного анализа Оксана Соболевская ❤️
У Оксаны за плечами многолетний опыт в анализе и целый отдел аналитиков, который она создала с нуля!

Следите за обновлениями: обязательно расскажем, как все проходит, и о следующих наборах тоже сообщим - у нас уже открыт лист ожидания😉

❗️Если ты тоже хочешь в нем оказаться, пиши нам на почту hr@evapps.ru
1
Сегодня завершим нашу линейку постов про транзакции.

🚨 1) Внешние действия внутри транзакции = проблемы
Любой внешний вызов внутри транзакции может выполниться два и более раз, потому что Laravel автоматически повторяет транзакцию при дедлоках и таймаутах.
DB::transaction($callback, $attempts) сам делает retry, если произошёл:
- дедлок
- lock wait timeout
- потеря блокировки

И важно: При повторе он запускает весь callback с нуля.
Он не понимает, что там было «одноразовым», а что идемпотентным.

Что под раздачу попадает:
- отправка писем
- пуши
- внешние HTTP-запросы
- интеграции (CRM, платёжки)

Если транзакция упала и Laravel её перезапустил → действие повторится, потому что MySQL откатывает только свои изменения, а внешние вызовы уже произошли и не откатываются.
Как избежать:
- всё внешнее — только после commit
- использовать afterCommit() у моделей или dispatch(fn)->afterCommit() — так действие выполняется один раз, после успешной фиксации данных.

🔒 2) Дедлоки из-за порядка блокировок
Дедлок (deadlock) — ситуация, когда две и более транзакций ожидают друг друга бесконечно, и MySQL вынужден прервать одну из них, чтобы система продолжила работать.
Чаще всего дедлоки возникают не из-за MySQL как такового, а из-за разного порядка, в котором код блокирует строки или ресурсы.

Пример типичной ситуации:
- Транзакция A начинает и лочит строки 5 → 10
- Транзакция B начинает и лочит строки 10 → 5
MySQL обнаруживает тупик и прерывает одну транзакцию с ошибкой Deadlock found.

Как снизить риск:
1. Всегда блокировать строки в одном порядке
Обычно по возрастанию ID, чтобы исключить циклические ожидания.
$rows = DB::table('accounts')
->whereIn('id', [$id1, $id2])
->orderBy('id')
->lockForUpdate()
->get();

2. Использовать блокировки корректно
- FOR UPDATE — эксклюзивная блокировка для изменения
- sharedLock() — блокировка для чтения без мешающих других читателей
3. Минимизировать время удержания блокировок
Чем быстрее транзакция завершится, тем ниже шанс дедлока.
4. Разделять независимые операции
Операции по разным таблицам или сущностям лучше выполнять в отдельных транзакциях, чтобы не увеличивать зону возможного конфликта.

🌐 3) Когда транзакции не работают вообще
Стандартные транзакции работают только в рамках одной базы данных и одного соединения.
Если операция затрагивает:
- несколько сервисов (например, микросервисы с разной логикой),
- несколько БД (разные инстансы, разные схемы),
- внешние API (CRM, платёжные системы, сторонние интеграции),то гарантировать атомарность стандартной транзакцией невозможно.

Причина: транзакция контролирует только изменения внутри конкретного движка базы данных.
Внешние действия, например HTTP-запросы или вызовы другой БД, не могут быть откатаны автоматически при ошибке — их состояние уже “зафиксировано” в сторонней системе.

🔹4) Стандартный подход — Saga pattern
Saga pattern — это архитектурный паттерн для управления распределёнными транзакциями.
Его идея проста:
1.Каждый шаг — независимый
Каждое действие выполняется как отдельная транзакция, которая гарантированно сохраняет свои изменения в локальной БД.
2. Компенсирующие действия
Для каждого шага создаётся обратная операция, которая может откатить изменения, если последующий шаг не удался.
Например, для биллинга:
Шаг 1: резервируем средства на счёте клиента (локальная транзакция)
Шаг 2: создаём заказ в системе (локальная транзакция)
Шаг 3: уведомляем склад о сборкеЕсли шаг 2 падает, шаг 1 компенсируется: средства возвращаются на счёт клиента.

Преимущества Saga pattern:
- Нет необходимости в глобальной межсервисной транзакции, которая блокировала бы все сервисы.
- Процесс становится устойчивым к сбоям: каждая ошибка обрабатывается локально и компенсируется.
- Подходит для систем с высокой нагрузкой и распределённой архитектурой: очереди, биллинг, бронирования, логистика.

Есть ли у вас интересные кейсы, когда Saga pattern спасала систему от ошибок?

#laravel #mysql #transactions #webdev #db #devops #saga #aftercommit
#️⃣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
А есть ли здесь представители компаний-аутстафферов, которые одним глазком наблюдают за нашей бурной деятельностью?😎

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

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

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

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

Принимаем заявки до 18:00 19 декабря - успевайте, возможно, мы ищем именно вас😉
🔥1
В этой части мы поговорим о движке таблиц 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 #Оптимизация
Продолжаем разбор движков 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,
`noscript` 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
А тем временем в новом эпизоде ITToLкового подкаста наши бессменные ведущие вывели на чистую воду человека, который привык оставаться за кадром (но у истоков💪) всех наших PR-активностей😎

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

🚀Что выяснили?
🔗 Смотри скорее по ссылке: https://vkvideo.ru/video-78780379_456239303
👍1
Продолжаем серию постов про 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
Всем привет!
Сегодня углубимся в детали ключей, партиций и дополнительных индексов — всё, что нужно для максимальной производительности.

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
Друзья, с наступающим 2026 годом! 🚀

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

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

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

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

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

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

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

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

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

С Новым годом! 🎄
🔥1🎉1