├── .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 の指定で制御が可能なため、ストリーミング検索の適用できます。 --------------------------------------------------------------------------------