TGViewer
Channel Public Channel
ryzhov_dev

ryzhov_dev

@ryzhov_dev

Subscribers
11
Photos
1
Videos
1
Links
2
Recent Posts 13 shown
Post #15 31
ryzhov_dev На прошлых выходных принял участие в AvitoCTF и решил сделать райтап для задания МедоИИд 👍
Мой райтап неожиданно залетел в топ-10 и Авито прислали свой мерч 🎉
  • 🎉 3
Post #14 139
На прошлых выходных принял участие в AvitoCTF и решил сделать райтап для задания МедоИИд 👍
  • ❤ 4
Post #13 129
Rate limiting в Go — без магии, по делу

Рейт-лимитер — штука, которую обычно добавляют в панике после первого всплеска трафика. Расскажу, как это выглядит в небольшой команде без выделенного продвинутого API-gateway.

Алгоритм. Leaky bucket сглаживает поток до ровной скорости — звучит красиво, но подход режет ожидаемые всплески (страница открывает десять запросов параллельно — и половина возвращает 429). Token bucket наполняется равномерно, но позволяет потратить накопленные токены пачкой, вплоть до размера ведра. Для HTTP API в 99% случаев нужен именно token bucket. В Go есть golang.org/x/time/rate — с него и стоит начинать.


func rateLimit(next http.Handler) http.Handler {
limiters := sync.Map{}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
key := r.Header.Get("X-API-Key")
l, _ := limiters.LoadOrStore(key, rate.NewLimiter(rate.Every(time.Second), 20))
if !l.(*rate.Limiter).Allow() {
http.Error(w, "too many requests", http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
})
}


Скоуп. Обычно одного лимита мало. Нужны три оси сразу: по IP (базовая защита), по пользователю, по API-ключу (тарифы).

Где ставить. Middleware в приложении видит авторизацию — значит, лимиты по юзеру и ключу живут там. Как только инстансов больше одного — переезжайте на Redis. Про лимиты на уровне Nginx будет отдельный пост.

А вы где режете трафик — на гейте, в приложении, или и там и там?
  • 🔥 2
Post #12 104
Индексы в MariaDB, которая тянет и OLTP, и OLAP

Писал недавно про одну базу под две нагрузки. Сегодня — про индексы внутри такого сетапа, потому что именно тут чаще всего и стреляют в ногу.

Сценарий знакомый: отчёт тормозит, добавляешь покрывающий индекс (covering — такой, в котором лежат все колонки, нужные запросу, и в саму таблицу база уже не ходит) — и тяжёлый аналитический запрос из 40 секунд превращается в "моментально". Красиво. А через неделю прилетает жалоба, что обычная страница со списком заказов начала тупить под нагрузкой, и оказывается, что каждый INSERT и UPDATE в горячую таблицу теперь поддерживает ещё одну структуру. На смешанной нагрузке цена записи чувствуется сразу.

Простой пример — индекс, который спасает отчёт и мешает записи:


CREATE INDEX idx_orders_report
ON orders (status, created_at, customer_id, total);


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

Что держу в голове перед тем, как добавить индекс:

• Порядок колонок в композите. Left-most prefix — банально, но забывается первым. Индекс (status, created_at) не поможет запросу, где в WHERE только created_at. Перед тем как создавать — выпиши все запросы, которые он должен закрыть, и проверь, что префикс совпадает с их WHERE и ORDER BY.
• Иногда отдельный "индекс под отчёт" лучше, чем переписывать запрос. Особенно если запрос генерится BI-инструментом и руки до него не дойдут. Главное — назвать индекс по делу (`idx_orders_report_*`), чтобы через полгода было видно, кто его заказчик, и можно было спокойно дропнуть, если отчёт умер.
• EXPLAIN показывает, что оптимизатор думает. Сделает ли он так на реальных данных — не факт. На пустой стейдж-таблице план может быть идеальным, а на проде с 50 млн строк оптимизатор выберет другой индекс или вообще full scan. ANALYZE TABLE и обновленная статистика тут решают больше, чем кажется.
• Селективность важнее красоты. Индекс по status, где 95% строк в одном значении, — это почти full scan с лишними блоками. Перед созданием — посчитай COUNT(DISTINCT ...) и оцени, что реально фильтруется первой колонкой.

