├── .gitignore
├── sample-apps
├── config
│ ├── basic
│ │ ├── components
│ │ │ └── kuromoji-linguistics.jar
│ │ ├── hosts.xml
│ │ ├── searchdefinitions
│ │ │ └── book.sd
│ │ └── services.xml
│ ├── cluster
│ │ ├── components
│ │ │ └── kuromoji-linguistics.jar
│ │ ├── searchdefinitions
│ │ │ ├── price_boost.expression
│ │ │ └── book.sd
│ │ ├── hosts.xml
│ │ └── services.xml
│ └── ranking
│ │ ├── components
│ │ └── kuromoji-linguistics.jar
│ │ ├── searchdefinitions
│ │ ├── price_boost.expression
│ │ └── book.sd
│ │ ├── hosts.xml
│ │ └── services.xml
├── plugin
│ └── setup.sh
└── feed
│ └── book-data-put.json
├── docs
├── src
│ └── main
│ │ └── asciidoc
│ │ ├── images
│ │ ├── vespa_grouping.jpg
│ │ ├── vespa_overview.jpg
│ │ ├── vespa_nn_example.jpg
│ │ ├── vespa_architecture.jpg
│ │ ├── vespa_ranking_phase.jpg
│ │ ├── vespa_bucket_distrib.jpg
│ │ ├── vespa_bucket_redistrib.jpg
│ │ ├── vespa_tensor_example.jpg
│ │ ├── vespa_tutorial_cluster.jpg
│ │ └── vespa_example_price_boost.jpg
│ │ ├── docinfo-footer.html
│ │ ├── _include.adoc
│ │ ├── index.adoc
│ │ ├── 3_update.adoc
│ │ ├── 1_setup.adoc
│ │ ├── 7_comparing.adoc
│ │ ├── 2_config.adoc
│ │ ├── 6_clustering.adoc
│ │ └── 4_search.adoc
├── README.md
└── pom.xml
├── appendix
├── elasticsearch
│ ├── Dockerfile
│ ├── docker-compose.yml
│ ├── sample-apps
│ │ ├── config
│ │ │ └── schema.json
│ │ └── feed
│ │ │ └── book-data-put.json
│ └── boot.sh
├── solr
│ ├── docker-compose.yml
│ ├── sample-apps
│ │ ├── config
│ │ │ ├── solrconfig.xml
│ │ │ └── schema.xml
│ │ └── feed
│ │ │ └── book-data-put.json
│ └── boot.sh
└── README.md
├── LICENSE
├── utils
├── vespa_feeder
├── vespa_status
├── vespa_deploy
└── vespa_cluster_status
├── docker-compose.yml
├── README.md
└── boot.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | docs/target
2 | sample-apps/plugin/*
3 | !sample-apps/plugin/setup.sh
4 |
--------------------------------------------------------------------------------
/sample-apps/config/basic/components/kuromoji-linguistics.jar:
--------------------------------------------------------------------------------
1 | ../../../plugin/kuromoji-linguistics.jar
--------------------------------------------------------------------------------
/sample-apps/config/cluster/components/kuromoji-linguistics.jar:
--------------------------------------------------------------------------------
1 | ../../../plugin/kuromoji-linguistics.jar
--------------------------------------------------------------------------------
/sample-apps/config/ranking/components/kuromoji-linguistics.jar:
--------------------------------------------------------------------------------
1 | ../../../plugin/kuromoji-linguistics.jar
--------------------------------------------------------------------------------
/sample-apps/config/cluster/searchdefinitions/price_boost.expression:
--------------------------------------------------------------------------------
1 | 1.0 - tanh(log10(attribute(price)/1000))
2 |
--------------------------------------------------------------------------------
/sample-apps/config/ranking/searchdefinitions/price_boost.expression:
--------------------------------------------------------------------------------
1 | 1.0 - tanh(log10(attribute(price)/1000))
2 |
--------------------------------------------------------------------------------
/docs/src/main/asciidoc/images/vespa_grouping.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yahoojapan/vespa-tutorial/HEAD/docs/src/main/asciidoc/images/vespa_grouping.jpg
--------------------------------------------------------------------------------
/docs/src/main/asciidoc/images/vespa_overview.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yahoojapan/vespa-tutorial/HEAD/docs/src/main/asciidoc/images/vespa_overview.jpg
--------------------------------------------------------------------------------
/docs/src/main/asciidoc/images/vespa_nn_example.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yahoojapan/vespa-tutorial/HEAD/docs/src/main/asciidoc/images/vespa_nn_example.jpg
--------------------------------------------------------------------------------
/docs/src/main/asciidoc/images/vespa_architecture.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yahoojapan/vespa-tutorial/HEAD/docs/src/main/asciidoc/images/vespa_architecture.jpg
--------------------------------------------------------------------------------
/docs/src/main/asciidoc/images/vespa_ranking_phase.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yahoojapan/vespa-tutorial/HEAD/docs/src/main/asciidoc/images/vespa_ranking_phase.jpg
--------------------------------------------------------------------------------
/docs/src/main/asciidoc/images/vespa_bucket_distrib.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yahoojapan/vespa-tutorial/HEAD/docs/src/main/asciidoc/images/vespa_bucket_distrib.jpg
--------------------------------------------------------------------------------
/docs/src/main/asciidoc/images/vespa_bucket_redistrib.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yahoojapan/vespa-tutorial/HEAD/docs/src/main/asciidoc/images/vespa_bucket_redistrib.jpg
--------------------------------------------------------------------------------
/docs/src/main/asciidoc/images/vespa_tensor_example.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yahoojapan/vespa-tutorial/HEAD/docs/src/main/asciidoc/images/vespa_tensor_example.jpg
--------------------------------------------------------------------------------
/docs/src/main/asciidoc/images/vespa_tutorial_cluster.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yahoojapan/vespa-tutorial/HEAD/docs/src/main/asciidoc/images/vespa_tutorial_cluster.jpg
--------------------------------------------------------------------------------
/docs/src/main/asciidoc/images/vespa_example_price_boost.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yahoojapan/vespa-tutorial/HEAD/docs/src/main/asciidoc/images/vespa_example_price_boost.jpg
--------------------------------------------------------------------------------
/docs/src/main/asciidoc/docinfo-footer.html:
--------------------------------------------------------------------------------
1 |
6 |
7 | {copyright}
8 |
--------------------------------------------------------------------------------
/sample-apps/config/basic/hosts.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 | node1
10 |
11 |
12 |
--------------------------------------------------------------------------------
/sample-apps/config/ranking/hosts.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 | node1
10 |
11 |
12 |
--------------------------------------------------------------------------------
/appendix/elasticsearch/Dockerfile:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2018 Yahoo Japan Corporation.
3 | # Licensed under the terms of the MIT license.
4 | # See LICENSE in the project root.
5 | #
6 | FROM docker.elastic.co/elasticsearch/elasticsearch:6.2.1
7 |
8 | # install japanese tokenizer
9 | RUN elasticsearch-plugin install analysis-kuromoji
10 |
11 | # install ltr plugin
12 | RUN elasticsearch-plugin install http://es-learn-to-rank.labs.o19s.com/ltr-1.0.0-es6.1.2.zip
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 |
6 | # ドキュメント
7 |
8 | チュートリアルのドキュメントは `asciidoc` で記述されています。
9 |
10 | ```
11 | $ ls src/main/asciidoc/
12 | index.adoc images/ ...
13 | ```
14 |
15 | 以下のように `maven` を用いてビルドすることで、HTML 版のドキュメントが生成できます。
16 |
17 | ```
18 | $ mvn
19 | $ ls target/generated-docs/
20 | index.html images/ ...
21 | ```
--------------------------------------------------------------------------------
/sample-apps/config/cluster/hosts.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 | node1
10 |
11 |
12 |
13 | node2
14 |
15 |
16 |
17 | node3
18 |
19 |
20 |
--------------------------------------------------------------------------------
/docs/src/main/asciidoc/_include.adoc:
--------------------------------------------------------------------------------
1 | ////
2 | Copyright 2018 Yahoo Japan Corporation.
3 | Licensed under the terms of the MIT license.
4 | See LICENSE in the project root.
5 | ////
6 |
7 | // common attributes
8 | :author: Yahoo Japan Corporation.
9 | :copyright: Copyright (C) 2018 Yahoo Japan Corporation. All Rights Reserved.
10 |
11 | :imagesdir: images
12 |
13 | :github_url: https://github.com/yahoojapan/vespa-tutorial
14 | :kl_github_url: https://github.com/yahoojapan/vespa-kuromoji-linguistics
--------------------------------------------------------------------------------
/docs/src/main/asciidoc/index.adoc:
--------------------------------------------------------------------------------
1 | ////
2 | Copyright 2018 Yahoo Japan Corporation.
3 | Licensed under the terms of the MIT license.
4 | See LICENSE in the project root.
5 | ////
6 |
7 | = Vespaチュートリアル
8 | include::_include.adoc[]
9 |
10 | このドキュメントでは、OSS 検索エンジンである http://vespa.ai/[Vespa] の使い方について紹介します。
11 |
12 | [TIP]
13 | ====
14 | チュートリアルのコードは以下のリポジトリにあります。
15 |
16 | * {github_url}
17 | ====
18 |
19 | :leveloffset: +1
20 |
21 | include::1_setup.adoc[]
22 |
23 | include::2_config.adoc[]
24 |
25 | include::3_update.adoc[]
26 |
27 | include::4_search.adoc[]
28 |
29 | include::5_ranking.adoc[]
30 |
31 | include::6_clustering.adoc[]
32 |
33 | include::7_comparing.adoc[]
--------------------------------------------------------------------------------
/appendix/solr/docker-compose.yml:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2018 Yahoo Japan Corporation.
3 | # Licensed under the terms of the MIT license.
4 | # See LICENSE in the project root.
5 | #
6 | version: "2"
7 |
8 | services:
9 | zk:
10 | image: zookeeper:3.4.11
11 | container_name: zk
12 | hostname: zk
13 | privileged: true
14 | networks:
15 | solr-nw:
16 | solr:
17 | image: solr:7.2.1
18 | command: solr-foreground -cloud -z zk:2181
19 | container_name: solr
20 | hostname: solr
21 | privileged: true
22 | depends_on:
23 | - zk
24 | volumes:
25 | - ./sample-apps:/solr-sample-apps
26 | ports:
27 | - 8983:8983
28 | networks:
29 | solr-nw:
30 |
31 | networks:
32 | solr-nw:
--------------------------------------------------------------------------------
/appendix/elasticsearch/docker-compose.yml:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2018 Yahoo Japan Corporation.
3 | # Licensed under the terms of the MIT license.
4 | # See LICENSE in the project root.
5 | #
6 | version: "2"
7 |
8 | services:
9 | elasticsearch:
10 | build:
11 | context: .
12 | container_name: elasticsearch
13 | hostname: elasticsearch
14 | privileged: true
15 | volumes:
16 | - ./sample-apps:/es-sample-apps
17 | ports:
18 | - 9200:9200
19 | networks:
20 | elasticsearch-nw:
21 | environment:
22 | discovery.type: single-node
23 | kibana:
24 | image: docker.elastic.co/kibana/kibana:6.2.1
25 | networks:
26 | elasticsearch-nw:
27 | ports:
28 | - 5601:5601
29 |
30 | networks:
31 | elasticsearch-nw:
--------------------------------------------------------------------------------
/appendix/elasticsearch/sample-apps/config/schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "settings" : {
3 | "number_of_shards" : 1
4 | },
5 |
6 | "mappings": {
7 | "book": {
8 | "dynamic": false,
9 | "dynamic_templates": [
10 | {
11 | "reviews": {
12 | "match_mapping_type": "string",
13 | "match": "reviews.*",
14 | "mapping": {
15 | "type": "integer"
16 | }
17 | }
18 | }
19 | ],
20 | "properties": {
21 | "default": {
22 | "type": "text",
23 | "analyzer": "kuromoji"
24 | },
25 | "title": {
26 | "type": "text",
27 | "analyzer": "kuromoji",
28 | "copy_to": "default"
29 | },
30 | "desc": {
31 | "type": "text",
32 | "analyzer": "kuromoji",
33 | "copy_to": "default"
34 | },
35 | "price": {
36 | "type": "integer"
37 | },
38 | "page": {
39 | "type": "integer"
40 | },
41 | "genres": {
42 | "type": "keyword"
43 | }
44 | }
45 | }
46 | }
47 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2018 Yahoo Japan Corporation
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/utils/vespa_feeder:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Copyright 2018 Yahoo Japan Corporation.
4 | # Licensed under the terms of the MIT license.
5 | # See LICENSE in the project root.
6 | #
7 |
8 | set -eu
9 |
10 |
11 | function usage {
12 | cat <&2
13 | usage: $0 FEED_DATA
14 |
15 | NOTE: you need root privilege to execute this script due to "docker-compose".
16 |
17 | ex) feed "book-data-put.json" (i.e., "sample-apps/feed/book-data-put.json") into Vespa.
18 | $ $0 book-data-put.json
19 | EOF
20 | }
21 |
22 | function feed {
23 | feed_data="/vespa-sample-apps/feed/$1"
24 | sudo docker-compose exec vespa1 /bin/bash -c "/opt/vespa/bin/vespa-feeder < ${feed_data}"
25 | return $?
26 | }
27 |
28 |
29 | if [ $# -eq 0 ]; then
30 | usage
31 | exit 1
32 | fi
33 |
34 | # move to project directory to refer docker-compose.yml
35 | PROJ_DIR=$(cd $(dirname $0)/..; pwd)
36 | cd ${PROJ_DIR}
37 |
38 | feed_data=`basename $1`
39 | if [ ! -f "${PROJ_DIR}/sample-apps/feed/${feed_data}" ]; then
40 | echo " failed to find \"${PROJ_DIR}/sample-apps/feed/${feed_data}\"" 1>&2
41 | exit 1
42 | fi
43 | feed ${feed_data}
44 |
45 | exit $?
46 |
--------------------------------------------------------------------------------
/sample-apps/plugin/setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Copyright 2018 Yahoo Japan Corporation.
4 | # Licensed under the terms of the MIT license.
5 | # See LICENSE in the project root.
6 | #
7 |
8 | set -eu
9 |
10 | PLUGIN_DIR=$(cd $(dirname $0);pwd)
11 | VESPA_VERSION=6.214.72
12 | KL_REPO=https://github.com/yahoojapan/vespa-kuromoji-linguistics.git
13 |
14 | if [ -e ${PLUGIN_DIR}/kuromoji-linguistics.jar ]; then
15 | echo " kuromoji-linguistics plugin already exists" 1>&2
16 | else
17 | echo " set up kuromoji-linguistics plugin" 1>&2
18 |
19 | # move to "sample-apps/plugin"
20 | pushd ${PLUGIN_DIR}
21 |
22 | # get and build kuromoji-linguistics
23 | git clone ${KL_REPO}
24 | sudo docker pull maven:3.5.2-jdk-8-slim
25 | # NOTE: use same version with docker image
26 | sudo docker run -it --rm \
27 | -v ${PLUGIN_DIR}/vespa-kuromoji-linguistics:/vespa-kuromoji-linguistics \
28 | -w /vespa-kuromoji-linguistics maven:3.5.2-jdk-8-slim \
29 | mvn clean package -DskipTests -Dvespa.version=${VESPA_VERSION}
30 | sudo ln -s vespa-kuromoji-linguistics/target/kuromoji-linguistics-*-deploy.jar ${PLUGIN_DIR}/kuromoji-linguistics.jar
31 |
32 | # back to the original directory
33 | popd
34 | fi
--------------------------------------------------------------------------------
/sample-apps/config/basic/searchdefinitions/book.sd:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2018 Yahoo Japan Corporation.
3 | # Licensed under the terms of the MIT license.
4 | # See LICENSE in the project root.
5 | #
6 | search book {
7 |
8 | document book {
9 |
10 | field language type string {
11 | indexing: "ja" | set_language
12 | }
13 |
14 | field title type string {
15 | indexing: summary | index
16 | summary-to: simple_set, detail_set
17 | }
18 |
19 | field desc type string {
20 | indexing: summary | index
21 | summary-to: detail_set
22 | }
23 |
24 | field price type int {
25 | indexing: summary | attribute
26 | summary-to: simple_set, detail_set
27 | }
28 |
29 | field page type int {
30 | indexing: summary | attribute
31 | }
32 |
33 | field genres type array {
34 | indexing: summary | attribute
35 | summary-to: detail_set
36 | }
37 |
38 | field reviews type weightedset {
39 | indexing: summary | attribute
40 | }
41 |
42 | }
43 |
44 | fieldset default {
45 | fields: title, desc
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2018 Yahoo Japan Corporation.
3 | # Licensed under the terms of the MIT license.
4 | # See LICENSE in the project root.
5 | #
6 | version: "2"
7 |
8 | services:
9 | vespa1:
10 | image: vespaengine/vespa:6.214.72
11 | command: configserver
12 | container_name: vespa1
13 | hostname: vespa1
14 | privileged: true
15 | volumes:
16 | - ./sample-apps:/vespa-sample-apps
17 | ports:
18 | - 8080:8080
19 | - 19050:19050
20 | - 19071:19071
21 | networks:
22 | vespa-nw:
23 | environment:
24 | VESPA_CONFIGSERVERS: vespa1
25 |
26 | vespa2:
27 | image: vespaengine/vespa:6.214.72
28 | command: services
29 | container_name: vespa2
30 | hostname: vespa2
31 | privileged: true
32 | volumes:
33 | - ./sample-apps:/vespa-sample-apps
34 | ports:
35 | - 8081:8080
36 | networks:
37 | vespa-nw:
38 | environment:
39 | VESPA_CONFIGSERVERS: vespa1
40 |
41 | vespa3:
42 | image: vespaengine/vespa:6.214.72
43 | command: services
44 | container_name: vespa3
45 | hostname: vespa3
46 | privileged: true
47 | volumes:
48 | - ./sample-apps:/vespa-sample-apps
49 | ports:
50 | - 8082:8080
51 | networks:
52 | vespa-nw:
53 | environment:
54 | VESPA_CONFIGSERVERS: vespa1
55 |
56 | networks:
57 | vespa-nw:
58 |
--------------------------------------------------------------------------------
/sample-apps/config/basic/services.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
23 |
24 | search
25 | true
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | 1
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/sample-apps/config/ranking/services.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
23 |
24 | search
25 | true
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | 1
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/utils/vespa_status:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Copyright 2018 Yahoo Japan Corporation.
4 | # Licensed under the terms of the MIT license.
5 | # See LICENSE in the project root.
6 | #
7 |
8 | set -u
9 |
10 |
11 | function check_status {
12 | status=`curl -LI -s -o /dev/null -w '%{http_code}\n' "$1"`
13 | if [ "${status}" == "200" ]; then
14 | echo -e "[ \e[32mOK\e[m ]" 1>&2
15 | return 0
16 | else
17 | echo -e "[ \e[31mNG\e[m ]" 1>&2
18 | return 1
19 | fi
20 | }
21 |
22 | function check_config {
23 | echo -n "configuration server ... " 1>&2
24 | check_status http://localhost:19071/ApplicationStatus
25 | return $?
26 | }
27 |
28 | function check_vespa1 {
29 | echo -n "application server (vespa1) ... " 1>&2
30 | check_status http://localhost:8080/ApplicationStatus
31 | return $?
32 | }
33 |
34 | function check_vespa2 {
35 | echo -n "application server (vespa2) ... " 1>&2
36 | check_status http://localhost:8081/ApplicationStatus
37 | return $?
38 | }
39 |
40 | function check_vespa3 {
41 | echo -n "application server (vespa3) ... " 1>&2
42 | check_status http://localhost:8082/ApplicationStatus
43 | return $?
44 | }
45 |
46 |
47 | declare -a targets=("config" "vespa1" "vespa2" "vespa3")
48 | if [ $# -ne 0 ]; then
49 | targets=($@)
50 | fi
51 |
52 | ret=0
53 | for target in ${targets[@]}; do
54 | eval "check_${target}"
55 | ret=$((ret+$?))
56 | done
57 |
58 | exit ${ret}
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
6 | # vespa-tutorial
7 |
8 | 検索エンジン [Vespa](http://vespa.ai/) の日本語環境チュートリアルのサンプルコードです。
9 |
10 | チュートリアル資料が `gh-pages` として公開されているので、併せて参照してください。
11 |
12 | * https://yahoojapan.github.io/vespa-tutorial/
13 |
14 | ## ライセンス
15 |
16 | このサンプルコードは MIT ライセンスにて提供しています。
17 | 詳しくは LICENSE ファイルをご確認ください。
18 |
19 | ## 事前準備
20 |
21 | このチュートリアルの実行には以下の2つのソフトウェアが必要です。
22 |
23 | * [`docker`](https://www.docker.com/)
24 | * [`docker-compose`](https://docs.docker.com/compose/)
25 |
26 | 事前にこれら2つを実行環境にインストールしてください。
27 |
28 | ### CentOS7 での例
29 |
30 | ```bash
31 | // install docker
32 | $ sudo yum install docker
33 | $ sudo systemctl enable docker
34 | $ sudo systemctl start docker
35 |
36 | // install dokcer-compose
37 | $ sudo curl -L https://github.com/docker/compose/releases/download/1.18.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
38 | $ sudo chmod +x /usr/local/bin/docker-compose
39 | ```
40 |
41 | ## クイックスタート
42 |
43 | 取り急ぎ Vespa の動作を確認したい場合は、以下のように `boot.sh` を用いて Vespa クラスタを構築することができます
44 | (Vespa クラスタの起動には *8GB* 程度のメモリが必要です、単体起動したい場合はチュートリアル資料を参照してください)。
45 |
46 | ```bash
47 | // please move to vespa-tutorial directory first
48 | $ cd vespa-tutorial/
49 |
50 | // start Vespa cluster
51 | $ ./boot.sh start
52 |
53 | // stop Vespa cluster
54 | $ ./boot.sh stop
55 | ```
56 |
57 | 起動した Vespa は `8080` ポートで検索を受け付けます。
58 |
59 | ```bash
60 | $ curl 'http://localhost:8080/search/?lang=ja&query=入門'
61 | ```
62 |
--------------------------------------------------------------------------------
/appendix/README.md:
--------------------------------------------------------------------------------
1 |
6 | # Appendix
7 |
8 | Vespa との動作比較のために、チュートリアルで用いた book インデックスのサンプルと同じデータを用いて、
9 | 以下の代表的な OSS 検索エンジンを構築するサンプルを付録として添付しています。
10 |
11 | * [Solr](http://lucene.apache.org/solr/)
12 | * [Elasticsearch](https://www.elastic.co/jp/products/elasticsearch)
13 |
14 | なお、Vespa のサンプルと同様に、
15 | 実行には `docker` 及び `docker-compose` が必要となります。
16 |
17 | ## Solr
18 |
19 | Solr の起動停止は `solr/boot.sh` によって行います。
20 |
21 | ```bash
22 | // please move to solr directory first
23 | $ cd solr/
24 |
25 | // start Solr
26 | $ ./boot.sh start
27 |
28 | // stop Solr
29 | $ ./boot.sh stop
30 | ```
31 |
32 | 起動した Solr は `8983` ポートで検索を受け付けます。
33 |
34 | ```bash
35 | $ curl 'http://localhost:8983/solr/book/select?q=*:*&indent=true'
36 | ```
37 |
38 | また、以下の URL にアクセスすることで Solr の UI にアクセスすることができます (ホスト名は適宜変えてください)。
39 |
40 | ```bash
41 | http://localhost:8983
42 | ```
43 |
44 | ## Elasticsearch
45 |
46 | Elasticsearch の起動停止は `elasticsearch/boot.sh` によって行います。
47 |
48 | ```bash
49 | // please move to elasticsearch directory first
50 | $ cd elasticsearch/
51 |
52 | // start Elasticsearch
53 | $ ./boot.sh start
54 |
55 | // stop Elasticsearch
56 | $ ./boot.sh stop
57 | ```
58 |
59 | 起動した Elasticsearch は `9200` ポートで検索を受け付けます。
60 |
61 | ```bash
62 | $ curl 'http://localhost:9200/book/_search?q=*:*&pretty=true'
63 | ```
64 |
65 | また、以下の URL にアクセスすることで Kibana の UI にアクセスすることができます (ホスト名は適宜変えてください)。
66 |
67 | ```bash
68 | http://localhost:5601
69 | ```
--------------------------------------------------------------------------------
/sample-apps/config/cluster/services.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
23 |
24 | search
25 | true
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | 2
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/appendix/solr/sample-apps/config/solrconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 | 7.2.1
9 |
10 |
11 |
12 | ${solr.data.dir:}
13 |
15 |
16 |
17 | ${solr.lock.type:native}
18 |
19 |
20 |
21 |
22 |
23 |
24 | ${solr.ulog.dir:}
25 |
26 |
27 |
28 | 15000
29 | false
30 |
31 |
32 |
33 | 1000
34 |
35 |
36 |
37 |
38 |
39 | explicit
40 | 10
41 |
42 |
43 |
44 |
45 |
46 | default
47 |
48 |
49 |
50 |
51 | text/plain; charset=UTF-8
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/utils/vespa_deploy:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Copyright 2018 Yahoo Japan Corporation.
4 | # Licensed under the terms of the MIT license.
5 | # See LICENSE in the project root.
6 | #
7 |
8 | set -eu
9 |
10 |
11 | function usage {
12 | cat <&2
13 | usage: $0 [prepare CONFIG | activate]
14 |
15 | NOTE: you need root privilege to execute this script due to "docker-compose".
16 |
17 | ex) upload "basic" config (i.e., "sample-apps/config/basic") to the configurarion server.
18 | $ $0 prepare basic
19 |
20 | ex) enable the latest config in all application server.
21 | $ $0 activate
22 | EOF
23 | }
24 |
25 | function prepare {
26 | config="/vespa-sample-apps/config/$1"
27 | sudo docker-compose exec vespa1 /bin/bash -c "/opt/vespa/bin/vespa-deploy prepare ${config}"
28 | return $?
29 | }
30 |
31 | function activate {
32 | sudo docker-compose exec vespa1 /bin/bash -c "/opt/vespa/bin/vespa-deploy activate"
33 | return $?
34 | }
35 |
36 |
37 | if [ $# -eq 0 ]; then
38 | usage
39 | exit 1
40 | fi
41 |
42 | # move to project directory to refer docker-compose.yml
43 | PROJ_DIR=$(cd $(dirname $0)/..; pwd)
44 | cd ${PROJ_DIR}
45 |
46 | action=$1
47 | case ${action} in
48 | "prepare")
49 | if [ $# -lt 2 ]; then
50 | echo " the name of config is required with \"prepare\"" 1>&2
51 | exit 1
52 | fi
53 | config=`basename $2`
54 | if [ ! -d "${PROJ_DIR}/sample-apps/config/${config}" ]; then
55 | echo " failed to find \"${PROJ_DIR}/sample-apps/config/${config}\"" 1>&2
56 | exit 1
57 | fi
58 | prepare ${config}
59 | ;;
60 | "activate")
61 | activate
62 | ;;
63 | *)
64 | echo " unknown action: ${action}" 1>&2
65 | exit 1
66 | ;;
67 | esac
68 |
69 | exit $?
70 |
--------------------------------------------------------------------------------
/appendix/solr/sample-apps/config/schema.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 | id
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/appendix/elasticsearch/boot.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Copyright 2018 Yahoo Japan Corporation.
4 | # Licensed under the terms of the MIT license.
5 | # See LICENSE in the project root.
6 | #
7 |
8 | set -eu
9 |
10 | BASE_DIR=$(cd $(dirname $0); pwd)
11 |
12 | function usage {
13 | echo "usage: $0 [start|stop]"
14 | exit 1
15 | }
16 |
17 | function start {
18 | echo " start a docker container for Elasticsearch" 1>&2
19 | sudo docker-compose up -d
20 |
21 | echo " wait until Elasticsearch becomes ready" 1>&2
22 | local status="0"
23 | for i in `seq 1 60`; do
24 | set +e
25 | status=`curl -XGET -LI -s -o /dev/null -w '%{http_code}\n' 'http://localhost:9200/_cat/health'`
26 | set -e
27 | if [ "${status}" == "200" ]; then
28 | break
29 | fi
30 | sleep 1
31 | done
32 | if [ "${status}" != "200" ]; then
33 | echo " Elasticsearch doesn't become ready after waiting 60 seconds"
34 | return 1
35 | fi
36 |
37 | echo " create book index" 1>&2
38 | curl -XPUT -H 'Content-type: application/json' --data-binary @${BASE_DIR}/sample-apps/config/schema.json 'http://localhost:9200/book'
39 |
40 | echo " feed sample documents" 1>&2
41 | curl -XPOST -H 'Content-type: application/json' --data-binary @${BASE_DIR}/sample-apps/feed/book-data-put.json 'http://localhost:9200/book/_bulk'
42 |
43 | echo " refresh the index" 1>&2
44 | curl -XPOST 'http://localhost:9200/book/_refresh'
45 |
46 | echo " done" 1>&2
47 | }
48 |
49 | function stop {
50 | echo " stop the running docker container for Elasticsearch" 1>&2
51 | sudo docker-compose down
52 | }
53 |
54 |
55 | if [ $# -lt 1 ]; then
56 | usage
57 | fi
58 | if [ `pwd` != ${BASE_DIR} ]; then
59 | echo " please execute this script in ${BASE_DIR}"
60 | exit 1
61 | fi
62 |
63 | case "$1" in
64 | "start")
65 | start
66 | ;;
67 | "stop")
68 | stop
69 | ;;
70 | *)
71 | usage
72 | ;;
73 | esac
--------------------------------------------------------------------------------
/sample-apps/config/cluster/searchdefinitions/book.sd:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2018 Yahoo Japan Corporation.
3 | # Licensed under the terms of the MIT license.
4 | # See LICENSE in the project root.
5 | #
6 | search book {
7 |
8 | document book {
9 |
10 | field language type string {
11 | indexing: "ja" | set_language
12 | }
13 |
14 | field title type string {
15 | indexing: summary | index
16 | summary-to: simple_set, detail_set
17 | }
18 |
19 | field desc type string {
20 | indexing: summary | index
21 | summary-to: detail_set
22 | }
23 |
24 | field price type int {
25 | indexing: summary | attribute
26 | summary-to: simple_set, detail_set
27 | }
28 |
29 | field page type int {
30 | indexing: summary | attribute
31 | }
32 |
33 | field genres type array {
34 | indexing: summary | attribute
35 | summary-to: detail_set
36 | }
37 |
38 | field reviews type weightedset {
39 | indexing: summary | attribute
40 | }
41 |
42 | }
43 |
44 | fieldset default {
45 | fields: title, desc
46 | }
47 |
48 | rank-profile basic inherits default {
49 |
50 | first-phase {
51 | expression: nativeRank
52 | }
53 |
54 | }
55 |
56 | rank-profile price_boost inherits basic {
57 |
58 | rank-properties {
59 | query(bias) : 0.1
60 | }
61 |
62 | macro price_boost() {
63 | expression: file:price_boost.expression
64 | }
65 |
66 | macro boosted_score(bias) {
67 | expression {
68 | (1.0 - bias) * firstPhase
69 | + bias * price_boost
70 | }
71 | }
72 |
73 | second-phase {
74 | expression: boosted_score(query(bias))
75 | rerank-count: 3
76 | }
77 |
78 | }
79 |
80 | rank-profile reviews_prefer inherits default {
81 |
82 | first-phase {
83 | expression: dotProduct(reviews, prefer)
84 | }
85 |
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/sample-apps/config/ranking/searchdefinitions/book.sd:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2018 Yahoo Japan Corporation.
3 | # Licensed under the terms of the MIT license.
4 | # See LICENSE in the project root.
5 | #
6 | search book {
7 |
8 | document book {
9 |
10 | field language type string {
11 | indexing: "ja" | set_language
12 | }
13 |
14 | field title type string {
15 | indexing: summary | index
16 | summary-to: simple_set, detail_set
17 | }
18 |
19 | field desc type string {
20 | indexing: summary | index
21 | summary-to: detail_set
22 | }
23 |
24 | field price type int {
25 | indexing: summary | attribute
26 | summary-to: simple_set, detail_set
27 | }
28 |
29 | field page type int {
30 | indexing: summary | attribute
31 | }
32 |
33 | field genres type array {
34 | indexing: summary | attribute
35 | summary-to: detail_set
36 | }
37 |
38 | field reviews type weightedset {
39 | indexing: summary | attribute
40 | }
41 |
42 | }
43 |
44 | fieldset default {
45 | fields: title, desc
46 | }
47 |
48 | rank-profile basic inherits default {
49 |
50 | first-phase {
51 | expression: nativeRank
52 | }
53 |
54 | }
55 |
56 | rank-profile price_boost inherits basic {
57 |
58 | rank-properties {
59 | query(bias) : 0.1
60 | }
61 |
62 | macro price_boost() {
63 | expression: file:price_boost.expression
64 | }
65 |
66 | macro boosted_score(bias) {
67 | expression {
68 | (1.0 - bias) * firstPhase
69 | + bias * price_boost
70 | }
71 | }
72 |
73 | second-phase {
74 | expression: boosted_score(query(bias))
75 | rerank-count: 3
76 | }
77 |
78 | }
79 |
80 | rank-profile reviews_prefer inherits default {
81 |
82 | first-phase {
83 | expression: dotProduct(reviews, prefer)
84 | }
85 |
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/appendix/solr/boot.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Copyright 2018 Yahoo Japan Corporation.
4 | # Licensed under the terms of the MIT license.
5 | # See LICENSE in the project root.
6 | #
7 |
8 | set -eu
9 |
10 | BASE_DIR=$(cd $(dirname $0); pwd)
11 |
12 | function usage {
13 | echo "usage: $0 [start|stop]"
14 | exit 1
15 | }
16 |
17 | function start {
18 | echo " start a docker container for Solr" 1>&2
19 | sudo docker-compose up -d
20 |
21 | echo " wait 10 seconds to ensure ZooKeeper becomes ready" 1>&2
22 | sleep 10
23 |
24 | echo " upload book config to ZooKeeper"
25 | sudo docker-compose exec solr /bin/bash -c "/opt/solr/server/scripts/cloud-scripts/zkcli.sh -cmd upconfig -zkhost zk:2181 -confdir /solr-sample-apps/config/ -confname book"
26 |
27 | echo " create book collection"
28 | curl 'http://localhost:8983/solr/admin/collections?action=CREATE&name=book&numShards=1&collection.configName=book'
29 |
30 | echo " wait until the book collection becomes ready" 1>&2
31 | local status="0"
32 | for i in `seq 1 60`; do
33 | set +e
34 | status=`curl -XGET -LI -s -o /dev/null -w '%{http_code}\n' 'http://localhost:8983/solr/book/admin/ping'`
35 | set -e
36 | if [ "${status}" == "200" ]; then
37 | break
38 | fi
39 | sleep 1
40 | done
41 | if [ "${status}" != "200" ]; then
42 | echo " Solr doesn't become ready after waiting 60 seconds"
43 | return 1
44 | fi
45 |
46 | echo " feed sample documents and commit" 1>&2
47 | curl -XPOST -H 'Content-type: application/json' --data-binary @${BASE_DIR}/sample-apps/feed/book-data-put.json 'http://localhost:8983/solr/book/update?commit=true'
48 |
49 | echo " done" 1>&2
50 | }
51 |
52 | function stop {
53 | echo " stop the running docker container for Solr" 1>&2
54 | sudo docker-compose down
55 | }
56 |
57 |
58 | if [ $# -lt 1 ]; then
59 | usage
60 | fi
61 | if [ `pwd` != ${BASE_DIR} ]; then
62 | echo " please execute this script in ${BASE_DIR}"
63 | exit 1
64 | fi
65 |
66 | case "$1" in
67 | "start")
68 | start
69 | ;;
70 | "stop")
71 | stop
72 | ;;
73 | *)
74 | usage
75 | ;;
76 | esac
--------------------------------------------------------------------------------
/docs/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
9 | 4.0.0
10 |
11 | jp.co.yahoo.vespa
12 | vespa-tutorial-docs
13 | 1.1.0
14 |
15 | Japanese tutorial documents for Vespa
16 | Project to manage Japanese tutorial documents for Vespa
17 | https://github.com/yahoojapan/vespa-tutorial
18 |
19 |
20 |
21 | MIT License
22 | http://www.opensource.org/licenses/mit-license.php
23 |
24 |
25 |
26 |
27 | UTF-8
28 | 1.5.6
29 |
30 |
31 |
32 | process-resources
33 |
34 |
35 | org.asciidoctor
36 | asciidoctor-maven-plugin
37 | ${asciidoctor.maven.plugin.version}
38 |
39 |
40 | ${project.version}
41 | left
42 | 3
43 | font
44 |
45 | true
46 |
47 |
48 |
49 |
50 | output-html
51 | generate-resources
52 |
53 | process-asciidoc
54 |
55 |
56 | html5
57 | coderay
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/boot.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Copyright 2018 Yahoo Japan Corporation.
4 | # Licensed under the terms of the MIT license.
5 | # See LICENSE in the project root.
6 | #
7 |
8 | set -eu
9 |
10 | BASE_DIR=$(cd $(dirname $0); pwd)
11 |
12 | function usage {
13 | echo "usage: $0 [start|stop]"
14 | exit 1
15 | }
16 |
17 | function start {
18 | echo " set up plugins if necessary" 1>&2
19 | sample-apps/plugin/setup.sh
20 |
21 | echo " start a docker container for Vespa" 1>&2
22 | sudo docker-compose up -d
23 |
24 | echo " wait until the configuration server becomes ready" 1>&2
25 | local ret=0
26 | for i in `seq 1 60`; do
27 | set +e
28 | utils/vespa_status config > /dev/null 2>&1
29 | ret=$?
30 | set -e
31 | if [ ${ret} -eq 0 ]; then
32 | break
33 | fi
34 | sleep 1
35 | done
36 | if [ ${ret} -ne 0 ]; then
37 | echo " the configuration server doesn't become ready after waiting 60 seconds"
38 | return 1
39 | fi
40 |
41 | echo " deploy cluster config to Vespa" 1>&2
42 | utils/vespa_deploy prepare ${BASE_DIR}/sample-apps/config/cluster
43 | utils/vespa_deploy activate
44 |
45 | echo " wait until all application servers become ready" 1>&2
46 | for i in `seq 1 60`; do
47 | set +e
48 | utils/vespa_status vespa1 vespa2 vespa3 > /dev/null 2>&1
49 | ret=$?
50 | set -e
51 | if [ ${ret} -eq 0 ]; then
52 | break
53 | fi
54 | sleep 1
55 | done
56 | if [ ${ret} -ne 0 ]; then
57 | echo " some of application servers don't become ready after waiting 60 seconds"
58 | return 1
59 | fi
60 |
61 | echo " feed sample documents" 1>&2
62 | utils/vespa_feeder sample-apps/feed/book-data-put.json
63 |
64 | echo " done" 1>&2
65 | }
66 |
67 | function stop {
68 | echo " stop the running docker container for Vespa" 1>&2
69 | sudo docker-compose down
70 | }
71 |
72 |
73 | if [ $# -lt 1 ]; then
74 | usage
75 | fi
76 | if [ `pwd` != ${BASE_DIR} ]; then
77 | echo " please execute this script in ${BASE_DIR}"
78 | exit 1
79 | fi
80 |
81 | case "$1" in
82 | "start")
83 | start
84 | ;;
85 | "stop")
86 | stop
87 | ;;
88 | *)
89 | usage
90 | ;;
91 | esac
--------------------------------------------------------------------------------
/appendix/elasticsearch/sample-apps/feed/book-data-put.json:
--------------------------------------------------------------------------------
1 | {"index": {"_index": "book", "_type": "book", "_id": "id:book:book::vespa_intro"}}
2 | {"title": "ゼロから始めるVespa入門", "desc": "話題のOSS検索エンジン、Vespaの使い方を初心者にもわかりやすく解説します", "price": 1500, "page": 200, "genres": ["コンピュータ", "検索エンジン", "Vespa"], "reviews.quality": 40, "reviews.readability": 90, "reviews.cost": 80}
3 | {"index": {"_index": "book", "_type": "book", "_id": "id:book:book::vespa_service"}}
4 | {"title": "詳細Vespa運用マニュアル", "desc": "検索エンジンVespaの運用するにあたり必要な知識を網羅しています","price": 6000, "page": 500, "genres": ["コンピュータ", "検索エンジン", "Vespa"], "reviews.quality": 70, "reviews.readability": 50, "reviews.cost": 20}
5 | {"index": {"_index": "book", "_type": "book", "_id": "id:book:book::vespa_performance"}}
6 | {"title": "ハイパフォーマンスVespa", "desc": "Vespaの性能を引き出すTipsをまとめています", "price": 3500, "page": 300, "genres": ["コンピュータ", "検索エンジン", "Vespa"], "reviews.quality": 90, "reviews.readability": 30, "reviews.cost": 50}
7 | {"index": {"_index": "book", "_type": "book", "_id": "id:book:book::solr_intro"}}
8 | {"title": "Solrイントロダクション", "desc": "基本から応用まで、検索エンジンSolrの使い方を広く紹介します", "price": 4000, "page": 400, "genres": ["コンピュータ", "検索エンジン", "Solr"], "reviews.quality": 60, "reviews.readability": 70, "reviews.cost": 40}
9 | {"index": {"_index": "book", "_type": "book", "_id": "id:book:book::solr_service"}}
10 | {"title": "サービス事例で学ぶSolr活用術", "desc": "Solrをどうサービスで活用するか、実際の事例をベースに説明します", "price": 2500, "page": 250, "genres": ["コンピュータ", "検索エンジン", "Solr"], "reviews.quality": 50, "reviews.readability": 30, "reviews.cost": 60}
11 | {"index": {"_index": "book", "_type": "book", "_id": "id:book:book::elasticsearch_science"}}
12 | {"title": "Elasticsearchで始めるデータサイエンス", "desc": "Elasticsearchとデータサイエンスツールとの連携について紹介します", "price": 3000, "page": 350, "genres": ["コンピュータ", "検索エンジン", "Elasticsearch"], "reviews.quality": 70, "reviews.readability": 20, "reviews.cost": 60}
13 | {"index": {"_index": "book", "_type": "book", "_id": "id:book:book::search_engine_history"}}
14 | {"title": "検索エンジン今昔物語", "desc": "検索エンジン技術の変遷について紹介します", "price": 1000, "page": 150, "genres": ["コンピュータ", "検索エンジン"], "reviews.quality": 30, "reviews.readability": 90, "reviews.cost": 90}
15 | {"index": {"_index": "book", "_type": "book", "_id": "id:book:book::java_intro"}}
16 | {"title": "サルでも分かるJava言語", "desc": "Java超初心者におすすめのJava言語の入門書です", "price": 1000, "page": 150, "genres": ["コンピュータ", "プログラミング", "Java"], "reviews.quality": 30, "reviews.readability": 90, "reviews.cost": 90}
17 | {"index": {"_index": "book", "_type": "book", "_id": "id:book:book::java_master"}}
18 | {"title": "マスタリングJava", "desc": "Javaの極めたいそんな貴方に捧げるディープなJava本です", "price": 5000, "page": 800, "genres": ["コンピュータ", "プログラミング", "Java"], "reviews.quality": 80, "reviews.readability": 10, "reviews.cost": 10}
19 | {"index": {"_index": "book", "_type": "book", "_id": "id:book:book::python_intro"}}
20 | {"title": "Python本格入門", "desc": "今話題のPythonの使い方をわかりやすく説明します", "price": 2000, "page": 450, "genres": ["コンピュータ", "プログラミング", "Python"], "reviews.quality": 50, "reviews.readability": 80, "reviews.cost": 70}
21 | {"index": {"_index": "book", "_type": "book", "_id": "id:book:book::python_science"}}
22 | {"title": "Pythonで楽しく学ぶ機械学習", "desc": "Pythonを用いて機械学習の各種手法について説明します", "price": 2500, "page": 300, "genres": ["コンピュータ", "プログラミング", "Python"], "reviews.quality": 60, "reviews.readability": 70, "reviews.cost": 60}
23 | {"index": {"_index": "book", "_type": "book", "_id": "id:book:book::java_tea"}}
24 | {"title": "Java: 魅惑の紅茶の世界", "desc": "ジャワティーの特徴や美味しい飲み方を紹介します", "price": 1000, "page": 150, "genres": ["食品", "料理", "紅茶"], "reviews.quality": 60, "reviews.readability": 90, "reviews.cost": 90}
25 | {"index": {"_index": "book", "_type": "book", "_id": "id:book:book::python_animal"}}
26 | {"title": "動物大図鑑シリーズ: Python", "desc": "知られざるニシキヘビの世界を迫力の写真とともに紹介します", "price": 2500, "page": 100, "genres": ["動物", "蛇"], "reviews.quality": 100, "reviews.readability": 100, "reviews.cost": 30}
27 |
--------------------------------------------------------------------------------
/utils/vespa_cluster_status:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2018 Yahoo Japan Corporation.
4 | # Licensed under the terms of the MIT license.
5 | # See LICENSE in the project root.
6 | #
7 |
8 | import argparse
9 | import json
10 | import urllib2
11 | from collections import OrderedDict
12 |
13 | VESPA_URL = "http://localhost:19050"
14 |
15 |
16 | def call_api(path):
17 | """
18 | @see http://docs.vespa.ai/documentation/content/api-state-rest-api.html
19 | """
20 | res = urllib2.urlopen(VESPA_URL + path.replace("\\/", "/"))
21 | return json.loads(res.read(), object_pairs_hook=OrderedDict)
22 |
23 |
24 | def get_cluster_status(cluster, services):
25 | cluster_res = call_api("/cluster/v2/{cluster}".format(cluster=cluster))
26 |
27 | stat = {}
28 |
29 | if "distributor" in services:
30 | # distributor
31 | distributor_res = call_api(cluster_res["service"]["distributor"]["link"])
32 | distributor_stat = OrderedDict()
33 | for node_id, node_link in distributor_res["node"].iteritems():
34 | node_res = call_api(node_link["link"])
35 | distributor_stat[node_id] = {
36 | "status": node_res["state"]["generated"]["state"]
37 | }
38 | stat["distributor"] = distributor_stat
39 |
40 | if "storage" in services:
41 | # storage
42 | storage_res = call_api(cluster_res["service"]["storage"]["link"])
43 | storage_stat = OrderedDict()
44 | for node_id, node_link in storage_res["node"].iteritems():
45 | node_res = call_api(node_link["link"])
46 | node_stat = {
47 | "status": node_res["state"]["generated"]["state"],
48 | "bucket-count": 0,
49 | "unique-document-count": 0,
50 | "unique-document-total-size": 0
51 | }
52 | for partition_id, partition_link in node_res.get("partition", {}).iteritems():
53 | partition_res = call_api(partition_link["link"])
54 |
55 | node_stat["bucket-count"] += partition_res["metrics"]["bucket-count"]
56 | node_stat["unique-document-count"] += partition_res["metrics"]["unique-document-count"]
57 | node_stat["unique-document-total-size"] += partition_res["metrics"]["unique-document-total-size"]
58 | storage_stat[node_id] = node_stat
59 | stat["storage"] = storage_stat
60 |
61 | return stat
62 |
63 |
64 | def print_distributor_status(stat):
65 | print "| node | status |"
66 | print "|------|-------------|"
67 | for node_id, node_stat in stat["distributor"].iteritems():
68 | print "| {n:>4} | {s:<11} |".format(
69 | n=node_id,
70 | s=node_stat["status"]
71 | )
72 |
73 |
74 | def print_storage_status(stat):
75 | print "| node | status | bucket-count | uniq-doc-count | uniq-doc-size |"
76 | print "|------|-------------|--------------|----------------|---------------|"
77 | for node_id, node_stat in stat["storage"].iteritems():
78 | print "| {n:>4} | {s:<11} | {bc:>12} | {udc:>14} | {uds:>13} |".format(
79 | n=node_id,
80 | s=node_stat["status"],
81 | bc=node_stat["bucket-count"],
82 | udc=node_stat["unique-document-count"],
83 | uds=node_stat["unique-document-total-size"]
84 | )
85 |
86 |
87 | def main():
88 | parser = argparse.ArgumentParser(prog="vespa_cluster_status",
89 | formatter_class=argparse.RawTextHelpFormatter,
90 | description="""An utility for checking status of a vespa cluster by using cluster APIs.
91 |
92 | Please see the following link for details of cluster APIs.
93 | > http://docs.vespa.ai/documentation/content/api-state-rest-api.html
94 | """)
95 | parser.add_argument("cluster",
96 | help="A name of a target cluster")
97 | parser.add_argument("-t", "--type", choices=["distributor", "storage", "all"], default="all",
98 | help="A service type to show (default: %(default)s)")
99 | args = parser.parse_args()
100 |
101 | service_types = ["distributor", "storage"]
102 | if args.type != "all":
103 | service_types = [args.type]
104 |
105 | # extract all status
106 | stat = get_cluster_status(args.cluster, service_types)
107 |
108 | # print results
109 | for service_type in service_types:
110 | print "=== status of {service} ===\n".format(service=service_type)
111 | globals()["print_{service}_status".format(service=service_type)](stat)
112 | print "\n"
113 |
114 |
115 | if __name__ == "__main__":
116 | main()
117 |
--------------------------------------------------------------------------------
/appendix/solr/sample-apps/feed/book-data-put.json:
--------------------------------------------------------------------------------
1 | {
2 | "add": {
3 | "doc": {
4 | "id": "id:book:book::vespa_intro",
5 | "title": "ゼロから始めるVespa入門",
6 | "desc": "話題のOSS検索エンジン、Vespaの使い方を初心者にもわかりやすく解説します",
7 | "price": 1500,
8 | "page": 200,
9 | "genres": [
10 | "コンピュータ",
11 | "検索エンジン",
12 | "Vespa"
13 | ],
14 | "reviews.quality": 40,
15 | "reviews.readability": 90,
16 | "reviews.cost": 80
17 | }
18 | },
19 | "add": {
20 | "doc": {
21 | "id": "id:book:book::vespa_service",
22 | "title": "詳細Vespa運用マニュアル",
23 | "desc": "検索エンジンVespaの運用するにあたり必要な知識を網羅しています",
24 | "price": 6000,
25 | "page": 500,
26 | "genres": [
27 | "コンピュータ",
28 | "検索エンジン",
29 | "Vespa"
30 | ],
31 | "reviews.quality": 70,
32 | "reviews.readability": 50,
33 | "reviews.cost": 20
34 | }
35 | },
36 | "add": {
37 | "doc": {
38 | "id": "id:book:book::vespa_performance",
39 | "title": "ハイパフォーマンスVespa",
40 | "desc": "Vespaの性能を引き出すTipsをまとめています",
41 | "price": 3500,
42 | "page": 300,
43 | "genres": [
44 | "コンピュータ",
45 | "検索エンジン",
46 | "Vespa"
47 | ],
48 | "reviews.quality": 90,
49 | "reviews.readability": 30,
50 | "reviews.cost": 50
51 | }
52 | },
53 | "add": {
54 | "doc": {
55 | "id": "id:book:book::solr_intro",
56 | "title": "Solrイントロダクション",
57 | "desc": "基本から応用まで、検索エンジンSolrの使い方を広く紹介します",
58 | "price": 4000,
59 | "page": 400,
60 | "genres": [
61 | "コンピュータ",
62 | "検索エンジン",
63 | "Solr"
64 | ],
65 | "reviews.quality": 60,
66 | "reviews.readability": 70,
67 | "reviews.cost": 40
68 | }
69 | },
70 | "add": {
71 | "doc": {
72 | "id": "id:book:book::solr_service",
73 | "title": "サービス事例で学ぶSolr活用術",
74 | "desc": "Solrをどうサービスで活用するか、実際の事例をベースに説明します",
75 | "price": 2500,
76 | "page": 250,
77 | "genres": [
78 | "コンピュータ",
79 | "検索エンジン",
80 | "Solr"
81 | ],
82 | "reviews.quality": 50,
83 | "reviews.readability": 30,
84 | "reviews.cost": 60
85 | }
86 | },
87 | "add": {
88 | "doc": {
89 | "id": "id:book:book::elasticsearch_science",
90 | "title": "Elasticsearchで始めるデータサイエンス",
91 | "desc": "Elasticsearchとデータサイエンスツールとの連携について紹介します",
92 | "price": 3000,
93 | "page": 350,
94 | "genres": [
95 | "コンピュータ",
96 | "検索エンジン",
97 | "Elasticsearch"
98 | ],
99 | "reviews.quality": 70,
100 | "reviews.readability": 20,
101 | "reviews.cost": 60
102 | }
103 | },
104 | "add": {
105 | "doc": {
106 | "id": "id:book:book::search_engine_history",
107 | "title": "検索エンジン今昔物語",
108 | "desc": "検索エンジン技術の変遷について紹介します",
109 | "price": 1000,
110 | "page": 150,
111 | "genres": [
112 | "コンピュータ",
113 | "検索エンジン"
114 | ],
115 | "reviews.quality": 30,
116 | "reviews.readability": 90,
117 | "reviews.cost": 90
118 | }
119 | },
120 | "add": {
121 | "doc": {
122 | "id": "id:book:book::java_intro",
123 | "title": "サルでも分かるJava言語",
124 | "desc": "Java超初心者におすすめのJava言語の入門書です",
125 | "price": 1000,
126 | "page": 150,
127 | "genres": [
128 | "コンピュータ",
129 | "プログラミング",
130 | "Java"
131 | ],
132 | "reviews.quality": 30,
133 | "reviews.readability": 90,
134 | "reviews.cost": 90
135 | }
136 | },
137 | "add": {
138 | "doc": {
139 | "id": "id:book:book::java_master",
140 | "title": "マスタリングJava",
141 | "desc": "Javaの極めたいそんな貴方に捧げるディープなJava本です",
142 | "price": 5000,
143 | "page": 800,
144 | "genres": [
145 | "コンピュータ",
146 | "プログラミング",
147 | "Java"
148 | ],
149 | "reviews.quality": 80,
150 | "reviews.readability": 10,
151 | "reviews.cost": 10
152 | }
153 | },
154 | "add": {
155 | "doc": {
156 | "id": "id:book:book::python_intro",
157 | "title": "Python本格入門",
158 | "desc": "今話題のPythonの使い方をわかりやすく説明します",
159 | "price": 2000,
160 | "page": 450,
161 | "genres": [
162 | "コンピュータ",
163 | "プログラミング",
164 | "Python"
165 | ],
166 | "reviews.quality": 50,
167 | "reviews.readability": 80,
168 | "reviews.cost": 70
169 | }
170 | },
171 | "add": {
172 | "doc": {
173 | "id": "id:book:book::python_science",
174 | "title": "Pythonで楽しく学ぶ機械学習",
175 | "desc": "Pythonを用いて機械学習の各種手法について説明します",
176 | "price": 2500,
177 | "page": 300,
178 | "genres": [
179 | "コンピュータ",
180 | "プログラミング",
181 | "Python"
182 | ],
183 | "reviews.quality": 60,
184 | "reviews.readability": 70,
185 | "reviews.cost": 60
186 | }
187 | },
188 | "add": {
189 | "doc": {
190 | "id": "id:book:book::java_tea",
191 | "title": "Java: 魅惑の紅茶の世界",
192 | "desc": "ジャワティーの特徴や美味しい飲み方を紹介します",
193 | "price": 1000,
194 | "page": 150,
195 | "genres": [
196 | "食品",
197 | "料理",
198 | "紅茶"
199 | ],
200 | "reviews.quality": 60,
201 | "reviews.readability": 90,
202 | "reviews.cost": 90
203 | }
204 | },
205 | "add": {
206 | "doc": {
207 | "id": "id:book:book::python_animal",
208 | "title": "動物大図鑑シリーズ: Python",
209 | "desc": "知られざるニシキヘビの世界を迫力の写真とともに紹介します",
210 | "price": 2500,
211 | "page": 100,
212 | "genres": [
213 | "動物",
214 | "蛇"
215 | ],
216 | "reviews.quality": 100,
217 | "reviews.readability": 100,
218 | "reviews.cost": 30
219 | }
220 | }
221 | }
--------------------------------------------------------------------------------
/sample-apps/feed/book-data-put.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "put": "id:book:book::vespa_intro",
4 | "fields": {
5 | "title": "ゼロから始めるVespa入門",
6 | "desc": "話題のOSS検索エンジン、Vespaの使い方を初心者にもわかりやすく解説します",
7 | "price": 1500,
8 | "page": 200,
9 | "genres": [
10 | "コンピュータ",
11 | "検索エンジン",
12 | "Vespa"
13 | ],
14 | "reviews": {
15 | "quality": 40,
16 | "readability": 90,
17 | "cost": 80
18 | }
19 | }
20 | },
21 | {
22 | "put": "id:book:book::vespa_service",
23 | "fields": {
24 | "title": "詳細Vespa運用マニュアル",
25 | "desc": "検索エンジンVespaの運用するにあたり必要な知識を網羅しています",
26 | "price": 6000,
27 | "page": 500,
28 | "genres": [
29 | "コンピュータ",
30 | "検索エンジン",
31 | "Vespa"
32 | ],
33 | "reviews": {
34 | "quality": 70,
35 | "readability": 50,
36 | "cost": 20
37 | }
38 | }
39 | },
40 | {
41 | "put": "id:book:book::vespa_performance",
42 | "fields": {
43 | "title": "ハイパフォーマンスVespa",
44 | "desc": "Vespaの性能を引き出すTipsをまとめています",
45 | "price": 3500,
46 | "page": 300,
47 | "genres": [
48 | "コンピュータ",
49 | "検索エンジン",
50 | "Vespa"
51 | ],
52 | "reviews": {
53 | "quality": 90,
54 | "readability": 30,
55 | "cost": 50
56 | }
57 | }
58 | },
59 | {
60 | "put": "id:book:book::solr_intro",
61 | "fields": {
62 | "title": "Solrイントロダクション",
63 | "desc": "基本から応用まで、検索エンジンSolrの使い方を広く紹介します",
64 | "price": 4000,
65 | "page": 400,
66 | "genres": [
67 | "コンピュータ",
68 | "検索エンジン",
69 | "Solr"
70 | ],
71 | "reviews": {
72 | "quality": 60,
73 | "readability": 70,
74 | "cost": 40
75 | }
76 | }
77 | },
78 | {
79 | "put": "id:book:book::solr_service",
80 | "fields": {
81 | "title": "サービス事例で学ぶSolr活用術",
82 | "desc": "Solrをどうサービスで活用するか、実際の事例をベースに説明します",
83 | "price": 2500,
84 | "page": 250,
85 | "genres": [
86 | "コンピュータ",
87 | "検索エンジン",
88 | "Solr"
89 | ],
90 | "reviews": {
91 | "quality": 50,
92 | "readability": 30,
93 | "cost": 60
94 | }
95 | }
96 | },
97 | {
98 | "put": "id:book:book::elasticsearch_science",
99 | "fields": {
100 | "title": "Elasticsearchで始めるデータサイエンス",
101 | "desc": "Elasticsearchとデータサイエンスツールとの連携について紹介します",
102 | "price": 3000,
103 | "page": 350,
104 | "genres": [
105 | "コンピュータ",
106 | "検索エンジン",
107 | "Elasticsearch"
108 | ],
109 | "reviews": {
110 | "quality": 70,
111 | "readability": 20,
112 | "cost": 60
113 | }
114 | }
115 | },
116 | {
117 | "put": "id:book:book::search_engine_history",
118 | "fields": {
119 | "title": "検索エンジン今昔物語",
120 | "desc": "検索エンジン技術の変遷について紹介します",
121 | "price": 1000,
122 | "page": 150,
123 | "genres": [
124 | "コンピュータ",
125 | "検索エンジン"
126 | ],
127 | "reviews": {
128 | "quality": 30,
129 | "readability": 90,
130 | "cost": 90
131 | }
132 | }
133 | },
134 | {
135 | "put": "id:book:book::java_intro",
136 | "fields": {
137 | "title": "サルでも分かるJava言語",
138 | "desc": "Java超初心者におすすめのJava言語の入門書です",
139 | "price": 1000,
140 | "page": 150,
141 | "genres": [
142 | "コンピュータ",
143 | "プログラミング",
144 | "Java"
145 | ],
146 | "reviews": {
147 | "quality": 30,
148 | "readability": 90,
149 | "cost": 90
150 | }
151 | }
152 | },
153 | {
154 | "put": "id:book:book::java_master",
155 | "fields": {
156 | "title": "マスタリングJava",
157 | "desc": "Javaの極めたいそんな貴方に捧げるディープなJava本です",
158 | "price": 5000,
159 | "page": 800,
160 | "genres": [
161 | "コンピュータ",
162 | "プログラミング",
163 | "Java"
164 | ],
165 | "reviews": {
166 | "quality": 80,
167 | "readability": 10,
168 | "cost": 10
169 | }
170 | }
171 | },
172 | {
173 | "put": "id:book:book::python_intro",
174 | "fields": {
175 | "title": "Python本格入門",
176 | "desc": "今話題のPythonの使い方をわかりやすく説明します",
177 | "price": 2000,
178 | "page": 450,
179 | "genres": [
180 | "コンピュータ",
181 | "プログラミング",
182 | "Python"
183 | ],
184 | "reviews": {
185 | "quality": 50,
186 | "readability": 80,
187 | "cost": 70
188 | }
189 | }
190 | },
191 | {
192 | "put": "id:book:book::python_science",
193 | "fields": {
194 | "title": "Pythonで楽しく学ぶ機械学習",
195 | "desc": "Pythonを用いて機械学習の各種手法について説明します",
196 | "price": 2500,
197 | "page": 300,
198 | "genres": [
199 | "コンピュータ",
200 | "プログラミング",
201 | "Python"
202 | ],
203 | "reviews": {
204 | "quality": 60,
205 | "readability": 70,
206 | "cost": 60
207 | }
208 | }
209 | },
210 | {
211 | "put": "id:book:book::java_tea",
212 | "fields": {
213 | "title": "Java: 魅惑の紅茶の世界",
214 | "desc": "ジャワティーの特徴や美味しい飲み方を紹介します",
215 | "price": 1000,
216 | "page": 150,
217 | "genres": [
218 | "食品",
219 | "料理",
220 | "紅茶"
221 | ],
222 | "reviews": {
223 | "quality": 60,
224 | "readability": 90,
225 | "cost": 90
226 | }
227 | }
228 | },
229 | {
230 | "put": "id:book:book::python_animal",
231 | "fields": {
232 | "title": "動物大図鑑シリーズ: Python",
233 | "desc": "知られざるニシキヘビの世界を迫力の写真とともに紹介します",
234 | "price": 2500,
235 | "page": 100,
236 | "genres": [
237 | "動物",
238 | "蛇"
239 | ],
240 | "reviews": {
241 | "quality": 100,
242 | "readability": 100,
243 | "cost": 30
244 | }
245 | }
246 | }
247 | ]
248 |
--------------------------------------------------------------------------------
/docs/src/main/asciidoc/3_update.adoc:
--------------------------------------------------------------------------------
1 | ////
2 | Copyright 2018 Yahoo Japan Corporation.
3 | Licensed under the terms of the MIT license.
4 | See LICENSE in the project root.
5 | ////
6 |
7 | [[update]]
8 | = Vespa と更新
9 | include::_include.adoc[]
10 |
11 | このセクションでは、Vespa にドキュメントを登録する方法について見ていきます。
12 |
13 | [[update_request]]
14 | == 更新リクエスト
15 |
16 | Vespa では更新リクエストは以下のように
17 | http://docs.vespa.ai/documentation/reference/document-json-format.html[JSON]
18 | を用いて記述します。
19 |
20 | [NOTE]
21 | ====
22 | ここで紹介しているフォーマットは後述の Client を用いて一括更新を行うケースを想定しています。
23 | ====
24 |
25 | [source, json]
26 | ----
27 | {
28 | {
29 | "put": "id:book:book::foo",
30 | "fields": {
31 | ...
32 | }
33 | },
34 | {
35 | "update": "id:book:book::bar",
36 | "fields": {
37 | ...
38 | }
39 | },
40 | {
41 | "remove": "id:book:book::foo"
42 | },
43 | ...
44 | }
45 | ----
46 |
47 | 各ドキュメントの先頭に記載された `put`、`update`、`remove`はそれぞれ追加、更新、削除の操作を意味しており、
48 | それぞれ値として対象のドキュメント ID を指定します。
49 |
50 | [[update_request_docid]]
51 | === ドキュメント ID
52 |
53 | ドキュメント ID のフォーマットは以下のように定義されています (
54 | http://docs.vespa.ai/documentation/documents.html[Documents]
55 | )。
56 |
57 | [source]
58 | ----
59 | id::::
60 | ----
61 |
62 | ドキュメント ID の各要素はそれぞれ以下のような意味があります。
63 |
64 | [options="header", cols="2,1,7"]
65 | |====
66 | ^| 要素 ^| 必須? ^| 意味
67 | | namespace ^| o | ドキュメントの名前空間、複数ユーザで Vespa をシェアしていたりするときに混在しないように付与します。
68 | | document-type ^| o | 対象のインデックス名、`searchdefinitions` で定義した `document` の識別子のこと。
69 | | key/value-pairs ^| | ドキュメントを特定のbucket (i.e., ノード) に偏らせたいときに指定します。
70 | | user-specified ^| o | ユーザ指定のユニークな ID を指定します。
71 | |====
72 |
73 | 例えば、book インデックスに "foo" という ID で登録する場合は以下のように指定します。
74 |
75 | [source]
76 | ----
77 | id:book:book::foo
78 | ----
79 |
80 | [NOTE]
81 | ====
82 | `namespace` は (おそらく) 任意の値を付けることが可能ですが、
83 | 通常の運用では `document-type` と同じものを指定すればいいかと思います。
84 | ====
85 |
86 | [TIP]
87 | ====
88 | `key/value=pairs` は `id:book:book:n=0:foo` もしくは `id:book:book:g=bar:foo` のように、
89 | `n=NUM` か `g=GROUP` のように指定します。
90 | `n=NUM` と指定した場合は `NUM` の数値が、
91 | `g=GROUP` と指定した場合は `GROUP` の文字列から生成されたハッシュ値がドキュメント分散で利用されます。
92 | より詳細な情報は
93 | http://docs.vespa.ai/documentation/content/buckets.html#document-to-bucket-distribution[こちら]
94 | を参照してください。
95 | ====
96 |
97 | [[update_request_put]]
98 | === put
99 |
100 | `put` は新規ドキュメントの追加もしくは上書きを行います。
101 | 以下のように対象のフィールドとその値のペアを `fields` の要素として定義していきます。
102 |
103 | [source, json]
104 | ----
105 | {
106 | "put": "id:book:book::foo",
107 | "fields": {
108 | "title": "fizz buzz",
109 | ...
110 | }
111 | }
112 | ----
113 |
114 | 各フィールドの値の書き方は、対応するフィールドのスキーマ定義によって以下のように変わります(
115 | http://docs.vespa.ai/documentation/reference/document-json-put-format.html[Document JSON format - Put]
116 | )。
117 |
118 | [source, json]
119 | ----
120 | // 文字列型 (string)
121 | "title": "fizz buzz"
122 |
123 | // 数値型 (integer, long, byte, float, double)
124 | "price": 3000
125 |
126 | // 配列型 (ここでは array の例)
127 | "genres": [
128 | "foo",
129 | "bar"
130 | ]
131 |
132 | // 辞書型 (ここでは weightedset の例)
133 | "reviews": {
134 | "foo": 100,
135 | "bar": 50
136 | }
137 | ----
138 |
139 | [TIP]
140 | ====
141 | Vespa の配列型フィールドでは feed 上での定義順が保持されたままインデックスされます。
142 | 上の例の場合、`["foo", "bar"]` という順序が登録後も記録されており、
143 | グルーピングやスコア計算などで配列への番号アクセスが可能となっています。
144 | ====
145 |
146 | [NOTE]
147 | ====
148 | `weightedset` の値は `integer` で固定なので注意。
149 | この値は各要素の重み (デフォルト値は `100`) に対応しており、後述するランキングでのスコア計算に影響してきます。
150 | ====
151 |
152 | [[update_request_update]]
153 | === update
154 |
155 | `update` は既存ドキュメントの部分更新を行います。
156 | 以下のように対象のフィールドとそこへの操作のペアを `fields` の要素として定義していきます。
157 |
158 | [source, json]
159 | ----
160 | {
161 | "update": "id:book:book::foo",
162 | "fields": {
163 | "title": {
164 | "assign": "buzz fizz"
165 | }
166 | }
167 | }
168 | ----
169 |
170 | 上記例の `assign` の部分は更新方法の指定を行っており、以下の3つが指定できます (
171 | http://docs.vespa.ai/documentation/reference/document-json-update-format.html[Document JSON format - Update]
172 | )。
173 |
174 | [options="header", cols="1,4"]
175 | |====
176 | ^| 方法 ^| 動作
177 | | assign | フィールドの値を指定した値で上書きします。
178 | | add | フィールドに指定した値を追加します (配列型や辞書型で利用可能)。
179 | | remove | 指定したフィールドの値を削除します。
180 | |====
181 |
182 | [[update_request_remove]]
183 | === remove
184 |
185 | `remove` は既存ドキュメントの削除を行います。
186 | `remove` ではドキュメント ID のみが必要なため、`fields` のような追加の要素の定義は不要です。
187 |
188 | [source, json]
189 | ----
190 | {
191 | "remove": "id:book:book::foo"
192 | }
193 | ----
194 |
195 | [TIP]
196 | ====
197 | インデックスから全ドキュメントを削除したい場合は、Vespa のサービスを停止した状態で
198 | http://docs.vespa.ai/documentation/reference/vespa-cmdline-tools.html#vespa-remove-index[vespa-remove-index]
199 | を叩くことで全削除できます。
200 | ====
201 |
202 | [[update_document]]
203 | == ドキュメントの更新
204 |
205 | Vespa では更新リクエストの投げ方には以下のような2つの方法が用意されています。
206 |
207 | * http://docs.vespa.ai/documentation/document-api.html[Document Operation API]
208 | * http://docs.vespa.ai/documentation/vespa-http-client.html[Vespa Java Feeding Client]
209 |
210 | 本ドキュメントでは、後者の Client を用いた方法でドキュメントの登録を行います。
211 |
212 | [NOTE]
213 | ====
214 | 実運用では、一括更新が可能な Client (
215 | http://docs.vespa.ai/documentation/reference/vespa-feeder.html[vespa-feeder]
216 | ) を利用する方が一般的かと思います。
217 | ====
218 |
219 | [[update_document_client]]
220 | === Client の実行方法
221 |
222 | Vepsa では Client プログラムを実行ラッパーとして
223 | https://github.com/vespa-engine/vespa/blob/master/vespaclient-java/src/main/sh/vespa-feeder.sh[vespa-feeder]
224 | というコマンドが提供されています。
225 |
226 | `vespa_feeder` では以下のように、引数として更新リクエストの `json` ファイルをわたすことで Vespa へのドキュメントの登録が行えます。
227 |
228 | [source, bash]
229 | ----
230 | $ vespa_feeder documents.json
231 | ----
232 |
233 | [[update_document_sample]]
234 | === サンプルデータの登録
235 |
236 | チュートリアルでは、サンブルデータとして前述の `book` インデックス向けに以下のようなドキュメントが用意されています。
237 |
238 | [source, bash]
239 | ----
240 | $ cat sample-apps/feed/book-data-put.json
241 | [
242 | {
243 | "put": "id:foo:book:g=foo:vespa_intro",
244 | "fields": {
245 | "title": "ゼロから始めるVespa入門",
246 | "desc": "話題のOSS検索エンジン、Vespaの使い方を初心者にもわかりやすく解説します",
247 | "price": 1500,
248 | "page": 200,
249 | "genres": [
250 | "コンピュータ"
251 | "検索エンジン",
252 | "Vespa"
253 | ],
254 | "reviews": {
255 | "quality": 40,
256 | "readability": 90,
257 | "cost": 80
258 | }
259 | }
260 | },
261 | ...
262 | ----
263 |
264 | まず、前節で起動した `vespa1` にログインします。
265 |
266 | [source, bash]
267 | ----
268 | $ sudo docker-compose exec vespa1 /bin/bash
269 | ----
270 |
271 | 次に、マウントされている `/vespa-sample-apps` 配下にあるサンプルデータを `vespa-feeder` を用いて登録します。
272 |
273 | [source, bash]
274 | ----
275 | [root@vespa1 /]# vespa-feeder /vespa-sample-apps/feed/book-data-put.json
276 |
277 |
278 | Messages sent to vespa (route default) :
279 | ----------------------------------------
280 | PutDocument: ok: 13 msgs/sec: 46.93 failed: 0 ignored: 0 latency(min, max, avg): 212, 219, 215
281 | ----
282 |
283 | これでドキュメントの登録は完了です。
284 | 試しに以下のコマンドで検索を行うと、
285 | `"totalCount":13` と登録されたドキュメントが返却されることが確認できます。
286 |
287 | [source, bash]
288 | ----
289 | [root@vespa1 /]# curl 'http://localhost:8080/search/?query=sddocname:book'
290 | {"root":{"id":"toplevel","relevance":1.0,"fields":{"totalCount":13}, ...
291 | ----
292 |
293 | [TIP]
294 | ====
295 | `sddocname:book` は `book` インデックスのドキュメント全体について検索することを意味しています。
296 | Solr や Elasticsearch でいうところの `q=\*:*` みたいなイメージです。
297 | ====
298 |
299 | [NOTE]
300 | ====
301 | feed されたドキュメントはレスポンスが返った時点で検索から見えるようになります。
302 | また、トランザクションログのディスクへの書き出しの周期は OS 依存となっていて、典型的には 30 秒程度で反映されます。
303 | 詳しくは
304 | http://docs.vespa.ai/documentation/content/consistency.html[Vespa consistency model]
305 | を参照してください。
306 | ====
--------------------------------------------------------------------------------
/docs/src/main/asciidoc/1_setup.adoc:
--------------------------------------------------------------------------------
1 | ////
2 | Copyright 2018 Yahoo Japan Corporation.
3 | Licensed under the terms of the MIT license.
4 | See LICENSE in the project root.
5 | ////
6 |
7 | [[setup]]
8 | = Vespa 環境の構築
9 | include::_include.adoc[]
10 |
11 | このセクションでは、
12 | http://docs.vespa.ai/documentation/vespa-quick-start.html[QuickStart]
13 | の中で利用されている Docker イメージ
14 | https://github.com/vespa-engine/docker-image[vespaengine/vespa]
15 | の処理を追うことで、Vespa の具体的な構築手順について見ていきます。
16 |
17 | [IMPORTANT]
18 | ====
19 | この構築手順は [red]#*CentOS7*# での実行を想定しています。
20 | また、クラスタまで構築する場合は最低でも [red]#*8GB*# 程度のメモリを持つ環境が必要となります。
21 | ====
22 |
23 | [[setup_install]]
24 | == Vespa のインストール
25 |
26 | https://github.com/vespa-engine/docker-image/blob/master/Dockerfile[Dockerfile]
27 | を見ると分かるように、Vespa は現在 rpm パッケージとして提供されています。
28 |
29 | Vespa を `yum` 経由でインストールするためには、まず Vespa のリポジトリを `yum` に追加する必要があります。
30 | 以下のように `yum-config-manager` を用いて `group_vespa-vespa-epel-7.repo` を追加します。
31 |
32 | [source, bash]
33 | ----
34 | # yum-config-manager --add-repo https://copr.fedorainfracloud.org/coprs/g/vespa/vespa/repo/epel-7/group_vespa-vespa-epel-7.repo
35 | ----
36 |
37 | 次に、Vespa の依存パッケージをインストールします。
38 |
39 | [source, bash]
40 | ----
41 | # yum install epel-release
42 | # yum install centos-release-scl
43 | ----
44 |
45 | 最後に、Vespa 本体をインストールします。
46 |
47 | [source, bash]
48 | ----
49 | # yum install vespa
50 | ----
51 |
52 | Vespa パッケージは `/opt/vespa` 配下にインストールされます。
53 |
54 | [source, bash]
55 | ----
56 | $ ls /opt/vespa
57 | bin conf etc include lib lib64 libexec man sbin share
58 | ----
59 |
60 | [TIP]
61 | ====
62 | Vespa 関連のコマンドは `/opt/vespa/bin` 配下にあるので、そこにパスを通しておくとコマンド実行が楽です。
63 |
64 | また、Vespa 関連のログは `/opt/vespa/logs/vespa/` 配下に出力され、
65 | 特に Vespa のアプリケーションログは `/opt/vespa/logs/vespa/vespa.log` に出力されます。
66 | ====
67 |
68 | [[setup_boot]]
69 | == Vespa の起動
70 |
71 | Vespa の構成を大雑把に図にまとめると以下のようになります
72 | (公式ドキュメントだと http://docs.vespa.ai/documentation/config-sentinel.html[この辺])。
73 |
74 | image::vespa_overview.jpg[width=700, align="center"]
75 |
76 | Vespa で起動されるプロセスは大きく分けて3つのグループに分けられます。
77 |
78 | * `configserver` (図の [blue]#青色# のプロセス群)
79 | ** いわゆる ZooKeeper のことで、クラスタ内で参照される設定ファイル群を管理
80 | * `vespa-config-sentinel` (図の [red]#赤色# のプロセス群)
81 | ** 各アプリケーションサーバにて対応するサービスプロセスを管理
82 | ** `config-proxy` を介して `configserver` から情報を取得します
83 | * `service` (図の [green]#緑色# のプロセス群)
84 | ** 実際の検索処理を担当するプロセス群
85 | ** 設定ファイルを元に必要なプロセスが `vespa-config-sentinel` によって起動されます
86 |
87 | このうち実際の処理に対応する `service` は、後述の設定ファイルのデプロイにて起動されるプロセスとなります。
88 | この時点ではまだ設定ファイルが登録されていないため、この節では `configserver` と `config-sentinel` の2つが対象となります。
89 |
90 | https://github.com/vespa-engine/docker-image/blob/master/include/start-container.sh[start-container.sh]
91 | を見ると分かるように、Vespa の起動は大きくわけて3つのステップに分けられます。
92 |
93 | . 環境変数の設定
94 | . `configserver` の起動
95 | . `vespa-config-sentinel` の起動
96 |
97 | [NOTE]
98 | ====
99 | 以降のコマンドの実行ユーザは各環境の設定に応じて変更してください。
100 | チュートリアルでは `root` 権限での実行を過程しています。
101 | ====
102 |
103 | [[setup_boot_env]]
104 | === 環境変数の設定
105 |
106 | 初めに、各 Vespa ノードで
107 | http://docs.vespa.ai/documentation/setting-vespa-variables.html[VESPA_CONFIGSERVERS]
108 | という環境変数に `configserver` のアドレスを指定する必要があります。
109 |
110 | [source, bash]
111 | ----
112 | # export VESPA_CONFIGSERVERS=${host1},${host2},...
113 | ----
114 |
115 | この環境変数が明示的に指定されていない場合、`localhost` がデフォルト値として利用されます。
116 |
117 | [[setup_boot_configserver]]
118 | === configserver の起動
119 |
120 | 次に、`VESPA_CONFIGSERVERS` にて指定したホスト上で以下のコマンドを実行し、`configserver` を起動します。
121 |
122 | [source, bash]
123 | ----
124 | # /opt/vespa/bin/vespa-start-configserver
125 | ----
126 |
127 | 起動後、以下のように2つのプロセスが起動していることが確認できます。
128 |
129 | [source, bash]
130 | ----
131 | # ps aux | grep vespa | grep -v grep
132 | vespa 571 0.0 0.4 97620 70704 ? Ss 07:09 0:00 vespa-runserver -s configserver -r 30 -p /opt/vespa/var/run/configserver.pid -- java -Xms128m -Xmx2048m -XX:+PreserveFramePointer -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/vespa/var/crash -XX:OnOutOfMemoryError=kill -9 %p -Djava.library.path=/opt/vespa/lib64 -Djava.awt.headless=true -Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.net.client.defaultConnectTimeout=5000 -Dsun.net.client.defaultReadTimeout=60000 -Djavax.net.ssl.keyStoreType=JKS -Djdisc.config.file=/opt/vespa/var/jdisc_core/configserver.properties -Djdisc.export.packages= -Djdisc.cache.path=/opt/vespa/var/vespa/bundlecache/configserver -Djdisc.debug.resources=false -Djdisc.bundle.path=/opt/vespa/lib/jars -Djdisc.logger.enabled=true -Djdisc.logger.level=ALL -Djdisc.logger.tag=jdisc/configserver -Dfile.encoding=UTF-8 -Dzookeeperlogfile=/opt/vespa/logs/vespa/zookeeper.configserver.log -cp /opt/vespa/lib/jars/jdisc_core-jar-with-dependencies.jar com.yahoo.jdisc.core.StandaloneMain standalone-container-jar-with-dependencies.jar
133 | vespa 572 55.6 3.9 4110952 655828 ? Sl 07:09 0:15 java -Xms128m -Xmx2048m -XX:+PreserveFramePointer -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/vespa/var/crash -XX:OnOutOfMemoryError=kill -9 %p -Djava.library.path=/opt/vespa/lib64 -Djava.awt.headless=true -Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.net.client.defaultConnectTimeout=5000 -Dsun.net.client.defaultReadTimeout=60000 -Djavax.net.ssl.keyStoreType=JKS -Djdisc.config.file=/opt/vespa/var/jdisc_core/configserver.properties -Djdisc.export.packages= -Djdisc.cache.path=/opt/vespa/var/vespa/bundlecache/configserver -Djdisc.debug.resources=false -Djdisc.bundle.path=/opt/vespa/lib/jars -Djdisc.logger.enabled=true -Djdisc.logger.level=ALL -Djdisc.logger.tag=jdisc/configserver -Dfile.encoding=UTF-8 -Dzookeeperlogfile=/opt/vespa/logs/vespa/zookeeper.configserver.log -cp /opt/vespa/lib/jars/jdisc_core-jar-with-dependencies.jar com.yahoo.jdisc.core.StandaloneMain standalone-container-jar-with-dependencies.jar
134 | ----
135 |
136 | [TIP]
137 | ====
138 | この2つのプロセスは以下のように親子関係になっています。
139 |
140 | [source, bash]
141 | ----
142 | # pstree -p
143 | bash(1)-+-pstree(827)
144 | `-vespa-runserver(571)---java(572)-+-{java}(573)
145 | |-{java}(574)
146 | |-{java}(575)
147 | ...
148 | ----
149 |
150 | Vespa のプロセスはこのように
151 | https://github.com/vespa-engine/vespa/blob/master/vespalog/src/logger/runserver.cpp[vespa-runserver]
152 | から fork される形式で起動されます。
153 | ====
154 |
155 | [[setup_boot_configsetinel]]
156 | === vespa-config-sentinel の起動
157 |
158 | 最後に、各 Vespa ノードにて 以下のコマンドを実行し、 `vespa-config-sentinel` を起動します。
159 |
160 | [source, bash]
161 | ----
162 | # /opt/vespa/bin/vespa-start-services
163 | ----
164 |
165 | 起動後、以下のように新たに4つのプロセスが起動していることが確認できます。
166 |
167 | [source, bash]
168 | ----
169 | # ps aux | grep vespa | grep -v configserver | grep -v grep
170 | vespa 1079 0.0 0.0 25604 708 ? Ss 07:27 0:00 vespa-runserver -r 10 -s configproxy -p var/run/configproxy.pid -- java -Xms32M -Xmx256M -XX:ThreadStackSize=256 -XX:MaxJavaStackTraceDepth=-1 -XX:OnOutOfMemoryError=kill -9 %p -Dproxyconfigsources=tcp/localhost:19070 -cp libexec/vespa/patches/configproxy:lib/jars/config-proxy-jar-with-dependencies.jar com.yahoo.vespa.config.proxy.ProxyServer 19090
171 | vespa 1080 0.8 0.2 1740948 44500 ? Sl 07:27 0:00 java -Xms32M -Xmx256M -XX:ThreadStackSize=256 -XX:MaxJavaStackTraceDepth=-1 -XX:OnOutOfMemoryError=kill -9 %p -Dproxyconfigsources=tcp/localhost:19070 -cp libexec/vespa/patches/configproxy:lib/jars/config-proxy-jar-with-dependencies.jar com.yahoo.vespa.config.proxy.ProxyServer 19090
172 | vespa 1148 0.0 0.0 25604 700 ? Ss 07:27 0:00 vespa-runserver -s config-sentinel -r 10 -p var/run/sentinel.pid -- sbin/vespa-config-sentinel -c hosts/76eae592a196
173 | vespa 1149 0.1 0.0 66552 4480 ? Sl 07:27 0:00 sbin/vespa-config-sentinel -c hosts/76eae592a196
174 | ----
175 |
176 | [TIP]
177 | ====
178 | この4つのプロセスも `configserver` と同様に
179 | https://github.com/vespa-engine/vespa/blob/master/vespalog/src/logger/runserver.cpp[vespa-runserver]
180 | 経由で起動しているため、実質のプロセス種別は `config-proxy` と `vespa-config-sentinel` の2つになります。
181 |
182 | [source, bash]
183 | ----
184 | # pstree -p
185 | bash(1)-+-pstree(1199)
186 | |-vespa-runserver(571)---java(572)-+-{java}(573)
187 | | |-{java}(574)
188 | ...
189 | |-vespa-runserver(1079)---java(1080)-+-{java}(1082)
190 | | |-{java}(1083)
191 | ...
192 | `-vespa-runserver(1148)---vespa-config-se(1191)-+-{vespa-config-se}(1192)
193 | |-{vespa-config-se}(1193)
194 | ...
195 | ----
196 | ====
197 |
198 | [NOTE]
199 | ====
200 | もし、`vespa-config-sentinel` を起動する前に `configserver` に設定ファイルをアップロードしている場合、
201 | 設定ファイルの記述に対応する各種 `service` もこのタイミングで起動されます。
202 | ====
203 |
204 | [[setup_tutorial]]
205 | ## チュートリアル環境の構築
206 |
207 | 本チュートリアルでは、ここまで見てきた `Dockerfile` を用いて実際に Docker 上に Vespa を構築します。
208 |
209 | [IMPORTANT]
210 | =====
211 | 実行には `docker` と `docker-compose` が必要です。
212 | =====
213 |
214 | 必要な設定はチュートリアルに付属の `docker-compose.xml` に定義されているため、
215 | 以下のように `docker-compose` を用いてコンテナを起動します。
216 |
217 | [source, bash, subs="attributes"]
218 | ----
219 | $ git clone {github_url}
220 | $ cd vespa-tutorial
221 | $ sudo docker-compose up -d
222 | ----
223 |
224 | 起動が完了したら、付属のスクリプトを用いて環境のステータスを確認します。
225 |
226 | [source, bash]
227 | ----
228 | $ utils/vespa_status
229 | configuration server ... [ OK ]
230 | application server (vespa1) ... [ NG ]
231 | application server (vespa2) ... [ NG ]
232 | application server (vespa3) ... [ NG ]
233 | ----
234 |
235 | この時点ではまだ設定ファイルがなく、
236 | `service` が起動していないため `configserver` のみが `OK` となります。
237 |
238 | [TIP]
239 | ====
240 | `utils/vespa_status` では以下の URL にアクセスしてステータスを確認しています。
241 |
242 | [source, bash]
243 | ----
244 | // configserverのステータス確認
245 | $ curl -sI 'http://localhost:19071/ApplicationStatus'
246 | HTTP/1.1 200 OK
247 | Date: Tue, 13 Feb 2018 08:27:04 GMT
248 | Content-Type: application/json
249 | Content-Length: 10683
250 |
251 | // 各service (container) のステータス確認
252 | $ curl -sI 'http://localhost:8080/ApplicationStatus'
253 | $ curl -sI 'http://localhost:8081/ApplicationStatus'
254 | $ curl -sI 'http://localhost:8082/ApplicationStatus'
255 | ----
256 | ====
--------------------------------------------------------------------------------
/docs/src/main/asciidoc/7_comparing.adoc:
--------------------------------------------------------------------------------
1 | ////
2 | Copyright 2018 Yahoo Japan Corporation.
3 | Licensed under the terms of the MIT license.
4 | See LICENSE in the project root.
5 | ////
6 |
7 | [[comparing]]
8 | = Solr/Elasticsearch との機能比較
9 | include::_include.adoc[]
10 |
11 | このセクションでは、Vespa と他の検索エンジンを比べて、
12 | 具体的にどのような差異があるのか見ていきます。
13 |
14 | [WARNING]
15 | ====
16 | 著者の個人的な見解がかなり含まれます。
17 | ====
18 |
19 | [NOTE]
20 | ====
21 | http://vespa.ai/[Vespaの公式ページ]
22 | の下の方にも機能比較表があります。
23 | ====
24 |
25 | ここでは、比較対象として
26 | http://lucene.apache.org/solr/[Solr]
27 | と
28 | https://www.elastic.co/jp/products/elasticsearch[Elasticsearch]
29 | の2つを取り上げます。
30 | Solr と Elasticsearch は共に Lucene をコアとする検索エンジンで、
31 | 世界的にも多くの利用実績がある代表的な OSS の一つです。
32 |
33 | [NOTE]
34 | ====
35 | チュートリアルには `appendix` に Solr と Elasticsearch の Docker コンテナの設定があります。
36 | それぞれのディレクトリに配置された `boot.sh` を実行することで Vespa と同じドキュメントを含む Solr および Elasticsearch を起動できます。
37 | 詳しいコマンドは `appendix` にある `README.md` を参照してください。
38 | ====
39 |
40 | [[comparing_update]]
41 | == スキーマと更新
42 |
43 | [[comparing_update_dynamic]]
44 | === 動的フィールド
45 |
46 | Solr/Elasticsearch と Vespa のスキーマ定義を比較したとき、
47 | 大きな違いの一つとして動的フィールドのサポートの有無があります。
48 |
49 | Solr では `dynamic fields` を、
50 | Elasticsearch では `dynamic templates` を使ってインデックスされたフィールド名に応じて動的にフィールドを追加することができます。
51 | この機能によって、Solr/Elasticsearch ではスキーマに縛られにくい柔軟なインデクシングを可能としています。
52 | 特に、Elasticsearch では
53 | https://www.elastic.co/guide/en/elasticsearch/reference/current/object.html[Object datetype]
54 | のようにかなり自由なデータをインデクシングできるのが特徴です。
55 |
56 | 一方、Vespa では前述のような動的フィールドに対応する機能は現状では提供されていません。
57 | そのため、ユーザは事前にスキーマ設計をしっかりと行うことが求められます。
58 |
59 | サービスとして検索エンジンを使う場合、
60 | 一般的にスキーマ設計を行うはずなので双方であまり差異はありませんが、
61 | データストアとして検索エンジンを使う場合は、
62 | 動的フィールドが可能な Solr/Elasticsearch の方が柔軟性が高いと考えられます。
63 |
64 | [NOTE]
65 | ====
66 | データストア・アナリシス的な用途なら
67 | https://www.elastic.co/jp/products[Elastic Stack]
68 | が有名かと思います。
69 | ====
70 |
71 | [[comparing_update_type]]
72 | === データ型
73 |
74 | Vespa、Solr/Elasticsearch ともに基本的なデータ型については大きな差異はありません。
75 | 2つで動作が大きく異なるケースとして、配列のような多次元の値を扱う場合があげられます。
76 |
77 | Vespa では
78 | <<2_config.adoc#config_file_searchdefinitions,searchdefinitions>>
79 | で述べたように、`array` と `weightedset` という2つの型をサポートしています。
80 | Vespa での複数値は、
81 | <<5_ranking.adoc#ranking_expression_feature_attribute,attribute>>
82 | といった関数を用いてインデックスやキーで各値に個別アクセスができ、
83 | 値の順序 (例えば階層構造とか) に意味を持たせることができます。
84 | また、Vespa では
85 | <<5_ranking.adoc#ranking_other_tensor,テンソルを用いたスコア計算>>
86 | で述べたテンソル型という高次元のデータ型を保持することができ、
87 | 近年注目を集めている分散表現やディープライーニングといった先進技術との親和性が高いことも大きなポイントです。
88 |
89 | 一方、Solr/Elasticsearch の場合、配列型以外については設計を工夫する必要があります。
90 | チュートリアルの例では、前述の動的フィールドを用いてマッピングを行うことで Vespa の `weightedset` の代替としています。
91 | この方法では、キーとなる新しい値が登録されるたびに裏側では新しいフィールドが定義されることとなるため、
92 | キーのバリエーションが非常に多いケースを扱うことが困難です。
93 | また、高次元ベクトルを検索で扱う場合に、Solr/Elasticsearch ではまだ適切なデータ型がないように感じます。
94 |
95 | [TIP]
96 | ====
97 | Solr/Elasticsearch の配列は、インデックス上では投入順序が保持されないという特徴があります。
98 | これは、内部で利用している Lucene の `docValues` というデータストアが、
99 | 複数値を登録する時にソートして値を保持するという制約があるためです。
100 | そのため、Vespa のようにインデックス指定で値を取得する、ということができません。
101 | ====
102 |
103 | [NOTE]
104 | ====
105 | Solr/Elasticsearch でも拡張フィールドを独自実装すれば、`weightedset` のような型を作ることは一応可能です。
106 | ただし、スコア計算との連携なども考えた場合、ランキング部分についても拡張が必要になります。
107 | ====
108 |
109 | [[comparing_update_realtime]]
110 | === リアルタイム性
111 |
112 | Solr/Elasticsearch の基盤となっている Lucene では、
113 | `softCommit` と `hardCommit` という2つのコミットによってインデックスを管理します。
114 | 前者は更新内容を検索できるようにするもの、後者は更新内容をディスクに永続化するものに対応します。
115 | Solr/Elasticsearch では、この `softCommit` の間隔を制御することで Near Real Time (NRT) 検索を実現しています (
116 | https://lucene.apache.org/solr/guide/7_2/near-real-time-searching.html[Solr]、
117 | https://www.elastic.co/guide/en/elasticsearch/guide/current/near-real-time.html[Elasticsearch]
118 | )。
119 | より高いリアルタイム性が必要な場合、この `softCommit` の間隔をチューニングすることになりますが、
120 | `softCommit` では `Searcher` と呼ばれる検索を担当するインスタンスの再生成が走るため、
121 | 実行にある程度のコストがかかります。
122 | そのため、`softCommit` の頻度が検索などのパフォーマンスに影響を与え、
123 | 性能要件によっては短い時間を設定することが困難な場合が多いです。
124 |
125 | Vespa では公式ドキュメントの
126 | http://docs.vespa.ai/documentation/features.html[Features]
127 | や
128 | http://docs.vespa.ai/documentation/content/consistency.html[Vespa consistency model]
129 | で述べられているように、
130 | 更新リクエストのレスポンスが返ったタイミングで検索の対象となるように設計されています。
131 | そのため、ドキュメント更新のリアルタイム性が非常に高い検索エンジンといえます。
132 |
133 | [WARNING]
134 | ====
135 | Vespa のインデクシングの中身の詳細については公式ドキュメントが特になかったため、
136 | ここでは上記 Vespa のドキュメントでの謳い文句と運用での経験ベースに話をしています。
137 |
138 | Vespa のインデックスのざっくりとした流れは
139 | http://docs.vespa.ai/documentation/performance/sizing-search.html[Vespa search sizing guide]
140 | の `Search/Content Node` で議論されており、
141 | 新規ドキュメントはオンメモリ上のインデックス (`memory index`) に追加され、
142 | サイズや時間経過を条件に適宜ディスク上のインデックスに書き出される、という動作をします。
143 | ====
144 |
145 | [[comparing_search]]
146 | == 検索周り
147 |
148 | [[comparing_search_aggregation]]
149 | === 集約処理
150 |
151 | 単純な検索機能については Vespa と Solr/Elasticsearch では大きな差はないですが、
152 | 集約処理については Vespa と Solr/Elasticsearch で差異があります。
153 |
154 | 検索結果を羅列する `grouping` という観点では、
155 | Vespa と Elasticsearch については大きく差異はない印象です。
156 | Vespa なら
157 | <<4_search.adoc#search_grouping,グルーピング検索>>
158 | で紹介したように、
159 | http://docs.vespa.ai/documentation/reference/grouping-syntax.html[Query result grouping reference]
160 | に記載された DSL を用いて多段グルーピングができ、
161 | Elasticsearch も
162 | https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html[Aggregations]
163 | の
164 | https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-top-hits-aggregation.html[top_hits]
165 | を組み合わせることで多段グルーピングが可能です。
166 |
167 | [NOTE]
168 | ====
169 | Solr は多段グルーピングをサポートしていません。
170 | また、
171 | https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-collapse.html[Field Collapsing]
172 | については集約処理とは若干用途が異なるので議論の対象から外しています。
173 | ====
174 |
175 | 一方、グループ内でのソートでは、Vespa は
176 | <<5_ranking.adoc#ranking,Vespa とランキング>>
177 | で紹介した `rank-profile` の定義をそのままグループ内のリランキングに適用できるため、
178 | Solr/Elasticsearch に比べてグループ内の上位をより精度よく選択することができます。
179 |
180 | 各グループの統計値といった特定の側面を計算する `faceting` という観点では、
181 | Solr/Elasticsearch の方が Vespa に比べて機能が豊富のように感じます。
182 | 前述の DSL のお陰で Vespa は `faceting` でも様々な集約条件を定義できます。
183 | 一方、Solr では
184 | https://lucene.apache.org/solr/guide/7_2/streaming-expressions.html[Streaming Expressions]
185 | を用いることで、検索結果をストリーミング処理の要領で細かく加工することが可能です。
186 | また、Elasticsearch も
187 | https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html[Aggregations]
188 | で紹介されているような多くの集約処理があったり、
189 | https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting.html[Scripting]
190 | で細かい調整が可能だったりと高機能です。
191 |
192 | [[comparing_search_advanced]]
193 | === 高度な検索機能
194 |
195 | <<4_search.adoc#search_other,その他の検索>>
196 | で紹介したように、
197 | WAND 検索、Predicate フィールド
198 | と Vespa には Solr/Elasticsearch にはない検索機能があります。
199 |
200 | [NOTE]
201 | ====
202 | 位置検索は Solr/Elasticsearch にもあります。
203 | また、ストリーミング検索については Solr/Elasticsearch のワイルドカードクエリと対応します。
204 | ====
205 |
206 | Predicate フィールドについては、Elasticsearch では
207 | https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-percolate-query.html[Percolate Query]
208 | という似た機能が存在しますが、Percolate Query ではクエリをインデックスに登録して入力ドキュメントに対して当てるという動作なのに対し、
209 | Vespa の Predicate フィールドは各ドキュメントのフィールド値として条件式を埋め込むため、
210 | 各ドキュメントを個別に制御できる点が異なります。
211 |
212 | これらの機能は単純な全文検索ではなかなか使うタイミングがありませんが、
213 | レコメンド、広告、メールなど具体的にターゲットを絞った場合に恩恵を受けられるケースがあるかもしれません。
214 |
215 | [[comparing_ranking]]
216 | == ランキング
217 |
218 | [IMPORTANT]
219 | ====
220 | ランキングは OSS で利用できる範囲での比較になります。
221 | そのため、Solr の有償プロダクトである
222 | https://lucidworks.com/products/[Fusion]
223 | や、Elasticsearch の有償の
224 | https://www.elastic.co/jp/products/x-pack[X-PACK]
225 | で提供されている機能については対象外としています。
226 | ====
227 |
228 | [[comparing_ranking_phase]]
229 | === フェーズ分け
230 |
231 | 検索エンジンのコア機能を使って任意のスコア式を記述する場合、
232 | Solr は
233 | https://lucene.apache.org/solr/guide/7_2/function-queries.html[FunctionQuery]
234 | を、Elasticsearch は
235 | https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html#score-functions[FunctionScoreQuery]
236 | を用いてスコアを定義します。
237 | また、最近では上位の結果をリランキングするプラグインとして Solr と Elasticsearch それぞれで
238 | LTR (Learning To Rank) プラグインが提供されています (
239 | https://lucene.apache.org/solr/guide/7_2/learning-to-rank.html[Solr]、
240 | http://elasticsearch-learning-to-rank.readthedocs.io/en/latest/[Elasticsearch]
241 | )。
242 |
243 | [TIP]
244 | ====
245 | Solr ではこれに加えて
246 | https://lucene.apache.org/solr/guide/7_2/query-re-ranking.html[ReRakQParserPlugin]
247 | と `FunctionQuery` を組み合わせる方法もあります。
248 | ====
249 |
250 | Vespa の
251 | <<5_ranking.adoc#ranking_phase,ランキングの流れ>>
252 | と対応させると、
253 | Solr の `FunctionQuery` および Elasticsearch の `FunctionScoreQuery` が `first-phase` に、
254 | LTRプラグインが `second-phase` に対応します。
255 |
256 | このように、Solr と Elasticsearch では2つのフェーズのランキングが別の機能として提供されます。
257 | そのため、2つのフェーズでスコア式を記述する場合、別々に定義を書かなくてはならないというのがネックです。
258 | それに対して、Vespa は `rank-profile` に全てまとめて定義できるため、
259 | ランキングの定義は Vespa の方がスッキリしています。
260 |
261 | [[comparing_ranking_dsl]]
262 | === 記述の自由度
263 |
264 | 単純な記述の自由度という観点では、
265 | 最も自由度が高いのは
266 | https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting.html[Scripting]
267 | を用いて Groovy ライクに書ける Elasticsearch の `Function Score Query` かと思います。
268 | ただし、`Scripting` ではいわゆる素性が扱えないため、
269 | 高度なモデルを定義したい場合にネックになると考えられます。
270 |
271 | [TIP]
272 | ====
273 | `Scripting` は
274 | http://asm.ow2.org/[ASM]
275 | を用いてバイトコードを動的に生成することで高速動作させています。
276 | ====
277 |
278 | 一方、Solr と Elasticsearch の LTR プラグインでは、
279 | 検索クエリを素性として定義できるため、より精度の高いモデルを定義できます。
280 | ただ、こちらはデフォルトで利用できるモデルの型が線形モデルとアンサンブル木に限定されており、
281 | チュートリアルの `price_boost` のような独自の数式を書きたい場合は、
282 | 別途対応するモデルを実装する必要があるというのが難点です。
283 |
284 | Vespa はランク式を DSL として提供しつつ、
285 | <<5_ranking.adoc#ranking_expression_feature,組み込み素性>>
286 | で紹介したように様々なランク素性が利用できるため、自由に高度なモデルの記述が可能です。
287 | また、検索クエリを使う LTR プラグインと異なり、これらランク素性はコードとして実装されているため動作が速いのも特徴です。
288 | ただし、組み込みの素性以外を使いたいという場合、`rank-profile` のマクロとして数式の定義はできますが、
289 | 全く新しい素性を実装して追加するためには検索コアの実装に手を加える必要があり、難易度が高いです。
290 |
291 | [[comparing_ranking_advanced]]
292 | === 高度なランキング
293 |
294 | <<5_ranking.adoc#ranking_other_tensor,テンソルを用いたスコア計算>>
295 | で紹介したテンソルへの対応は、Vespa がもつ非常に強力な機能です。
296 | 内積や行列演算は state-of-the-art な手法をランキングで用いたい場合に必要になる機能なので、
297 | 検索エンジンが標準でそれをサポートしている意味は大きいです。
298 |
299 | また、
300 | <<4_search.adoc#search_other_wand,WAND 検索>>
301 | もランキングの視点で見ると疎ベクトルの内積計算に対応しており、
302 | Vespa の持つ高度なランキング機能の一つと捉えることができます。
303 |
304 | WAND 検索については、
305 | Solr/Elasticsearch でも payload でテキストの単語に重みを埋め込み、
306 | 検索クエリにブースト値を付与してスコア計算に組み込めば似たようなことは可能です。
307 | ただし、WAND 検索のような賢い枝刈りアルゴリズムがないため、Vespa より速度がでないと思います。
308 |
309 | [NOTE]
310 | ====
311 | 最近では Solr コミュニティで
312 | https://deeplearning4j.org/[DL4J]
313 | を LTR プラグインに組み込もうという動きもあります (
314 | https://issues.apache.org/jira/browse/SOLR-11838[SOLR-11838]
315 | )。
316 | ====
317 |
318 | [[comparing_scalability]]
319 | == スケーラビリティ
320 |
321 | [[comparing_scalability_distrib]]
322 | === 分散インデクシング
323 |
324 | Vepsa は
325 | <<6_clustering.adoc#clustering_distrib,ドキュメントの分散>>
326 | でも述べたように、ドキュメントを `bucket` という細かい粒度で分散させるのが特徴で、
327 | ここは Solr/Elasticsearch と大きく異なる点の一つです。
328 |
329 | Solr/Elasticsearch では、クラスタ上でのインデックスの分割数 (シャード数) を事前に決めて分散インデクシングを行います。
330 | このシャード数は、一度決めると容易にその数を変更することができず、
331 | 変更する場合はシャードの分割やインデックスの再構築が必要となります。
332 |
333 | 一方、Vespa は `bucket` というより細かい粒度でドキュメントを管理しており、
334 | その分割数も状況によって増減します。
335 | そのため、ユーザはインデックスの冗長数 (Solr/Elasticsearch のレプリカ数) だけを意識すればよく、
336 | 分散インデクシングの管理が非常に容易かつ効率的に行うことができます。
337 |
338 | ドキュメントを細かい粒度で分散させることは、障害時のパフォーマンスという観点でも有意性があります。
339 | Solr/Elasticsearch の場合、特定のノードがダウンすると、
340 | 本来そのノードが処理するはずだったリクエストを対応するレプリカを持つノード群が肩代わりします。
341 | そのため、障害時に特定のノードのリクエスト数が `1 + 1/#replicas` 倍に増加することになります。
342 | 一方、Vespa の場合、特定のノードがダウンしたとしても、対になる `bucket` はクラスタ全体に分散しているため、
343 | ダウンしたノードのリクエストをクラスタ全体でカバーすることができ、
344 | リクエスト数の増加を `1 + 1/#nodes` 倍に抑えることができます。
345 | 基本的に `#nodes > #replicas` となるため、障害時の負荷増加は Vespa の方が小さくなります。
346 |
347 | [[comparing_scalability_node]]
348 | === ノードの追加・削除
349 |
350 | クラスタへのノードの追加・削除も、
351 | Vespa では非常に簡単に行うことができます。
352 |
353 | Solr/Elasticsearch の場合、前述のようにシャード数が変更できないため、
354 | ノードの増減にあわせてシャード/レプリカ単位での再分配を行うことになります。
355 | この作業は、Elasticsearch の場合、再分配は自動で行われますが、Solr の場合は現状手作業で行う必要があります。
356 | シャード自体の分割が困難なため、ノード追加・削除が発生した場合、
357 | 場合によってはシャード数を再設計して再インデックスする必要があります。
358 |
359 | Vespa では、
360 | <<6_clustering.adoc#clustering_setup_deploy,クラスタ設定の反映>>
361 | の例で示したように、`services.xml` と `hosts.xml` に追加・削除するノードの情報を追記して反映させるだけで、
362 | Vespa が `bucket` の再分配を行ってくれます。
363 | また、Solr/Elasticsearch のようにシャード数を気にする必要もないため、
364 | カジュアルにノードの増減を行うことが可能です。
365 |
366 | [[comparing_extensibility]]
367 | == 拡張性
368 |
369 | [[comparing_extensibility_language]]
370 | === 多言語対応
371 |
372 | 多言語対応という点では、現状 Vespa は Solr/Elasticsearch に比べると大きく差があります。
373 |
374 | Solr/Elasticsearch は世界的に利用実績があるため、現状で多くの言語をサポートしています。
375 | また、Solr/Elasticsearch の言語処理は、
376 | 入力文字列の1文字1文字に変換を加える `CharFilter`、
377 | 文字列をトークン分けする `Tokenizer`、
378 | トークン毎に変換を加える `TokenFilter`
379 | の3つのクラスの組み合わせによって定義されます (
380 | https://lucene.apache.org/solr/guide/7_2/understanding-analyzers-tokenizers-and-filters.html[Solr]、
381 | https://www.elastic.co/guide/en/elasticsearch/reference/current/analyzer-anatomy.html[Elasticsearch]
382 | )。
383 | そのため、これらの組み合わせ方によって言語処理フローを柔軟に構築できるというのも特徴の一つです。
384 |
385 | 一方、Vespa は OSS としてまだ若いこともあり、公式でサポートしている言語は今のところ英語圏に限られています。
386 | Vespa では
387 | http://docs.vespa.ai/documentation/linguistics.html[Linguistics]
388 | というクラスを拡張することで対応言語を増やすことができます。
389 | Solr/Elasticsearch とは異なり、
390 | Vespa ではステミングや正規化といった処理も `Lingusitics` の中に内包させます。
391 | 幸い、今回使用した `KuromojiLinguistics` のように、
392 | 既存のパッケージを組み合わせれば対応自体はそこまで難しくはないので、
393 | 今後ユーザが増えていって多言語対応の差自体が埋まっていくことを期待します。
394 |
395 | [[comparing_extensibility_plugin]]
396 | === プラグインの追加
397 |
398 | Solr/Elasticsearch は、
399 | 世の中にサードパーティ製のプラグインがたくさんあることからも分かるように、
400 | 拡張性が高い検索エンジンとなっています。
401 | また、Solr/Elasticsearch は Pure Java で実装された検索エンジンなので、
402 | 機能開発の敷居も比較的低いといえます。
403 |
404 | Vespa でプラグインを追加する場合は、基本的に
405 | <<2_config.adoc#config_file_services_container,container>>
406 | にプラグインを追加していく形になります。
407 | 典型的な拡張は、
408 | 検索リクエスト/レスポンスの加工を行う `Searcher`、
409 | 更新リクエストの加工を行う `DocumentProcessor` の追加です。
410 | これらはサーブレット・フィルタのようにチェインさせてパイプライン処理させるように利用します(
411 | http://docs.vespa.ai/documentation/component-types.html[Container component types]
412 | )。
413 | また、Vespa の `container` は
414 | http://docs.vespa.ai/documentation/jdisc/[JDisc]
415 | (Java Data Intensive Serving Container) というフレームワークの上で動作しており、
416 | 内部で独自のサーバを立てたり、DI コンテナでインジェクトするインスタンスを差し替えたりと、
417 | 色々自由に動作を変えることができます。
418 |
419 | [TIP]
420 | ====
421 | 前述の `Linguistics` も JDisc の DI コンテナ経由で独自実装をインジェクトすることで動作を差し替えています。
422 | ====
423 |
424 | 一方、Vespa の検索コアは
425 | http://docs.vespa.ai/documentation/proton.html[proton]
426 | と呼ばれる C++ のパッケージとなっており、
427 | こちらに機能を追加したい場合はコードを実装して再ビルドする必要があります。
428 | そのため、検索コアの深いところまで手を加えたい場合、
429 | Vespa は Solr/Elasticsearch に比べると実装難易度が高いです。
430 |
431 | [[comparing_summary]]
432 | == まとめ
433 |
434 | ここまで述べてきた Vespa と Solr/Elasticsearch の特徴を、
435 | http://vespa.ai/[公式サイト]
436 | の表のようにまとめると以下のようになります。
437 |
438 | [options="header", cols="1,1,1,3a"]
439 | |====
440 | ^| 特徴 ^| Vespa ^| Solr/Elasticsearch ^| 備考
441 | ^| データ解析 ^| ☆ ^| ☆☆☆ |
442 | * Solr/Elasticsearch は動的フィールドが使える
443 | * Solr/Elasticsearch の方が解析処理が充実してる印象
444 | * Elastic Stack のように周辺パッケージも充実
445 | ^| 全文検索 ^| ☆☆☆ ^| ☆☆ |
446 | * 両方とも検索・集約の基本機能はカバー
447 | * Vespa では WAND 検索のような高度な検索機能を提供
448 | * インデクシングが Vespa の方が優秀
449 | ^| ランキング ^| ☆☆☆ ^| ☆ |
450 | * Vespa は標準機能でランキングをフルサポート
451 | * フェーズ制御が Vespa の方がしっかりしてる
452 | * テンソルが使えるなど Vespa の方が先進的
453 | ^| スケーラビリティ ^| ☆☆☆ ^| ☆☆ |
454 | * Vespa の方が分散粒度が細かく柔軟性が高い
455 | * ノード増減時のインデックス調整が Vespa の方が楽
456 | * Solr/Elasticsearch はシャード数が変更しづらい
457 | ^| 拡張性 ^| ☆☆ ^| ☆☆☆ |
458 | * Solr/Elasticsearch の方が敷居は低め
459 | * Vespa も自由度は高いが検索コアがいじりにくい
460 | * 多言語対応などの既存遺産の差が大きい
461 | |====
462 |
463 | 見解としては公式サイトでの表と同じで、全文検索がしたいなら Vespa はよい選択肢になると思います。
464 | 逆に、データ解析での利用を検討している場合は、Elastic Stack のようなパッケージを利用した方が効率的かと思います。
465 |
466 | 機能面では、まずランキング機能が Solr/Elasticsearch と比べてかなり先進的で、
467 | 特にテンソルが扱えることで、近年話題の Deep Learning 系の技術を取り込める点は大きな強みです。
468 | また、ドキュメント分散の仕組みが賢く、スケールアウトが容易な点も、
469 | 特に大規模なクラスタを組むようなケースで大きなポイントになるかと思います。
470 |
471 | 一方、拡張性という観点では、OSS としてまだ若いこともあり、
472 | Solr/Elasticsearch に比べてサードパーティ製のプラグインなどのオプションが少ないところはネックかと思います。
473 | Vespa 自体の拡張性は JDisc のおかげで非常に高いのですが、
474 | 個人的には検索コアに手が入れにくいところが気になる部分で、
475 | このため拡張性については公式サイトと優劣を逆につけています。
--------------------------------------------------------------------------------
/docs/src/main/asciidoc/2_config.adoc:
--------------------------------------------------------------------------------
1 | ////
2 | Copyright 2018 Yahoo Japan Corporation.
3 | Licensed under the terms of the MIT license.
4 | See LICENSE in the project root.
5 | ////
6 |
7 | [[config]]
8 | = Vespa の設定
9 | include::_include.adoc[]
10 |
11 | このセクションでは、Vespa の具体的な設定方法について見ていきます。
12 |
13 | [NOTE]
14 | ====
15 | 複数ノードを使ったクラスタの構成については
16 | <<6_clustering.adoc#clustering,Vespa とクラスタリング>>
17 | を参照してください。
18 | ====
19 |
20 | [[config_file]]
21 | == 設定ファイル
22 |
23 | Vespa の設定ファイルは以下のようなディレクトリ構成で定義されます。
24 |
25 | [source, bash]
26 | ----
27 | myconfig/
28 | |- hosts.xml
29 | |- services.xml
30 | |- searchdefinitions/
31 | | |- myindex.sd
32 | | `- ...
33 | |- components/
34 | | |- myplugin.jar
35 | | `- ...
36 | `- search/query-profiles/
37 | `- myprofile.xml
38 | ----
39 |
40 | 各設定ファイルにはそれぞれ以下のような役割があります。
41 |
42 | [options="header", cols="1,4"]
43 | |====
44 | ^| 設定ファイル ^| 役割
45 | | http://docs.vespa.ai/documentation/reference/hosts.html[hosts.xml] | Vespa クラスタに所属する host 名の一覧。
46 | | http://docs.vespa.ai/documentation/reference/services.html[services.xml] | Vespa で起動するサービスの定義。
47 | | http://docs.vespa.ai/documentation/reference/search-definitions-reference.html[searchdefinitions] | Vespa で扱うインデックスの定義。
48 | | components | Vespa で利用するプラグイン (`jar` ファイル)。
49 | | http://docs.vespa.ai/documentation/reference/query-profile-reference.html[query-profiles] | 検索クエリに付与するデフォルトパラメタの定義。
50 | |====
51 |
52 | このうち最低限必要となるのは `hosts.xml`、`services.xml`、`searchdefinitions` の3つです。
53 |
54 | [[config_file_hosts]]
55 | === hosts.xml
56 |
57 | `hosts.xml` には Vespa クラスタに所属するホスト名の定義を記述します。
58 | 例えば、チュートリアルの `sample-apps/config/basic/hosts.xml` では以下のように記述されています。
59 |
60 | [source, xml]
61 | ----
62 |
63 |
64 |
65 | node1
66 |
67 |
68 | ----
69 |
70 | 上記例のように、`hosts.xml` ではノードのホスト名を `name` 属性で、
71 | 後述の `services.xml` で参照されるエイリアス名を `alias` 要素でそれぞれ定義します。
72 |
73 | [TIP]
74 | ====
75 | `alias` は一つのホストに対して複数定義できます。例えば以下のように役割毎に `alias` を定義することで、
76 | `services.xml` との対応を明確にできます。
77 |
78 | [source, xml]
79 | ---
80 |
81 | search1
82 | docproc1
83 | content1
84 |
85 | ---
86 | ====
87 |
88 | [[config_file_services]]
89 | === services.xml
90 |
91 | `services.xml` には Vespa の各ノードがどのようなサービスを起動するかの設定を記述します。
92 | Vespa のサービスは
93 | http://docs.vespa.ai/documentation/reference/services-admin.html[admin]、
94 | http://docs.vespa.ai/documentation/reference/services-container.html[container]、
95 | http://docs.vespa.ai/documentation/reference/services-content.html[content]
96 | の3つに大別されます
97 |
98 | image::vespa_architecture.jpg[width=900, align="center"]
99 |
100 | [TIP]
101 | ====
102 | サービスを含めて Vespa で起動されるプロセスの一覧は以下のページに記載されています。
103 |
104 | * http://docs.vespa.ai/documentation/reference/files-processes-and-ports.html[Files, processes and ports]
105 |
106 | なお、Vespa は `C\++` と `Java` の2つの言語で書かれた複数のプロセスが通信し合うことで動作しています。
107 | 大雑把に分けると、`admin` と `container` は `Java` のコード、`content` は `C++` のコードで実装されています。
108 | ====
109 |
110 | [NOTE]
111 | ====
112 | 実際は他にもサービスがいますが、ここでは動作上必要な前述の3つのみに対象を絞って説明します。
113 | 他のサービスの設定については
114 | http://docs.vespa.ai/documentation/reference/services.html[公式ドキュメント]
115 | を参照してください。
116 | ====
117 |
118 | [[config_file_services_admin]]
119 | ==== admin
120 |
121 | Vespa クラスタの管理サービスで、
122 | チュートリアルの `sample-apps/config/basic/services.xml` では以下のように記述されています。
123 |
124 | [source, xml]
125 | ----
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 | ----
137 |
138 | それぞれの要素は以下のようなサービスを意味しています。
139 |
140 | [options="header", cols="1,4"]
141 | |====
142 | ^| 要素 ^| 役割
143 | | adminserver | 管理ノード本体を担当するサーバで、admin コマンドの実行ノードになります (たぶん)。
144 | | configservers | ZooKeeper に対応するサーバ群で、設定ファイルの管理を行います。複数ノードを指定することで冗長化が可能です。
145 | | logserver | Vespa のログ収集を担当するサーバで、Vespa クラスタ内の全サービスのログをアーカイブします。
146 | | slobroks | `slobrok` は Service location broker の略で、各サービスのロケーション情報を管理します。
147 | |====
148 |
149 | [NOTE]
150 | ====
151 | これ以外にも ` cluster-controllers`、`filedistribution` および `monitoring` という要素があります。
152 | 詳しくは http://docs.vespa.ai/documentation/reference/services-admin.html[公式ドキュメント] を参照してください。
153 | ====
154 |
155 | [[config_file_services_container]]
156 | ==== container
157 |
158 | Vespa のフロントエンドに対応するサービスで、
159 | チュートリアルの `sample-apps/config/basic/services.xml` では以下のように記述されています。
160 |
161 | [source, xml]
162 | ----
163 |
164 |
166 |
167 | search
168 | true
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 | ----
179 |
180 | `container` はユーザからの検索リクエストや更新リクエストを受け付ける層に対応しています。
181 | 上記の設定の場合、セクションの中身は大きく以下の3つのグループに分解できます。
182 |
183 | * `component`
184 | * `document-api`, `document-processing`, `search`
185 | * `nodes`
186 |
187 | `component` は追加モジュールの定義で、
188 | ここでは日本語の形態素解析器に対応する `KuromojiLinguistics` というモジュールを追加しています。
189 |
190 | [TIP]
191 | ====
192 | より具体的には、`component` で定義されたクラスのインスタンスが Vespa の DI コンテナにデプロイされるイメージです。
193 | `KuromojiLinguistics` は言語に対応するインタフェースである `Linguistics` を継承したクラスであり、
194 | Vespa のコード上で `Lingusitics` を Inject している箇所に差し込まれます。
195 | ====
196 |
197 | [IMPORTANT]
198 | ====
199 | `KuromojiLinguistics` を有効にするには別途 `jar` ファイルを入手して配置する必要があります。
200 | 詳しくは
201 | <>
202 | で述べます。
203 | ====
204 |
205 | `document-api`、`document-processing` および `search` はコンテナ上に起動する各種サービスに対応しており、
206 | それぞれ以下のような機能を有効化します。
207 |
208 | [options="header", cols="1,4"]
209 | |====
210 | ^| 要素 ^| 機能
211 | | document-api | 更新リクエストのための http://docs.vespa.ai/documentation/document-api.html[Document API] を有効にします。
212 | | document-processing | 更新対象のドキュメントに対する加工処理 (`DocumentProcessorChain`) を有効にします。
213 | | search | 検索リクエストのための http://docs.vespa.ai/documentation/reference/search-api-reference.html[Search API] を有効化します。
214 | |====
215 |
216 | [TIP]
217 | ====
218 | デフォルトでは `8080` ポートにて API が提供されますが、
219 | 以下のように `http` セクションを定義することでポート番号を変更できます。
220 |
221 | [source, xml]
222 | ----
223 |
224 |
225 |
226 | ----
227 | ====
228 |
229 | [IMPORTANT]
230 | ====
231 | Vespa の https://github.com/vespa-engine/sample-apps/tree/master/basic-search[公式チュートリアル] の設定では
232 | `document-processing` が定義されていませんが、日本語処理を行う場合は `document-processing` の定義が必須となります。
233 | これは、`document-processing` が定義されることで Vespa にて
234 | https://github.com/vespa-engine/vespa/blob/master/docprocs/src/main/java/com/yahoo/docprocs/indexing/IndexingProcessor.java[IndexingProcessor]
235 | が有効となり、この中で形態素解析が実行されるため (たぶん)。
236 | ====
237 |
238 | `nodes` ではこのコンテナ定義を適用するノードの一覧を記述しています。
239 | チュートリアルのサンプルでは全ノードに対して同一の設定を適用していますが、
240 | ノード毎に `container` セクションを別々に定義することで個別に設定を行うことも可能です。
241 |
242 | [IMPORTANT]
243 | ====
244 | `container` を複数定義する場合は `id` 属性 (コンテナの識別子) が被らないように注意してください。
245 | ====
246 |
247 | [[config_file_services_content]]
248 | ==== content
249 |
250 | Vespa のインデックス本体を持つノードで、
251 | チュートリアルの `sample-apps/config/basic/services.xml` では以下のように記述されています。
252 |
253 | [source, xml]
254 | ----
255 |
256 | 1
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 | ----
266 |
267 | `redundancy` はドキュメントの冗長数のことで、インデックス中にドキュメントの複製をいくつ保持するかを指定します。
268 |
269 | [NOTE]
270 | ====
271 | Vespa ではインデックスを `bucket` という単位に分割し、
272 | それぞれを指定された冗長数に応じてクラスタ内に分配することで冗長性を担保しています。
273 | このため、Solr や Elasticsearch のような shard/replica 方式に比べてデータの分割が細かくなっています。
274 | 詳しくは
275 | <<6_clustering.adoc#clustering_distrib,ドキュメントの分散>>
276 | で紹介します。
277 | ====
278 |
279 | `documents` はインデックスの具体的な定義に対応する設定で、参照するスキーマ定義および前処理の参照先を指定しています。
280 | `document` 要素の `type` は後述の `searchdefitions` に記述されたスキーマ定義の識別子を指定しています。
281 | また、`mode` はインデックスの保持方法を選択しており、通常の全文検索の場合は `mode=index` となります。
282 | `document-processing` では対応する前処理が実行される `container` の識別子を指定します (ここでは前述の "container" が対応)。
283 |
284 | [TIP]
285 | ====
286 | `document` は複数定義が可能で、複数の異なるインデックスを保持できます。
287 | ====
288 |
289 | `nodes` は `container` と同じように構築対象のノードを指定しています。
290 | `distribution-key` はドキュメントを分散させるときの配置先決めに利用される値で、
291 | 全てのノードで異なるキーとなるように設定します。
292 |
293 | [IMPORTANT]
294 | ====
295 | ノードの追加・削除で `nodes` の設定が変わるとき、
296 | 既存のノードに割り当てられている `distribution-key` は変更しないでください。
297 | 例えば、
298 |
299 | [source, xml]
300 | ----
301 |
302 |
303 |
304 |
305 |
306 | ----
307 |
308 | という3ノード構成から "node2" を外す場合、新しい設定は以下のようになります。
309 |
310 | [source, xml]
311 | ----
312 |
313 |
314 |
315 |
316 | ----
317 | ====
318 |
319 | [[config_file_searchdefinitions]]
320 | === searchdefinitions
321 |
322 | http://docs.vespa.ai/documentation/search-definitions.html[searchdefinitions]
323 | では Vespa の検索に関する定義のことで、`.sd` という拡張子のファイルとなっています。
324 | `searchdefinitions` の中身は独自のフォーマットで書かれており、具体的には以下のような情報が記載されています。
325 |
326 | * スキーマ定義 (http://docs.vespa.ai/documentation/reference/search-definitions-reference.html#document[document])
327 | * 検索対象のフィールド群のエイリアス定義 (http://docs.vespa.ai/documentation/reference/search-definitions-reference.html#fieldset[fieldset])
328 | * リランキングのためのモデル定義 (http://docs.vespa.ai/documentation/reference/search-definitions-reference.html#rank-profile[rank-profile])
329 |
330 | ここでは `document` と `fieldset` の2つについて説明します (モデル定義については
331 | <<5_ranking.adoc#ranking,Vespa とランキング>>
332 | で説明)。
333 |
334 | [IMPORTANT]
335 | ====
336 | `searchdefinitions` のファイル名は、以下の例のように `search` セクションの識別子と揃える必要があります。
337 |
338 | [source]
339 | ----
340 | $ cat book.sd <1>
341 | search book {
342 | ...
343 | }
344 | ----
345 |
346 | <1> `search book` と揃えて `book.sd` とします
347 | ====
348 |
349 | [NOTE]
350 | ====
351 | http://docs.vespa.ai/documentation/reference/search-definitions-reference.html[公式ドキュメント]
352 | を見ると分かるように、実際にはもっと色々な定義が可能ですが、ここではよく使う項目に対象を絞っています。
353 | ====
354 |
355 | [[config_file_searchdefinitions_document]]
356 | ==== document
357 |
358 | `document` はスキーマ定義に対応する項目で、
359 | チュートリアルの `sample-apps/config/basic/searchdefinitions/book.sd` では以下のように記述されています。
360 |
361 | [source]
362 | ----
363 | document book {
364 |
365 | field language type string {
366 | indexing: "ja" | set_language
367 | }
368 |
369 | field title type string {
370 | indexing: summary | index
371 | summary-to: simple_set, detail_set
372 | }
373 |
374 | field desc type string {
375 | indexing: summary | index
376 | summary-to: detail_set
377 | }
378 |
379 | field price type int {
380 | indexing: summary | attribute
381 | summary-to: simple_set, detail_set
382 | }
383 |
384 | field page type int {
385 | indexing: summary | attribute
386 | }
387 |
388 | field genres type array {
389 | indexing: summary | attribute
390 | summary-to: detail_set
391 | }
392 |
393 | field reviews type weightedset {
394 | indexing: summary | attribute
395 | }
396 |
397 | }
398 | ----
399 |
400 | 各 `field` 要素はスキーマに存在するフィールドの定義に対応しています。
401 | ただし、一番上の `language` だけは特殊で、ドキュメントが日本語 (`ja`) であることを明示的に宣言しています。
402 |
403 | `field` では以下のように名称 (`name`)、型 (`type`)、処理方法 (`indexing`) の3つの項目でフィールドを定義するのが基本となります。
404 |
405 | [NOTE]
406 | ====
407 | `summary-to` については
408 | <<4_search.adoc#search_query_summary,summary>>
409 | を参照してください。
410 | ====
411 |
412 | [source]
413 | ----
414 | field ${name} type ${type} {
415 | indexing: ${indexing}
416 | }
417 | ----
418 |
419 | `type` には代表的なものとして以下のような型が指定できます (詳細は
420 | http://docs.vespa.ai/documentation/reference/search-definitions-reference.html#field_types[こちら]
421 | を参照)。
422 |
423 | [options="header", cols="1,4"]
424 | |====
425 | ^| 型 ^| 説明
426 | | string | 文字列型、処理方法によって形態素解析の有無が変わります。
427 | | integer | 32-bit 整数の数値型、単一の値を保持します。
428 | | long | 64-bit 整数の数値型、単一の値を保持します。
429 | | byte | 8-bit 整数の数値型、単一の値を保持します。
430 | | float | 単精度浮動小数点型、単一の値を保持します。
431 | | double | 倍精度浮動小数点型、単一の値を保持します。
432 | | array | 配列型、`element-type` で指定された型を複数保持します。
433 | | weightedset | 辞書型、`element-type` を key、`integer` を value とする複数の key-value を保持します。
434 | |====
435 |
436 | `indexing` には以下の3つが指定できます (詳細は
437 | http://docs.vespa.ai/documentation/reference/search-definitions-reference.html#indexing[こちら]
438 | を参照)。
439 |
440 | [options="header", cols="1,4"]
441 | |====
442 | ^| 処理 ^| 説明
443 | | attribute | 値をオンメモリ上に展開します (ソートやグルーピングで用いるフィールドに指定)。
444 | | index | 形態素解析してインデックスに登録します (`string` と組み合わせる)。
445 | | summary | レスポンスに指定されたフィールドの値を付与します
446 | |====
447 |
448 | チュートリアルの例のように、これらの設定は `summary | index` と組み合わせて利用できます。
449 | ただし、`attribute` と `index` は対の関係にあるため、通常は `attribute` と `index` を同時に指定することはないです。
450 | 基本的に `index` は文章のような長い文字列型にのみ指定し、
451 | それ以外の数値型やキーワードのような単一文字列については `attribute` を用います。
452 |
453 | [TIP]
454 | ====
455 | より正確にいうと、`indexing` は
456 | http://docs.vespa.ai/documentation/reference/advanced-indexing-language.html[indexing-language]
457 | と呼ばれる機能に対応します。
458 | ====
459 |
460 | [[config_file_searchdefinitions_fieldset]]
461 | ==== fieldset
462 |
463 | `fieldset` は複数のフィールドを束ねたエイリアスの定義に利用します。
464 | チュートリアルの `sample-apps/config/basic/searchdefinitions/book.sd` では以下のように記述されています。
465 |
466 | [source]
467 | ----
468 | fieldset default {
469 | fields: title, desc
470 | }
471 | ----
472 |
473 | 上記のように定義した場合、検索時に `query=default:foo` と検索フィールドとして
474 | `fieldset` の名称を指定することで、紐付いているフィールド全体に対して検索したのと同じ意味になります。
475 |
476 | [TIP]
477 | ====
478 | `default` という `fieldset` には
479 | 「検索時に明示的にフィールドされなかった場合にデフォルトで参照されるフィールド」
480 | という特別な意味があります。
481 | ====
482 |
483 | [[config_deploy]]
484 | == Vespa へのデプロイ
485 |
486 | 設定ファイルの Vespa への反映には `vespa-deploy` というコマンドを用います。
487 | ここでは、シングルノード用の設定である `sample-apps/config/basic` を実際にデプロイする手順を追っていきます。
488 |
489 | [IMPORTANT]
490 | ====
491 | 事前に
492 | <<1_setup.adoc#setup_tutorial,チュートリアル環境の構築>>
493 | の手順に従って Vespa を起動しておいてください。
494 | ====
495 |
496 | [[config_deploy_tokenizer]]
497 | === 日本語トークナイザの配置
498 |
499 | 現在の Vespa は日本語トークナイザを内包していないため、
500 | 対応する `jar` を別途入手して `components` 配下に配置する必要があります。
501 | <> の節で述べたように、ここでは `KuromojiLinguistics` を利用します。
502 |
503 | `KuromojiLinguistics` は
504 | {kl_github_url}[vespa-kuromoji-linguistics]
505 | にて公開されている Vespa のプラグインで、
506 | 文書のトークン分けに https://www.atilika.com/ja/kuromoji/[Kuromoji] を利用する実装となっています。
507 |
508 | https://www.atilika.com/ja/kuromoji/[Kuromoji] はJavaで実装されたオープンソースの日本語形態素解析エンジンで、
509 | http://lucene.apache.org/solr/[Solr]
510 | や
511 | https://www.elastic.co/jp/products/elasticsearch[Elasticsearch]
512 | でも日本語トークナイザとして採用されています。
513 |
514 | [NOTE]
515 | ====
516 | `KuromojiLingusitcs` 自体の詳細については
517 | {kl_github_url}[vespa-kuromoji-linguistics]
518 | を参照してください。
519 | ====
520 |
521 | 本チュートリアルでは、事前に用意した以下のスクリプトを用いて `kuromoji-linguistics.jar` をセットアップします。
522 |
523 | [source, bash]
524 | ----
525 | $ sample-apps/plugin/setup.sh <1>
526 |
527 | $ ls sample-apps/plugin/ <2>
528 | kuromoji-linguistics.jar setup.sh vespa-kuromoji-linguistics
529 | ----
530 |
531 | <1> リポジトリからソースをダウンロードし、パッケージをビルド
532 | <2> `kuromoji-lingustics.jar` としてプラグインが配置されます
533 |
534 | [TIP]
535 | ====
536 | チュートリアルでは、`config` 配下にあるそれぞれの設定の
537 | `components/kuromoji-lingusitics.jar`
538 | から
539 | `sample-apps/plugin/kuromoji-lingusitics.jar`
540 | へシンボリックリンクを貼る構成となっています。
541 | ====
542 |
543 | [[config_deploy_prepare]]
544 | === 設定ファイルのアップロード
545 |
546 | 起動した Vespa ノードのうち、`vespa1` にログインします。
547 |
548 | [source, bash]
549 | ----
550 | $ sudo docker-compose exec vespa1 /bin/bash
551 | ----
552 |
553 | チュートリアル環境では Docker コンテナの `/vespa-sample-apps` に `sample-apps/` がマウントされています。
554 |
555 | [source, bash]
556 | ----
557 | [root@vespa1 /]# ls /vespa-sample-apps/
558 | config feed plugin
559 | ----
560 |
561 | 以下のコマンドを実行し、`/vespa-sample/apps/config/basic` を `configserver` にアップロードします。
562 |
563 | [source, bash]
564 | ----
565 | [root@vespa1 /]# vespa-deploy prepare /vespa-sample-apps/config/basic/
566 | Uploading application '/vespa-sample-apps/config/basic/' using http://vespa1:19071/application/v2/tenant/default/session?name=basic
567 | Session 2 for tenant 'default' created.
568 | Preparing session 2 using http://vespa1:19071/application/v2/tenant/default/session/2/prepared
569 | Session 2 for tenant 'default' prepared.
570 | ----
571 |
572 | このように、設定ファイルの Vespa へのアップロードは次のコマンドで実行します。
573 |
574 | [source, bash]
575 | ----
576 | vespa-deploy prepare ${config}
577 | ----
578 |
579 | もし、設定ファイルに不備がある場合、`prepare` は失敗し、エラーログがコンソールに出力されます。
580 |
581 | [TIP]
582 | ====
583 | 上記コマンドは、内部的には以下の2つのコマンドを実行しています。
584 |
585 | [source, bash]
586 | ----
587 | # vespa-deploy upload /vespa-sample-apps/config/basic/
588 | # vespa-deploy prepare
589 | ----
590 | ====
591 |
592 | [[config_deploy_activate]]
593 | === 設定ファイルの反映
594 |
595 | 次に、以下のコマンドを実行し、アップロードした設定を実際に Vespa に反映させます。
596 |
597 | [source, bash]
598 | ----
599 | [root@vespa1 /]# vespa-deploy activate
600 | Activating session 2 using http://vespa1:19071/application/v2/tenant/default/session/2/active
601 | Session 2 for tenant 'default' activated.
602 | Checksum: a60feb4256b6f0051b462252b6b398ad
603 | Timestamp: 1518510769258
604 | Generation: 2
605 | ----
606 |
607 | これにより、各 Vespa に最新の設定が行き渡り、しばらくすると対応するサービスが起動します。
608 | 試しに `8080` ポートから検索を行うと、0件 (`"totalCount":0`) と結果が返ってきます。
609 |
610 | [source, bash]
611 | ----
612 | [root@vespa1 /]# curl 'http://localhost:8080/search/?query=foo'
613 | {"root":{"id":"toplevel","relevance":1.0,"fields":{"totalCount":0},"coverage":{"coverage":100,"documents":0,"full":true,"nodes":0,"results":1,"resultsFull":1}}}
614 | ----
615 |
616 | `Ctrl-D` でコンテナを抜けて `utils/vespa_status` を実行すると、
617 | 先程 `NG` だった vespa1 が `OK` に変わっていることが確認できます。
618 |
619 | [source, bash]
620 | ----
621 | $ utils/vespa_status
622 | configuration server ... [ OK ]
623 | application server (vespa1) ... [ OK ]
624 | application server (vespa2) ... [ NG ]
625 | application server (vespa3) ... [ NG ]
626 | ----
--------------------------------------------------------------------------------
/docs/src/main/asciidoc/6_clustering.adoc:
--------------------------------------------------------------------------------
1 | ////
2 | Copyright 2018 Yahoo Japan Corporation.
3 | Licensed under the terms of the MIT license.
4 | See LICENSE in the project root.
5 | ////
6 |
7 | [[clustering]]
8 | = Vespa とクラスタリング
9 | include::_include.adoc[]
10 |
11 | このセクションでは、複数ノードを用いて Vespa をクラスタリングする方法について見ていきます。
12 |
13 | [[clustering_setup]]
14 | == クラスタの構築
15 |
16 | 本チュートリアルでは、実際に3つのノードを用いた Vespa クラスタを構築します。
17 | 対応する設定は
18 | `sample-apps/config/cluster`
19 | にあります。
20 |
21 | [[clustering_setup_tutorial]]
22 | === チュートリアルでの構成
23 |
24 | 構築する Vespa クラスタの構成を図にすると以下のようになります。
25 |
26 | image::vespa_tutorial_cluster.jpg[width=900, align="center"]
27 |
28 | [NOTE]
29 | ====
30 | 上図は役割軸でコンポーネントをざっくり書いたもので、ブロックと実際のプロセスが対応しているわけではない点に注意してください。
31 |
32 | なお、実際にどのノードで何のプロセスが起動するかは
33 | http://docs.vespa.ai/documentation/reference/files-processes-and-ports.html[Files, processes and ports]
34 | にまとめられています。
35 | ====
36 |
37 | Vespa クラスタの構築で修正が必要となるのは `hosts.xml` と `services.xml` の2つです。
38 |
39 | [[clustering_setup_tutorial_hosts]]
40 | ==== hosts.xml
41 |
42 | `hosts.xml` の具体的な中身は以下のようになります。
43 |
44 | [source, xml]
45 | ----
46 |
47 |
48 |
49 | node1
50 |
51 |
52 | <1>
53 | node2
54 |
55 |
56 | <2>
57 | node3
58 |
59 |
60 | ----
61 |
62 | <1> `vespa2` のホストを追加
63 | <2> `vespa3` のホストを追加
64 |
65 | <<2_config.adoc#config,Vespa の設定>>
66 | で用いたシングル構成と比べて、指定されているホスト名が増えています。
67 |
68 | [[clustering_setup_tutorial_services]]
69 | ==== services.xml
70 |
71 | `services.xml` の具体的な中身は以下のようになります。
72 |
73 | [source, xml]
74 | ----
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
92 |
93 | search
94 | true
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | 2 <1>
109 |
110 |
111 |
112 |
113 |
114 |
115 | <2>
116 | <3>
117 |
118 |
119 |
120 |
121 | ----
122 |
123 | <1> ドキュメントの冗長数を `2` に変更
124 | <2> `vespa2` のホストを追加
125 | <3> `vespa3` のホストを追加
126 |
127 | こちらもシングル構成に比べて、`container` と `content` の `nodes` セクションの定義が増えていることがわかります。
128 | 今回は3つのノードで同じ設定を利用するため、定義の追加はこれだけで OK です。
129 | また、それに加えて、クラスタ設定ではドキュメントの冗長数を `1` から `2` に増やしています。
130 |
131 | [IMPORTANT]
132 | ====
133 | <<2_config.adoc#config_file_services_content,content>>
134 | で述べたように、`content` セクションに追加した `node` は全て異なる `distribution-key` を設定する必要があります。
135 | ====
136 |
137 | [[clustering_setup_deploy]]
138 | === クラスタ設定の反映
139 |
140 | [IMPORTANT]
141 | ====
142 | すでにシングル構成での Vespa が起動して、データまで投入されていることを前提としています。
143 | ====
144 |
145 | シングル構成では、`vespa1` のノードに13件のドキュメントが登録されているという状態でした。
146 | チュートリアルで用意している `utils/vespa_cluster_status` を以下のように実行すると、
147 | `vespa1` (`node=0`) に13件のドキュメントがいることが確認できます。
148 |
149 | [source, bash]
150 | ----
151 | $ utils/vespa_cluster_status -t storage book
152 | === status of storage ===
153 |
154 | | node | status | bucket-count | uniq-doc-count | uniq-doc-size |
155 | |------|-------------|--------------|----------------|---------------|
156 | | 0 | up | 13 | 13 | 9008 |
157 | ----
158 |
159 | [TIP]
160 | ====
161 | `utils/vespa_cluster_status` は
162 | http://docs.vespa.ai/documentation/content/api-state-rest-api.html[State Rest API]
163 | を用いて `content` ノードの状態を確認しています。
164 | ====
165 |
166 | 次に、以下のコマンドでクラスタの設定を Vespa にデプロイします。
167 |
168 | [source, bash]
169 | ----
170 | $ sudo docker-compose exec vespa1 /bin/bash <1>
171 |
172 | [root@vespa1 /]# vespa-deploy prepare /vespa-sample-apps/config/cluster/ <2>
173 | Uploading application '/vespa-sample-apps/config/cluster/' using http://vespa1:19071/application/v2/tenant/default/session?name=cluster
174 | Session 4 for tenant 'default' created.
175 | Preparing session 4 using http://vespa1:19071/application/v2/tenant/default/session/4/prepared
176 | WARNING: Host named 'vespa2' may not receive any config since it does not match its canonical hostname: vespa2.vespatutorial_vespa-nw
177 | WARNING: Host named 'vespa3' may not receive any config since it does not match its canonical hostname: vespa3.vespatutorial_vespa-nw
178 | Session 4 for tenant 'default' prepared.
179 |
180 | [root@vespa1 /]# vespa-deploy activate <3>
181 | Activating session 4 using http://vespa1:19071/application/v2/tenant/default/session/4/active
182 | Session 4 for tenant 'default' activated.
183 | Checksum: c170db03dc38f7f53d9b98aa89d782de
184 | Timestamp: 1519282647370
185 | Generation: 4
186 | ----
187 |
188 | <1> `vespa1` の Docker コンテナにログイン
189 | <2> `/vespa-sample-apps/config/cluster/` を Vespa にアップロード
190 | <3> 最新の設定を Vespa に反映
191 |
192 | [NOTE]
193 | ====
194 | チュートリアルでは `hosts.xml` のホスト名が若干雑なので `WARNING` がでていますが、
195 | ノード間で通信はできているため動作上は問題ありません。
196 | ====
197 |
198 | チュートリアル付属の `utils/vespa_status` を実行すると、
199 | `vespa2` と `vepsa3` も `OK` になっていることがわかります。
200 |
201 | [source, bash]
202 | ----
203 | $ utils/vespa_status
204 | configuration server ... [ OK ]
205 | application server (vespa1) ... [ OK ]
206 | application server (vespa2) ... [ OK ]
207 | application server (vespa3) ... [ OK ]
208 | ----
209 |
210 | また、先程の `utils/vespa_cluster_status` を実行すると、
211 | ドキュメントが3つのノードに分散され、
212 | さらに冗長数が `2` になるようにコピーが行われている (総数が26件になっている) ことが確認できます。
213 |
214 | [source, bash]
215 | ----
216 | $ utils/vespa_cluster_status -t storage book
217 | === status of storage ===
218 |
219 | | node | status | bucket-count | uniq-doc-count | uniq-doc-size |
220 | |------|-------------|--------------|----------------|---------------|
221 | | 0 | up | 7 | 7 | 5079 |
222 | | 1 | up | 11 | 11 | 7665 |
223 | | 2 | up | 8 | 8 | 5272 |
224 | ----
225 |
226 | [NOTE]
227 | ====
228 | 実際にドキュメントの情報が `State REST API` の結果に反映されるまで少し時間がかかります。
229 | ====
230 |
231 | 実際に検索すると、初めに登録していた13件のドキュメントが取得できることがわかります。
232 |
233 | [source, bash]
234 | ----
235 | $ curl 'http://localhost:8080/search/?query=sddocname:book'
236 | {"root":{"id":"toplevel","relevance":1.0,"fields":{"totalCount":13}, ...
237 | ----
238 |
239 | [[clustering_state]]
240 | == クラスタの状態管理
241 |
242 | Vespa のクラスタを運用する上で、クラスタ内の各ノードの状態を把握することが重要です。
243 | ここでは、Vespa で提供されているコマンドを用いてクラスタの状態を管理する手順について説明します。
244 |
245 | [[clustering_state_howto]]
246 | === 状態管理の仕組み
247 |
248 | Vespa では
249 | http://docs.vespa.ai/documentation/clustercontroller.html[Cluster Controller]
250 | と呼ばれるコンポーネントが各ノードの状態を管理しています。
251 | `Cluster Controller` は `services.xml` の
252 | <<2_config.adoc#config_file_services_admin,admin>> セクションの `cluster-controllers` で定義していたサーバのことで、
253 | Vespa クラスタ全体の状態管理を担当しています。
254 |
255 | [TIP]
256 | ====
257 | 実際にはチュートリアルの設定では `cluster-controllers` を明示的に定義していません。
258 | `cluster-controllers` の定義が無い場合は `configservers` のノードが `Cluster Controller` を兼務します。
259 | ====
260 |
261 | http://docs.vespa.ai/documentation/img/design/clustercontroller-overview.png[公式ドキュメントの図]
262 | のように、`Cluster Controller` の動作の流れは以下の通りです。
263 |
264 | . `slobrok` (Service Location Broker) からノードのリストを取得
265 | . 各ノードに対して現在の状態を問い合わせ (`u/d/m/r` は後述のノード状態に対応)
266 | . 得られた情報から最終的なクラスタの状態を更新して全体に通知
267 |
268 | チュートリアルの例では `Cluster Controller` は一つのみ指定していますが、
269 | 複数指定して冗長構成を取ることも可能です。
270 |
271 | [TIP]
272 | ====
273 | http://docs.vespa.ai/documentation/slobrok.html[slobrok]
274 | は各サービスがどこのホストのどのポートで動作しているかを管理するコンポーネントです。
275 |
276 | http://docs.vespa.ai/documentation/reference/vespa-cmdline-tools.html#vespa-model-inspect[vespa-model-inspect]
277 | コマンドを用いると `slobrok` に問い合わせることができ、
278 | 以下のように指定したサービスのホストと対応するポート番号を取得できます。
279 |
280 | [source, bash]
281 | ----
282 | [root@vespa1 /]# vespa-model-inspect service container
283 | container @ vespa1 :
284 | container/container.0
285 | tcp/vespa1:8080 (STATE EXTERNAL QUERY HTTP)
286 | tcp/vespa1:19100 (EXTERNAL HTTP)
287 | tcp/vespa1:19101 (MESSAGING RPC)
288 | tcp/vespa1:19102 (ADMIN RPC)
289 | container @ vespa2 :
290 | container/container.1
291 | tcp/vespa2:8080 (STATE EXTERNAL QUERY HTTP)
292 | tcp/vespa2:19100 (EXTERNAL HTTP)
293 | tcp/vespa2:19101 (MESSAGING RPC)
294 | tcp/vespa2:19102 (ADMIN RPC)
295 | container @ vespa3 :
296 | container/container.2
297 | tcp/vespa3:8080 (STATE EXTERNAL QUERY HTTP)
298 | tcp/vespa3:19100 (EXTERNAL HTTP)
299 | tcp/vespa3:19101 (MESSAGING RPC)
300 | tcp/vespa3:19102 (ADMIN RPC)
301 | ----
302 | ====
303 |
304 | [IMPORTANT]
305 | ====
306 | `Cluster Controller` を冗長構成にした場合、
307 | 内部では `Cluster Controller` のどれか一つがマスターとなって状態管理を行います。
308 |
309 | マスターの選別は `Cluster Controller` 全体での投票により行われ、
310 | 過半数以上の票を集めた `Cluster Controller` がマスターとなります。
311 | このため、例えば `N` プロセスまでのダウンを許容するようにシステムを構築する場合、
312 | 過半数を担保するため `Cluster Controller` の総数は `2 * N + 1` プロセスとする必要があるので注意してください。
313 | ====
314 |
315 | [[clustering_state_node]]
316 | === ノードの状態
317 |
318 | Vespa クラスタのノードは
319 | http://docs.vespa.ai/documentation/content/admin-states.html[Cluster and node states]
320 | にあるように6つの状態を取りますが、運用上で使うのは起動処理中と停止処理中を除いた以下の4つです。
321 |
322 | [options="header", cols="2,2,1,1"]
323 | |====
324 | ^| 状態 ^| 意味 ^| 分散検索の対象? ^| 分散配置の対象?
325 | | up | サービスが提供可能。 ^| o ^| o
326 | | down | サービスが提供不可能。 ^| x ^| x
327 | | maintenance | メンテナンス中。 ^| x ^| o
328 | | retired | 退役済み。 ^| o ^| x
329 | |====
330 |
331 | [NOTE]
332 | ====
333 | 4つと言いましたが、実運用では `up` と `down` の2つだけあればだいたいなんとかなります。
334 | ====
335 |
336 | 表のように、4つはドキュメントの分散検索および分散配置の対象となるかどうか、という観点で動作が異なります。
337 |
338 | `up` と `down` は非常にシンプルで、それぞれ全ての対象となるか完全に除外されるかに対応します。
339 |
340 | `maintenance` は分散検索の対象からは外れますが、分散配置の対象にはなる、という動作をします。
341 | `maintenance` はソフトウェアの更新作業など、比較的短いメンテナンス作業を行うときの指定が想定された状態で、
342 | 少しの間だけなので状態更新に伴うドキュメント再分散の負荷を避けたい、というときに利用します。
343 |
344 | `retired` は分散検索の対象は維持しますが、分散配置の対象から外れる、という動作をします。
345 | `retired` では分散配置の対象から外れるため、
346 | 状態更新後にノード上にあるドキュメントは0件になるまで (低優先度で) クラスタ内の他のノードへの再分配されていきます。
347 | 一方で、`retired` では分散検索の対象にはなるため、
348 | サービスは提供しながら徐々に担当ドキュメントを別ノードへ逃がしていき、
349 | 最終的に0件となったところで役割を終えて引退させる、というような使い方をします。
350 |
351 | 各ノードの状態は `vespa-get-node-state` というコマンドを用いて取得できます。
352 |
353 | [source, bash]
354 | ----
355 | [root@vespa1 /]# vespa-get-node-state -i 0 <1>
356 | Shows the various states of one or more nodes in a Vespa Storage cluster. There
357 | exist three different type of node states. They are:
358 |
359 | Unit state - The state of the node seen from the cluster controller.
360 | User state - The state we want the node to be in. By default up. Can be
361 | set by administrators or by cluster controller when it
362 | detects nodes that are behaving badly.
363 | Generated state - The state of a given node in the current cluster state.
364 | This is the state all the other nodes know about. This
365 | state is a product of the other two states and cluster
366 | controller logic to keep the cluster stable.
367 |
368 | book/distributor.0: <2>
369 | Unit: up:
370 | Generated: up:
371 | User: up:
372 |
373 | book/storage.0: <3>
374 | Unit: up:
375 | Generated: up:
376 | User: up:
377 | ----
378 |
379 | <1> `distribution-key=0` のノード (`-i 0`) の状態を取得
380 | <2> `distributor` (ドキュメント分散を行うコンポーネント) の状態
381 | <3> `storage` (ドキュメント保持を行うコンポーネント) の状態
382 |
383 | また、クラスタ全体の状態は `vepsa-get-cluster-state` というコマンドで確認できます。
384 |
385 | [source, bash]
386 | ----
387 | [root@vespa1 /]# vespa-get-cluster-state
388 |
389 | Cluster book:
390 | book/distributor/0: up
391 | book/distributor/1: up
392 | book/distributor/2: up
393 | book/storage/0: up
394 | book/storage/1: up
395 | book/storage/2: up
396 | ----
397 |
398 | [[clustering_state_check]]
399 | === ノード状態の判定
400 |
401 | Vespa では、ノードの状態は
402 |
403 | * システム的に判断した状態 (`Unit state`)
404 | * ユーザが明示的に指定した状態 (`User state`)
405 |
406 | の2つの情報から最終状態 (`Generated state`) が判断されます。
407 |
408 | [[clustering_state_check_unit]]
409 | ==== Unit state
410 |
411 | `Unit state` はプロセスの状態から機械的に判断される状態のことです。
412 |
413 | 例えば以下のように `vespa2` の Docker コンテナを停止されると、
414 | `vespa2` に対応する `distribution-key=1` のノードの `Unit` が `down` に更新されます。
415 |
416 | [source, bash]
417 | ----
418 | $ sudo docker-compose stop vespa2 <1>
419 | Stopping vespa2 ... done
420 |
421 | $ sudo docker-compose exec vespa1 /bin/bash <2>
422 |
423 | [root@vespa1 /]# vespa-get-node-state -i 1 <3>
424 | ...
425 | book/distributor.1:
426 | Unit: down: Connection error: Closed at other end. (Node or switch likely shutdown) <4>
427 | Generated: down: Connection error: Closed at other end. (Node or switch likely
428 | shut down)
429 | User: up:
430 |
431 | book/storage.1:
432 | Unit: down: Connection error: Closed at other end. (Node or switch likely shutdown) <5>
433 | Generated: down: Connection error: Closed at other end. (Node or switch likely
434 | shut down)
435 | User: up:
436 | ----
437 |
438 | <1> `vespa2` の Docker コンテナを停止
439 | <2> `vespa1` の Docker コンテナにログイン
440 | <3> `vespa2` (`distribution-key=1`) の状態を確認
441 | <4> `distributor` の `Unit state` が `down` に変化
442 | <5> `storage` の `Unit state` が `down` に変化
443 |
444 | [[clustering_state_check_user]]
445 | ==== User state
446 |
447 | `User state` はユーザが明示的に指定したノードの状態のことで、
448 | `vespa-set-node-state` コマンドを利用して指定します。
449 |
450 | 例えば、`vespa3` ノードのハードウェアが不調で、Vespa クラスタから明示的に外す (`down` にする) 場合は、
451 | 以下のようなコマンドとなります。
452 |
453 | [source]
454 | ----
455 | [root@vespa1 /]# vespa-set-node-state -i 2 down "hardware trouble" <1>
456 | {"wasModified":true,"reason":"ok"}OK
457 | {"wasModified":true,"reason":"ok"}OK
458 |
459 | [root@vespa1 /]# vespa-get-node-state -i 2 <2>
460 | ...
461 | book/distributor.2:
462 | Unit: up:
463 | Generated: down: hardware trouble
464 | User: down: hardware trouble <3>
465 |
466 | book/storage.2:
467 | Unit: up:
468 | Generated: down: hardware trouble
469 | User: down: hardware trouble <4>
470 | ----
471 |
472 | <1> `vespa3` (`distribution-key=2`) の状態を `hardware trouble` という理由で `down` に変更
473 | <2> `vespa3` (`distribution-key=2`) の状態を確認
474 | <3> `distributor` の `User state` が `down` に変化
475 | <4> `storage` の `User state` が `down` に変化
476 |
477 | なお、`vespa-set-node-state` では上記例のように、第2引数でメモを付けることができます (省略も可)。
478 |
479 | [TIP]
480 | ====
481 | ノードの状態を明示的に変更することは、
482 | 例えば不調なノードが意図せず起動してしまったときに Vespa クラスタに参加させないようにする、
483 | といった意図しない状態更新を抑止する効果があります。
484 | ====
485 |
486 | [NOTE]
487 | ====
488 | `maintenance` の状態は `storage` にしか指定できません。
489 | これは、`distributor` を `maintenance` にすると、ドキュメント分散自体が止まる可能性があるためです。
490 |
491 | `vespa-set-node-state` で特定のコンポーネントを指定する場合は、
492 | 以下のように `-t` オプションで指定します。
493 |
494 | [source]
495 | ----
496 | [root@vespa1 /]# vespa-get-node-state -i 2 -t storage maintenance "update software"
497 | ----
498 | ====
499 |
500 | [[clustering_distrib]]
501 | == ドキュメントの分散
502 |
503 | Vespa ではドキュメントを
504 | http://docs.vespa.ai/documentation/content/buckets.html[bucket]
505 | と呼ばれる単位に分割されて管理されます。
506 | `bucket` という細かい粒度でドキュメントの冗長性を管理することで、
507 | Vespa ではノード増減に対する高い柔軟性を担保しています。
508 |
509 | [TIP]
510 | ====
511 | 公式ドキュメントでは
512 | http://docs.vespa.ai/documentation/elastic-vespa.html[Vespa elasticity]
513 | にVespaの柔軟性に関する情報がまとめられています。
514 | ====
515 |
516 | [[clustering_distrib_process]]
517 | === Distributor と SearchNode
518 |
519 | ドキュメントの分散は、ノード状態の話で名前のでてきた `Distributor` と `Storage` という2つのコンポーネントが関わってきます。
520 | なお、
521 | http://docs.vespa.ai/documentation/elastic-vespa.html[公式ドキュメント]
522 | では `Storage` は `SearchNode` と呼ばれているため、
523 | ここでは `SearchNode` と呼んでいきます。
524 |
525 | [TIP]
526 | ====
527 | より具体的なプロセス名だと、
528 | `Distributor` は `vespa-distributord-bin` に、
529 | `SearchNode` は `vespa-proton-bin` に対応します。
530 | ====
531 |
532 | `Distributor` はドキュメントの分散を担当するコンポーネントで、
533 | 各 `bucket` のチェックサムや割り振り先などのクラスタ全体での `bucket` の一貫性の担保に関わる情報を管理しています。
534 | `Distributor` は `services.xml` で指定された冗長数と `Cluster Controller` から得られるクラスタの状態を元に、
535 | 各 `bucket` がどこに配置されるべきか、再分配が必要なのか、といったことを判断します。
536 |
537 | `SearchNode` は実際にインデックスへのアクセスを担当するコンポーネントで、
538 | `bucket` の本体を保持しています。
539 | `SearchNode` では Vespa の永続化や検索といった検索エンジンのコア実装が含まれています。
540 |
541 | [TIP]
542 | ====
543 | `SearchNode` のより細かい話は
544 | http://docs.vespa.ai/documentation/proton.html[Proton]
545 | を参照してください。
546 | ====
547 |
548 | [[clustering_distrib_bucket]]
549 | === bucket の配置と再分配
550 |
551 | `bucket` の配置は、以下のように各ノードに対して乱数を生成し、
552 | それをソートした順番をもとに優先順位を決めることで行われます。
553 |
554 | image::vespa_bucket_distrib.jpg[width=700, align="center"]
555 |
556 | [TIP]
557 | ====
558 | 分散アルゴリズムのより詳しい話は
559 | http://docs.vespa.ai/documentation/content/idealstate.html[Distribution algorithm]
560 | を参照してください。
561 | ====
562 |
563 | 得られたノード番号 (`distribution-key`) 列のうち、一番先頭のノードに `bucket` のプライマリコピーが、
564 | 冗長数に応じてそれ以降のノードに `bucket` のセカンダリコピーが配置されます。
565 | Vespa ではこのルールに従い、ノードが増減したときの `bucket` の再分配が行われます。
566 | 例えば、冗長数が `2` の状態でノードが増減したときに `bucket` の動きは以下のようになります。
567 |
568 | image::vespa_bucket_redistrib.jpg[width=800, align="center"]
569 |
570 | [IMPORTANT]
571 | ====
572 | 各 `bucket` に割り当てられるシーケンスは常に同じ結果になります。
573 | そのため、
574 | <<2_config.adoc#config_file_services_content,content>>
575 | にある「各ノードに割り振られた `distribution-key` が変わらないようにすること」が重要となるわけです。
576 | ====
577 |
578 | [[clustering_distrib_example]]
579 | === チュートリアル環境での例
580 |
581 | チュートリアルの Vespa クラスタを用いて、
582 | 実際にノードを増減させた場合にどのような動作をするか見ていきます。
583 | クラスタにドキュメントを全件追加したときの状態は以下のようになっていました。
584 |
585 | [source, bash]
586 | ----
587 | $ utils/vespa_cluster_status -t storage book
588 | === status of storage ===
589 |
590 | | node | status | bucket-count | uniq-doc-count | uniq-doc-size |
591 | |------|-------------|--------------|----------------|---------------|
592 | | 0 | up | 7 | 7 | 5079 |
593 | | 1 | up | 11 | 11 | 7665 |
594 | | 2 | up | 8 | 8 | 5272 |
595 | ----
596 |
597 | ここで、試しに `vespa2` (上図の `node=1`) の Docker コンテナを停止させると、
598 | `vespa2` のドキュメントが他の2つのノードに分配されることが確認できます。
599 |
600 | [source, bash]
601 | ----
602 | $ sudo docker-compose stop vespa2 <1>
603 | Stopping vespa2 ... done
604 |
605 | $ utils/vespa_cluster_status -t storage book <2>
606 | === status of storage ===
607 |
608 | | node | status | bucket-count | uniq-doc-count | uniq-doc-size |
609 | |------|-------------|--------------|----------------|---------------|
610 | | 0 | up | 13 | 13 | 9008 |
611 | | 1 | down | 0 | 0 | 0 |
612 | | 2 | up | 13 | 13 | 9008 |
613 | ----
614 |
615 | <1> `vespa2` の Docker コンテナを停止
616 | <2> `vespa2` (`node=1`) が担当していた11件が他の2ノードに再分配
617 |
618 | [NOTE]
619 | ====
620 | 停止させた直後は `node=1` の状態が `maintenance` に代わり、
621 | しばらくして `down` になります。
622 | ====
623 |
624 | 次に、停止させた `vepsa2` を再び起動させると、
625 | 3つのノードに再分配されて元の状態に戻ることが確認できます。
626 |
627 | [source, bash]
628 | ----
629 | $ sudo docker-compose start vespa2 <1>
630 | Starting vespa2 ... done
631 |
632 | $ utils/vespa_cluster_status -t storage book <2>
633 | === status of storage ===
634 |
635 | | node | status | bucket-count | uniq-doc-count | uniq-doc-size |
636 | |------|-------------|--------------|----------------|---------------|
637 | | 0 | up | 7 | 7 | 5079 |
638 | | 1 | up | 11 | 11 | 7665 |
639 | | 2 | up | 8 | 8 | 5272 |
640 | ----
641 |
642 | <1> `vespa2` の Docker コンテナを起動
643 | <2> 3ノードに再分配されて初めの状態に戻る
644 |
645 | [[clustering_other]]
646 | == その他のトピック
647 |
648 | [[clustering_other_logging]]
649 | === ログの確認
650 |
651 | <<1_setup.adoc#setup_boot,Vespa の起動>>
652 | で紹介した構成図のように、
653 | Vespa では各サービスのログは `log server` に集約されます。
654 | そのため、`log server` のログを参照することで、クラスタ全体のログを確認できます。
655 |
656 | [TIP]
657 | ====
658 | `log_server` は
659 | <<2_config.adoc#config_file_services_admin,admin>> セクションの `logserver` で定義していたサーバのことです。
660 | ====
661 |
662 | `log server` では、以下のように `/opt/vespa/logs/vespa/logarchive/` の下にログが集約されています。
663 |
664 | [source, bash]
665 | ----
666 | [root@vespa1 /]# tree /opt/vespa/logs/vespa/logarchive/
667 | /opt/vespa/logs/vespa/logarchive/
668 | └── 2018
669 | └── 02
670 | └── 23
671 | └── 04-0
672 | ----
673 |
674 | Vespa のログを見るときは
675 | http://docs.vespa.ai/documentation/reference/logfmt.html[vespa-logfmt]
676 | を使うと便利です。
677 | `vespa-logfmt` は、以下のようにオプションとして多数のログのフィルタが提供されています。
678 |
679 | [source, bash]
680 | ----
681 | [root@vespa1 /]# vespa-logfmt -h
682 | Usage: /opt/vespa/bin/vespa-logfmt [options] [inputfile ...]
683 | Options:
684 | -l LEVELLIST --level=LEVELLIST select levels to include
685 | -s FIELDLIST --show=FIELDLIST select fields to print
686 | -p PID --pid=PID select messages from given PID
687 | -S SERVICE --service=SERVICE select messages from given SERVICE
688 | -H HOST --host=HOST select messages from given HOST
689 | -c REGEX --component=REGEX select components matching REGEX
690 | -m REGEX --message=REGEX select message text matching REGEX
691 | -f --follow invoke tail -F to follow input file
692 | -L --livestream follow log stream from logserver
693 | -N --nldequote dequote newlines in message text field
694 | -t --tc --truncatecomponent chop component to 15 chars
695 | --ts --truncateservice chop service to 9 chars
696 |
697 | FIELDLIST is comma separated, available fields:
698 | time fmttime msecs usecs host level pid service component message
699 | Available levels for LEVELLIST:
700 | fatal error warning info event debug spam
701 | for both lists, use 'all' for all possible values, and -xxx to disable xxx.
702 | ----
703 |
704 | `vespa-logfmt` を引数なしで実行すると、
705 | デフォルトではノードで起動しているサービスのアプリケーションログに対応する
706 | `/opt/vespa/logs/vespa/vespa.log` が参照されます。
707 |
708 | [source, bash]
709 | ----
710 | [root@vespa1 /]# vespa-logfmt | tail -3
711 | [2018-02-23 04:14:48.700] INFO : container-clustercontroller Container.com.yahoo.vespa.clustercontroller.core.database.ZooKeeperDatabase Fleetcontroller 0: Storing new cluster state version in ZooKeeper: 10
712 | [2018-02-23 04:14:58.612] INFO : container-clustercontroller Container.com.yahoo.vespa.clustercontroller.core.SystemStateBroadcaster Publishing cluster state version 10
713 | [2018-02-23 04:46:40.991] INFO : container-clustercontroller stdout [GC (Allocation Failure) 232656K->16316K(498112K), 0.0054783 secs]
714 | ----
715 |
716 | Vespa クラスタ全体のログを見る場合は、引数の `inputfile` として先程のアーカイブログを指定する必要があります。
717 | 例えば、`vespa2` ノードの `warning` レベルのログが見たい場合は以下のようなコマンドとなります。
718 |
719 | [source, bash]
720 | ----
721 | [root@vespa1 /]# vespa-logfmt -l warning -H vespa2 /opt/vespa/logs/vespa/logarchive/2018/02/23/04-0 | tail -3
722 | [2018-02-23 04:14:35.944] WARNING : searchnode proton.searchlib.docstore.logdatastore We detected an empty idx file for part '/opt/vespa/var/db/vespa/search/cluster.book/n1/documents/book/2.notready/summary/1519358811014181000'. Erasing it.
723 | [2018-02-23 04:14:35.944] WARNING : searchnode proton.searchlib.docstore.logdatastore Removing dangling file '/opt/vespa/var/db/vespa/search/cluster.book/n1/documents/book/1.removed/summary/1519358811013672000.dat'
724 | [2018-02-23 04:14:35.944] WARNING : searchnode proton.searchlib.docstore.logdatastore Removing dangling file '/opt/vespa/var/db/vespa/search/cluster.book/n1/documents/book/2.notready/summary/1519358811014181000.dat'
725 | ----
726 |
727 | [NOTE]
728 | ====
729 | チュートリアルの例では時間が標準時間となっていますが、
730 | これは起動している環境のタイムゾーンが `UTC` のためです。
731 |
732 | [source, bash]
733 | ----
734 | [root@vespa1 /]# date
735 | Fri Feb 23 04:56:06 UTC 2018
736 | ----
737 |
738 | タイムゾーンを `JST` で実行すれば、ログのタイムスタンプも日本時間になります (なるはず)。
739 | ====
740 |
741 | [[clustering_other_metrics]]
742 | === メトリクスの取得
743 |
744 | Vespa では、検索レイテンシなどのメトリクスを取得するための
745 | http://docs.vespa.ai/documentation/reference/metrics-health-format.html[Metrics API]
746 | が提供されています。
747 |
748 | `Metrics API` が提供されているポートはサービスによって異なります。
749 | 各サービスで提供されているポートは
750 | http://docs.vespa.ai/documentation/reference/vespa-cmdline-tools.html#vespa-model-inspect[vespa-model-inspect]
751 | コマンドを使うことで確認できます。
752 |
753 | [source, bash]
754 | ----
755 | [root@vespa1 /]# vespa-model-inspect service container <1>
756 | container @ vespa1 :
757 | container/container.0
758 | tcp/vespa1:8080 (STATE EXTERNAL QUERY HTTP) <2>
759 | tcp/vespa1:19100 (EXTERNAL HTTP)
760 | tcp/vespa1:19101 (MESSAGING RPC)
761 | tcp/vespa1:19102 (ADMIN RPC)
762 | container @ vespa2 :
763 | container/container.1
764 | tcp/vespa2:8080 (STATE EXTERNAL QUERY HTTP)
765 | tcp/vespa2:19100 (EXTERNAL HTTP)
766 | tcp/vespa2:19101 (MESSAGING RPC)
767 | tcp/vespa2:19102 (ADMIN RPC)
768 | container @ vespa3 :
769 | container/container.2
770 | tcp/vespa3:8080 (STATE EXTERNAL QUERY HTTP)
771 | tcp/vespa3:19100 (EXTERNAL HTTP)
772 | tcp/vespa3:19101 (MESSAGING RPC)
773 | tcp/vespa3:19102 (ADMIN RPC)
774 |
775 | [root@vespa1 /]# vespa-model-inspect service searchnode <3>
776 | searchnode @ vespa1 : search
777 | book/search/cluster.book/0
778 | tcp/vespa1:19109 (STATUS ADMIN RTC RPC)
779 | tcp/vespa1:19110 (FS4)
780 | tcp/vespa1:19111 (UNUSED)
781 | tcp/vespa1:19112 (UNUSED)
782 | tcp/vespa1:19113 (STATE HEALTH JSON HTTP) <4>
783 | searchnode @ vespa2 : search
784 | book/search/cluster.book/1
785 | tcp/vespa2:19108 (STATUS ADMIN RTC RPC)
786 | tcp/vespa2:19109 (FS4)
787 | tcp/vespa2:19110 (UNUSED)
788 | tcp/vespa2:19111 (UNUSED)
789 | tcp/vespa2:19112 (STATE HEALTH JSON HTTP)
790 | searchnode @ vespa3 : search
791 | book/search/cluster.book/2
792 | tcp/vespa3:19108 (STATUS ADMIN RTC RPC)
793 | tcp/vespa3:19109 (FS4)
794 | tcp/vespa3:19110 (UNUSED)
795 | tcp/vespa3:19111 (UNUSED)
796 | tcp/vespa3:19112 (STATE HEALTH JSON HTTP)
797 | ----
798 |
799 | <1> `container` サービス (検索リクエストを受けるところ) のポートを確認
800 | <2> `vespa1` ノードでは `8080` ポート
801 | <3> `searchnode` サービス (インデックスを管理してるところ) のポートを確認
802 | <4> `vespa1` ノードでは `19113` ポート
803 |
804 | 実際に `Metrics API` を用いると、以下のように `json` 形式でメトリクスが取得できます。
805 |
806 | [source, json]
807 | ----
808 | [root@vespa1 /]# curl 'http://localhost:8080/state/v1/metrics'
809 | {
810 | "metrics": {
811 | "snapshot": {
812 | "from": 1.519363748005E9,
813 | "to": 1.519363808005E9
814 | },
815 | "values": [
816 | {
817 | "name": "search_connections",
818 | "values": {
819 | "average": 0,
820 | "count": 1,
821 | "last": 0,
822 | "max": 0,
823 | "min": 0,
824 | "rate": 0.016666666666666666
825 | }
826 | },
827 | ...
828 | ----
829 |
830 | 返却されるメトリクスは `snapshot` に書かれた期間でのスナップショットに対応しており、
831 | デフォルトのインターバルは `container` ノードが `300秒` が、
832 | `searchnode` ノードが `60秒` (たぶん) となっています。
833 | 各メトリクスは `values` として複数の値を返しますが、共通の項目は以下のように定義されます。
834 |
835 | [options="header", cols="1,4"]
836 | |====
837 | ^| 項目 ^| 内容
838 | | count | 期間中にメトリクスが計測された回数。
839 | | average | 期間中のメトリクスの平均値 (`sum/count`)。
840 | | rate | 1秒あたりの計測回数 (`count/s`)。
841 | | min | 期間中の最小値。
842 | | max | 期間中の最大値。
843 | | sum | 期間中のメトリクスの総和。
844 | |====
845 |
846 | 各サービスで色々なメトリクスが出力されますが、例えば以下のようなメトリクスがとれます。
847 |
848 | [options="header", cols="1,1,3"]
849 | |====
850 | ^| 項目 ^| サービス ^| 内容
851 | | queries | container | クエリの処理数、`rate` がいわゆるQPS。
852 | | query_latency | container | 検索レイテンシの統計値。
853 | | proton.numdocs | searchnode | インデックスされているドキュメント数。
854 | |====
855 |
856 | [NOTE]
857 | ====
858 | 具体的なメトリクス周り公式ドキュメントが
859 | http://docs.vespa.ai/documentation/jdisc/metrics.html#metrics-from-custom-components[このあたり]
860 | しかなく、正直どれがどれに対応しているかはコードを確認する必要があるのが現状です。。。
861 |
862 | 実際のサービスだとこれに加えて更新リクエストの方もチェックしていましたが、
863 | それは投げる側でモニタリングしていたため、Vespa の `Metrics API` は経由していませんでした。
864 | ====
865 |
866 | [CAUTION]
867 | ====
868 | `container` から返されるメトリクスで
869 | `query_latency`、`mean_query_latency`、`max_query_latency`
870 | の3つは、Vespa側の実装のバグっぽくて全て同じ値になります (`query_latency` と同じ挙動)。
871 |
872 | ただ、実際のところ `query_latency` の統計値で3つともわかるため、
873 | 残りの2つは冗長な出力に見えます。
874 | ====
--------------------------------------------------------------------------------
/docs/src/main/asciidoc/4_search.adoc:
--------------------------------------------------------------------------------
1 | ////
2 | Copyright 2018 Yahoo Japan Corporation.
3 | Licensed under the terms of the MIT license.
4 | See LICENSE in the project root.
5 | ////
6 |
7 | [[search]]
8 | = Vespa と検索
9 | include::_include.adoc[]
10 |
11 | このセクションでは、Vespa での検索方法について見ていきます。
12 |
13 | [[search_query]]
14 | == 検索クエリ
15 |
16 | Vespa では検索クエリの指定方法として大きく2つのフォーマットが提供されています。
17 |
18 | * http://docs.vespa.ai/documentation/reference/search-api-reference.html[Search API]
19 | * http://docs.vespa.ai/documentation/query-language.html[YQL]
20 |
21 | 本ドキュメントでは、このうち `Search API` の方に対象を絞って紹介します。
22 |
23 | [TIP]
24 | ====
25 | `YQL` は SQL ライクに検索を行うことができる記法で、`Search API` を使うのに比べて、
26 | より高度な検索が可能な機能となっています。
27 | ====
28 |
29 | `Search API` を用いて検索を行う場合、URL は以下のようなパスとなります。
30 |
31 | [source]
32 | ----
33 | http://${host]:8080/search/?p1=v1&p2=v2&...
34 | ----
35 |
36 | http://docs.vespa.ai/documentation/reference/search-api-reference.html[Vespa Search API reference]
37 | にあるように、`Search API` では様々なパラメタが定義されていますが、ここでは基本的なものとして以下のパラメタについて説明します。
38 |
39 | [options=header, cols="1,4"]
40 | |====
41 | ^| パラメタ ^| 役割
42 | | language (lang) | クエリの言語を指定します。
43 | | query | 検索クエリを指定します。
44 | | hits (count) | 返却するドキュメントの件数を指定します。
45 | | offset (start) | 返却するドキュメントの開始位置を指定します。
46 | | sorting | 検索結果をソートする条件を指定します。
47 | | filter | 検索対象を絞り込むためのフィルタ条件を指定します (ランキング計算にも影響)。
48 | | recall | 検索対象を絞り込むためのフィルタ条件を指定します。
49 | | summary | レスポンスに含めるフィールドのセットを指定します。
50 | | format | レスポンスのフォーマットを指定します。
51 | | timeout | 検索リクエストのタイムアウト時間 (ms) を指定します。
52 | | tracelevel | レスポンスに付与するデバッグ情報のレベル (1-9) を指定します。
53 | |====
54 |
55 | [TIP]
56 | ====
57 | Vespaのアクセスログは `/opt/vespa/logs/vespa/qrs/` に出力されます (時間は `UTC` です)。
58 |
59 | [source, bash]
60 | ----
61 | [root@vespa1 ~]# ls -l /opt/vespa/logs/vespa/qrs/
62 | total 44
63 | lrwxrwxrwx 1 vespa vespa 65 Feb 23 04:06 QueryAccessLog.container -> /opt/vespa/logs/vespa/qrs/QueryAccessLog.container.20180223040658
64 | -rw-r--r-- 1 vespa vespa 701 Feb 23 04:42 QueryAccessLog.container.20180223040658
65 | lrwxrwxrwx 1 vespa vespa 66 Feb 23 04:07 QueryAccessLog.controller -> /opt/vespa/logs/vespa/qrs/QueryAccessLog.controller.20180223040701
66 | -rw-r--r-- 1 vespa vespa 37528 Feb 23 04:16 QueryAccessLog.controller.20180223040701
67 | ----
68 |
69 | 後ろの `container` と `controller` が対応するコンポーネントを表していて、
70 | 検索リクエストのアクセスログは `QueryAccessLog.container` となります。
71 | ====
72 |
73 | [NOTE]
74 | ====
75 | `ranking` などのドキュメントのランキングに関わるパラメタは
76 | <<5_ranking.adoc#ranking,Vepsaとランキング>>
77 | で説明します。
78 | ====
79 |
80 | [[search_query_language]]
81 | === language (lang)
82 |
83 | `language` はクエリの言語指定を行うためのパラメタで、日本語での検索を行う際に重要です。
84 | Vespa では、デフォルトでは検索クエリは英語 (`en`) であると解釈され、英語用のクエリ解析が行われます。
85 | 日本語で検索を行う場合は、以下のように `language` パラメタを用いて言語が日本語 (`ja`) であることを指定する必要があります。
86 |
87 | [source]
88 | ----
89 | search/?language=ja&query=ほげ
90 | ----
91 |
92 | 例えば、今回のサンプルデータの場合、以下の2つのクエリで検索結果に差異があることが確認できます。
93 |
94 | [source]
95 | ----
96 | // ヒットなし
97 | search/?query=入門書
98 |
99 | // 1件ヒット
100 | search/?language=ja&query=入門書
101 | ----
102 |
103 | これは、前者では言語が英語と認識されているために、"入門書"がトークナイズされずそのまま検索クエリとして投げられているのに対し、
104 | 後者では言語が日本語となっているため、"/入門/書/"と2つのトークンに正しく分割されるという違いがあるためです。
105 |
106 | [TIP]
107 | ====
108 | トークナイズされたトークンは内部的には AND 検索として扱われます。
109 | 例えば `入門書` の場合、内部的には `入門 AND 書` というクエリに展開されています。
110 | ====
111 |
112 | [[search_query_query]]
113 | === query
114 |
115 | `query` は実際の検索クエリを指定するパラメタです。
116 | 指定できる記法は
117 | http://docs.vespa.ai/documentation/reference/simple-query-language-reference.html[Simple Query Language Reference]
118 | となっていますが、典型的なものをピックアップすると以下のようになります。
119 |
120 | [options="header", cols="3,2"]
121 | |====
122 | ^| 内容 ^| クエリ
123 | | defaultフィールドに対して"foo"を検索 | `query=foo`
124 | | titleフィールドに対して"foo"を検索 (フィールド指定検索) | `query=title:foo`
125 | | "foo"かつ"bar"を含むものを検索 (AND検索) | `query=foo bar`
126 | | "foo"もしくは"bar"を含むものを検索 (OR検索) | `query=(foo bar)`
127 | | "foo"を含むが"bar"を含まないものを検索 (NOT検索) | `query=foo -bar`
128 | | "foo bar"というフレーズを検索 (フレーズ検索) | `query="foo bar"`
129 | | `1000\<=price\<=10000` なものを検索 (範囲検索) | `query=price:[1000;10000]`
130 | | "foo"に150%の重みを付与して検索 (重み付き検索) | `query=foo!150 bar`
131 | |====
132 |
133 | [TIP]
134 | ====
135 | 重み付き検索は Solr や Elasticsearch における `Boosting` に対応した機能となります。
136 | Vespa では百分率として重みを表現していて、デフォルト値は `100` です。
137 | この重みは後述のランキングのスコア計算に影響してきます。
138 | ====
139 |
140 | [NOTE]
141 | ====
142 | Vespa では `query=\*foo*` のようなワイルドカード系のクエリは、
143 | Vespa の設定ファイルで `mode="streaming"` と指定したインデックスにのみ使うことができます。
144 | ====
145 |
146 | [[search_query_hits-offset]]
147 | === hits (count), offset (start)
148 |
149 | `hits` と `offset` は検索結果のうちどの範囲を取得するかを指定します。
150 | 例えば、`hits=20` かつ `offset=10` と指定した場合、
151 | 最終的に得られた検索結果のうち、上から数えて11番目から30番目までの計20件を取得することを意味しています。
152 |
153 | [NOTE]
154 | ====
155 | この例の場合、内部的には以下のような動きになります。
156 |
157 | . 検索で上位30件 (`10+20=30`) の結果を取得
158 | . 得られた30件のうち、11番目〜30番目の結果をレスポンスとして返却
159 | ====
160 |
161 | [[search_query_sorting]]
162 | === sorting
163 |
164 | `sorting` では検索結果をソートするときのルールを指定します。
165 | `sorting` の記法は
166 | http://docs.vespa.ai/documentation/reference/sorting.html[Query result sorting]
167 | にまとめられていますが、基本的には以下のようにフィールド名に `+` か `-` を付けて指定します。
168 |
169 | [source]
170 | ----
171 | // priceの昇順 ("+") でソート
172 | select/?language=ja&q=入門&sorting=+price
173 |
174 | // priceの降順 ("-") でソート
175 | select/?language=ja&q=入門&sorting=-price
176 |
177 | // priceの降順 ("-") でソートし、同一priceはpageの昇順 ("+") でソート
178 | select/?language=ja&q=入門&sorting=-price +page
179 |
180 | // relevancy (スコア) の降順でソート
181 | select/?language=ja&q=入門&sorting=-[rank]
182 | ----
183 |
184 | 上記例のように、スペース区切りで複数の条件を並べることで多段にソートを行うことができます。
185 | また、`[rank]` と指定した場合は特別な意味があり、これは文書の `relevancy` (ランキングのスコア) が参照されます。
186 | なお、`sorting` が明示的に指定されていない場合は `sorting=-[rank]` がデフォルトで指定されます。
187 |
188 | [[search_query_filter-recall]]
189 | === filter, recall
190 |
191 | `filter` と `recall` は検索クエリ (`query`) とは別に検索対象を絞る条件を追加する目的で利用されます。
192 | イメージとしては、
193 |
194 | . `filter` および `recall` の条件式にマッチするドキュメントの集合を取得
195 | . `query` の条件にマッチするドキュメントをその集合から選択
196 |
197 | というような動作となります。
198 |
199 | `filter` と `recall` では `query` で用いたシンタックスがそのまま使えますが、
200 | 以下のように各クエリの先頭に `+` および `-` を付ける必要があります。
201 |
202 | [source]
203 | ----
204 | // titleに"python"を含む ("+") ドキュメントの中から、"入門"を含むものを選択
205 | select/?language=ja&q=入門&filter=+title:python
206 | select/?language=ja&q=入門&recall=+title:python
207 |
208 | // titleに"python"を含まない ("-") ドキュメントの中から、"入門"を含むものを選択
209 | select/?language=ja&q=入門&filter=-title:python
210 | select/?language=ja&q=入門&recall=-title:python
211 | ----
212 |
213 | [NOTE]
214 | ====
215 | 実際にURLとして指定するときは `+` を `%2b` とエンコードする必要があります。
216 | ====
217 |
218 | `filter` と `recall` は
219 | 「*`filter` のフィルタ条件はランキングのスコア計算に影響するが、`recall` の方は影響しない*」
220 | という点で異なります。
221 | 例えば、`filter=+title:python` と指定した場合、title に `python` を含むかどうかがスコアに影響を与えます。
222 | 一方、`recall=+title:python` と指定した場合は単純にドキュメントのフィルタとして機能し、スコアには影響を与えません。
223 |
224 | なお、`filter` には `+` と `-` の指定の他に、以下のように「なにも付けない」という指定も可能です。
225 |
226 | [source]
227 | ----
228 | // titleに"python"を含むかどうかをスコア計算で考慮
229 | select/?language=ja&q=入門&filter=title:python
230 | ----
231 |
232 | この場合、`filter` で指定した条件はドキュメントのヒット判定には影響しませんが、
233 | ランキングのスコア計算のときに考慮されるようになります。
234 | 例えば、特定の単語を含むドキュメントのスコアを底上げしたい、のようなスコア調整をする際にこの記法が有効です。
235 |
236 | [IMPORTANT]
237 | ====
238 | `filter` および `recall` はSolrの `fq` のように複数指定することができない点に注意してください。
239 | 例えば、以下のように `filter` を2つ定義した場合、実際には最後のフィルタ条件のみが検索に影響し、それ以外は無視されます。
240 |
241 | [source]
242 | ----
243 | // "filter=+title:java"で"filter=+title:python"が上書きされる!
244 | select/?language=ja&q=入門&filter=+title:python&filter=+title:java
245 | ----
246 |
247 | このような AND 条件を指定したい場合は、以下のように一つのクエリとして表現する必要があります。
248 |
249 | [source]
250 | ----
251 | select/?language=ja&q=入門&filter=+title:python +title:java
252 | ----
253 | ====
254 |
255 | [[search_query_summary]]
256 | === summary
257 |
258 | `summary` はレスポンスに含めるフィールドのセットを指定します。
259 | Vespa では、
260 | <<2_config.adoc#config_file_searchdefinitions,searchdefinitions>>
261 | の中で各フィールドが所属する
262 | http://docs.vespa.ai/documentation/document-summaries.html#summary-classes-in-queries[summary-class]
263 | を定義することでフィールドのセットを作成できます。
264 |
265 | `summary-class` の定義の方法は大きく2つあります。
266 | 1つ目は各フィールドに以下のような `summary-to` 属性を付与して所属するセット名を指定する方法です。
267 |
268 | [source]
269 | ----
270 | field title type string {
271 | indexing: index | summary
272 | summary-to: simple_set, detail_set <1>
273 | }
274 | ----
275 |
276 | <1> `title` フィールドを `simple_set` と `full_set` というセットに追加
277 |
278 | 2つ目は以下のように `document-summary` というブロックで所属するフィールド群を指定する方法です。
279 |
280 | [source]
281 | ----
282 | search book {
283 | ...
284 | document-sumamry simple-set {
285 |
286 | summary simple_set type string { <1>
287 | source: title, price
288 | }
289 |
290 | summary detail_set type string { <2>
291 | source: title, desc, price, genres
292 | }
293 | }
294 | }
295 | ----
296 |
297 | <1> `title` と `price` を含む `simple_set` というセットを定義
298 | <2> `title`、`desc`、`price`、`genres` を含む `detail_set` というセットを定義
299 |
300 | 検索では以下のように定義したセット名を `summary` として指定します。
301 |
302 | [source]
303 | ----
304 | search/?language=ja&query=入門&summary=simple_set
305 | ----
306 |
307 | [[search_query_format]]
308 | === format
309 |
310 | `format` はレスポンスのフォーマットの指定を行います。
311 | Vespa はデフォルトでは `json` フォーマットでレスポンスを返しますが、
312 | 例えば `format=xml` と指定すると `xml` フォーマットでレスポンスが返却されます。
313 |
314 | [TIP]
315 | ====
316 | レスポンスのフォーマットは独自の `Render` を実装することで、`json` や `xml` 以外のフォーマットにも対応させることができます。
317 | 詳しくは
318 | http://docs.vespa.ai/documentation/result-rendering.html[Search Result Renderers]
319 | を参照してください。
320 | ====
321 |
322 | [[search_query_timeout]]
323 | === timeout
324 |
325 | `timeout` では検索リクエストのタイムアウト時間を指定します。
326 | Vespa ではデフォルトで `5000` ミリ秒のタイムアウトが設定されています。
327 | もし、このタイムアウト時間を変更したい場合は、このパラメタにタイムアウト時間をミリ秒で指定して検索を行います。
328 |
329 | ちなみに、検索がタイムアウトした場合は以下のように `Timed out` とレスポンスが返されます。
330 |
331 | [source]
332 | ----
333 | [root@vespa1 /]# curl 'http://localhost:8080/search/?query=java&timeout=0'
334 | {"root":{"id":"toplevel","relevance":1.0,"fields":{"totalCount":0},"errors":[{"code":12,"summary":"Timed out","source":"book","message":"The search chain 'book' timed out."}]}}
335 | ----
336 |
337 | [[search_query_tracelevel]]
338 | === tracelevel
339 |
340 | `tracelevel` は検索リクエストのデバッグを行うときに指定するパラメタです。
341 | Vespa では内部でこの `tracelevel` の値に応じてデバッグログのレスポンスへの付与を制御しています。
342 | `tracelevel` は `1` から `9` までの9段階の指定が可能で、高いほどより詳細なログが出力されるようになります。
343 |
344 | [TIP]
345 | ====
346 | `tracelevel` を有効にした時に出力されるログは、
347 | Vespa が検索クエリを受けてからインデックス (`content` ノード) にリクエストを実際に投げるまでの区間のコンポーネントが出力します。
348 | 実際に実行すると分かりますが、
349 | Vespa では検索クエリを投げてから実際にインデックスに飛ぶまでの間に `Searcher` と呼ばれるコンポーネントが呼び出され、
350 | 検索リクエストおよびレスポンスの加工処理を行っています。
351 |
352 | `tracelevel` の典型的な使い方の一つに「検索クエリのトークナイズの確認」があります。
353 | 例えば、`language` で紹介したヒット数の異なるクエリに `tracelevel=2` を付けて検索すると、
354 |
355 | [source]
356 | ----
357 | search/?query=入門書&tracelevel=2
358 | ... dispatch: query=[入門書] ...
359 |
360 | search/?language=ja&query=入門書&tracelevel=2
361 | ... dispatch: query=[SAND 入門 書] ...
362 | ----
363 |
364 | のように、最終的に検索されるクエリに差異があることがわかります。
365 | ====
366 |
367 | [[search_grouping]]
368 | == グルーピング検索
369 |
370 | Vespa では他の検索エンジンと同様に
371 | http://docs.vespa.ai/documentation/grouping.html[グルーピング検索]
372 | の機能を提供しています。
373 | Vespa のグルーピング機能は他の検索エンジンに比べて高機能で、
374 | これだけで多種多様な集約系の処理が表現可能となっています。
375 |
376 | [[search_grouping_syntax]]
377 | === グルーピングの構文
378 |
379 | Vespa のグルーピングの構文は公式ドキュメントの
380 | http://docs.vespa.ai/documentation/reference/grouping-syntax.html[Query result grouping reference]
381 | にまとめられています。
382 | 記載されている文法を見ると分かるように、非常に複雑なものとなっています。
383 |
384 | 実際に例を見ながら構文を説明していきます。
385 | まず、サンプルデータに対して「`genres` の各要素毎にドキュメントを一つ選択して表示」を行う場合、
386 | 検索リクエストは以下のようになります。
387 |
388 | [source]
389 | ----
390 | search/?language=ja&query=sddocname:book&select=all(group(genres) each(max(1) each(output(summary()))))
391 | ----
392 |
393 | 実際に検索すると、レスポンスに `"id": "group:root:0"` という要素が増えていることが確認できます。
394 | また、 ``"id": "group:root:0"`` の `children` の下に各ジャンルに紐づくドキュメントが1件ずつ出力されます。
395 |
396 | [NOTE]
397 | ====
398 | 非常に長い `json` が返ってくるので `jq` などの整形ツールを通すことを推奨します。
399 | もしくは `format=xml` と付けてブラウザから参照すると見やすいかもしれません。
400 | ====
401 |
402 | [source, json]
403 | ----
404 | {
405 | "root": {
406 | ...
407 | "children": [
408 | {
409 | "id": "group:root:0",
410 | "relevance": 1,
411 | "continuation": {
412 | "this": ""
413 | },
414 | "children": [
415 | {
416 | "id": "grouplist:genres",
417 | "relevance": 1,
418 | "label": "genres",
419 | "children": [
420 | {
421 | "id": "group:string:Elasticsearch",
422 | "relevance": 0,
423 | "value": "Elasticsearch",
424 | "children": [
425 | {
426 | "id": "hitlist:hits",
427 | "relevance": 1,
428 | "label": "hits",
429 | "children": [
430 | {
431 | "id": "id:foo:book:g=foo:elasticsearch_science",
432 | "relevance": 0,
433 | "source": "book",
434 | "fields": {
435 | "sddocname": "book",
436 | "title": "Elasticsearchで始めるデータサイエンス",
437 | "desc": "Elasticsearchとデータサイエンスツールとの連携について紹介します",
438 | "price": 3000,
439 | ...
440 | ----
441 |
442 | 先程の検索リクエストの中で、グルーピングに関する部分は `select` パラメタになります。
443 | `select` パラメタの中身を整形すると以下のようになります。
444 |
445 | [source]
446 | ----
447 | all(
448 | group(genres)
449 | each(
450 | max(1)
451 | each(
452 | output(summary())
453 | )
454 | )
455 | )
456 | ----
457 |
458 | グルーピングの構文を理解するには、まず `all` と `each` を理解することが第一歩です。
459 | 外側の `all` は検索結果全体への操作を、内部の `each` はそれぞれ各グループおよび各ドキュメントへの操作を表しています。
460 | 上記の操作を図にすると以下のようなイメージとなります。
461 |
462 | image::vespa_grouping.jpg[width=900, align="center"]
463 |
464 | Vespa のグルーピングはこのように、
465 |
466 | . `all` もしくは `each` を指定して対象を選択する
467 | . 選択対象に対する操作を記述する
468 | . 1.に戻る
469 |
470 | というように再帰的な手順を踏んで定義していきます。
471 | `all` は後述の `group` 操作を行うときに指定が必要で、例えば多段のグルーピングを行うときに複数回出現します (
472 | <>
473 | 参照)。
474 | `each` はグルーピングで選ばれた要素に対して操作を行うときに利用します。
475 | 定義できる操作は対象がドキュメントの集合 (グループ) なのか、それとも単一のドキュメントなのか、
476 | によって利用可否が決まるため、ルールを記述するときは今の選択範囲がどこなのかを意識することが重要となります。
477 |
478 | グルーピングの対象は `group(field_name)` のように定義します。
479 | 上記例では `genres` という配列型のフィールドを対象としています。
480 |
481 | [TIP]
482 | ====
483 | グルーピングの対象として配列型のフィールドを指定した場合、
484 | Vespa では配列中の各要素を独立なものとして集約処理が実施されます。
485 |
486 | 配列型要素のうち、特定のインデックスの要素が欲しい場合は、
487 | `group(array.at(genres, 0))` のように `arrays.at(field, idx)` を利用することで取得が可能です。
488 | ====
489 |
490 | 上記例の中の `max(1)` はそのグループから最大で1件の結果を取得する事を意味しています。
491 | 初めの `each` の中で定義されているため、この操作の対象は各グループのドキュメント群となります。
492 |
493 | 最後に `output(summary())` はドキュメントの検索結果を出力することを意味しています。
494 | 2つ目の `each` の中で定義されているため、この操作の対象は各グループの各ドキュメントとなります。
495 | `output` は検索レスポンスへの情報の付与に対応していて、
496 | 例えばグループを対象としている階層なら `output(avg(price))` のような統計値を指定もできます。
497 | `summary` はドキュメントを対象としているときに指定できる操作で、前述のようにドキュメントの内容の参照に対応しています。
498 |
499 | [[search_grouping_example]]
500 | === グルーピングの具体例
501 |
502 | 前述のように、Vespa のグルーピングは `each` で対象を絞りつつ、各グループ or ドキュメントに対して操作を定義していくというものでした。
503 | ここでは、実際に具体例を見ながら Vespa のグルーピングでできる機能について紹介していきます。
504 |
505 | [NOTE]
506 | ====
507 | ここの具体例は代表的なものだけをピックアップしていますが、
508 | Vespa のグルーピングではより複雑な処理も記述できます。
509 | より詳細な機能を知りたい場合は
510 | http://docs.vespa.ai/documentation/reference/grouping-syntax.html[Query result grouping reference]
511 | に記載されている Example も併せて参照してください。
512 | ====
513 |
514 | [[search_grouping_example_order]]
515 | ==== グループの並び順の変更
516 |
517 | Vespa のグルーピングでは、`order` という操作を用いてグループの並び順を制御できます。
518 | 例えば、各ジャンルについて所属するドキュメントの価格の最大値が高いものから順に表示したい場合は以下のような式となります。
519 |
520 | [source]
521 | ----
522 | all(
523 | group(genres) <1>
524 | order(-max(price)) <2>
525 | each(
526 | output(max(price)) <3>
527 | )
528 | )
529 | ----
530 |
531 | <1> `genres` の値についてグルーピング
532 | <2> 各グループの `price` の最大値の降順でソート
533 | <3> 結果がわかりやすいように各グループの `price` の最大値を出力
534 |
535 | `order` は得られたグループをどのように並び替えるかを指定するための式です。
536 | この例では、`max(price)` から各グループの `price` 最大値が、
537 | `-max(price)` と `-` がついてることから降順であることがわかります。
538 |
539 | [NOTE]
540 | ====
541 | `order` はグループの並び替えにのみ使えます。
542 | 例えば以下のようにドキュメントのソートに指定するとエラーになります。
543 |
544 | [source]
545 | ----
546 | all(
547 | group(genres)
548 | each(
549 | order(-price) <1>
550 | output(max(price))
551 | )
552 | )
553 | ----
554 |
555 | <1> UnsupportedOperationException: Can not order single group content.
556 | ====
557 |
558 | これを例えば `query=title:入門` と組み合わせると、検索クエリは以下のようになります。
559 |
560 | [source]
561 | ----
562 | search/?language=ja&query=title:入門&select=all(group(genres) order(-max(price)) each(output(max(price))))
563 | ----
564 |
565 | 結果、以下のようにタイトルに `入門` が含まれるドキュメントについて、価格の最大値が高い順のジャンルのグループが得られます。
566 |
567 | [source, json]
568 | ----
569 | {
570 | "root": {
571 | ...
572 | "children": [
573 | {
574 | "id": "group:root:0",
575 | "relevance": 1,
576 | "continuation": {
577 | "this": ""
578 | },
579 | "children": [
580 | {
581 | "id": "grouplist:genres",
582 | "relevance": 1,
583 | "label": "genres",
584 | "children": [
585 | {
586 | "id": "group:string:Python",
587 | "relevance": 1,
588 | "value": "Python",
589 | "fields": {
590 | "max(price)": 2000
591 | }
592 | },
593 | {
594 | "id": "group:string:コンピュータ",
595 | "relevance": 0.8,
596 | "value": "コンピュータ",
597 | "fields": {
598 | "max(price)": 2000
599 | }
600 | },
601 | {
602 | "id": "group:string:プログラミング",
603 | "relevance": 0.6,
604 | "value": "プログラミング",
605 | "fields": {
606 | "max(price)": 2000
607 | }
608 | },
609 | {
610 | "id": "group:string:Vespa",
611 | "relevance": 0.4,
612 | "value": "Vespa",
613 | "fields": {
614 | "max(price)": 1500
615 | }
616 | },
617 | {
618 | "id": "group:string:検索エンジン",
619 | "relevance": 0.2,
620 | "value": "検索エンジン",
621 | "fields": {
622 | "max(price)": 1500
623 | }
624 | }
625 | ]
626 | }
627 | ]
628 | },
629 | ----
630 |
631 | [[search_grouping_stat]]
632 | ==== 各グループの統計情報の取得
633 |
634 | 前述の `max(price)` のように、Vespa のグルーピングではグループ内の統計情報を取得するための操作が定義されています。
635 | 例えば、`genres` の各グループについて、 `price` の合計、平均、最小、最大、標準偏差を出力する場合は以下のような式になります。
636 |
637 | [source]
638 | ----
639 | all(
640 | group(genres)
641 | order(-count()) <1>
642 | each(
643 | output(sum(price)) <2>
644 | output(avg(price)) <3>
645 | output(min(price)) <4>
646 | output(max(price)) <5>
647 | output(stddev(price)) <6>
648 | )
649 | )
650 | ----
651 |
652 | <1> グループをヒット件数 (`count()`) の降順で並び替え
653 | <2> `price` の合計値 (`sum`) を出力
654 | <3> `price` の平均値 (`avg`) を出力
655 | <4> `price` の最小値 (`min`) を出力
656 | <5> `price` の最大値 (`max`) を出力
657 | <6> `price` の標準偏差 (`stddev`) を出力
658 |
659 | これを例えば `query=title:入門` と組み合わせると、検索クエリは以下のようになります。
660 |
661 | [source]
662 | ----
663 | search/?language=ja&query=title:入門&select=all(group(genres) order(-count()) each(output(sum(price)) output(avg(price)) output(min(price)) output(max(price)) output(stddev(price))))
664 | ----
665 |
666 | 結果、以下のように各ジャンルでの `price` の統計値が出力されます。
667 |
668 | [source, json]
669 | ----
670 | {
671 | "root": {
672 | ...
673 | "children": [
674 | {
675 | "id": "group:root:0",
676 | "relevance": 1,
677 | "continuation": {
678 | "this": ""
679 | },
680 | "children": [
681 | {
682 | "id": "grouplist:genres",
683 | "relevance": 1,
684 | "label": "genres",
685 | "children": [
686 | {
687 | "id": "group:string:コンピュータ",
688 | "relevance": 1,
689 | "value": "コンピュータ",
690 | "fields": {
691 | "sum(price)": 3500,
692 | "avg(price)": 1750,
693 | "min(price)": 1500,
694 | "max(price)": 2000,
695 | "stddev(price)": 250
696 | }
697 | },
698 | ...
699 | ----
700 |
701 | [TIP]
702 | ====
703 | 上の例では、レスポンスのラベルが `sum(price)` のように式そのままになっていますが、
704 | Vespaでは以下のように `as(name)` 構文を使うことで出力ラベルを変更する事ができます。
705 |
706 | [source]
707 | ----
708 | all(
709 | group(genres)
710 | order(-count())
711 | each(
712 | output(sum(price) as(sum_price)) <1>
713 | )
714 | )
715 | ----
716 |
717 | <1> レスポンスのラベルを `sum_price` に変更
718 | ====
719 |
720 | [[search_grouping_facet]]
721 | ==== 各グループに対するヒット数および検索結果を取得
722 |
723 | いわゆる `faceting` や `result grouping` に対応する処理も、
724 | Vespa ではグルーピング式を用いて定義します。
725 |
726 | 例えば、`genres` の各グループについて、ヒット数と上位3件を表示する場合は以下のような式になります。
727 |
728 | [source]
729 | ----
730 | all(
731 | group(genres)
732 | order(-count())
733 | each(
734 | max(3) <1>
735 | output(count() as(total)) <2>
736 | each(
737 | output(summary()) <3>
738 | )
739 | )
740 | )
741 | ----
742 |
743 | <1> 各グループから最大で3件を取得
744 | <2> 各グループのヒット数 (`count()`) を "total" というラベル (`as(total)`) で取得
745 | <3> 各ドキュメントの情報 (`summary()`) を出力
746 |
747 | これを例えば `query=title:入門` と組み合わせると、検索クエリは以下のようになります。
748 |
749 | [source]
750 | ----
751 | search/?language=ja&query=title:入門&select=all(group(genres) order(-count()) each(max(3) output(count() as(total)) each(output(summary()))))
752 | ----
753 |
754 | 結果、以下のように各ジャンルでのヒット数と検索結果が出力されます。
755 |
756 | [source, json]
757 | ----
758 | {
759 | "root": {
760 | ...
761 | "children": [
762 | {
763 | "id": "group:root:0",
764 | "relevance": 1,
765 | "continuation": {
766 | "this": ""
767 | },
768 | "children": [
769 | {
770 | "id": "grouplist:genres",
771 | "relevance": 1,
772 | "label": "genres",
773 | "children": [
774 | {
775 | "id": "group:string:コンピュータ",
776 | "relevance": 1,
777 | "value": "コンピュータ",
778 | "fields": {
779 | "total": 2
780 | },
781 | "children": [
782 | {
783 | "id": "hitlist:hits",
784 | "relevance": 1,
785 | "label": "hits",
786 | "children": [
787 | {
788 | "id": "id:book:book::python_intro",
789 | "relevance": 0.15974580091895013,
790 | "source": "book",
791 | "fields": {
792 | "sddocname": "book",
793 | "title": "Python本格入門",
794 | "desc": "今話題のPythonの使い方をわかりやすく説明します",
795 | "price": 2000,
796 | "page": 450,
797 | "genres": [
798 | "コンピュータ",
799 | "プログラミング",
800 | "Python"
801 | ],
802 | "reviews": [
803 | {
804 | "item": "readability",
805 | "weight": 80
806 | },
807 | {
808 | "item": "cost",
809 | "weight": 70
810 | },
811 | {
812 | "item": "quality",
813 | "weight": 50
814 | }
815 | ],
816 | "documentid": "id:book:book::python_intro"
817 | }
818 | },
819 | {
820 | "id": "id:book:book::vespa_intro",
821 | "relevance": 0.15968230614070084,
822 | "source": "book",
823 | "fields": {
824 | "sddocname": "book",
825 | "title": "ゼロから始めるVespa入門",
826 | "desc": "話題のOSS検索エンジン、Vespaの使い方を初心者にもわかりやすく解説します",
827 | "price": 1500,
828 | "page": 200,
829 | "genres": [
830 | "コンピュータ",
831 | "検索エンジン",
832 | "Vespa"
833 | ],
834 | "reviews": [
835 | {
836 | "item": "readability",
837 | "weight": 90
838 | },
839 | {
840 | "item": "cost",
841 | "weight": 80
842 | },
843 | {
844 | "item": "quality",
845 | "weight": 40
846 | }
847 | ],
848 | "documentid": "id:book:book::vespa_intro"
849 | }
850 | }
851 | ]
852 | }
853 | ]
854 | },
855 | {
856 | "id": "group:string:Python",
857 | "relevance": 0.8,
858 | "value": "Python",
859 | "fields": {
860 | "total": 1
861 | },
862 | "children": [
863 | {
864 | "id": "hitlist:hits",
865 | "relevance": 1,
866 | "label": "hits",
867 | "children": [
868 | {
869 | "id": "id:book:book::python_intro",
870 | "relevance": 0.15974580091895013,
871 | "source": "book",
872 | "fields": {
873 | "sddocname": "book",
874 | "title": "Python本格入門",
875 | "desc": "今話題のPythonの使い方をわかりやすく説明します",
876 | "price": 2000,
877 | "page": 450,
878 | "genres": [
879 | "コンピュータ",
880 | "プログラミング",
881 | "Python"
882 | ],
883 | "reviews": [
884 | {
885 | "item": "readability",
886 | "weight": 80
887 | },
888 | {
889 | "item": "cost",
890 | "weight": 70
891 | },
892 | {
893 | "item": "quality",
894 | "weight": 50
895 | }
896 | ],
897 | "documentid": "id:book:book::python_intro"
898 | }
899 | }
900 | ]
901 | }
902 | ]
903 | },
904 | ....
905 | ----
906 |
907 | Vespa のグルーピングでは、各グループ内のドキュメント群は `relevancy` の降順でソートされます。
908 | そのため、`max` 指定をした場合は *そのグループを `relevancy` の降順で並べた場合の上位* が選ばれます。
909 |
910 | [IMPORTANT]
911 | ====
912 | 上記のように、Vespa のグルーピングでは、グループ内のドキュメントは必ず `relevancy` 順でソートされるという制約があります。
913 | そのため、`sorting` のように特定のフィールドを指定してソートということがクエリからはできません。
914 |
915 | 幸い、`relevancy` の計算方法は
916 | <<5_ranking.adoc#ranking,次章>>
917 | で述べるようにカスタマイズが可能なため、
918 | 例えば価格の降順に並ぶようなスコア式を定義することで `sorting` と同じような動作をさせることが可能です。
919 | ====
920 |
921 | [[search_grouping_example_bucket]]
922 | ==== 連続値に対するグルーピング
923 |
924 | Vepsa では連続値のフィールドに対しても、分割の単位 (`bucket`) を定義することでグルーピングを行うことができます。
925 | 例えば、`price` を 2000円未満、2000円以上4000未満、4000円以上 の3つのバケットでグルーピングする場合の式は以下のようになります。
926 |
927 | [source]
928 | ----
929 | all(
930 | group(
931 | predefined( <1>
932 | price, <2>
933 | bucket(0, 2000), <3>
934 | bucket(2000, 4000), <4>
935 | bucket(4000, inf) <5>
936 | )
937 | )
938 | each(
939 | output(count())
940 | )
941 | )
942 | ----
943 |
944 | <1> `predefined(field, bucket, ...)` でグルーピング対象のバケットを定義
945 | <2> 対象フィールドは `price`
946 | <3> `0 \<= price < 2000` のバケットを定義
947 | <4> `2000 \<= price < 4000` のバケットを定義
948 | <5> `4000 \<= price` のバケットを定義
949 |
950 | 例えば、全ドキュメントに対して上記のグルーピングを組み合わせると以下のような検索クエリになります。
951 |
952 | [source]
953 | ----
954 | search/?language=ja&query=sddocname:book&select=all(group(predefined(price, bucket(0, 2000), bucket(2000, 4000), bucket(4000, inf))) each(output(count())))
955 | ----
956 |
957 | 結果、以下のように各価格帯でのヒット件数が得られます。
958 |
959 | [source, json]
960 | ----
961 | {
962 | "root": {
963 | ...
964 | "children": [
965 | {
966 | "id": "group:root:0",
967 | "relevance": 1,
968 | "continuation": {
969 | "this": ""
970 | },
971 | "children": [
972 | {
973 | "id": "grouplist:predefined(price, bucket[0, 2000>, bucket[2000, 4000>, bucket[4000, inf>)",
974 | "relevance": 1,
975 | "label": "predefined(price, bucket[0, 2000>, bucket[2000, 4000>, bucket[4000, inf>)",
976 | "children": [
977 | {
978 | "id": "group:long_bucket:0:2000",
979 | "relevance": 0,
980 | "limits": {
981 | "from": "0",
982 | "to": "2000"
983 | },
984 | "fields": {
985 | "count()": 4
986 | }
987 | },
988 | {
989 | "id": "group:long_bucket:2000:4000",
990 | "relevance": 0,
991 | "limits": {
992 | "from": "2000",
993 | "to": "4000"
994 | },
995 | "fields": {
996 | "count()": 6
997 | }
998 | },
999 | {
1000 | "id": "group:long_bucket:4000:9223372036854775807",
1001 | "relevance": 0,
1002 | "limits": {
1003 | "from": "4000",
1004 | "to": "9223372036854775807"
1005 | },
1006 | "fields": {
1007 | "count()": 3
1008 | }
1009 | }
1010 | ]
1011 | }
1012 | ]
1013 | },
1014 | ...
1015 | ----
1016 |
1017 | [TIP]
1018 | ====
1019 | `bucket(low, high)` という記法は、内部的には `bucket[low, high>` という記法に展開されます。
1020 | `bucket` では括弧で開区間と閉区間を表現しており、
1021 | `[ ]` は閉区間 (`bucket[low, high] : low \<= val \<= high`) に、
1022 | `< >` は開区間 (`bucket : low < val < high`) に対応しています。
1023 | ====
1024 |
1025 | [[search_grouping_example_nest]]
1026 | ==== 多階層グルーピング
1027 |
1028 | Vespa ではグルーピングの中でさらにグルーピングを定義する、いわゆる多階層グルーピングをサポートしています。
1029 | 例えば、`genres` の第一ジャンルでグルーピングしたのち、さらに第二ジャンルでグルーピングする場合は以下のような式になります。
1030 |
1031 | [source]
1032 | ----
1033 | all(
1034 | group(array.at(genres, 0)) <1>
1035 | each(
1036 | output(count()) <2>
1037 | all(
1038 | group(array.at(genres, 1)) <3>
1039 | each(
1040 | output(count()) <4>
1041 | )
1042 | )
1043 | )
1044 | )
1045 | ----
1046 |
1047 | <1> 第一ジャンル (`array.at(genres, 0)`) でグルーピング
1048 | <2> 第一ジャンルの各グループについてヒット数を出力
1049 | <3> 第二ジャンル (`array.at(genres, 1)`) でさらにグルーピング
1050 | <4> 第二ジャンルの各グループについてヒット数を出力
1051 |
1052 | [NOTE]
1053 | ====
1054 | 新しい `group` を定義する場合、ブロックを `all` でくくって明示的に全体を対象することを宣言する必要があります。
1055 | 例えば、以下のように `all` でくくらなかった場合はエラーとなります。
1056 |
1057 | [source]
1058 | ----
1059 | all(
1060 | group(array.at(genres, 0))
1061 | each(
1062 | output(count())
1063 | group(array.at(genres, 1)) <1>
1064 | each(
1065 | output(count())
1066 | )
1067 | )
1068 | )
1069 | ----
1070 |
1071 | <1> `all` ではなく `each` の中で `group` が宣言されているためエラーとなる
1072 | ====
1073 |
1074 | これを例えば `query=title:入門` と組み合わせると、検索クエリは以下のようになります。
1075 |
1076 | [source]
1077 | ----
1078 | search/?language=ja&query=title:入門&select=all(group(array.at(genres, 0)) each(output(count()) all(group(array.at(genres, 1)) each(output(count())))))
1079 | ----
1080 |
1081 | 結果、以下のように第一ジャンルと第二ジャンルでネストしたグルーピング結果が取得されます。
1082 |
1083 | [source, json]
1084 | ----
1085 | {
1086 | "root": {
1087 | ...
1088 | "children": [
1089 | {
1090 | "id": "group:root:0",
1091 | "relevance": 1,
1092 | "continuation": {
1093 | "this": ""
1094 | },
1095 | "children": [
1096 | {
1097 | "id": "grouplist:array.at(genres, 0)",
1098 | "relevance": 1,
1099 | "label": "array.at(genres, 0)",
1100 | "children": [
1101 | {
1102 | "id": "group:string:コンピュータ",
1103 | "relevance": 0.15974580091895013,
1104 | "value": "コンピュータ",
1105 | "fields": {
1106 | "count()": 2
1107 | },
1108 | "children": [
1109 | {
1110 | "id": "grouplist:array.at(genres, 1)",
1111 | "relevance": 1,
1112 | "label": "array.at(genres, 1)",
1113 | "children": [
1114 | {
1115 | "id": "group:string:プログラミング",
1116 | "relevance": 0.15974580091895013,
1117 | "value": "プログラミング",
1118 | "fields": {
1119 | "count()": 1
1120 | }
1121 | },
1122 | {
1123 | "id": "group:string:検索エンジン",
1124 | "relevance": 0.15968230614070084,
1125 | "value": "検索エンジン",
1126 | "fields": {
1127 | "count()": 1
1128 | }
1129 | }
1130 | ]
1131 | }
1132 | ]
1133 | }
1134 | ]
1135 | }
1136 | ]
1137 | },
1138 | ...
1139 | ----
1140 |
1141 | [[search_other]]
1142 | == その他の検索
1143 |
1144 | Vespa ではここまでで紹介した検索以外にも、次のような機能が提供されています。
1145 |
1146 | * http://docs.vespa.ai/documentation/geo-search.html[位置検索]
1147 | * http://docs.vespa.ai/documentation/reference/wand-operator.html[WAND 検索]
1148 | * http://docs.vespa.ai/documentation/predicate-fields.html[Predicate フィールド]
1149 | * http://docs.vespa.ai/documentation/streaming-search.html[ストリーミング検索]
1150 |
1151 | この節では、これら機能の概要について簡単に紹介します (詳細は公式ドキュメントを参照してください)。
1152 |
1153 | [IMPORTANT]
1154 | ====
1155 | WAND 検索と Predicate フィールドでは検索クエリとして
1156 | http://docs.vespa.ai/documentation/query-language.html[YQL]
1157 | を用いる必要があります。
1158 | ====
1159 |
1160 | [[search_other_spatial]]
1161 | === 位置検索
1162 |
1163 | http://docs.vespa.ai/documentation/geo-search.html[位置検索]
1164 | では、スキーマ定義の時に `position` 型という緯度・経度を保持するフィールドを定義して利用します。
1165 |
1166 | [source]
1167 | ----
1168 | // schema
1169 | field latlong type position { <1>
1170 | indexing: attribute
1171 | }
1172 |
1173 | // feed
1174 | "fields": {
1175 | "latlong": "N35.680;W139.737" <2>
1176 | }
1177 |
1178 | // search
1179 | search/?query=yahoo&pos.ll=N35.680%3BW139.737&pos.radius=1km <3>
1180 | ----
1181 |
1182 | <1> `position` 型としてフィールドを定義
1183 | <2> 北緯32.680度、東経139.737を登録
1184 | <3> 北緯32.680度、東経139.737の地点から半径1km圏内
1185 |
1186 | 上の例のように、位置検索では緯度・経度に基づく範囲検索が可能となっています。
1187 |
1188 | [[search_other_wand]]
1189 | === WAND 検索
1190 |
1191 | http://docs.vespa.ai/documentation/reference/wand-operator.html[WAND 検索]
1192 | は
1193 | http://cis.poly.edu/westlab/papers/cntdstrb/p426-broder.pdf[Broder 等の論文]
1194 | で発表された `WAND` (`Weak AND` or `Weighted AND` の略) と呼ばれる手法を用いた検索機能です。
1195 | WAND 検索は、イメージとして以下のようにクエリとドキュメントの各単語に重みを付け、
1196 | その内積のスコアを元に Top Nを検索するような手法です。
1197 |
1198 | [source]
1199 | ----
1200 | query : {"foo": 2, "bar": 4}
1201 |
1202 | doc1 : {"foo": 0.6, "fizz": 0.1} -> 2 * 0.6 = 1.2
1203 | doc2 : {"foo": 0.3, "bar": 0.5} -> 2 * 0.3 + 4 * 0.5 = 2.6
1204 | doc3 : {"bar": 0.2, "buzz": 0.8} -> 4 * 0.2 = 0.8
1205 | ----
1206 |
1207 | WAND 検索では、ドキュメントの各単語について全ドキュメント中での上限スコアを予め計算し、
1208 | それを用いて候補ドキュメントを効率的に枝刈りしていきます。
1209 | なお、WAND 検索を行う場合、対象のフィールドは `weightedset` として定義されている必要があります。
1210 |
1211 | WAND 検索は、特に非常に多くの OR 条件があるようなクエリを扱う場合に効果的です。
1212 | 典型的な例としては「ユーザの行動履歴に基いてレコメンドを行うシステム」が考えられます。
1213 | レコメンドシステムでは、ユーザが行動履歴から推定されたユーザのタグ情報と、
1214 | ドキュメントが持つタグ情報との類似度を計算して、ユーザが興味を持ちそうなドキュメントを選択します。
1215 | この類似度は2つのタグ集合 (ベクトル) の内積として表現できるため、
1216 | 先程の例の "単語" を "タグ"、"重み" を "関連度" に置き換えれば実現できることがわかります。
1217 | このタグ情報は非常に種類が多くなるはずで、普通にユーザの興味タグを OR 検索すると計算コストがとても大きくなります。
1218 | それに対して、WAND 検索の場合は前述のように検索の過程で効率的に枝刈りを行うため、計算コストを劇的に抑えることができます。
1219 |
1220 | [NOTE]
1221 | ====
1222 | Vespa のドキュメントでは WAND 検索として `Parallel Wand` と `Vespa Wand` の2つがありますが、
1223 | ここでの例は `Parallel Wand` について記述しています。
1224 |
1225 | `Parallel Wand` はいわゆる `WAND` アルゴリズムを実装したものとなっており、
1226 | Top N の結果が実際のスコアの大小と一致することが保障されていますが、
1227 | 検索対象として1つのフィールドしか指定できず、スコア計算も内積限定となっています。
1228 | 一方、`Vespa Wand` では複数のフィールドを指定したり、独自のスコア計算を利用したりできますが、
1229 | 枝刈りのスコア計算にヒューリスティックな手法が利用されるため、
1230 | 最終スコアが高いドキュメントでも検索の途中で枝刈りされてしまう可能性があります。
1231 |
1232 | 個人的な見解としては、
1233 | WAND 検索を行うならば理論保障がしっかりしている `Parallel Wand` をまずは検討するのが無難かと思います。
1234 | ====
1235 |
1236 | [[search_other_predicate]]
1237 | === Predicate フィールド
1238 |
1239 | http://docs.vespa.ai/documentation/predicate-fields.html[Predicate フィールド]
1240 | はドキュメント側に条件式を埋め込み、検索クエリで指定された属性値にマッチしたドキュメントを返却する機能です。
1241 | 通常の検索の場合は検索クエリ側で条件式を書きますが、
1242 | Predicate フィールドではインデックス側にその構造を埋め込む、というのが特徴です。
1243 |
1244 | [source]
1245 | ----
1246 | // schema
1247 | field target type predicate { <1>
1248 | indexing: attribute
1249 | }
1250 |
1251 | // feed
1252 | "fields": {
1253 | "target": "gender in [Female] and age in [20..30]" <2>
1254 | }
1255 |
1256 | // query
1257 | select/?yql=select * from sources * where predicate(target, {"gender":"Female"}, {"age": 20L}) <3>
1258 | ----
1259 |
1260 | <1> `predicate` 型としてフィールドを定義
1261 | <2> ドキュメントのターゲットを女性 (`gender in [Female]`) で20-30歳 (`age in [20..30]`) に設定
1262 | <3> 女性 (`{"gender":"Female"}`) かつ 20歳 (`{"age":20L}`) にマッチするドキュメントを検索
1263 |
1264 | [TIP]
1265 | ====
1266 | クエリ中の `predicate` は、
1267 | 第一引数にフィールド名、第二引数にカテゴリ条件、第三引数に範囲条件を指定します。
1268 | ====
1269 |
1270 | Predicate フィールドの典型的な利用例としては、広告のターゲティングが考えられます。
1271 | 広告のターゲティングの場合、ドキュメントは広告本体であり、
1272 | そこに Predicate フィールドとして広告のターゲット層の条件式を指定することになります。
1273 | 検索時はユーザの属性情報をクエリに指定することで、対応する広告が取得できます。
1274 |
1275 | [[search_other_streaming]]
1276 | === ストリーミング検索
1277 |
1278 | http://docs.vespa.ai/documentation/streaming-search.html[ストリーミング検索]
1279 | はドキュメントを `grep` で検索するような機能に相当します。
1280 | ストリーミング検索ではインデックス構造が通常とは異なり、転置インデックスを構築せずに生データのみを保持します。
1281 | このため、ストリーミング検索を有効にするためには、
1282 | インデックス構築時に以下のような明示的に `streaming` を指定する必要があります。
1283 |
1284 | [source]
1285 | ----
1286 |
1287 |
1288 |
1289 | ----
1290 |
1291 | [IMPORTANT]
1292 | ====
1293 | インデックス構造が変わるため、設定を変更する場合は再インデックスが必須です。
1294 | ====
1295 |
1296 | ストリーミング検索では、全てのドキュメントを検索するのは非常にコストがかかるためで、
1297 | 検索時に検索対象のドキュメントのブロックを指定する必要があります。
1298 | このブロックは
1299 | <<3_update.adoc#update_request_docid,ドキュメントID>>
1300 | の TIP の中で説明した `n=NUM` および `g=GROUP` の指定が対応します。
1301 |
1302 | ストリーミング検索では、以下のようにワイルドカードを用いた検索が可能です。
1303 |
1304 | [source]
1305 | ----
1306 | search/?q=*ytho*&streaming.userid=12345678
1307 | ----
1308 |
1309 | ここで、`streaming.userid` はドキュメント ID の `n=NUM` に相当する番号です。
1310 | `g=GRUOP` と指定して登録した場合は `streaming.groupname` を用います。
1311 |
1312 | ストリーミング検索は非常に限定的な範囲を検索するときに選択肢の一つとなります。
1313 | 具体例としては、ユーザが自分自身のデータを検索するようなケース (メールとか) が考えられます。
1314 | このユースケースの場合、ユーザが参照するドキュメントは非常に限定的であり、
1315 | その配置は前述のドキュメント ID の指定で制御が可能なため、ストリーミング検索の適用できます。
--------------------------------------------------------------------------------