Open74

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を利用した、記事の要約と、加工されたデータを保持し、活用する機能を実装したい
  • ユーザー認証機能を実装したい
  • それによって、フィードの各機能を実装したい
    • お気に入り機能
    • ブックマーク機能
    • 人気フィードの管理
Kaikei EzakiKaikei Ezaki

gRPC FederationによるBFFの責務を削減する試みが気になっている

Kaikei EzakiKaikei Ezaki

ローカルLLMと自作のRSSフィード取集ツールを活用して、毎朝や日次での要約やトピックまとめをしてくれるアプリ作れると思った。
Kotlinが初めてレベルの状態で、書いていて楽しかったのでAPIサーバーはKotlinとKtorで作りたい。

Kaikei EzakiKaikei Ezaki

自分で使っていて欲しいと思った機能。
全て、/mobileパス配下の機能になる。

  • フィルター機能
  • フォローリスト管理機能