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

Связь: @devmangx

РКН: https://clck.ru/3FobxK
Download Telegram
Паттерн, который я использую в 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
Топ-ресурсы для изучения backend и системного программирования:

▪️OSDev Wiki
▪️Beej's Guides to Network Programming
▪️Julia Evans' Blog
▪️Low Level Programming University
▪️Computer Science from the Bottom Up
▪️Crafting Interpreters
▪️The Linux Documentation Project
▪️Writing an OS in Rust
▪️Destroy All Software
▪️Курсы Casey Muratori
▪️MIT OpenCourseWare
▪️Compiler Explorer
▪️Computer Systems: A Programmer's Perspective
▪️Hacker News
▪️Lobsters
▪️/r/systems
▪️ByteByteGo
▪️High Scalability
▪️Martin Kleppmann's Blog
▪️Architecture Notes
▪️The Morning Paper
▪️Brendan Gregg's Blog
▪️Dan Luu's Blog
▪️Phil Eaton's Blog
▪️Database Internals
▪️Designing Data‑Intensive Applications
▪️Systems We Love
▪️Strange Loop Conference (видеозаписи)
▪️Papers We Love
▪️CMU Database Group

👉 @BackendPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
9
Большинство разработчиков используют Spring каждый день… но многие до сих пор не до конца понимают AOP

Давайте разберёмся.

Что такое AOP?

AOP — это способ добавлять общий (cross-cutting) функционал без изменения основного бизнес-кода.

Примеры cross-cutting логики:

- логирование (Logging)
- проверки безопасности (Security checks)
- транзакции (Transactions)
- кэширование (Caching)
- отслеживание производительности (Performance tracking)
- обработка ошибок (Error handling)

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

Зачем нужен AOP?

Без AOP:
одна и та же логика повторяется во множестве методов.

С AOP:
вы пишете эту логику один раз и применяете ко всем нужным местам.

Плюсы:

- чистый код
- повторное использование
- отсутствие вмешательства в бизнес-логику (non-intrusive)

Как AOP работает в Spring

В Spring создаётся proxy (объект-обёртка) вокруг вашего bean.

Этот proxy решает:

- нужно ли запускать аспектную логику
- когда её запускать
- вокруг каких методов её применять

Вы продолжаете вызывать тот же самый bean, но Spring незаметно оборачивает его proxy-объектом.

Где AOP используется в реальных проектах

- @Transactional — транзакционный AOP
- @Cacheable — AOP для кэширования
- @Async — асинхронный AOP
- Spring Security
- системы логирования
- аудит (Auditing)
- rate limiting
- таймеры производительности

Практически каждый серьёзный проект на Spring Boot использует AOP “под капотом”.

👉 @BackendPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
2
Недавний вопрос с code review на собеседовании по Java: что не так с этим сервисом?

@Service
@RequiredArgsConstructor
public class СервисВывода {

private final РепозиторийАккаунтов accountRepository;
private final РепозиторийВыводов withdrawalRepository;

public void запросВывода(Long userId, BigDecimal amount) {
Account account =
accountRepository.findByUserId(userId)
.orElseThrow();

if (account.getBalance().compareTo(amount) < 0) {
throw new ИсключениеНедостаточноСредств();
}

boolean hasPending = withdrawalRepository
.existsByUserIdAndStatus(uid, Status.PENDING);

if (hasPending) {
throw new ИсключениеВыводУжеВОбработке();
}

account.setBalance(account.getBalance().subtract(amount));
accountRepository.save(account);

withdrawalRepository.save(
new Withdrawal(uid, amount, Status.PENDING)
);
}
}


Код компилируется, тесты проходят (green), но в production всё ломается.

[Дано]
Сервис обрабатывает запросы на вывод средств. Требования простые:

- нельзя вывести больше, чем есть на балансе;
- нельзя создать два запроса одновременно.

(код на прикреплённом изображении)

[Задача]

Два запроса от одного пользователя пришли одновременно. В результате баланс ушёл в минус, и было создано два запроса.

[Объясните]

- точную последовательность событий при конкурентных запросах;
- спасёт ли ситуацию аннотация @Transactional над методом или нет? Почему?
- как правильно исправить эту проблему.

👉 @BackendPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
2
Разработчики на Go знают эту боль:

