Open71

RSSフィードアグリゲーター実装やレコメンドシステムへの拡張を狙ったものの開発記録

Kaikei EzakiKaikei Ezaki

以下は採用技術候補
言語やフレームワークなどを羅列する。
当初はデスクトップアプリを想定していたが、Webアプリのほうが良いかも?

  • フロントエンド

    • TypeScript
    • React
      • emotion
      • Next.js?
    • Svelte
      • Sveltekit
    • Chakra UI
  • バックエンド

    • Go
      • Echo
    • Rust
      • actix-web
      • axum
      • serde
      • tokio
      • anyhow
      • sqlx
    • Kotlin?
      • Ktor
      • Micronaut
    • F#
      • .net Core
  • ML系

    • Julia
  • DB

    • SQLite3
    • MySQL
  • ネイティブApp (超余裕があれば)

    • Kotlin Multi Platform?
    • Flatter / Dart?
    • Swift / Kotlin (超余裕があれば)
  • デザイン

    • Figma
    • FugJam
Kaikei EzakiKaikei Ezaki

Webアプリなら、MySQLを使ったほうが良いのでは。
Docker Composeで動くように構成して、最初は自分を含めた開発者が使えれば良くて、その後クラウドに持っていくとか、ネイティブ側の実装をすれば良い気がしてきた。

Kaikei EzakiKaikei Ezaki

Docker composeの構成が完了した。荒いけど。


docker build' error: "failed to solve with frontend dockerfile.v0

このエラーで詰まった。
原因は単純で、対象のディレクトリに「Dockerfile」という名前のファイルがないことだった。

Kaikei EzakiKaikei Ezaki

RSSフィードの取得を並列化、画像の読み込みの廃止と絵文字での代替、APIサーバーからの転送データ容量の削減を行い、2秒ほどかかっていたトップページへの遷移と描画を、600ms代まで短縮した。


Kaikei EzakiKaikei Ezaki

LightHouseのスコアが悪化したので、パフォーマンスチューニングをする。

前:

Kaikei EzakiKaikei Ezaki

ビルドしたらパフォーマンスが当然良くなった。
Chakura UI, React DOMなどが大きすぎて、Nginxのgzipエンコーディングを有効にしているのに遅いと思ったら、普通に計測する際にはビルドしてポート4173で実行すれば良いだけの話だった。

Kaikei EzakiKaikei Ezaki

並列処理が正しく実装できておらず、意味のないものだったため修正した。

Kaikei EzakiKaikei Ezaki

実世界のプロダクトレベルの品質を意識して、それらの実装を習得することが目的なのが本プロジェクトである。
そのためには、テストファーストでの実装を意識する必要がある。
GItHub Actionsは悪用などをされるとめんどくさいので、とりあえずGoのローカルコミットを行った際にGitのhooksである、pre-commitが機能するようにした。
これを行うことで、Commitする度にGoのユニットテストが実行される。

以下のファイルを「PROJECT_ROOT/.git/hooks/」に設置すると、コミット時に実行されて検証できる。


#!/bin/sh

cd "$(git rev-parse --show-toplevel)"

pwd

cd ./InsightStream/InsightStream/

pwd

export PATH=$PATH:/usr/local/go/bin

go test ./...


Kaikei EzakiKaikei Ezaki

ちょっと時間が空いてしまったが再開。

現在はchatGPTが猛威を振るっていて、その「要約」という能力に非常に惹かれている。
そこで、あらかじめRSSフィードを登録しておき、それらを定期的に更新し、要約、関連記事や関連事項ごとにネットワークを生成するようなアプリを作れないか検討中。

どうやっても深層学習は避けられなさそうなので、その辺りも少しずつ勉強する。

Kaikei EzakiKaikei Ezaki

DDDを用いた設計について勉強する必要あり。
なぜならプロダクションレベルの品質のコードを目指すことが、本プロジェクトの主目標であり、それには複雑なコードを各責務に応じて分割し整理することが必要だから。

Kaikei EzakiKaikei Ezaki

肝となる推薦システムに最近流行りの、というか革新的だと理解しているTransformerをベースとした推薦モデルを作れば、理想に近づくかも?
深層学習とか全くわからない。この記事は参考になりそう。

https://qiita.com/sho_shimazu/items/0d05bb402576485f3a78

Kaikei EzakiKaikei Ezaki

というよりこの方の書かれている記事の第2弾の方がやりたいことへ近づいている気がする。

Kaikei EzakiKaikei Ezaki

やる気出てきたので、再開。
なんかパフォーマンス落ちていたので、改修入れる

Kaikei EzakiKaikei Ezaki