Правило, к которому возвращаюсь: если отчёт бегает 10 раз в день, а записей 10 000 — математика обычно против индекса, проще ночной materialized snapshot или реплика под аналитику. Если отчёт закрывает месяц для финансов и не может ждать — математика другая, и плата оправдана.
  • 🔥 2
Post #11 76
Вчера у меня приняли финальный проект на курсе Go разработчика в Яндекс Практикуме 🥳 Теперь остается дождаться, когда мне пришлют еще один диплом о профессиональной переподготовке.

Рассказываю о курсе.
Он существует в двух вариантах: “Продвинутый Go-разработчик” и “Продвинутый Go-разработчик + инфраструктура и продакшн”. Первый вариант длится 6 месяцев, второй на 2 месяца дольше.
Я выбрал вариант с инфраструктурой, т.к. хотел посмотреть на Observability и конечно же на Kubernetes.
Все обучение разбито на спринты, в каждый спринт входит одна или несколько тем и практическое задание, которое нужно успеть сделать и сдать в рамках активного проекта. Всего проектов 4:
1. Сокращатель URL или Сервис сбора метрик и алертинга. Тут ты сам выбираешь что хочешь делать.
2. Накопительная система лояльности.
3. Менеджер паролей. Достаточно объемный проект, т.к. нужно сделать и сервер и cli клиента. Кстати, именно благодаря этому проекту я в целом узнал о понятии TUI, т.е. я, конечно, раньше видел разные штуки в терминале, но не думал, что это целый подход на ровне с GUI. Для тех, кому эта тема интересна, рекомендую посмотреть на ГОшный пакет https://github.com/charmbracelet/bubbletea
4. Сервис открытого профиля. Этот проект начинается на инфраструктурной части, поэтому бОльшая часть практики там – это всякие YAML манифесты.

Как я писал выше – каждый спринт нужно:
1. Прочитать теоретическую часть.
2. Выполнить практическое задание. К нескольким проектом Яндекс дает готовые автотесты, без прохождения которых ты не можешь отправить код на ревью.
3. Далее нужно подождать пока ревьюер посмотрит на твой код и оставит свой фидбек.
4. Когда ревьюер одобрит твои изменения, то ты их можешь сливать в мастер. Далее ждешь следующий спринт, т.к. теория раньше не открывается.
Сроки по спринтам надо соблюдать. Что происходит в случае факапа я не знаю, т.к. все сдавал вовремя, но что-то точно происходит.

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

Мне курс показался не очень простым. Я учился в среднем по 6-10 часов в неделю и для этого курса это минимум, по-хорошему лучше закладывать больше времени. В 2024 году я прошел курс по управлению командой разработки, и он мне показался гораздо проще, но тут надо учитывать тот факт, что я сам уже давно занимаюсь в первую очередь управлением, а не непосредственно разработкой.

В общем и целом, курс я рекомендую. Особенно тем, кто давно хотел перейти на Go, но на текущем месте работы у вас его еще нет. По ссылке https://practicum.yandex.ru/referrals/?ref_code=gAAAAABqBLFT8-wgh89MTtqbbS5AHJkPeC-WmCLtombGTv62FpRsprZuVG0D8QoUHX1pg2PgGW68poXOVqkR5VAtZkNi9-U2wg%3D%3D можно получить скидку в 9% (не только на этот курс, а вообще на любой в Практикуме)
GitHub GitHub - charmbracelet/bubbletea: A powerful little TUI framework 🏗 A powerful little TUI framework 🏗. Contribute to charmbracelet/bubbletea development by creating an account on GitHub.
  • 🔥 2
