Backend Portal | Программирование
16.8K subscribers
1.56K photos
147 videos
42 files
1.35K links
Присоединяйтесь к нашему каналу и погрузитесь в мир Backend-разработки

Связь: @devmangx

РКН: https://clck.ru/3FobxK
Download Telegram
Видел интересное видео от CTO Zerodha про то, как они масштабировали Postgres с 7+ млн таблиц.
Технология просто выносит мозг, а если копнуть глубже, становится еще веселее:

- синхронщина? не масштабируется, значит выкидываем.
- все в async. тяжелую генерацию отчетов ставим в очередь.
- независимый middleware, которому все равно на базу и на приложение.
- собрали “DungBeetle” на Go: обобщенные, независимые от СУБД HTTP API, чтобы тянуть отчеты из любой базы.
- результаты сливаем в отдельную Results DB, а приложение читает уже оттуда.

Вот так выглядит настоящий масштаб.

Смотреть видео

👉 @BackendPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
5
Хочешь посмотреть, как Java эволюционировала, фича за фичей?

brunoborges сделал офигенный сайт, где изменения языка Java показаны рядом, в формате side-by-side. Идеально для студентов и для тех, кто модернизирует легаси-приложения.

Зацени:
https://javaevolved.github.io

👉 @BackendPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
4🤔4
Повышения это про проактивность, а не реакцию постфактум.

Важно, чтобы твою работу и влияние замечали, и чтобы тебя награждали за вклад. Но как этого добиться?

Не жди, пока менеджер сам поднимет тему. Начни разговор первым. Просить повышение нормально. Многие стесняются, но тебе не обязательно. Сформулируй свой кейс на повышение и посмотри, как менеджер отреагирует.

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

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

И если ты сделал все, что просили, но тебе все равно говорят "пока нет", то возможные пути такие:

1. подождать: может реально нет открытых позиций
2. жестче, но корректно обсудить выше по цепочке и снова презентовать кейс
3. поискать возможности роста в соседних командах
4. начать искать новую роль в другом месте и, когда придет время, аккуратно и ответственно перейти

Тут нет универсально правильного или неправильного выбора. Единственная реально плохая реакция это сорваться на резкие или эмоциональные слова из разочарования. Всегда держи профессиональный тон.

Гнаться за повышением важно, но это не должно съедать всю твою энергию. Даже если сейчас съедает, тоже ок, потому что это твоя карьера. Просто следи, чтобы ты параллельно рос, делал работу, которой гордишься, и находил спокойствие в том, чем занимаешься.

👉 @BackendPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
7
Продвинутые паттерны конкурентности в Go:
Bridge Channel

bridge разворачивает канал каналов в один выходной канал. Он читает каждый внутренний канал по порядку и форвардит значения в выходной.

Если у тебя есть последовательность каналов (например, из постраничных вызовов API или продьюсеров, которые отдают данные чанками), Bridge превращает это в один цельный поток.

В основном это отлично подходит для пагинированных или батчевых продьюсеров.

👉 @BackendPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
4👍2
Один умный человек, написал эссе про алгоритм BM25, который лежит в основе Elasticsearch, Apache Solr и Apache Lucene.

Зачем? Просто потому что ему было нечем заняться, он нырнул в кроличью нору информационного поиска: пытался понять, почему и где TF-IDF в итоге упирается в свои пределы, как на самом деле устроен BM25 под капотом, и почему системы вроде Elasticsearch, Solr и Lucene сделали его своей дефолтной функцией ранжирования.

В эссе узнаете, почему BM25 так хорошо работает, как каждая часть формулы влияет на ранжирование, и какое место он занимает в современном retrieval-стеке.

Этот текст должен дать тебе нормальную ментальную модель, чтобы понимать BM25 и также IDF из популярного TF-IDF. Зацени.

👉 @BackendPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
16🔥2
This media is not supported in your browser
VIEW IN TELEGRAM
Лучшая тулза для создания софтверных диаграмм.
И вдобавок бесплатная и с поддержкой GitHub

Идеально подходит для UML, флоу и процессов.
Экспортирует в картинку, PDF, HTML и другое.

http://app.diagrams.net

👉 @BackendPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
9👍3
Вот одна из самых базовых вещей про то, как работает память в операционных системах, но со временем многие это просто забывают. Небольшое напоминание: когда вы вызываете malloc(), вы на самом деле не получаете память, вы получаете обещание.

Когда вы делаете malloc на 1 ГБ, ОС говорит: «Окей, вот тебе 1 ГБ», но реально пока не отдает ни одного байта. На этом этапе выделяется виртуальная память, а фактическое потребление RAM, то есть RSS (Resident Set Size), остается прежним.

