Вступление
Полнотекстовый поиск Couchbase (FTS) отлично подходит для индексации нескольких массивов и выполнения запросов с несколькими предикатами фильтра в массивах. В этой статье я продемонстрирую преимущества использования FTS над GSI (Global Secondary Index) для индексации массивов при работе с примером варианта использования, который требует запроса нескольких массивов. Мы будем создавать индекс для нескольких массивов FTS и запрашивать индекс с помощью N1QL с помощью новой функции SEARCH (), представленной в Couchbase Server 6.5.
Ведро с образцом путешествия
В этой статье мы будем ссылаться на набор данных Travel Sample, доступный для установки в любом экземпляре Couchbase Server. Ведро с образцом путешествия имеет несколько различных типов документов: авиакомпания, маршрут, аэропорт, ориентир и гостиница. Модель документа для каждого вида документа содержит:
-
Ключ, который действует как первичный ключ
-
Поле id, которое идентифицирует документ
-
Поле типа, определяющее тип документа
Примеры в этой статье будут использовать документы отеля. Приведенный ниже образец документа дает представление о структуре документа отеля:
Рисунок 1 — Пример документа отеля
Эта проблема
Наш пример — это случай использования, когда пользователь может искать отели, которые были просмотрены или понравились человеку с конкретным именем. Для этого необходимо запросить документы отеля, касающиеся как лайков, так и отзывов, которые являются массивами в модели документов отеля:
Рисунок 2. Массивы «public_likes» и «reviews» в примере документа гостиницы
Сначала давайте рассмотрим реализацию этого варианта использования с N1QL и GSI (Global Secondary Index). Чтобы найти отели, которые кому-либо по имени Ozella понравились или просмотрели, запрос может выглядеть следующим образом:
SQL
xxxxxxxxxx
1
SELECT name, address, city, country,
2
phone, public_likes, reviews
3
FROM `travel-sample`
4
WHERE type="hotel"
5
AND (ANY l IN public_likes SATISFIES l LIKE "%Ozella%" END
6
OR ANY r IN reviews SATISFIES r.author LIKE "%Ozella%" END);
Нам нужно создать соответствующий индекс для этого запроса. Может быть что-то вроде этого, которое индексирует оба массива интереса для документов отеля:
SQL
xxxxxxxxxx
1
CREATE INDEX idx_hotel_public_likes_review_author ON `travel-sample`
2
(DISTINCT ARRAY `l` FOR l IN `public_likes` END,
3
DISTINCT ARRAY `r`.`author` FOR r IN `reviews` END)
4
WHERE `type` = 'hotel';
Это не работает, и мы получаем ошибку, показанную на рисунке 3:
Рисунок 3 — Ошибка создания индекса с несколькими массивами
Как писал Кешав Мурти в своем блоге « Поиск и спасение»: 7 причин для разработчиков N1QL (SQL) использовать Search (задача № 6) с N1QL в Couchbase «для достижения максимальной производительности при поиске внутри массивов, вам необходимо создать индексы». с ключами массива . Индекс массива имеет ограничение: каждый индекс массива может иметь только один ключ массива на индекс. Поэтому, когда у вас есть объект customer с несколькими полями массива, вы не можете искать их все по одному индексу … вызывая дорогостоящие запросы ». Как отмечает Кешав в этой статье, это ограничение для индексов b-дерева в базах данных вообще.
Итак, теперь давайте попробуем два отдельных индекса массива. Индексы для поддержки этого запроса могут выглядеть следующим образом, которые были созданы с помощью Couchbase N1QL Index Advisor , новой функции (DP) в Couchbase 6.5:
SQL
xxxxxxxxxx
1
CREATE INDEX adv_DISTINCT_public_likes_type ON `travel-sample`(DISTINCT ARRAY `l` FOR l in `public_likes` END) WHERE `type` = 'hotel';
2
CREATE INDEX adv_DISTINCT_reviews_author_type ON `travel-sample`(DISTINCT ARRAY `r`.`author` FOR r in `reviews` END) WHERE `type` = 'hotel';
С этими двумя индексами наш запрос успешно выполняется с 5 результатами (hotel_26020, hotel_10025, hotel_5081, hotel_20425, hotel_25327) и следующим планом выполнения:
Рисунок 4 - План выполнения с использованием нескольких индексов (GSI)
Тот же план в JSON:
JSON
xxxxxxxxxx
1
{
2
"#operator": "Sequence",
3
"#stats": {
4
"#phaseSwitches": 1,
5
"execTime": "1.321µs"
6
},
7
"~children": [
8
{
9
"#operator": "Authorize",
10
"#stats": {
11
"#phaseSwitches": 3,
12
"execTime": "3.034µs",
13
"servTime": "1.037859ms"
14
},
15
"privileges": {
16
"List": [
17
{
18
"Target": "default:travel-sample",
19
"Priv": 7
20
}
21
]
22
},
23
"~child": {
24
"#operator": "Sequence",
25
"#stats": {
26
"#phaseSwitches": 1,
27
"execTime": "2.235µs"
28
},
29
"~children": [
30
{
31
"#operator": "UnionScan",
32
"#stats": {
33
"#itemsIn": 1646,
34
"#itemsOut": 904,
35
"#phaseSwitches": 5107,
36
"execTime": "1.32474ms",
37
"kernTime": "113.495553ms"
38
},
39
"scans": [
40
{
41
"#operator": "DistinctScan",
42
"#stats": {
43
"#itemsIn": 4004,
44
"#itemsOut": 813,
45
"#phaseSwitches": 9641,
46
"execTime": "1.381997ms",
47
"kernTime": "69.065425ms"
48
},
49
"scan": {
50
"#operator": "IndexScan3",
51
"#stats": {
52
"#itemsOut": 4004,
53
"#phaseSwitches": 16021,
54
"execTime": "19.678094ms",
55
"kernTime": "30.973177ms",
56
"servTime": "17.461885ms"
57
},
58
"index": "adv_DISTINCT_public_likes_type",
59
"index_id": "288083a758973630",
60
"index_projection": {
61
"primary_key": true
62
},
63
"keyspace": "travel-sample",
64
"namespace": "default",
65
"spans": [
66
{
67
"range": [
68
{
69
"high": "[]",
70
"inclusion": 1,
71
"low": "\"\""
72
}
73
]
74
}
75
],
76
"using": "gsi",
77
"#time_normal": "00:00.037",
78
"#time_absolute": 0.037139979000000004
79
},
80
"#time_normal": "00:00.001",
81
"#time_absolute": 0.0013819969999999998
82
},
83
{
84
"#operator": "DistinctScan",
85
"#stats": {
86
"#itemsIn": 4104,
87
"#itemsOut": 833,
88
"#phaseSwitches": 9881,
89
"execTime": "2.475034ms",
90
"kernTime": "80.914158ms"
91
},
92
"scan": {
93
"#operator": "IndexScan3",
94
"#stats": {
95
"#itemsOut": 4104,
96
"#phaseSwitches": 16421,
97
"execTime": "8.610445ms",
98
"kernTime": "52.02497ms",
99
"servTime": "22.586149ms"
100
},
101
"index": "adv_DISTINCT_reviews_author_type",
102
"index_id": "cca7f912cab1a4c6",
103
"index_projection": {
104
"primary_key": true
105
},
106
"keyspace": "travel-sample",
107
"namespace": "default",
108
"spans": [
109
{
110
"range": [
111
{
112
"high": "[]",
113
"inclusion": 1,
114
"low": "\"\""
115
}
116
]
117
}
118
],
119
"using": "gsi",
120
"#time_normal": "00:00.031",
121
"#time_absolute": 0.031196594
122
},
123
"#time_normal": "00:00.002",
124
"#time_absolute": 0.002475034
125
}
126
],
127
"#time_normal": "00:00.001",
128
"#time_absolute": 0.00132474
129
},
130
{
131
"#operator": "Fetch",
132
"#stats": {
133
"#itemsIn": 904,
134
"#itemsOut": 904,
135
"#phaseSwitches": 3733,
136
"execTime": "2.887995ms",
137
"kernTime": "8.826606ms",
138
"servTime": "170.010321ms"
139
},
140
"keyspace": "travel-sample",
141
"namespace": "default",
142
"#time_normal": "00:00.172",
143
"#time_absolute": 0.172898316
144
},
145
{
146
"#operator": "Parallel",
147
"#stats": {
148
"#phaseSwitches": 1,
149
"execTime": "6.134µs"
150
},
151
"copies": 2,
152
"~child": {
153
"#operator": "Sequence",
154
"#stats": {
155
"#phaseSwitches": 2,
156
"execTime": "3.621µs"
157
},
158
"~children": [
159
{
160
"#operator": "Filter",
161
"#stats": {
162
"#itemsIn": 904,
163
"#itemsOut": 5,
164
"#phaseSwitches": 1824,
165
"execTime": "279.461548ms",
166
"kernTime": "85.245883ms"
167
},
168
"condition": "(((`travel-sample`.`type`) = \"hotel\") and (any `l` in (`travel-sample`.`public_likes`) satisfies (`l` like \"%Ozella%\") end or any `r` in (`travel-sample`.`reviews`) satisfies ((`r`.`author`) like \"%Ozella%\") end))",
169
"#time_normal": "00:00.279",
170
"#time_absolute": 0.279461548
171
},
172
{
173
"#operator": "InitialProject",
174
"#stats": {
175
"#itemsIn": 5,
176
"#itemsOut": 5,
177
"#phaseSwitches": 25,
178
"execTime": "7.156613ms",
179
"kernTime": "357.453351ms"
180
},
181
"result_terms": [
182
{
183
"expr": "(`travel-sample`.`name`)"
184
},
185
{
186
"expr": "(`travel-sample`.`address`)"
187
},
188
{
189
"expr": "(`travel-sample`.`city`)"
190
},
191
{
192
"expr": "(`travel-sample`.`country`)"
193
},
194
{
195
"expr": "(`travel-sample`.`phone`)"
196
},
197
{
198
"expr": "(`travel-sample`.`public_likes`)"
199
},
200
{
201
"expr": "(`travel-sample`.`reviews`)"
202
}
203
],
204
"#time_normal": "00:00.007",
205
"#time_absolute": 0.007156613
206
},
207
{
208
"#operator": "FinalProject",
209
"#stats": {
210
"#itemsIn": 5,
211
"#itemsOut": 5,
212
"#phaseSwitches": 17,
213
"execTime": "12.167µs",
214
"kernTime": "98.849µs"
215
},
216
"#time_normal": "00:00.000",
217
"#time_absolute": 0.000012167
218
}
219
],
220
"#time_normal": "00:00.000",
221
"#time_absolute": 0.000003621
222
},
223
"#time_normal": "00:00.000",
224
"#time_absolute": 0.000006134
225
}
226
],
227
"#time_normal": "00:00.000",
228
"#time_absolute": 0.0000022349999999999998
229
},
230
"#time_normal": "00:00.001",
231
"#time_absolute": 0.0010408930000000002
232
},
233
{
234
"#operator": "Stream",
235
"#stats": {
236
"#itemsIn": 5,
237
"#itemsOut": 5,
238
"#phaseSwitches": 13,
239
"execTime": "939.145µs",
240
"kernTime": "182.523171ms"
241
},
242
"#time_normal": "00:00.000",
243
"#time_absolute": 0.000939145
244
}
245
],
246
"~versions": [
247
"2.0.0-N1QL",
248
"6.5.0-4960-enterprise"
249
],
250
"#time_normal": "00:00.000",
251
"#time_absolute": 0.000001321
252
}
В одноузловом кластере, используемом в этих примерах, время запроса составляет около 190-200 миллисекунд для возврата 5 итоговых документов. Как видно из плана, есть два оператора IndexScan3, которые используют каждый из двух созданных нами индексов массива, затем DistinctScan для результатов каждого сканирования индекса и затем UnionScan. UnionScan показывает значение #itemsIn из 1646 документов и значение #itemsOut из 904 документов, оператор Fetch также получает 904 документа, и, наконец, с помощью оператора Filter мы получаем значение #ItemsOut, равное 5. Выборка из 904 документов является потеря, учитывая, что в результате мы получили 5 документов, возвращенных запросом, и фактически около 170 миллисекунд общего затраченного времени тратится на выборку 905 документов, когда требуется только 5.
Решение
Напротив, инвертированный индекс FTS может быть легко создан для нескольких массивов и хорошо подходит для случаев, когда вам нужно искать поля в нескольких массивах. Мы создадим индекс FTS для документов отеля как для массива public_likes, так и для поля автора в массиве отзывов.
Этапы создания индекса:
-
В пользовательском интерфейсе полнотекстового поиска нажмите «Добавить индекс».
-
Укажите имя индекса, например, «hotel_mult_arrays», и выберите сегмент выборки для путешествий.
-
Поскольку каждый документ в корзине с образцами командировок имеет поле «тип», указывающее тип документа, оставьте для поля «Тип JSON» значение «тип».
-
Под типом отображений:
-
Нажмите «+ Добавить сопоставление типов» и укажите «hotel» в качестве имени типа, поскольку необходимо выполнить поиск по всем документам отеля.
-
Доступ к списку доступных анализаторов можно получить с помощью выпадающего меню справа от поля имени типа. Для этого варианта использования оставьте «Наследовать» выбранным, чтобы сопоставление типов наследовало анализатор по умолчанию из индекса.
-
Поскольку требование заключается в том, чтобы искать в общедоступных лайках отеля и просматривать поля авторов, отметьте «индексировать только указанные поля». Если этот флажок установлен, в индекс для сопоставления типа отеля включаются только указанные пользователем поля из документа (сопоставление не будет динамическим, что означает, что все поля считаются доступными для индексации).
-
Нажмите ОК.
-
Наведите указатель мыши на строку с отображением типа отеля, нажмите кнопку +, а затем нажмите «вставить дочернее поле». Это позволит индивидуально включить массив public_likes в индекс. Укажите следующее:
-
field: введите имя поля для индексации, «public_likes».
-
type: оставьте этот набор для текста для массива public_likes.
-
searchable as: Оставьте это так же, как имя поля для текущего варианта использования. Может использоваться для указания альтернативного имени поля.
-
анализатор: Как и в случае с сопоставлением типов, для этого варианта использования оставьте «наследовать» выбранным, чтобы сопоставление типов наследовало анализатор по умолчанию.
-
Флажок index: оставьте этот флажок установленным, чтобы поле включалось в индекс. Если снять флажок, поле будет явно удалено из индекса.
-
флажок store: установите этот флажок, чтобы включить содержимое результатов в результаты поиска, что позволяет выделять соответствующие выражения в результатах. Это полезно для тестирования индекса, но не рекомендуется в Prod, если подсветка не требуется, поскольку она увеличивает размер индекса.
-
Флажок «включить в _ все поле»: оставьте этот флажок установленным, поскольку требование варианта использования заключается в поиске в нескольких полях.
-
Флажок «включить векторы терминов»: оставьте этот флажок также во время разработки и тестирования нашего индекса, чтобы можно было выделить результаты.
-
Флажок docvalues: снимите этот флажок. Этот параметр хранит значения полей в индексе, который обеспечивает поддержку фасетов поиска и сортировку результатов поиска по значениям полей, которые нам не нужны в этом случае использования.
-
Нажмите ОК.
-
-
Наведите указатель мыши на строку с отображением типа отеля, нажмите кнопку + и выберите «вставить дочернее сопоставление». Это позволит включить в индекс массив проверочных поддокументов. Введите имя свойства «отзывы», оставьте «наследовать» выбранным в раскрывающемся списке анализатора, отметьте «индексировать только указанные поля» и нажмите «ОК».
-
Наведите указатель мыши на строку с дочерним сопоставлением рецензий, нажмите кнопку + и нажмите «вставить дочернее поле». Это позволит включить в индекс поле автора из массива поддокументов рецензирования. Укажите следующее:
-
field: введите имя поля для индексации, «автор».
-
type: оставьте этот текст для поля автора.
-
searchable as: Оставьте это так же, как имя поля для текущего варианта использования. Может использоваться для указания альтернативного имени поля.
-
анализатор: Как и в случае с сопоставлением типов, для этого варианта использования оставьте «наследовать» выбранным, чтобы сопоставление типов наследовало анализатор по умолчанию.
-
Флажок index: оставьте этот флажок установленным, чтобы поле включалось в индекс. Если снять флажок, поле будет явно удалено из индекса.
-
флажок store: установите этот флажок, чтобы включить содержимое результатов в результаты поиска, что позволяет выделять соответствующие выражения в результатах. Это полезно для тестирования индекса, но не рекомендуется в Prod, если подсветка не требуется, поскольку она увеличивает размер индекса.
-
Флажок «включить в _ все поле»: оставьте этот флажок установленным, поскольку требование варианта использования заключается в поиске в нескольких полях.
-
Флажок «включить векторы терминов»: оставьте этот флажок также во время разработки и тестирования нашего индекса, чтобы можно было выделить результаты.
-
Флажок docvalues: снимите этот флажок. Этот параметр хранит значения полей в индексе, который обеспечивает поддержку фасетов поиска и сортировку результатов поиска по значениям полей, которые нам не нужны в этом случае использования.
-
Нажмите ОК.
-
-
Наконец, снимите флажок рядом с отображением типа «по умолчанию». Если сопоставление по умолчанию оставлено включенным, все документы в корзине включаются в индекс независимо от того, активно ли пользователь указывает сопоставления типов. Требуются только документы отеля, и они включены в карту типа отеля, добавленную ранее.
-
-
Значения по умолчанию достаточны для оставшихся свернутых панелей (анализаторы, пользовательские фильтры, анализаторы даты и времени и расширенные).
-
Реплики индекса можно установить в 1, 2 или 3, при условии, что в кластере запущена служба поиска на n + 1 узлах. В среде разработки с одним узлом значение по умолчанию составляет 0.
-
Для типа индекса значение по умолчанию «Версия 6.0 (Scorch)» подходит для всех вновь создаваемых индексов. Scorch уменьшает размер индекса на диске и обеспечивает повышенную производительность для индексации и обработки мутаций.
-
Для разделов индекса можно оставить значение по умолчанию 6.
-
На этом этапе страница создания индекса должна выглядеть как последний кадр, захваченный на рисунке 5. Нажмите «Создать индекс», чтобы завершить процесс.
Рисунок 5 - Создание индекса FTS с несколькими массивами
Примечание: см. Приложение для полезной нагрузки JSON, используемой для создания этого индекса через REST API.
Тестирование запросов по индексу:
-
В пользовательском интерфейсе полнотекстового поиска дождитесь, пока прогресс индексации покажет 100%, затем нажмите на имя индекса «hotel_mult_arrays».
-
Чтобы найти отели с лайками или отзывами кого-то по имени «Озелла», в текстовом поле «Поиск по этому индексу…» введите «Озелла» и нажмите «Поиск». Определение области поиска не требуется, поскольку оба индексированных поля включены в поле по умолчанию «_all».
-
Результаты показаны (аналогично рисунку 6) с ключом каждого соответствующего документа и выделены соответствующие поля. Возвращенные идентификаторы документов такие же, как в нашем предыдущем запросе N1QL.
Рисунок 6 - Индекс результатов поиска «hotel_mult_arrays» для «Ozella»
Это один индекс для двух ключей массива, который, как упоминалось ранее, никогда нельзя делать в индексе на основе b-дерева. Итак, теперь давайте воспользуемся этим индексом FTS в запросе N1QL с помощью функции SEARCH (). Наш запрос может выглядеть так:
SQL
xxxxxxxxxx
1
SELECT name, address, city, country,
2
phone, public_likes, reviews
3
FROM `travel-sample` AS t USE INDEX(hotel_mult_arrays USING FTS)
4
WHERE t.type="hotel"
5
AND SEARCH(t, {"query": {"match":"Ozella"}}, {"index":"hotel_mult_arrays"});
Несколько замечаний по поводу запроса:
-
Предложение USE INDEX ... USING FTS указывает, что следует использовать индекс FTS, а не индекс GSI, поэтому этот запрос не использует службу индекса. ( Документация )
-
Поскольку в нашем индексе FTS используется сопоставление пользовательских типов, в запросе должен быть указан соответствующий тип, указанный в предложении WHERE (t.type = "hotel").
-
Имя индекса FTS указывается в поле «index» в функции SEARCH () как подсказка, но это необязательно, так как предложение USE INDEX имеет приоритет над подсказкой, предоставленной в поле «index». ( Документация )
Используя созданный нами индекс FTS, наш запрос N1QL успешно выполняется и возвращает 5 результатов (hotel_5081, hotel_26020, hotel_10025, hotel_20425, hotel_25327) и следующий план выполнения:
Рисунок 7 - План выполнения с использованием нескольких индексов (FTS)
Тот же план в JSON:
JSON
xxxxxxxxxx
1
{
2
"#operator": "Sequence",
3
"#stats": {
4
"#phaseSwitches": 1,
5
"execTime": "18.8µs"
6
},
7
"~children": [
8
{
9
"#operator": "Authorize",
10
"#stats": {
11
"#phaseSwitches": 3,
12
"execTime": "32.1µs",
13
"servTime": "3.421ms"
14
},
15
"privileges": {
16
"List": [
17
{
18
"Target": "default:travel-sample",
19
"Priv": 7
20
}
21
]
22
},
23
"~child": {
24
"#operator": "Sequence",
25
"#stats": {
26
"#phaseSwitches": 1,
27
"execTime": "122.8µs"
28
},
29
"~children": [
30
{
31
"#operator": "IndexFtsSearch",
32
"#stats": {
33
"#itemsOut": 5,
34
"#phaseSwitches": 23,
35
"execTime": "239.3µs",
36
"kernTime": "84.5µs",
37
"servTime": "3.9146ms"
38
},
39
"as": "t",
40
"index": "hotel_mult_arrays",
41
"index_id": "7a28a8346fad6118",
42
"keyspace": "travel-sample",
43
"namespace": "default",
44
"search_info": {
45
"field": "\"\"",
46
"options": "{\"index\": \"hotel_mult_arrays\"}",
47
"outname": "out",
48
"query": "{\"query\": {\"match\": \"Ozella\"}}"
49
},
50
"using": "fts",
51
"#time_normal": "00:00.004",
52
"#time_absolute": 0.0041539
53
},
54
{
55
"#operator": "Fetch",
56
"#stats": {
57
"#itemsIn": 5,
58
"#itemsOut": 5,
59
"#phaseSwitches": 25,
60
"execTime": "334.8µs",
61
"kernTime": "4.4328ms",
62
"servTime": "1.5272ms"
63
},
64
"as": "t",
65
"keyspace": "travel-sample",
66
"namespace": "default",
67
"#time_normal": "00:00.001",
68
"#time_absolute": 0.001862
69
},
70
{
71
"#operator": "Parallel",
72
"#stats": {
73
"#phaseSwitches": 1,
74
"execTime": "21.1µs"
75
},
76
"copies": 2,
77
"~child": {
78
"#operator": "Sequence",
79
"#stats": {
80
"#phaseSwitches": 2,
81
"execTime": "49.1µs"
82
},
83
"~children": [
84
{
85
"#operator": "Filter",
86
"#stats": {
87
"#itemsIn": 5,
88
"#itemsOut": 5,
89
"#phaseSwitches": 26,
90
"execTime": "6.8953ms",
91
"kernTime": "14.8149ms"
92
},
93
"condition": "(((`t`.`type`) = \"hotel\") and search(`t`, {\"query\": {\"match\": \"Ozella\"}}, {\"index\": \"hotel_mult_arrays\"}))",
94
"#time_normal": "00:00.006",
95
"#time_absolute": 0.0068953
96
},
97
{
98
"#operator": "InitialProject",
99
"#stats": {
100
"#itemsIn": 5,
101
"#itemsOut": 5,
102
"#phaseSwitches": 25,
103
"execTime": "2.3597ms",
104
"kernTime": "20.7458ms"
105
},
106
"result_terms": [
107
{
108
"expr": "(`t`.`name`)"
109
},
110
{
111
"expr": "(`t`.`address`)"
112
},
113
{
114
"expr": "(`t`.`city`)"
115
},
116
{
117
"expr": "(`t`.`country`)"
118
},
119
{
120
"expr": "(`t`.`phone`)"
121
},
122
{
123
"expr": "(`t`.`public_likes`)"
124
},
125
{
126
"expr": "(`t`.`reviews`)"
127
}
128
],
129
"#time_normal": "00:00.002",
130
"#time_absolute": 0.0023597
131
},
132
{
133
"#operator": "FinalProject",
134
"#stats": {
135
"#itemsIn": 5,
136
"#itemsOut": 5,
137
"#phaseSwitches": 17,
138
"execTime": "300µs",
139
"kernTime": "375.9µs"
140
},
141
"#time_normal": "00:00",
142
"#time_absolute": 0
143
}
144
],
145
"#time_normal": "00:00.000",
146
"#time_absolute": 0.0000491
147
},
148
"#time_normal": "00:00.000",
149
"#time_absolute": 0.0000211
150
}
151
],
152
"#time_normal": "00:00.000",
153
"#time_absolute": 0.0001228
154
},
155
"#time_normal": "00:00.003",
156
"#time_absolute": 0.0034531
157
},
158
{
159
"#operator": "Stream",
160
"#stats": {
161
"#itemsIn": 5,
162
"#itemsOut": 5,
163
"#phaseSwitches": 13,
164
"execTime": "1.3409ms",
165
"kernTime": "14.8586ms"
166
},
167
"#time_normal": "00:00.001",
168
"#time_absolute": 0.0013409
169
}
170
],
171
"~versions": [
172
"2.0.0-N1QL",
173
"6.5.0-4960-enterprise"
174
],
175
"#time_normal": "00:00.000",
176
"#time_absolute": 0.0000188
177
}
В одноузловом кластере, используемом в этих примерах, время запроса составляет около 20 миллисекунд для возврата тех же 5 документов. Как видно из плана, есть оператор IndexFtsSearch, но нет операторов IndexScan3, DistinctScan, UnionScan или IntersectScan. Общий запрос гораздо эффективнее без этих дорогих операторов GSI. Оператор IndexFtsSearch отправляет 5 соответствующих документов из индекса FTS оператору выборки, который получает только эти 5 документов. Выборка здесь гораздо более эффективна, чем в предыдущем запросе, поскольку она выбирает только 5 против 904 документов, и это также можно наблюдать при сравнении общего истекшего времени (и servTime для операторов выборки: 170 мс в запросе 1 и 1,5 мс в запросе 2) между запросами.
Заключение
С GSI вы можете смешивать и сопоставлять несколько индексов массива в одном запросе, но с FTS вы можете смешивать и сопоставлять несколько массивов в одном индексе FTS (а с FTS нет проблемы с ведущими ключами, как в GSI, в отношении порядка полей в индекс). Как мы показали в этом простом примере запроса 2-х массивов в документах отеля, использование новой функции SEARCH () в N1QL может привести к более простым и более производительным запросам к массивам. Та же концепция может быть применена к запросам, использующим несколько массивов, что даст еще более благоприятные результаты по сравнению с запросами N1QL, использующими несколько индексов массива GSI. Этот подход использует меньше системных ресурсов и обеспечивает более высокую пропускную способность, что приводит к повышению общей эффективности системы.
Это лишь один из примеров преимуществ интеграции между N1QL и FTS, а другие преимущества описаны в постах блога в разделе ссылок ниже.
Ссылки
-
Поисковые ресурсы Couchbase: https://www.couchbase.com/products/full-text-search
-
Документация Couchbase FTS: https://docs.couchbase.com/server/current/fts/full-text-intro.html
-
Couchbase N1QL Search Documentation: https://docs.couchbase.com/server/current/n1ql/n1ql-language-reference/searchfun.html.
-
Сообщения в блоге Couchbase FTS: https://blog.couchbase.com/category/full-text-search/
-
Онлайн-тренинг по Couchbase FTS: https://learn.couchbase.com/store/509465-cb121-intro-to-couchbase-full-text-search-fts
аппендикс
Определение индекса JSON: hotel_mult_arrays
JSON
xxxxxxxxxx
1
{
2
"type": "fulltext-index",
3
"name": "hotel_mult_arrays",
4
"uuid": "5fc5d43dfebc4a60",
5
"sourceType": "couchbase",
6
"sourceName": "travel-sample",
7
"sourceUUID": "a1dd9dbb6aa27a47fac317dabfe74f61",
8
"planParams": {
9
"maxPartitionsPerPIndex": 171,
10
"indexPartitions": 6
11
},
12
"params": {
13
"doc_config": {
14
"docid_prefix_delim": "",
15
"docid_regexp": "",
16
"mode": "type_field",
17
"type_field": "type"
18
},
19
"mapping": {
20
"analysis": {},
21
"default_analyzer": "standard",
22
"default_datetime_parser": "dateTimeOptional",
23
"default_field": "_all",
24
"default_mapping": {
25
"dynamic": true,
26
"enabled": false
27
},
28
"default_type": "_default",
29
"docvalues_dynamic": true,
30
"index_dynamic": true,
31
"store_dynamic": false,
32
"type_field": "_type",
33
"types": {
34
"hotel": {
35
"dynamic": false,
36
"enabled": true,
37
"properties": {
38
"public_likes": {
39
"dynamic": false,
40
"enabled": true,
41
"fields": [
42
{
43
"docvalues": true,
44
"include_in_all": true,
45
"include_term_vectors": true,
46
"index": true,
47
"name": "public_likes",
48
"store": true,
49
"type": "text"
50
}
51
]
52
},
53
"reviews": {
54
"dynamic": false,
55
"enabled": true,
56
"properties": {
57
"author": {
58
"dynamic": false,
59
"enabled": true,
60
"fields": [
61
{
62
"docvalues": true,
63
"include_in_all": true,
64
"include_term_vectors": true,
65
"index": true,
66
"name": "author",
67
"store": true,
68
"type": "text"
69
}
70
]
71
}
72
}
73
}
74
}
75
}
76
}
77
},
78
"store": {
79
"indexType": "scorch"
80
}
81
},
82
"sourceParams": {}
83
}
84