Post #10 48
Nginx как reverse proxy — баги, которые вылезают только после деплоя

Nginx выглядит простым, пока не выкатишь в прод. Конфиг валидный, стейджинг зелёный, а потом начинается "почему вдруг 413", "почему SSE не стримит", "почему бэкенд видит не тот путь".

Самая простая грабля — слеш в proxy_pass. Один символ меняет всё:


location /api/ {
proxy_pass http://app/; # бэкенд видит /users
}

location /api/ {
proxy_pass http://app; # бэкенд видит /api/users
}


Оба конфига валидны. Только один совпадает с тем, что ждёт твоё приложение.

Остальные, на которые стоит смотреть первым делом:

- X-Forwarded-For и X-Forwarded-Proto — доверяй только тем, что проставил сам. То, что прислал клиент, — это пользовательский ввод, и на нём нельзя строить ни rate limiting, ни аудит.
- client_max_body_size против лимитов приложения — выигрывает меньший, а 413 прилетает от того, кто первый отказал.
- proxy_buffering on по умолчанию тихо ломает SSE и стриминг. Выключать надо точечно, на нужных location.
- Таймауты: proxy_read_timeout, proxy_send_timeout, keepalive_timeout, send_timeout — каждый рубит запрос на своей стадии. Половина дебага — понять, кто именно выстрелил.

Проблема не в том, что nginx сложный. Проблема в том, что дефолты разумные для статики и не всегда разумные для API.
  • 🔥 2
Post #9 53
Вебхуки в Go — как тихо сломать себе безопасность

Вебхуки выглядят как самая скучная часть интеграции: пришёл POST, распарсил, ответил 200. Но в e-commerce через них ходят деньги и заказы — от маркетплейсов, от платёжных провайдеров. И проверка подписи там — это место, где легко сделать "почти правильно", что на самом деле неправильно.

Три грабли, на которые я сам наступал или видел рядом:

1. Сравнение подписи через == или bytes.Equal. Это не constant-time, и теоретически подпись можно подобрать по утечкам времени. Надо hmac.Equal.
2. Нет проверки таймстемпа — значит, перехваченный запрос можно переиграть хоть через сутки. Окно в 5 минут закрывает 99% проблем.
3. Парсинг JSON до проверки подписи ломает всё: ты re-сериализуешь тело, порядок ключей и пробелы меняются, и подпись уже не сходится. Хешировать надо ровно те байты, что пришли.

Примерно так:


body, _ := io.ReadAll(r.Body)
ts := r.Header.Get("X-Signature-Timestamp")
unix, _ := strconv.ParseInt(ts, 10, 64)
if time.Since(time.Unix(unix, 0)).Abs() > 5*time.Minute {
return errors.New("stale")
}
mac := hmac.New(sha256.New, secret)
mac.Write([]byte(ts + "."))
mac.Write(body)
got, _ := hex.DecodeString(r.Header.Get("X-Signature"))
if !hmac.Equal(got, mac.Sum(nil)) {
return errors.New("bad sig")
}


Мало кода, много смысла. И главное — сначала проверяем байты, потом парсим JSON, а не наоборот.
  • 🔥 2
Post #8 49
Одна MariaDB на OLTP и OLAP — как в учебнике нельзя, а в жизни так и живём

В учебниках всё красиво: операционка отдельно, аналитика отдельно, между ними — ETL и уважительная дистанция. В реальности у меня одна MariaDB, в которой лежат товары, заказы, клиенты — и к ней же прицеплены внутренняя аналитика и внешний OLAP-куб. Всё в одном инстансе, на одном буфере, в одном бэкап-окне.

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

С чем живём:

— Индексы, которые спасают аналитику, тормозят записи. Любой новый индекс — это компромисс.
— Лок-контеншен, когда тяжёлый отчёт приходит в час пик.
— Бэкап-окно, которое с ростом данных всё уже и уже.
— Планы запросов, которые иногда удивляют — особенно когда нагрузки конкурируют за буфер.

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