記事を要約しておいて、RDBに格納する機能がほしいので、OSSのLLMを使用したAPIサーバーを立てて、そこへ問い合わせる構成にしてみたい。
かつ、なるべく多くのマシンで動くように、メモリ使用量も少ないモデルを選びたい。

Kaikei EzakiKaikei Ezaki

Rustでごりごりに重い処理をしたいと思ったが、目的と手段が逆転している気がする。
とりあえず、3つずつ各RSSリストで保持している記事リンクを展開して、個別のRSSフィードとして保存するバッチ的なプログラムを実装することにした。

Kaikei EzakiKaikei Ezaki

現状GoのAPIサーバーから、RustのサービスへRSSフィードリストをフィード単体への分割変換保存が可能になったが、フィードのテーブルに異なるカテゴリーで同URLのRSSフィードが入ってきた場合には、それらは別個のものとしてみなされる。そのため、内容はほぼ同一のRSSフィードが複数存在することになる。
今後はRust側のサービスで、同一のURLをUpsertなどで弾く処理をする処理を実装する。

Kaikei EzakiKaikei Ezaki

フィードへ分割した際に増殖するバグがあった。MySQLでUpsertに近いことをするには以下の、「 ON DUPLICATE KEY UPDATE」を用いることで可能らしい。

let _row = sqlx::query(
                "INSERT INTO feeds (id, site_url, title, description, feed_url, language, favorites, dt_created, dt_updated)
                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
                    ON DUPLICATE KEY UPDATE feed_url = ?;",   
            )

https://gihyo.jp/article/2023/02/mysql-rcn0191

Kaikei EzakiKaikei Ezaki

既存のサイトURLでGROUP_BYして、JSONのタイトルリストにマージするクエリ。

SELECT site_url, JSON_ARRAYAGG(title) as titles
FROM feeds
GROUP BY site_url;
Kaikei EzakiKaikei Ezaki

取得したフィードをタイムライン表示して、無限スクロールできるようにしたが、同じフィードが表示されることがある。
たとえば、以下の条件の場合
・Zennで「iOS」を購読している
・Zennで「Swift」を購読している
上記の場合には、非常に分野が近しい、というより単一の記事に両方のタグがつくことがある。そのため、前述の同じフィードが複数回表示される現象が発生する。

そこで、ただの雑なアイデアだが、将来的にはフィードの属性をタグ付けして、重複を弾いて表示できるようにしたい。

Kaikei EzakiKaikei Ezaki

とりあえず、同一URLのものを弾くような単純な実装で対処した。

Kaikei EzakiKaikei Ezaki

MySQLのoffset句について誤解していたので、直した。
LIMITはそのままでOFFSETの数を増やすことで、例えば下記クエリなら、「41〜80」番目の取得ができる。

SELECT * FROM feeds
ORDER BY dt_updated DESC, feed_url DESC
LIMIT 40
OFFSET 80;
Kaikei EzakiKaikei Ezaki

タイムラインの取得はフィードが常に新しいものから取得するから、以下のインデックスを貼ることで、コストを低減させた。

3300レコードへの実行に対して、365.0であったクエリのTotal Costが、2.37まで下がった。
インデックスを貼ることは慎重に行わなければならないが、最新のフィードを取得することはタイムライン表示では必須であり、また今後も必要とされる可能性があるので、この決定にした。

CREATE INDEX idx_feeds_dt_updated_feed_url ON feeds (dt_updated DESC, feed_url DESC);
Kaikei EzakiKaikei Ezaki

過去90日更新のなかったRSSフィードの更新を停止する機能を実装した。
アカウント機能とRSSフィードのフォロワー管理機能実装のために、早急に認証機能を実装する必要があるが、重い腰が上がらない。

Kaikei EzakiKaikei Ezaki

Goのtestとそのカバレッジ出力を1行で行うコマンド

go test ./... -coverprofile=coverage.out && go tool cover -html=coverage.out

Kaikei EzakiKaikei Ezaki
  • .envでの環境変数の取り扱いをやめたい
  • ESなどの検索機能の強化をしたい
  • LLMを利用した、記事の要約と、加工されたデータを保持し、活用する機能を実装したい
  • ユーザー認証機能を実装したい
  • それによって、フィードの各機能を実装したい
    • お気に入り機能
    • ブックマーク機能
    • 人気フィードの管理
Kaikei EzakiKaikei Ezaki

再掲。作業が滞っていたので。

  • .envでの環境変数の取り扱いをやめたい
  • ESなどの検索機能の強化をしたい
  • LLMを利用した、記事の要約と、加工されたデータを保持し、活用する機能を実装したい
  • ユーザー認証機能を実装したい
  • それによって、フィードの各機能を実装したい
    • お気に入り機能
    • ブックマーク機能
    • 人気フィードの管理