Это и есть lazy allocation. ОС назначает реальные страницы только тогда, когда вы начинаете в них писать. Вы можете вызвать malloc() хоть на терабайты на ноутбуке с 16 ГБ RAM, и вызов пройдет успешно. Падение, тот самый segmentation fault, случится позже, когда вы попытаетесь эту память использовать, а система уже не сможет ее реально предоставить.

- malloc(1GB) -> виртуальная память +1 ГБ, RSS +0 МБ
- memset(ptr, 0, 1GB) -> RSS +1 ГБ

Почему это важно? Потому что именно RSS реально конкурирует за ресурсы. Именно на него смотрит OOM killer, и именно он в итоге тормозит систему.

У вас может быть процесс, который в профайлере показывает 10 ГБ выделенной памяти, а реально потребляет только 2 ГБ RAM. Разница возникает потому, что большая часть этих областей, выделенных через malloc, была зарезервирована, но к ней так и не было доступа.

Кстати, именно поэтому утечки памяти могут долго оставаться незаметными. Если вы сделали malloc(), потеряли указатель и ни разу в эту память не записали, то вы утекли по виртуальному адресному пространству, но не по физической памяти.

Самый простой способ посмотреть RSS процесса, это запустить ps aux. В выводе вы увидите VSZ (virtual) и RSS (resident) в КБ. Это можно получить и программно :)

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

👉 @BackendPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
8👍5
Паттерн, который я использую в Go для запуска долгоживущих сервисов или фоновых процессов.

Идея в том, что сервис реализует всего один блокирующий метод Run(ctx), который делает всю основную работу: обслуживает запросы, следит за изменениями и так далее.
Чтобы его остановить, достаточно отменить контекст.

За счет этого интерфейс получается очень простым. Если нужен неблокирующий старт/стоп, их можно собрать прямо в том месте, где они нужны:

stop() -> просто используйте context.WithCancel. Возвращаемый cancel() и будет вашей функцией остановки.

start() -> оберните Run(ctx) в замыкание и запустите как goroutine. Владельцем goroutine остается вызывающая сторона, а не сам сервис. Это позволяет держать сервис простым и предсказуемым.

Также можно добавить канал done, чтобы понимать, когда Run полностью завершился, для graceful shutdown.

Этот подход особенно хорош, когда нужно управлять сразу многими такими сервисами. Если запускать их через errgroup, вы получаете:

- автоматическую отмену всех сервисов, если один из них завершился с ошибкой
- один общий Wait() для ожидания завершения всех сервисов

// Service only implements a blocking Run(ctx). Cancel the context to stop it.

type Service struct{}

func (s *Service) Run(ctx context.Context) error {
fmt.Println("running")

// Do busy work: serve requests, watch for changes, etc.
// Return when ctx is canceled
<-ctx.Done()

fmt.Println("stopped")
return nil
}

func main() {
ctx := context.Background()
s := &Service{}

// 1. blocking: just call Run with a context
ctx1, _ := context.WithTimeout(ctx, 2*time.Second) // canceled after 2 seconds for demo
err := s.Run(ctx1)

// 2. non-blocking: implement ad-hoc start/stop and wait for completion if needed
ctx2, stop := context.WithCancel(ctx)
done := make(chan error, 1)
start := func() { // start() wrapper to send the result of Run to done channel
done <- s.Run(ctx2)
}

go start() // non-blocking start in a goroutine
time.Sleep(2 * time.Second)
stop() // cancel ctx to signal shutdown
err = <-done // wait for Run to return if needed
}


👉 @BackendPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
1👍1
Избегайте использования CASE в WHERE

Хотя SQL допускает использование выражений CASE внутри WHERE, они часто делают запросы менее читаемыми и усложняют понимание их логики. Логика фильтрации оказывается спрятана внутри условного выражения вместо того, чтобы быть выраженной напрямую через булевы предикаты. Кроме того, оборачивание условий в CASE может затруднить для оптимизатора запросов анализ предиката и эффективное использование индексов. Во многих случаях это делает запрос non-SARGable (Search ARGument Able), то есть база данных не может эффективно использовать индексы для поиска нужных строк.

Вот наивный вариант:

SELECT *
FROM Products
WHERE CASE
WHEN Category = 'Electronics' THEN Price < 500
ELSE Price < 100
END;


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

Лучший способ выразить такую логику, это использовать операторы AND и OR напрямую в условии фильтрации. Явно записанные булевы предикаты делают логику проще для чтения и понимания, потому что условия хорошо видны, а не спрятаны внутри CASE.

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