Это не "правильная архитектура". Это та, с которой мы живём и знаем, где у неё болит. И, подозреваю, таких сетапов вокруг сильно больше, чем принято признавать.
  • 🔥 2
  • 😭 1
Post #7 57
Контекст в Go — грабли, на которые PHP-шники наступают стабильно

Честно: я тоже на это наступал. Причём не один раз.

В PHP всё просто — клиент отвалился, процесс умер, соединения закрылись. В Go так не работает. context.Context — это не "таймаут на запрос", это контракт: "вот сигнал остановиться, будь добр, услышь его". Если его игнорировать, работа продолжается, хотя результат уже никому не нужен. А ресурсы — коннекты к БД, горутины, память — висят.

Классика, которую я ловил:

- Горутина, запущенная из хендлера, не слушает ctx.Done().
- http.Client без таймаута и без контекста в запросе.
- db.Query(...) вместо db.QueryContext(ctx, ...).

Вот так горутина и утекает:


// было: горутина живёт дольше, чем нужно
func handler(w http.ResponseWriter, r *http.Request) {
go func() {
for {
doWork() // никто не останавливает
time.Sleep(time.Second)
}
}()
}

// стало: горутина слышит отмену и выходит
func handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
go func() {
for {
select {
case <-ctx.Done():
return
default:
doWork()
time.Sleep(time.Second)
}
}
}()
}


Без select с <-ctx.Done() горутина переживёт и хендлер, и смысл своего существования.

Самое противное — оно не падает. Просто под нагрузкой медленно течёт: пул коннектов забивается, горутин всё больше, память ползёт вверх. Ищется потом долго.

Мораль: в Go отмену надо прокидывать руками. Везде. Привычка из PHP "оно само" тут не работает.
  • 🔥 1
Post #6 61
В прошлых двух постах я рассказал, кто я и зачем полез из PHP в Go. Сегодня — про безопасность. Не абстрактную, а ту, с которой сталкиваюсь каждый день в e-commerce.

Я управляю пятью онлайн-магазинами, у нас 22+ интеграции с маркетплейсами, платежки через Adyen, YooMoney, крипту и банковские переводы, ERP-системы. Это значит — PII клиентов, API-ключи, платежные данные. И я видел, как это всё ломается.

Мой чеклист для веб-разработчика — из боевого опыта:

1. Секреты не в коде. Видел API-ключи маркетплейсов прямо в репозитории. Используйте vault или хотя бы .env + .gitignore. Серьёзно.

2. Security-заголовки. Content-Security-Policy, X-Frame-Options, Strict-Transport-Security — проверьте прямо сейчас. Половина проектов, которые я видел, отдают голый ответ без единого заголовка.

3. Валидация на сервере. Фронтовая валидация — это UX, не безопасность. Платежные колбэки от Adyen надо верифицировать, а не просто парсить.

4. Логирование без PII. Однажды нашёл в логах полные данные карт. Логируйте действия, не данные клиентов.

5. Зависимости. composer audit, govulncheck — запускайте в CI. Уязвимая библиотека — это открытая дверь.

6. Rate limiting. Без него ваш API для авторизации — подарок для брутфорса.

Всё это — не rocket science, а базовая гигиена. Но именно на базе чаще всего и спотыкаются.

Хороший старт для углубления — OWASP Top 10 (owasp.org/www-project-top-ten/).

А какой пункт из списка вы последний раз проверяли у себя в проекте? Делитесь в комментариях 💬
owasp.org OWASP Top Ten Web Application Security Risks | OWASP Foundation The OWASP Top 10 is the reference standard for the most critical web application security risks. Adopting the OWASP Top 10 is perhaps the most effective first step towards changing your software development culture focused on producing secure code.
  • 🔥 3
Post #5 52
Что меня удивило в Go после 18 лет на PHP

