meatballs.live 〜 ремиксирование опыта Hacker News с помощью Redis Stack — часть 3

Добро пожаловать в третью часть этой серии!

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


РЕДАКТИРОВАНИЕ: С тех пор я перевел сервисы приема и генерации с Vercel ($20/мес) на Railway ($5-10/мес), чтобы снизить расходы. Обновленное после хакатона руководство по установке находится в процессе разработки.

  • JOBS SERVER services-migration branch
  • репо SERVICES SERVER

Теперь, когда прием заявок на участие в хакатоне DEV x Redis завершен и началось судейство, я завершу(?) эту серию статей своего рода вскрытием, сравнивая коллекции meatballs.live (MB) с главной страницей Hacker News (HN).

Передовая страница Hacker News все еще меняется, и ранговые позиции, указанные в следующей таблице, могут быть другими после даты публикации. Я буду редактировать по мере необходимости.

EDIT: Таблица обновлена сравнением оригинального ранга и обновленного ранга.

MB HN Звание Создано (CT)
1 57/58 Наблюдения из нашего опыта Joe Rogan Experience 6:41 AM
2 65/66 Telegram спрашивает немецких пользователей, когда делиться информацией с правоохранительными органами 5:51 AM
3 80/81 Covid «Fudge Factor» — карта коррупции данных Covid и подход, который сработал 7:13 AM
4 79/80 Трекеры проблем, считающиеся вредными 3:13 AM
5 5/16 Почему WebMD такой ужасный? 8:03 AM
6 85/85 Спросите HN: Должен ли я сообщать инвесторам, что работодатель моего стартапа — мошенник? 3:33 AM
7 60/61 Использование вашего телефона в качестве платформы для разработки программного обеспечения 3:15 AM
8 42/43 Спросите HN: Как вы получаете подработки? 6:19 AM
9 28/35 Дистанционно управляемый газонный трактор 7:08 AM

На момент публикации только одна история из коллекции meatballs.live, созданной за 30 августа, находится в топ-9 на Hacker News.

EDIT: Обновленный топ-9 от Hacker News. Алгоритм meatballs.live предвзято относится к историям, опубликованным в начале дня, поскольку процессор для генерации коллекций ограничен 24-часовым диапазоном (00:00-23:59). Имеет смысл расширить этот диапазон еще на 12 или около того часов с потолка. К счастью, данные уже есть, так что это просто вопрос повторной генерации прошлых дат.

Наши алгоритмы совершенно разные.

Для контекста, вот проницательный пост прошлого года о том, как ранжируется Hacker News.

HN: rankingScore = pow(upvotes, 0.8) / pow(ageHours + 2, 1.8).

С другой стороны, meatballs.live измеряет производительность временного ряда, делая упор на комментарии, а не на рейтинг. Давайте рассмотрим, как достигаются эти результаты.

Алгоритм генерации коллекций

Через конечную точку /api/services/generate/new-collections/ можно сгенерировать коллекции за предыдущий день (или за любой прошлый день с данными о входе) в 00:00 UTC. API требует только параметр dateKey в формате YYYY:M:D, например, 2022:8:30. Затем выполняется processNewCollections.

Что происходит дальше?

  • Возвращается success: false, если запрошенный dateKey находится раньше значения переменной окружения MEATBALLS_COLLECTIONS_START_DATE_KEY или позже вчерашнего дня.
  • Возвращает success: false, если данные коллекции уже существуют для запрошенного dateKey.
export const getCollectionsByDate = async ({
  repository,
  date: { year, month, day }
}: {
  repository: Repository<Collection>
  date: CollectionDate
}) =>
  await repository
    .search()
    .where('year')
    .eq(year)
    .and('month')
    .eq(month)
    .and('day')
    .eq(day)
    .sortBy('position')
    .return.all()
Вход в полноэкранный режим Выйти из полноэкранного режима
  • Возврат success: false, если данные временного ряда не найдены для запрашиваемого dateKey
await redisClient.ts.mRange(
  startOfRequestedDayInMilliseconds,
  endOfRequestedDayInMilliseconds,
  ['type=weighted', 'compacted=day'],
  {
    GROUPBY: { label: 'story', reducer: TimeSeriesReducers.MAXIMUM }
  }
)
Вход в полноэкранный режим Выйти из полноэкранного режима
  • Сортировка по наибольшему значению выборки, по убыванию и возвращает первые 20; это значение вычисляется и сохраняется во временном ряду истории через getStoryActivityTimeSeriesSampleValue в активности истории
  • Возвращает success: false, если конкретные данные истории не найдены в графике
const findStoriesTransaction = redisClient.multi()