SELECT *
FROM Products
WHERE Price < 100
OR (Category = 'Electronics' AND Price < 500);


👉 @BackendPortal
👍74💊1
Сейчас все учат Next.js и AI-инструменты.

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

CMU Advanced Database Systems - Полный плейлист из 22 лекций

Освойте редкие и ценные навыки:

• Как на самом деле устроены движки выполнения запросов и оптимизаторы
• Колончатое хранение, векторизованное выполнение и современный OLAP
• Почему Snowflake, Redshift и DuckDB всё перевернули
• Сбои в распределённых БД и миф про «одну базу данных, которая решит всё»

👉 @BackendPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
9🔥1
Избегайте использования IN с NULL

Оператор IN относится к тем конструкциям, которые легко вносят тихие баги в запрос, если использовать его неправильно. Когда вы включаете NULL в список IN, сравнение никогда не даст TRUE для части с NULL. В результате строки, содержащие NULL, не матчятся так, как многие ожидают. SQL использует трёхзначную логику: TRUE, FALSE и UNKNOWN. Сравнения с NULL не возвращают ни TRUE, ни FALSE; они возвращают UNKNOWN. Вот наивный вариант использования IN:

SELECT *
FROM Employees
WHERE DepartmentID IN (1, 2, NULL);


Поскольку NULL даёт UNKNOWN, запрос выше выполнится без ошибок, но гарантированно вернёт пустой результат.

Правильный способ обрабатывать NULL - использовать IS NULL. IS NULL явно учитывает то, как SQL работает с отсутствующими значениями. Это помогает запросу корректно различать реальные значения и неизвестные значения, что предотвращает тихие логические ошибки. Вот как этот запрос лучше писать:

SELECT *
FROM Employees
WHERE DepartmentID IN (1, 2)
OR DepartmentID IS NULL;


👉 @BackendPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
Многие разработчики до сих пор по умолчанию думают в стиле: HTTP работает поверх TCP

Но в HTTP/3 под капотом используется UDP через протокол QUIC (Quick UDP Internet Connections).

> HTTP/1.1 → TCP → Ориентированное на соединение, надёжное
> HTTP/2 → TCP → С мультиплексированием, но всё ещё поверх TCP
> HTTP/3 → QUIC → Построен поверх UDP ради скорости и снижения задержек

Сейчас HTTP/3 используют 38,2% всех сайтов.

👉 @BackendPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔4
Большинство API заявляют, что они REST.
Но на практике нарушают принципы REST почти везде.

Вот 5 самых частых ошибок, которые я постоянно вижу.

1. Глаголы в URL

Плохо

/getUser
/createOrder


Хорошо

GET /users/{id}
POST /orders


REST-ресурсы должны быть существительными, а не действиями.

2. Игнорирование HTTP-методов

Часто встречается такое:

POST /updateUser


Вместо

PUT /users/{id}


HTTP-методы придуманы не просто так.
Их нужно использовать по назначению.

3. Всегда возвращается 200

API должно чётко сигнализировать результат.

Примеры:

200 → успех
201 → ресурс создан
400 → неверный запрос
404 → ресурс не найден
409 → конфликт


Статус-коды — это часть контракта API.

4. Нет пагинации

Возвращать тысячи записей в одном ответе — это ловушка для производительности.

Нужно сразу проектировать что-то вроде:

GET /orders?page=1&size=20


или использовать cursor-pagination.

5. Версионирование «потом как-нибудь»

Ломающие изменения в API неизбежны.
Без версионирования вы просто ломаете клиентов.

Используйте, например:

/api/v1/...


О версионировании нужно думать с самого начала.

👉 @BackendPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
13👍4
Ты сказал ИИ: «сделай систему уведомлений».

Он сделал через HTTP.

Пользователь каждые 2 секунды спрашивает сервер:
— «Есть ли уведомления?»
— «Есть ли уведомления?»
— «Есть ли уведомления?»

Это называется polling. Работает, но крайне неэффективно.

Если пользователей 1000, сервер получает 500 запросов в секунду. Все ответы: «Нет».

Правильный подход: WebSocket.

Соединение открывается один раз.
Когда приходит уведомление, сервер пушит его клиенту.
Пользователь ничего не спрашивает.

Разница такова:

Polling: «Пришёл пакет? Пришёл? Пришёл?»
WebSocket: «У двери звонок — когда приходит, звонит».

Для чата, живых уведомлений, мгновенных цен лучше думать о WebSocket.

ИИ по умолчанию пишет на HTTP.
Если скажешь «нужны живые обновления» — перейдёт на WebSocket.