Ну что, первый технический пост. Как писал в интро — заканчиваю курс по Go в Яндекс Практикуме и уже пробую его в боевых проектах. После 18 лет на PHP ощущения... специфические. Делюсь тем, что реально зацепило 👇

Обработка ошибок — никаких try/catch

Функция возвращает ошибку, и ты обязан разобраться с ней прямо здесь. Не "где-то наверху в catch", не "потом". Сейчас.


file, err := os.Open("config.yaml")
if err != nil {
return fmt.Errorf("failed to open config: %w", err)
}


Первую неделю это раздражает — код разбухает, if err != nil преследует во сне. Потом замечаешь: в проде перестают всплывать те самые "а откуда это прилетело?" ошибки. Стоит того.

Стандартная библиотека

HTTP-сервер, JSON, крипто, тесты — всё из коробки. На PHP я бы сначала полчаса ковырял composer.json и подключал пакетов десять, прежде чем написать первую строчку логики.

Деплой — один бинарник

Без PHP-FPM, без Nginx, без composer install на сервере. Собрал, скопировал, запустил. Кто танцевал с конфигами на пяти e-commerce площадках — тот поймёт, какое это облегчение.

Горутины вместо "один запрос — один процесс"

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

Типизация без компромиссов

PHP годами добавлял type hints. В Go типы — не пожелание, а требование. Компилятор ловит то, что раньше ловили юнит-тесты (если они были).

Go не лучше PHP — они про разное. Но после 18 лет в одной экосистеме полезно увидеть знакомые задачи с другой стороны. Буду делиться процессом — подписывайтесь, если тема близка 🤔
  • 🔥 4
Post #4 71
Привет! 👋 Меня зовут Всеволод.

Коротко: 18 лет в разработке, из них последние 5+ — Director of Engineering в PHILIPP PLEIN. Да, та самая fashion-компания с черепами. Нет, я не выбираю ткани — я отвечаю за то, чтобы всё цифровое работало.

Под «всё цифровое» я имею в виду: 5 e-commerce платформ, 22+ интеграций с маркетплейсами, ERP, и команда из 9 человек, которую я собрал с нуля. До этого был CTO в IT-консалтинге — аутстафф, e-commerce, финтех. Начинал, как многие, с PHP в 2007-м.

Почему завёл канал? Потому что происходит сразу несколько вещей, про которые хочется думать вслух:

🔸 Перехожу на Go после 15+ лет на PHP. Заканчиваю курс в Яндекс Практикуме, начинаю тащить Go в рабочие проекты. Ощущения — как заново учиться водить после автомата на механике.

🔸 Полез в инфобез — тестирование веб-приложений, OSINT, готовлюсь к первым CTF. Оказывается, ломать то, что сам строишь — отдельный вид удовольствия.

🔸 Строю инженерные процессы в компании, где IT — это не продукт, а поддержка бизнеса. Со всеми вытекающими.

О чём буду писать ✏️

— PHP -> Go: что удивляет, что бесит, что реально работает лучше
— Инженерное управление за пределами big tech — без agile-коучей и бесконечных бюджетов
— Безопасность веб-приложений на практике, а не в теории
— Честные заметки из повседневной работы: грабли, находки, выводы

Я не эксперт-на-сцене. Я человек, который каждый день решает задачи и попутно разбирается в новом. Если вам интересно наблюдать за этим процессом — подписывайтесь, будет полезно 🙌
  • 🔥 4
Post #1
Channel created

About this channel

How can I read @ryzhov_dev without a Telegram account?
TGViewer shows the public web preview Telegram publishes for ryzhov_dev: recent posts, photos, videos and the subscriber count, with no app, login or account.
How many subscribers does ryzhov_dev have?
ryzhov_dev (@ryzhov_dev) has 11 subscribers on Telegram, refreshed roughly every 30 minutes.
Does ryzhov_dev know I viewed it here?
No. Public channel previews carry no viewer identity, and TGViewer has no accounts or tracking of what you look up.
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 →