// prepare transaction calls
timeSeriesWithSamples.map((series) => {
  const storyId = series.key.replace('story=', `${DATA_SOURCE.HN}:`)

  findStoriesTransaction.graph.query(
    `${MEATBALLS_DB_KEY.GRAPH}`,
    `
    MATCH (s:Story)
    WHERE s.name = "${storyId}"
    return s.name, s.score, s.comment_total, s.created
    `
  )
})

const foundStories = await findStoriesTransaction.exec()
Вход в полноэкранный режим Выйти из полноэкранного режима
  • Ранжирование найденных историй, выводя комментарии на самый верх и возвращая первые 9 из них
  • Создавайте коллекции; находите рекомендации по историям, комментариям и изображениям обложек.

Подробнее о рекомендациях после перерыва.

  • Сохранение и кэширование страницы каждой коллекции
  • Кэширование страницы коллекций
  • Возврат success: true

Подробнее о рекомендациях

Моя любимая функция meatballs.live — это рекомендации по комментариям к коллекциям и историям.

Когда вы открываете коллекцию, вас приветствуют максимум 5 комментариев, а справа — блок из максимум 5 рекомендаций, основанных на названии истории. Для Observations from our Joe Rogan Experience рекомендации выглядят следующим образом:

  • Марк Цукерберг на Joe Rogan Experience
  • Спросите HN: Что вы думаете об интервью Цука с Joe Rogan Experience?
  • Спросите HN: Дизайн опыта для AR. Кто делает интересную работу?
  • AlphaCX Lifetime Deal — лучшая платформа для унифицированного клиентского опыта 2022 года
  • Об опыте разработчиков Ethereum

Процесс получения рекомендаций в настоящее время прост, но достаточно эффективен на данном этапе:

const queryTitle = storyContent.title
  ? removeSpecialCharacters(storyContent.title).replace(/ /g, '|')
  : undefined

const recommendedStories: { id: string; title: string }[] = []

const foundDocuments = (
      await redisClient.ft.search(`Story:index`, queryTitle, {
      LIMIT: { from: 0, size: 5 }
    })
  ).documents,
  docTitles = foundDocuments.map(({ value }) => value.title)

  foundDocuments
    .filter(
      ({ id, value }, index) =>
        value.title &&
        !docTitles.includes(value.title, index + 1) &&
        id.replace('Story:', '') !== story.id &&
        value.title !== storyContent.title
      )
      .map(({ id, value }) => {
        if (value.title)
          recommendedStories.push({
            id: id.replace('Story:hn:', ''),
            title: value.title as string
          })
        })
Войдите в полноэкранный режим Выйти из полноэкранного режима

Поиск рекомендованных комментариев более сложен:

redisClient.graph.query(
  `${MEATBALLS_DB_KEY.GRAPH}`,
  `
  MATCH (:Story { name: "${story.id}" })-[:PROVOKED]->(topComment)<-[:REACTION_TO*1..]-(childComment)
  WITH topComment, collect(childComment) as childComments
  RETURN topComment.name, topComment.created, SIZE(childComments)
  ORDER BY SIZE(childComments) DESC LIMIT 5
  `
)
Войти в полноэкранный режим Выход из полноэкранного режима

Здесь запрос находит максимум 5 topComment от корня, основываясь на активности и глубине реакций с помощью функции Cypher’s collect aggregation.

Является ли этот комментарий верхнего уровня просто одноразовым или он вызвал оживленную беседу?

Визуальное представление части запроса MATCH будет выглядеть примерно так; выполняется в RedisInsight:

GRAPH.QUERY _meatballs 'MATCH graph=(:Story { name: "hn:32567147" })-[:PROVOKED]->(topComment)<-[:REACTION_TO*1..]-(childComment)
  RETURN graph'
Войти в полноэкранный режим Выход из полноэкранного режима

Первые мысли по поводу улучшений?

Мне интересны ваши отзывы. Вот некоторые из моих ближайших мыслей:

  1. Взвешивание по времени, чтобы лучшие результаты не создавались чаще всего утром, т.е. вместо агрегирования по всему дню, просматривайте выборки, основанные на переменных временных диапазонах в течение дня, а затем сравнивайте.
  2. Поддержка параметра перезаписи в API new-collections.
  3. Удаление частично созданных данных коллекции, если processNewCollections завершится неудачей до завершения усложнения
  4. В дополнение к названиям, поиск комментариев и текста о пользователе как часть выбора рекомендуемых историй

И это все для этой записи!

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

Спасибо за чтение и удачи всем участникам! 😎


Оцените статью
devanswers.ru
Добавить комментарий