Dev의 관련성 피드 빌더로 다이빙

33657 단어 railsmetarubypostgres

구성별 SQL 구성



2021년 11월에 alternate mechanism for building the relevancy feedForem code base의 다른 인스턴스를 추가했습니다. 약간의 구성 후에 관련성 피드를 생성하는 기본 수단이 되었습니다.

그때부터 지금까지 우리는 관련성 피드의 "레버"를 실험하여 더 관련성 높은 피드 경험을 제공하려고 했습니다.

4월 21일에 significant refinement to how we build the relevancy feed을(를) 병합했습니다. 설계상 이 새로운 메커니즘은 이전 메커니즘과 동일한 쿼리를 생성합니다.

이 새로운 접근 방식을 통해 관련성 피드를 보다 쉽게 ​​실험할 수 있습니다. 이러한 실험 결과를 캡처, 기록, 분석하고 주석을 추가합니다.

그리고 가장 좋은 점은 이것이 오픈 소스이기 때문에 우리가 실행 중인 실험과 이러한 레버를 구성하는 방법을 볼 수 있다는 것입니다.

데이터 모델링 Forem의 피드 빌더



아래는 이 모든 것을 데이터베이스에 넣었을 때 사용할 ERD의 대략적인 스케치입니다.

이러한 개념 테이블의 목표는 사용자의 기본 설정을 기반으로 각 기사의 관련성 점수를 계산하는 SQL을 구성하고 생성하는 수단을 제공하는 것입니다. 그런 다음 사용자에게 가장 관련성이 높은 기사를 반환합니다.



ERD의 PlantUML 텍스트 버전


@startuml
!theme amiga
entity experiment {
* id
--
* label
* start date
* end date
}

entity variant_experiment {
* experiment_id
--
* variant_id
* probability of using this variant
}

entity variant {
* id
--
* label
* order_by_lever_id
}

entity variant_relevancy_lever {
* variant_id
* relevancy_lever_id
--
* config (e.g. relevancy score range)
}

entity order_by_lever {
* id
--
* order_by_fragment
}

entity relevancy_lever {
* id
--
* label
* user_required
* select_fragment
* joins_fragments
* group_by_fragment
}