Но если не уточнишь, он выберет самый простой путь.

👉 @BackendPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
👍142
Запускать несколько проектов локально — тот ещё минус вайб

localhost:3000, localhost:3001, localhost:8080… где вообще какой?

Один конфликт портов, и вся локальная среда разваливается.

Portless от Vercel Labs решает это аккуратно.

Вместо номеров портов ты получаешь стабильные именованные URL:
http://myapp.localhost:1355
http://api.myapp.localhost:1355
http://docs.myapp.localhost:1355

Что это решает:
• конфликты портов между проектами
• утечки cookies и storage между приложениями на разных портах
• путаницу «какая вкладка к чему относится?» в монорепозиториях
• Git worktrees: каждая ветка автоматически получает собственный поддомен

Работает с Next.js, Vite, Express, Nuxt, React Router, Angular, Expo.

Есть и AI-аспект.
Кодовые агенты часто хардкодили порты и ошибались. Именованные URL означают, что агент всегда точно знает, куда обращаться.

3.8k звёзд.
v0.5.2.
Проект активно поддерживается командой Vercel Labs.

npm install -g portless
portless run next dev


И всё.

https://github.com/vercel-labs/portless

👉 @BackendPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
4
This media is not supported in your browser
VIEW IN TELEGRAM
Если это компилируется, то оно должно:

1. задеплоиться, и
2. запуститься .

Выше на демо: если я забуду предоставить Service, необходимый моей Lambda-функции (например, BucketEventSource), то получу ошибку типов.

Layers включают Resources и IAM-политики с минимально необходимыми правами (least-privilege). Корректность гарантирована.

👉 @BackendPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
.git/info/exclude — одна из моих любимых возможностей Git, но я постоянно удивляюсь, когда разговариваю с людьми, которые о ней никогда не слышали.

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

Это особенно удобно, когда нужно временно игнорировать какие-то файлы, появившиеся во время дебага или локальной разработки.

👉 @BackendPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥101
Я только что нашел 100% открытый и полностью бесплатный заменитель Postman, который работает прямо в вашем браузере без необходимости установки.

Его название — Hoppscotch.

Без лишнего веса для десктопа. Без $14/месяц с пользователя. Без сбора данных.

HTTP, GraphQL, WebSocket, тестирование в реальном времени, генерация кодовых фрагментов и миграция из Postman в один клик. Включает десктопную версию и CLI.

100% Открытый исходный код. Лицензия MIT.

👉 @BackendPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
15🤔2
Приглашаем вас на встречу Архитектурного клуба Яндекс 360!

Инженеры Яндекс 360 накопили большой опыт в проектировании систем, которыми пользуются более 100 миллионов человек каждый месяц, и теперь готовы делиться этим опытом и объединять вокруг него единомышленников.

26 марта в 17:00 Вместе с Дарьей Андреевой, руководителем бэкенд-разработки Биллинга и B2B‑платформы, мы разберём нетривиальную задачу по проектированию больших групп в организации на примере Яндекс 360.

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

Нужно только оставить почту: ссылка.
Rate Limiting vs Throttling

Rate limiting = отклонение запросов после достижения лимита

Throttling = замедление обработки запросов вместо их отклонения

Пример:

Клиент отправляет 100 запросов/сек

Rate limiting
→ разрешить первые 50
→ остальные отклонить с ошибкой 429 Too Many Requests

Throttling
→ поставить запросы в очередь или задержать
→ обрабатывать их постепенно

Rate limiting — отбрасывает лишний трафик
Throttling — контролирует скорость обработки трафика

Оба подхода защищают систему от перегрузки.

Как это реализуют

Rate limiting → алгоритмы token bucket или sliding window counters

Throttlingочередь запросов + пул воркеров (контролируемая скорость обработки)

👉 @BackendPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥8
Интересный факт: Stripe использует MongoDB для хранения своих основных данных, и при этом система обрабатывает 5 миллионов QPS и работает с 2000+ шардами

Некоторое время назад Stripe опубликовали инженерный блог, в котором рассказали о своей архитектуре баз данных и о том, как они масштабировали MongoDB-кластер, чтобы поддерживать нагрузку в 5 млн QPS, при этом сохраняя стабильность и управляемость системы.

В статье также подробно разбираются:

- платформа перемещения данных (data movement platform)
- логическое и физическое шардинг-разделение
- и, что особенно интересно, механизм “flip switch” — переключатель, позволяющий безопасно менять маршрутизацию или конфигурацию системы.

Всё это реализовано очень аккуратно и производит действительно сильное впечатление.

👉 @BackendPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥3💊1