Розробка алгоритмічної ленти рекомендацій в мобільній програмі
Алгоритмічна лента — це не список постів за датою, відсортований в зворотному порядку. Це ранжуюча система, яка в реальному часі вирішує: який контент показати конкретному користувачу в конкретний момент. TikTok, Instagram Reels, YouTube Shorts використовують один принцип: максимізація часу взаємодії через predicted engagement score для кожної одиниці контенту.
Архітектура: candidate generation → ranking → serving
Алгоритмічна лента працює в два етапи, мобільна програма критично залежить від обох.
Candidate generation — з мільйонів одиниць контенту відбираємо кілька сотень кандидатів для конкретного користувача. Зазвичай це облегчена модель (Approximate Nearest Neighbor за user embedding) або набір правил: дотримувані користувачі, trending у гео, topic affinity. Цей етап повинен укладатися у 50–100ms.
Ranking — кандидати ранжуються тяжкою моделлю, яка передбачає вероятність взаємодії (like, share, comment, completion rate). Gradient boosted trees (XGBoost, LightGBM), two-tower neural networks або DLRM. Результат — упорядкований список з scores.
Serving — мобільна програма запитує N наступних елементів ленти, отримує їх з передрахованим порядком. Prefetch наступної сторінки до того, як користувач дійде до кінця поточної.
Сигнали ранжування: що збирати у програмі
Якість алгоритму визначається якістю сигналів. Мобільна програма — головний джерело:
Engagement signals:
-
like,share,comment,save— явні сигнали з високою вагою -
video_completion_rate— досмотрів ли користувач відео до кінця, на якому моменті вийшов -
dwell_time— час на картці/посту, але не повний час (може бути відкрита інша програма) -
swipe_away_velocity— швидкий свайп вниз без зупинки це негативний сигнал
Implicit negative signals:
- Користувач промотав мимо за < 0.5 секунди — вероватний skip
- Report / Hide content — сильний негативний сигнал
- App backgrounded одразу після показу контенту
Трекинг video completion на iOS з AVPlayer:
class VideoProgressTracker {
private var timeObserver: Any?
private let player: AVPlayer
private let itemId: String
private var maxProgress: Float = 0
init(player: AVPlayer, itemId: String) {
self.player = player
self.itemId = itemId
setupObserver()
}
private func setupObserver() {
let interval = CMTime(seconds: 0.5, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
timeObserver = player.addPeriodicTimeObserver(forInterval: interval, queue: .main) { [weak self] time in
guard let self,
let duration = self.player.currentItem?.duration.seconds,
duration > 0 else { return }
let progress = Float(time.seconds / duration)
if progress > self.maxProgress {
self.maxProgress = progress
}
}
}
func reportCompletion() {
Analytics.track(.videoProgress(itemId: itemId,
completionRate: maxProgress,
source: .algorithmicFeed))
}
}
На Android — ExoPlayer з AnalyticsListener.onPlaybackStateChanged() та Player.Listener.onPositionDiscontinuity().
Prefetch та нескінченна лента
Користувач не повинен бачити лоадер при скролингу. Стандартний підхід: підгружаємо наступну сторінку, коли користувач дійшов до передостаннього елемента поточної.
// Android, RecyclerView + ViewModel
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
val lastVisible = layoutManager.findLastVisibleItemPosition()
val total = layoutManager.itemCount
if (total - lastVisible <= PREFETCH_THRESHOLD) {
viewModel.loadNextPage()
}
}
})
PREFETCH_THRESHOLD — зазвичай 3–5 елементів. Для відео-ленти збільшуємо до 7–10, тому що завантаження відео довше.
Дедупликація: сервер може вернути один і той же item у двох пагінованих відповідях. Клієнт зберігає Set<String> показаних ID та фільтрує дублі перед додаванням у список.
Контекстні сигнали від пристрою
Час дня, день тижня, тип мережі (WiFi vs cellular), заряд батареї — все це контекст, що поліпшує ранжування. На iOS — CTTelephonyNetworkInfo для типу мережі, UIDevice.current.batteryLevel для заряду. Не потрібно відправляти ці дані з кожним запитом — достатньо при відкритті сесії.
Об'яснимість: чому цей контент
Користувачі хочуть розуміти, чому лента показує те, що показує. Мінімум — тег «Тому що вам сподобалось X» або «Популярно у вашому регіоні». Це і UX, і довіра. Сервер повертає explanation_key разом з контентом, мобільна програма рендерить відповідну мітку.
A/B тестування алгоритму
Нову версію ранжуючої моделі не выпускають одразу на всіх. Типічна схема: 5% трафіку → 20% → 50% → 100%, з моніторингом метрик сесії (retention D1/D7, середній час у програмі, engagement rate) на кожному кроці.
Feature flags управляються через Firebase Remote Config або власну систему. Клієнт передає experiment_variant у кожному запиті до feed API — це дозволяє серверу вибрати потрібний ранкер.
Процес роботи
Аудит поточного трекингу: що вже збирається, наскільки точні дані про перегляди.
Проектування event schema для ленти: completion rate, dwell time, explicit signals.
Розробка клієнтської частини: нескінченний скролинг, prefetch, дедупликація, відео-плеєр з трекингом.
Інтеграція з feed API: постраничная завантаження, обробка помилок, offline fallback (кешована лента).
Реалізація explanation labels та UI-елементів контролю (скрити контент, не цікавить).
Монітор та A/B інфраструктура.
Орієнтири за часовими рамками
Клієнтська частина з правильним трекингом для існуючого feed API — 2–3 тижні. Повна система з серверним ранкером, pipeline навчання та мобільною інтеграцією — 2–3 місяці. Вартість залежить від обсягу контенту, потрібної latency та наявності готової аналітичної інфраструктури.