experiment ||–|{ variant_experiment
variant ||–o{ variant_experiment
relevancy_lever ||–o{ variant_relevancy_lever
variant ||–o{ variant_relevancy_lever
order_by_lever ||–o{ variant
@enduml



다음과 같은 몇 가지 이유로 데이터베이스에 저장하지 않습니다.
  • 데이터베이스가 더 불투명합니다.
  • 데이터베이스는 Forem 설치 간 동기화와 관련하여 더 많은 고려가 필요합니다.

  • 대신 위의 정보를 응용 프로그램에 넣습니다. 이를 통해 각 실험과 변형을 테스트할 수 있습니다.

    또한 관련성 피드를 구축하는 방법이 투명하다는 의미이기도 합니다. 위의 다이어그램에서 구축하면 6개의 개념적 엔터티가 있습니다.
  • experiments
  • experiment_variants
  • variants
  • order_by_levers
  • variant_relevancy_levers
  • relevancy_levers
  • experimentsexperiment_variants config/field_test.yml 에 정의되어 있습니다. variants config/feed-variants 에 정의되어 있습니다. 이름이 지정된 각 변형은 config/feed-variants 디렉토리에 해당 JSON 파일이 있습니다. config/field-variants 디렉토리의 각 변형은 사용된 variant_relevancy_leversorder_by_lever를 정의합니다. 예제는 config/feed-variants/original.json 을 참조하십시오. relevancy_leversorder_by_levers Articles::Feeds module에 정의되어 있습니다.

    Forem의 관련성 피드 조합



    다음은 관련성 피드를 구축하는 방법에 대한 개념적 시퀀스 다이어그램입니다.



    개념적 피드 쿼리 빌더 시퀀스의 PlantUML 텍스트 버전


    @startuml
    !theme amiga
    participant “GET relevancy feed” as GetRequest
    participant “AbExperiment.get” as AbExperiment
    participant “Articles::Feeds::\nVariantQuery.build_for” as VariantQuery
    participant “Articles::Feeds::\nVariantAssembler.call” as Assembler
    participant “/config/feed-variants/*.json” as VariantConfig
    participant “Articles::Feeds\n.lever_catalog” as LeverCatalog
    GetRequest –> AbExperiment : with :user
    GetRequest <– AbExperiment : :variant
    GetRequest –> VariantQuery : with :user, :variant
    VariantQuery –> Assembler : with :variant
    Assembler –> VariantConfig : with :variant
    Assembler <– VariantConfig : :variant_config
    Assembler –> LeverCatalog : with :variant_config
    Assembler <– LeverCatalog : :query_config
    VariantQuery <– Assembler : :query_config
    VariantQuery –> Article : with :user, :query_config
    GetRequest <– Article : Article::ActiveRecord::Relation
    @enduml
    



    홈페이지에 방문하면 무작위로 할당된 변형을 검색합니다. 해당 변형을 사용하여 쿼리 구성을 조합한 다음 쿼리를 수행하여 관련성 피드에 표시되는 기사를 반환합니다.

    이미 SQL 표시



    아래는 2022-04-15 incumbent feed variant 의 평가된 SQL입니다. 그리고 궁금한 분들을 위해 "챌린저"가 4월 25일에 전투에 참가합니다. 병합하면 Create new feed-variant 20220422 . inline documentation on configuring the VariantQuery 을(를) 체크아웃할 수도 있습니다.

    그리고 이제 SQL의 벽…

    PosgreSQL Select 문


    SELECT
      "articles"."path",
      "articles"."title",
      "articles"."id",
      "articles"."published",
      "articles"."comments_count",
      "articles"."public_reactions_count",
      "articles"."cached_tag_list",
      "articles"."main_image",
      "articles"."main_image_background_hex_color",
      "articles"."updated_at",
      "articles"."slug",
      "articles"."video",
      "articles"."user_id",
      "articles"."organization_id",
      "articles"."video_source_url",
      "articles"."video_code",
      "articles"."video_thumbnail_url",
      "articles"."video_closed_caption_track_url",
      "articles"."experience_level_rating",
      "articles"."experience_level_rating_distribution",
      "articles"."cached_user",
      "articles"."cached_organization",
      "articles"."published_at",
      "articles"."crossposted_at",
      "articles"."description",
      "articles"."reading_time",
      "articles"."video_duration_in_seconds",
      "articles"."last_comment_at"
    FROM
      "articles"
      INNER JOIN (
        SELECT
          articles.id,
          (
        (
          CASE (
            current_date - articles.published_at :: date
          ) WHEN 0 THEN 1.0 WHEN 1 THEN 0.99 WHEN 2 THEN 0.985 WHEN 3 THEN 0.98 WHEN 4 THEN 0.975 WHEN 5 THEN 0.97 WHEN 6 THEN 0.965 WHEN 7 THEN 0.96 WHEN 8 THEN 0.955 WHEN 9 THEN 0.95 WHEN 10 THEN 0.945 WHEN 11 THEN 0.94 WHEN 12 THEN 0.935 WHEN 13 THEN 0.93 WHEN 14 THEN 0.925 ELSE 0.9 END
        ) * (
          CASE COUNT(comments_by_followed.id) WHEN 0 THEN 0.95 WHEN 1 THEN 0.98 WHEN 2 THEN 0.99 ELSE 0.93 END
        ) * (
          CASE articles.comments_count WHEN 0 THEN 0.8 WHEN 1 THEN 0.82 WHEN 2 THEN 0.84 WHEN 3 THEN 0.86 WHEN 4 THEN 0.88 WHEN 5 THEN 0.9 WHEN 6 THEN 0.92 WHEN 7 THEN 0.94 WHEN 8 THEN 0.96 WHEN 9 THEN 0.98 ELSE 1.0 END
        ) * (
          CASE (
            CASE articles.featured WHEN true THEN 1 ELSE 0 END
          ) WHEN 1 THEN 1.0 ELSE 0.85 END
        ) * (
          CASE COUNT(followed_user.follower_id) WHEN 0 THEN 0.8 WHEN 1 THEN 1.0 ELSE 1.0 END
        ) * (
          CASE COUNT(followed_org.follower_id) WHEN 0 THEN 0.95 WHEN 1 THEN 1.0 ELSE 1.0 END
        ) * (
          CASE (
            current_date - MAX(comments.created_at):: date
          ) WHEN 0 THEN 1.0 WHEN 1 THEN 0.9988 ELSE 0.988 END
        ) * (
          CASE LEAST(
            10.0,
            SUM(followed_tags.points)
          ):: integer WHEN 0 THEN 0.7 WHEN 1 THEN 0.7303 WHEN 2 THEN 0.7606 WHEN 3 THEN 0.7909 WHEN 4 THEN 0.8212 WHEN 5 THEN 0.8515 WHEN 6 THEN 0.8818 WHEN 7 THEN 0.9121 WHEN 8 THEN 0.9424 WHEN 9 THEN 0.9727 ELSE 1.0 END
        ) * (
          CASE (
            CASE WHEN articles.privileged_users_reaction_points_sum < -10 THEN -1 WHEN articles.privileged_users_reaction_points_sum > 10 THEN 1 ELSE 0 END
          ) WHEN -1 THEN 0.2 WHEN 1 THEN 1.0 ELSE 0.95 END
        ) * (
          CASE articles.public_reactions_count WHEN 0 THEN 0.9988 WHEN 1 THEN 0.9988 WHEN 2 THEN 0.9988 WHEN 3 THEN 0.9988 ELSE 1.0 END
        )
          ) as relevancy_score
        FROM
          articles
          LEFT OUTER JOIN user_blocks ON user_blocks.blocked_id = articles.user_id
          AND user_blocks.blocked_id IS NULL
          AND user_blocks.blocker_id = : user_id
          LEFT OUTER JOIN follows AS followed_user ON articles.user_id = followed_user.followable_id
          AND followed_user.followable_type = 'User'
          AND followed_user.follower_id = : user_id
          AND followed_user.follower_type = 'User'
          LEFT OUTER JOIN comments AS comments_by_followed ON comments_by_followed.commentable_id = articles.id
          AND comments_by_followed.commentable_type = 'Article'
          AND followed_user.followable_id = comments_by_followed.user_id
          AND followed_user.followable_type = 'User'
          AND comments_by_followed.deleted = false
          AND comments_by_followed.created_at > '2022-04-08 12:22:04.501182'
          LEFT OUTER JOIN follows AS followed_org ON articles.organization_id = followed_org.followable_id
          AND followed_org.followable_type = 'Organization'
          AND followed_org.follower_id = : user_id
          AND followed_org.follower_type = 'User'
          LEFT OUTER JOIN comments ON comments.commentable_id = articles.id
          AND comments.commentable_type = 'Article'
          AND comments.deleted = false
          AND comments.created_at > '2022-04-08 12:22:04.501182'
          LEFT OUTER JOIN taggings ON taggings.taggable_id = articles.id
          AND taggable_type = 'Article'
          INNER JOIN tags ON taggings.tag_id = tags.id
          LEFT OUTER JOIN follows AS followed_tags ON tags.id = followed_tags.followable_id
          AND followed_tags.followable_type = 'ActsAsTaggableOn::Tag'
          AND followed_tags.follower_type = 'User'
          AND followed_tags.follower_id = : user_id
          AND followed_tags.explicit_points >= 0
        WHERE
          articles.published = true
          AND articles.published_at > '2022-04-08 12:22:04.501182'
          AND articles.published_at < '2022-04-23 12:22:04.501592'
        GROUP BY
          articles.id,
          articles.published_at,
          articles.comments_count,
          articles.featured,
          articles.privileged_users_reaction_points_sum,
          articles.public_reactions_count
        ORDER BY
          relevancy_score DESC,
          articles.published_at DESC
        LIMIT
          50
      ) AS article_relevancies ON articles.id = article_relevancies.id
    ORDER BY
      article_relevancies.relevancy_score DESC,
      articles.published_at DESC
    



    Rspec tests of each of the variant configs 제품군에 다음을 추가하여 위 쿼리를 생성했습니다.

    File.open(Rails.root.join("tmp/#{variant}.sql").to_s, "w+") do |file|
      file.puts query_call.to_sql
    end
    
    


    또한 테스트 특정 사용자 ID를 제거하고 :user_id로 바꾸고 SQL을 정리했습니다.

    좋은 웹페이지 즐겨찾기