これくらいの実例とともにソリューション選択できるようにしていきたい。
-
-
Save shrek-kurata/ee8597c61e68cbf706744ff12ea72581 to your computer and use it in GitHub Desktop.
ドリルダウンの検索導線において、カテゴリごとにヒットする検索結果を表示することで、カスタマがそのリンクを押す/押さない動機を 強めることができます。
ヒット件数を求めるにはカテゴリごとの検索結果を集計する必要があり、たいていの場合、負荷の高いクエリとなります。
SELECT SR_OUT.name, CASE WHEN cnt IS NULL THEN 0 ELSE cnt END
FROM salary_ranges SR_OUT
LEFT OUTER JOIN (
SELECT SR.name, COUNT(SR.name) AS cnt
FROM job_postings JP
JOIN salary_ranges SR ON JP.salary BETWEEN SR.lower AND SR.upper
GROUP BY SR.name
) AS PER_SR ON SR_OUT.name = PER_SR.name
job_postingsのフルスキャンは避けられません。
上記の「推定年収」「雇用形態」といったカテゴリが多くなるとそれだけで、SQL発行回数が増えます。 job_descriptionsをElasticsearchやSolrのような検索エンジンでインデックス化し、 そちらからデータをすることで性能的なアドバンテージがあります。
例えば、ElasticSearchのAggregationの機能を使うと、
GET index/job_postings/_search
{
"from" : 0, "size" : 0,
"aggs": {
"salary_ranges": {
"range": {
"field": "salary",
"ranges": [
{"from": 200, "to": 299},
{"from": 300, "to": 399},
{"from": 400, "to": 499},
{"from": 500, "to": 599},
{"from": 600, "to": 699}
]
}
},
"workplaces": {
"terms": {
"field": "workplace"
}
}
}
}
このようなクエリ1回で、以下のように複数のカテゴリごとの件数表示データを取得できます。
{
"took": 5,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 2,
"max_score": 0,
"hits": []
},
"aggregations": {
"workplaces": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "新宿",
"doc_count": 1
},
{
"key": "渋谷",
"doc_count": 1
}
]
},
"salary_ranges": {
"buckets": [
{
"key": "200.0-299.0",
"from": 200,
"to": 299,
"doc_count": 1
},
{
"key": "300.0-399.0",
"from": 300,
"to": 399,
"doc_count": 0
},
{
"key": "400.0-499.0",
"from": 400,
"to": 499,
"doc_count": 1
},
{
"key": "500.0-599.0",
"from": 500,
"to": 599,
"doc_count": 0
},
{
"key": "600.0-699.0",
"from": 600,
"to": 699,
"doc_count": 0
}
]
}
}
}
件数はリクエストごとに計算するのでなく、キャッシュしておくことで検索負荷を大幅に下げることができます。
集計用のテーブルを作って、定期的にカテゴリごとの集計します。
O/Rマッパーやデータベース、検索エンジンのリザルトキャッシュの機能を使って、キャッシュさせることができます (リザルトキャッシュの詳細は別章にて)。大抵の場合、透過的にキャッシュできるので、コードがシンプルになり、特別なミドルウェアや設計が必要でないことがメリットです。が、結局集計のクエリが重いのであれば、キャッシュ有効期限切れの際のリクエストは遅くなってしまうので、 そういうケースを許容できないのであれば、Aの定期集計にしておくのが無難です。
負荷の高いときや、キャッシュの仕組みが停止している場合、件数のみださなくするようにデグラデーションさせる設計をしておくと、サービス全体のダウンを防ぐことができます。
例えばカテゴリのみを以下のようなJSONファイルに出力しておいて、HTMLをレンダリングする際には、 取得した件数をマージします。件数が取得できなければ、カテゴリのみ表示します。
{
"saraly_ranges": [
{
"key": "salary200",
"label": "200万円"
},
{
"key": "salary300",
"label": "300万円"
}
],
"workplaces": [
{
"key": "place001",
"label": "渋谷"
},
{
"key": "place002",
"label": "新宿"
}
]
}
ただし想定する障害ケースが、一連のユーザ導線に影響があると意味がないので、デグラデーションを作り込むかどうかは 一連のユーザ導線単位で決めます。
CREATE TABLE job_descriptions(
id BIGINT PRIMARY KEY,
title VARCHAR(100) NOT NULL,
salary BIGINT
);
CREATE TABLE salary_ranges(
name VARCHAR(100) NOT NULL,
lower BIGINT NOT NULL,
upper BIGINT NOT NULL
);
INSERT INTO job_descriptions(id, title, salary) VALUES
(1, 'AAA', 342),
(2, 'BBB', 442),
(3, 'CCC', 388),
(4, 'DDD', 242),
(5, 'EEE', 790),
(6, 'FFF', 678)
;
INSERT INTO salary_ranges(name, lower, upper) VALUES
('200万円', 200, 299),
('300万円', 300, 399),
('400万円', 400, 499),
('500万円', 500, 599),
('600万円', 600, 699);