Tech記事の検索とレコメンデーションが可能なサービスを個人開発しました！

この度、Tech記事の検索とレコメンデーションが可能なサービス「[HottyTech検索](https://tech.hottydb.com/)」を個人開発しました！

https://tech.hottydb.com/index.html

「[HottyTech検索](https://tech.hottydb.com/)」は、HottyDBという次世代型RDBMSを利用して実装しています。
HottyDBを利用することで、全文検索、機械学習ランキング（ランキング学習）、レコメンデーションの機能を簡単に実装することができるので、本記事ではその方法を解説していきたいと思います。

# HottyDBとは？
まずはじめに「HottyTech検索」が利用している次世代型RDBMS＝「**[HottyDB](https://www.hottydb.com/)**」について簡単に紹介したいと思います。

[HottyDB](https://www.hottydb.com/)は、検索エンジンとレコメンドエンジンの機能を搭載したRDBMSです。 SQLを用いた通常のデータ操作に加え、文書の全文検索や検索結果の機械学習ランキング（MLR）・アイテムレコメンデーションのロジックを全て1つのソフトウェアで担います。 1つのソフトウェアでこれらを実現することで、面倒なデータ伝送システム（ETL）の構築を一切不要にし、利用者は機械学習などの知識がなくてもSQL LIKEな命令だけでこれらを実現することができます。
![HottyDB](https://www.hottydb.com/hottydb-system.svg)

HottyDBについて詳しく解説した記事もありますので、是非参考にしてみてください。

https://qiita.com/toru1055/items/0fe400989373823e9c78


# HottyTech検索とは？
「[HottyTech検索](https://tech.hottydb.com/)」は、HottyDBを利用して作られたTech記事の検索とレコメンデーションのサービスです。

https://tech.hottydb.com/index.html

Tech記事といっても現状はQiitaのAPIで取得できた記事に限られますが、その記事の中から興味のある記事を検索することができるサービスです。

「HottyTech検索」の主な機能は次の4つです。

## 主な機能
1. キーワード検索機能（全文検索＋機械学習ランキング）
2. 関連記事レコメンデーション機能（協調フィルタリング）
3. 関連記事レコメンデーション機能（類似記事検索）
4. 「あなたにおすすめの記事」機能

本記事では、これらの機能をHottyDBを使って簡単に実装する方法を解説したいと思います！

## 技術スタック
「HottyTech検索」で利用している技術スタックは以下の通りです。

| 項目 | 利用技術 |
| --- | --- |
| フロントエンド |  React |
| バックエンド（API） | Spring Boot |
| データベース | HottyDB |
| 検索エンジン | HottyDB |
| 機械学習システム | HottyDB |
| レコメンドエンジン | HottyDB |
| ETLシステム | **なし**（データは全てHottyDBに入ってるのでETLが不要！） |

ご覧のとおり、ほとんどがHottyDBです！！

## 動作環境
続いて現状「HottyTech検索」が動作している実行環境を紹介します。

| 項目 | 値 |
| --- | --- |
| サービス | [KAGOYA CLOUD VPS](https://www.kagoya.jp/cloud/vps/) |
| インスタンスタイプ | スタンダード | 
| CPU | 1コア |
| メモリ | 1 GB |
| 料金 | 550円 / 月（2022年9月6日現在） |

この最小限スペックのインスタンス1つでSpring Bootとデータベース（HottyDB）の両方を動かしています。
なので、多少速度が遅くとも許してください＞＜；
（裏側でQiitaAPIを叩いてDB登録する処理が5秒おきに動いているので、その処理と被ると遅くなる可能性が高いです。）

# 各機能の解説
## 0. 前提
### 記事テーブルのスキーマ
各機能の解説をする前に、前提となる記事テーブル（`article`）を紹介します。

```
CREATE TABLE article (
  id INT,     -- PK
  qid VARCHAR(64), -- Qiita上での記事ID
  title VARCHAR(256), -- タイトル
  tags VARCHAR(256), -- タグ
  likes_count INT, -- いいね数
  url VARCHAR(1024), -- URL
  user_id VARCHAR(64), -- 投稿者ID 
  user_items_count INT, -- 投稿者の投稿記事数
  user_followers_count INT, -- 投稿者のフォロワー数
  description VARCHAR(1024), -- 記事の詳細
  contents VARCHAR(1024), -- 検索用文書（title + tags）
  body_length INT, -- 記事本文の文字数
  created_at INT -- 投稿日時(秒数)
)
```

上記のスキーマで作成した `article` テーブルがある前提で、各機能の解説をしていきます。

## 1. キーワード検索機能
まず最初に解説するのは「キーワード検索機能」です。
「キーワード検索機能」は、ユーザーが入力したキーワードに関連する記事を表示する機能です。
この機能を実現するために、裏側では **全文検索＋機械学習ランキング** のシステムが動いているので、この節ではその実現方法を解説していきます。

:::note info
**機械学習ランキング（ランキング学習）とは？**
単純な全文検索だけだとキーワードとの類似度順に検索結果が並べられますが、機械学習ランキング（ランキング学習）をすることで、いいね数などと組み合わせた最適なランキングで並べることができるようになります。
:::

### DDL（データベース構築時のコマンド）
まず、サービス構築時の最初に一度だけ実行すべきDDLコマンドを紹介します。

#### (1) 全文検索用の転置インデックス作成

articleテーブルのcontentsフィールド（記事のtitleとtagsを連結したフィールド）に転置インデックス(s1)を作成します。
```
CREATE SEARCH INDEX 
  s1 
ON
  article (contents)
```
これで後述のSEARCHメソッドが使えるようになります。

#### (2) 機械学習ランキング用のMLRテンプレート作成

続いて、MLRテンプレートと呼ばれるSELECT文のテンプレート（`t1`）を作成します。
ここで登録したSELECT文は、後述するMLRメソッド呼び出しでMLRのランキング結果として取得することができます。
```
CREATE MLR_TEMPLATE t1 KEY(id)
SELECT 
  id, -- PK
  title, -- 記事タイトル
  url, -- 記事URL
  ..., -- 省略（記事表示に使うその他のフィールド）
  _similarity, -- 全文検索のコサイン類似度
  likes_count, -- 記事のいいね数
  user_items_count,  -- 投稿者の投稿記事数
  body_length, -- 記事本文の文字数
  user_followers_count -- 投稿者のフォロワー数
FROM SEARCH(article, contents, ?)
```

FROM句のSEARCHメソッドは先ほど作成した転置インデックスに対して全文検索結果を返すメソッドです。
このMLRテンプレートでは、全文検索結果に対して機械学習ランキングを実行するテンプレートとなります。
`?` の部分は後述のMLRメソッド実行時に指定することで実際の値を挿入することができます。



#### (3) 機械学習ランキング用のMLRモデル作成

次に、先ほど作成したMLRテンプレートを指定し、MLRモデル（m1）という機械学習のモデルを作成します。 
MLRモデルでは、MLRテンプレートの出力フィールドのうち、機械学習の特徴量として使うフィールドを指定します。
```
CREATE MLR_MODEL m1 WITH t1 (
  _similarity, 
  likes_count, 
  user_items_count, 
  body_length, 
  user_followers_count
)
```
この例では、以下の特徴量を使って機械学習ランキングモデルを作成しています。
* 記事のいいね数（likes_count）
* 全文検索のコサイン類似度（_similarity）
* 投稿者の投稿記事数（user_items_count）
* 記事本文の文字数（body_length）
* 投稿者のフォロワー数（user_followers_count）


### DML（SELECT系クエリ）
次に、実際にサービス内でキーワード検索をする際に使用するSELECT系クエリについて解説していきます。

#### (1) 全文検索＋機械学習ランキングの実行（推論）

ユーザーが「python」という検索キーワードを入力した場合、下記のようなSQLで全文検索＋機械学習ランキングを実行することができます。
```
SELECT 
  title, -- 記事タイトル
  url, -- 記事URL
  ..., -- 省略（MLRテンプレートで指定したフィールドを指定可能）
  _request_id, -- 学習時に使うリクエストID（MLRメソッドで追加されるフィールド）
  _key_id, -- article.idと同じ（MLRメソッドで追加されるフィールド）
  _score -- MLRの推論スコア（MLRメソッドで追加されるフィールド）
FROM 
  MLR(m1, 'python')(30, 0)
```
このSQLの実行結果は、機械学習ランキングによりランキングされた結果（上位30件）が出力されます。
MLRメソッドの第一引数：`m1`で先ほど作成したMLRモデルのモデル名を指定します。
MLRメソッドの第二引数では 検索クエリ `python`を指定しています。ここで指定した引数はMLRテンプレートの `?` としていた箇所に挿入されます。
`_request_id` は次の学習フェーズで利用するリクエストIDを特定するためのフィールドです。
`_key_id` は、MLRテンプレートでKEYとして指定したフィールドで、この例の場合はarticle.idの値が出力されます。

#### (2) 機械学習ランキングの学習

先ほどのランキング結果をユーザーに表示してクリックされたアイテムを、HottyDBに連携し機械学習ランキングの学習を実行します。
```
INSERT MLR_POSITIVE(m1, 5, 100)
```
上記は、_request_id = 5 のリクエストで 記事ID = 100 がクリックされた場合のSQLコマンドとなります。

### より詳しい解説
キーワード検索機能に関する詳細な説明はHottyDBの公式サイトでも解説していますので、こちらもご参照ください。

https://www.hottydb.com/tutorial/search-engine-usage/


## 2. 関連記事レコメンデーション機能（協調フィルタリング）
「この記事にいいねした人は、この記事にもいいねしています」のレコメンデーション機能を実現する方法を解説します。

### DDL（データベース構築時のコマンド）
サービス構築時の最初に一度だけ実行すべきDDLコマンドを紹介します。

#### (1) likesテーブルの作成
「この記事にいいねした人は、この記事にもいいねしています」を実現するために、まず誰がどの記事にいいねしているかを登録するためのテーブルを作成します。
これは普通のCREATE TABLE命令で作成します。
```
CREATE TABLE likes (
  uid VARCHAR(64),   -- QiitaのユーザーID
  aid INT  -- 記事ID
)
```
誰がどの記事にいいねしたかも、QiitaのAPIから取得できるので、APIから取得した情報をこのテーブルに登録します。

#### (2) レコメンドインデックスの作成
協調フィルタリングベースのレコメンデーションを実現するために、HottyDBではレコメンドインデックスというインデックスを作成する必要があります。
そのためのコマンドは以下のようになります。
```
CREATE RECOMMEND INDEX 
  r1 
ON 
  likes (uid, aid)
```
つまり、レコメンドインデックスの作成は、レコメンデーションの元となるデータテーブルの、どのフィールドがユーザーIDでどのフィールドがアイテムIDかをHottyDBに教えてあげることを指します。

これにより、HottyDBは「このアイテムを選択したユーザーは、このアイテムも選択しています」というレコメンデーションの関係性を学習することができます。

### DML（SELECT系クエリ）
続いて、実際にサービス内でユーザーが記事詳細に訪れた時に、関連記事を探すためのDMLコマンド（SQL）を解説します。
例では、記事ID=1234の記事の関連記事をレコメンドする際のSQLコマンドとなります。
```
SELECT _item_id,  _similarity 
FROM RECOMMEND(r1, 1234)
ORDER BY _similarity DESC 
LIMIT 10
```
ここで、`_item_id`はレコメンド記事のID、`_similarity`は元記事とそのレコメンド記事との関連度を示す指標となります。

以上のようにレコメンドインデックスを作成するだけで、レコメンド記事を簡単に取得することができます。

### より詳しい解説
レコメンデーション機能に関する詳細な説明はHottyDBの公式サイトでも解説していますので、こちらもご参照ください。

https://www.hottydb.com/tutorial/recommend-engine-usage/

## 3. 関連記事レコメンデーション機能（類似記事検索）
「この記事によく似た記事」のレコメンデーション機能を実現する方法を解説します。

### DDL（データベース構築時のコマンド）
類似記事検索機能では「キーワード検索機能」で作成した全文検索用の転置インデックスを使うので、特別なDDLは不要です。

### DML（SELECT系クエリ）
類似記事機能は、閲覧中記事の全タグを検索クエリとし、OR検索で全文検索をすることで実現しています。
閲覧中記事のタグが「`HTML` `CSS` `Chrome` `VSCode`」だった場合のSQLは下記の通りです。

```
SELECT 
  title, -- 記事タイトル
  url, -- 記事URL
  ..., -- 省略（記事表示に使うその他のフィールド）
  _similarity 
FROM 
  SEARCH(article, contents, 'html css chrome vscode', OR)
WHERE likes_count >= 3 
ORDER BY _similarity DESC 
LIMIT 10
```

このSQLでは、いいね数を3以上に絞り込んだ上で、閲覧中記事のタグとの類似度順に表示しています。
注目すべきはSEARCHメソッドの最終引数に `OR`というオペレーターを追加することで、OR検索を指定していることです。
（デフォルトではAND検索となります）

OR検索を指定することで、タグの1つにだけマッチするような記事も関連記事として並べることができます。


## 4. 「あなたにおすすめの記事」機能
最後に、「あなたにおすすめの記事」という過去の閲覧記事に基づいたレコメンデーション機能を解説します。


### DDL（データベース構築時のコマンド）
こちらの機能も類似記事の機能と同様に、「キーワード検索機能」で作成した全文検索用の転置インデックスを使うので、特別なDDLは不要です。


### DML（SELECT系クエリ）
「あなたにおすすめの記事」機能は、ユーザーの閲覧記事に基づいて出現頻度の高いタグをブラウザのlocalStorageに保存しておき、TOP5件のタグで記事の全文検索をすることで実現しています。

出現頻度の高いタグTOP5件が「`HTML` `CSS` `Chrome` `VSCode`」だった場合のSQLは下記の通りです。

```
SELECT 
  title, -- 記事タイトル
  url, -- 記事URL
  ..., -- 省略（記事表示に使うその他のフィールド）
  _similarity 
FROM 
  SEARCH(article, contents, 'html css chrome vscode', OR) 
WHERE likes_count >= 5 
AND created_at > `14日前の日時` 
LIMIT 10
```
ご覧の通り類似記事の場合とSQLはほとんど同じです。
違う点はいいね数を5以上にしている点と、記事の投稿日時を直近14日間に投稿されたものに絞り込んでいる点です。

TOPページに表示されるおすすめ記事なので、直近のものの方が良いと考えてそうしています。


# さいごに
以上で「HottyTech検索」の各機能に関する解説を終わります。
HottyDBを利用すると、こういった全文検索・機械学習ランキング・アイテムレコメンデーションの機能がたった1台のマシンで簡単に構築することができます！

HottyDBはまだまだα版で改善点が山のようにありますが、ぜひみなさまのフィードバックを経てより良いソフトウェアにしていきたいと思っています。

「HottyTech検索」や「HottyDB」に関するお問い合わせ・フィードバックなどありましたら[Twitter（@hottydb）](https://twitter.com/hottydb)や[こちらの連絡先](https://www.hottydb.com/other/contact/)までご連絡いただければと思います。