Більшість пошукових вікон існують, щоб відповісти на одне питання: що відповідає словам, які я ввів? Це правильне питання для бібліотечного каталогу. Це неправильне питання для додатка про харчування.
Коли користувач відкриває сторінку рецептів в Appetec, слова, які він вводить, є найменш цікавою частиною запиту. Що справді важливо, це невидиме: скільки калорій залишилося сьогодні, що вони готували тиждень за тижнем, не замислюючись про це, чи є вони веганами, і чи вони вже перевищили свій бюджет до вечері. Пошукова система, яка ігнорує все це, із задоволенням поверне відповідні рецепти — і так само із задоволенням саботуватиме причину, чому користувач відкрив додаток. Це архітектура, що стоїть за шаблоном, створеним для усунення цього розриву: релевантність, яка є правильною для цієї людини, прямо зараз.
Чому лише текстової відповідності недостатньо
Цей шаблон застосовується щоразу, коли той самий запит має повертати різні результати для різних користувачів — коли "найкращий" залежить від контексту, який користувач ніколи не вводив, і коли ваш власний каталог менший, ніж каталог, на який очікують ваші користувачі. Персоналізований пошук рецептів розглядає релевантність як поєднання трьох сигналів замість одного:
| Сигнал | Що він фіксує |
|---|---|
| Текст | Що ввів користувач — або, в режимі сканування страви, що було виявлено на його тарілці |
| Відповідність цілі | Наскільки добре рецепт відповідає калоріям, що залишилися у користувача на цей прийом їжі, сьогодні |
| Звичка | Як часто цей конкретний користувач насправді їсть цей рецепт |
Наївний двигун бачить лише перший рядок цієї таблиці. Цей шаблон поєднує всі три в єдиний ранжований список, а потім заповнює з зовнішнього джерела щоразу, коли локальний каталог стає тонким — тихо розширюючи цей каталог кожного разу, коли це відбувається. Результат менше схожий на запит до бази даних, а більше на друга, який вже знає вашу кухню та вашу дієту.
Як організовано конвеєр
Система працює як конвеєр вилучення з шаром персоналізації попереду та самовідновлюваним каталогом позаду.
Дві пошукові системи, один контракт. Elasticsearch є основною: нечітка толерантність до помилок, англійський стеммінг ("grill" знаходить "grilled"), зважене ранжування релевантності. Якщо вона коли-небудь недоступна, той самий запит переходить до пошуку в MongoDB, який дотримується тих самих фільтрів і правил персоналізації. Пошук погіршується при збої; він ніколи не зникає.
Шар, який користувачі відчувають, але ніколи не бачать. Перш ніж результати повернуться, три речі змінюють список. Бюджет калорій розраховується з денної цілі мінус те, що було зареєстровано, збільшуючи рецепти, які відповідають тому, що залишилося — і якщо користувач перевищив свою ціль, список тихо пересортовується за принципом "найменше калорій першими", спонукаючи до відновлення замість поблажливості. Історія харчування зводить журнали прийомів їжі за останні 60 днів у карту "що ви насправді їсте", зберігаючи улюблені страви в один дотик на першій сторінці. Дієтичні уподобання — veg, vegan, non-veg, а також два десятки більш точних тегів, таких як keto, gluten-free та high-protein — фільтрують та переранжовують усе нижче.
Каталог, що самостійно зростає. Коли локальні результати вичерпуються, конвеєр звертається до зовнішнього джерела, FatSecret, вбудовуючи ці результати в той самий безкінечний скролл-фід без видимого шва. Потім він записує ці зовнішні рецепти назад у локальний каталог, використовуючи LLM для класифікації кожного як вегетаріанського, не-вегетаріанського або веганського, щоб наступного разу його можна було правильно персоналізувати — той самий інстинкт, що стоїть за гібридною архітектурою вилучення, яку ми застосовували в інших місцях, де локальні та зовнішні результати повинні сприйматися як єдина система.
Три кеші, три завдання. Об'єднані набори результатів, карта звичок для кожного користувача та відповіді зовнішніх API мають свій власний незалежний термін служби, тому персоналізований запит з кількох джерел все ще повертається приблизно зі швидкістю одного простого пошуку.
Компроміси, які варто назвати
Нічого з цього не далося безкоштовно, і чесність щодо витрат є частиною того, що робить цей шаблон надійним, а не просто розумним.
Релевантність була навмисно зроблена не-нейтральною. Чисто текстове ранжування за релевантністю є "справедливим" у спосіб, який тут активно некорисний. Результати навмисно упереджені до відповідності цілі та звичок, тому що рецепт, який є трохи менш текстово-досконалим, але відповідає бюджету та смаку людини, є кращою відповіддю. Питання "Чому це зайняло перше місце?" стає продуктовим рішенням, а не типовим налаштуванням пошукової системи — ось чому ці ваги знаходяться в коді програми, а не в некерованому файлі конфігурації.
Володіння всім каталогом не було метою — метою було володіння правильною його частиною. Розглянуті крайнощі були повністю курований каталог (висока якість, вузька широта) або повний проксі до зовнішнього API (необмежена широта, нульовий контроль, нульова персоналізація). Система ділить різницю: спочатку пропонує власні та створені користувачами рецепти, заповнює з зовнішніх джерел за потреби та поглинає те, що заповнюється — поступово, з часом, прямуючи до володіння саме тим, що користувачі насправді шукають.
Стійкість була обрана над піковою ефективністю. Дві повноцінні пошукові системи коштують дорожче в експлуатації, ніж одна — це та сама філософія стійкості насамперед, що стоїть за більшістю наших рішень щодо бекенду. Ця вартість була прийнята свідомо: пошук рецептів є основною функціональністю щоденного використання, де погіршений пошук є незначною незручністю, але відсутність пошуку — це непрацюючий додаток.
Персоналізація коштує передбачуваності. Результати дійсно відрізняються залежно від часу доби, спожитих калорій та історії — навіть для одного й того ж користувача на сніданок проти вечері. Це задумано, але це підвищує планку для тестування та підтримки: "працює на моєму телефоні" перестає багато значити, коли правильність визначається для кожного користувача, для кожного моменту.
Коли цей шаблон підходить — і коли ні
Варто чітко зазначити, де цей шаблон не слід використовувати. Його складність виправдовується, коли результати повинні справді відрізнятися для кожного користувача на основі контексту, який вони ніколи не вводили, коли власний каталог менший, ніж очікують користувачі, але може бути збагачений ззовні, коли функція є достатньо частою, щоб плавна деградація була важливішою за мінімальну інфраструктуру, і коли "релевантність" законно включає сигнали, окрім тексту.
Це неправильний інструмент, коли результати ідентичні для кожного користувача — пошук публічних документів, пошук SKU — де персоналізація додає витрати без вигоди. Це непотрібно, коли каталог достатньо малий і стабільний, щоб одного двигуна з простим фільтруванням було достатньо, і це працює проти вас скрізь, де суворе, ідентичне, повністю пояснюване ранжування є жорсткою вимогою.
Справжнє завдання
Якість вилучення тут розглядається як проблема персоналізації, а не просто проблема відповідності. Пошук рецептів, який є текстово ідеальним, ігноруючи те, що користувач веган, вже перевищив бюджет і готував одні й ті ж п'ять вечерь весь місяць, технічно спрацював, але практично провалився. Завдання ніколи не полягало в тому, щоб знайти рецепти, які відповідають запиту — воно полягає в тому, щоб показати рецепт, який ця людина повинна приготувати далі. Кожен шар цієї архітектури, від подвійних пошукових систем до тихого пересортування, коли хтось перевищив бюджет, існує, щоб закрити цей розрив.
Якщо ви обмірковуєте схоже рішення "будувати чи поєднувати" для пошуку або вилучення у своєму власному продукті, ми з радістю обговоримо це.
Інші блоги
1. Масштабування цифрової медичної платформи за допомогою мікросервісної архітектури
2. Синхронізація Apple Health та Health Connect
3. Оптимізація логотипу каналу для різних роздільних здатностей відео