if err != nil
if err != nil
if err != nil


Dingo пробует кое-что интересное.

Мета-язык, который компилируется в чистый Go, но добавляет такие вещи, как:
• типы Result
• сопоставление с образцом (pattern matching)
• распространение ошибок через ?

Пишешь на современном синтаксисе → на выходе получаешь чистый Go.

👉 @BackendPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
😁62
Алгоритм Sliding Window Log в основном используется для rate limiting в системе. Он использует логи (журнал временных меток), чтобы проверять и разрешать запросы от пользователей.

Как это работает?

Алгоритм сохраняет временные метки запросов и постоянно проверяет их, удаляя те, которые вышли за пределы заданного временного окна.

Например, представим окно 5 секунд, и разрешено не более 5 запросов за 5 секунд.

Сервер принимает запросы в текущем окне. Если количество запросов меньше или равно 5, сервер их обрабатывает.

Когда мы переходим к 6-й секунде, удаляется временная метка запроса, который был получен на 1-й секунде. После этого сервер может принять новый запрос, полученный на 6-й секунде.

Таким образом, скользящее окно (sliding window) постоянно сдвигается вперёд секунда за секундой, удаляя временные метки старых запросов, которые выходят за пределы окна.

Алгоритм также обрабатывает краевые случаи (edge cases). Например, если в какой-то момент приходит 8 запросов, а лимит составляет 5 запросов за 5 секунд, лишние запросы будут отклонены. На следующей секунде сервер снова сможет принимать запросы, если после удаления устаревших временных меток появится свободное место в окне.

На практике через окно может проходить гораздо больше запросов. Например, можно задать окно 10 секунд с лимитом 1000 запросов. Такой подход позволяет обрабатывать всплески (bursts) запросов и при этом продолжать работу без остановки.

Мне потребовалось некоторое время, чтобы разобраться в этом алгоритме, но в итоге стало понятно.

Следующий и последний алгоритм для rate limiting — это Sliding Window Counter.

👉 @BackendPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
6
Бэкенд-разработчики, это для вас:

Сценарий:
У вашей e-commerce платформы миллионы заказов. Команда маркетинга хочет видеть все завершённые заказы за последние 24 часа на дашборде.

SELECT o.id, o.user_id, o.total, o.status
FROM orders o
WHERE o.status = 'completed'
AND o.created_at >= NOW() - INTERVAL '1 day';


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

👉 @BackendPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
Каждая масштабируемая система начинается с одного важного компонента: балансировщика нагрузки (Load Balancer).

Без него ваш backend рано или поздно начнёт падать под нагрузкой.

Вот как на самом деле работают балансировщики нагрузки.

1. Один сервер

Представим, что в системе есть один backend-сервер.

Весь трафик идёт на одну машину.

Users → Server

Когда трафик растёт:

- загрузка CPU увеличивается
- память заполняется
- latency растёт

В итоге сервер падает.

2. Решение — горизонтальное масштабирование

Вместо одного сервера запускается несколько серверов.

- Server 1
- Server 2
- Server 3
- Server 4

Но теперь нужен способ распределять запросы между ними.

Именно для этого используется Load Balancer.

3. Новая архитектура

Теперь схема выглядит так:

Users → Load Balancer → Multiple Servers

Балансировщик решает, какой сервер будет обрабатывать каждый запрос.

4. Популярные алгоритмы балансировки

Round Robin
Запросы распределяются по очереди.

Req1 → Server1
Req2 → Server2
Req3 → Server3

Простой и очень распространённый метод.

Least Connections
Трафик отправляется на сервер с наименьшим количеством активных соединений.

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

IP Hash
Запросы от одного IP-адреса пользователя направляются на один и тот же сервер.

Часто используется, когда нужна session stickiness.

5. Дополнительные функции балансировщиков

Балансировщики также обеспечивают:

- health checks
- failover
- SSL termination
- routing трафика

Если сервер становится нездоровым, он автоматически исключается из пула.

Пользователи этого даже не замечают.

6. Популярные балансировщики в продакшене

- Nginx
- HAProxy
- AWS Elastic Load Balancing (ELB / ALB)
- Cloudflare

Балансировщик нагрузки — это первый шаг к масштабированию backend-системы.

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