> Что такое пагинация? (Go)
Уровень: junior · Роль: backend · Язык: Go · Категория: Технические вопросы
Компании: InDrive
Стек: Go
> Пример ответа
Короткий ответ
Пагинация - это механизм разбиения большого набора данных на последовательные страницы фиксированного размера. В backend-разработке она используется для ограничения объёма данных, передаваемых клиенту за один запрос, что снижает нагрузку на БД, сеть и память. Основные параметры - page (номер страницы) и page_size (количество элементов), либо offset и limit. В Go пагинация реализуется через SQL-запросы с LIMIT и OFFSET или через cursor-based подход для больших объёмов.
Подробное объяснение
Пагинация решает проблему передачи больших выборок данных. Без неё клиент получил бы все записи, что приводит к медленным ответам, высокому потреблению памяти и нестабильной работе API.
Существует два основных подхода:
-
Offset-based (page-based) - использует
LIMITиOFFSETв SQL. Клиент передаёт номер страницы и размер. Пример:LIMIT 20 OFFSET 40вернёт записи с 41-й по 60-ю. Прост в реализации, но имеет проблемы при больших OFFSET - БД всё равно сканирует все пропущенные строки, что замедляет запрос. -
Cursor-based (keyset) - использует уникальное поле (например, id или created_at) как маркер позиции. Клиент передаёт значение курсора из последней записи предыдущей страницы. Пример:
WHERE id > 12345 ORDER BY id LIMIT 20. Эффективен для больших таблиц, стабилен при изменении данных, но сложнее в реализации и не позволяет перейти на произвольную страницу.
В REST API пагинация обычно оформляется через query-параметры: ?page=2&page_size=20 или ?limit=20&offset=40. Ответ часто содержит метаданные: total, page, page_size, has_next.
В Go пагинация реализуется на уровне репозитория - в SQL-запросах, и на уровне хендлера - парсинг параметров и валидация. Для cursor-based часто используются time.Time или int64 id.
На практике
В Go-проекте пагинация обычно выглядит так:
- Хендлер принимает query-параметры, валидирует их (page >= 1, page_size в разумных пределах, например, 1-100).
- Репозиторий принимает структуру с параметрами пагинации и формирует SQL-запрос.
- Ответ содержит данные текущей страницы и метаданные для построения навигации.
Для offset-based пагинации важно также получить общее количество записей - отдельным COUNT(*) запросом, чтобы клиент мог понять, сколько всего страниц.
Для cursor-based пагинации в ответе возвращается next_cursor - значение поля, по которому идёт сортировка, из последней записи. Клиент передаёт его в следующем запросе.
В Go часто используют структуру вида:
GOtype Pagination struct {Page intPageSize int}
Или для cursor-based:
GOtype CursorPagination struct {Cursor int64Limit int}
Пример кода
Пример offset-based пагинации в Go с использованием database/sql:
GOtype PaginationParams struct {Page intPageSize int}type PaginatedResponse struct {Data []Item `json:"data"`Total int `json:"total"`Page int `json:"page"`PageSize int `json:"page_size"`HasNext bool `json:"has_next"`}func GetItems(db *sql.DB, params PaginationParams) (PaginatedResponse, error) {offset := (params.Page - 1) * params.PageSizevar total interr := db.QueryRow("SELECT COUNT(*) FROM items").Scan(&total)if err != nil {return PaginatedResponse{}, err}rows, err := db.Query("SELECT id, name FROM items ORDER BY id LIMIT $1 OFFSET $2",params.PageSize, offset,)if err != nil {return PaginatedResponse{}, err}defer rows.Close()items := []Item{}for rows.Next() {var item Itemif err := rows.Scan(&item.ID, &item.Name); err != nil {return PaginatedResponse{}, err}items = append(items, item)}hasNext := offset+len(items) < totalreturn PaginatedResponse{Data: items,Total: total,Page: params.Page,PageSize: params.PageSize,HasNext: hasNext,}, nil}
Пример cursor-based пагинации:
GOfunc GetItemsByCursor(db *sql.DB, cursor int64, limit int) ([]Item, int64, error) {rows, err := db.Query("SELECT id, name FROM items WHERE id > $1 ORDER BY id LIMIT $2",cursor, limit,)if err != nil {return nil, 0, err}defer rows.Close()items := []Item{}var lastID int64for rows.Next() {var item Itemif err := rows.Scan(&item.ID, &item.Name); err != nil {return nil, 0, err}items = append(items, item)lastID = item.ID}return items, lastID, nil}
Как отвечать на собеседовании
Начни с определения: пагинация - это разбиение данных на страницы. Затем упомяни два подхода - offset-based и cursor-based, объясни разницу и когда что использовать. Для junior-уровня достаточно уверенно объяснить offset-based, но знание cursor-based будет плюсом.
Расскажи, как это реализуется в Go: парсинг параметров, валидация, SQL-запросы с LIMIT/OFFSET, формирование ответа с метаданными. Если спросят про производительность - отметь, что большие OFFSET медленные, и cursor-based решает эту проблему.
Приведи пример кода, если попросят. Не углубляйся в сложные темы вроде индексов или шардирования, если не спрашивают.
Что проверяет интервьюер
Интервьюер проверяет:
- Понимание базовой проблемы - зачем нужна пагинация.
- Знание SQL-конструкций LIMIT и OFFSET.
- Умение проектировать API-параметры и ответ.
- Понимание trade-off между offset-based и cursor-based.
- Способность написать корректный код на Go с обработкой ошибок и валидацией.
Для junior важно показать, что ты понимаешь, как пагинация влияет на производительность и UX, и умеешь реализовать простой вариант без ошибок.
Типичные ошибки
- Не валидировать параметры: page = 0 или page_size = 100000 - нужно ограничивать.
- Забывать про общее количество записей - клиент не сможет построить навигацию.
- Использовать OFFSET для больших таблиц без понимания проблем производительности.
- Не обрабатывать ошибки SQL-запросов.
- Путать page и offset: page = 2, page_size = 20 - это offset = 20, а не 40.
- Возвращать
next_cursorкак int64 без учёта, что поле может быть не числовым. - Не закрывать rows через defer - утечка соединений в Go.
> Похожие задачи по Go
Как исправить SQL запрос, который собирает строку без контекста?
Зачем добавлять контекст в запросы к базе данных?
Какие стратегии пагинации можно использовать вместо LIMIT OFFSET?
Что делать, если после добавления индекса селективность низкая и запрос остается медленным?
> Похожие задачи по backend
Как исправить SQL запрос, который собирает строку без контекста?
Зачем добавлять контекст в запросы к базе данных?
Какие стратегии пагинации можно использовать вместо LIMIT OFFSET?
Что делать, если после добавления индекса селективность низкая и запрос остается медленным?
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью