best practice ๊ฐ ์๋ ์ ์์ต๋๋ค. ์ฐธ๊ณ ํด์ฃผ์๋ฉด ๊ฐ์ฌํ๊ฒ ์ต๋๋ค.
๋ฌธ์ ์ํฉ
OpenSearch์์ from๊ณผ size๋ฅผ ํ์ฉํด ํ์ด์ง ์ฟผ๋ฆฌ๋ฅผ ์์ฒญํ๋ ์ค, ๋ฐ์ดํฐ๊ฐ 10,000๊ฑด์ ๋์ด๊ฐ๋ฉด์ ๋ค์๊ณผ ๊ฐ์ ์๋ฌ๊ฐ ๋ฐ์ํ์ต๋๋ค.
"Result window is too large, from + size must be less than or equal to: [10000] but was [10008].
See the scroll api for a more efficient way to request large data sets.
This limit can be set by changing the [index.max_result_window] index level setting."
OpenSearch(ElasticSearch) ๋ ๊น์ ํ์ด์ง๋ฅผ ์กฐํํ๋ ๊ฒ์ ์ฑ๋ฅ์ ์๋นํ ๋ถ๋ด์ด ๋๊ธฐ ๋๋ฌธ์ ๊ธฐ๋ณธ ์ค์ ์ from + size๊ฐ 10,000์ ์ด๊ณผํ ์ ์๋ค๋ ์ ์ฝ์ด์์ต๋๋ค.
Querying for pages deep in your results can have a significant performance impact, so OpenSearch limits this approach to 10,000 results.
๊ธฐ์กด ๋ฐฉ์์ ์ ์ฑ ์ผ๋ก 10,000๊ฑด ์ด์ ์กฐํ ์, ํ์ด์ง์ ํ์ง ์๊ณ "10,000+ ๊ฑด" ์ UI ์ ๋ณด์ฌ์ฃผ๋ฉฐ ์ ์ฑ ์ผ๋ก ํด๊ฒฐํ์ต๋๋ค.
ํ์ง๋ง, ๊ณ ๊ฐ์ฌ์ ์์ฒญ์ ๋ฐ๋ผ 10,000๊ฑด ์ด์์ ํ์ด์ง์ ์๊ตฌ๋ฐ์๊ณ , ๊ทธ ๊ณผ์ ์ ์ค๋ช ํฉ๋๋ค.
์ ์ ์ด ๋ฐ์ดํฐ๋ 13,000๊ฑด ์ ๋๋ค.
ํด๊ฒฐ ๋ฐฉ๋ฒ ๋์ ๋ฐ ๊ฒฐ์
์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ณ ์ Paginate Results ์์ 3๊ฐ์ง ๋์์ ์ฐพ์์ต๋๋ค.
- Scroll API
- index.max_result_window ์ค์ ๊ฐ ์ฆ๊ฐ
- search_after API
์ด ์ค, search_after API ๋ฅผ ์ ํํ ์ด์ ๋ฅผ ์ค๋ช ํฉ๋๋ค.
1. Scroll API
๋ฌธ์์ ๋ฐ๋ฅด๋ฉด Scroll API๋ ์คํฌ๋กค์ ์ด์ฉํด ๋์ฉ๋ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ๋ ๋ฐฉ๋ฒ์ ๋๋ค. ๊ฐ ์ค๋์ ์ธ๋ฑ์ค๋ง๋ค ์ด๋๊น์ง ์ฝ์๋์ง, ๋ฐ์ดํฐ์ ์ฝ์ ์์น๋ฅผ ๋ฉ๋ชจ๋ฆฌ์ ์ ์ฅํ๋ค๊ณ ํฉ๋๋ค.
๊ทธ๋ฌ๋ค ๋ณด๋, ์น ํ๊ฒฝ๊ฐ์ด ์ฌ์ฉ์์ ์์ฒญ์ด ๋น๋ฒํ ๋ scroll API ๋ฅผ ์ฌ์ฉํ๋ฉด ์ฌ์ฉ์๋ง๋ค scroll ์ ์์น๋ฅผ ์ ์ฅํด์ผ ํ๋ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ๋ง์์ง๊ฒ ๋ฉ๋๋ค.
Because open search contexts consume a lot of memory, we suggest you don’t use the scroll
operation for frequent user queries that don’t need the search context to be open
๊ทธ๋์ OpenSearch์์๋ ๋์ฉ๋ ๋จธ์ ๋ฌ๋ ์์ ๊ฐ์ ๋ฐฐ์น ์ฒ๋ฆฌ์ ๊ถ์ฅํ๊ณ ์์ต๋๋ค.
If you need to request volumes of data larger than 1 PB from, for example, a machine learning job, use the scroll operation instead. The scroll operation allows you to request an unlimited number of results.
์ ๋ ์น ํ๊ฒฝ์์ ํ์ด์ง์ ํด์ผํ๊ธฐ ๋๋ฌธ์ ์ ํํ์ง ์์์ต๋๋ค.
2. index.max_result_window ์ฆ๊ฐ
index.max_result_window ์ค์ ๊ฐ์ ๋๋ ค OpenSearch์ ๊ธฐ๋ณธ ์ค์ ๋ 10,000๊ฑด์ ์ฐํํด์ from + size ๋ฅผ ์ฌ์ฉํ ์ ์์ง๋ง, ์ ํํ์ง ์์์ต๋๋ค.
์ฒซ ๋ฒ์งธ ์ด์ ๋ ์ฑ๋ฅ ๋ฌธ์ ์ ๋๋ค.
from + size ๋ฐฉ์์ offset ๊ธฐ๋ฐ ํ์ด์ง์ผ๋ก, offset ์ด ์ปค์ง์๋ก ์ฝ์ ๋ฐ์ดํฐ๋ฅผ ๋ฒ๋ฆฌ๊ธฐ ๋๋ฌธ์ ๋์ฉ๋ ๋ฐ์ดํฐ์์ ์ ํฉํ์ง ์์ต๋๋ค. ์๋ฅผ ๋ค์ด 10๋ง ๋ฒ์งธ offset(from) ๋ถํฐ 10๊ฑด์ ์กฐํํ๋ ค๋ฉด, ์์ 10๋ง ๊ฑด์ ๋ชจ๋ ์ฝ๊ณ ๋ฒ๋ ค์ผ ํฉ๋๋ค.
๋ ๋ฒ์งธ ์ด์ ๋ ์ค์น ๋ณต์ก์ฑ์ ๋๋ค.
์ ํ์ฌ๋ ์๋ฃจ์ ์ ์จํ๋ ๋ฏธ์ค์ ์ค์นํ๋ ํํ์ ๋๋ค. ๊ณต๊ณต๊ธฐ๊ด๋ง๋ค ๋ฐ์ดํฐ ์์ด ๋ค๋ฅผ ํ ๋ฐ, ๊ทธ๋๋ง๋ค index์ ์ค์ ๊ฐ์ ๋ณ๊ฒฝํด์ผ ํ๋ ๋ณต์ก์ฑ์ ๋ฎ์ถ๊ณ ์ถ์์ต๋๋ค.
์ค์น ์, ์ค์ ๊ฐ์ ํ๋๋ผ๋ ์ค์ด๋ ๊ฒ์ด ์ข๋ค๊ณ ํ๋จํ์ต๋๋ค.
3. search_after API
search_after๋ ์ด์ ํ์ด์ง์ ๊ฒฐ๊ณผ๋ฅผ ์ด์ฉํด์ ๋ค์ ํ์ด์ง์ ๊ฒฐ๊ณผ๋ฅผ ์ป๋ ๋ฐฉ๋ฒ์ ๋๋ค.
๋ฌธ์๋ฅผ ์ฝ์ด๋ณด๋ฉด search_after ๋ ์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๊ณผ ์ ์ฌํ ๊ฒ ๊ฐ์ต๋๋ค.
ํนํ, search_after ๋ฌธ์ ๋ด์ฉ์ ํด๋น API๋ฅผ ์ ๋ ฌ ์กฐ๊ฑด๊ณผ ํจ๊ป ์จ์ผ ํ๋ ๊ฒ์ด ๋ช ์๋์ด ์์ต๋๋ค.
The search_after parameter provides a live cursor that uses the previous page’s results to obtain the next page’s results. ... You can use search_after only when sorting is applied.
์ด๋ MySQL์ ์ธ๋ฑ์ค๊ฐ ์ ๋ ฌ๋์ด ์๋ค๋ ๊ฒ์ ์ด์ฉํด ์ปค๋ฒ๋ง ์ธ๋ฑ์ค๋ก PK๋ฅผ ๊ฐ์ ธ์ค๊ณ , PK๋ก ๋ค์ ์กฐํํ์ฌ ๋์ฉ๋ ๋ฐ์ดํฐ๋ฅผ ๋น ๋ฅด๊ฒ ํ์ด์ง ์ฒ๋ฆฌํ ์ ์๋ ๋ฐฉ์์ ์๊ฐํ๋ฉด search_after ์ฌ์ฉ ์ ์ ๋ ฌ ์กฐ๊ฑด์ ํจ๊ป ์จ์ผํ๋ ๊ฒ์ด ์ดํด๊ฐ ๊ฐ๋๋ค.
๋ค๋ง, search_after ๋ stateless(๋ฌด์ํ) ๋ฐฉ์์ด๊ธฐ ๋๋ฌธ์ ์ค์๊ฐ์ผ๋ก ์ธ๋ฑ์ค๊ฐ ์ ๋ฐ์ดํธ๋๊ฑฐ๋ ์ญ์ ๋ ๊ฒฝ์ฐ ๊ฒฐ๊ณผ์ ์ผ๊ด์ฑ์ด ๋ณด์ฅ๋์ง ์์ ๋ฐ์ดํฐ ๋๋ฝ์ด ๋ฐ์ํ ์ ์์ต๋๋ค. ์ฆ, ๋ฐฐ์น์ฒ๋ฆฌ์ search_after ๋ฅผ ์ฌ์ฉํ๋ค๋ฉด ๋ฐฐ์น ๋์ค์ ๋ฐ์ดํฐ๊ฐ ๋๋ฝ๋ ์ ์๋ค๋ ๋จ์ ์ด ์์ต๋๋ค.
์ด๋ฅผ ์ํด OpenSearch๋ ์ด๋ฌํ ์ผ๊ด์ฑ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด PIT (Point In Time) API๋ฅผ ์ง์ํฉ๋๋ค.
PIT๋ ์ฟผ๋ฆฌ ์์ ์ ์ค๋ ์ท์ ์ฐ์ด ํด๋น ์๊ฐ ๋์(์ฟผ๋ฆฌ์ ํ๋ผ๋ฏธํฐ๋ก ์ค ์๊ฐ) ์ผ๊ด๋ ์ฝ๊ธฐ๋ฅผ ๋ณด์ฅํ ์ ์๋ค๊ณ ํฉ๋๋ค.
๋ณธ๋ก ์ผ๋ก ๋์์ ์ ์ํฉ์ ์๋ฒฝ 3์์ 10๋ถ์ ๋ ์ผ๊ด์ ์ผ๋ก ์ธ๋ฑ์ฑ์ ์๋ฃ๋๋ฉฐ ์ฌ์ฉ์๊ฐ ์กฐํ ์ค์๋ ์ถ๊ฐ ์ธ๋ฑ์ฑ์ด ๋ฐ์ํ์ง ์์ ์ผ๊ด๋ ์ฝ๊ธฐ๊ฐ ๋น์ฆ๋์ค ํน์ฑ ์ ๋ณด์ฅ๋ฉ๋๋ค. ๋ฌผ๋ก , ํด๋น ํ์ด์ง์ ํ๋ ค๋ ๊ธฐ๋ฅ์ ๋์ค์ ๋ฐ์ดํฐ๊ฐ ์ถ๊ฐ๋์ด๋ ๊ด์ฐฎ์ต๋๋ค.
๋ฐ๋ผ์ PIT ์์ด search_after ๋ฅผ ์ฌ์ฉํ๊ธฐ๋ก ๊ฒฐ์ ํ์ต๋๋ค.
๊ตฌํ ์์ธ: from/size ๋ฐฉ์๊ณผ search_after ๋ฐฉ์์ ๋น๊ต ๋ฐ ์ ์ฉ
1. ๊ธฐ์กด ๊ตฌํ ๋ฐฉ์: from/size (Offset ๊ธฐ๋ฐ ํ์ด์ง)
from๊ณผ size๋ฅผ ํ์ฉํ ๋ฐฉ์์ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ Offset ๊ธฐ๋ฐ ํ์ด์ง์ ๋๋ค.
๊ธฐ์กด์๋ 10,000๊ฑด ์ด์์ ๋ฐ์ดํฐ ์กฐํ ์๊ตฌ์ฌํญ์ด ์์ด, hits.total.value๊ฐ 10,000๊ฑด์ด๊ณ relation์ด gte์ผ ๊ฒฝ์ฐ ๋ฐฑ์คํผ์ค UI ์์์ '10,000+'๋ก ํ์ํ๊ณ ์ต๋ 10,000๊ฑด๊น์ง๋ง ํ์ด์ง์ ์ ๊ณตํ์ต๋๋ค
์ด๋ฅผ ์ํด page์ limit์ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ์ OpenSearch ์ฟผ๋ฆฌ๋ฅผ ๊ตฌ์ฑํ์์ต๋๋ค.
String query = """
{
"from": %d, // (page - 1) * limit
"size": %d,
"query": {
"match": {
"meta_id": %s
}
},
"_source": {
"excludes": ["..."]
},
"sort": [
{ "meta_id": "ASC" } // ์ ๋ ฌ ์กฐ๊ฑด
]
}
""".formatted((page - 1) * limit, limit, meta_id);
์ด ๊ฒฐ๊ณผ์์ ํ์ด์ง ์ฒ๋ฆฌ๋ฅผ ์ํด hits.total.value ์ hits.total.relation ์ผ๋ก ํ๋จํฉ๋๋ค.
value ๊ฐ 10,000 ์ด๊ณ relation ์ด "gte" ๋ผ๋ฉด, UI ์์์ "10,000+" ๋ฅผ ๋ณด์ฌ์ฃผ๊ณ , ๊ทธ ์ ๊น์ง offset ๋ฐฉ์์ผ๋ก ํ์ด์ง ์ฒ๋ฆฌํ๋ฉด ๋ฉ๋๋ค.
2. ๊ฐ์ ๋ ๊ตฌํ ๋ฐฉ์: search_after (์ปค์ ๊ธฐ๋ฐ ํ์ด์ง)
10,000๊ฑด ์ด๊ณผ ํ์ด์ง ์๊ตฌ์ฌํญ์ ํด๊ฒฐํ๊ธฐ ์ํด RDB์ ์ปค์ ๊ธฐ๋ฐ ํ์ด์ง ๋ฐฉ์๊ณผ ๊ฐ๋ค๊ณ ์๊ฐํ๋ search_after๋ฅผ ๋์ ํ์ต๋๋ค.
์ด ๋ฐฉ์์ ์ด์ ํ์ด์ง์ ๋ง์ง๋ง ๊ฒฐ๊ณผ(์ปค์)๋ฅผ ๊ธฐ์ค์ผ๋ก ๋ค์ ํ์ด์ง๋ฅผ ์กฐํํฉ๋๋ค.
๊ฒ์ ์์ฒญ ์(GET {index_name}/_search), ์ด์ ํ์ด์ง์ ๋ง์ง๋ง meta_id ๊ฐ์ธ lastMetaId๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌ๋ฐ์ search_after๋ฅผ ๊ตฌ์ฑํฉ๋๋ค.
์ด lastMetaId ๋ ์ด์ ํ์ด์ง๋ฅผ sort ๋ก ์ ๋ ฌํ์ ๋์ ๋ง์ง๋ง ๋ฐ์ดํฐ์ ์๋ sort ํ๋(meta_id)์ ๊ฐ์ ๋ปํฉ๋๋ค.
ํด๋ผ์ด์ธํธ๋ก๊ฐ lastMetaId ๋ฅผ null ๋ก ์ค๋ค๋ฉด ์ฒซ ํ์ด์ง๋ฅผ ์กฐํํ๋ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ ์๋์ ๊ฐ์ด ์ฟผ๋ฆฌ๋ฅผ ๊ตฌ์ฑํ ์ ์์ต๋๋ค.
// search_after ํ๋ผ๋ฏธํฐ ๊ตฌ์ฑ
String searchAfterQuery = "";
if (request.lastMetaId() != null) {
searchAfterQuery = """
"search_after": ["%s"],
""".formatted(request.lastMetaId());
}
// ๊ฒ์ ์ฟผ๋ฆฌ {index_name}/_search
String searchQuery = """
{
"size": %s,
%s
"sort": {
"meta_id": "asc" // search_after ์ฌ์ฉ ์ ์ ๋ ฌ ์กฐ๊ฑด ํ์
},
"_source": ["meta_id", "title", "createdAt", "sort"]
}
""".formatted(limit, searchAfterQuery);
ํ์ง๋ง, search_after ๋ฐฉ์์ ์ ์ฒด ๋ฐ์ดํฐ ์๋ฅผ ์ ์ ์๊ธฐ ๋๋ฌธ์ ๋ค์ ํ์ด์ง ๊ทธ๋ฃน์ผ๋ก ์ด๋ํ๋ ๋ฒํผ(>)์ ํ์ฑํ ์ฌ๋ถ๋ฅผ ํ๋จํ๊ธฐ ์ํด ๋ณ๋์ count ์ฟผ๋ฆฌ๊ฐ ํ์ํฉ๋๋ค.

๋ฒํผ(>) ํ์ฑํ๋ฅผ ์ํด ์ ์ฒด count ๋ฅผ ํ ์ ์์ง๋ง, OpenSearch ๋ ์๋ต ๊ฐ์๋ฅผ 10,000๊ฐ๋ก ๊ธฐ๋ณธ ๊ฐ์ ์ ์ดํ๊ณ ์์ต๋๋ค.
์ด๋ ์ฑ๋ฅ ์ 10,000๊ฑด ์ด์์ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ์ง ์๊ฒ ํ๋ ค๋ ์๋๊ฐ ๋ด๊ฒจ์๋ค๊ณ ์๊ฐ์ ํ๊ธฐ ๋๋ฌธ์ _count API ๋ฅผ ์ฌ์ฉํ์ง ์๊ณ ์ ์ฒด ์นด์ดํธ ๋์ ํ์ํ ๋ฒ์๊น์ง๋ง ์นด์ดํธํ๊ณ ์ ํ์ต๋๋ค.
์๋ฅผ ๋ค์ด, ํ ํ๋ฉด์ 10๊ฐ์ ํ์ด์ง๊ฐ ์๋ค๊ณ ํ ๋ (1~10, 11~20 ๋ฑ) ์ด๋ ๋ฒํผ(>)์ ํ์ฑํ ์ฌ๋ถ๋ฅผ ์๊ธฐ ์ํด์๋ ์๋ ๊ณต์์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ๋ง์ฝ ํ ํ๋ฉด์ 5๊ฐ์ ํ์ด์ง์ฉ ๋ณด์ฌ์ค๋ค๋ฉด 10L ๋์ 5L ์ด ๊ณต์์ ๋ค์ด๊ฐ๋๋ค.
Long pageLimit = ((page - 1) / 10L + 1) * limit * 10L + 1;
์ด ๊ณต์๊ณผ ํจ๊ป track_total_hits ํ๋ผ๋ฏธํฐ๋ฅผ ์ฌ์ฉํ์ต๋๋ค.
track_total_hits ๋ ๋งค์นญ๋๋ ๋ฌธ์์ ๊ฐ์๊ฐ ํน์ ์๊ณ๊ฐ(์: 10,000๊ฐ)์ ์ด๊ณผํ ๊ฒฝ์ฐ, ์ ํํ ์ ์ฒด ๊ฐ์๋ฅผ ๋ฐํํ์ง ์์ต๋๋ค. ๋์ 10000์ ๋ฐํํ๊ณ , relation ๊ฐ์ gte (greater than or equal to, ์ด์)๋ก ํ์ํฉ๋๋ค.
The number of hits matching the query to count accurately. If true, the exact number of hits is returned at the cost of some performance. If false, the response does not include the total number of hits matching the query
์ฟผ๋ฆฌ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
// count ์ฟผ๋ฆฌ
// ํ ํ๋ฉด์ 10๊ฐ์ ํ์ด์ง๊ฐ ์๋ค๊ณ ๊ฐ์ (1~10, 11~20, 21~30)
Long pageLimit = ((page - 1) / 10L + 1) * limit * 10L + 1;
// {index_name}/_search
String countQuery = """
{
"size": 0,
"track_total_hits": %d
}
""".formatted(pageLimit);
_search API์ track_total_hits ์ต์ ์ ์ค์ ํ๋ฉด, ์๋ต ๊ฒฐ๊ณผ์์ hits.total.value์ hits.total.relation ๊ฐ์ ์ป์ ์ ์์ต๋๋ค.
์ด ๋ ๊ฐ์ ๊ธฐ์ค์ผ๋ก ๋ค์ ํ์ด์ง ๊ทธ๋ฃน์ผ๋ก ์ด๋ํ๋ ๋ฒํผ(>)์ ํ์ฑํ ์ฌ๋ถ๋ฅผ ํ๋จํ์ต๋๋ค.
๋ง์ฝ, 1~10ํ์ด์ง๋ฅผ ๋ณด์ฌ์ค ๋, pageLimit ์ ๊ฒฐ๊ณผ๋ 101์ด ๋ฉ๋๋ค. ์ด ๋, hits.total.value ๊ฐ 101, hits.total.relation ์ด gte ๊ฐ ๋์ค๋ฉด ">" ๋ฒํผ์ ํ์ฑํ ํฉ๋๋ค.
1. ์ฒซ ํ์ด์ง ๊ทธ๋ฃน(1~10ํ์ด์ง)์์ ๋ค์ ๊ทธ๋ฃน ์กด์ฌ ์ฌ๋ถ ํ์ธ
- 1~10ํ์ด์ง(Limit 10 ๊ธฐ์ค)๋ฅผ ๋ณด์ฌ์ฃผ๋ ์ํฉ์์, ๋ค์ ํ์ด์ง ๊ทธ๋ฃน์ด ์กด์ฌํ๋์ง ํ์ธํ๊ธฐ ์ํด pageLimit์ 101๋ก ์ค์ ํฉ๋๋ค.
- ๋ง์ฝ, hits.total.value๊ฐ 101์ด๊ณ , hits.total.relation์ด gte (ํฌ๊ฑฐ๋ ๊ฐ์)๋ก ๋ฐํ๋๋ค๋ฉด, ์ด๋ ๋ฐ์ดํฐ๊ฐ 101๊ฑด ์ด์ ์กด์ฌํ๋ค๋ ์๋ฏธ์ด๋ฏ๋ก > ๋ฒํผ์ ํ์ฑํํฉ๋๋ค.
2. ๋ค์ ํ์ด์ง ๊ทธ๋ฃน(11~20ํ์ด์ง)์์ ๋ง์ง๋ง ํ์ด์ง ํ์ธ
- ์ฌ์ฉ์๊ฐ > ๋ฒํผ์ ํด๋ฆญํ์ฌ 11ํ์ด์ง๋ฅผ ์์ฒญํฉ๋๋ค.
- 11~20ํ์ด์ง ๊ทธ๋ฃน์ ์ํด์๋ ์ํฉ์์, ๋ค์ ๊ทธ๋ฃน(21ํ์ด์ง)์ด ์๋์ง ํ์ธํ๊ธฐ ์ํด pageLimit์ 201๋ก ์ค์ ํฉ๋๋ค.
- ์์ฒญ์ ๋ํ ๊ฒฐ๊ณผ๋ก hits.total.value๊ฐ 145์ด๊ณ , hits.total.relation์ด eq (๊ฐ์)๋ก ๋ฐํ๋๋ค๋ฉด, ์ด๋ ๋ฐ์ดํฐ๊ฐ 145๊ฑด๊น์ง ์ ํํ ์กด์ฌํ๋ค๋ ์๋ฏธ์ ๋๋ค.
- ๋ฐ๋ผ์ 11~20ํ์ด์ง ๊ทธ๋ฃน ๋ด์์ 15ํ์ด์ง๊น์ง๋ง ๋ฐ์ดํฐ๊ฐ ์์ผ๋ฉฐ, ๊ทธ ์ดํ ํ์ด์ง๋ ์๋ค๋ ๊ฒ์ ์ ์ ์์ต๋๋ค. ์ด ๊ฒฝ์ฐ > ๋ฒํผ์ ๋นํ์ฑํ๋ฉ๋๋ค.
ํ์ง๋ง, ์ด ๋ฐฉ์์ผ๋ก ๋ค์ ํ์ด์ง ๊ทธ๋ฃน์ผ๋ก ์ด๋ ๋ฒํผ(>) ์ ๊ตฌํ ํ ์ ์์ง๋ง, ์ด ๊ฐ์๋ ์ฌ์ ํ ๋ชจ๋ฅด๊ธฐ ๋๋ฌธ์ ๋ง์ง๋ง ํ์ด์ง๋ก ํ ๋ฒ์ ๊ฐ ์ ์์ต๋๋ค.
์ด๋ ์ ๋ ฌ ์ต์ ์ asc ๋ก ์ฃผ๋ ๊ฒ์ desc ๋ก ๋ณ๊ฒฝํ์ฌ ํด๊ฒฐ ํ์์ต๋๋ค.
์ถ๊ฐ ๊ณ ๋ ค ์ฌํญ: YML ์ค์ ์ ๋ฐ๋ผ Bean ์ฃผ์ ํ๊ธฐ
๊ธฐ์กด์๋ 10,000๊ฑด ์ด์์ ๋ฐ์ดํฐ ์กฐํ ์๊ตฌ์ฌํญ์ด ์์ด, hits.total.value๊ฐ 10,000๊ฑด์ด๊ณ relation์ด gte์ผ ๊ฒฝ์ฐ ๋ฐฑ์คํผ์ค UI ์์์ '10,000+'๋ก ํ์ํ๊ณ ์ต๋ 10,000๊ฑด๊น์ง๋ง ํ์ด์ง์ ์ ๊ณตํ์ต๋๋ค
ํ์ง๋ง, "10,000๊ฑด ์ด์์ ๋ฌธ์๋ ์ต๋ํ ๋ชจ๋ ๋ณผ ์ ์์ผ๋ฉด ์ข๊ฒ ๋ค"๋ ์ด๋ฒ ๊ณ ๊ฐ์ฌ์ ์๊ตฌ์ฌํญ์ ๋ง์ถฐ search_after๋ฅผ ์ ์ฉํ์ต๋๋ค.
๊ทธ๋ฌ๋ค ๋ณด๋ ํฅํ ๋ค๋ฅธ ๊ณ ๊ฐ์ฌ์์๋ ์ด์ ๊ฐ์ ์๊ตฌ์ฌํญ์ด ๋ฐ์ํ ์ ์๋ค๊ณ ํ๋จํ์ฌ, ํ๊ฒฝ ์ค์ (YML) ๊ฐ์ ๋ฐ๋ผ ๊ธฐ์กด์ Offset ๋ฐฉ์(from/size)๊ณผ ์๋ก์ด ์ปค์ ๊ธฐ๋ฐ(search_after) ๋ฐฉ์ ์ค ํ๋๋ฅผ ์ ํ์ ์ผ๋ก ์ฃผ์
ํ๊ณ ์ ํ์ต๋๋ค. ์๋๋ ๊ทธ ๊ตฌํ ์์์
๋๋ค.

์๋ ์ฒ๋ผ @ConditionalOnProperty ๋ฅผ ์ฌ์ฉํด yml ์ด ๊ฐ์ง๋ ๊ฐ์ ๊ธฐ์ค์ผ๋ก ๋น์ ๋ฑ๋กํ๊ณ , ์ค์ ๊ฐ์ด ์์ ๊ฒฝ์ฐ๋ ๊ณ ๋ คํด ๊ธฐ๋ณธ ๊ฐ๋ ์ค์ ํ์ต๋๋ค.
@Slf4j
@Configuration
public class OpenSearchQueryServiceConfig {
@Bean
@ConditionalOnProperty(name = "opensearch.search.offset.enabled", havingValue = "true", matchIfMissing = true)
public OpenSearchQueryService openSearchOffsetQueryService(..., OpenSearchRestClient openSearchRestClient) {
return new OpenSearchOffsetQueryService(..., openSearchRestClient);
}
@Bean
@ConditionalOnProperty(name = "opensearch.search.offset.enabled", havingValue = "false")
public OpenSearchQueryService openSearchSearchAfterQueryService(..., OpenSearchRestClient openSearchRestClient) {
return new OpenSearchSearchAfterQueryService(..., openSearchRestClient);
}
}
ํ์ง๋ง, ์ ๋ฐฉ์์ ํ๋์ API ์๋ํฌ์ธํธ์์ YML ์ค์ ์ ๋ฐ๋ผ ๋ด๋ถ ๋์ ๋ฐฉ์์ด ๋ฌ๋ผ์ง๊ณ , ๊ทธ ๊ฒฐ๊ณผ API ์๋ฒ๊ฐ ์๊ตฌํ๋ Query Parameter(Offset ๋ฐฉ์์ page, limit / search_after ๋ฐฉ์์ lastMetaId, limit)๊ฐ ๋ฌ๋ผ์ง๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค.
์ด๋ ํ๋ก ํธ์๋ ์ฝ๋์ ๋ถ๊ธฐ๋ฌธ์ ๋๋ ค ๋ถํ์ํ ๋ณต์ก์ฑ์ ์ ๋ฐํ๋ค๊ณ ํ๋จํ์ต๋๋ค.
๋ฐ๋ผ์, YML์ ๋ฐ๋ฅธ Bean ์ฃผ์ ๋์ API๋ฅผ ๋ถ๋ฆฌํ๊ธฐ๋ก ๊ฒฐ์ ํ์ต๋๋ค.
- Offset ๊ธฐ๋ฐ ํ์ด์ง: /api/offset
- search_after ๊ธฐ๋ฐ ํ์ด์ง: /api/cursor
Query Parameter๊ฐ ๋ฌ๋ผ์ง๋ ๊ฒฝ์ฐ, ์ฝ๋๋ฅผ ์ฐ์ํ๊ฒ ํตํฉํ๋ ค ํ๊ธฐ๋ณด๋ค๋ API๋ฅผ ๋ช ํํ๊ฒ ๋ถ๋ฆฌํ๋ ๊ฒ์ด ํ์ ๊ณผ ์ ์ง๋ณด์ ์ธก๋ฉด์์ ๋ ํจ์จ์ ์ด๋ผ๋ ๊ฒ์ ๋ฐฐ์ธ ์ ์์์ต๋๋ค.
๋ง๋ฌด๋ฆฌ
์ถ๊ฐ ํ์ต ํฌ์ธํธ, Doc Value ๋?
MySQL ์ ์ธ์ปจ๋๋ฆฌ ์ธ๋ฑ์ค๋ฅผ ํ์ฉํด ์ปค๋ฒ๋ง ์ธ๋ฑ์ค๋ก where ์กฐ๊ฑด์ ๋ง๋ PK๋ฅผ ๊ฐ์ ธ์จ ํ PK๋ฅผ ๋ฐํ์ผ๋ก select ์ ์ ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ผ๋ก ๋์ฉ๋ ๋ฐ์ดํฐ๋ฅผ ๋น ๋ฅด๊ฒ ์ฒ๋ฆฌํฉ๋๋ค. ์ด๋ ์ธ๋ฑ์ค๊ฐ ๋ฏธ๋ฆฌ ์ ๋ ฌ๋์ด ์๋ค๋ ํน์ฑ์ ์ด์ฉํ ๊ฒ์ ๋๋ค.
OpenSearch ๋ ์ ๋ ฌ๊ณผ ์ง๊ณ๋ฅผ ์ํด Doc values ๋ฅผ ์ฌ์ฉํ๋ค๊ณ ํฉ๋๋ค. Doc values๊ฐ ์ด๋ค ๋ฐฉ์์ผ๋ก ๊ตฌ์ฑ๋์ด ์๊ธฐ์ OpenSearch์์ ๋น ๋ฅธ ์ ๋ ฌ์ด ๊ฐ๋ฅํ์ง, ์ญ ์ธ๋ฑ์ค์๋ ์ด๋ป๊ฒ ๋ค๋ฅธ์ง ์ถ๊ฐ ํ์ต์ด ํ์ํ๋ค๊ณ ์๊ฐํฉ๋๋ค.
์ถํ์ ํ์ต ํ, ํฌ์คํ ํด๋ณด๊ฒ ์ต๋๋ค.