mirror of
https://github.com/meilisearch/meilisearch.git
synced 2025-12-14 08:27:18 +00:00
Compare commits
143 Commits
v1.6.0
...
upgrade-ri
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c02d585f5b | ||
|
|
023c2d755f | ||
|
|
407ad753ed | ||
|
|
285aa15d2f | ||
|
|
bf43a3f60a | ||
|
|
2c88131bb1 | ||
|
|
35aa9d5904 | ||
|
|
cfb3e6b51f | ||
|
|
1502382316 | ||
|
|
ef994d84d0 | ||
|
|
1b74010e9e | ||
|
|
08af0e690c | ||
|
|
d71b77f18b | ||
|
|
c443ed7e3f | ||
|
|
db722d201a | ||
|
|
91eb67e981 | ||
|
|
902d700a24 | ||
|
|
f70a615ed9 | ||
|
|
7ff722b72e | ||
|
|
bcf7909bba | ||
|
|
ceb211c515 | ||
|
|
f3c34d5b8c | ||
|
|
4de2db6786 | ||
|
|
661baa716b | ||
|
|
02dcaf07db | ||
|
|
d78ada07b5 | ||
|
|
bc097d90cb | ||
|
|
b393823f36 | ||
|
|
e773dfa9ba | ||
|
|
f158e96fe7 | ||
|
|
e23ec4886d | ||
|
|
7793ba67a4 | ||
|
|
80774148fd | ||
|
|
bf5cea8b10 | ||
|
|
38e1c40f38 | ||
|
|
afc0585c1c | ||
|
|
0e7a411d4d | ||
|
|
0f327f2821 | ||
|
|
77254765e8 | ||
|
|
ce6e6ec2c5 | ||
|
|
91a8f74763 | ||
|
|
abaa72e2bf | ||
|
|
3c3a258a22 | ||
|
|
73e66d5a97 | ||
|
|
b8da117b9c | ||
|
|
5e52107474 | ||
|
|
bcf1c4dae5 | ||
|
|
50f84d43f5 | ||
|
|
f76cc0806e | ||
|
|
2f1abd2c03 | ||
|
|
dedc91e2cf | ||
|
|
a61d8c59ff | ||
|
|
6e23040464 | ||
|
|
8febbf64ce | ||
|
|
b141c82a04 | ||
|
|
cc79cd0b04 | ||
|
|
256538ccb9 | ||
|
|
ca8990394e | ||
|
|
83fb2949c3 | ||
|
|
6cf703387d | ||
|
|
771861599b | ||
|
|
7e47cea0c4 | ||
|
|
5d7061682e | ||
|
|
02e6c8a440 | ||
|
|
89401d097b | ||
|
|
72ebac1fbb | ||
|
|
a616a1d37b | ||
|
|
3e120619fa | ||
|
|
a1caac9bfb | ||
|
|
88d03c56ab | ||
|
|
32ee05ccef | ||
|
|
74c180267e | ||
|
|
517f5332d6 | ||
|
|
9ac5750096 | ||
|
|
7ae4013478 | ||
|
|
fb705116a6 | ||
|
|
053306c0e7 | ||
|
|
84235a63df | ||
|
|
29f8300ac7 | ||
|
|
05edd85d75 | ||
|
|
9eeb75d501 | ||
|
|
4792651462 | ||
|
|
58c3501b54 | ||
|
|
ff76d8f21a | ||
|
|
698ea5139d | ||
|
|
880e790bff | ||
|
|
fbf5f2a392 | ||
|
|
1555870088 | ||
|
|
9f8f3105d5 | ||
|
|
318843aacd | ||
|
|
6d111139b5 | ||
|
|
dff2707471 | ||
|
|
c57f7f7379 | ||
|
|
b968616a99 | ||
|
|
c1bf33a112 | ||
|
|
ddc2b7129a | ||
|
|
b6fc181993 | ||
|
|
388fce9e46 | ||
|
|
d35fe43fd5 | ||
|
|
f692021bfc | ||
|
|
1b90778bf5 | ||
|
|
66ae81a909 | ||
|
|
4aa4a15dc9 | ||
|
|
4b4e8ea2a4 | ||
|
|
84f49d76cd | ||
|
|
afb0e8eab9 | ||
|
|
b5b2333a05 | ||
|
|
40fa0b4df6 | ||
|
|
ab4d614599 | ||
|
|
262b20fdba | ||
|
|
9020606c45 | ||
|
|
0887186ecf | ||
|
|
7d190d8078 | ||
|
|
3b8a9597e2 | ||
|
|
f275554982 | ||
|
|
d997ea1f01 | ||
|
|
50e1d34c66 | ||
|
|
406531c991 | ||
|
|
01e2c3d6bb | ||
|
|
cfaa522d68 | ||
|
|
0c8d1644a6 | ||
|
|
5e0268d40e | ||
|
|
9f9ad4cc05 | ||
|
|
3ee7682fa7 | ||
|
|
7f125bfb12 | ||
|
|
5869ca7716 | ||
|
|
7a89abd2a0 | ||
|
|
d9d0419845 | ||
|
|
5dc8d9e9bf | ||
|
|
9e12a91afb | ||
|
|
8e016fbfeb | ||
|
|
1ccde9bf0b | ||
|
|
34e814f400 | ||
|
|
857cd09285 | ||
|
|
552127021f | ||
|
|
b4d7d80ad9 | ||
|
|
5204c0b60b | ||
|
|
e73cd692db | ||
|
|
29b453346b | ||
|
|
c4bb435374 | ||
|
|
2bcff2ea46 | ||
|
|
1275e72e0b | ||
|
|
d868131bb7 |
2
.cargo/config.toml
Normal file
2
.cargo/config.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[alias]
|
||||||
|
xtask = "run --package xtask --"
|
||||||
17
.github/ISSUE_TEMPLATE/sprint_issue.md
vendored
17
.github/ISSUE_TEMPLATE/sprint_issue.md
vendored
@@ -27,6 +27,23 @@ Related spec: WIP
|
|||||||
- [ ] If prototype validated, merge changes into `main`
|
- [ ] If prototype validated, merge changes into `main`
|
||||||
- [ ] Update the spec
|
- [ ] Update the spec
|
||||||
|
|
||||||
|
### Reminders when modifying the Setting API
|
||||||
|
|
||||||
|
<!--- Special steps to remind when adding a new index setting -->
|
||||||
|
|
||||||
|
- [ ] Ensure the new setting route is at least tested by the [`test_setting_routes` macro](https://github.com/meilisearch/meilisearch/blob/5204c0b60b384cbc79621b6b2176fca086069e8e/meilisearch/tests/settings/get_settings.rs#L276)
|
||||||
|
- [ ] Ensure Analytics are fully implemented
|
||||||
|
- [ ] `/settings/my-new-setting` configurated in the [`make_setting_routes` macro](https://github.com/meilisearch/meilisearch/blob/5204c0b60b384cbc79621b6b2176fca086069e8e/meilisearch/src/routes/indexes/settings.rs#L141-L165)
|
||||||
|
- [ ] global `/settings` route configurated in the [`update_all` function](https://github.com/meilisearch/meilisearch/blob/5204c0b60b384cbc79621b6b2176fca086069e8e/meilisearch/src/routes/indexes/settings.rs#L655-L751)
|
||||||
|
- [ ] Ensure the dump serializing is consistent with the `/settings` route serializing, e.g., enums case can be different (`camelCase` in route and `PascalCase` in the dump)
|
||||||
|
|
||||||
|
#### Special cases when adding a setting for an experimental feature
|
||||||
|
|
||||||
|
- [ ] ⚠️ API stability: The setting does not appear on the main settings route when the feature has never been enabled (e.g. mark it `Unset` when returned from the index in this situation. See [an example](https://github.com/meilisearch/meilisearch/blob/7a89abd2a025606a42f8b219e539117eb2eb029f/meilisearch-types/src/settings.rs#L608))
|
||||||
|
- [ ] The setting cannot be set when the feature is disabled, either by the main settings route or the subroute (see [`validate_settings` function](https://github.com/meilisearch/meilisearch/blob/7a89abd2a025606a42f8b219e539117eb2eb029f/meilisearch/src/routes/indexes/settings.rs#L811))
|
||||||
|
- [ ] If possible, the setting is reset when the feature is disabled (hard if it requires reindexing)
|
||||||
|
|
||||||
## Impacted teams
|
## Impacted teams
|
||||||
|
|
||||||
<!---Ping the related teams. Ask for the engine manager if any hesitation-->
|
<!---Ping the related teams. Ask for the engine manager if any hesitation-->
|
||||||
|
<!---@meilisearch/docs-team when there is any API change, e.g. settings addition-->
|
||||||
|
|||||||
2
.github/workflows/publish-docker-images.yml
vendored
2
.github/workflows/publish-docker-images.yml
vendored
@@ -97,7 +97,7 @@ jobs:
|
|||||||
- name: Send CI information to Cloud team
|
- name: Send CI information to Cloud team
|
||||||
# Do not send if nightly build (i.e. 'schedule' or 'workflow_dispatch' event)
|
# Do not send if nightly build (i.e. 'schedule' or 'workflow_dispatch' event)
|
||||||
if: github.event_name == 'push'
|
if: github.event_name == 'push'
|
||||||
uses: peter-evans/repository-dispatch@v2
|
uses: peter-evans/repository-dispatch@v3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.MEILI_BOT_GH_PAT }}
|
token: ${{ secrets.MEILI_BOT_GH_PAT }}
|
||||||
repository: meilisearch/meilisearch-cloud
|
repository: meilisearch/meilisearch-cloud
|
||||||
|
|||||||
38
.github/workflows/sdks-tests.yml
vendored
38
.github/workflows/sdks-tests.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
docker-image: ${{ steps.define-image.outputs.docker-image }}
|
docker-image: ${{ steps.define-image.outputs.docker-image }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Define the Docker image we need to use
|
- name: Define the Docker image we need to use
|
||||||
id: define-image
|
id: define-image
|
||||||
run: |
|
run: |
|
||||||
@@ -46,11 +46,11 @@ jobs:
|
|||||||
MEILISEARCH_VERSION: ${{ needs.define-docker-image.outputs.docker-image }}
|
MEILISEARCH_VERSION: ${{ needs.define-docker-image.outputs.docker-image }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: meilisearch/meilisearch-dotnet
|
repository: meilisearch/meilisearch-dotnet
|
||||||
- name: Setup .NET Core
|
- name: Setup .NET Core
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: "6.0.x"
|
dotnet-version: "6.0.x"
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@@ -75,12 +75,12 @@ jobs:
|
|||||||
ports:
|
ports:
|
||||||
- '7700:7700'
|
- '7700:7700'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: meilisearch/meilisearch-dart
|
repository: meilisearch/meilisearch-dart
|
||||||
- uses: dart-lang/setup-dart@v1
|
- uses: dart-lang/setup-dart@v1
|
||||||
with:
|
with:
|
||||||
sdk: 3.1.1
|
sdk: 'latest'
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: dart pub get
|
run: dart pub get
|
||||||
- name: Run integration tests
|
- name: Run integration tests
|
||||||
@@ -100,10 +100,10 @@ jobs:
|
|||||||
- '7700:7700'
|
- '7700:7700'
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: stable
|
go-version: stable
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: meilisearch/meilisearch-go
|
repository: meilisearch/meilisearch-go
|
||||||
- name: Get dependencies
|
- name: Get dependencies
|
||||||
@@ -129,11 +129,11 @@ jobs:
|
|||||||
ports:
|
ports:
|
||||||
- '7700:7700'
|
- '7700:7700'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: meilisearch/meilisearch-java
|
repository: meilisearch/meilisearch-java
|
||||||
- name: Set up Java
|
- name: Set up Java
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
java-version: 8
|
java-version: 8
|
||||||
distribution: 'zulu'
|
distribution: 'zulu'
|
||||||
@@ -156,7 +156,7 @@ jobs:
|
|||||||
ports:
|
ports:
|
||||||
- '7700:7700'
|
- '7700:7700'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: meilisearch/meilisearch-js
|
repository: meilisearch/meilisearch-js
|
||||||
- name: Setup node
|
- name: Setup node
|
||||||
@@ -191,7 +191,7 @@ jobs:
|
|||||||
ports:
|
ports:
|
||||||
- '7700:7700'
|
- '7700:7700'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: meilisearch/meilisearch-php
|
repository: meilisearch/meilisearch-php
|
||||||
- name: Install PHP
|
- name: Install PHP
|
||||||
@@ -220,11 +220,11 @@ jobs:
|
|||||||
ports:
|
ports:
|
||||||
- '7700:7700'
|
- '7700:7700'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: meilisearch/meilisearch-python
|
repository: meilisearch/meilisearch-python
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v5
|
||||||
- name: Install pipenv
|
- name: Install pipenv
|
||||||
uses: dschep/install-pipenv-action@v1
|
uses: dschep/install-pipenv-action@v1
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@@ -245,7 +245,7 @@ jobs:
|
|||||||
ports:
|
ports:
|
||||||
- '7700:7700'
|
- '7700:7700'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: meilisearch/meilisearch-ruby
|
repository: meilisearch/meilisearch-ruby
|
||||||
- name: Set up Ruby 3
|
- name: Set up Ruby 3
|
||||||
@@ -270,7 +270,7 @@ jobs:
|
|||||||
ports:
|
ports:
|
||||||
- '7700:7700'
|
- '7700:7700'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: meilisearch/meilisearch-rust
|
repository: meilisearch/meilisearch-rust
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -291,7 +291,7 @@ jobs:
|
|||||||
ports:
|
ports:
|
||||||
- '7700:7700'
|
- '7700:7700'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: meilisearch/meilisearch-swift
|
repository: meilisearch/meilisearch-swift
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
@@ -314,7 +314,7 @@ jobs:
|
|||||||
ports:
|
ports:
|
||||||
- '7700:7700'
|
- '7700:7700'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: meilisearch/meilisearch-js-plugins
|
repository: meilisearch/meilisearch-js-plugins
|
||||||
- name: Setup node
|
- name: Setup node
|
||||||
@@ -345,7 +345,7 @@ jobs:
|
|||||||
ports:
|
ports:
|
||||||
- '7700:7700'
|
- '7700:7700'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: meilisearch/meilisearch-rails
|
repository: meilisearch/meilisearch-rails
|
||||||
- name: Set up Ruby 3
|
- name: Set up Ruby 3
|
||||||
@@ -369,7 +369,7 @@ jobs:
|
|||||||
ports:
|
ports:
|
||||||
- '7700:7700'
|
- '7700:7700'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: meilisearch/meilisearch-symfony
|
repository: meilisearch/meilisearch-symfony
|
||||||
- name: Install PHP
|
- name: Install PHP
|
||||||
|
|||||||
24
.github/workflows/test-suite.yml
vendored
24
.github/workflows/test-suite.yml
vendored
@@ -66,6 +66,10 @@ jobs:
|
|||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Cache dependencies
|
- name: Cache dependencies
|
||||||
uses: Swatinem/rust-cache@v2.7.1
|
uses: Swatinem/rust-cache@v2.7.1
|
||||||
|
- uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
override: true
|
||||||
- name: Run cargo check without any default features
|
- name: Run cargo check without any default features
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
@@ -78,7 +82,7 @@ jobs:
|
|||||||
args: --locked --release --all
|
args: --locked --release --all
|
||||||
|
|
||||||
test-all-features:
|
test-all-features:
|
||||||
name: Tests all features
|
name: Tests almost all features
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
# Use ubuntu-18.04 to compile with glibc 2.27, which are the production expectations
|
# Use ubuntu-18.04 to compile with glibc 2.27, which are the production expectations
|
||||||
@@ -94,16 +98,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
override: true
|
override: true
|
||||||
- name: Run cargo build with all features
|
- name: Run cargo build with almost all features
|
||||||
uses: actions-rs/cargo@v1
|
run: |
|
||||||
with:
|
cargo build --workspace --locked --release --features "$(cargo xtask list-features --exclude-feature cuda)"
|
||||||
command: build
|
- name: Run cargo test with almost all features
|
||||||
args: --workspace --locked --release --all-features
|
run: |
|
||||||
- name: Run cargo test with all features
|
cargo test --workspace --locked --release --features "$(cargo xtask list-features --exclude-feature cuda)"
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: test
|
|
||||||
args: --workspace --locked --release --all-features
|
|
||||||
|
|
||||||
test-disabled-tokenization:
|
test-disabled-tokenization:
|
||||||
name: Test disabled tokenization
|
name: Test disabled tokenization
|
||||||
@@ -164,7 +164,7 @@ jobs:
|
|||||||
- uses: actions-rs/toolchain@v1
|
- uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
toolchain: 1.71.1
|
toolchain: 1.75.0
|
||||||
override: true
|
override: true
|
||||||
components: clippy
|
components: clippy
|
||||||
- name: Cache dependencies
|
- name: Cache dependencies
|
||||||
|
|||||||
@@ -75,6 +75,12 @@ If you get a "Too many open files" error you might want to increase the open fil
|
|||||||
ulimit -Sn 3000
|
ulimit -Sn 3000
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Build tools
|
||||||
|
|
||||||
|
Meilisearch follows the [cargo xtask](https://github.com/matklad/cargo-xtask) workflow to provide some build tools.
|
||||||
|
|
||||||
|
Run `cargo xtask --help` from the root of the repository to find out what is available.
|
||||||
|
|
||||||
## Git Guidelines
|
## Git Guidelines
|
||||||
|
|
||||||
### Git Branches
|
### Git Branches
|
||||||
|
|||||||
1264
Cargo.lock
generated
1264
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -16,11 +16,16 @@ members = [
|
|||||||
"json-depth-checker",
|
"json-depth-checker",
|
||||||
"benchmarks",
|
"benchmarks",
|
||||||
"fuzzers",
|
"fuzzers",
|
||||||
|
"tracing-trace",
|
||||||
|
"xtask",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "1.6.0"
|
version = "1.6.1"
|
||||||
authors = ["Quentin de Quelen <quentin@dequelen.me>", "Clément Renault <clement@meilisearch.com>"]
|
authors = [
|
||||||
|
"Quentin de Quelen <quentin@dequelen.me>",
|
||||||
|
"Clément Renault <clement@meilisearch.com>",
|
||||||
|
]
|
||||||
description = "Meilisearch HTTP server"
|
description = "Meilisearch HTTP server"
|
||||||
homepage = "https://meilisearch.com"
|
homepage = "https://meilisearch.com"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Compile
|
# Compile
|
||||||
FROM rust:1.71.1-alpine3.18 AS compiler
|
FROM rust:1.75.0-alpine3.18 AS compiler
|
||||||
|
|
||||||
RUN apk add -q --update-cache --no-cache build-base openssl-dev
|
RUN apk add -q --update-cache --no-cache build-base openssl-dev
|
||||||
|
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2019-2022 Meili SAS
|
Copyright (c) 2019-2024 Meili SAS
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
10
README.md
10
README.md
@@ -41,10 +41,10 @@ Meilisearch helps you shape a delightful search experience in a snap, offering f
|
|||||||
## ✨ Features
|
## ✨ Features
|
||||||
|
|
||||||
- **Search-as-you-type:** find search results in less than 50 milliseconds
|
- **Search-as-you-type:** find search results in less than 50 milliseconds
|
||||||
- **[Typo tolerance](https://www.meilisearch.com/docs/learn/getting_started/customizing_relevancy?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=features#typo-tolerance):** get relevant matches even when queries contain typos and misspellings
|
- **[Typo tolerance](https://www.meilisearch.com/docs/learn/configuration/typo_tolerance?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=features):** get relevant matches even when queries contain typos and misspellings
|
||||||
- **[Filtering](https://www.meilisearch.com/docs/learn/fine_tuning_results/filtering?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=features) and [faceted search](https://www.meilisearch.com/docs/learn/fine_tuning_results/faceted_search?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=features):** enhance your user's search experience with custom filters and build a faceted search interface in a few lines of code
|
- **[Filtering](https://www.meilisearch.com/docs/learn/fine_tuning_results/filtering?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=features) and [faceted search](https://www.meilisearch.com/docs/learn/fine_tuning_results/faceted_search?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=features):** enhance your users' search experience with custom filters and build a faceted search interface in a few lines of code
|
||||||
- **[Sorting](https://www.meilisearch.com/docs/learn/fine_tuning_results/sorting?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=features):** sort results based on price, date, or pretty much anything else your users need
|
- **[Sorting](https://www.meilisearch.com/docs/learn/fine_tuning_results/sorting?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=features):** sort results based on price, date, or pretty much anything else your users need
|
||||||
- **[Synonym support](https://www.meilisearch.com/docs/learn/getting_started/customizing_relevancy?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=features#synonyms):** configure synonyms to include more relevant content in your search results
|
- **[Synonym support](https://www.meilisearch.com/docs/learn/configuration/synonyms?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=features):** configure synonyms to include more relevant content in your search results
|
||||||
- **[Geosearch](https://www.meilisearch.com/docs/learn/fine_tuning_results/geosearch?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=features):** filter and sort documents based on geographic data
|
- **[Geosearch](https://www.meilisearch.com/docs/learn/fine_tuning_results/geosearch?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=features):** filter and sort documents based on geographic data
|
||||||
- **[Extensive language support](https://www.meilisearch.com/docs/learn/what_is_meilisearch/language?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=features):** search datasets in any language, with optimized support for Chinese, Japanese, Hebrew, and languages using the Latin alphabet
|
- **[Extensive language support](https://www.meilisearch.com/docs/learn/what_is_meilisearch/language?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=features):** search datasets in any language, with optimized support for Chinese, Japanese, Hebrew, and languages using the Latin alphabet
|
||||||
- **[Security management](https://www.meilisearch.com/docs/learn/security/master_api_keys?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=features):** control which users can access what data with API keys that allow fine-grained permissions handling
|
- **[Security management](https://www.meilisearch.com/docs/learn/security/master_api_keys?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=features):** control which users can access what data with API keys that allow fine-grained permissions handling
|
||||||
@@ -61,8 +61,6 @@ You can consult Meilisearch's documentation at [https://www.meilisearch.com/docs
|
|||||||
|
|
||||||
For basic instructions on how to set up Meilisearch, add documents to an index, and search for documents, take a look at our [Quick Start](https://www.meilisearch.com/docs/learn/getting_started/quick_start?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=get-started) guide.
|
For basic instructions on how to set up Meilisearch, add documents to an index, and search for documents, take a look at our [Quick Start](https://www.meilisearch.com/docs/learn/getting_started/quick_start?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=get-started) guide.
|
||||||
|
|
||||||
You may also want to check out [Meilisearch 101](https://www.meilisearch.com/docs/learn/getting_started/filtering_and_sorting?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=get-started) for an introduction to some of Meilisearch's most popular features.
|
|
||||||
|
|
||||||
## ⚡ Supercharge your Meilisearch experience
|
## ⚡ Supercharge your Meilisearch experience
|
||||||
|
|
||||||
Say goodbye to server deployment and manual updates with [Meilisearch Cloud](https://www.meilisearch.com/cloud?utm_campaign=oss&utm_source=github&utm_medium=meilisearch). No credit card required.
|
Say goodbye to server deployment and manual updates with [Meilisearch Cloud](https://www.meilisearch.com/cloud?utm_campaign=oss&utm_source=github&utm_medium=meilisearch). No credit card required.
|
||||||
@@ -101,7 +99,7 @@ Meilisearch is a search engine created by [Meili](https://www.welcometothejungle
|
|||||||
|
|
||||||
- For feature requests, please visit our [product repository](https://github.com/meilisearch/product/discussions)
|
- For feature requests, please visit our [product repository](https://github.com/meilisearch/product/discussions)
|
||||||
- Found a bug? Open an [issue](https://github.com/meilisearch/meilisearch/issues)!
|
- Found a bug? Open an [issue](https://github.com/meilisearch/meilisearch/issues)!
|
||||||
- Want to be part of our Discord community? [Join us!](https://discord.gg/meilisearch)
|
- Want to be part of our Discord community? [Join us!](https://discord.meilisearch.com/?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=contact)
|
||||||
|
|
||||||
Thank you for your support!
|
Thank you for your support!
|
||||||
|
|
||||||
|
|||||||
@@ -106,7 +106,7 @@
|
|||||||
},
|
},
|
||||||
"editorMode": "builder",
|
"editorMode": "builder",
|
||||||
"exemplar": true,
|
"exemplar": true,
|
||||||
"expr": "meilisearch_index_count{job=\"meilisearch\", instance=\"$instance\"}",
|
"expr": "meilisearch_index_count{job=\"$job\", instance=\"$instance\"}",
|
||||||
"interval": "",
|
"interval": "",
|
||||||
"legendFormat": "",
|
"legendFormat": "",
|
||||||
"range": true,
|
"range": true,
|
||||||
@@ -165,7 +165,7 @@
|
|||||||
"type": "prometheus"
|
"type": "prometheus"
|
||||||
},
|
},
|
||||||
"editorMode": "builder",
|
"editorMode": "builder",
|
||||||
"expr": "meilisearch_index_docs_count{job=\"meilisearch\", index=\"$Index\", instance=\"$instance\"}",
|
"expr": "meilisearch_index_docs_count{job=\"$job\", index=\"$Index\", instance=\"$instance\"}",
|
||||||
"hide": false,
|
"hide": false,
|
||||||
"range": true,
|
"range": true,
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
@@ -228,7 +228,7 @@
|
|||||||
},
|
},
|
||||||
"editorMode": "builder",
|
"editorMode": "builder",
|
||||||
"exemplar": true,
|
"exemplar": true,
|
||||||
"expr": "round(increase(meilisearch_http_requests_total{method=\"POST\", path=\"/indexes/$Index/search\", job=\"meilisearch\"}[1h]))",
|
"expr": "round(increase(meilisearch_http_requests_total{method=\"POST\", path=\"/indexes/$Index/search\", job=\"$job\"}[1h]))",
|
||||||
"interval": "",
|
"interval": "",
|
||||||
"legendFormat": "",
|
"legendFormat": "",
|
||||||
"range": true,
|
"range": true,
|
||||||
@@ -288,7 +288,7 @@
|
|||||||
},
|
},
|
||||||
"editorMode": "builder",
|
"editorMode": "builder",
|
||||||
"exemplar": true,
|
"exemplar": true,
|
||||||
"expr": "round(increase(meilisearch_http_requests_total{method=\"POST\", path=\"/indexes/$Index/search\", job=\"meilisearch\"}[24h]))",
|
"expr": "round(increase(meilisearch_http_requests_total{method=\"POST\", path=\"/indexes/$Index/search\", job=\"$job\"}[24h]))",
|
||||||
"interval": "",
|
"interval": "",
|
||||||
"legendFormat": "",
|
"legendFormat": "",
|
||||||
"range": true,
|
"range": true,
|
||||||
@@ -348,7 +348,7 @@
|
|||||||
},
|
},
|
||||||
"editorMode": "builder",
|
"editorMode": "builder",
|
||||||
"exemplar": true,
|
"exemplar": true,
|
||||||
"expr": "round(increase(meilisearch_http_requests_total{method=\"POST\", path=\"/indexes/$Index/search\", job=\"meilisearch\"}[30d]))",
|
"expr": "round(increase(meilisearch_http_requests_total{method=\"POST\", path=\"/indexes/$Index/search\", job=\"$job\"}[30d]))",
|
||||||
"interval": "",
|
"interval": "",
|
||||||
"legendFormat": "",
|
"legendFormat": "",
|
||||||
"range": true,
|
"range": true,
|
||||||
@@ -447,7 +447,7 @@
|
|||||||
},
|
},
|
||||||
"editorMode": "builder",
|
"editorMode": "builder",
|
||||||
"exemplar": true,
|
"exemplar": true,
|
||||||
"expr": "meilisearch_db_size_bytes{job=\"meilisearch\", instance=\"$instance\"}",
|
"expr": "meilisearch_db_size_bytes{job=\"$job\", instance=\"$instance\"}",
|
||||||
"interval": "",
|
"interval": "",
|
||||||
"legendFormat": "Database size on disk",
|
"legendFormat": "Database size on disk",
|
||||||
"range": true,
|
"range": true,
|
||||||
@@ -458,7 +458,7 @@
|
|||||||
"type": "prometheus"
|
"type": "prometheus"
|
||||||
},
|
},
|
||||||
"editorMode": "builder",
|
"editorMode": "builder",
|
||||||
"expr": "meilisearch_used_db_size_bytes{job=\"meilisearch\", instance=\"$instance\"}",
|
"expr": "meilisearch_used_db_size_bytes{job=\"$job\", instance=\"$instance\"}",
|
||||||
"hide": false,
|
"hide": false,
|
||||||
"legendFormat": "Used bytes",
|
"legendFormat": "Used bytes",
|
||||||
"range": true,
|
"range": true,
|
||||||
@@ -553,7 +553,7 @@
|
|||||||
},
|
},
|
||||||
"editorMode": "builder",
|
"editorMode": "builder",
|
||||||
"exemplar": true,
|
"exemplar": true,
|
||||||
"expr": "rate(meilisearch_http_response_time_seconds_sum{instance=\"$instance\", job=\"meilisearch\"}[5m]) / rate(meilisearch_http_response_time_seconds_count[5m])",
|
"expr": "rate(meilisearch_http_response_time_seconds_sum{instance=\"$instance\", job=\"$job\"}[5m]) / rate(meilisearch_http_response_time_seconds_count[5m])",
|
||||||
"interval": "",
|
"interval": "",
|
||||||
"legendFormat": "{{method}} {{path}}",
|
"legendFormat": "{{method}} {{path}}",
|
||||||
"range": true,
|
"range": true,
|
||||||
@@ -646,7 +646,7 @@
|
|||||||
},
|
},
|
||||||
"editorMode": "builder",
|
"editorMode": "builder",
|
||||||
"exemplar": true,
|
"exemplar": true,
|
||||||
"expr": "rate(meilisearch_http_requests_total{instance=\"$instance\", job=\"meilisearch\"}[5m])",
|
"expr": "rate(meilisearch_http_requests_total{instance=\"$instance\", job=\"$job\"}[5m])",
|
||||||
"interval": "",
|
"interval": "",
|
||||||
"legendFormat": "{{method}} {{path}}",
|
"legendFormat": "{{method}} {{path}}",
|
||||||
"range": true,
|
"range": true,
|
||||||
@@ -744,7 +744,7 @@
|
|||||||
},
|
},
|
||||||
"editorMode": "builder",
|
"editorMode": "builder",
|
||||||
"exemplar": true,
|
"exemplar": true,
|
||||||
"expr": "sum by(le) (increase(meilisearch_http_response_time_seconds_bucket{path=\"/indexes/$Index/search\", instance=\"$instance\", job=\"meilisearch\"}[30s]))",
|
"expr": "sum by(le) (increase(meilisearch_http_response_time_seconds_bucket{path=\"/indexes/$Index/search\", instance=\"$instance\", job=\"$job\"}[30s]))",
|
||||||
"format": "heatmap",
|
"format": "heatmap",
|
||||||
"interval": "",
|
"interval": "",
|
||||||
"legendFormat": "{{le}}",
|
"legendFormat": "{{le}}",
|
||||||
@@ -854,7 +854,7 @@
|
|||||||
},
|
},
|
||||||
"editorMode": "builder",
|
"editorMode": "builder",
|
||||||
"exemplar": true,
|
"exemplar": true,
|
||||||
"expr": "meilisearch_nb_tasks{instance=\"$instance\", job=\"meilisearch\", kind=\"statuses\"}",
|
"expr": "meilisearch_nb_tasks{instance=\"$instance\", job=\"$job\", kind=\"statuses\"}",
|
||||||
"interval": "",
|
"interval": "",
|
||||||
"legendFormat": "{{value}} ",
|
"legendFormat": "{{value}} ",
|
||||||
"range": true,
|
"range": true,
|
||||||
@@ -947,7 +947,7 @@
|
|||||||
},
|
},
|
||||||
"editorMode": "builder",
|
"editorMode": "builder",
|
||||||
"exemplar": true,
|
"exemplar": true,
|
||||||
"expr": "meilisearch_nb_tasks{instance=\"$instance\", job=\"meilisearch\", kind=\"types\"}",
|
"expr": "meilisearch_nb_tasks{instance=\"$instance\", job=\"$job\", kind=\"types\"}",
|
||||||
"interval": "",
|
"interval": "",
|
||||||
"legendFormat": "{{value}} ",
|
"legendFormat": "{{value}} ",
|
||||||
"range": true,
|
"range": true,
|
||||||
@@ -1040,7 +1040,7 @@
|
|||||||
},
|
},
|
||||||
"editorMode": "builder",
|
"editorMode": "builder",
|
||||||
"exemplar": true,
|
"exemplar": true,
|
||||||
"expr": "meilisearch_nb_tasks{instance=\"$instance\", job=\"meilisearch\", kind=\"indexes\"}",
|
"expr": "meilisearch_nb_tasks{instance=\"$instance\", job=\"$job\", kind=\"indexes\"}",
|
||||||
"interval": "",
|
"interval": "",
|
||||||
"legendFormat": "{{value}} ",
|
"legendFormat": "{{value}} ",
|
||||||
"range": true,
|
"range": true,
|
||||||
@@ -1161,7 +1161,7 @@
|
|||||||
},
|
},
|
||||||
"editorMode": "builder",
|
"editorMode": "builder",
|
||||||
"exemplar": true,
|
"exemplar": true,
|
||||||
"expr": "rate(process_cpu_seconds_total{job=\"meilisearch\", instance=\"$instance\"}[1m])",
|
"expr": "rate(process_cpu_seconds_total{job=\"$job\", instance=\"$instance\"}[1m])",
|
||||||
"interval": "",
|
"interval": "",
|
||||||
"legendFormat": "process",
|
"legendFormat": "process",
|
||||||
"range": true,
|
"range": true,
|
||||||
@@ -1264,7 +1264,7 @@
|
|||||||
},
|
},
|
||||||
"editorMode": "builder",
|
"editorMode": "builder",
|
||||||
"exemplar": true,
|
"exemplar": true,
|
||||||
"expr": "process_resident_memory_bytes{job=\"meilisearch\", instance=\"$instance\"} / 1024 / 1024",
|
"expr": "process_resident_memory_bytes{job=\"$job\", instance=\"$instance\"} / 1024 / 1024",
|
||||||
"interval": "",
|
"interval": "",
|
||||||
"legendFormat": "process",
|
"legendFormat": "process",
|
||||||
"range": true,
|
"range": true,
|
||||||
@@ -1342,6 +1342,33 @@
|
|||||||
"skipUrlSync": false,
|
"skipUrlSync": false,
|
||||||
"sort": 0,
|
"sort": 0,
|
||||||
"type": "query"
|
"type": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"current": {
|
||||||
|
"selected": true,
|
||||||
|
"text": "meilisearch",
|
||||||
|
"value": "meilisearch"
|
||||||
|
},
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus"
|
||||||
|
},
|
||||||
|
"definition": "label_values(job)",
|
||||||
|
"description": "Prometheus job_name from scrape config (default is meilisearch)",
|
||||||
|
"hide": 0,
|
||||||
|
"includeAll": false,
|
||||||
|
"label": "Job",
|
||||||
|
"multi": false,
|
||||||
|
"name": "job",
|
||||||
|
"options": [],
|
||||||
|
"query": {
|
||||||
|
"query": "label_values(job)",
|
||||||
|
"refId": "StandardVariableQuery"
|
||||||
|
},
|
||||||
|
"refresh": 1,
|
||||||
|
"regex": "",
|
||||||
|
"skipUrlSync": false,
|
||||||
|
"sort": 0,
|
||||||
|
"type": "query"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,24 +11,24 @@ edition.workspace = true
|
|||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.70"
|
anyhow = "1.0.79"
|
||||||
csv = "1.2.1"
|
csv = "1.3.0"
|
||||||
milli = { path = "../milli" }
|
milli = { path = "../milli" }
|
||||||
mimalloc = { version = "0.1.37", default-features = false }
|
mimalloc = { version = "0.1.39", default-features = false }
|
||||||
serde_json = { version = "1.0.95", features = ["preserve_order"] }
|
serde_json = { version = "1.0.111", features = ["preserve_order"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = { version = "0.5.1", features = ["html_reports"] }
|
criterion = { version = "0.5.1", features = ["html_reports"] }
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
rand_chacha = "0.3.1"
|
rand_chacha = "0.3.1"
|
||||||
roaring = "0.10.1"
|
roaring = "0.10.2"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
anyhow = "1.0.70"
|
anyhow = "1.0.79"
|
||||||
bytes = "1.4.0"
|
bytes = "1.5.0"
|
||||||
convert_case = "0.6.0"
|
convert_case = "0.6.0"
|
||||||
flate2 = "1.0.25"
|
flate2 = "1.0.28"
|
||||||
reqwest = { version = "0.11.16", features = ["blocking", "rustls-tls"], default-features = false }
|
reqwest = { version = "0.11.23", features = ["blocking", "rustls-tls"], default-features = false }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["milli/all-tokenizations"]
|
default = ["milli/all-tokenizations"]
|
||||||
|
|||||||
@@ -11,22 +11,22 @@ readme.workspace = true
|
|||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.70"
|
anyhow = "1.0.79"
|
||||||
flate2 = "1.0.25"
|
flate2 = "1.0.28"
|
||||||
http = "0.2.9"
|
http = "0.2.11"
|
||||||
log = "0.4.17"
|
|
||||||
meilisearch-auth = { path = "../meilisearch-auth" }
|
meilisearch-auth = { path = "../meilisearch-auth" }
|
||||||
meilisearch-types = { path = "../meilisearch-types" }
|
meilisearch-types = { path = "../meilisearch-types" }
|
||||||
once_cell = "1.17.1"
|
once_cell = "1.19.0"
|
||||||
regex = "1.7.3"
|
regex = "1.10.2"
|
||||||
roaring = { version = "0.10.1", features = ["serde"] }
|
roaring = { version = "0.10.2", features = ["serde"] }
|
||||||
serde = { version = "1.0.160", features = ["derive"] }
|
serde = { version = "1.0.195", features = ["derive"] }
|
||||||
serde_json = { version = "1.0.95", features = ["preserve_order"] }
|
serde_json = { version = "1.0.111", features = ["preserve_order"] }
|
||||||
tar = "0.4.38"
|
tar = "0.4.40"
|
||||||
tempfile = "3.5.0"
|
tempfile = "3.9.0"
|
||||||
thiserror = "1.0.40"
|
thiserror = "1.0.56"
|
||||||
time = { version = "0.3.20", features = ["serde-well-known", "formatting", "parsing", "macros"] }
|
time = { version = "0.3.31", features = ["serde-well-known", "formatting", "parsing", "macros"] }
|
||||||
uuid = { version = "1.3.1", features = ["serde", "v4"] }
|
tracing = "0.1.40"
|
||||||
|
uuid = { version = "1.6.1", features = ["serde", "v4"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
big_s = "1.0.2"
|
big_s = "1.0.2"
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ impl From<v1::settings::Settings> for v2::Settings<v2::Unchecked> {
|
|||||||
criterion.as_ref().map(ToString::to_string)
|
criterion.as_ref().map(ToString::to_string)
|
||||||
}
|
}
|
||||||
Err(()) => {
|
Err(()) => {
|
||||||
log::warn!(
|
tracing::warn!(
|
||||||
"Could not import the following ranking rule: `{}`.",
|
"Could not import the following ranking rule: `{}`.",
|
||||||
ranking_rule
|
ranking_rule
|
||||||
);
|
);
|
||||||
@@ -152,11 +152,11 @@ impl From<v1::update::UpdateStatus> for Option<v2::updates::UpdateStatus> {
|
|||||||
use v2::updates::UpdateStatus as UpdateStatusV2;
|
use v2::updates::UpdateStatus as UpdateStatusV2;
|
||||||
Some(match source {
|
Some(match source {
|
||||||
UpdateStatusV1::Enqueued { content } => {
|
UpdateStatusV1::Enqueued { content } => {
|
||||||
log::warn!(
|
tracing::warn!(
|
||||||
"Cannot import task {} (importing enqueued tasks from v1 dumps is unsupported)",
|
"Cannot import task {} (importing enqueued tasks from v1 dumps is unsupported)",
|
||||||
content.update_id
|
content.update_id
|
||||||
);
|
);
|
||||||
log::warn!("Task will be skipped in the queue of imported tasks.");
|
tracing::warn!("Task will be skipped in the queue of imported tasks.");
|
||||||
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -229,7 +229,7 @@ impl From<v1::update::UpdateType> for Option<v2::updates::UpdateMeta> {
|
|||||||
Some(match source {
|
Some(match source {
|
||||||
v1::update::UpdateType::ClearAll => v2::updates::UpdateMeta::ClearDocuments,
|
v1::update::UpdateType::ClearAll => v2::updates::UpdateMeta::ClearDocuments,
|
||||||
v1::update::UpdateType::Customs => {
|
v1::update::UpdateType::Customs => {
|
||||||
log::warn!("Ignoring task with type 'Customs' that is no longer supported");
|
tracing::warn!("Ignoring task with type 'Customs' that is no longer supported");
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
v1::update::UpdateType::DocumentsAddition { .. } => {
|
v1::update::UpdateType::DocumentsAddition { .. } => {
|
||||||
@@ -296,7 +296,7 @@ impl From<v1::settings::RankingRule> for Option<v2::settings::Criterion> {
|
|||||||
v1::settings::RankingRule::Proximity => Some(v2::settings::Criterion::Proximity),
|
v1::settings::RankingRule::Proximity => Some(v2::settings::Criterion::Proximity),
|
||||||
v1::settings::RankingRule::Attribute => Some(v2::settings::Criterion::Attribute),
|
v1::settings::RankingRule::Attribute => Some(v2::settings::Criterion::Attribute),
|
||||||
v1::settings::RankingRule::WordsPosition => {
|
v1::settings::RankingRule::WordsPosition => {
|
||||||
log::warn!("Removing the 'WordsPosition' ranking rule that is no longer supported, please check the resulting ranking rules of your indexes");
|
tracing::warn!("Removing the 'WordsPosition' ranking rule that is no longer supported, please check the resulting ranking rules of your indexes");
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
v1::settings::RankingRule::Exactness => Some(v2::settings::Criterion::Exactness),
|
v1::settings::RankingRule::Exactness => Some(v2::settings::Criterion::Exactness),
|
||||||
|
|||||||
@@ -146,8 +146,8 @@ impl From<v2::updates::UpdateStatus> for v3::updates::UpdateStatus {
|
|||||||
started_processing_at: processing.started_processing_at,
|
started_processing_at: processing.started_processing_at,
|
||||||
}),
|
}),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::warn!("Error with task {}: {}", processing.from.update_id, e);
|
tracing::warn!("Error with task {}: {}", processing.from.update_id, e);
|
||||||
log::warn!("Task will be marked as `Failed`.");
|
tracing::warn!("Task will be marked as `Failed`.");
|
||||||
v3::updates::UpdateStatus::Failed(v3::updates::Failed {
|
v3::updates::UpdateStatus::Failed(v3::updates::Failed {
|
||||||
from: v3::updates::Processing {
|
from: v3::updates::Processing {
|
||||||
from: v3::updates::Enqueued {
|
from: v3::updates::Enqueued {
|
||||||
@@ -172,8 +172,8 @@ impl From<v2::updates::UpdateStatus> for v3::updates::UpdateStatus {
|
|||||||
enqueued_at: enqueued.enqueued_at,
|
enqueued_at: enqueued.enqueued_at,
|
||||||
}),
|
}),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::warn!("Error with task {}: {}", enqueued.update_id, e);
|
tracing::warn!("Error with task {}: {}", enqueued.update_id, e);
|
||||||
log::warn!("Task will be marked as `Failed`.");
|
tracing::warn!("Task will be marked as `Failed`.");
|
||||||
v3::updates::UpdateStatus::Failed(v3::updates::Failed {
|
v3::updates::UpdateStatus::Failed(v3::updates::Failed {
|
||||||
from: v3::updates::Processing {
|
from: v3::updates::Processing {
|
||||||
from: v3::updates::Enqueued {
|
from: v3::updates::Enqueued {
|
||||||
@@ -353,7 +353,7 @@ impl From<String> for v3::Code {
|
|||||||
"malformed_payload" => v3::Code::MalformedPayload,
|
"malformed_payload" => v3::Code::MalformedPayload,
|
||||||
"missing_payload" => v3::Code::MissingPayload,
|
"missing_payload" => v3::Code::MissingPayload,
|
||||||
other => {
|
other => {
|
||||||
log::warn!("Unknown error code {}", other);
|
tracing::warn!("Unknown error code {}", other);
|
||||||
v3::Code::UnretrievableErrorCode
|
v3::Code::UnretrievableErrorCode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,20 +76,20 @@ impl CompatV3ToV4 {
|
|||||||
let index_uid = match index_uid {
|
let index_uid = match index_uid {
|
||||||
Some(uid) => uid,
|
Some(uid) => uid,
|
||||||
None => {
|
None => {
|
||||||
log::warn!(
|
tracing::warn!(
|
||||||
"Error while importing the update {}.",
|
"Error while importing the update {}.",
|
||||||
task.update.id()
|
task.update.id()
|
||||||
);
|
);
|
||||||
log::warn!(
|
tracing::warn!(
|
||||||
"The index associated to the uuid `{}` could not be retrieved.",
|
"The index associated to the uuid `{}` could not be retrieved.",
|
||||||
task.uuid.to_string()
|
task.uuid.to_string()
|
||||||
);
|
);
|
||||||
if task.update.is_finished() {
|
if task.update.is_finished() {
|
||||||
// we're fucking with his history but not his data, that's ok-ish.
|
// we're fucking with his history but not his data, that's ok-ish.
|
||||||
log::warn!("The index-uuid will be set as `unknown`.");
|
tracing::warn!("The index-uuid will be set as `unknown`.");
|
||||||
String::from("unknown")
|
String::from("unknown")
|
||||||
} else {
|
} else {
|
||||||
log::warn!("The task will be ignored.");
|
tracing::warn!("The task will be ignored.");
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -305,7 +305,7 @@ impl From<v4::ResponseError> for v5::ResponseError {
|
|||||||
"invalid_api_key_expires_at" => v5::Code::InvalidApiKeyExpiresAt,
|
"invalid_api_key_expires_at" => v5::Code::InvalidApiKeyExpiresAt,
|
||||||
"invalid_api_key_description" => v5::Code::InvalidApiKeyDescription,
|
"invalid_api_key_description" => v5::Code::InvalidApiKeyDescription,
|
||||||
other => {
|
other => {
|
||||||
log::warn!("Unknown error code {}", other);
|
tracing::warn!("Unknown error code {}", other);
|
||||||
v5::Code::UnretrievableErrorCode
|
v5::Code::UnretrievableErrorCode
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -304,7 +304,7 @@ impl From<v5::ResponseError> for v6::ResponseError {
|
|||||||
"immutable_field" => v6::Code::BadRequest,
|
"immutable_field" => v6::Code::BadRequest,
|
||||||
"api_key_already_exists" => v6::Code::ApiKeyAlreadyExists,
|
"api_key_already_exists" => v6::Code::ApiKeyAlreadyExists,
|
||||||
other => {
|
other => {
|
||||||
log::warn!("Unknown error code {}", other);
|
tracing::warn!("Unknown error code {}", other);
|
||||||
v6::Code::UnretrievableErrorCode
|
v6::Code::UnretrievableErrorCode
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -329,7 +329,7 @@ impl<T> From<v5::Settings<T>> for v6::Settings<v6::Unchecked> {
|
|||||||
new_ranking_rules.push(new_rule);
|
new_ranking_rules.push(new_rule);
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
log::warn!("Error while importing settings. The ranking rule `{rule}` does not exist anymore.")
|
tracing::warn!("Error while importing settings. The ranking rule `{rule}` does not exist anymore.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[allow(clippy::enum_variant_names)]
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
||||||
pub enum Code {
|
pub enum Code {
|
||||||
// index related error
|
// index related error
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ impl fmt::Display for ErrorType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::enum_variant_names)]
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
||||||
pub enum Code {
|
pub enum Code {
|
||||||
// index related error
|
// index related error
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ impl ResponseError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::enum_variant_names)]
|
||||||
#[derive(Deserialize, Debug, Clone, Copy)]
|
#[derive(Deserialize, Debug, Clone, Copy)]
|
||||||
#[cfg_attr(test, derive(serde::Serialize))]
|
#[cfg_attr(test, derive(serde::Serialize))]
|
||||||
pub enum Code {
|
pub enum Code {
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ use std::fs::{self, File};
|
|||||||
use std::io::{BufRead, BufReader, ErrorKind};
|
use std::io::{BufRead, BufReader, ErrorKind};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use log::debug;
|
|
||||||
pub use meilisearch_types::milli;
|
pub use meilisearch_types::milli;
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
use tracing::debug;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::Document;
|
use super::Document;
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ edition.workspace = true
|
|||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tempfile = "3.5.0"
|
tempfile = "3.9.0"
|
||||||
thiserror = "1.0.40"
|
thiserror = "1.0.56"
|
||||||
uuid = { version = "1.3.1", features = ["serde", "v4"] }
|
uuid = { version = "1.6.1", features = ["serde", "v4"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
faux = "0.1.9"
|
faux = "0.1.10"
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ license.workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nom = "7.1.3"
|
nom = "7.1.3"
|
||||||
nom_locate = "4.1.0"
|
nom_locate = "4.2.0"
|
||||||
unescaper = "0.1.2"
|
unescaper = "0.1.3"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
insta = "1.29.0"
|
insta = "1.34.0"
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ edition.workspace = true
|
|||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
arbitrary = { version = "1.3.0", features = ["derive"] }
|
arbitrary = { version = "1.3.2", features = ["derive"] }
|
||||||
clap = { version = "4.3.0", features = ["derive"] }
|
clap = { version = "4.4.17", features = ["derive"] }
|
||||||
fastrand = "2.0.0"
|
fastrand = "2.0.1"
|
||||||
milli = { path = "../milli" }
|
milli = { path = "../milli" }
|
||||||
serde = { version = "1.0.160", features = ["derive"] }
|
serde = { version = "1.0.195", features = ["derive"] }
|
||||||
serde_json = { version = "1.0.95", features = ["preserve_order"] }
|
serde_json = { version = "1.0.111", features = ["preserve_order"] }
|
||||||
tempfile = "3.5.0"
|
tempfile = "3.9.0"
|
||||||
|
|||||||
@@ -11,32 +11,36 @@ edition.workspace = true
|
|||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.70"
|
anyhow = "1.0.79"
|
||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
csv = "1.2.1"
|
csv = "1.3.0"
|
||||||
derive_builder = "0.12.0"
|
derive_builder = "0.12.0"
|
||||||
dump = { path = "../dump" }
|
dump = { path = "../dump" }
|
||||||
enum-iterator = "1.4.0"
|
enum-iterator = "1.5.0"
|
||||||
file-store = { path = "../file-store" }
|
file-store = { path = "../file-store" }
|
||||||
flate2 = "1.0.28"
|
flate2 = "1.0.28"
|
||||||
log = "0.4.17"
|
|
||||||
meilisearch-auth = { path = "../meilisearch-auth" }
|
meilisearch-auth = { path = "../meilisearch-auth" }
|
||||||
meilisearch-types = { path = "../meilisearch-types" }
|
meilisearch-types = { path = "../meilisearch-types" }
|
||||||
page_size = "0.5.0"
|
page_size = "0.5.0"
|
||||||
puffin = { version = "0.16.0", features = ["serialization"] }
|
puffin = { version = "0.16.0", features = ["serialization"] }
|
||||||
roaring = { version = "0.10.1", features = ["serde"] }
|
roaring = { version = "0.10.2", features = ["serde"] }
|
||||||
serde = { version = "1.0.160", features = ["derive"] }
|
serde = { version = "1.0.195", features = ["derive"] }
|
||||||
serde_json = { version = "1.0.95", features = ["preserve_order"] }
|
serde_json = { version = "1.0.111", features = ["preserve_order"] }
|
||||||
synchronoise = "1.0.1"
|
synchronoise = "1.0.1"
|
||||||
tempfile = "3.5.0"
|
tempfile = "3.9.0"
|
||||||
thiserror = "1.0.40"
|
thiserror = "1.0.56"
|
||||||
time = { version = "0.3.20", features = ["serde-well-known", "formatting", "parsing", "macros"] }
|
time = { version = "0.3.31", features = [
|
||||||
|
"serde-well-known",
|
||||||
|
"formatting",
|
||||||
|
"parsing",
|
||||||
|
"macros",
|
||||||
|
] }
|
||||||
|
tracing = "0.1.40"
|
||||||
ureq = "2.9.1"
|
ureq = "2.9.1"
|
||||||
uuid = { version = "1.3.1", features = ["serde", "v4"] }
|
uuid = { version = "1.6.1", features = ["serde", "v4"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
big_s = "1.0.2"
|
big_s = "1.0.2"
|
||||||
crossbeam = "0.8.2"
|
crossbeam = "0.8.4"
|
||||||
insta = { version = "1.29.0", features = ["json", "redactions"] }
|
insta = { version = "1.34.0", features = ["json", "redactions"] }
|
||||||
meili-snap = { path = "../meili-snap" }
|
meili-snap = { path = "../meili-snap" }
|
||||||
nelson = { git = "https://github.com/meilisearch/nelson.git", rev = "675f13885548fb415ead8fbb447e9e6d9314000a"}
|
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ use std::fs::{self, File};
|
|||||||
use std::io::BufWriter;
|
use std::io::BufWriter;
|
||||||
|
|
||||||
use dump::IndexMetadata;
|
use dump::IndexMetadata;
|
||||||
use log::{debug, error, info, trace};
|
|
||||||
use meilisearch_types::error::Code;
|
use meilisearch_types::error::Code;
|
||||||
use meilisearch_types::heed::{RoTxn, RwTxn};
|
use meilisearch_types::heed::{RoTxn, RwTxn};
|
||||||
use meilisearch_types::milli::documents::{obkv_to_object, DocumentsBatchReader};
|
use meilisearch_types::milli::documents::{obkv_to_object, DocumentsBatchReader};
|
||||||
@@ -60,7 +59,7 @@ pub(crate) enum Batch {
|
|||||||
/// The list of tasks that were processing when this task cancelation appeared.
|
/// The list of tasks that were processing when this task cancelation appeared.
|
||||||
previous_processing_tasks: RoaringBitmap,
|
previous_processing_tasks: RoaringBitmap,
|
||||||
},
|
},
|
||||||
TaskDeletion(Task),
|
TaskDeletions(Vec<Task>),
|
||||||
SnapshotCreation(Vec<Task>),
|
SnapshotCreation(Vec<Task>),
|
||||||
Dump(Task),
|
Dump(Task),
|
||||||
IndexOperation {
|
IndexOperation {
|
||||||
@@ -146,13 +145,12 @@ impl Batch {
|
|||||||
pub fn ids(&self) -> Vec<TaskId> {
|
pub fn ids(&self) -> Vec<TaskId> {
|
||||||
match self {
|
match self {
|
||||||
Batch::TaskCancelation { task, .. }
|
Batch::TaskCancelation { task, .. }
|
||||||
| Batch::TaskDeletion(task)
|
|
||||||
| Batch::Dump(task)
|
| Batch::Dump(task)
|
||||||
| Batch::IndexCreation { task, .. }
|
| Batch::IndexCreation { task, .. }
|
||||||
| Batch::IndexUpdate { task, .. } => vec![task.uid],
|
| Batch::IndexUpdate { task, .. } => vec![task.uid],
|
||||||
Batch::SnapshotCreation(tasks) | Batch::IndexDeletion { tasks, .. } => {
|
Batch::SnapshotCreation(tasks)
|
||||||
tasks.iter().map(|task| task.uid).collect()
|
| Batch::TaskDeletions(tasks)
|
||||||
}
|
| Batch::IndexDeletion { tasks, .. } => tasks.iter().map(|task| task.uid).collect(),
|
||||||
Batch::IndexOperation { op, .. } => match op {
|
Batch::IndexOperation { op, .. } => match op {
|
||||||
IndexOperation::DocumentOperation { tasks, .. }
|
IndexOperation::DocumentOperation { tasks, .. }
|
||||||
| IndexOperation::Settings { tasks, .. }
|
| IndexOperation::Settings { tasks, .. }
|
||||||
@@ -180,7 +178,7 @@ impl Batch {
|
|||||||
use Batch::*;
|
use Batch::*;
|
||||||
match self {
|
match self {
|
||||||
TaskCancelation { .. }
|
TaskCancelation { .. }
|
||||||
| TaskDeletion(_)
|
| TaskDeletions(_)
|
||||||
| SnapshotCreation(_)
|
| SnapshotCreation(_)
|
||||||
| Dump(_)
|
| Dump(_)
|
||||||
| IndexSwap { .. } => None,
|
| IndexSwap { .. } => None,
|
||||||
@@ -199,7 +197,7 @@ impl fmt::Display for Batch {
|
|||||||
let tasks = self.ids();
|
let tasks = self.ids();
|
||||||
match self {
|
match self {
|
||||||
Batch::TaskCancelation { .. } => f.write_str("TaskCancelation")?,
|
Batch::TaskCancelation { .. } => f.write_str("TaskCancelation")?,
|
||||||
Batch::TaskDeletion(_) => f.write_str("TaskDeletion")?,
|
Batch::TaskDeletions(_) => f.write_str("TaskDeletion")?,
|
||||||
Batch::SnapshotCreation(_) => f.write_str("SnapshotCreation")?,
|
Batch::SnapshotCreation(_) => f.write_str("SnapshotCreation")?,
|
||||||
Batch::Dump(_) => f.write_str("Dump")?,
|
Batch::Dump(_) => f.write_str("Dump")?,
|
||||||
Batch::IndexOperation { op, .. } => write!(f, "{op}")?,
|
Batch::IndexOperation { op, .. } => write!(f, "{op}")?,
|
||||||
@@ -515,6 +513,7 @@ impl IndexScheduler {
|
|||||||
/// 3. We get the *next* snapshot to process.
|
/// 3. We get the *next* snapshot to process.
|
||||||
/// 4. We get the *next* dump to process.
|
/// 4. We get the *next* dump to process.
|
||||||
/// 5. We get the *next* tasks to process for a specific index.
|
/// 5. We get the *next* tasks to process for a specific index.
|
||||||
|
#[tracing::instrument(level = "trace", skip(self, rtxn), target = "indexing::scheduler")]
|
||||||
pub(crate) fn create_next_batch(&self, rtxn: &RoTxn) -> Result<Option<Batch>> {
|
pub(crate) fn create_next_batch(&self, rtxn: &RoTxn) -> Result<Option<Batch>> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
self.maybe_fail(crate::tests::FailureLocation::InsideCreateBatch)?;
|
self.maybe_fail(crate::tests::FailureLocation::InsideCreateBatch)?;
|
||||||
@@ -539,9 +538,9 @@ impl IndexScheduler {
|
|||||||
|
|
||||||
// 2. we get the next task to delete
|
// 2. we get the next task to delete
|
||||||
let to_delete = self.get_kind(rtxn, Kind::TaskDeletion)? & enqueued;
|
let to_delete = self.get_kind(rtxn, Kind::TaskDeletion)? & enqueued;
|
||||||
if let Some(task_id) = to_delete.min() {
|
if !to_delete.is_empty() {
|
||||||
let task = self.get_task(rtxn, task_id)?.ok_or(Error::CorruptedTaskQueue)?;
|
let tasks = self.get_existing_tasks(rtxn, to_delete)?;
|
||||||
return Ok(Some(Batch::TaskDeletion(task)));
|
return Ok(Some(Batch::TaskDeletions(tasks)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. we batch the snapshot.
|
// 3. we batch the snapshot.
|
||||||
@@ -620,6 +619,7 @@ impl IndexScheduler {
|
|||||||
/// The list of tasks that were processed. The metadata of each task in the returned
|
/// The list of tasks that were processed. The metadata of each task in the returned
|
||||||
/// list is updated accordingly, with the exception of the its date fields
|
/// list is updated accordingly, with the exception of the its date fields
|
||||||
/// [`finished_at`](meilisearch_types::tasks::Task::finished_at) and [`started_at`](meilisearch_types::tasks::Task::started_at).
|
/// [`finished_at`](meilisearch_types::tasks::Task::finished_at) and [`started_at`](meilisearch_types::tasks::Task::started_at).
|
||||||
|
#[tracing::instrument(level = "trace", skip(self, batch), target = "indexing::scheduler", fields(batch=batch.to_string()))]
|
||||||
pub(crate) fn process_batch(&self, batch: Batch) -> Result<Vec<Task>> {
|
pub(crate) fn process_batch(&self, batch: Batch) -> Result<Vec<Task>> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
{
|
{
|
||||||
@@ -669,9 +669,10 @@ impl IndexScheduler {
|
|||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
for content_uuid in canceled_tasks_content_uuids {
|
for content_uuid in canceled_tasks_content_uuids {
|
||||||
if let Err(error) = self.delete_update_file(content_uuid) {
|
if let Err(error) = self.delete_update_file(content_uuid) {
|
||||||
error!(
|
tracing::error!(
|
||||||
"We failed deleting the content file indentified as {}: {}",
|
file_content_uuid = %content_uuid,
|
||||||
content_uuid, error
|
%error,
|
||||||
|
"Failed deleting content file"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -681,31 +682,43 @@ impl IndexScheduler {
|
|||||||
|
|
||||||
Ok(vec![task])
|
Ok(vec![task])
|
||||||
}
|
}
|
||||||
Batch::TaskDeletion(mut task) => {
|
Batch::TaskDeletions(mut tasks) => {
|
||||||
// 1. Retrieve the tasks that matched the query at enqueue-time.
|
// 1. Retrieve the tasks that matched the query at enqueue-time.
|
||||||
let matched_tasks =
|
let mut matched_tasks = RoaringBitmap::new();
|
||||||
|
|
||||||
|
for task in tasks.iter() {
|
||||||
if let KindWithContent::TaskDeletion { tasks, query: _ } = &task.kind {
|
if let KindWithContent::TaskDeletion { tasks, query: _ } = &task.kind {
|
||||||
tasks
|
matched_tasks |= tasks;
|
||||||
} else {
|
} else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut wtxn = self.env.write_txn()?;
|
||||||
|
let mut deleted_tasks = self.delete_matched_tasks(&mut wtxn, &matched_tasks)?;
|
||||||
|
wtxn.commit()?;
|
||||||
|
|
||||||
|
for task in tasks.iter_mut() {
|
||||||
|
task.status = Status::Succeeded;
|
||||||
|
let KindWithContent::TaskDeletion { tasks, query: _ } = &task.kind else {
|
||||||
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut wtxn = self.env.write_txn()?;
|
let deleted_tasks_count = deleted_tasks.intersection_len(tasks);
|
||||||
let deleted_tasks_count = self.delete_matched_tasks(&mut wtxn, matched_tasks)?;
|
deleted_tasks -= tasks;
|
||||||
|
|
||||||
task.status = Status::Succeeded;
|
match &mut task.details {
|
||||||
match &mut task.details {
|
Some(Details::TaskDeletion {
|
||||||
Some(Details::TaskDeletion {
|
matched_tasks: _,
|
||||||
matched_tasks: _,
|
deleted_tasks,
|
||||||
deleted_tasks,
|
original_filter: _,
|
||||||
original_filter: _,
|
}) => {
|
||||||
}) => {
|
*deleted_tasks = Some(deleted_tasks_count);
|
||||||
*deleted_tasks = Some(deleted_tasks_count);
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
}
|
||||||
wtxn.commit()?;
|
Ok(tasks)
|
||||||
Ok(vec![task])
|
|
||||||
}
|
}
|
||||||
Batch::SnapshotCreation(mut tasks) => {
|
Batch::SnapshotCreation(mut tasks) => {
|
||||||
fs::create_dir_all(&self.snapshots_path)?;
|
fs::create_dir_all(&self.snapshots_path)?;
|
||||||
@@ -958,7 +971,10 @@ impl IndexScheduler {
|
|||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(e) => error!("Could not write the stats of the index {}", e),
|
Err(e) => tracing::error!(
|
||||||
|
error = &e as &dyn std::error::Error,
|
||||||
|
"Could not write the stats of the index"
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(tasks)
|
Ok(tasks)
|
||||||
@@ -986,7 +1002,7 @@ impl IndexScheduler {
|
|||||||
builder.set_primary_key(primary_key);
|
builder.set_primary_key(primary_key);
|
||||||
let must_stop_processing = self.must_stop_processing.clone();
|
let must_stop_processing = self.must_stop_processing.clone();
|
||||||
builder.execute(
|
builder.execute(
|
||||||
|indexing_step| debug!("update: {:?}", indexing_step),
|
|indexing_step| tracing::debug!(update = ?indexing_step),
|
||||||
|| must_stop_processing.get(),
|
|| must_stop_processing.get(),
|
||||||
)?;
|
)?;
|
||||||
index_wtxn.commit()?;
|
index_wtxn.commit()?;
|
||||||
@@ -1013,7 +1029,10 @@ impl IndexScheduler {
|
|||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(e) => error!("Could not write the stats of the index {}", e),
|
Err(e) => tracing::error!(
|
||||||
|
error = &e as &dyn std::error::Error,
|
||||||
|
"Could not write the stats of the index"
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(vec![task])
|
Ok(vec![task])
|
||||||
@@ -1132,6 +1151,11 @@ impl IndexScheduler {
|
|||||||
///
|
///
|
||||||
/// ## Return
|
/// ## Return
|
||||||
/// The list of processed tasks.
|
/// The list of processed tasks.
|
||||||
|
#[tracing::instrument(
|
||||||
|
level = "trace",
|
||||||
|
skip(self, index_wtxn, index),
|
||||||
|
target = "indexing::scheduler"
|
||||||
|
)]
|
||||||
fn apply_index_operation<'i>(
|
fn apply_index_operation<'i>(
|
||||||
&self,
|
&self,
|
||||||
index_wtxn: &mut RwTxn<'i>,
|
index_wtxn: &mut RwTxn<'i>,
|
||||||
@@ -1192,7 +1216,7 @@ impl IndexScheduler {
|
|||||||
milli::update::Settings::new(index_wtxn, index, indexer_config);
|
milli::update::Settings::new(index_wtxn, index, indexer_config);
|
||||||
builder.set_primary_key(primary_key);
|
builder.set_primary_key(primary_key);
|
||||||
builder.execute(
|
builder.execute(
|
||||||
|indexing_step| debug!("update: {:?}", indexing_step),
|
|indexing_step| tracing::debug!(update = ?indexing_step),
|
||||||
|| must_stop_processing.clone().get(),
|
|| must_stop_processing.clone().get(),
|
||||||
)?;
|
)?;
|
||||||
primary_key_has_been_set = true;
|
primary_key_has_been_set = true;
|
||||||
@@ -1211,7 +1235,7 @@ impl IndexScheduler {
|
|||||||
index,
|
index,
|
||||||
indexer_config,
|
indexer_config,
|
||||||
config,
|
config,
|
||||||
|indexing_step| trace!("update: {:?}", indexing_step),
|
|indexing_step| tracing::trace!(?indexing_step, "Update"),
|
||||||
|| must_stop_processing.get(),
|
|| must_stop_processing.get(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@@ -1283,7 +1307,7 @@ impl IndexScheduler {
|
|||||||
|
|
||||||
if !tasks.iter().all(|res| res.error.is_some()) {
|
if !tasks.iter().all(|res| res.error.is_some()) {
|
||||||
let addition = builder.execute()?;
|
let addition = builder.execute()?;
|
||||||
info!("document addition done: {:?}", addition);
|
tracing::info!(indexing_result = ?addition, "document indexing done");
|
||||||
} else if primary_key_has_been_set {
|
} else if primary_key_has_been_set {
|
||||||
// Everything failed but we've set a primary key.
|
// Everything failed but we've set a primary key.
|
||||||
// We need to remove it.
|
// We need to remove it.
|
||||||
@@ -1291,7 +1315,7 @@ impl IndexScheduler {
|
|||||||
milli::update::Settings::new(index_wtxn, index, indexer_config);
|
milli::update::Settings::new(index_wtxn, index, indexer_config);
|
||||||
builder.reset_primary_key();
|
builder.reset_primary_key();
|
||||||
builder.execute(
|
builder.execute(
|
||||||
|indexing_step| trace!("update: {:?}", indexing_step),
|
|indexing_step| tracing::trace!(update = ?indexing_step),
|
||||||
|| must_stop_processing.clone().get(),
|
|| must_stop_processing.clone().get(),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
@@ -1361,7 +1385,7 @@ impl IndexScheduler {
|
|||||||
|
|
||||||
let must_stop_processing = self.must_stop_processing.clone();
|
let must_stop_processing = self.must_stop_processing.clone();
|
||||||
builder.execute(
|
builder.execute(
|
||||||
|indexing_step| debug!("update: {:?}", indexing_step),
|
|indexing_step| tracing::debug!(update = ?indexing_step),
|
||||||
|| must_stop_processing.get(),
|
|| must_stop_processing.get(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@@ -1435,7 +1459,11 @@ impl IndexScheduler {
|
|||||||
/// Delete each given task from all the databases (if it is deleteable).
|
/// Delete each given task from all the databases (if it is deleteable).
|
||||||
///
|
///
|
||||||
/// Return the number of tasks that were actually deleted.
|
/// Return the number of tasks that were actually deleted.
|
||||||
fn delete_matched_tasks(&self, wtxn: &mut RwTxn, matched_tasks: &RoaringBitmap) -> Result<u64> {
|
fn delete_matched_tasks(
|
||||||
|
&self,
|
||||||
|
wtxn: &mut RwTxn,
|
||||||
|
matched_tasks: &RoaringBitmap,
|
||||||
|
) -> Result<RoaringBitmap> {
|
||||||
// 1. Remove from this list the tasks that we are not allowed to delete
|
// 1. Remove from this list the tasks that we are not allowed to delete
|
||||||
let enqueued_tasks = self.get_status(wtxn, Status::Enqueued)?;
|
let enqueued_tasks = self.get_status(wtxn, Status::Enqueued)?;
|
||||||
let processing_tasks = &self.processing_tasks.read().unwrap().processing.clone();
|
let processing_tasks = &self.processing_tasks.read().unwrap().processing.clone();
|
||||||
@@ -1500,7 +1528,7 @@ impl IndexScheduler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(to_delete_tasks.len())
|
Ok(to_delete_tasks)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cancel each given task from all the databases (if it is cancelable).
|
/// Cancel each given task from all the databases (if it is cancelable).
|
||||||
@@ -1569,7 +1597,7 @@ fn delete_document_by_filter<'a>(
|
|||||||
index,
|
index,
|
||||||
indexer_config,
|
indexer_config,
|
||||||
config,
|
config,
|
||||||
|indexing_step| debug!("update: {:?}", indexing_step),
|
|indexing_step| tracing::debug!(update = ?indexing_step),
|
||||||
|| must_stop_processing.get(),
|
|| must_stop_processing.get(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|||||||
@@ -30,19 +30,6 @@ impl RoFeatures {
|
|||||||
self.runtime
|
self.runtime
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_score_details(&self) -> Result<()> {
|
|
||||||
if self.runtime.score_details {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(FeatureNotEnabledError {
|
|
||||||
disabled_action: "Computing score details",
|
|
||||||
feature: "score details",
|
|
||||||
issue_link: "https://github.com/meilisearch/product/discussions/674",
|
|
||||||
}
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check_metrics(&self) -> Result<()> {
|
pub fn check_metrics(&self) -> Result<()> {
|
||||||
if self.runtime.metrics {
|
if self.runtime.metrics {
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -56,6 +43,19 @@ impl RoFeatures {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn check_logs_route(&self) -> Result<()> {
|
||||||
|
if self.runtime.logs_route {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(FeatureNotEnabledError {
|
||||||
|
disabled_action: "getting logs through the `/logs/stream` route",
|
||||||
|
feature: "logs route",
|
||||||
|
issue_link: "https://github.com/orgs/meilisearch/discussions/721",
|
||||||
|
}
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn check_vector(&self, disabled_action: &'static str) -> Result<()> {
|
pub fn check_vector(&self, disabled_action: &'static str) -> Result<()> {
|
||||||
if self.runtime.vector_store {
|
if self.runtime.vector_store {
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -94,6 +94,7 @@ impl FeatureData {
|
|||||||
runtime_features_db.get(&txn, EXPERIMENTAL_FEATURES)?.unwrap_or_default();
|
runtime_features_db.get(&txn, EXPERIMENTAL_FEATURES)?.unwrap_or_default();
|
||||||
let runtime = Arc::new(RwLock::new(RuntimeTogglableFeatures {
|
let runtime = Arc::new(RwLock::new(RuntimeTogglableFeatures {
|
||||||
metrics: instance_features.metrics || persisted_features.metrics,
|
metrics: instance_features.metrics || persisted_features.metrics,
|
||||||
|
logs_route: instance_features.logs_route || persisted_features.logs_route,
|
||||||
..persisted_features
|
..persisted_features
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ use std::sync::{Arc, RwLock};
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::{fs, thread};
|
use std::{fs, thread};
|
||||||
|
|
||||||
use log::error;
|
|
||||||
use meilisearch_types::heed::types::{SerdeJson, Str};
|
use meilisearch_types::heed::types::{SerdeJson, Str};
|
||||||
use meilisearch_types::heed::{Database, Env, RoTxn, RwTxn};
|
use meilisearch_types::heed::{Database, Env, RoTxn, RwTxn};
|
||||||
use meilisearch_types::milli::update::IndexerConfig;
|
use meilisearch_types::milli::update::IndexerConfig;
|
||||||
use meilisearch_types::milli::{FieldDistribution, Index};
|
use meilisearch_types::milli::{FieldDistribution, Index};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
use tracing::error;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use self::index_map::IndexMap;
|
use self::index_map::IndexMap;
|
||||||
|
|||||||
@@ -535,17 +535,17 @@ impl IndexScheduler {
|
|||||||
let budget = if Self::is_good_heed(tasks_path, DEFAULT_BUDGET) {
|
let budget = if Self::is_good_heed(tasks_path, DEFAULT_BUDGET) {
|
||||||
DEFAULT_BUDGET
|
DEFAULT_BUDGET
|
||||||
} else {
|
} else {
|
||||||
log::debug!("determining budget with dichotomic search");
|
tracing::debug!("determining budget with dichotomic search");
|
||||||
utils::dichotomic_search(DEFAULT_BUDGET / 2, |map_size| {
|
utils::dichotomic_search(DEFAULT_BUDGET / 2, |map_size| {
|
||||||
Self::is_good_heed(tasks_path, map_size)
|
Self::is_good_heed(tasks_path, map_size)
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
log::debug!("memmap budget: {budget}B");
|
tracing::debug!("memmap budget: {budget}B");
|
||||||
let mut budget = budget / 2;
|
let mut budget = budget / 2;
|
||||||
if task_db_size > (budget / 2) {
|
if task_db_size > (budget / 2) {
|
||||||
task_db_size = clamp_to_page_size(budget * 2 / 5);
|
task_db_size = clamp_to_page_size(budget * 2 / 5);
|
||||||
log::debug!(
|
tracing::debug!(
|
||||||
"Decreasing max size of task DB to {task_db_size}B due to constrained memory space"
|
"Decreasing max size of task DB to {task_db_size}B due to constrained memory space"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -555,13 +555,13 @@ impl IndexScheduler {
|
|||||||
let budget = budget;
|
let budget = budget;
|
||||||
let task_db_size = task_db_size;
|
let task_db_size = task_db_size;
|
||||||
|
|
||||||
log::debug!("index budget: {budget}B");
|
tracing::debug!("index budget: {budget}B");
|
||||||
let mut index_count = budget / base_map_size;
|
let mut index_count = budget / base_map_size;
|
||||||
if index_count < 2 {
|
if index_count < 2 {
|
||||||
// take a bit less than half than the budget to make sure we can always afford to open an index
|
// take a bit less than half than the budget to make sure we can always afford to open an index
|
||||||
let map_size = (budget * 2) / 5;
|
let map_size = (budget * 2) / 5;
|
||||||
// single index of max budget
|
// single index of max budget
|
||||||
log::debug!("1 index of {map_size}B can be opened simultaneously.");
|
tracing::debug!("1 index of {map_size}B can be opened simultaneously.");
|
||||||
return IndexBudget { map_size, index_count: 1, task_db_size };
|
return IndexBudget { map_size, index_count: 1, task_db_size };
|
||||||
}
|
}
|
||||||
// give us some space for an additional index when the cache is already full
|
// give us some space for an additional index when the cache is already full
|
||||||
@@ -570,7 +570,7 @@ impl IndexScheduler {
|
|||||||
if index_count > max_index_count {
|
if index_count > max_index_count {
|
||||||
index_count = max_index_count;
|
index_count = max_index_count;
|
||||||
}
|
}
|
||||||
log::debug!("Up to {index_count} indexes of {base_map_size}B opened simultaneously.");
|
tracing::debug!("Up to {index_count} indexes of {base_map_size}B opened simultaneously.");
|
||||||
IndexBudget { map_size: base_map_size, index_count, task_db_size }
|
IndexBudget { map_size: base_map_size, index_count, task_db_size }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -617,7 +617,7 @@ impl IndexScheduler {
|
|||||||
Ok(TickOutcome::TickAgain(_)) => (),
|
Ok(TickOutcome::TickAgain(_)) => (),
|
||||||
Ok(TickOutcome::WaitForSignal) => run.wake_up.wait(),
|
Ok(TickOutcome::WaitForSignal) => run.wake_up.wait(),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("{e}");
|
tracing::error!("{e}");
|
||||||
// Wait one second when an irrecoverable error occurs.
|
// Wait one second when an irrecoverable error occurs.
|
||||||
if !e.is_recoverable() {
|
if !e.is_recoverable() {
|
||||||
std::thread::sleep(Duration::from_secs(1));
|
std::thread::sleep(Duration::from_secs(1));
|
||||||
@@ -634,15 +634,15 @@ impl IndexScheduler {
|
|||||||
let mut file = match File::create(format!("{}.puffin", now)) {
|
let mut file = match File::create(format!("{}.puffin", now)) {
|
||||||
Ok(file) => file,
|
Ok(file) => file,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("{e}");
|
tracing::error!("{e}");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if let Err(e) = frame_view.save_to_writer(&mut file) {
|
if let Err(e) = frame_view.save_to_writer(&mut file) {
|
||||||
log::error!("{e}");
|
tracing::error!("{e}");
|
||||||
}
|
}
|
||||||
if let Err(e) = file.sync_all() {
|
if let Err(e) = file.sync_all() {
|
||||||
log::error!("{e}");
|
tracing::error!("{e}");
|
||||||
}
|
}
|
||||||
// We erase this frame view as it is no more useful. We want to
|
// We erase this frame view as it is no more useful. We want to
|
||||||
// measure the new frames now that we exported the previous ones.
|
// measure the new frames now that we exported the previous ones.
|
||||||
@@ -1190,10 +1190,10 @@ impl IndexScheduler {
|
|||||||
self.update_task(&mut wtxn, &task)
|
self.update_task(&mut wtxn, &task)
|
||||||
.map_err(|e| Error::TaskDatabaseUpdate(Box::new(e)))?;
|
.map_err(|e| Error::TaskDatabaseUpdate(Box::new(e)))?;
|
||||||
if let Err(e) = self.delete_persisted_task_data(&task) {
|
if let Err(e) = self.delete_persisted_task_data(&task) {
|
||||||
log::error!("Failure to delete the content files associated with task {}. Error: {e}", task.uid);
|
tracing::error!("Failure to delete the content files associated with task {}. Error: {e}", task.uid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log::info!("A batch of tasks was successfully completed.");
|
tracing::info!("A batch of tasks was successfully completed.");
|
||||||
}
|
}
|
||||||
// If we have an abortion error we must stop the tick here and re-schedule tasks.
|
// If we have an abortion error we must stop the tick here and re-schedule tasks.
|
||||||
Err(Error::Milli(milli::Error::InternalError(
|
Err(Error::Milli(milli::Error::InternalError(
|
||||||
@@ -1247,7 +1247,7 @@ impl IndexScheduler {
|
|||||||
self.maybe_fail(tests::FailureLocation::UpdatingTaskAfterProcessBatchFailure)?;
|
self.maybe_fail(tests::FailureLocation::UpdatingTaskAfterProcessBatchFailure)?;
|
||||||
|
|
||||||
if let Err(e) = self.delete_persisted_task_data(&task) {
|
if let Err(e) = self.delete_persisted_task_data(&task) {
|
||||||
log::error!("Failure to delete the content files associated with task {}. Error: {e}", task.uid);
|
tracing::error!("Failure to delete the content files associated with task {}. Error: {e}", task.uid);
|
||||||
}
|
}
|
||||||
self.update_task(&mut wtxn, &task)
|
self.update_task(&mut wtxn, &task)
|
||||||
.map_err(|e| Error::TaskDatabaseUpdate(Box::new(e)))?;
|
.map_err(|e| Error::TaskDatabaseUpdate(Box::new(e)))?;
|
||||||
@@ -1341,7 +1341,7 @@ impl IndexScheduler {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = request.send(reader) {
|
if let Err(e) = request.send(reader) {
|
||||||
log::error!("While sending data to the webhook: {e}");
|
tracing::error!("While sending data to the webhook: {e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1367,12 +1367,12 @@ impl IndexScheduler {
|
|||||||
// /!\ the len must be at least 2 or else we might enter an infinite loop where we only delete
|
// /!\ the len must be at least 2 or else we might enter an infinite loop where we only delete
|
||||||
// the deletion tasks we enqueued ourselves.
|
// the deletion tasks we enqueued ourselves.
|
||||||
if to_delete.len() < 2 {
|
if to_delete.len() < 2 {
|
||||||
log::warn!("The task queue is almost full, but no task can be deleted yet.");
|
tracing::warn!("The task queue is almost full, but no task can be deleted yet.");
|
||||||
// the only thing we can do is hope that the user tasks are going to finish
|
// the only thing we can do is hope that the user tasks are going to finish
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
log::info!(
|
tracing::info!(
|
||||||
"The task queue is almost full. Deleting the oldest {} finished tasks.",
|
"The task queue is almost full. Deleting the oldest {} finished tasks.",
|
||||||
to_delete.len()
|
to_delete.len()
|
||||||
);
|
);
|
||||||
@@ -2244,10 +2244,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
index_scheduler.assert_internally_consistent();
|
index_scheduler.assert_internally_consistent();
|
||||||
}
|
}
|
||||||
for _ in 0..2 {
|
handle.advance_one_successful_batch();
|
||||||
handle.advance_one_successful_batch();
|
|
||||||
index_scheduler.assert_internally_consistent();
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshot!(snapshot_index_scheduler(&index_scheduler), name: "task_deletion_processed");
|
snapshot!(snapshot_index_scheduler(&index_scheduler), name: "task_deletion_processed");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,12 +34,10 @@ catto: { number_of_documents: 1, field_distribution: {"id": 1} }
|
|||||||
[timestamp] [3,]
|
[timestamp] [3,]
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
### Started At:
|
### Started At:
|
||||||
[timestamp] [2,]
|
[timestamp] [2,3,]
|
||||||
[timestamp] [3,]
|
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
### Finished At:
|
### Finished At:
|
||||||
[timestamp] [2,]
|
[timestamp] [2,3,]
|
||||||
[timestamp] [3,]
|
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
### File Store:
|
### File Store:
|
||||||
00000000-0000-0000-0000-000000000001
|
00000000-0000-0000-0000-000000000001
|
||||||
|
|||||||
@@ -11,6 +11,6 @@ edition.workspace = true
|
|||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
insta = { version = "^1.29.0", features = ["json", "redactions"] }
|
insta = { version = "^1.34.0", features = ["json", "redactions"] }
|
||||||
md5 = "0.7.0"
|
md5 = "0.7.0"
|
||||||
once_cell = "1.17"
|
once_cell = "1.19"
|
||||||
|
|||||||
@@ -11,16 +11,16 @@ edition.workspace = true
|
|||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = "0.21.0"
|
base64 = "0.21.7"
|
||||||
enum-iterator = "1.4.0"
|
enum-iterator = "1.5.0"
|
||||||
hmac = "0.12.1"
|
hmac = "0.12.1"
|
||||||
maplit = "1.0.2"
|
maplit = "1.0.2"
|
||||||
meilisearch-types = { path = "../meilisearch-types" }
|
meilisearch-types = { path = "../meilisearch-types" }
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
roaring = { version = "0.10.1", features = ["serde"] }
|
roaring = { version = "0.10.2", features = ["serde"] }
|
||||||
serde = { version = "1.0.160", features = ["derive"] }
|
serde = { version = "1.0.195", features = ["derive"] }
|
||||||
serde_json = { version = "1.0.95", features = ["preserve_order"] }
|
serde_json = { version = "1.0.111", features = ["preserve_order"] }
|
||||||
sha2 = "0.10.6"
|
sha2 = "0.10.8"
|
||||||
thiserror = "1.0.40"
|
thiserror = "1.0.56"
|
||||||
time = { version = "0.3.20", features = ["serde-well-known", "formatting", "parsing", "macros"] }
|
time = { version = "0.3.31", features = ["serde-well-known", "formatting", "parsing", "macros"] }
|
||||||
uuid = { version = "1.3.1", features = ["serde", "v4"] }
|
uuid = { version = "1.6.1", features = ["serde", "v4"] }
|
||||||
|
|||||||
@@ -11,31 +11,31 @@ edition.workspace = true
|
|||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = { version = "4.3.1", default-features = false }
|
actix-web = { version = "4.5.1", default-features = false }
|
||||||
anyhow = "1.0.70"
|
anyhow = "1.0.79"
|
||||||
convert_case = "0.6.0"
|
convert_case = "0.6.0"
|
||||||
csv = "1.2.1"
|
csv = "1.3.0"
|
||||||
deserr = { version = "0.6.0", features = ["actix-web"] }
|
deserr = { version = "0.6.1", features = ["actix-web"] }
|
||||||
either = { version = "1.8.1", features = ["serde"] }
|
either = { version = "1.9.0", features = ["serde"] }
|
||||||
enum-iterator = "1.4.0"
|
enum-iterator = "1.5.0"
|
||||||
file-store = { path = "../file-store" }
|
file-store = { path = "../file-store" }
|
||||||
flate2 = "1.0.25"
|
flate2 = "1.0.28"
|
||||||
fst = "0.4.7"
|
fst = "0.4.7"
|
||||||
memmap2 = "0.7.1"
|
memmap2 = "0.7.1"
|
||||||
milli = { path = "../milli" }
|
milli = { path = "../milli" }
|
||||||
roaring = { version = "0.10.1", features = ["serde"] }
|
roaring = { version = "0.10.2", features = ["serde"] }
|
||||||
serde = { version = "1.0.160", features = ["derive"] }
|
serde = { version = "1.0.195", features = ["derive"] }
|
||||||
serde-cs = "0.2.4"
|
serde-cs = "0.2.4"
|
||||||
serde_json = "1.0.95"
|
serde_json = "1.0.111"
|
||||||
tar = "0.4.38"
|
tar = "0.4.40"
|
||||||
tempfile = "3.5.0"
|
tempfile = "3.9.0"
|
||||||
thiserror = "1.0.40"
|
thiserror = "1.0.56"
|
||||||
time = { version = "0.3.20", features = ["serde-well-known", "formatting", "parsing", "macros"] }
|
time = { version = "0.3.31", features = ["serde-well-known", "formatting", "parsing", "macros"] }
|
||||||
tokio = "1.27"
|
tokio = "1.35"
|
||||||
uuid = { version = "1.3.1", features = ["serde", "v4"] }
|
uuid = { version = "1.6.1", features = ["serde", "v4"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
insta = "1.29.0"
|
insta = "1.34.0"
|
||||||
meili-snap = { path = "../meili-snap" }
|
meili-snap = { path = "../meili-snap" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|||||||
@@ -310,6 +310,8 @@ TooManyVectors , InvalidRequest , BAD_REQUEST ;
|
|||||||
UnretrievableDocument , Internal , BAD_REQUEST ;
|
UnretrievableDocument , Internal , BAD_REQUEST ;
|
||||||
UnretrievableErrorCode , InvalidRequest , BAD_REQUEST ;
|
UnretrievableErrorCode , InvalidRequest , BAD_REQUEST ;
|
||||||
UnsupportedMediaType , InvalidRequest , UNSUPPORTED_MEDIA_TYPE ;
|
UnsupportedMediaType , InvalidRequest , UNSUPPORTED_MEDIA_TYPE ;
|
||||||
|
|
||||||
|
// Experimental features
|
||||||
VectorEmbeddingError , InvalidRequest , BAD_REQUEST
|
VectorEmbeddingError , InvalidRequest , BAD_REQUEST
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,6 +349,9 @@ impl ErrorCode for milli::Error {
|
|||||||
UserError::InvalidFieldForSource { .. }
|
UserError::InvalidFieldForSource { .. }
|
||||||
| UserError::MissingFieldForSource { .. }
|
| UserError::MissingFieldForSource { .. }
|
||||||
| UserError::InvalidOpenAiModel { .. }
|
| UserError::InvalidOpenAiModel { .. }
|
||||||
|
| UserError::InvalidOpenAiModelDimensions { .. }
|
||||||
|
| UserError::InvalidOpenAiModelDimensionsMax { .. }
|
||||||
|
| UserError::InvalidSettingsDimensions { .. }
|
||||||
| UserError::InvalidPrompt(_) => Code::InvalidSettingsEmbedders,
|
| UserError::InvalidPrompt(_) => Code::InvalidSettingsEmbedders,
|
||||||
UserError::TooManyEmbedders(_) => Code::InvalidSettingsEmbedders,
|
UserError::TooManyEmbedders(_) => Code::InvalidSettingsEmbedders,
|
||||||
UserError::InvalidPromptForEmbeddings(..) => Code::InvalidSettingsEmbedders,
|
UserError::InvalidPromptForEmbeddings(..) => Code::InvalidSettingsEmbedders,
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ use serde::{Deserialize, Serialize};
|
|||||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "camelCase", default)]
|
#[serde(rename_all = "camelCase", default)]
|
||||||
pub struct RuntimeTogglableFeatures {
|
pub struct RuntimeTogglableFeatures {
|
||||||
pub score_details: bool,
|
|
||||||
pub vector_store: bool,
|
pub vector_store: bool,
|
||||||
pub metrics: bool,
|
pub metrics: bool,
|
||||||
|
pub logs_route: bool,
|
||||||
pub export_puffin_reports: bool,
|
pub export_puffin_reports: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Copy)]
|
#[derive(Default, Debug, Clone, Copy)]
|
||||||
pub struct InstanceTogglableFeatures {
|
pub struct InstanceTogglableFeatures {
|
||||||
pub metrics: bool,
|
pub metrics: bool,
|
||||||
|
pub logs_route: bool,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,124 +13,126 @@ license.workspace = true
|
|||||||
default-run = "meilisearch"
|
default-run = "meilisearch"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-cors = "0.6.4"
|
actix-cors = "0.7.0"
|
||||||
actix-http = { version = "3.3.1", default-features = false, features = [
|
actix-http = { version = "3.6.0", default-features = false, features = [
|
||||||
"compress-brotli",
|
"compress-brotli",
|
||||||
"compress-gzip",
|
"compress-gzip",
|
||||||
"rustls",
|
"rustls-0_21",
|
||||||
] }
|
] }
|
||||||
actix-utils = "3.0.1"
|
actix-utils = "3.0.1"
|
||||||
actix-web = { version = "4.3.1", default-features = false, features = [
|
actix-web = { version = "4.5.1", default-features = false, features = [
|
||||||
"macros",
|
"macros",
|
||||||
"compress-brotli",
|
"compress-brotli",
|
||||||
"compress-gzip",
|
"compress-gzip",
|
||||||
"cookies",
|
"cookies",
|
||||||
"rustls",
|
"rustls-0_21",
|
||||||
] }
|
] }
|
||||||
actix-web-static-files = { git = "https://github.com/kilork/actix-web-static-files.git", rev = "2d3b6160", optional = true }
|
actix-web-static-files = { git = "https://github.com/kilork/actix-web-static-files.git", rev = "2d3b6160", optional = true }
|
||||||
anyhow = { version = "1.0.70", features = ["backtrace"] }
|
anyhow = { version = "1.0.79", features = ["backtrace"] }
|
||||||
async-stream = "0.3.5"
|
async-stream = "0.3.5"
|
||||||
async-trait = "0.1.68"
|
async-trait = "0.1.77"
|
||||||
bstr = "1.4.0"
|
bstr = "1.9.0"
|
||||||
byte-unit = { version = "4.0.19", default-features = false, features = [
|
byte-unit = { version = "4.0.19", default-features = false, features = [
|
||||||
"std",
|
"std",
|
||||||
"serde",
|
"serde",
|
||||||
] }
|
] }
|
||||||
bytes = "1.4.0"
|
bytes = "1.5.0"
|
||||||
clap = { version = "4.2.1", features = ["derive", "env"] }
|
clap = { version = "4.4.17", features = ["derive", "env"] }
|
||||||
crossbeam-channel = "0.5.8"
|
crossbeam-channel = "0.5.11"
|
||||||
deserr = { version = "0.6.0", features = ["actix-web"] }
|
deserr = { version = "0.6.1", features = ["actix-web"] }
|
||||||
dump = { path = "../dump" }
|
dump = { path = "../dump" }
|
||||||
either = "1.8.1"
|
either = "1.9.0"
|
||||||
env_logger = "0.10.0"
|
|
||||||
file-store = { path = "../file-store" }
|
file-store = { path = "../file-store" }
|
||||||
flate2 = "1.0.25"
|
flate2 = "1.0.28"
|
||||||
fst = "0.4.7"
|
fst = "0.4.7"
|
||||||
futures = "0.3.28"
|
futures = "0.3.30"
|
||||||
futures-util = "0.3.28"
|
futures-util = "0.3.30"
|
||||||
http = "0.2.9"
|
http = "0.2.11"
|
||||||
index-scheduler = { path = "../index-scheduler" }
|
index-scheduler = { path = "../index-scheduler" }
|
||||||
indexmap = { version = "2.0.0", features = ["serde"] }
|
indexmap = { version = "2.1.0", features = ["serde"] }
|
||||||
is-terminal = "0.4.8"
|
is-terminal = "0.4.10"
|
||||||
itertools = "0.11.0"
|
itertools = "0.11.0"
|
||||||
jsonwebtoken = "8.3.0"
|
jsonwebtoken = "9.2.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
log = "0.4.17"
|
|
||||||
meilisearch-auth = { path = "../meilisearch-auth" }
|
meilisearch-auth = { path = "../meilisearch-auth" }
|
||||||
meilisearch-types = { path = "../meilisearch-types" }
|
meilisearch-types = { path = "../meilisearch-types" }
|
||||||
mimalloc = { version = "0.1.37", default-features = false }
|
mimalloc = { version = "0.1.39", default-features = false }
|
||||||
mime = "0.3.17"
|
mime = "0.3.17"
|
||||||
num_cpus = "1.15.0"
|
num_cpus = "1.16.0"
|
||||||
obkv = "0.2.0"
|
obkv = "0.2.1"
|
||||||
once_cell = "1.17.1"
|
once_cell = "1.19.0"
|
||||||
ordered-float = "3.7.0"
|
ordered-float = "4.2.0"
|
||||||
parking_lot = "0.12.1"
|
parking_lot = "0.12.1"
|
||||||
permissive-json-pointer = { path = "../permissive-json-pointer" }
|
permissive-json-pointer = { path = "../permissive-json-pointer" }
|
||||||
pin-project-lite = "0.2.9"
|
pin-project-lite = "0.2.13"
|
||||||
platform-dirs = "0.3.0"
|
platform-dirs = "0.3.0"
|
||||||
prometheus = { version = "0.13.3", features = ["process"] }
|
prometheus = { version = "0.13.3", features = ["process"] }
|
||||||
puffin = { version = "0.16.0", features = ["serialization"] }
|
puffin = { version = "0.16.0", features = ["serialization"] }
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
rayon = "1.7.0"
|
rayon = "1.8.0"
|
||||||
regex = "1.7.3"
|
regex = "1.10.2"
|
||||||
reqwest = { version = "0.11.16", features = [
|
reqwest = { version = "0.11.23", features = [
|
||||||
"rustls-tls",
|
"rustls-tls",
|
||||||
"json",
|
"json",
|
||||||
], default-features = false }
|
], default-features = false }
|
||||||
rustls = "0.20.8"
|
rustls = "0.21.6"
|
||||||
rustls-pemfile = "1.0.2"
|
rustls-pemfile = "1.0.2"
|
||||||
segment = { version = "0.2.2", optional = true }
|
segment = { version = "0.2.3", optional = true }
|
||||||
serde = { version = "1.0.160", features = ["derive"] }
|
serde = { version = "1.0.195", features = ["derive"] }
|
||||||
serde_json = { version = "1.0.95", features = ["preserve_order"] }
|
serde_json = { version = "1.0.111", features = ["preserve_order"] }
|
||||||
sha2 = "0.10.6"
|
sha2 = "0.10.8"
|
||||||
siphasher = "0.3.10"
|
siphasher = "1.0.0"
|
||||||
slice-group-by = "0.3.0"
|
slice-group-by = "0.3.1"
|
||||||
static-files = { version = "0.2.3", optional = true }
|
static-files = { version = "0.2.3", optional = true }
|
||||||
sysinfo = "0.29.7"
|
sysinfo = "0.30.5"
|
||||||
tar = "0.4.38"
|
tar = "0.4.40"
|
||||||
tempfile = "3.5.0"
|
tempfile = "3.9.0"
|
||||||
thiserror = "1.0.40"
|
thiserror = "1.0.56"
|
||||||
time = { version = "0.3.20", features = [
|
time = { version = "0.3.31", features = [
|
||||||
"serde-well-known",
|
"serde-well-known",
|
||||||
"formatting",
|
"formatting",
|
||||||
"parsing",
|
"parsing",
|
||||||
"macros",
|
"macros",
|
||||||
] }
|
] }
|
||||||
tokio = { version = "1.27.0", features = ["full"] }
|
tokio = { version = "1.35.1", features = ["full"] }
|
||||||
tokio-stream = "0.1.12"
|
tokio-stream = "0.1.14"
|
||||||
toml = "0.7.3"
|
toml = "0.8.8"
|
||||||
uuid = { version = "1.3.1", features = ["serde", "v4"] }
|
uuid = { version = "1.6.1", features = ["serde", "v4"] }
|
||||||
walkdir = "2.3.3"
|
walkdir = "2.4.0"
|
||||||
yaup = "0.2.1"
|
yaup = "0.2.1"
|
||||||
serde_urlencoded = "0.7.1"
|
serde_urlencoded = "0.7.1"
|
||||||
termcolor = "1.2.0"
|
termcolor = "1.4.1"
|
||||||
url = { version = "2.5.0", features = ["serde"] }
|
url = { version = "2.5.0", features = ["serde"] }
|
||||||
|
tracing = "0.1.40"
|
||||||
|
tracing-subscriber = "0.3.18"
|
||||||
|
tracing-trace = { version = "0.1.0", path = "../tracing-trace" }
|
||||||
|
tracing-actix-web = "0.7.9"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.8.0"
|
actix-rt = "2.9.0"
|
||||||
assert-json-diff = "2.0.2"
|
assert-json-diff = "2.0.2"
|
||||||
brotli = "3.3.4"
|
brotli = "3.4.0"
|
||||||
insta = "1.29.0"
|
insta = "1.34.0"
|
||||||
manifest-dir-macros = "0.1.16"
|
manifest-dir-macros = "0.1.18"
|
||||||
maplit = "1.0.2"
|
maplit = "1.0.2"
|
||||||
meili-snap = { path = "../meili-snap" }
|
meili-snap = { path = "../meili-snap" }
|
||||||
temp-env = "0.3.3"
|
temp-env = "0.3.6"
|
||||||
urlencoding = "2.1.2"
|
urlencoding = "2.1.3"
|
||||||
yaup = "0.2.1"
|
yaup = "0.2.1"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
anyhow = { version = "1.0.70", optional = true }
|
anyhow = { version = "1.0.79", optional = true }
|
||||||
cargo_toml = { version = "0.15.2", optional = true }
|
cargo_toml = { version = "0.18.0", optional = true }
|
||||||
hex = { version = "0.4.3", optional = true }
|
hex = { version = "0.4.3", optional = true }
|
||||||
reqwest = { version = "0.11.16", features = [
|
reqwest = { version = "0.11.23", features = [
|
||||||
"blocking",
|
"blocking",
|
||||||
"rustls-tls",
|
"rustls-tls",
|
||||||
], default-features = false, optional = true }
|
], default-features = false, optional = true }
|
||||||
sha-1 = { version = "0.10.1", optional = true }
|
sha-1 = { version = "0.10.1", optional = true }
|
||||||
static-files = { version = "0.2.3", optional = true }
|
static-files = { version = "0.2.3", optional = true }
|
||||||
tempfile = { version = "3.5.0", optional = true }
|
tempfile = { version = "3.9.0", optional = true }
|
||||||
vergen = { version = "7.5.1", default-features = false, features = ["git"] }
|
vergen = { version = "7.5.1", default-features = false, features = ["git"] }
|
||||||
zip = { version = "0.6.4", optional = true }
|
zip = { version = "0.6.6", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["analytics", "meilisearch-types/all-tokenizations", "mini-dashboard"]
|
default = ["analytics", "meilisearch-types/all-tokenizations", "mini-dashboard"]
|
||||||
@@ -154,5 +156,5 @@ greek = ["meilisearch-types/greek"]
|
|||||||
khmer = ["meilisearch-types/khmer"]
|
khmer = ["meilisearch-types/khmer"]
|
||||||
|
|
||||||
[package.metadata.mini-dashboard]
|
[package.metadata.mini-dashboard]
|
||||||
assets-url = "https://github.com/meilisearch/mini-dashboard/releases/download/v0.2.12/build.zip"
|
assets-url = "https://github.com/meilisearch/mini-dashboard/releases/download/v0.2.13/build.zip"
|
||||||
sha1 = "acfe9a018c93eb0604ea87ee87bff7df5474e18e"
|
sha1 = "e20cc9b390003c6c844f4b8bcc5c5013191a77ff"
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ use segment::message::{Identify, Track, User};
|
|||||||
use segment::{AutoBatcher, Batcher, HttpClient};
|
use segment::{AutoBatcher, Batcher, HttpClient};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
use sysinfo::{DiskExt, System, SystemExt};
|
use sysinfo::{Disks, System};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
use tokio::select;
|
use tokio::select;
|
||||||
use tokio::sync::mpsc::{self, Receiver, Sender};
|
use tokio::sync::mpsc::{self, Receiver, Sender};
|
||||||
@@ -250,6 +250,7 @@ impl super::Analytics for SegmentAnalytics {
|
|||||||
struct Infos {
|
struct Infos {
|
||||||
env: String,
|
env: String,
|
||||||
experimental_enable_metrics: bool,
|
experimental_enable_metrics: bool,
|
||||||
|
experimental_enable_logs_route: bool,
|
||||||
experimental_reduce_indexing_memory_usage: bool,
|
experimental_reduce_indexing_memory_usage: bool,
|
||||||
experimental_max_number_of_batched_tasks: usize,
|
experimental_max_number_of_batched_tasks: usize,
|
||||||
db_path: bool,
|
db_path: bool,
|
||||||
@@ -287,6 +288,7 @@ impl From<Opt> for Infos {
|
|||||||
let Opt {
|
let Opt {
|
||||||
db_path,
|
db_path,
|
||||||
experimental_enable_metrics,
|
experimental_enable_metrics,
|
||||||
|
experimental_enable_logs_route,
|
||||||
experimental_reduce_indexing_memory_usage,
|
experimental_reduce_indexing_memory_usage,
|
||||||
experimental_max_number_of_batched_tasks,
|
experimental_max_number_of_batched_tasks,
|
||||||
http_addr,
|
http_addr,
|
||||||
@@ -333,6 +335,7 @@ impl From<Opt> for Infos {
|
|||||||
Self {
|
Self {
|
||||||
env,
|
env,
|
||||||
experimental_enable_metrics,
|
experimental_enable_metrics,
|
||||||
|
experimental_enable_logs_route,
|
||||||
experimental_reduce_indexing_memory_usage,
|
experimental_reduce_indexing_memory_usage,
|
||||||
db_path: db_path != PathBuf::from("./data.ms"),
|
db_path: db_path != PathBuf::from("./data.ms"),
|
||||||
import_dump: import_dump.is_some(),
|
import_dump: import_dump.is_some(),
|
||||||
@@ -386,16 +389,17 @@ impl Segment {
|
|||||||
fn compute_traits(opt: &Opt, stats: Stats) -> Value {
|
fn compute_traits(opt: &Opt, stats: Stats) -> Value {
|
||||||
static FIRST_START_TIMESTAMP: Lazy<Instant> = Lazy::new(Instant::now);
|
static FIRST_START_TIMESTAMP: Lazy<Instant> = Lazy::new(Instant::now);
|
||||||
static SYSTEM: Lazy<Value> = Lazy::new(|| {
|
static SYSTEM: Lazy<Value> = Lazy::new(|| {
|
||||||
|
let disks = Disks::new_with_refreshed_list();
|
||||||
let mut sys = System::new_all();
|
let mut sys = System::new_all();
|
||||||
sys.refresh_all();
|
sys.refresh_all();
|
||||||
let kernel_version =
|
let kernel_version = System::kernel_version()
|
||||||
sys.kernel_version().and_then(|k| k.split_once('-').map(|(k, _)| k.to_string()));
|
.and_then(|k| k.split_once('-').map(|(k, _)| k.to_string()));
|
||||||
json!({
|
json!({
|
||||||
"distribution": sys.name(),
|
"distribution": System::name(),
|
||||||
"kernel_version": kernel_version,
|
"kernel_version": kernel_version,
|
||||||
"cores": sys.cpus().len(),
|
"cores": sys.cpus().len(),
|
||||||
"ram_size": sys.total_memory(),
|
"ram_size": sys.total_memory(),
|
||||||
"disk_size": sys.disks().iter().map(|disk| disk.total_space()).max(),
|
"disk_size": disks.iter().map(|disk| disk.total_space()).max(),
|
||||||
"server_provider": std::env::var("MEILI_SERVER_PROVIDER").ok(),
|
"server_provider": std::env::var("MEILI_SERVER_PROVIDER").ok(),
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ pub enum MeilisearchHttpError {
|
|||||||
#[error("A Content-Type header is missing. Accepted values for the Content-Type header are: {}",
|
#[error("A Content-Type header is missing. Accepted values for the Content-Type header are: {}",
|
||||||
.0.iter().map(|s| format!("`{}`", s)).collect::<Vec<_>>().join(", "))]
|
.0.iter().map(|s| format!("`{}`", s)).collect::<Vec<_>>().join(", "))]
|
||||||
MissingContentType(Vec<String>),
|
MissingContentType(Vec<String>),
|
||||||
|
#[error("The `/logs/stream` route is currently in use by someone else.")]
|
||||||
|
AlreadyUsedLogRoute,
|
||||||
#[error("The Content-Type `{0}` does not support the use of a csv delimiter. The csv delimiter can only be used with the Content-Type `text/csv`.")]
|
#[error("The Content-Type `{0}` does not support the use of a csv delimiter. The csv delimiter can only be used with the Content-Type `text/csv`.")]
|
||||||
CsvDelimiterWithWrongContentType(String),
|
CsvDelimiterWithWrongContentType(String),
|
||||||
#[error(
|
#[error(
|
||||||
@@ -59,6 +61,7 @@ impl ErrorCode for MeilisearchHttpError {
|
|||||||
fn error_code(&self) -> Code {
|
fn error_code(&self) -> Code {
|
||||||
match self {
|
match self {
|
||||||
MeilisearchHttpError::MissingContentType(_) => Code::MissingContentType,
|
MeilisearchHttpError::MissingContentType(_) => Code::MissingContentType,
|
||||||
|
MeilisearchHttpError::AlreadyUsedLogRoute => Code::BadRequest,
|
||||||
MeilisearchHttpError::CsvDelimiterWithWrongContentType(_) => Code::InvalidContentType,
|
MeilisearchHttpError::CsvDelimiterWithWrongContentType(_) => Code::InvalidContentType,
|
||||||
MeilisearchHttpError::MissingPayload(_) => Code::MissingPayload,
|
MeilisearchHttpError::MissingPayload(_) => Code::MissingPayload,
|
||||||
MeilisearchHttpError::InvalidContentType(_, _) => Code::InvalidContentType,
|
MeilisearchHttpError::InvalidContentType(_, _) => Code::InvalidContentType,
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ use error::PayloadError;
|
|||||||
use extractors::payload::PayloadConfig;
|
use extractors::payload::PayloadConfig;
|
||||||
use http::header::CONTENT_TYPE;
|
use http::header::CONTENT_TYPE;
|
||||||
use index_scheduler::{IndexScheduler, IndexSchedulerOptions};
|
use index_scheduler::{IndexScheduler, IndexSchedulerOptions};
|
||||||
use log::error;
|
|
||||||
use meilisearch_auth::AuthController;
|
use meilisearch_auth::AuthController;
|
||||||
use meilisearch_types::milli::documents::{DocumentsBatchBuilder, DocumentsBatchReader};
|
use meilisearch_types::milli::documents::{DocumentsBatchBuilder, DocumentsBatchReader};
|
||||||
use meilisearch_types::milli::update::{IndexDocumentsConfig, IndexDocumentsMethod};
|
use meilisearch_types::milli::update::{IndexDocumentsConfig, IndexDocumentsMethod};
|
||||||
@@ -39,6 +38,8 @@ use meilisearch_types::versioning::{check_version_file, create_version_file};
|
|||||||
use meilisearch_types::{compression, milli, VERSION_FILE_NAME};
|
use meilisearch_types::{compression, milli, VERSION_FILE_NAME};
|
||||||
pub use option::Opt;
|
pub use option::Opt;
|
||||||
use option::ScheduleSnapshot;
|
use option::ScheduleSnapshot;
|
||||||
|
use tracing::{error, info_span};
|
||||||
|
use tracing_subscriber::filter::Targets;
|
||||||
|
|
||||||
use crate::error::MeilisearchHttpError;
|
use crate::error::MeilisearchHttpError;
|
||||||
|
|
||||||
@@ -86,10 +87,21 @@ fn is_empty_db(db_path: impl AsRef<Path>) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The handle used to update the logs at runtime. Must be accessible from the `main.rs` and the `route/logs.rs`.
|
||||||
|
pub type LogRouteHandle =
|
||||||
|
tracing_subscriber::reload::Handle<LogRouteType, tracing_subscriber::Registry>;
|
||||||
|
|
||||||
|
pub type LogRouteType = tracing_subscriber::filter::Filtered<
|
||||||
|
Option<Box<dyn tracing_subscriber::Layer<tracing_subscriber::Registry> + Send + Sync>>,
|
||||||
|
Targets,
|
||||||
|
tracing_subscriber::Registry,
|
||||||
|
>;
|
||||||
|
|
||||||
pub fn create_app(
|
pub fn create_app(
|
||||||
index_scheduler: Data<IndexScheduler>,
|
index_scheduler: Data<IndexScheduler>,
|
||||||
auth_controller: Data<AuthController>,
|
auth_controller: Data<AuthController>,
|
||||||
opt: Opt,
|
opt: Opt,
|
||||||
|
logs: LogRouteHandle,
|
||||||
analytics: Arc<dyn Analytics>,
|
analytics: Arc<dyn Analytics>,
|
||||||
enable_dashboard: bool,
|
enable_dashboard: bool,
|
||||||
) -> actix_web::App<
|
) -> actix_web::App<
|
||||||
@@ -108,6 +120,7 @@ pub fn create_app(
|
|||||||
index_scheduler.clone(),
|
index_scheduler.clone(),
|
||||||
auth_controller.clone(),
|
auth_controller.clone(),
|
||||||
&opt,
|
&opt,
|
||||||
|
logs,
|
||||||
analytics.clone(),
|
analytics.clone(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -123,11 +136,49 @@ pub fn create_app(
|
|||||||
.allow_any_method()
|
.allow_any_method()
|
||||||
.max_age(86_400), // 24h
|
.max_age(86_400), // 24h
|
||||||
)
|
)
|
||||||
.wrap(actix_web::middleware::Logger::default())
|
.wrap(tracing_actix_web::TracingLogger::<AwebTracingLogger>::new())
|
||||||
.wrap(actix_web::middleware::Compress::default())
|
.wrap(actix_web::middleware::Compress::default())
|
||||||
.wrap(actix_web::middleware::NormalizePath::new(actix_web::middleware::TrailingSlash::Trim))
|
.wrap(actix_web::middleware::NormalizePath::new(actix_web::middleware::TrailingSlash::Trim))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct AwebTracingLogger;
|
||||||
|
|
||||||
|
impl tracing_actix_web::RootSpanBuilder for AwebTracingLogger {
|
||||||
|
fn on_request_start(request: &actix_web::dev::ServiceRequest) -> tracing::Span {
|
||||||
|
use tracing::field::Empty;
|
||||||
|
|
||||||
|
let conn_info = request.connection_info();
|
||||||
|
let headers = request.headers();
|
||||||
|
let user_agent = headers
|
||||||
|
.get(http::header::USER_AGENT)
|
||||||
|
.map(|value| String::from_utf8_lossy(value.as_bytes()).into_owned())
|
||||||
|
.unwrap_or_default();
|
||||||
|
info_span!("HTTP request", method = %request.method(), host = conn_info.host(), route = %request.path(), query_parameters = %request.query_string(), %user_agent, status_code = Empty, error = Empty)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_request_end<B: MessageBody>(
|
||||||
|
span: tracing::Span,
|
||||||
|
outcome: &Result<ServiceResponse<B>, actix_web::Error>,
|
||||||
|
) {
|
||||||
|
match &outcome {
|
||||||
|
Ok(response) => {
|
||||||
|
let code: i32 = response.response().status().as_u16().into();
|
||||||
|
span.record("status_code", code);
|
||||||
|
|
||||||
|
if let Some(error) = response.response().error() {
|
||||||
|
// use the status code already constructed for the outgoing HTTP response
|
||||||
|
span.record("error", &tracing::field::display(error.as_response_error()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
let code: i32 = error.error_response().status().as_u16().into();
|
||||||
|
span.record("status_code", code);
|
||||||
|
span.record("error", &tracing::field::display(error.as_response_error()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum OnFailure {
|
enum OnFailure {
|
||||||
RemoveDb,
|
RemoveDb,
|
||||||
KeepDb,
|
KeepDb,
|
||||||
@@ -280,15 +331,15 @@ fn import_dump(
|
|||||||
let mut dump_reader = dump::DumpReader::open(reader)?;
|
let mut dump_reader = dump::DumpReader::open(reader)?;
|
||||||
|
|
||||||
if let Some(date) = dump_reader.date() {
|
if let Some(date) = dump_reader.date() {
|
||||||
log::info!(
|
tracing::info!(
|
||||||
"Importing a dump of meilisearch `{:?}` from the {}",
|
version = ?dump_reader.version(), // TODO: get the meilisearch version instead of the dump version
|
||||||
dump_reader.version(), // TODO: get the meilisearch version instead of the dump version
|
%date,
|
||||||
date
|
"Importing a dump of meilisearch"
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
log::info!(
|
tracing::info!(
|
||||||
"Importing a dump of meilisearch `{:?}`",
|
version = ?dump_reader.version(), // TODO: get the meilisearch version instead of the dump version
|
||||||
dump_reader.version(), // TODO: get the meilisearch version instead of the dump version
|
"Importing a dump of meilisearch",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,7 +373,7 @@ fn import_dump(
|
|||||||
for index_reader in dump_reader.indexes()? {
|
for index_reader in dump_reader.indexes()? {
|
||||||
let mut index_reader = index_reader?;
|
let mut index_reader = index_reader?;
|
||||||
let metadata = index_reader.metadata();
|
let metadata = index_reader.metadata();
|
||||||
log::info!("Importing index `{}`.", metadata.uid);
|
tracing::info!("Importing index `{}`.", metadata.uid);
|
||||||
|
|
||||||
let date = Some((metadata.created_at, metadata.updated_at));
|
let date = Some((metadata.created_at, metadata.updated_at));
|
||||||
let index = index_scheduler.create_raw_index(&metadata.uid, date)?;
|
let index = index_scheduler.create_raw_index(&metadata.uid, date)?;
|
||||||
@@ -336,14 +387,15 @@ fn import_dump(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 4.2 Import the settings.
|
// 4.2 Import the settings.
|
||||||
log::info!("Importing the settings.");
|
tracing::info!("Importing the settings.");
|
||||||
let settings = index_reader.settings()?;
|
let settings = index_reader.settings()?;
|
||||||
apply_settings_to_builder(&settings, &mut builder);
|
apply_settings_to_builder(&settings, &mut builder);
|
||||||
builder.execute(|indexing_step| log::debug!("update: {:?}", indexing_step), || false)?;
|
builder
|
||||||
|
.execute(|indexing_step| tracing::debug!("update: {:?}", indexing_step), || false)?;
|
||||||
|
|
||||||
// 4.3 Import the documents.
|
// 4.3 Import the documents.
|
||||||
// 4.3.1 We need to recreate the grenad+obkv format accepted by the index.
|
// 4.3.1 We need to recreate the grenad+obkv format accepted by the index.
|
||||||
log::info!("Importing the documents.");
|
tracing::info!("Importing the documents.");
|
||||||
let file = tempfile::tempfile()?;
|
let file = tempfile::tempfile()?;
|
||||||
let mut builder = DocumentsBatchBuilder::new(BufWriter::new(file));
|
let mut builder = DocumentsBatchBuilder::new(BufWriter::new(file));
|
||||||
for document in index_reader.documents()? {
|
for document in index_reader.documents()? {
|
||||||
@@ -365,15 +417,16 @@ fn import_dump(
|
|||||||
update_method: IndexDocumentsMethod::ReplaceDocuments,
|
update_method: IndexDocumentsMethod::ReplaceDocuments,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|indexing_step| log::trace!("update: {:?}", indexing_step),
|
|indexing_step| tracing::trace!("update: {:?}", indexing_step),
|
||||||
|| false,
|
|| false,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let (builder, user_result) = builder.add_documents(reader)?;
|
let (builder, user_result) = builder.add_documents(reader)?;
|
||||||
log::info!("{} documents found.", user_result?);
|
let user_result = user_result?;
|
||||||
|
tracing::info!(documents_found = user_result, "{} documents found.", user_result);
|
||||||
builder.execute()?;
|
builder.execute()?;
|
||||||
wtxn.commit()?;
|
wtxn.commit()?;
|
||||||
log::info!("All documents successfully imported.");
|
tracing::info!("All documents successfully imported.");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut index_scheduler_dump = index_scheduler.register_dumped_task()?;
|
let mut index_scheduler_dump = index_scheduler.register_dumped_task()?;
|
||||||
@@ -391,6 +444,7 @@ pub fn configure_data(
|
|||||||
index_scheduler: Data<IndexScheduler>,
|
index_scheduler: Data<IndexScheduler>,
|
||||||
auth: Data<AuthController>,
|
auth: Data<AuthController>,
|
||||||
opt: &Opt,
|
opt: &Opt,
|
||||||
|
logs: LogRouteHandle,
|
||||||
analytics: Arc<dyn Analytics>,
|
analytics: Arc<dyn Analytics>,
|
||||||
) {
|
) {
|
||||||
let http_payload_size_limit = opt.http_payload_size_limit.get_bytes() as usize;
|
let http_payload_size_limit = opt.http_payload_size_limit.get_bytes() as usize;
|
||||||
@@ -398,6 +452,7 @@ pub fn configure_data(
|
|||||||
.app_data(index_scheduler)
|
.app_data(index_scheduler)
|
||||||
.app_data(auth)
|
.app_data(auth)
|
||||||
.app_data(web::Data::from(analytics))
|
.app_data(web::Data::from(analytics))
|
||||||
|
.app_data(web::Data::new(logs))
|
||||||
.app_data(
|
.app_data(
|
||||||
web::JsonConfig::default()
|
web::JsonConfig::default()
|
||||||
.limit(http_payload_size_limit)
|
.limit(http_payload_size_limit)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::env;
|
use std::env;
|
||||||
use std::io::{stderr, Write};
|
use std::io::{stderr, Write};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use actix_web::http::KeepAlive;
|
use actix_web::http::KeepAlive;
|
||||||
@@ -9,37 +10,60 @@ use actix_web::HttpServer;
|
|||||||
use index_scheduler::IndexScheduler;
|
use index_scheduler::IndexScheduler;
|
||||||
use is_terminal::IsTerminal;
|
use is_terminal::IsTerminal;
|
||||||
use meilisearch::analytics::Analytics;
|
use meilisearch::analytics::Analytics;
|
||||||
use meilisearch::{analytics, create_app, prototype_name, setup_meilisearch, Opt};
|
use meilisearch::{
|
||||||
|
analytics, create_app, prototype_name, setup_meilisearch, LogRouteHandle, LogRouteType, Opt,
|
||||||
|
};
|
||||||
use meilisearch_auth::{generate_master_key, AuthController, MASTER_KEY_MIN_SIZE};
|
use meilisearch_auth::{generate_master_key, AuthController, MASTER_KEY_MIN_SIZE};
|
||||||
|
use mimalloc::MiMalloc;
|
||||||
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
|
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
|
||||||
|
use tracing::level_filters::LevelFilter;
|
||||||
|
use tracing_subscriber::layer::SubscriberExt as _;
|
||||||
|
use tracing_subscriber::Layer;
|
||||||
|
|
||||||
#[global_allocator]
|
#[global_allocator]
|
||||||
static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
static ALLOC: MiMalloc = MiMalloc;
|
||||||
|
|
||||||
|
fn default_layer() -> LogRouteType {
|
||||||
|
None.with_filter(tracing_subscriber::filter::Targets::new().with_target("", LevelFilter::OFF))
|
||||||
|
}
|
||||||
|
|
||||||
/// does all the setup before meilisearch is launched
|
/// does all the setup before meilisearch is launched
|
||||||
fn setup(opt: &Opt) -> anyhow::Result<()> {
|
fn setup(opt: &Opt) -> anyhow::Result<LogRouteHandle> {
|
||||||
let mut log_builder = env_logger::Builder::new();
|
let (route_layer, route_layer_handle) = tracing_subscriber::reload::Layer::new(default_layer());
|
||||||
let log_filters = format!(
|
let route_layer: tracing_subscriber::reload::Layer<_, _> = route_layer;
|
||||||
"{},h2=warn,hyper=warn,tokio_util=warn,tracing=warn,rustls=warn,mio=warn,reqwest=warn",
|
|
||||||
opt.log_level
|
let subscriber = tracing_subscriber::registry().with(route_layer).with(
|
||||||
|
tracing_subscriber::fmt::layer()
|
||||||
|
.with_span_events(tracing_subscriber::fmt::format::FmtSpan::CLOSE)
|
||||||
|
.with_filter(
|
||||||
|
tracing_subscriber::filter::LevelFilter::from_str(&opt.log_level.to_string())
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
log_builder.parse_filters(&log_filters);
|
|
||||||
|
|
||||||
log_builder.init();
|
// set the subscriber as the default for the application
|
||||||
|
tracing::subscriber::set_global_default(subscriber).unwrap();
|
||||||
|
|
||||||
Ok(())
|
Ok(route_layer_handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_panic(info: &std::panic::PanicInfo) {
|
||||||
|
let info = info.to_string().replace('\n', " ");
|
||||||
|
tracing::error!(%info);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
let (opt, config_read_from) = Opt::try_build()?;
|
let (opt, config_read_from) = Opt::try_build()?;
|
||||||
|
|
||||||
|
std::panic::set_hook(Box::new(on_panic));
|
||||||
|
|
||||||
anyhow::ensure!(
|
anyhow::ensure!(
|
||||||
!(cfg!(windows) && opt.experimental_reduce_indexing_memory_usage),
|
!(cfg!(windows) && opt.experimental_reduce_indexing_memory_usage),
|
||||||
"The `experimental-reduce-indexing-memory-usage` flag is not supported on Windows"
|
"The `experimental-reduce-indexing-memory-usage` flag is not supported on Windows"
|
||||||
);
|
);
|
||||||
|
|
||||||
setup(&opt)?;
|
let log_handle = setup(&opt)?;
|
||||||
|
|
||||||
match (opt.env.as_ref(), &opt.master_key) {
|
match (opt.env.as_ref(), &opt.master_key) {
|
||||||
("production", Some(master_key)) if master_key.len() < MASTER_KEY_MIN_SIZE => {
|
("production", Some(master_key)) if master_key.len() < MASTER_KEY_MIN_SIZE => {
|
||||||
@@ -77,7 +101,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
print_launch_resume(&opt, analytics.clone(), config_read_from);
|
print_launch_resume(&opt, analytics.clone(), config_read_from);
|
||||||
|
|
||||||
run_http(index_scheduler, auth_controller, opt, analytics).await?;
|
run_http(index_scheduler, auth_controller, opt, log_handle, analytics).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -86,6 +110,7 @@ async fn run_http(
|
|||||||
index_scheduler: Arc<IndexScheduler>,
|
index_scheduler: Arc<IndexScheduler>,
|
||||||
auth_controller: Arc<AuthController>,
|
auth_controller: Arc<AuthController>,
|
||||||
opt: Opt,
|
opt: Opt,
|
||||||
|
logs: LogRouteHandle,
|
||||||
analytics: Arc<dyn Analytics>,
|
analytics: Arc<dyn Analytics>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let enable_dashboard = &opt.env == "development";
|
let enable_dashboard = &opt.env == "development";
|
||||||
@@ -98,6 +123,7 @@ async fn run_http(
|
|||||||
index_scheduler.clone(),
|
index_scheduler.clone(),
|
||||||
auth_controller.clone(),
|
auth_controller.clone(),
|
||||||
opt.clone(),
|
opt.clone(),
|
||||||
|
logs.clone(),
|
||||||
analytics.clone(),
|
analytics.clone(),
|
||||||
enable_dashboard,
|
enable_dashboard,
|
||||||
)
|
)
|
||||||
@@ -107,7 +133,7 @@ async fn run_http(
|
|||||||
.keep_alive(KeepAlive::Os);
|
.keep_alive(KeepAlive::Os);
|
||||||
|
|
||||||
if let Some(config) = opt_clone.get_ssl_config()? {
|
if let Some(config) = opt_clone.get_ssl_config()? {
|
||||||
http_server.bind_rustls(opt_clone.http_addr, config)?.run().await?;
|
http_server.bind_rustls_021(opt_clone.http_addr, config)?.run().await?;
|
||||||
} else {
|
} else {
|
||||||
http_server.bind(&opt_clone.http_addr)?.run().await?;
|
http_server.bind(&opt_clone.http_addr)?.run().await?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ use rustls::server::{
|
|||||||
use rustls::RootCertStore;
|
use rustls::RootCertStore;
|
||||||
use rustls_pemfile::{certs, pkcs8_private_keys, rsa_private_keys};
|
use rustls_pemfile::{certs, pkcs8_private_keys, rsa_private_keys};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sysinfo::{RefreshKind, System, SystemExt};
|
use sysinfo::{MemoryRefreshKind, RefreshKind, System};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
const POSSIBLE_ENV: [&str; 2] = ["development", "production"];
|
const POSSIBLE_ENV: [&str; 2] = ["development", "production"];
|
||||||
@@ -51,6 +51,7 @@ const MEILI_IGNORE_MISSING_DUMP: &str = "MEILI_IGNORE_MISSING_DUMP";
|
|||||||
const MEILI_IGNORE_DUMP_IF_DB_EXISTS: &str = "MEILI_IGNORE_DUMP_IF_DB_EXISTS";
|
const MEILI_IGNORE_DUMP_IF_DB_EXISTS: &str = "MEILI_IGNORE_DUMP_IF_DB_EXISTS";
|
||||||
const MEILI_DUMP_DIR: &str = "MEILI_DUMP_DIR";
|
const MEILI_DUMP_DIR: &str = "MEILI_DUMP_DIR";
|
||||||
const MEILI_LOG_LEVEL: &str = "MEILI_LOG_LEVEL";
|
const MEILI_LOG_LEVEL: &str = "MEILI_LOG_LEVEL";
|
||||||
|
const MEILI_EXPERIMENTAL_ENABLE_LOGS_ROUTE: &str = "MEILI_EXPERIMENTAL_ENABLE_LOGS_ROUTE";
|
||||||
const MEILI_EXPERIMENTAL_ENABLE_METRICS: &str = "MEILI_EXPERIMENTAL_ENABLE_METRICS";
|
const MEILI_EXPERIMENTAL_ENABLE_METRICS: &str = "MEILI_EXPERIMENTAL_ENABLE_METRICS";
|
||||||
const MEILI_EXPERIMENTAL_REDUCE_INDEXING_MEMORY_USAGE: &str =
|
const MEILI_EXPERIMENTAL_REDUCE_INDEXING_MEMORY_USAGE: &str =
|
||||||
"MEILI_EXPERIMENTAL_REDUCE_INDEXING_MEMORY_USAGE";
|
"MEILI_EXPERIMENTAL_REDUCE_INDEXING_MEMORY_USAGE";
|
||||||
@@ -309,6 +310,13 @@ pub struct Opt {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub experimental_enable_metrics: bool,
|
pub experimental_enable_metrics: bool,
|
||||||
|
|
||||||
|
/// Experimental logs route feature. For more information, see: <https://github.com/orgs/meilisearch/discussions/721>
|
||||||
|
///
|
||||||
|
/// Enables the log route on the `POST /logs/stream` endpoint and the `DELETE /logs/stream` to stop receiving logs.
|
||||||
|
#[clap(long, env = MEILI_EXPERIMENTAL_ENABLE_LOGS_ROUTE)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub experimental_enable_logs_route: bool,
|
||||||
|
|
||||||
/// Experimental RAM reduction during indexing, do not use in production, see: <https://github.com/meilisearch/product/discussions/652>
|
/// Experimental RAM reduction during indexing, do not use in production, see: <https://github.com/meilisearch/product/discussions/652>
|
||||||
#[clap(long, env = MEILI_EXPERIMENTAL_REDUCE_INDEXING_MEMORY_USAGE)]
|
#[clap(long, env = MEILI_EXPERIMENTAL_REDUCE_INDEXING_MEMORY_USAGE)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -414,6 +422,7 @@ impl Opt {
|
|||||||
#[cfg(feature = "analytics")]
|
#[cfg(feature = "analytics")]
|
||||||
no_analytics,
|
no_analytics,
|
||||||
experimental_enable_metrics,
|
experimental_enable_metrics,
|
||||||
|
experimental_enable_logs_route,
|
||||||
experimental_reduce_indexing_memory_usage,
|
experimental_reduce_indexing_memory_usage,
|
||||||
} = self;
|
} = self;
|
||||||
export_to_env_if_not_present(MEILI_DB_PATH, db_path);
|
export_to_env_if_not_present(MEILI_DB_PATH, db_path);
|
||||||
@@ -470,6 +479,10 @@ impl Opt {
|
|||||||
MEILI_EXPERIMENTAL_ENABLE_METRICS,
|
MEILI_EXPERIMENTAL_ENABLE_METRICS,
|
||||||
experimental_enable_metrics.to_string(),
|
experimental_enable_metrics.to_string(),
|
||||||
);
|
);
|
||||||
|
export_to_env_if_not_present(
|
||||||
|
MEILI_EXPERIMENTAL_ENABLE_LOGS_ROUTE,
|
||||||
|
experimental_enable_logs_route.to_string(),
|
||||||
|
);
|
||||||
export_to_env_if_not_present(
|
export_to_env_if_not_present(
|
||||||
MEILI_EXPERIMENTAL_REDUCE_INDEXING_MEMORY_USAGE,
|
MEILI_EXPERIMENTAL_REDUCE_INDEXING_MEMORY_USAGE,
|
||||||
experimental_reduce_indexing_memory_usage.to_string(),
|
experimental_reduce_indexing_memory_usage.to_string(),
|
||||||
@@ -490,11 +503,11 @@ impl Opt {
|
|||||||
}
|
}
|
||||||
if self.ssl_require_auth {
|
if self.ssl_require_auth {
|
||||||
let verifier = AllowAnyAuthenticatedClient::new(client_auth_roots);
|
let verifier = AllowAnyAuthenticatedClient::new(client_auth_roots);
|
||||||
config.with_client_cert_verifier(verifier)
|
config.with_client_cert_verifier(Arc::from(verifier))
|
||||||
} else {
|
} else {
|
||||||
let verifier =
|
let verifier =
|
||||||
AllowAnyAnonymousOrAuthenticatedClient::new(client_auth_roots);
|
AllowAnyAnonymousOrAuthenticatedClient::new(client_auth_roots);
|
||||||
config.with_client_cert_verifier(verifier)
|
config.with_client_cert_verifier(Arc::from(verifier))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => config.with_no_client_auth(),
|
None => config.with_no_client_auth(),
|
||||||
@@ -524,7 +537,10 @@ impl Opt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn to_instance_features(&self) -> InstanceTogglableFeatures {
|
pub(crate) fn to_instance_features(&self) -> InstanceTogglableFeatures {
|
||||||
InstanceTogglableFeatures { metrics: self.experimental_enable_metrics }
|
InstanceTogglableFeatures {
|
||||||
|
metrics: self.experimental_enable_metrics,
|
||||||
|
logs_route: self.experimental_enable_logs_route,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -633,8 +649,8 @@ impl MaxMemory {
|
|||||||
|
|
||||||
/// Returns the total amount of bytes available or `None` if this system isn't supported.
|
/// Returns the total amount of bytes available or `None` if this system isn't supported.
|
||||||
fn total_memory_bytes() -> Option<u64> {
|
fn total_memory_bytes() -> Option<u64> {
|
||||||
if System::IS_SUPPORTED {
|
if sysinfo::IS_SUPPORTED_SYSTEM {
|
||||||
let memory_kind = RefreshKind::new().with_memory();
|
let memory_kind = RefreshKind::new().with_memory(MemoryRefreshKind::new().with_ram());
|
||||||
let mut system = System::new_with_specifics(memory_kind);
|
let mut system = System::new_with_specifics(memory_kind);
|
||||||
system.refresh_memory();
|
system.refresh_memory();
|
||||||
Some(system.total_memory())
|
Some(system.total_memory())
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
use actix_web::{web, HttpRequest, HttpResponse};
|
use actix_web::{web, HttpRequest, HttpResponse};
|
||||||
use index_scheduler::IndexScheduler;
|
use index_scheduler::IndexScheduler;
|
||||||
use log::debug;
|
|
||||||
use meilisearch_auth::AuthController;
|
use meilisearch_auth::AuthController;
|
||||||
use meilisearch_types::error::ResponseError;
|
use meilisearch_types::error::ResponseError;
|
||||||
use meilisearch_types::tasks::KindWithContent;
|
use meilisearch_types::tasks::KindWithContent;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::analytics::Analytics;
|
use crate::analytics::Analytics;
|
||||||
use crate::extractors::authentication::policies::*;
|
use crate::extractors::authentication::policies::*;
|
||||||
@@ -32,6 +32,6 @@ pub async fn create_dump(
|
|||||||
let task: SummarizedTaskView =
|
let task: SummarizedTaskView =
|
||||||
tokio::task::spawn_blocking(move || index_scheduler.register(task)).await??.into();
|
tokio::task::spawn_blocking(move || index_scheduler.register(task)).await??.into();
|
||||||
|
|
||||||
debug!("returns: {:?}", task);
|
debug!(returns = ?task, "Create dump");
|
||||||
Ok(HttpResponse::Accepted().json(task))
|
Ok(HttpResponse::Accepted().json(task))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ use actix_web::{HttpRequest, HttpResponse};
|
|||||||
use deserr::actix_web::AwebJson;
|
use deserr::actix_web::AwebJson;
|
||||||
use deserr::Deserr;
|
use deserr::Deserr;
|
||||||
use index_scheduler::IndexScheduler;
|
use index_scheduler::IndexScheduler;
|
||||||
use log::debug;
|
|
||||||
use meilisearch_types::deserr::DeserrJsonError;
|
use meilisearch_types::deserr::DeserrJsonError;
|
||||||
use meilisearch_types::error::ResponseError;
|
use meilisearch_types::error::ResponseError;
|
||||||
use meilisearch_types::keys::actions;
|
use meilisearch_types::keys::actions;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::analytics::Analytics;
|
use crate::analytics::Analytics;
|
||||||
use crate::extractors::authentication::policies::ActionPolicy;
|
use crate::extractors::authentication::policies::ActionPolicy;
|
||||||
@@ -33,20 +33,21 @@ async fn get_features(
|
|||||||
let features = index_scheduler.features();
|
let features = index_scheduler.features();
|
||||||
|
|
||||||
analytics.publish("Experimental features Seen".to_string(), json!(null), Some(&req));
|
analytics.publish("Experimental features Seen".to_string(), json!(null), Some(&req));
|
||||||
debug!("returns: {:?}", features.runtime_features());
|
let features = features.runtime_features();
|
||||||
HttpResponse::Ok().json(features.runtime_features())
|
debug!(returns = ?features, "Get features");
|
||||||
|
HttpResponse::Ok().json(features)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserr)]
|
#[derive(Debug, Deserr)]
|
||||||
#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)]
|
#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)]
|
||||||
pub struct RuntimeTogglableFeatures {
|
pub struct RuntimeTogglableFeatures {
|
||||||
#[deserr(default)]
|
|
||||||
pub score_details: Option<bool>,
|
|
||||||
#[deserr(default)]
|
#[deserr(default)]
|
||||||
pub vector_store: Option<bool>,
|
pub vector_store: Option<bool>,
|
||||||
#[deserr(default)]
|
#[deserr(default)]
|
||||||
pub metrics: Option<bool>,
|
pub metrics: Option<bool>,
|
||||||
#[deserr(default)]
|
#[deserr(default)]
|
||||||
|
pub logs_route: Option<bool>,
|
||||||
|
#[deserr(default)]
|
||||||
pub export_puffin_reports: Option<bool>,
|
pub export_puffin_reports: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,12 +61,13 @@ async fn patch_features(
|
|||||||
analytics: Data<dyn Analytics>,
|
analytics: Data<dyn Analytics>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
let features = index_scheduler.features();
|
let features = index_scheduler.features();
|
||||||
|
debug!(parameters = ?new_features, "Patch features");
|
||||||
|
|
||||||
let old_features = features.runtime_features();
|
let old_features = features.runtime_features();
|
||||||
let new_features = meilisearch_types::features::RuntimeTogglableFeatures {
|
let new_features = meilisearch_types::features::RuntimeTogglableFeatures {
|
||||||
score_details: new_features.0.score_details.unwrap_or(old_features.score_details),
|
|
||||||
vector_store: new_features.0.vector_store.unwrap_or(old_features.vector_store),
|
vector_store: new_features.0.vector_store.unwrap_or(old_features.vector_store),
|
||||||
metrics: new_features.0.metrics.unwrap_or(old_features.metrics),
|
metrics: new_features.0.metrics.unwrap_or(old_features.metrics),
|
||||||
|
logs_route: new_features.0.logs_route.unwrap_or(old_features.logs_route),
|
||||||
export_puffin_reports: new_features
|
export_puffin_reports: new_features
|
||||||
.0
|
.0
|
||||||
.export_puffin_reports
|
.export_puffin_reports
|
||||||
@@ -76,22 +78,23 @@ async fn patch_features(
|
|||||||
// the it renames to camelCase, which we don't want for analytics.
|
// the it renames to camelCase, which we don't want for analytics.
|
||||||
// **Do not** ignore fields with `..` or `_` here, because we want to add them in the future.
|
// **Do not** ignore fields with `..` or `_` here, because we want to add them in the future.
|
||||||
let meilisearch_types::features::RuntimeTogglableFeatures {
|
let meilisearch_types::features::RuntimeTogglableFeatures {
|
||||||
score_details,
|
|
||||||
vector_store,
|
vector_store,
|
||||||
metrics,
|
metrics,
|
||||||
|
logs_route,
|
||||||
export_puffin_reports,
|
export_puffin_reports,
|
||||||
} = new_features;
|
} = new_features;
|
||||||
|
|
||||||
analytics.publish(
|
analytics.publish(
|
||||||
"Experimental features Updated".to_string(),
|
"Experimental features Updated".to_string(),
|
||||||
json!({
|
json!({
|
||||||
"score_details": score_details,
|
|
||||||
"vector_store": vector_store,
|
"vector_store": vector_store,
|
||||||
"metrics": metrics,
|
"metrics": metrics,
|
||||||
|
"logs_route": logs_route,
|
||||||
"export_puffin_reports": export_puffin_reports,
|
"export_puffin_reports": export_puffin_reports,
|
||||||
}),
|
}),
|
||||||
Some(&req),
|
Some(&req),
|
||||||
);
|
);
|
||||||
index_scheduler.put_runtime_features(new_features)?;
|
index_scheduler.put_runtime_features(new_features)?;
|
||||||
|
debug!(returns = ?new_features, "Patch features");
|
||||||
Ok(HttpResponse::Ok().json(new_features))
|
Ok(HttpResponse::Ok().json(new_features))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ use deserr::actix_web::{AwebJson, AwebQueryParameter};
|
|||||||
use deserr::Deserr;
|
use deserr::Deserr;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use index_scheduler::IndexScheduler;
|
use index_scheduler::IndexScheduler;
|
||||||
use log::debug;
|
|
||||||
use meilisearch_types::deserr::query_params::Param;
|
use meilisearch_types::deserr::query_params::Param;
|
||||||
use meilisearch_types::deserr::{DeserrJsonError, DeserrQueryParamError};
|
use meilisearch_types::deserr::{DeserrJsonError, DeserrQueryParamError};
|
||||||
use meilisearch_types::document_formats::{read_csv, read_json, read_ndjson, PayloadType};
|
use meilisearch_types::document_formats::{read_csv, read_json, read_ndjson, PayloadType};
|
||||||
@@ -28,6 +27,7 @@ use serde_json::Value;
|
|||||||
use tempfile::tempfile;
|
use tempfile::tempfile;
|
||||||
use tokio::fs::File;
|
use tokio::fs::File;
|
||||||
use tokio::io::{AsyncSeekExt, AsyncWriteExt, BufWriter};
|
use tokio::io::{AsyncSeekExt, AsyncWriteExt, BufWriter};
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::analytics::{Analytics, DocumentDeletionKind, DocumentFetchKind};
|
use crate::analytics::{Analytics, DocumentDeletionKind, DocumentFetchKind};
|
||||||
use crate::error::MeilisearchHttpError;
|
use crate::error::MeilisearchHttpError;
|
||||||
@@ -101,6 +101,7 @@ pub async fn get_document(
|
|||||||
analytics: web::Data<dyn Analytics>,
|
analytics: web::Data<dyn Analytics>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
let DocumentParam { index_uid, document_id } = document_param.into_inner();
|
let DocumentParam { index_uid, document_id } = document_param.into_inner();
|
||||||
|
debug!(parameters = ?params, "Get document");
|
||||||
let index_uid = IndexUid::try_from(index_uid)?;
|
let index_uid = IndexUid::try_from(index_uid)?;
|
||||||
|
|
||||||
analytics.get_fetch_documents(&DocumentFetchKind::PerDocumentId, &req);
|
analytics.get_fetch_documents(&DocumentFetchKind::PerDocumentId, &req);
|
||||||
@@ -110,7 +111,7 @@ pub async fn get_document(
|
|||||||
|
|
||||||
let index = index_scheduler.index(&index_uid)?;
|
let index = index_scheduler.index(&index_uid)?;
|
||||||
let document = retrieve_document(&index, &document_id, attributes_to_retrieve)?;
|
let document = retrieve_document(&index, &document_id, attributes_to_retrieve)?;
|
||||||
debug!("returns: {:?}", document);
|
debug!(returns = ?document, "Get document");
|
||||||
Ok(HttpResponse::Ok().json(document))
|
Ok(HttpResponse::Ok().json(document))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +132,7 @@ pub async fn delete_document(
|
|||||||
};
|
};
|
||||||
let task: SummarizedTaskView =
|
let task: SummarizedTaskView =
|
||||||
tokio::task::spawn_blocking(move || index_scheduler.register(task)).await??.into();
|
tokio::task::spawn_blocking(move || index_scheduler.register(task)).await??.into();
|
||||||
debug!("returns: {:?}", task);
|
debug!(returns = ?task, "Delete document");
|
||||||
Ok(HttpResponse::Accepted().json(task))
|
Ok(HttpResponse::Accepted().json(task))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,9 +169,8 @@ pub async fn documents_by_query_post(
|
|||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
analytics: web::Data<dyn Analytics>,
|
analytics: web::Data<dyn Analytics>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
debug!("called with body: {:?}", body);
|
|
||||||
|
|
||||||
let body = body.into_inner();
|
let body = body.into_inner();
|
||||||
|
debug!(parameters = ?body, "Get documents POST");
|
||||||
|
|
||||||
analytics.post_fetch_documents(
|
analytics.post_fetch_documents(
|
||||||
&DocumentFetchKind::Normal {
|
&DocumentFetchKind::Normal {
|
||||||
@@ -191,7 +191,7 @@ pub async fn get_documents(
|
|||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
analytics: web::Data<dyn Analytics>,
|
analytics: web::Data<dyn Analytics>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
debug!("called with params: {:?}", params);
|
debug!(parameters = ?params, "Get documents GET");
|
||||||
|
|
||||||
let BrowseQueryGet { limit, offset, fields, filter } = params.into_inner();
|
let BrowseQueryGet { limit, offset, fields, filter } = params.into_inner();
|
||||||
|
|
||||||
@@ -235,7 +235,7 @@ fn documents_by_query(
|
|||||||
|
|
||||||
let ret = PaginationView::new(offset, limit, total as usize, documents);
|
let ret = PaginationView::new(offset, limit, total as usize, documents);
|
||||||
|
|
||||||
debug!("returns: {:?}", ret);
|
debug!(returns = ?ret, "Get documents");
|
||||||
Ok(HttpResponse::Ok().json(ret))
|
Ok(HttpResponse::Ok().json(ret))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,7 +271,7 @@ pub async fn replace_documents(
|
|||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
||||||
|
|
||||||
debug!("called with params: {:?}", params);
|
debug!(parameters = ?params, "Replace documents");
|
||||||
let params = params.into_inner();
|
let params = params.into_inner();
|
||||||
|
|
||||||
analytics.add_documents(¶ms, index_scheduler.index(&index_uid).is_err(), &req);
|
analytics.add_documents(¶ms, index_scheduler.index(&index_uid).is_err(), &req);
|
||||||
@@ -288,6 +288,7 @@ pub async fn replace_documents(
|
|||||||
allow_index_creation,
|
allow_index_creation,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
debug!(returns = ?task, "Replace documents");
|
||||||
|
|
||||||
Ok(HttpResponse::Accepted().json(task))
|
Ok(HttpResponse::Accepted().json(task))
|
||||||
}
|
}
|
||||||
@@ -302,8 +303,8 @@ pub async fn update_documents(
|
|||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
||||||
|
|
||||||
debug!("called with params: {:?}", params);
|
|
||||||
let params = params.into_inner();
|
let params = params.into_inner();
|
||||||
|
debug!(parameters = ?params, "Update documents");
|
||||||
|
|
||||||
analytics.update_documents(¶ms, index_scheduler.index(&index_uid).is_err(), &req);
|
analytics.update_documents(¶ms, index_scheduler.index(&index_uid).is_err(), &req);
|
||||||
|
|
||||||
@@ -319,6 +320,7 @@ pub async fn update_documents(
|
|||||||
allow_index_creation,
|
allow_index_creation,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
debug!(returns = ?task, "Update documents");
|
||||||
|
|
||||||
Ok(HttpResponse::Accepted().json(task))
|
Ok(HttpResponse::Accepted().json(task))
|
||||||
}
|
}
|
||||||
@@ -427,7 +429,10 @@ async fn document_addition(
|
|||||||
Err(index_scheduler::Error::FileStore(file_store::Error::IoError(e)))
|
Err(index_scheduler::Error::FileStore(file_store::Error::IoError(e)))
|
||||||
if e.kind() == ErrorKind::NotFound => {}
|
if e.kind() == ErrorKind::NotFound => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::warn!("Unknown error happened while deleting a malformed update file with uuid {uuid}: {e}");
|
tracing::warn!(
|
||||||
|
index_uuid = %uuid,
|
||||||
|
"Unknown error happened while deleting a malformed update file: {e}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// We still want to return the original error to the end user.
|
// We still want to return the original error to the end user.
|
||||||
@@ -453,7 +458,6 @@ async fn document_addition(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!("returns: {:?}", task);
|
|
||||||
Ok(task.into())
|
Ok(task.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,7 +468,7 @@ pub async fn delete_documents_batch(
|
|||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
analytics: web::Data<dyn Analytics>,
|
analytics: web::Data<dyn Analytics>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
debug!("called with params: {:?}", body);
|
debug!(parameters = ?body, "Delete documents by batch");
|
||||||
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
||||||
|
|
||||||
analytics.delete_documents(DocumentDeletionKind::PerBatch, &req);
|
analytics.delete_documents(DocumentDeletionKind::PerBatch, &req);
|
||||||
@@ -479,7 +483,7 @@ pub async fn delete_documents_batch(
|
|||||||
let task: SummarizedTaskView =
|
let task: SummarizedTaskView =
|
||||||
tokio::task::spawn_blocking(move || index_scheduler.register(task)).await??.into();
|
tokio::task::spawn_blocking(move || index_scheduler.register(task)).await??.into();
|
||||||
|
|
||||||
debug!("returns: {:?}", task);
|
debug!(returns = ?task, "Delete documents by batch");
|
||||||
Ok(HttpResponse::Accepted().json(task))
|
Ok(HttpResponse::Accepted().json(task))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -497,7 +501,7 @@ pub async fn delete_documents_by_filter(
|
|||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
analytics: web::Data<dyn Analytics>,
|
analytics: web::Data<dyn Analytics>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
debug!("called with params: {:?}", body);
|
debug!(parameters = ?body, "Delete documents by filter");
|
||||||
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
||||||
let index_uid = index_uid.into_inner();
|
let index_uid = index_uid.into_inner();
|
||||||
let filter = body.into_inner().filter;
|
let filter = body.into_inner().filter;
|
||||||
@@ -515,7 +519,7 @@ pub async fn delete_documents_by_filter(
|
|||||||
let task: SummarizedTaskView =
|
let task: SummarizedTaskView =
|
||||||
tokio::task::spawn_blocking(move || index_scheduler.register(task)).await??.into();
|
tokio::task::spawn_blocking(move || index_scheduler.register(task)).await??.into();
|
||||||
|
|
||||||
debug!("returns: {:?}", task);
|
debug!(returns = ?task, "Delete documents by filter");
|
||||||
Ok(HttpResponse::Accepted().json(task))
|
Ok(HttpResponse::Accepted().json(task))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -532,7 +536,7 @@ pub async fn clear_all_documents(
|
|||||||
let task: SummarizedTaskView =
|
let task: SummarizedTaskView =
|
||||||
tokio::task::spawn_blocking(move || index_scheduler.register(task)).await??.into();
|
tokio::task::spawn_blocking(move || index_scheduler.register(task)).await??.into();
|
||||||
|
|
||||||
debug!("returns: {:?}", task);
|
debug!(returns = ?task, "Delete all documents");
|
||||||
Ok(HttpResponse::Accepted().json(task))
|
Ok(HttpResponse::Accepted().json(task))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ use actix_web::web::Data;
|
|||||||
use actix_web::{web, HttpRequest, HttpResponse};
|
use actix_web::{web, HttpRequest, HttpResponse};
|
||||||
use deserr::actix_web::AwebJson;
|
use deserr::actix_web::AwebJson;
|
||||||
use index_scheduler::IndexScheduler;
|
use index_scheduler::IndexScheduler;
|
||||||
use log::debug;
|
|
||||||
use meilisearch_types::deserr::DeserrJsonError;
|
use meilisearch_types::deserr::DeserrJsonError;
|
||||||
use meilisearch_types::error::deserr_codes::*;
|
use meilisearch_types::error::deserr_codes::*;
|
||||||
use meilisearch_types::error::ResponseError;
|
use meilisearch_types::error::ResponseError;
|
||||||
use meilisearch_types::index_uid::IndexUid;
|
use meilisearch_types::index_uid::IndexUid;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::analytics::{Analytics, FacetSearchAggregator};
|
use crate::analytics::{Analytics, FacetSearchAggregator};
|
||||||
use crate::extractors::authentication::policies::*;
|
use crate::extractors::authentication::policies::*;
|
||||||
@@ -56,7 +56,7 @@ pub async fn search(
|
|||||||
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
||||||
|
|
||||||
let query = params.into_inner();
|
let query = params.into_inner();
|
||||||
debug!("facet search called with params: {:?}", query);
|
debug!(parameters = ?query, "Facet search");
|
||||||
|
|
||||||
let mut aggregate = FacetSearchAggregator::from_query(&query, &req);
|
let mut aggregate = FacetSearchAggregator::from_query(&query, &req);
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ pub async fn search(
|
|||||||
|
|
||||||
let search_result = search_result?;
|
let search_result = search_result?;
|
||||||
|
|
||||||
debug!("returns: {:?}", search_result);
|
debug!(returns = ?search_result, "Facet search");
|
||||||
Ok(HttpResponse::Ok().json(search_result))
|
Ok(HttpResponse::Ok().json(search_result))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ use actix_web::{web, HttpRequest, HttpResponse};
|
|||||||
use deserr::actix_web::{AwebJson, AwebQueryParameter};
|
use deserr::actix_web::{AwebJson, AwebQueryParameter};
|
||||||
use deserr::{DeserializeError, Deserr, ValuePointerRef};
|
use deserr::{DeserializeError, Deserr, ValuePointerRef};
|
||||||
use index_scheduler::IndexScheduler;
|
use index_scheduler::IndexScheduler;
|
||||||
use log::debug;
|
|
||||||
use meilisearch_types::deserr::query_params::Param;
|
use meilisearch_types::deserr::query_params::Param;
|
||||||
use meilisearch_types::deserr::{immutable_field_error, DeserrJsonError, DeserrQueryParamError};
|
use meilisearch_types::deserr::{immutable_field_error, DeserrJsonError, DeserrQueryParamError};
|
||||||
use meilisearch_types::error::deserr_codes::*;
|
use meilisearch_types::error::deserr_codes::*;
|
||||||
@@ -16,6 +15,7 @@ use meilisearch_types::tasks::KindWithContent;
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
use super::{Pagination, SummarizedTaskView, PAGINATION_DEFAULT_LIMIT};
|
use super::{Pagination, SummarizedTaskView, PAGINATION_DEFAULT_LIMIT};
|
||||||
use crate::analytics::Analytics;
|
use crate::analytics::Analytics;
|
||||||
@@ -93,6 +93,7 @@ pub async fn list_indexes(
|
|||||||
index_scheduler: GuardedData<ActionPolicy<{ actions::INDEXES_GET }>, Data<IndexScheduler>>,
|
index_scheduler: GuardedData<ActionPolicy<{ actions::INDEXES_GET }>, Data<IndexScheduler>>,
|
||||||
paginate: AwebQueryParameter<ListIndexes, DeserrQueryParamError>,
|
paginate: AwebQueryParameter<ListIndexes, DeserrQueryParamError>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
|
debug!(parameters = ?paginate, "List indexes");
|
||||||
let filters = index_scheduler.filters();
|
let filters = index_scheduler.filters();
|
||||||
let indexes: Vec<Option<IndexView>> =
|
let indexes: Vec<Option<IndexView>> =
|
||||||
index_scheduler.try_for_each_index(|uid, index| -> Result<Option<IndexView>, _> {
|
index_scheduler.try_for_each_index(|uid, index| -> Result<Option<IndexView>, _> {
|
||||||
@@ -105,7 +106,7 @@ pub async fn list_indexes(
|
|||||||
let indexes: Vec<IndexView> = indexes.into_iter().flatten().collect();
|
let indexes: Vec<IndexView> = indexes.into_iter().flatten().collect();
|
||||||
let ret = paginate.as_pagination().auto_paginate_sized(indexes.into_iter());
|
let ret = paginate.as_pagination().auto_paginate_sized(indexes.into_iter());
|
||||||
|
|
||||||
debug!("returns: {:?}", ret);
|
debug!(returns = ?ret, "List indexes");
|
||||||
Ok(HttpResponse::Ok().json(ret))
|
Ok(HttpResponse::Ok().json(ret))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,6 +125,7 @@ pub async fn create_index(
|
|||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
analytics: web::Data<dyn Analytics>,
|
analytics: web::Data<dyn Analytics>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
|
debug!(parameters = ?body, "Create index");
|
||||||
let IndexCreateRequest { primary_key, uid } = body.into_inner();
|
let IndexCreateRequest { primary_key, uid } = body.into_inner();
|
||||||
|
|
||||||
let allow_index_creation = index_scheduler.filters().allow_index_creation(&uid);
|
let allow_index_creation = index_scheduler.filters().allow_index_creation(&uid);
|
||||||
@@ -137,6 +139,7 @@ pub async fn create_index(
|
|||||||
let task = KindWithContent::IndexCreation { index_uid: uid.to_string(), primary_key };
|
let task = KindWithContent::IndexCreation { index_uid: uid.to_string(), primary_key };
|
||||||
let task: SummarizedTaskView =
|
let task: SummarizedTaskView =
|
||||||
tokio::task::spawn_blocking(move || index_scheduler.register(task)).await??.into();
|
tokio::task::spawn_blocking(move || index_scheduler.register(task)).await??.into();
|
||||||
|
debug!(returns = ?task, "Create index");
|
||||||
|
|
||||||
Ok(HttpResponse::Accepted().json(task))
|
Ok(HttpResponse::Accepted().json(task))
|
||||||
} else {
|
} else {
|
||||||
@@ -177,7 +180,7 @@ pub async fn get_index(
|
|||||||
let index = index_scheduler.index(&index_uid)?;
|
let index = index_scheduler.index(&index_uid)?;
|
||||||
let index_view = IndexView::new(index_uid.into_inner(), &index)?;
|
let index_view = IndexView::new(index_uid.into_inner(), &index)?;
|
||||||
|
|
||||||
debug!("returns: {:?}", index_view);
|
debug!(returns = ?index_view, "Get index");
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(index_view))
|
Ok(HttpResponse::Ok().json(index_view))
|
||||||
}
|
}
|
||||||
@@ -189,7 +192,7 @@ pub async fn update_index(
|
|||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
analytics: web::Data<dyn Analytics>,
|
analytics: web::Data<dyn Analytics>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
debug!("called with params: {:?}", body);
|
debug!(parameters = ?body, "Update index");
|
||||||
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
||||||
let body = body.into_inner();
|
let body = body.into_inner();
|
||||||
analytics.publish(
|
analytics.publish(
|
||||||
@@ -206,7 +209,7 @@ pub async fn update_index(
|
|||||||
let task: SummarizedTaskView =
|
let task: SummarizedTaskView =
|
||||||
tokio::task::spawn_blocking(move || index_scheduler.register(task)).await??.into();
|
tokio::task::spawn_blocking(move || index_scheduler.register(task)).await??.into();
|
||||||
|
|
||||||
debug!("returns: {:?}", task);
|
debug!(returns = ?task, "Update index");
|
||||||
Ok(HttpResponse::Accepted().json(task))
|
Ok(HttpResponse::Accepted().json(task))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,6 +221,7 @@ pub async fn delete_index(
|
|||||||
let task = KindWithContent::IndexDeletion { index_uid: index_uid.into_inner() };
|
let task = KindWithContent::IndexDeletion { index_uid: index_uid.into_inner() };
|
||||||
let task: SummarizedTaskView =
|
let task: SummarizedTaskView =
|
||||||
tokio::task::spawn_blocking(move || index_scheduler.register(task)).await??.into();
|
tokio::task::spawn_blocking(move || index_scheduler.register(task)).await??.into();
|
||||||
|
debug!(returns = ?task, "Delete index");
|
||||||
|
|
||||||
Ok(HttpResponse::Accepted().json(task))
|
Ok(HttpResponse::Accepted().json(task))
|
||||||
}
|
}
|
||||||
@@ -255,6 +259,6 @@ pub async fn get_index_stats(
|
|||||||
|
|
||||||
let stats = IndexStats::from(index_scheduler.index_stats(&index_uid)?);
|
let stats = IndexStats::from(index_scheduler.index_stats(&index_uid)?);
|
||||||
|
|
||||||
debug!("returns: {:?}", stats);
|
debug!(returns = ?stats, "Get index stats");
|
||||||
Ok(HttpResponse::Ok().json(stats))
|
Ok(HttpResponse::Ok().json(stats))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ use actix_web::web::Data;
|
|||||||
use actix_web::{web, HttpRequest, HttpResponse};
|
use actix_web::{web, HttpRequest, HttpResponse};
|
||||||
use deserr::actix_web::{AwebJson, AwebQueryParameter};
|
use deserr::actix_web::{AwebJson, AwebQueryParameter};
|
||||||
use index_scheduler::IndexScheduler;
|
use index_scheduler::IndexScheduler;
|
||||||
use log::{debug, warn};
|
|
||||||
use meilisearch_types::deserr::query_params::Param;
|
use meilisearch_types::deserr::query_params::Param;
|
||||||
use meilisearch_types::deserr::{DeserrJsonError, DeserrQueryParamError};
|
use meilisearch_types::deserr::{DeserrJsonError, DeserrQueryParamError};
|
||||||
use meilisearch_types::error::deserr_codes::*;
|
use meilisearch_types::error::deserr_codes::*;
|
||||||
@@ -12,6 +11,7 @@ use meilisearch_types::milli;
|
|||||||
use meilisearch_types::milli::vector::DistributionShift;
|
use meilisearch_types::milli::vector::DistributionShift;
|
||||||
use meilisearch_types::serde_cs::vec::CS;
|
use meilisearch_types::serde_cs::vec::CS;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
use tracing::{debug, warn};
|
||||||
|
|
||||||
use crate::analytics::{Analytics, SearchAggregator};
|
use crate::analytics::{Analytics, SearchAggregator};
|
||||||
use crate::extractors::authentication::policies::*;
|
use crate::extractors::authentication::policies::*;
|
||||||
@@ -186,7 +186,7 @@ pub async fn search_with_url_query(
|
|||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
analytics: web::Data<dyn Analytics>,
|
analytics: web::Data<dyn Analytics>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
debug!("called with params: {:?}", params);
|
debug!(parameters = ?params, "Search get");
|
||||||
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
||||||
|
|
||||||
let mut query: SearchQuery = params.into_inner().into();
|
let mut query: SearchQuery = params.into_inner().into();
|
||||||
@@ -213,7 +213,7 @@ pub async fn search_with_url_query(
|
|||||||
|
|
||||||
let search_result = search_result?;
|
let search_result = search_result?;
|
||||||
|
|
||||||
debug!("returns: {:?}", search_result);
|
debug!(returns = ?search_result, "Search get");
|
||||||
Ok(HttpResponse::Ok().json(search_result))
|
Ok(HttpResponse::Ok().json(search_result))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,7 +227,7 @@ pub async fn search_with_post(
|
|||||||
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
||||||
|
|
||||||
let mut query = params.into_inner();
|
let mut query = params.into_inner();
|
||||||
debug!("search called with params: {:?}", query);
|
debug!(parameters = ?query, "Search post");
|
||||||
|
|
||||||
// Tenant token search_rules.
|
// Tenant token search_rules.
|
||||||
if let Some(search_rules) = index_scheduler.filters().get_index_search_rules(&index_uid) {
|
if let Some(search_rules) = index_scheduler.filters().get_index_search_rules(&index_uid) {
|
||||||
@@ -252,7 +252,7 @@ pub async fn search_with_post(
|
|||||||
|
|
||||||
let search_result = search_result?;
|
let search_result = search_result?;
|
||||||
|
|
||||||
debug!("returns: {:?}", search_result);
|
debug!(returns = ?search_result, "Search post");
|
||||||
Ok(HttpResponse::Ok().json(search_result))
|
Ok(HttpResponse::Ok().json(search_result))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ use actix_web::web::Data;
|
|||||||
use actix_web::{web, HttpRequest, HttpResponse};
|
use actix_web::{web, HttpRequest, HttpResponse};
|
||||||
use deserr::actix_web::AwebJson;
|
use deserr::actix_web::AwebJson;
|
||||||
use index_scheduler::IndexScheduler;
|
use index_scheduler::IndexScheduler;
|
||||||
use log::debug;
|
|
||||||
use meilisearch_types::deserr::DeserrJsonError;
|
use meilisearch_types::deserr::DeserrJsonError;
|
||||||
use meilisearch_types::error::ResponseError;
|
use meilisearch_types::error::ResponseError;
|
||||||
use meilisearch_types::facet_values_sort::FacetValuesSort;
|
use meilisearch_types::facet_values_sort::FacetValuesSort;
|
||||||
@@ -11,6 +10,7 @@ use meilisearch_types::milli::update::Setting;
|
|||||||
use meilisearch_types::settings::{settings, RankingRuleView, Settings, Unchecked};
|
use meilisearch_types::settings::{settings, RankingRuleView, Settings, Unchecked};
|
||||||
use meilisearch_types::tasks::KindWithContent;
|
use meilisearch_types::tasks::KindWithContent;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::analytics::Analytics;
|
use crate::analytics::Analytics;
|
||||||
use crate::extractors::authentication::policies::*;
|
use crate::extractors::authentication::policies::*;
|
||||||
@@ -24,12 +24,12 @@ macro_rules! make_setting_route {
|
|||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
use actix_web::{web, HttpRequest, HttpResponse, Resource};
|
use actix_web::{web, HttpRequest, HttpResponse, Resource};
|
||||||
use index_scheduler::IndexScheduler;
|
use index_scheduler::IndexScheduler;
|
||||||
use log::debug;
|
|
||||||
use meilisearch_types::error::ResponseError;
|
use meilisearch_types::error::ResponseError;
|
||||||
use meilisearch_types::index_uid::IndexUid;
|
use meilisearch_types::index_uid::IndexUid;
|
||||||
use meilisearch_types::milli::update::Setting;
|
use meilisearch_types::milli::update::Setting;
|
||||||
use meilisearch_types::settings::{settings, Settings};
|
use meilisearch_types::settings::{settings, Settings};
|
||||||
use meilisearch_types::tasks::KindWithContent;
|
use meilisearch_types::tasks::KindWithContent;
|
||||||
|
use tracing::debug;
|
||||||
use $crate::analytics::Analytics;
|
use $crate::analytics::Analytics;
|
||||||
use $crate::extractors::authentication::policies::*;
|
use $crate::extractors::authentication::policies::*;
|
||||||
use $crate::extractors::authentication::GuardedData;
|
use $crate::extractors::authentication::GuardedData;
|
||||||
@@ -61,7 +61,7 @@ macro_rules! make_setting_route {
|
|||||||
.await??
|
.await??
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
debug!("returns: {:?}", task);
|
debug!(returns = ?task, "Delete settings");
|
||||||
Ok(HttpResponse::Accepted().json(task))
|
Ok(HttpResponse::Accepted().json(task))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,6 +78,7 @@ macro_rules! make_setting_route {
|
|||||||
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
||||||
|
|
||||||
let body = body.into_inner();
|
let body = body.into_inner();
|
||||||
|
debug!(parameters = ?body, "Update settings");
|
||||||
|
|
||||||
#[allow(clippy::redundant_closure_call)]
|
#[allow(clippy::redundant_closure_call)]
|
||||||
$analytics(&body, &req);
|
$analytics(&body, &req);
|
||||||
@@ -109,7 +110,7 @@ macro_rules! make_setting_route {
|
|||||||
.await??
|
.await??
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
debug!("returns: {:?}", task);
|
debug!(returns = ?task, "Update settings");
|
||||||
Ok(HttpResponse::Accepted().json(task))
|
Ok(HttpResponse::Accepted().json(task))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +127,7 @@ macro_rules! make_setting_route {
|
|||||||
let rtxn = index.read_txn()?;
|
let rtxn = index.read_txn()?;
|
||||||
let settings = settings(&index, &rtxn)?;
|
let settings = settings(&index, &rtxn)?;
|
||||||
|
|
||||||
debug!("returns: {:?}", settings);
|
debug!(returns = ?settings, "Update settings");
|
||||||
let mut json = serde_json::json!(&settings);
|
let mut json = serde_json::json!(&settings);
|
||||||
let val = json[$camelcase_attr].take();
|
let val = json[$camelcase_attr].take();
|
||||||
|
|
||||||
@@ -656,6 +657,7 @@ pub async fn update_all(
|
|||||||
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
||||||
|
|
||||||
let new_settings = body.into_inner();
|
let new_settings = body.into_inner();
|
||||||
|
debug!(parameters = ?new_settings, "Update all settings");
|
||||||
let new_settings = validate_settings(new_settings, &index_scheduler)?;
|
let new_settings = validate_settings(new_settings, &index_scheduler)?;
|
||||||
|
|
||||||
analytics.publish(
|
analytics.publish(
|
||||||
@@ -768,7 +770,7 @@ pub async fn update_all(
|
|||||||
let task: SummarizedTaskView =
|
let task: SummarizedTaskView =
|
||||||
tokio::task::spawn_blocking(move || index_scheduler.register(task)).await??.into();
|
tokio::task::spawn_blocking(move || index_scheduler.register(task)).await??.into();
|
||||||
|
|
||||||
debug!("returns: {:?}", task);
|
debug!(returns = ?task, "Update all settings");
|
||||||
Ok(HttpResponse::Accepted().json(task))
|
Ok(HttpResponse::Accepted().json(task))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -781,7 +783,7 @@ pub async fn get_all(
|
|||||||
let index = index_scheduler.index(&index_uid)?;
|
let index = index_scheduler.index(&index_uid)?;
|
||||||
let rtxn = index.read_txn()?;
|
let rtxn = index.read_txn()?;
|
||||||
let new_settings = settings(&index, &rtxn)?;
|
let new_settings = settings(&index, &rtxn)?;
|
||||||
debug!("returns: {:?}", new_settings);
|
debug!(returns = ?new_settings, "Get all settings");
|
||||||
Ok(HttpResponse::Ok().json(new_settings))
|
Ok(HttpResponse::Ok().json(new_settings))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -804,7 +806,7 @@ pub async fn delete_all(
|
|||||||
let task: SummarizedTaskView =
|
let task: SummarizedTaskView =
|
||||||
tokio::task::spawn_blocking(move || index_scheduler.register(task)).await??.into();
|
tokio::task::spawn_blocking(move || index_scheduler.register(task)).await??.into();
|
||||||
|
|
||||||
debug!("returns: {:?}", task);
|
debug!(returns = ?task, "Delete all settings");
|
||||||
Ok(HttpResponse::Accepted().json(task))
|
Ok(HttpResponse::Accepted().json(task))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
281
meilisearch/src/routes/logs.rs
Normal file
281
meilisearch/src/routes/logs.rs
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
use std::convert::Infallible;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::ops::ControlFlow;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use actix_web::web::{Bytes, Data};
|
||||||
|
use actix_web::{web, HttpResponse};
|
||||||
|
use deserr::actix_web::AwebJson;
|
||||||
|
use deserr::{DeserializeError, Deserr, ErrorKind, MergeWithError, ValuePointerRef};
|
||||||
|
use futures_util::Stream;
|
||||||
|
use index_scheduler::IndexScheduler;
|
||||||
|
use meilisearch_types::deserr::DeserrJsonError;
|
||||||
|
use meilisearch_types::error::deserr_codes::*;
|
||||||
|
use meilisearch_types::error::{Code, ResponseError};
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
use tracing_subscriber::filter::Targets;
|
||||||
|
use tracing_subscriber::Layer;
|
||||||
|
|
||||||
|
use crate::error::MeilisearchHttpError;
|
||||||
|
use crate::extractors::authentication::policies::*;
|
||||||
|
use crate::extractors::authentication::GuardedData;
|
||||||
|
use crate::extractors::sequential_extractor::SeqHandler;
|
||||||
|
use crate::LogRouteHandle;
|
||||||
|
|
||||||
|
pub fn configure(cfg: &mut web::ServiceConfig) {
|
||||||
|
cfg.service(
|
||||||
|
web::resource("stream")
|
||||||
|
.route(web::post().to(SeqHandler(get_logs)))
|
||||||
|
.route(web::delete().to(SeqHandler(cancel_logs))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, Copy, Deserr, PartialEq, Eq)]
|
||||||
|
#[deserr(rename_all = camelCase)]
|
||||||
|
pub enum LogMode {
|
||||||
|
#[default]
|
||||||
|
Human,
|
||||||
|
Profile,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Simple wrapper around the `Targets` from `tracing_subscriber` to implement `MergeWithError` on it.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct MyTargets(Targets);
|
||||||
|
|
||||||
|
/// Simple wrapper around the `ParseError` from `tracing_subscriber` to implement `MergeWithError` on it.
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
enum MyParseError {
|
||||||
|
#[error(transparent)]
|
||||||
|
ParseError(#[from] tracing_subscriber::filter::ParseError),
|
||||||
|
#[error(
|
||||||
|
"Empty string is not a valid target. If you want to get no logs use `OFF`. Usage: `info`, `meilisearch=info`, or you can write multiple filters in one target: `index_scheduler=info,milli=trace`"
|
||||||
|
)]
|
||||||
|
Example,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for MyTargets {
|
||||||
|
type Err = MyParseError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
if s.is_empty() {
|
||||||
|
Err(MyParseError::Example)
|
||||||
|
} else {
|
||||||
|
Ok(MyTargets(Targets::from_str(s).map_err(MyParseError::ParseError)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MergeWithError<MyParseError> for DeserrJsonError<BadRequest> {
|
||||||
|
fn merge(
|
||||||
|
_self_: Option<Self>,
|
||||||
|
other: MyParseError,
|
||||||
|
merge_location: ValuePointerRef,
|
||||||
|
) -> ControlFlow<Self, Self> {
|
||||||
|
Self::error::<Infallible>(
|
||||||
|
None,
|
||||||
|
ErrorKind::Unexpected { msg: other.to_string() },
|
||||||
|
merge_location,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserr)]
|
||||||
|
#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields, validate = validate_get_logs -> DeserrJsonError<InvalidSettingsTypoTolerance>)]
|
||||||
|
pub struct GetLogs {
|
||||||
|
#[deserr(default = "info".parse().unwrap(), try_from(&String) = MyTargets::from_str -> DeserrJsonError<BadRequest>)]
|
||||||
|
target: MyTargets,
|
||||||
|
|
||||||
|
#[deserr(default, error = DeserrJsonError<BadRequest>)]
|
||||||
|
mode: LogMode,
|
||||||
|
|
||||||
|
#[deserr(default = false, error = DeserrJsonError<BadRequest>)]
|
||||||
|
profile_memory: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_get_logs<E: DeserializeError>(
|
||||||
|
logs: GetLogs,
|
||||||
|
location: ValuePointerRef,
|
||||||
|
) -> Result<GetLogs, E> {
|
||||||
|
if logs.profile_memory && logs.mode != LogMode::Profile {
|
||||||
|
Err(deserr::take_cf_content(E::error::<Infallible>(
|
||||||
|
None,
|
||||||
|
ErrorKind::Unexpected {
|
||||||
|
msg: format!("`profile_memory` can only be used while profiling code and is not compatible with the {:?} mode.", logs.mode),
|
||||||
|
},
|
||||||
|
location,
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
Ok(logs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LogWriter {
|
||||||
|
sender: mpsc::UnboundedSender<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Write for LogWriter {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||||
|
self.sender.send(buf.to_vec()).map_err(std::io::Error::other)?;
|
||||||
|
Ok(buf.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> std::io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HandleGuard {
|
||||||
|
/// We need to keep an handle on the logs to make it available again when the streamer is dropped
|
||||||
|
logs: Arc<LogRouteHandle>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for HandleGuard {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Err(e) = self.logs.modify(|layer| *layer.inner_mut() = None) {
|
||||||
|
tracing::error!("Could not free the logs route: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn byte_stream(
|
||||||
|
receiver: mpsc::UnboundedReceiver<Vec<u8>>,
|
||||||
|
guard: HandleGuard,
|
||||||
|
) -> impl futures_util::Stream<Item = Result<Bytes, ResponseError>> {
|
||||||
|
futures_util::stream::unfold((receiver, guard), move |(mut receiver, guard)| async move {
|
||||||
|
let vec = receiver.recv().await;
|
||||||
|
|
||||||
|
vec.map(From::from).map(Ok).map(|a| (a, (receiver, guard)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type PinnedByteStream = Pin<Box<dyn Stream<Item = Result<Bytes, ResponseError>>>>;
|
||||||
|
|
||||||
|
fn make_layer<
|
||||||
|
S: tracing::Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>,
|
||||||
|
>(
|
||||||
|
opt: &GetLogs,
|
||||||
|
logs: Data<LogRouteHandle>,
|
||||||
|
) -> (Box<dyn Layer<S> + Send + Sync>, PinnedByteStream) {
|
||||||
|
let guard = HandleGuard { logs: logs.into_inner() };
|
||||||
|
match opt.mode {
|
||||||
|
LogMode::Human => {
|
||||||
|
let (sender, receiver) = tokio::sync::mpsc::unbounded_channel();
|
||||||
|
|
||||||
|
let fmt_layer = tracing_subscriber::fmt::layer()
|
||||||
|
.with_writer(move || LogWriter { sender: sender.clone() })
|
||||||
|
.with_span_events(tracing_subscriber::fmt::format::FmtSpan::ACTIVE);
|
||||||
|
|
||||||
|
let stream = byte_stream(receiver, guard);
|
||||||
|
(Box::new(fmt_layer) as Box<dyn Layer<S> + Send + Sync>, Box::pin(stream))
|
||||||
|
}
|
||||||
|
LogMode::Profile => {
|
||||||
|
let (trace, layer) = tracing_trace::Trace::new(opt.profile_memory);
|
||||||
|
|
||||||
|
let stream = entry_stream(trace, guard);
|
||||||
|
|
||||||
|
(Box::new(layer) as Box<dyn Layer<S> + Send + Sync>, Box::pin(stream))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn entry_stream(
|
||||||
|
trace: tracing_trace::Trace,
|
||||||
|
guard: HandleGuard,
|
||||||
|
) -> impl Stream<Item = Result<Bytes, ResponseError>> {
|
||||||
|
let receiver = trace.into_receiver();
|
||||||
|
let entry_buf = Vec::new();
|
||||||
|
|
||||||
|
futures_util::stream::unfold(
|
||||||
|
(receiver, entry_buf, guard),
|
||||||
|
move |(mut receiver, mut entry_buf, guard)| async move {
|
||||||
|
let mut bytes = Vec::new();
|
||||||
|
|
||||||
|
while bytes.len() < 8192 {
|
||||||
|
entry_buf.clear();
|
||||||
|
|
||||||
|
let Ok(count) = tokio::time::timeout(
|
||||||
|
std::time::Duration::from_secs(1),
|
||||||
|
receiver.recv_many(&mut entry_buf, 100),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
if count == 0 {
|
||||||
|
if !bytes.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// channel closed, exit
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
for entry in &entry_buf {
|
||||||
|
if let Err(error) = serde_json::to_writer(&mut bytes, entry) {
|
||||||
|
tracing::error!(
|
||||||
|
error = &error as &dyn std::error::Error,
|
||||||
|
"deserializing entry"
|
||||||
|
);
|
||||||
|
return Some((
|
||||||
|
Err(ResponseError::from_msg(
|
||||||
|
format!("error deserializing entry: {error}"),
|
||||||
|
Code::Internal,
|
||||||
|
)),
|
||||||
|
(receiver, entry_buf, guard),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((Ok(bytes.into()), (receiver, entry_buf, guard)))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_logs(
|
||||||
|
index_scheduler: GuardedData<ActionPolicy<{ actions::METRICS_GET }>, Data<IndexScheduler>>,
|
||||||
|
logs: Data<LogRouteHandle>,
|
||||||
|
body: AwebJson<GetLogs, DeserrJsonError>,
|
||||||
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
|
index_scheduler.features().check_logs_route()?;
|
||||||
|
|
||||||
|
let opt = body.into_inner();
|
||||||
|
let mut stream = None;
|
||||||
|
|
||||||
|
logs.modify(|layer| match layer.inner_mut() {
|
||||||
|
None => {
|
||||||
|
// there is no one getting logs
|
||||||
|
*layer.filter_mut() = opt.target.0.clone();
|
||||||
|
let (new_layer, new_stream) = make_layer(&opt, logs.clone());
|
||||||
|
|
||||||
|
*layer.inner_mut() = Some(new_layer);
|
||||||
|
stream = Some(new_stream);
|
||||||
|
}
|
||||||
|
Some(_) => {
|
||||||
|
// there is already someone getting logs
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if let Some(stream) = stream {
|
||||||
|
Ok(HttpResponse::Ok().streaming(stream))
|
||||||
|
} else {
|
||||||
|
Err(MeilisearchHttpError::AlreadyUsedLogRoute.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn cancel_logs(
|
||||||
|
index_scheduler: GuardedData<ActionPolicy<{ actions::METRICS_GET }>, Data<IndexScheduler>>,
|
||||||
|
logs: Data<LogRouteHandle>,
|
||||||
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
|
index_scheduler.features().check_logs_route()?;
|
||||||
|
|
||||||
|
if let Err(e) = logs.modify(|layer| *layer.inner_mut() = None) {
|
||||||
|
tracing::error!("Could not free the logs route: {e}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HttpResponse::NoContent().finish())
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@ use std::collections::BTreeMap;
|
|||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
use actix_web::{web, HttpRequest, HttpResponse};
|
use actix_web::{web, HttpRequest, HttpResponse};
|
||||||
use index_scheduler::IndexScheduler;
|
use index_scheduler::IndexScheduler;
|
||||||
use log::debug;
|
|
||||||
use meilisearch_auth::AuthController;
|
use meilisearch_auth::AuthController;
|
||||||
use meilisearch_types::error::ResponseError;
|
use meilisearch_types::error::ResponseError;
|
||||||
use meilisearch_types::settings::{Settings, Unchecked};
|
use meilisearch_types::settings::{Settings, Unchecked};
|
||||||
@@ -11,6 +10,7 @@ use meilisearch_types::tasks::{Kind, Status, Task, TaskId};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::analytics::Analytics;
|
use crate::analytics::Analytics;
|
||||||
use crate::extractors::authentication::policies::*;
|
use crate::extractors::authentication::policies::*;
|
||||||
@@ -22,6 +22,7 @@ mod api_key;
|
|||||||
mod dump;
|
mod dump;
|
||||||
pub mod features;
|
pub mod features;
|
||||||
pub mod indexes;
|
pub mod indexes;
|
||||||
|
mod logs;
|
||||||
mod metrics;
|
mod metrics;
|
||||||
mod multi_search;
|
mod multi_search;
|
||||||
mod snapshot;
|
mod snapshot;
|
||||||
@@ -31,6 +32,7 @@ pub mod tasks;
|
|||||||
pub fn configure(cfg: &mut web::ServiceConfig) {
|
pub fn configure(cfg: &mut web::ServiceConfig) {
|
||||||
cfg.service(web::scope("/tasks").configure(tasks::configure))
|
cfg.service(web::scope("/tasks").configure(tasks::configure))
|
||||||
.service(web::resource("/health").route(web::get().to(get_health)))
|
.service(web::resource("/health").route(web::get().to(get_health)))
|
||||||
|
.service(web::scope("/logs").configure(logs::configure))
|
||||||
.service(web::scope("/keys").configure(api_key::configure))
|
.service(web::scope("/keys").configure(api_key::configure))
|
||||||
.service(web::scope("/dumps").configure(dump::configure))
|
.service(web::scope("/dumps").configure(dump::configure))
|
||||||
.service(web::scope("/snapshots").configure(snapshot::configure))
|
.service(web::scope("/snapshots").configure(snapshot::configure))
|
||||||
@@ -250,7 +252,7 @@ async fn get_stats(
|
|||||||
|
|
||||||
let stats = create_all_stats((*index_scheduler).clone(), (*auth_controller).clone(), filters)?;
|
let stats = create_all_stats((*index_scheduler).clone(), (*auth_controller).clone(), filters)?;
|
||||||
|
|
||||||
debug!("returns: {:?}", stats);
|
debug!(returns = ?stats, "Get stats");
|
||||||
Ok(HttpResponse::Ok().json(stats))
|
Ok(HttpResponse::Ok().json(stats))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ use actix_web::web::{self, Data};
|
|||||||
use actix_web::{HttpRequest, HttpResponse};
|
use actix_web::{HttpRequest, HttpResponse};
|
||||||
use deserr::actix_web::AwebJson;
|
use deserr::actix_web::AwebJson;
|
||||||
use index_scheduler::IndexScheduler;
|
use index_scheduler::IndexScheduler;
|
||||||
use log::debug;
|
|
||||||
use meilisearch_types::deserr::DeserrJsonError;
|
use meilisearch_types::deserr::DeserrJsonError;
|
||||||
use meilisearch_types::error::ResponseError;
|
use meilisearch_types::error::ResponseError;
|
||||||
use meilisearch_types::keys::actions;
|
use meilisearch_types::keys::actions;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::analytics::{Analytics, MultiSearchAggregator};
|
use crate::analytics::{Analytics, MultiSearchAggregator};
|
||||||
use crate::extractors::authentication::policies::ActionPolicy;
|
use crate::extractors::authentication::policies::ActionPolicy;
|
||||||
@@ -52,7 +52,7 @@ pub async fn multi_search_with_post(
|
|||||||
for (query_index, (index_uid, mut query)) in
|
for (query_index, (index_uid, mut query)) in
|
||||||
queries.into_iter().map(SearchQueryWithIndex::into_index_query).enumerate()
|
queries.into_iter().map(SearchQueryWithIndex::into_index_query).enumerate()
|
||||||
{
|
{
|
||||||
debug!("multi-search #{query_index}: called with params: {:?}", query);
|
debug!(on_index = query_index, parameters = ?query, "Multi-search");
|
||||||
|
|
||||||
// Check index from API key
|
// Check index from API key
|
||||||
if !index_scheduler.filters().is_index_authorized(&index_uid) {
|
if !index_scheduler.filters().is_index_authorized(&index_uid) {
|
||||||
@@ -107,7 +107,7 @@ pub async fn multi_search_with_post(
|
|||||||
err
|
err
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
debug!("returns: {:?}", search_results);
|
debug!(returns = ?search_results, "Multi-search");
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(SearchResults { results: search_results }))
|
Ok(HttpResponse::Ok().json(SearchResults { results: search_results }))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
use actix_web::{web, HttpRequest, HttpResponse};
|
use actix_web::{web, HttpRequest, HttpResponse};
|
||||||
use index_scheduler::IndexScheduler;
|
use index_scheduler::IndexScheduler;
|
||||||
use log::debug;
|
|
||||||
use meilisearch_types::error::ResponseError;
|
use meilisearch_types::error::ResponseError;
|
||||||
use meilisearch_types::tasks::KindWithContent;
|
use meilisearch_types::tasks::KindWithContent;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::analytics::Analytics;
|
use crate::analytics::Analytics;
|
||||||
use crate::extractors::authentication::policies::*;
|
use crate::extractors::authentication::policies::*;
|
||||||
@@ -27,6 +27,6 @@ pub async fn create_snapshot(
|
|||||||
let task: SummarizedTaskView =
|
let task: SummarizedTaskView =
|
||||||
tokio::task::spawn_blocking(move || index_scheduler.register(task)).await??.into();
|
tokio::task::spawn_blocking(move || index_scheduler.register(task)).await??.into();
|
||||||
|
|
||||||
debug!("returns: {:?}", task);
|
debug!(returns = ?task, "Create snapshot");
|
||||||
Ok(HttpResponse::Accepted().json(task))
|
Ok(HttpResponse::Accepted().json(task))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -441,10 +441,6 @@ fn prepare_search<'t>(
|
|||||||
ScoringStrategy::Skip
|
ScoringStrategy::Skip
|
||||||
});
|
});
|
||||||
|
|
||||||
if query.show_ranking_score_details {
|
|
||||||
features.check_score_details()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(HybridQuery { embedder: Some(embedder), .. }) = &query.hybrid {
|
if let Some(HybridQuery { embedder: Some(embedder), .. }) = &query.hybrid {
|
||||||
search.embedder_name(embedder);
|
search.embedder_name(embedder);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ pub static AUTHORIZATIONS: Lazy<HashMap<(&'static str, &'static str), HashSet<&'
|
|||||||
("POST", "/snapshots") => hashset!{"snapshots.create", "snapshots.*", "*"},
|
("POST", "/snapshots") => hashset!{"snapshots.create", "snapshots.*", "*"},
|
||||||
("GET", "/version") => hashset!{"version", "*"},
|
("GET", "/version") => hashset!{"version", "*"},
|
||||||
("GET", "/metrics") => hashset!{"metrics.get", "metrics.*", "*"},
|
("GET", "/metrics") => hashset!{"metrics.get", "metrics.*", "*"},
|
||||||
|
("POST", "/logs/stream") => hashset!{"metrics.get", "metrics.*", "*"},
|
||||||
|
("DELETE", "/logs/stream") => hashset!{"metrics.get", "metrics.*", "*"},
|
||||||
("PATCH", "/keys/mykey/") => hashset!{"keys.update", "*"},
|
("PATCH", "/keys/mykey/") => hashset!{"keys.update", "*"},
|
||||||
("GET", "/keys/mykey/") => hashset!{"keys.get", "*"},
|
("GET", "/keys/mykey/") => hashset!{"keys.get", "*"},
|
||||||
("DELETE", "/keys/mykey/") => hashset!{"keys.delete", "*"},
|
("DELETE", "/keys/mykey/") => hashset!{"keys.delete", "*"},
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ impl Display for Value {
|
|||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"{}",
|
"{}",
|
||||||
json_string!(self, { ".enqueuedAt" => "[date]", ".processedAt" => "[date]", ".finishedAt" => "[date]", ".duration" => "[duration]" })
|
json_string!(self, { ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]", ".duration" => "[duration]" })
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ use meilisearch::{analytics, create_app, setup_meilisearch};
|
|||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
use tokio::time::sleep;
|
use tokio::time::sleep;
|
||||||
|
use tracing::level_filters::LevelFilter;
|
||||||
|
use tracing_subscriber::Layer;
|
||||||
|
|
||||||
use super::index::Index;
|
use super::index::Index;
|
||||||
use super::service::Service;
|
use super::service::Service;
|
||||||
@@ -81,10 +83,16 @@ impl Server {
|
|||||||
Response = ServiceResponse<impl MessageBody>,
|
Response = ServiceResponse<impl MessageBody>,
|
||||||
Error = actix_web::Error,
|
Error = actix_web::Error,
|
||||||
> {
|
> {
|
||||||
|
let (_route_layer, route_layer_handle) =
|
||||||
|
tracing_subscriber::reload::Layer::new(None.with_filter(
|
||||||
|
tracing_subscriber::filter::Targets::new().with_target("", LevelFilter::OFF),
|
||||||
|
));
|
||||||
|
|
||||||
actix_web::test::init_service(create_app(
|
actix_web::test::init_service(create_app(
|
||||||
self.service.index_scheduler.clone().into(),
|
self.service.index_scheduler.clone().into(),
|
||||||
self.service.auth.clone().into(),
|
self.service.auth.clone().into(),
|
||||||
self.service.options.clone(),
|
self.service.options.clone(),
|
||||||
|
route_layer_handle,
|
||||||
analytics::MockAnalytics::new(&self.service.options),
|
analytics::MockAnalytics::new(&self.service.options),
|
||||||
true,
|
true,
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ use actix_web::test::TestRequest;
|
|||||||
use index_scheduler::IndexScheduler;
|
use index_scheduler::IndexScheduler;
|
||||||
use meilisearch::{analytics, create_app, Opt};
|
use meilisearch::{analytics, create_app, Opt};
|
||||||
use meilisearch_auth::AuthController;
|
use meilisearch_auth::AuthController;
|
||||||
|
use tracing::level_filters::LevelFilter;
|
||||||
|
use tracing_subscriber::Layer;
|
||||||
|
|
||||||
use crate::common::encoder::Encoder;
|
use crate::common::encoder::Encoder;
|
||||||
use crate::common::Value;
|
use crate::common::Value;
|
||||||
@@ -105,10 +107,16 @@ impl Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn request(&self, mut req: test::TestRequest) -> (Value, StatusCode) {
|
pub async fn request(&self, mut req: test::TestRequest) -> (Value, StatusCode) {
|
||||||
|
let (_route_layer, route_layer_handle) =
|
||||||
|
tracing_subscriber::reload::Layer::new(None.with_filter(
|
||||||
|
tracing_subscriber::filter::Targets::new().with_target("", LevelFilter::OFF),
|
||||||
|
));
|
||||||
|
|
||||||
let app = test::init_service(create_app(
|
let app = test::init_service(create_app(
|
||||||
self.index_scheduler.clone().into(),
|
self.index_scheduler.clone().into(),
|
||||||
self.auth.clone().into(),
|
self.auth.clone().into(),
|
||||||
self.options.clone(),
|
self.options.clone(),
|
||||||
|
route_layer_handle,
|
||||||
analytics::MockAnalytics::new(&self.options),
|
analytics::MockAnalytics::new(&self.options),
|
||||||
true,
|
true,
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -1760,6 +1760,181 @@ async fn add_documents_invalid_geo_field() {
|
|||||||
"finishedAt": "[date]"
|
"finishedAt": "[date]"
|
||||||
}
|
}
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
|
// The three next tests are related to #4333
|
||||||
|
|
||||||
|
// _geo has a lat and lng but set to `null`
|
||||||
|
let documents = json!([
|
||||||
|
{
|
||||||
|
"id": "12",
|
||||||
|
"_geo": { "lng": null, "lat": 67}
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
let (response, code) = index.add_documents(documents, None).await;
|
||||||
|
snapshot!(code, @"202 Accepted");
|
||||||
|
let response = index.wait_task(response.uid()).await;
|
||||||
|
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
|
||||||
|
@r###"
|
||||||
|
{
|
||||||
|
"uid": 14,
|
||||||
|
"indexUid": "test",
|
||||||
|
"status": "failed",
|
||||||
|
"type": "documentAdditionOrUpdate",
|
||||||
|
"canceledBy": null,
|
||||||
|
"details": {
|
||||||
|
"receivedDocuments": 1,
|
||||||
|
"indexedDocuments": 0
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"message": "Could not parse longitude in the document with the id: `12`. Was expecting a finite number but instead got `null`.",
|
||||||
|
"code": "invalid_document_geo_field",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#invalid_document_geo_field"
|
||||||
|
},
|
||||||
|
"duration": "[duration]",
|
||||||
|
"enqueuedAt": "[date]",
|
||||||
|
"startedAt": "[date]",
|
||||||
|
"finishedAt": "[date]"
|
||||||
|
}
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// _geo has a lat and lng but set to `null`
|
||||||
|
let documents = json!([
|
||||||
|
{
|
||||||
|
"id": "12",
|
||||||
|
"_geo": { "lng": 35, "lat": null }
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
let (response, code) = index.add_documents(documents, None).await;
|
||||||
|
snapshot!(code, @"202 Accepted");
|
||||||
|
let response = index.wait_task(response.uid()).await;
|
||||||
|
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
|
||||||
|
@r###"
|
||||||
|
{
|
||||||
|
"uid": 15,
|
||||||
|
"indexUid": "test",
|
||||||
|
"status": "failed",
|
||||||
|
"type": "documentAdditionOrUpdate",
|
||||||
|
"canceledBy": null,
|
||||||
|
"details": {
|
||||||
|
"receivedDocuments": 1,
|
||||||
|
"indexedDocuments": 0
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"message": "Could not parse latitude in the document with the id: `12`. Was expecting a finite number but instead got `null`.",
|
||||||
|
"code": "invalid_document_geo_field",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#invalid_document_geo_field"
|
||||||
|
},
|
||||||
|
"duration": "[duration]",
|
||||||
|
"enqueuedAt": "[date]",
|
||||||
|
"startedAt": "[date]",
|
||||||
|
"finishedAt": "[date]"
|
||||||
|
}
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// _geo has a lat and lng but set to `null`
|
||||||
|
let documents = json!([
|
||||||
|
{
|
||||||
|
"id": "13",
|
||||||
|
"_geo": { "lng": null, "lat": null }
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
let (response, code) = index.add_documents(documents, None).await;
|
||||||
|
snapshot!(code, @"202 Accepted");
|
||||||
|
let response = index.wait_task(response.uid()).await;
|
||||||
|
snapshot!(json_string!(response, { ".duration" => "[duration]", ".enqueuedAt" => "[date]", ".startedAt" => "[date]", ".finishedAt" => "[date]" }),
|
||||||
|
@r###"
|
||||||
|
{
|
||||||
|
"uid": 16,
|
||||||
|
"indexUid": "test",
|
||||||
|
"status": "failed",
|
||||||
|
"type": "documentAdditionOrUpdate",
|
||||||
|
"canceledBy": null,
|
||||||
|
"details": {
|
||||||
|
"receivedDocuments": 1,
|
||||||
|
"indexedDocuments": 0
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"message": "Could not parse latitude nor longitude in the document with the id: `13`. Was expecting finite numbers but instead got `null` and `null`.",
|
||||||
|
"code": "invalid_document_geo_field",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#invalid_document_geo_field"
|
||||||
|
},
|
||||||
|
"duration": "[duration]",
|
||||||
|
"enqueuedAt": "[date]",
|
||||||
|
"startedAt": "[date]",
|
||||||
|
"finishedAt": "[date]"
|
||||||
|
}
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Related to #4333
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn add_invalid_geo_and_then_settings() {
|
||||||
|
let server = Server::new().await;
|
||||||
|
let index = server.index("test");
|
||||||
|
index.create(Some("id")).await;
|
||||||
|
|
||||||
|
// _geo is not an object
|
||||||
|
let documents = json!([
|
||||||
|
{
|
||||||
|
"id": "11",
|
||||||
|
"_geo": { "lat": null, "lng": null },
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
let (ret, code) = index.add_documents(documents, None).await;
|
||||||
|
snapshot!(code, @"202 Accepted");
|
||||||
|
let ret = index.wait_task(ret.uid()).await;
|
||||||
|
snapshot!(ret, @r###"
|
||||||
|
{
|
||||||
|
"uid": 1,
|
||||||
|
"indexUid": "test",
|
||||||
|
"status": "succeeded",
|
||||||
|
"type": "documentAdditionOrUpdate",
|
||||||
|
"canceledBy": null,
|
||||||
|
"details": {
|
||||||
|
"receivedDocuments": 1,
|
||||||
|
"indexedDocuments": 1
|
||||||
|
},
|
||||||
|
"error": null,
|
||||||
|
"duration": "[duration]",
|
||||||
|
"enqueuedAt": "[date]",
|
||||||
|
"startedAt": "[date]",
|
||||||
|
"finishedAt": "[date]"
|
||||||
|
}
|
||||||
|
"###);
|
||||||
|
|
||||||
|
let (ret, code) = index.update_settings(json!({"sortableAttributes": ["_geo"]})).await;
|
||||||
|
snapshot!(code, @"202 Accepted");
|
||||||
|
let ret = index.wait_task(ret.uid()).await;
|
||||||
|
snapshot!(ret, @r###"
|
||||||
|
{
|
||||||
|
"uid": 2,
|
||||||
|
"indexUid": "test",
|
||||||
|
"status": "failed",
|
||||||
|
"type": "settingsUpdate",
|
||||||
|
"canceledBy": null,
|
||||||
|
"details": {
|
||||||
|
"sortableAttributes": [
|
||||||
|
"_geo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"message": "Could not parse latitude in the document with the id: `\"11\"`. Was expecting a finite number but instead got `null`.",
|
||||||
|
"code": "invalid_document_geo_field",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#invalid_document_geo_field"
|
||||||
|
},
|
||||||
|
"duration": "[duration]",
|
||||||
|
"enqueuedAt": "[date]",
|
||||||
|
"startedAt": "[date]",
|
||||||
|
"finishedAt": "[date]"
|
||||||
|
}
|
||||||
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
|
|||||||
@@ -1845,9 +1845,9 @@ async fn import_dump_v6_containing_experimental_features() {
|
|||||||
meili_snap::snapshot!(code, @"200 OK");
|
meili_snap::snapshot!(code, @"200 OK");
|
||||||
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
||||||
{
|
{
|
||||||
"scoreDetails": false,
|
|
||||||
"vectorStore": false,
|
"vectorStore": false,
|
||||||
"metrics": false,
|
"metrics": false,
|
||||||
|
"logsRoute": false,
|
||||||
"exportPuffinReports": false
|
"exportPuffinReports": false
|
||||||
}
|
}
|
||||||
"###);
|
"###);
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ async fn experimental_features() {
|
|||||||
meili_snap::snapshot!(code, @"200 OK");
|
meili_snap::snapshot!(code, @"200 OK");
|
||||||
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
||||||
{
|
{
|
||||||
"scoreDetails": false,
|
|
||||||
"vectorStore": false,
|
"vectorStore": false,
|
||||||
"metrics": false,
|
"metrics": false,
|
||||||
|
"logsRoute": false,
|
||||||
"exportPuffinReports": false
|
"exportPuffinReports": false
|
||||||
}
|
}
|
||||||
"###);
|
"###);
|
||||||
@@ -30,9 +30,9 @@ async fn experimental_features() {
|
|||||||
meili_snap::snapshot!(code, @"200 OK");
|
meili_snap::snapshot!(code, @"200 OK");
|
||||||
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
||||||
{
|
{
|
||||||
"scoreDetails": false,
|
|
||||||
"vectorStore": true,
|
"vectorStore": true,
|
||||||
"metrics": false,
|
"metrics": false,
|
||||||
|
"logsRoute": false,
|
||||||
"exportPuffinReports": false
|
"exportPuffinReports": false
|
||||||
}
|
}
|
||||||
"###);
|
"###);
|
||||||
@@ -42,9 +42,9 @@ async fn experimental_features() {
|
|||||||
meili_snap::snapshot!(code, @"200 OK");
|
meili_snap::snapshot!(code, @"200 OK");
|
||||||
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
||||||
{
|
{
|
||||||
"scoreDetails": false,
|
|
||||||
"vectorStore": true,
|
"vectorStore": true,
|
||||||
"metrics": false,
|
"metrics": false,
|
||||||
|
"logsRoute": false,
|
||||||
"exportPuffinReports": false
|
"exportPuffinReports": false
|
||||||
}
|
}
|
||||||
"###);
|
"###);
|
||||||
@@ -55,9 +55,9 @@ async fn experimental_features() {
|
|||||||
meili_snap::snapshot!(code, @"200 OK");
|
meili_snap::snapshot!(code, @"200 OK");
|
||||||
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
||||||
{
|
{
|
||||||
"scoreDetails": false,
|
|
||||||
"vectorStore": true,
|
"vectorStore": true,
|
||||||
"metrics": false,
|
"metrics": false,
|
||||||
|
"logsRoute": false,
|
||||||
"exportPuffinReports": false
|
"exportPuffinReports": false
|
||||||
}
|
}
|
||||||
"###);
|
"###);
|
||||||
@@ -68,9 +68,9 @@ async fn experimental_features() {
|
|||||||
meili_snap::snapshot!(code, @"200 OK");
|
meili_snap::snapshot!(code, @"200 OK");
|
||||||
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
||||||
{
|
{
|
||||||
"scoreDetails": false,
|
|
||||||
"vectorStore": true,
|
"vectorStore": true,
|
||||||
"metrics": false,
|
"metrics": false,
|
||||||
|
"logsRoute": false,
|
||||||
"exportPuffinReports": false
|
"exportPuffinReports": false
|
||||||
}
|
}
|
||||||
"###);
|
"###);
|
||||||
@@ -88,9 +88,9 @@ async fn experimental_feature_metrics() {
|
|||||||
meili_snap::snapshot!(code, @"200 OK");
|
meili_snap::snapshot!(code, @"200 OK");
|
||||||
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
||||||
{
|
{
|
||||||
"scoreDetails": false,
|
|
||||||
"vectorStore": false,
|
"vectorStore": false,
|
||||||
"metrics": true,
|
"metrics": true,
|
||||||
|
"logsRoute": false,
|
||||||
"exportPuffinReports": false
|
"exportPuffinReports": false
|
||||||
}
|
}
|
||||||
"###);
|
"###);
|
||||||
@@ -146,7 +146,7 @@ async fn errors() {
|
|||||||
meili_snap::snapshot!(code, @"400 Bad Request");
|
meili_snap::snapshot!(code, @"400 Bad Request");
|
||||||
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
||||||
{
|
{
|
||||||
"message": "Unknown field `NotAFeature`: expected one of `scoreDetails`, `vectorStore`, `metrics`, `exportPuffinReports`",
|
"message": "Unknown field `NotAFeature`: expected one of `vectorStore`, `metrics`, `logsRoute`, `exportPuffinReports`",
|
||||||
"code": "bad_request",
|
"code": "bad_request",
|
||||||
"type": "invalid_request",
|
"type": "invalid_request",
|
||||||
"link": "https://docs.meilisearch.com/errors#bad_request"
|
"link": "https://docs.meilisearch.com/errors#bad_request"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ mod documents;
|
|||||||
mod dumps;
|
mod dumps;
|
||||||
mod features;
|
mod features;
|
||||||
mod index;
|
mod index;
|
||||||
|
mod logs;
|
||||||
mod search;
|
mod search;
|
||||||
mod settings;
|
mod settings;
|
||||||
mod snapshot;
|
mod snapshot;
|
||||||
|
|||||||
182
meilisearch/tests/logs/error.rs
Normal file
182
meilisearch/tests/logs/error.rs
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
use meili_snap::*;
|
||||||
|
|
||||||
|
use crate::common::Server;
|
||||||
|
use crate::json;
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn logs_stream_bad_target() {
|
||||||
|
let server = Server::new().await;
|
||||||
|
|
||||||
|
// Wrong type
|
||||||
|
let (response, code) = server.service.post("/logs/stream", json!({ "target": true })).await;
|
||||||
|
snapshot!(code, @"400 Bad Request");
|
||||||
|
snapshot!(response, @r###"
|
||||||
|
{
|
||||||
|
"message": "Invalid value type at `.target`: expected a string, but found a boolean: `true`",
|
||||||
|
"code": "bad_request",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#bad_request"
|
||||||
|
}
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Wrong type
|
||||||
|
let (response, code) = server.service.post("/logs/stream", json!({ "target": [] })).await;
|
||||||
|
snapshot!(code, @"400 Bad Request");
|
||||||
|
snapshot!(response, @r###"
|
||||||
|
{
|
||||||
|
"message": "Invalid value type at `.target`: expected a string, but found an array: `[]`",
|
||||||
|
"code": "bad_request",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#bad_request"
|
||||||
|
}
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Our help message
|
||||||
|
let (response, code) = server.service.post("/logs/stream", json!({ "target": "" })).await;
|
||||||
|
snapshot!(code, @"400 Bad Request");
|
||||||
|
snapshot!(response, @r###"
|
||||||
|
{
|
||||||
|
"message": "Invalid value at `.target`: Empty string is not a valid target. If you want to get no logs use `OFF`. Usage: `info`, `meilisearch=info`, or you can write multiple filters in one target: `index_scheduler=info,milli=trace`",
|
||||||
|
"code": "bad_request",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#bad_request"
|
||||||
|
}
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// An error from the target parser
|
||||||
|
let (response, code) = server.service.post("/logs/stream", json!({ "target": "==" })).await;
|
||||||
|
snapshot!(code, @"400 Bad Request");
|
||||||
|
snapshot!(response, @r###"
|
||||||
|
{
|
||||||
|
"message": "Invalid value at `.target`: invalid filter directive: too many '=' in filter directive, expected 0 or 1",
|
||||||
|
"code": "bad_request",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#bad_request"
|
||||||
|
}
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn logs_stream_bad_mode() {
|
||||||
|
let server = Server::new().await;
|
||||||
|
|
||||||
|
// Wrong type
|
||||||
|
let (response, code) = server.service.post("/logs/stream", json!({ "mode": true })).await;
|
||||||
|
snapshot!(code, @"400 Bad Request");
|
||||||
|
snapshot!(response, @r###"
|
||||||
|
{
|
||||||
|
"message": "Invalid value type at `.mode`: expected a string, but found a boolean: `true`",
|
||||||
|
"code": "bad_request",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#bad_request"
|
||||||
|
}
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Wrong type
|
||||||
|
let (response, code) = server.service.post("/logs/stream", json!({ "mode": [] })).await;
|
||||||
|
snapshot!(code, @"400 Bad Request");
|
||||||
|
snapshot!(response, @r###"
|
||||||
|
{
|
||||||
|
"message": "Invalid value type at `.mode`: expected a string, but found an array: `[]`",
|
||||||
|
"code": "bad_request",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#bad_request"
|
||||||
|
}
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Wrong value
|
||||||
|
let (response, code) = server.service.post("/logs/stream", json!({ "mode": "tamo" })).await;
|
||||||
|
snapshot!(code, @"400 Bad Request");
|
||||||
|
snapshot!(response, @r###"
|
||||||
|
{
|
||||||
|
"message": "Unknown value `tamo` at `.mode`: expected one of `human`, `profile`",
|
||||||
|
"code": "bad_request",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#bad_request"
|
||||||
|
}
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn logs_stream_bad_profile_memory() {
|
||||||
|
let server = Server::new().await;
|
||||||
|
|
||||||
|
// Wrong type
|
||||||
|
let (response, code) =
|
||||||
|
server.service.post("/logs/stream", json!({ "profileMemory": "tamo" })).await;
|
||||||
|
snapshot!(code, @"400 Bad Request");
|
||||||
|
snapshot!(response, @r###"
|
||||||
|
{
|
||||||
|
"message": "Invalid value type at `.profileMemory`: expected a boolean, but found a string: `\"tamo\"`",
|
||||||
|
"code": "bad_request",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#bad_request"
|
||||||
|
}
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Wrong type
|
||||||
|
let (response, code) =
|
||||||
|
server.service.post("/logs/stream", json!({ "profileMemory": ["hello", "kefir"] })).await;
|
||||||
|
snapshot!(code, @"400 Bad Request");
|
||||||
|
snapshot!(response, @r###"
|
||||||
|
{
|
||||||
|
"message": "Invalid value type at `.profileMemory`: expected a boolean, but found an array: `[\"hello\",\"kefir\"]`",
|
||||||
|
"code": "bad_request",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#bad_request"
|
||||||
|
}
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Used with default parameters
|
||||||
|
let (response, code) =
|
||||||
|
server.service.post("/logs/stream", json!({ "profileMemory": true })).await;
|
||||||
|
snapshot!(code, @"400 Bad Request");
|
||||||
|
snapshot!(response, @r###"
|
||||||
|
{
|
||||||
|
"message": "Invalid value: `profile_memory` can only be used while profiling code and is not compatible with the Human mode.",
|
||||||
|
"code": "invalid_settings_typo_tolerance",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#invalid_settings_typo_tolerance"
|
||||||
|
}
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Used with an unsupported mode
|
||||||
|
let (response, code) =
|
||||||
|
server.service.post("/logs/stream", json!({ "mode": "fmt", "profileMemory": true })).await;
|
||||||
|
snapshot!(code, @"400 Bad Request");
|
||||||
|
snapshot!(response, @r###"
|
||||||
|
{
|
||||||
|
"message": "Unknown value `fmt` at `.mode`: expected one of `human`, `profile`",
|
||||||
|
"code": "bad_request",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#bad_request"
|
||||||
|
}
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn logs_stream_without_enabling_the_route() {
|
||||||
|
let server = Server::new().await;
|
||||||
|
|
||||||
|
let (response, code) = server.service.post("/logs/stream", json!({})).await;
|
||||||
|
snapshot!(code, @"400 Bad Request");
|
||||||
|
snapshot!(response, @r###"
|
||||||
|
{
|
||||||
|
"message": "getting logs through the `/logs/stream` route requires enabling the `logs route` experimental feature. See https://github.com/orgs/meilisearch/discussions/721",
|
||||||
|
"code": "feature_not_enabled",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#feature_not_enabled"
|
||||||
|
}
|
||||||
|
"###);
|
||||||
|
|
||||||
|
let (response, code) = server.service.delete("/logs/stream").await;
|
||||||
|
snapshot!(code, @"400 Bad Request");
|
||||||
|
snapshot!(response, @r###"
|
||||||
|
{
|
||||||
|
"message": "getting logs through the `/logs/stream` route requires enabling the `logs route` experimental feature. See https://github.com/orgs/meilisearch/discussions/721",
|
||||||
|
"code": "feature_not_enabled",
|
||||||
|
"type": "invalid_request",
|
||||||
|
"link": "https://docs.meilisearch.com/errors#feature_not_enabled"
|
||||||
|
}
|
||||||
|
"###);
|
||||||
|
}
|
||||||
92
meilisearch/tests/logs/mod.rs
Normal file
92
meilisearch/tests/logs/mod.rs
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
mod error;
|
||||||
|
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use actix_web::http::header::ContentType;
|
||||||
|
use meili_snap::snapshot;
|
||||||
|
use meilisearch::{analytics, create_app, Opt};
|
||||||
|
use tracing::level_filters::LevelFilter;
|
||||||
|
use tracing_subscriber::layer::SubscriberExt;
|
||||||
|
use tracing_subscriber::Layer;
|
||||||
|
|
||||||
|
use crate::common::{default_settings, Server};
|
||||||
|
use crate::json;
|
||||||
|
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn basic_test_log_stream_route() {
|
||||||
|
let db_path = tempfile::tempdir().unwrap();
|
||||||
|
let server = Server::new_with_options(Opt {
|
||||||
|
experimental_enable_logs_route: true,
|
||||||
|
..default_settings(db_path.path())
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let (route_layer, route_layer_handle) =
|
||||||
|
tracing_subscriber::reload::Layer::new(None.with_filter(
|
||||||
|
tracing_subscriber::filter::Targets::new().with_target("", LevelFilter::OFF),
|
||||||
|
));
|
||||||
|
|
||||||
|
let subscriber = tracing_subscriber::registry().with(route_layer).with(
|
||||||
|
tracing_subscriber::fmt::layer()
|
||||||
|
.with_span_events(tracing_subscriber::fmt::format::FmtSpan::ACTIVE)
|
||||||
|
.with_filter(tracing_subscriber::filter::LevelFilter::from_str("INFO").unwrap()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let app = actix_web::test::init_service(create_app(
|
||||||
|
server.service.index_scheduler.clone().into(),
|
||||||
|
server.service.auth.clone().into(),
|
||||||
|
server.service.options.clone(),
|
||||||
|
route_layer_handle,
|
||||||
|
analytics::MockAnalytics::new(&server.service.options),
|
||||||
|
true,
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// set the subscriber as the default for the application
|
||||||
|
tracing::subscriber::set_global_default(subscriber).unwrap();
|
||||||
|
|
||||||
|
let app = Rc::new(app);
|
||||||
|
|
||||||
|
// First, we start listening on the `/logs/stream` route
|
||||||
|
let handle_app = app.clone();
|
||||||
|
let handle = tokio::task::spawn_local(async move {
|
||||||
|
let req = actix_web::test::TestRequest::post()
|
||||||
|
.uri("/logs/stream")
|
||||||
|
.insert_header(ContentType::json())
|
||||||
|
.set_payload(
|
||||||
|
serde_json::to_vec(&json!({
|
||||||
|
"mode": "human",
|
||||||
|
"target": "info",
|
||||||
|
}))
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
let req = req.to_request();
|
||||||
|
let ret = actix_web::test::call_service(&*handle_app, req).await;
|
||||||
|
actix_web::test::read_body(ret).await
|
||||||
|
});
|
||||||
|
|
||||||
|
// We're going to create an index to get at least one info log saying we processed a batch of task
|
||||||
|
let (ret, _code) = server.create_index(json!({ "uid": "tamo" })).await;
|
||||||
|
snapshot!(ret, @r###"
|
||||||
|
{
|
||||||
|
"taskUid": 0,
|
||||||
|
"indexUid": "tamo",
|
||||||
|
"status": "enqueued",
|
||||||
|
"type": "indexCreation",
|
||||||
|
"enqueuedAt": "[date]"
|
||||||
|
}
|
||||||
|
"###);
|
||||||
|
server.wait_task(ret.uid()).await;
|
||||||
|
|
||||||
|
let req = actix_web::test::TestRequest::delete().uri("/logs/stream");
|
||||||
|
let req = req.to_request();
|
||||||
|
let ret = actix_web::test::call_service(&*app, req).await;
|
||||||
|
let code = ret.status();
|
||||||
|
snapshot!(code, @"204 No Content");
|
||||||
|
|
||||||
|
let logs = handle.await.unwrap();
|
||||||
|
let logs = String::from_utf8(logs.to_vec()).unwrap();
|
||||||
|
assert!(logs.contains("INFO"), "{logs}");
|
||||||
|
}
|
||||||
@@ -13,9 +13,9 @@ async fn index_with_documents<'a>(server: &'a Server, documents: &Value) -> Inde
|
|||||||
meili_snap::snapshot!(code, @"200 OK");
|
meili_snap::snapshot!(code, @"200 OK");
|
||||||
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
||||||
{
|
{
|
||||||
"scoreDetails": false,
|
|
||||||
"vectorStore": true,
|
"vectorStore": true,
|
||||||
"metrics": false,
|
"metrics": false,
|
||||||
|
"logsRoute": false,
|
||||||
"exportPuffinReports": false
|
"exportPuffinReports": false
|
||||||
}
|
}
|
||||||
"###);
|
"###);
|
||||||
@@ -87,6 +87,52 @@ async fn simple_search() {
|
|||||||
snapshot!(response["hits"], @r###"[{"title":"Captain Marvel","desc":"a Shazam ersatz","id":"3","_vectors":{"default":[2.0,3.0]},"_semanticScore":0.99029034},{"title":"Captain Planet","desc":"He's not part of the Marvel Cinematic Universe","id":"2","_vectors":{"default":[1.0,2.0]},"_semanticScore":0.97434163},{"title":"Shazam!","desc":"a Captain Marvel ersatz","id":"1","_vectors":{"default":[1.0,3.0]},"_semanticScore":0.9472136}]"###);
|
snapshot!(response["hits"], @r###"[{"title":"Captain Marvel","desc":"a Shazam ersatz","id":"3","_vectors":{"default":[2.0,3.0]},"_semanticScore":0.99029034},{"title":"Captain Planet","desc":"He's not part of the Marvel Cinematic Universe","id":"2","_vectors":{"default":[1.0,2.0]},"_semanticScore":0.97434163},{"title":"Shazam!","desc":"a Captain Marvel ersatz","id":"1","_vectors":{"default":[1.0,3.0]},"_semanticScore":0.9472136}]"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn highlighter() {
|
||||||
|
let server = Server::new().await;
|
||||||
|
let index = index_with_documents(&server, &SIMPLE_SEARCH_DOCUMENTS).await;
|
||||||
|
|
||||||
|
let (response, code) = index
|
||||||
|
.search_post(json!({"q": "Captain Marvel", "vector": [1.0, 1.0],
|
||||||
|
"hybrid": {"semanticRatio": 0.2},
|
||||||
|
"attributesToHighlight": [
|
||||||
|
"desc"
|
||||||
|
],
|
||||||
|
"highlightPreTag": "**BEGIN**",
|
||||||
|
"highlightPostTag": "**END**"
|
||||||
|
}))
|
||||||
|
.await;
|
||||||
|
snapshot!(code, @"200 OK");
|
||||||
|
snapshot!(response["hits"], @r###"[{"title":"Captain Marvel","desc":"a Shazam ersatz","id":"3","_vectors":{"default":[2.0,3.0]},"_formatted":{"title":"Captain Marvel","desc":"a Shazam ersatz","id":"3","_vectors":{"default":["2.0","3.0"]}}},{"title":"Shazam!","desc":"a Captain Marvel ersatz","id":"1","_vectors":{"default":[1.0,3.0]},"_formatted":{"title":"Shazam!","desc":"a **BEGIN**Captain**END** **BEGIN**Marvel**END** ersatz","id":"1","_vectors":{"default":["1.0","3.0"]}}},{"title":"Captain Planet","desc":"He's not part of the Marvel Cinematic Universe","id":"2","_vectors":{"default":[1.0,2.0]},"_formatted":{"title":"Captain Planet","desc":"He's not part of the **BEGIN**Marvel**END** Cinematic Universe","id":"2","_vectors":{"default":["1.0","2.0"]}}}]"###);
|
||||||
|
|
||||||
|
let (response, code) = index
|
||||||
|
.search_post(json!({"q": "Captain Marvel", "vector": [1.0, 1.0],
|
||||||
|
"hybrid": {"semanticRatio": 0.8},
|
||||||
|
"attributesToHighlight": [
|
||||||
|
"desc"
|
||||||
|
],
|
||||||
|
"highlightPreTag": "**BEGIN**",
|
||||||
|
"highlightPostTag": "**END**"
|
||||||
|
}))
|
||||||
|
.await;
|
||||||
|
snapshot!(code, @"200 OK");
|
||||||
|
snapshot!(response["hits"], @r###"[{"title":"Captain Marvel","desc":"a Shazam ersatz","id":"3","_vectors":{"default":[2.0,3.0]},"_formatted":{"title":"Captain Marvel","desc":"a Shazam ersatz","id":"3","_vectors":{"default":["2.0","3.0"]}},"_semanticScore":0.99029034},{"title":"Captain Planet","desc":"He's not part of the Marvel Cinematic Universe","id":"2","_vectors":{"default":[1.0,2.0]},"_formatted":{"title":"Captain Planet","desc":"He's not part of the **BEGIN**Marvel**END** Cinematic Universe","id":"2","_vectors":{"default":["1.0","2.0"]}},"_semanticScore":0.97434163},{"title":"Shazam!","desc":"a Captain Marvel ersatz","id":"1","_vectors":{"default":[1.0,3.0]},"_formatted":{"title":"Shazam!","desc":"a **BEGIN**Captain**END** **BEGIN**Marvel**END** ersatz","id":"1","_vectors":{"default":["1.0","3.0"]}},"_semanticScore":0.9472136}]"###);
|
||||||
|
|
||||||
|
// no highlighting on full semantic
|
||||||
|
let (response, code) = index
|
||||||
|
.search_post(json!({"q": "Captain Marvel", "vector": [1.0, 1.0],
|
||||||
|
"hybrid": {"semanticRatio": 1.0},
|
||||||
|
"attributesToHighlight": [
|
||||||
|
"desc"
|
||||||
|
],
|
||||||
|
"highlightPreTag": "**BEGIN**",
|
||||||
|
"highlightPostTag": "**END**"
|
||||||
|
}))
|
||||||
|
.await;
|
||||||
|
snapshot!(code, @"200 OK");
|
||||||
|
snapshot!(response["hits"], @r###"[{"title":"Captain Marvel","desc":"a Shazam ersatz","id":"3","_vectors":{"default":[2.0,3.0]},"_formatted":{"title":"Captain Marvel","desc":"a Shazam ersatz","id":"3","_vectors":{"default":["2.0","3.0"]}},"_semanticScore":0.99029034},{"title":"Captain Planet","desc":"He's not part of the Marvel Cinematic Universe","id":"2","_vectors":{"default":[1.0,2.0]},"_formatted":{"title":"Captain Planet","desc":"He's not part of the Marvel Cinematic Universe","id":"2","_vectors":{"default":["1.0","2.0"]}},"_semanticScore":0.97434163},{"title":"Shazam!","desc":"a Captain Marvel ersatz","id":"1","_vectors":{"default":[1.0,3.0]},"_formatted":{"title":"Shazam!","desc":"a Captain Marvel ersatz","id":"1","_vectors":{"default":["1.0","3.0"]}}}]"###);
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn invalid_semantic_ratio() {
|
async fn invalid_semantic_ratio() {
|
||||||
let server = Server::new().await;
|
let server = Server::new().await;
|
||||||
|
|||||||
@@ -766,38 +766,14 @@ async fn faceting_max_values_per_facet() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn experimental_feature_score_details() {
|
async fn test_score_details() {
|
||||||
let server = Server::new().await;
|
let server = Server::new().await;
|
||||||
let index = server.index("test");
|
let index = server.index("test");
|
||||||
|
|
||||||
let documents = DOCUMENTS.clone();
|
let documents = DOCUMENTS.clone();
|
||||||
|
|
||||||
index.add_documents(json!(documents), None).await;
|
let res = index.add_documents(json!(documents), None).await;
|
||||||
index.wait_task(0).await;
|
index.wait_task(res.0.uid()).await;
|
||||||
|
|
||||||
index
|
|
||||||
.search(
|
|
||||||
json!({
|
|
||||||
"q": "train dragon",
|
|
||||||
"showRankingScoreDetails": true,
|
|
||||||
}),
|
|
||||||
|response, code| {
|
|
||||||
meili_snap::snapshot!(code, @"400 Bad Request");
|
|
||||||
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
|
|
||||||
{
|
|
||||||
"message": "Computing score details requires enabling the `score details` experimental feature. See https://github.com/meilisearch/product/discussions/674",
|
|
||||||
"code": "feature_not_enabled",
|
|
||||||
"type": "invalid_request",
|
|
||||||
"link": "https://docs.meilisearch.com/errors#feature_not_enabled"
|
|
||||||
}
|
|
||||||
"###);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let (response, code) = server.set_features(json!({"scoreDetails": true})).await;
|
|
||||||
meili_snap::snapshot!(code, @"200 OK");
|
|
||||||
meili_snap::snapshot!(response["scoreDetails"], @"true");
|
|
||||||
|
|
||||||
index
|
index
|
||||||
.search(
|
.search(
|
||||||
|
|||||||
@@ -44,10 +44,6 @@ struct WebhookHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn create_webhook_server() -> WebhookHandle {
|
async fn create_webhook_server() -> WebhookHandle {
|
||||||
let mut log_builder = env_logger::Builder::new();
|
|
||||||
log_builder.parse_filters("info");
|
|
||||||
log_builder.init();
|
|
||||||
|
|
||||||
let (sender, receiver) = mpsc::unbounded_channel();
|
let (sender, receiver) = mpsc::unbounded_channel();
|
||||||
let sender = Arc::new(sender);
|
let sender = Arc::new(sender);
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ edition.workspace = true
|
|||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.75"
|
anyhow = "1.0.79"
|
||||||
clap = { version = "4.2.1", features = ["derive"] }
|
clap = { version = "4.4.17", features = ["derive"] }
|
||||||
dump = { path = "../dump" }
|
dump = { path = "../dump" }
|
||||||
file-store = { path = "../file-store" }
|
file-store = { path = "../file-store" }
|
||||||
meilisearch-auth = { path = "../meilisearch-auth" }
|
meilisearch-auth = { path = "../meilisearch-auth" }
|
||||||
meilisearch-types = { path = "../meilisearch-types" }
|
meilisearch-types = { path = "../meilisearch-types" }
|
||||||
time = { version = "0.3.30", features = ["formatting"] }
|
time = { version = "0.3.31", features = ["formatting"] }
|
||||||
uuid = { version = "1.5.0", features = ["v4"], default-features = false }
|
uuid = { version = "1.6.1", features = ["v4"], default-features = false }
|
||||||
|
|||||||
@@ -14,14 +14,14 @@ license.workspace = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
bimap = { version = "0.6.3", features = ["serde"] }
|
bimap = { version = "0.6.3", features = ["serde"] }
|
||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
bstr = "1.4.0"
|
bstr = "1.9.0"
|
||||||
bytemuck = { version = "1.13.1", features = ["extern_crate_alloc"] }
|
bytemuck = { version = "1.14.0", features = ["extern_crate_alloc"] }
|
||||||
byteorder = "1.4.3"
|
byteorder = "1.5.0"
|
||||||
charabia = { version = "0.8.5", default-features = false }
|
charabia = { version = "0.8.5", default-features = false }
|
||||||
concat-arrays = "0.1.2"
|
concat-arrays = "0.1.2"
|
||||||
crossbeam-channel = "0.5.8"
|
crossbeam-channel = "0.5.11"
|
||||||
deserr = "0.6.0"
|
deserr = "0.6.1"
|
||||||
either = { version = "1.8.1", features = ["serde"] }
|
either = { version = "1.9.0", features = ["serde"] }
|
||||||
flatten-serde-json = { path = "../flatten-serde-json" }
|
flatten-serde-json = { path = "../flatten-serde-json" }
|
||||||
fst = "0.4.7"
|
fst = "0.4.7"
|
||||||
fxhash = "0.2.1"
|
fxhash = "0.2.1"
|
||||||
@@ -35,32 +35,32 @@ heed = { version = "0.20.0-alpha.9", default-features = false, features = [
|
|||||||
"serde-bincode",
|
"serde-bincode",
|
||||||
"read-txn-no-tls",
|
"read-txn-no-tls",
|
||||||
] }
|
] }
|
||||||
indexmap = { version = "2.0.0", features = ["serde"] }
|
indexmap = { version = "2.1.0", features = ["serde"] }
|
||||||
json-depth-checker = { path = "../json-depth-checker" }
|
json-depth-checker = { path = "../json-depth-checker" }
|
||||||
levenshtein_automata = { version = "0.2.1", features = ["fst_automaton"] }
|
levenshtein_automata = { version = "0.2.1", features = ["fst_automaton"] }
|
||||||
memmap2 = "0.7.1"
|
memmap2 = "0.7.1"
|
||||||
obkv = "0.2.0"
|
obkv = "0.2.1"
|
||||||
once_cell = "1.17.1"
|
once_cell = "1.19.0"
|
||||||
ordered-float = "3.6.0"
|
ordered-float = "4.2.0"
|
||||||
rand_pcg = { version = "0.3.1", features = ["serde1"] }
|
rand_pcg = { version = "0.3.1", features = ["serde1"] }
|
||||||
rayon = "1.7.0"
|
rayon = "1.8.0"
|
||||||
roaring = "0.10.1"
|
roaring = "0.10.2"
|
||||||
rstar = { version = "0.11.0", features = ["serde"] }
|
rstar = { version = "0.11.0", features = ["serde"] }
|
||||||
serde = { version = "1.0.160", features = ["derive"] }
|
serde = { version = "1.0.195", features = ["derive"] }
|
||||||
serde_json = { version = "1.0.95", features = ["preserve_order"] }
|
serde_json = { version = "1.0.111", features = ["preserve_order"] }
|
||||||
slice-group-by = "0.3.0"
|
slice-group-by = "0.3.1"
|
||||||
smallstr = { version = "0.3.0", features = ["serde"] }
|
smallstr = { version = "0.3.0", features = ["serde"] }
|
||||||
smallvec = "1.10.0"
|
smallvec = "1.12.0"
|
||||||
smartstring = "1.0.1"
|
smartstring = "1.0.1"
|
||||||
tempfile = "3.5.0"
|
tempfile = "3.9.0"
|
||||||
thiserror = "1.0.40"
|
thiserror = "1.0.56"
|
||||||
time = { version = "0.3.20", features = [
|
time = { version = "0.3.31", features = [
|
||||||
"serde-well-known",
|
"serde-well-known",
|
||||||
"formatting",
|
"formatting",
|
||||||
"parsing",
|
"parsing",
|
||||||
"macros",
|
"macros",
|
||||||
] }
|
] }
|
||||||
uuid = { version = "1.3.1", features = ["v4"] }
|
uuid = { version = "1.6.1", features = ["v4"] }
|
||||||
|
|
||||||
filter-parser = { path = "../filter-parser" }
|
filter-parser = { path = "../filter-parser" }
|
||||||
|
|
||||||
@@ -71,9 +71,8 @@ itertools = "0.11.0"
|
|||||||
puffin = "0.16.0"
|
puffin = "0.16.0"
|
||||||
|
|
||||||
# logging
|
# logging
|
||||||
log = "0.4.17"
|
|
||||||
logging_timer = "1.1.0"
|
logging_timer = "1.1.0"
|
||||||
csv = "1.2.1"
|
csv = "1.3.0"
|
||||||
candle-core = { git = "https://github.com/huggingface/candle.git", version = "0.3.1" }
|
candle-core = { git = "https://github.com/huggingface/candle.git", version = "0.3.1" }
|
||||||
candle-transformers = { git = "https://github.com/huggingface/candle.git", version = "0.3.1" }
|
candle-transformers = { git = "https://github.com/huggingface/candle.git", version = "0.3.1" }
|
||||||
candle-nn = { git = "https://github.com/huggingface/candle.git", version = "0.3.1" }
|
candle-nn = { git = "https://github.com/huggingface/candle.git", version = "0.3.1" }
|
||||||
@@ -81,36 +80,29 @@ tokenizers = { git = "https://github.com/huggingface/tokenizers.git", tag = "v0.
|
|||||||
hf-hub = { git = "https://github.com/dureuill/hf-hub.git", branch = "rust_tls", default_features = false, features = [
|
hf-hub = { git = "https://github.com/dureuill/hf-hub.git", branch = "rust_tls", default_features = false, features = [
|
||||||
"online",
|
"online",
|
||||||
] }
|
] }
|
||||||
tokio = { version = "1.34.0", features = ["rt"] }
|
tokio = { version = "1.35.1", features = ["rt"] }
|
||||||
futures = "0.3.29"
|
futures = "0.3.30"
|
||||||
reqwest = { version = "0.11.16", features = [
|
reqwest = { version = "0.11.23", features = [
|
||||||
"rustls-tls",
|
"rustls-tls",
|
||||||
"json",
|
"json",
|
||||||
], default-features = false }
|
], default-features = false }
|
||||||
tiktoken-rs = "0.5.7"
|
tiktoken-rs = "0.5.8"
|
||||||
liquid = "0.26.4"
|
liquid = "0.26.4"
|
||||||
arroy = { git = "https://github.com/meilisearch/arroy.git", version = "0.1.0" }
|
arroy = "0.2.0"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
tracing = "0.1.40"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
mimalloc = { version = "0.1.37", default-features = false }
|
mimalloc = { version = "0.1.39", default-features = false }
|
||||||
big_s = "1.0.2"
|
big_s = "1.0.2"
|
||||||
insta = "1.29.0"
|
insta = "1.34.0"
|
||||||
maplit = "1.0.2"
|
maplit = "1.0.2"
|
||||||
md5 = "0.7.0"
|
md5 = "0.7.0"
|
||||||
meili-snap = { path = "../meili-snap" }
|
meili-snap = { path = "../meili-snap" }
|
||||||
rand = { version = "0.8.5", features = ["small_rng"] }
|
rand = { version = "0.8.5", features = ["small_rng"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
all-tokenizations = [
|
all-tokenizations = ["charabia/chinese", "charabia/hebrew", "charabia/japanese", "charabia/thai", "charabia/korean", "charabia/greek", "charabia/khmer"]
|
||||||
"charabia/chinese",
|
|
||||||
"charabia/hebrew",
|
|
||||||
"charabia/japanese",
|
|
||||||
"charabia/thai",
|
|
||||||
"charabia/korean",
|
|
||||||
"charabia/greek",
|
|
||||||
"charabia/khmer",
|
|
||||||
]
|
|
||||||
|
|
||||||
# Use POSIX semaphores instead of SysV semaphores in LMDB
|
# Use POSIX semaphores instead of SysV semaphores in LMDB
|
||||||
# For more information on this feature, see heed's Cargo.toml
|
# For more information on this feature, see heed's Cargo.toml
|
||||||
@@ -137,3 +129,6 @@ greek = ["charabia/greek"]
|
|||||||
|
|
||||||
# allow khmer specialized tokenization
|
# allow khmer specialized tokenization
|
||||||
khmer = ["charabia/khmer"]
|
khmer = ["charabia/khmer"]
|
||||||
|
|
||||||
|
# allow CUDA support, see <https://github.com/meilisearch/meilisearch/issues/4306>
|
||||||
|
cuda = ["candle-core/cuda"]
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ impl<R: io::Read + io::Seek> DocumentsBatchReader<R> {
|
|||||||
///
|
///
|
||||||
/// It first retrieves the index, then moves to the first document. Use the `into_cursor`
|
/// It first retrieves the index, then moves to the first document. Use the `into_cursor`
|
||||||
/// method to iterator over the documents, from the first to the last.
|
/// method to iterator over the documents, from the first to the last.
|
||||||
|
#[tracing::instrument(level = "trace", skip_all, target = "indexing::documents")]
|
||||||
pub fn from_reader(reader: R) -> Result<Self, Error> {
|
pub fn from_reader(reader: R) -> Result<Self, Error> {
|
||||||
let reader = grenad::Reader::new(reader)?;
|
let reader = grenad::Reader::new(reader)?;
|
||||||
let mut cursor = reader.into_cursor()?;
|
let mut cursor = reader.into_cursor()?;
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ only composed of alphanumeric characters (a-z A-Z 0-9), hyphens (-) and undersco
|
|||||||
TooManyDocumentIds { primary_key: String, document: Object },
|
TooManyDocumentIds { primary_key: String, document: Object },
|
||||||
#[error("The primary key inference failed as the engine did not find any field ending with `id` in its name. Please specify the primary key manually using the `primaryKey` query parameter.")]
|
#[error("The primary key inference failed as the engine did not find any field ending with `id` in its name. Please specify the primary key manually using the `primaryKey` query parameter.")]
|
||||||
NoPrimaryKeyCandidateFound,
|
NoPrimaryKeyCandidateFound,
|
||||||
#[error("The primary key inference failed as the engine found {} fields ending with `id` in their names: '{}' and '{}'. Please specify the primary key manually using the `primaryKey` query parameter.", .candidates.len(), .candidates.get(0).unwrap(), .candidates.get(1).unwrap())]
|
#[error("The primary key inference failed as the engine found {} fields ending with `id` in their names: '{}' and '{}'. Please specify the primary key manually using the `primaryKey` query parameter.", .candidates.len(), .candidates.first().unwrap(), .candidates.get(1).unwrap())]
|
||||||
MultiplePrimaryKeyCandidatesFound { candidates: Vec<String> },
|
MultiplePrimaryKeyCandidatesFound { candidates: Vec<String> },
|
||||||
#[error("There is no more space left on the device. Consider increasing the size of the disk/partition.")]
|
#[error("There is no more space left on the device. Consider increasing the size of the disk/partition.")]
|
||||||
NoSpaceLeftOnDevice,
|
NoSpaceLeftOnDevice,
|
||||||
@@ -227,6 +227,22 @@ only composed of alphanumeric characters (a-z A-Z 0-9), hyphens (-) and undersco
|
|||||||
source_: crate::vector::settings::EmbedderSource,
|
source_: crate::vector::settings::EmbedderSource,
|
||||||
embedder_name: String,
|
embedder_name: String,
|
||||||
},
|
},
|
||||||
|
#[error("`.embedders.{embedder_name}.dimensions`: Model `{model}` does not support overriding its native dimensions of {expected_dimensions}. Found {dimensions}")]
|
||||||
|
InvalidOpenAiModelDimensions {
|
||||||
|
embedder_name: String,
|
||||||
|
model: &'static str,
|
||||||
|
dimensions: usize,
|
||||||
|
expected_dimensions: usize,
|
||||||
|
},
|
||||||
|
#[error("`.embedders.{embedder_name}.dimensions`: Model `{model}` does not support overriding its dimensions to a value higher than {max_dimensions}. Found {dimensions}")]
|
||||||
|
InvalidOpenAiModelDimensionsMax {
|
||||||
|
embedder_name: String,
|
||||||
|
model: &'static str,
|
||||||
|
dimensions: usize,
|
||||||
|
max_dimensions: usize,
|
||||||
|
},
|
||||||
|
#[error("`.embedders.{embedder_name}.dimensions`: `dimensions` cannot be zero")]
|
||||||
|
InvalidSettingsDimensions { embedder_name: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<crate::vector::Error> for Error {
|
impl From<crate::vector::Error> for Error {
|
||||||
@@ -251,6 +267,7 @@ impl From<arroy::Error> for Error {
|
|||||||
arroy::Error::DatabaseFull
|
arroy::Error::DatabaseFull
|
||||||
| arroy::Error::InvalidItemAppend
|
| arroy::Error::InvalidItemAppend
|
||||||
| arroy::Error::UnmatchingDistance { .. }
|
| arroy::Error::UnmatchingDistance { .. }
|
||||||
|
| arroy::Error::MissingNode
|
||||||
| arroy::Error::MissingMetadata => {
|
| arroy::Error::MissingMetadata => {
|
||||||
Error::InternalError(InternalError::ArroyError(value))
|
Error::InternalError(InternalError::ArroyError(value))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ mod tests {
|
|||||||
get_simple_string_index_with_multiple_field_ids(),
|
get_simple_string_index_with_multiple_field_ids(),
|
||||||
get_random_looking_string_index_with_multiple_field_ids(),
|
get_random_looking_string_index_with_multiple_field_ids(),
|
||||||
];
|
];
|
||||||
for (_i, index) in indexes.iter().enumerate() {
|
for index in indexes {
|
||||||
let txn = index.env.read_txn().unwrap();
|
let txn = index.env.read_txn().unwrap();
|
||||||
let candidates = RoaringBitmap::new();
|
let candidates = RoaringBitmap::new();
|
||||||
let mut results = String::new();
|
let mut results = String::new();
|
||||||
@@ -212,7 +212,7 @@ mod tests {
|
|||||||
get_simple_string_index_with_multiple_field_ids(),
|
get_simple_string_index_with_multiple_field_ids(),
|
||||||
get_random_looking_string_index_with_multiple_field_ids(),
|
get_random_looking_string_index_with_multiple_field_ids(),
|
||||||
];
|
];
|
||||||
for (_i, index) in indexes.iter().enumerate() {
|
for index in indexes {
|
||||||
let txn = index.env.read_txn().unwrap();
|
let txn = index.env.read_txn().unwrap();
|
||||||
let candidates = RoaringBitmap::new();
|
let candidates = RoaringBitmap::new();
|
||||||
let mut results = String::new();
|
let mut results = String::new();
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ mod tests {
|
|||||||
get_simple_string_index_with_multiple_field_ids(),
|
get_simple_string_index_with_multiple_field_ids(),
|
||||||
get_random_looking_string_index_with_multiple_field_ids(),
|
get_random_looking_string_index_with_multiple_field_ids(),
|
||||||
];
|
];
|
||||||
for (_i, index) in indexes.iter().enumerate() {
|
for index in indexes {
|
||||||
let txn = index.env.read_txn().unwrap();
|
let txn = index.env.read_txn().unwrap();
|
||||||
let candidates = RoaringBitmap::new();
|
let candidates = RoaringBitmap::new();
|
||||||
let mut results = String::new();
|
let mut results = String::new();
|
||||||
@@ -226,7 +226,7 @@ mod tests {
|
|||||||
get_simple_string_index_with_multiple_field_ids(),
|
get_simple_string_index_with_multiple_field_ids(),
|
||||||
get_random_looking_string_index_with_multiple_field_ids(),
|
get_random_looking_string_index_with_multiple_field_ids(),
|
||||||
];
|
];
|
||||||
for (_i, index) in indexes.iter().enumerate() {
|
for index in indexes {
|
||||||
let txn = index.env.read_txn().unwrap();
|
let txn = index.env.read_txn().unwrap();
|
||||||
let candidates = RoaringBitmap::new();
|
let candidates = RoaringBitmap::new();
|
||||||
let mut results = String::new();
|
let mut results = String::new();
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ pub(crate) mod tests {
|
|||||||
let mut txn = index.env.write_txn().unwrap();
|
let mut txn = index.env.write_txn().unwrap();
|
||||||
let mut rng = rand::rngs::SmallRng::from_seed([0; 32]);
|
let mut rng = rand::rngs::SmallRng::from_seed([0; 32]);
|
||||||
|
|
||||||
for (_i, key) in std::iter::from_fn(|| Some(rng.gen_range(0..256))).take(128).enumerate() {
|
for key in std::iter::from_fn(|| Some(rng.gen_range(0..256))).take(128) {
|
||||||
let mut bitmap = RoaringBitmap::new();
|
let mut bitmap = RoaringBitmap::new();
|
||||||
bitmap.insert(key);
|
bitmap.insert(key);
|
||||||
bitmap.insert(key + 100);
|
bitmap.insert(key + 100);
|
||||||
@@ -172,7 +172,7 @@ pub(crate) mod tests {
|
|||||||
let keys =
|
let keys =
|
||||||
std::iter::from_fn(|| Some(rng.gen_range(0..256))).take(128).collect::<Vec<u32>>();
|
std::iter::from_fn(|| Some(rng.gen_range(0..256))).take(128).collect::<Vec<u32>>();
|
||||||
for fid in 0..2 {
|
for fid in 0..2 {
|
||||||
for (_i, &key) in keys.iter().enumerate() {
|
for &key in &keys {
|
||||||
let mut bitmap = RoaringBitmap::new();
|
let mut bitmap = RoaringBitmap::new();
|
||||||
bitmap.insert(key);
|
bitmap.insert(key);
|
||||||
bitmap.insert(key + 100);
|
bitmap.insert(key + 100);
|
||||||
@@ -207,7 +207,7 @@ pub(crate) mod tests {
|
|||||||
let keys =
|
let keys =
|
||||||
std::iter::from_fn(|| Some(rng.gen_range(0..256))).take(128).collect::<Vec<u32>>();
|
std::iter::from_fn(|| Some(rng.gen_range(0..256))).take(128).collect::<Vec<u32>>();
|
||||||
for fid in 0..2 {
|
for fid in 0..2 {
|
||||||
for (_i, &key) in keys.iter().enumerate() {
|
for &key in &keys {
|
||||||
let mut bitmap = RoaringBitmap::new();
|
let mut bitmap = RoaringBitmap::new();
|
||||||
bitmap.insert(key);
|
bitmap.insert(key);
|
||||||
bitmap.insert(key + 100);
|
bitmap.insert(key + 100);
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ impl ScoreWithRatioResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SearchResult {
|
SearchResult {
|
||||||
matching_words: left.matching_words,
|
matching_words: right.matching_words,
|
||||||
candidates: left.candidates | right.candidates,
|
candidates: left.candidates | right.candidates,
|
||||||
documents_ids,
|
documents_ids,
|
||||||
document_scores,
|
document_scores,
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ use charabia::Normalize;
|
|||||||
use fst::automaton::{Automaton, Str};
|
use fst::automaton::{Automaton, Str};
|
||||||
use fst::{IntoStreamer, Streamer};
|
use fst::{IntoStreamer, Streamer};
|
||||||
use levenshtein_automata::{LevenshteinAutomatonBuilder as LevBuilder, DFA};
|
use levenshtein_automata::{LevenshteinAutomatonBuilder as LevBuilder, DFA};
|
||||||
use log::error;
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use roaring::bitmap::RoaringBitmap;
|
use roaring::bitmap::RoaringBitmap;
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
pub use self::facet::{FacetDistribution, Filter, OrderBy, DEFAULT_VALUES_PER_FACET};
|
pub use self::facet::{FacetDistribution, Filter, OrderBy, DEFAULT_VALUES_PER_FACET};
|
||||||
pub use self::new::matches::{FormatOptions, MatchBounds, MatcherBuilder, MatchingWords};
|
pub use self::new::matches::{FormatOptions, MatchBounds, MatcherBuilder, MatchingWords};
|
||||||
|
|||||||
@@ -166,6 +166,9 @@ pub fn bucket_sort<'ctx, Q: RankingRuleQueryTrait>(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let span = tracing::trace_span!(target: "search::bucket_sort", "next_bucket", id = ranking_rules[cur_ranking_rule_index].id());
|
||||||
|
let entered = span.enter();
|
||||||
|
|
||||||
let Some(next_bucket) = ranking_rules[cur_ranking_rule_index].next_bucket(
|
let Some(next_bucket) = ranking_rules[cur_ranking_rule_index].next_bucket(
|
||||||
ctx,
|
ctx,
|
||||||
logger,
|
logger,
|
||||||
@@ -175,6 +178,7 @@ pub fn bucket_sort<'ctx, Q: RankingRuleQueryTrait>(
|
|||||||
back!();
|
back!();
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
drop(entered);
|
||||||
|
|
||||||
ranking_rule_scores.push(next_bucket.score);
|
ranking_rule_scores.push(next_bucket.score);
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,12 @@ impl<'t, 'i> ClearDocuments<'t, 'i> {
|
|||||||
ClearDocuments { wtxn, index }
|
ClearDocuments { wtxn, index }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(
|
||||||
|
level = "trace",
|
||||||
|
skip(self),
|
||||||
|
target = "indexing::documents",
|
||||||
|
name = "clear_documents"
|
||||||
|
)]
|
||||||
pub fn execute(self) -> Result<u64> {
|
pub fn execute(self) -> Result<u64> {
|
||||||
puffin::profile_function!();
|
puffin::profile_function!();
|
||||||
|
|
||||||
|
|||||||
@@ -751,7 +751,7 @@ mod tests {
|
|||||||
let mut rng = rand::rngs::SmallRng::from_seed([0; 32]);
|
let mut rng = rand::rngs::SmallRng::from_seed([0; 32]);
|
||||||
keys.shuffle(&mut rng);
|
keys.shuffle(&mut rng);
|
||||||
|
|
||||||
for (_i, key) in keys.into_iter().enumerate() {
|
for key in keys {
|
||||||
let mut bitmap = RoaringBitmap::new();
|
let mut bitmap = RoaringBitmap::new();
|
||||||
bitmap.insert(key);
|
bitmap.insert(key);
|
||||||
index.insert(&mut txn, 0, &(key as f64), &bitmap);
|
index.insert(&mut txn, 0, &(key as f64), &bitmap);
|
||||||
@@ -770,7 +770,7 @@ mod tests {
|
|||||||
let mut rng = rand::rngs::SmallRng::from_seed([0; 32]);
|
let mut rng = rand::rngs::SmallRng::from_seed([0; 32]);
|
||||||
keys.shuffle(&mut rng);
|
keys.shuffle(&mut rng);
|
||||||
|
|
||||||
for (_i, key) in keys.into_iter().enumerate() {
|
for key in keys {
|
||||||
let mut bitmap = RoaringBitmap::new();
|
let mut bitmap = RoaringBitmap::new();
|
||||||
bitmap.insert(key);
|
bitmap.insert(key);
|
||||||
bitmap.insert(rng.gen_range(256..512));
|
bitmap.insert(rng.gen_range(256..512));
|
||||||
|
|||||||
@@ -85,8 +85,8 @@ use charabia::normalizer::{Normalize, NormalizerOption};
|
|||||||
use grenad::{CompressionType, SortAlgorithm};
|
use grenad::{CompressionType, SortAlgorithm};
|
||||||
use heed::types::{Bytes, DecodeIgnore, SerdeJson};
|
use heed::types::{Bytes, DecodeIgnore, SerdeJson};
|
||||||
use heed::BytesEncode;
|
use heed::BytesEncode;
|
||||||
use log::debug;
|
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
use self::incremental::FacetsUpdateIncremental;
|
use self::incremental::FacetsUpdateIncremental;
|
||||||
use super::FacetsUpdateBulk;
|
use super::FacetsUpdateBulk;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ use crate::{FieldId, Index, Result};
|
|||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// - if reader.is_empty(), this function may panic in some cases
|
/// - if reader.is_empty(), this function may panic in some cases
|
||||||
|
#[tracing::instrument(level = "trace", skip_all, target = "indexing::documents")]
|
||||||
pub fn enrich_documents_batch<R: Read + Seek>(
|
pub fn enrich_documents_batch<R: Read + Seek>(
|
||||||
rtxn: &heed::RoTxn,
|
rtxn: &heed::RoTxn,
|
||||||
index: &Index,
|
index: &Index,
|
||||||
@@ -77,7 +78,7 @@ pub fn enrich_documents_batch<R: Read + Seek>(
|
|||||||
},
|
},
|
||||||
[] => return Ok(Err(UserError::NoPrimaryKeyCandidateFound)),
|
[] => return Ok(Err(UserError::NoPrimaryKeyCandidateFound)),
|
||||||
[(field_id, name)] => {
|
[(field_id, name)] => {
|
||||||
log::info!("Primary key was not specified in index. Inferred to '{name}'");
|
tracing::info!("Primary key was not specified in index. Inferred to '{name}'");
|
||||||
PrimaryKey::Flat { name, field_id: *field_id }
|
PrimaryKey::Flat { name, field_id: *field_id }
|
||||||
}
|
}
|
||||||
multiple => {
|
multiple => {
|
||||||
@@ -143,6 +144,8 @@ pub fn enrich_documents_batch<R: Read + Seek>(
|
|||||||
|
|
||||||
/// Retrieve the document id after validating it, returning a `UserError`
|
/// Retrieve the document id after validating it, returning a `UserError`
|
||||||
/// if the id is invalid or can't be guessed.
|
/// if the id is invalid or can't be guessed.
|
||||||
|
#[tracing::instrument(level = "trace", skip(uuid_buffer, documents_batch_index, document)
|
||||||
|
target = "indexing::documents")]
|
||||||
fn fetch_or_generate_document_id(
|
fn fetch_or_generate_document_id(
|
||||||
document: &obkv::KvReader<FieldId>,
|
document: &obkv::KvReader<FieldId>,
|
||||||
documents_batch_index: &DocumentsBatchIndex,
|
documents_batch_index: &DocumentsBatchIndex,
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ pub type ScriptLanguageDocidsMap = HashMap<(Script, Language), (RoaringBitmap, R
|
|||||||
///
|
///
|
||||||
/// Returns the generated internal documents ids and a grenad reader
|
/// Returns the generated internal documents ids and a grenad reader
|
||||||
/// with the list of extracted words from the given chunk of documents.
|
/// with the list of extracted words from the given chunk of documents.
|
||||||
#[logging_timer::time]
|
#[tracing::instrument(level = "trace", skip_all, target = "indexing::extract")]
|
||||||
pub fn extract_docid_word_positions<R: io::Read + io::Seek>(
|
pub fn extract_docid_word_positions<R: io::Read + io::Seek>(
|
||||||
obkv_documents: grenad::Reader<R>,
|
obkv_documents: grenad::Reader<R>,
|
||||||
indexer: GrenadParameters,
|
indexer: GrenadParameters,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ use crate::Result;
|
|||||||
///
|
///
|
||||||
/// Returns a grenad reader with the list of extracted facet numbers and
|
/// Returns a grenad reader with the list of extracted facet numbers and
|
||||||
/// documents ids from the given chunk of docid facet number positions.
|
/// documents ids from the given chunk of docid facet number positions.
|
||||||
#[logging_timer::time]
|
#[tracing::instrument(level = "trace", skip_all, target = "indexing::extract")]
|
||||||
pub fn extract_facet_number_docids<R: io::Read + io::Seek>(
|
pub fn extract_facet_number_docids<R: io::Read + io::Seek>(
|
||||||
fid_docid_facet_number: grenad::Reader<R>,
|
fid_docid_facet_number: grenad::Reader<R>,
|
||||||
indexer: GrenadParameters,
|
indexer: GrenadParameters,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use crate::{FieldId, Result};
|
|||||||
///
|
///
|
||||||
/// Returns a grenad reader with the list of extracted facet strings and
|
/// Returns a grenad reader with the list of extracted facet strings and
|
||||||
/// documents ids from the given chunk of docid facet string positions.
|
/// documents ids from the given chunk of docid facet string positions.
|
||||||
#[logging_timer::time]
|
#[tracing::instrument(level = "trace", skip_all, target = "indexing::extract")]
|
||||||
pub fn extract_facet_string_docids<R: io::Read + io::Seek>(
|
pub fn extract_facet_string_docids<R: io::Read + io::Seek>(
|
||||||
docid_fid_facet_string: grenad::Reader<R>,
|
docid_fid_facet_string: grenad::Reader<R>,
|
||||||
indexer: GrenadParameters,
|
indexer: GrenadParameters,
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ pub struct ExtractedFacetValues {
|
|||||||
/// Returns the generated grenad reader containing the docid the fid and the orginal value as key
|
/// Returns the generated grenad reader containing the docid the fid and the orginal value as key
|
||||||
/// and the normalized value as value extracted from the given chunk of documents.
|
/// and the normalized value as value extracted from the given chunk of documents.
|
||||||
/// We need the fid of the geofields to correctly parse them as numbers if they were sent as strings initially.
|
/// We need the fid of the geofields to correctly parse them as numbers if they were sent as strings initially.
|
||||||
#[logging_timer::time]
|
#[tracing::instrument(level = "trace", skip_all, target = "indexing::extract")]
|
||||||
pub fn extract_fid_docid_facet_values<R: io::Read + io::Seek>(
|
pub fn extract_fid_docid_facet_values<R: io::Read + io::Seek>(
|
||||||
obkv_documents: grenad::Reader<R>,
|
obkv_documents: grenad::Reader<R>,
|
||||||
indexer: GrenadParameters,
|
indexer: GrenadParameters,
|
||||||
@@ -431,7 +431,7 @@ fn extract_facet_values(value: &Value, geo_field: bool) -> FilterableValues {
|
|||||||
if let Ok(float) = original.parse() {
|
if let Ok(float) = original.parse() {
|
||||||
output_numbers.push(float);
|
output_numbers.push(float);
|
||||||
} else {
|
} else {
|
||||||
log::warn!(
|
tracing::warn!(
|
||||||
"Internal error, could not parse a geofield that has been validated. Please open an issue."
|
"Internal error, could not parse a geofield that has been validated. Please open an issue."
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const MAX_COUNTED_WORDS: usize = 30;
|
|||||||
///
|
///
|
||||||
/// Returns a grenad reader with the list of extracted field id word counts
|
/// Returns a grenad reader with the list of extracted field id word counts
|
||||||
/// and documents ids from the given chunk of docid word positions.
|
/// and documents ids from the given chunk of docid word positions.
|
||||||
#[logging_timer::time]
|
#[tracing::instrument(level = "trace", skip_all, target = "indexing::extract")]
|
||||||
pub fn extract_fid_word_count_docids<R: io::Read + io::Seek>(
|
pub fn extract_fid_word_count_docids<R: io::Read + io::Seek>(
|
||||||
docid_word_positions: grenad::Reader<R>,
|
docid_word_positions: grenad::Reader<R>,
|
||||||
indexer: GrenadParameters,
|
indexer: GrenadParameters,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use crate::{FieldId, InternalError, Result};
|
|||||||
/// Extracts the geographical coordinates contained in each document under the `_geo` field.
|
/// Extracts the geographical coordinates contained in each document under the `_geo` field.
|
||||||
///
|
///
|
||||||
/// Returns the generated grenad reader containing the docid as key associated to the (latitude, longitude)
|
/// Returns the generated grenad reader containing the docid as key associated to the (latitude, longitude)
|
||||||
#[logging_timer::time]
|
#[tracing::instrument(level = "trace", skip_all, target = "indexing::extract")]
|
||||||
pub fn extract_geo_points<R: io::Read + io::Seek>(
|
pub fn extract_geo_points<R: io::Read + io::Seek>(
|
||||||
obkv_documents: grenad::Reader<R>,
|
obkv_documents: grenad::Reader<R>,
|
||||||
indexer: GrenadParameters,
|
indexer: GrenadParameters,
|
||||||
@@ -34,7 +34,9 @@ pub fn extract_geo_points<R: io::Read + io::Seek>(
|
|||||||
// since we only need the primary key when we throw an error
|
// since we only need the primary key when we throw an error
|
||||||
// we create this getter to lazily get it when needed
|
// we create this getter to lazily get it when needed
|
||||||
let document_id = || -> Value {
|
let document_id = || -> Value {
|
||||||
let document_id = obkv.get(primary_key_id).unwrap();
|
let reader = KvReaderDelAdd::new(obkv.get(primary_key_id).unwrap());
|
||||||
|
let document_id =
|
||||||
|
reader.get(DelAdd::Deletion).or(reader.get(DelAdd::Addition)).unwrap();
|
||||||
serde_json::from_slice(document_id).unwrap()
|
serde_json::from_slice(document_id).unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ impl VectorStateDelta {
|
|||||||
/// Extracts the embedding vector contained in each document under the `_vectors` field.
|
/// Extracts the embedding vector contained in each document under the `_vectors` field.
|
||||||
///
|
///
|
||||||
/// Returns the generated grenad reader containing the docid as key associated to the Vec<f32>
|
/// Returns the generated grenad reader containing the docid as key associated to the Vec<f32>
|
||||||
#[logging_timer::time]
|
#[tracing::instrument(level = "trace", skip_all, target = "indexing::extract")]
|
||||||
pub fn extract_vector_points<R: io::Read + io::Seek>(
|
pub fn extract_vector_points<R: io::Read + io::Seek>(
|
||||||
obkv_documents: grenad::Reader<R>,
|
obkv_documents: grenad::Reader<R>,
|
||||||
indexer: GrenadParameters,
|
indexer: GrenadParameters,
|
||||||
@@ -135,7 +135,7 @@ pub fn extract_vector_points<R: io::Read + io::Seek>(
|
|||||||
let del_vectors = extract_vectors(old, document_id, embedder_name)?;
|
let del_vectors = extract_vectors(old, document_id, embedder_name)?;
|
||||||
let add_vectors = extract_vectors(new, document_id, embedder_name)?;
|
let add_vectors = extract_vectors(new, document_id, embedder_name)?;
|
||||||
|
|
||||||
if add_vectors.len() > u8::MAX.into() {
|
if add_vectors.len() > usize::from(u8::MAX) {
|
||||||
return Err(crate::Error::UserError(crate::UserError::TooManyVectors(
|
return Err(crate::Error::UserError(crate::UserError::TooManyVectors(
|
||||||
document_id().to_string(),
|
document_id().to_string(),
|
||||||
add_vectors.len(),
|
add_vectors.len(),
|
||||||
@@ -164,7 +164,7 @@ pub fn extract_vector_points<R: io::Read + io::Seek>(
|
|||||||
(None, Some(new)) => {
|
(None, Some(new)) => {
|
||||||
// was possibly autogenerated, remove all vectors for that document
|
// was possibly autogenerated, remove all vectors for that document
|
||||||
let add_vectors = extract_vectors(new, document_id, embedder_name)?;
|
let add_vectors = extract_vectors(new, document_id, embedder_name)?;
|
||||||
if add_vectors.len() > u8::MAX.into() {
|
if add_vectors.len() > usize::from(u8::MAX) {
|
||||||
return Err(crate::Error::UserError(crate::UserError::TooManyVectors(
|
return Err(crate::Error::UserError(crate::UserError::TooManyVectors(
|
||||||
document_id().to_string(),
|
document_id().to_string(),
|
||||||
add_vectors.len(),
|
add_vectors.len(),
|
||||||
@@ -186,12 +186,12 @@ pub fn extract_vector_points<R: io::Read + io::Seek>(
|
|||||||
prompt.render(obkv, DelAdd::Deletion, field_id_map).unwrap_or_default();
|
prompt.render(obkv, DelAdd::Deletion, field_id_map).unwrap_or_default();
|
||||||
let new_prompt = prompt.render(obkv, DelAdd::Addition, field_id_map)?;
|
let new_prompt = prompt.render(obkv, DelAdd::Addition, field_id_map)?;
|
||||||
if old_prompt != new_prompt {
|
if old_prompt != new_prompt {
|
||||||
log::trace!(
|
tracing::trace!(
|
||||||
"🚀 Changing prompt from\n{old_prompt}\n===to===\n{new_prompt}"
|
"🚀 Changing prompt from\n{old_prompt}\n===to===\n{new_prompt}"
|
||||||
);
|
);
|
||||||
VectorStateDelta::NowGenerated(new_prompt)
|
VectorStateDelta::NowGenerated(new_prompt)
|
||||||
} else {
|
} else {
|
||||||
log::trace!("⏭️ Prompt unmodified, skipping");
|
tracing::trace!("⏭️ Prompt unmodified, skipping");
|
||||||
VectorStateDelta::NoChange
|
VectorStateDelta::NoChange
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -339,9 +339,7 @@ pub fn extract_embeddings<R: io::Read + io::Seek>(
|
|||||||
indexer: GrenadParameters,
|
indexer: GrenadParameters,
|
||||||
embedder: Arc<Embedder>,
|
embedder: Arc<Embedder>,
|
||||||
) -> Result<grenad::Reader<BufReader<File>>> {
|
) -> Result<grenad::Reader<BufReader<File>>> {
|
||||||
let rt = tokio::runtime::Builder::new_current_thread().enable_io().enable_time().build()?;
|
let n_chunks = embedder.chunk_count_hint(); // chunk level parallelism
|
||||||
|
|
||||||
let n_chunks = embedder.chunk_count_hint(); // chunk level parellelism
|
|
||||||
let n_vectors_per_chunk = embedder.prompt_count_in_chunk_hint(); // number of vectors in a single chunk
|
let n_vectors_per_chunk = embedder.prompt_count_in_chunk_hint(); // number of vectors in a single chunk
|
||||||
|
|
||||||
// docid, state with embedding
|
// docid, state with embedding
|
||||||
@@ -375,11 +373,8 @@ pub fn extract_embeddings<R: io::Read + io::Seek>(
|
|||||||
current_chunk_ids.push(docid);
|
current_chunk_ids.push(docid);
|
||||||
|
|
||||||
if chunks.len() == chunks.capacity() {
|
if chunks.len() == chunks.capacity() {
|
||||||
let chunked_embeds = rt
|
let chunked_embeds = embedder
|
||||||
.block_on(
|
.embed_chunks(std::mem::replace(&mut chunks, Vec::with_capacity(n_chunks)))
|
||||||
embedder
|
|
||||||
.embed_chunks(std::mem::replace(&mut chunks, Vec::with_capacity(n_chunks))),
|
|
||||||
)
|
|
||||||
.map_err(crate::vector::Error::from)
|
.map_err(crate::vector::Error::from)
|
||||||
.map_err(crate::Error::from)?;
|
.map_err(crate::Error::from)?;
|
||||||
|
|
||||||
@@ -396,8 +391,8 @@ pub fn extract_embeddings<R: io::Read + io::Seek>(
|
|||||||
|
|
||||||
// send last chunk
|
// send last chunk
|
||||||
if !chunks.is_empty() {
|
if !chunks.is_empty() {
|
||||||
let chunked_embeds = rt
|
let chunked_embeds = embedder
|
||||||
.block_on(embedder.embed_chunks(std::mem::take(&mut chunks)))
|
.embed_chunks(std::mem::take(&mut chunks))
|
||||||
.map_err(crate::vector::Error::from)
|
.map_err(crate::vector::Error::from)
|
||||||
.map_err(crate::Error::from)?;
|
.map_err(crate::Error::from)?;
|
||||||
for (docid, embeddings) in chunks_ids
|
for (docid, embeddings) in chunks_ids
|
||||||
@@ -410,13 +405,15 @@ pub fn extract_embeddings<R: io::Read + io::Seek>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !current_chunk.is_empty() {
|
if !current_chunk.is_empty() {
|
||||||
let embeds = rt
|
let embeds = embedder
|
||||||
.block_on(embedder.embed(std::mem::take(&mut current_chunk)))
|
.embed_chunks(vec![std::mem::take(&mut current_chunk)])
|
||||||
.map_err(crate::vector::Error::from)
|
.map_err(crate::vector::Error::from)
|
||||||
.map_err(crate::Error::from)?;
|
.map_err(crate::Error::from)?;
|
||||||
|
|
||||||
for (docid, embeddings) in current_chunk_ids.iter().zip(embeds.iter()) {
|
if let Some(embeds) = embeds.first() {
|
||||||
state_writer.insert(docid.to_be_bytes(), cast_slice(embeddings.as_inner()))?;
|
for (docid, embeddings) in current_chunk_ids.iter().zip(embeds.iter()) {
|
||||||
|
state_writer.insert(docid.to_be_bytes(), cast_slice(embeddings.as_inner()))?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ use crate::{DocumentId, FieldId, Result};
|
|||||||
///
|
///
|
||||||
/// The first returned reader is the one for normal word_docids, and the second one is for
|
/// The first returned reader is the one for normal word_docids, and the second one is for
|
||||||
/// exact_word_docids
|
/// exact_word_docids
|
||||||
#[logging_timer::time]
|
#[tracing::instrument(level = "trace", skip_all, target = "indexing::extract")]
|
||||||
pub fn extract_word_docids<R: io::Read + io::Seek>(
|
pub fn extract_word_docids<R: io::Read + io::Seek>(
|
||||||
docid_word_positions: grenad::Reader<R>,
|
docid_word_positions: grenad::Reader<R>,
|
||||||
indexer: GrenadParameters,
|
indexer: GrenadParameters,
|
||||||
@@ -135,6 +135,7 @@ pub fn extract_word_docids<R: io::Read + io::Seek>(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "trace", skip_all, target = "indexing::extract")]
|
||||||
fn words_into_sorter(
|
fn words_into_sorter(
|
||||||
document_id: DocumentId,
|
document_id: DocumentId,
|
||||||
fid: FieldId,
|
fid: FieldId,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ use crate::{DocumentId, Result};
|
|||||||
///
|
///
|
||||||
/// Returns a grenad reader with the list of extracted word pairs proximities and
|
/// Returns a grenad reader with the list of extracted word pairs proximities and
|
||||||
/// documents ids from the given chunk of docid word positions.
|
/// documents ids from the given chunk of docid word positions.
|
||||||
#[logging_timer::time]
|
#[tracing::instrument(level = "trace", skip_all, target = "indexing::extract")]
|
||||||
pub fn extract_word_pair_proximity_docids<R: io::Read + io::Seek>(
|
pub fn extract_word_pair_proximity_docids<R: io::Read + io::Seek>(
|
||||||
docid_word_positions: grenad::Reader<R>,
|
docid_word_positions: grenad::Reader<R>,
|
||||||
indexer: GrenadParameters,
|
indexer: GrenadParameters,
|
||||||
@@ -59,6 +59,10 @@ pub fn extract_word_pair_proximity_docids<R: io::Read + io::Seek>(
|
|||||||
if current_document_id.map_or(false, |id| id != document_id) {
|
if current_document_id.map_or(false, |id| id != document_id) {
|
||||||
puffin::profile_scope!("Document into sorter");
|
puffin::profile_scope!("Document into sorter");
|
||||||
|
|
||||||
|
// FIXME: span inside of a hot loop might degrade performance and create big reports
|
||||||
|
let span = tracing::trace_span!(target: "indexing::details", "document_into_sorter");
|
||||||
|
let _entered = span.enter();
|
||||||
|
|
||||||
document_word_positions_into_sorter(
|
document_word_positions_into_sorter(
|
||||||
current_document_id.unwrap(),
|
current_document_id.unwrap(),
|
||||||
&del_word_pair_proximity,
|
&del_word_pair_proximity,
|
||||||
@@ -77,7 +81,7 @@ pub fn extract_word_pair_proximity_docids<R: io::Read + io::Seek>(
|
|||||||
if let Some(deletion) = KvReaderDelAdd::new(value).get(DelAdd::Deletion) {
|
if let Some(deletion) = KvReaderDelAdd::new(value).get(DelAdd::Deletion) {
|
||||||
for (position, word) in KvReaderU16::new(deletion).iter() {
|
for (position, word) in KvReaderU16::new(deletion).iter() {
|
||||||
// drain the proximity window until the head word is considered close to the word we are inserting.
|
// drain the proximity window until the head word is considered close to the word we are inserting.
|
||||||
while del_word_positions.get(0).map_or(false, |(_w, p)| {
|
while del_word_positions.front().map_or(false, |(_w, p)| {
|
||||||
index_proximity(*p as u32, position as u32) >= MAX_DISTANCE
|
index_proximity(*p as u32, position as u32) >= MAX_DISTANCE
|
||||||
}) {
|
}) {
|
||||||
word_positions_into_word_pair_proximity(
|
word_positions_into_word_pair_proximity(
|
||||||
@@ -106,7 +110,7 @@ pub fn extract_word_pair_proximity_docids<R: io::Read + io::Seek>(
|
|||||||
if let Some(addition) = KvReaderDelAdd::new(value).get(DelAdd::Addition) {
|
if let Some(addition) = KvReaderDelAdd::new(value).get(DelAdd::Addition) {
|
||||||
for (position, word) in KvReaderU16::new(addition).iter() {
|
for (position, word) in KvReaderU16::new(addition).iter() {
|
||||||
// drain the proximity window until the head word is considered close to the word we are inserting.
|
// drain the proximity window until the head word is considered close to the word we are inserting.
|
||||||
while add_word_positions.get(0).map_or(false, |(_w, p)| {
|
while add_word_positions.front().map_or(false, |(_w, p)| {
|
||||||
index_proximity(*p as u32, position as u32) >= MAX_DISTANCE
|
index_proximity(*p as u32, position as u32) >= MAX_DISTANCE
|
||||||
}) {
|
}) {
|
||||||
word_positions_into_word_pair_proximity(
|
word_positions_into_word_pair_proximity(
|
||||||
@@ -138,6 +142,10 @@ pub fn extract_word_pair_proximity_docids<R: io::Read + io::Seek>(
|
|||||||
|
|
||||||
if let Some(document_id) = current_document_id {
|
if let Some(document_id) = current_document_id {
|
||||||
puffin::profile_scope!("Final document into sorter");
|
puffin::profile_scope!("Final document into sorter");
|
||||||
|
// FIXME: span inside of a hot loop might degrade performance and create big reports
|
||||||
|
let span = tracing::trace_span!(target: "indexing::details", "final_document_into_sorter");
|
||||||
|
let _entered = span.enter();
|
||||||
|
|
||||||
document_word_positions_into_sorter(
|
document_word_positions_into_sorter(
|
||||||
document_id,
|
document_id,
|
||||||
&del_word_pair_proximity,
|
&del_word_pair_proximity,
|
||||||
@@ -147,6 +155,10 @@ pub fn extract_word_pair_proximity_docids<R: io::Read + io::Seek>(
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
puffin::profile_scope!("sorter_into_reader");
|
puffin::profile_scope!("sorter_into_reader");
|
||||||
|
// FIXME: span inside of a hot loop might degrade performance and create big reports
|
||||||
|
let span = tracing::trace_span!(target: "indexing::details", "sorter_into_reader");
|
||||||
|
let _entered = span.enter();
|
||||||
|
|
||||||
let mut writer = create_writer(
|
let mut writer = create_writer(
|
||||||
indexer.chunk_compression_type,
|
indexer.chunk_compression_type,
|
||||||
indexer.chunk_compression_level,
|
indexer.chunk_compression_level,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ use crate::{bucketed_position, DocumentId, Result};
|
|||||||
///
|
///
|
||||||
/// Returns a grenad reader with the list of extracted words at positions and
|
/// Returns a grenad reader with the list of extracted words at positions and
|
||||||
/// documents ids from the given chunk of docid word positions.
|
/// documents ids from the given chunk of docid word positions.
|
||||||
#[logging_timer::time]
|
#[tracing::instrument(level = "trace", skip_all, target = "indexing::extract")]
|
||||||
pub fn extract_word_position_docids<R: io::Read + io::Seek>(
|
pub fn extract_word_position_docids<R: io::Read + io::Seek>(
|
||||||
docid_word_positions: grenad::Reader<R>,
|
docid_word_positions: grenad::Reader<R>,
|
||||||
indexer: GrenadParameters,
|
indexer: GrenadParameters,
|
||||||
@@ -94,6 +94,7 @@ pub fn extract_word_position_docids<R: io::Read + io::Seek>(
|
|||||||
Ok(word_position_docids_reader)
|
Ok(word_position_docids_reader)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "trace", skip_all, target = "indexing::extract")]
|
||||||
fn words_position_into_sorter(
|
fn words_position_into_sorter(
|
||||||
document_id: DocumentId,
|
document_id: DocumentId,
|
||||||
key_buffer: &mut Vec<u8>,
|
key_buffer: &mut Vec<u8>,
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ use std::fs::File;
|
|||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
|
|
||||||
use crossbeam_channel::Sender;
|
use crossbeam_channel::Sender;
|
||||||
use log::debug;
|
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
use self::extract_docid_word_positions::extract_docid_word_positions;
|
use self::extract_docid_word_positions::extract_docid_word_positions;
|
||||||
use self::extract_facet_number_docids::extract_facet_number_docids;
|
use self::extract_facet_number_docids::extract_facet_number_docids;
|
||||||
@@ -41,6 +41,7 @@ use crate::{FieldId, FieldsIdsMap, Result};
|
|||||||
/// Extract data for each databases from obkv documents in parallel.
|
/// Extract data for each databases from obkv documents in parallel.
|
||||||
/// Send data in grenad file over provided Sender.
|
/// Send data in grenad file over provided Sender.
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
#[tracing::instrument(level = "trace", skip_all, target = "indexing::extract")]
|
||||||
pub(crate) fn data_from_obkv_documents(
|
pub(crate) fn data_from_obkv_documents(
|
||||||
original_obkv_chunks: impl Iterator<Item = Result<grenad::Reader<BufReader<File>>>> + Send,
|
original_obkv_chunks: impl Iterator<Item = Result<grenad::Reader<BufReader<File>>>> + Send,
|
||||||
flattened_obkv_chunks: impl Iterator<Item = Result<grenad::Reader<BufReader<File>>>> + Send,
|
flattened_obkv_chunks: impl Iterator<Item = Result<grenad::Reader<BufReader<File>>>> + Send,
|
||||||
@@ -113,7 +114,7 @@ pub(crate) fn data_from_obkv_documents(
|
|||||||
{
|
{
|
||||||
let lmdb_writer_sx = lmdb_writer_sx.clone();
|
let lmdb_writer_sx = lmdb_writer_sx.clone();
|
||||||
rayon::spawn(move || {
|
rayon::spawn(move || {
|
||||||
debug!("merge {} database", "facet-id-exists-docids");
|
debug!(database = "facet-id-exists-docids", "merge");
|
||||||
match facet_exists_docids_chunks.merge(merge_deladd_cbo_roaring_bitmaps, &indexer) {
|
match facet_exists_docids_chunks.merge(merge_deladd_cbo_roaring_bitmaps, &indexer) {
|
||||||
Ok(reader) => {
|
Ok(reader) => {
|
||||||
let _ = lmdb_writer_sx.send(Ok(TypedChunk::FieldIdFacetExistsDocids(reader)));
|
let _ = lmdb_writer_sx.send(Ok(TypedChunk::FieldIdFacetExistsDocids(reader)));
|
||||||
@@ -129,7 +130,7 @@ pub(crate) fn data_from_obkv_documents(
|
|||||||
{
|
{
|
||||||
let lmdb_writer_sx = lmdb_writer_sx.clone();
|
let lmdb_writer_sx = lmdb_writer_sx.clone();
|
||||||
rayon::spawn(move || {
|
rayon::spawn(move || {
|
||||||
debug!("merge {} database", "facet-id-is-null-docids");
|
debug!(database = "facet-id-is-null-docids", "merge");
|
||||||
match facet_is_null_docids_chunks.merge(merge_deladd_cbo_roaring_bitmaps, &indexer) {
|
match facet_is_null_docids_chunks.merge(merge_deladd_cbo_roaring_bitmaps, &indexer) {
|
||||||
Ok(reader) => {
|
Ok(reader) => {
|
||||||
let _ = lmdb_writer_sx.send(Ok(TypedChunk::FieldIdFacetIsNullDocids(reader)));
|
let _ = lmdb_writer_sx.send(Ok(TypedChunk::FieldIdFacetIsNullDocids(reader)));
|
||||||
@@ -145,7 +146,7 @@ pub(crate) fn data_from_obkv_documents(
|
|||||||
{
|
{
|
||||||
let lmdb_writer_sx = lmdb_writer_sx.clone();
|
let lmdb_writer_sx = lmdb_writer_sx.clone();
|
||||||
rayon::spawn(move || {
|
rayon::spawn(move || {
|
||||||
debug!("merge {} database", "facet-id-is-empty-docids");
|
debug!(database = "facet-id-is-empty-docids", "merge");
|
||||||
match facet_is_empty_docids_chunks.merge(merge_deladd_cbo_roaring_bitmaps, &indexer) {
|
match facet_is_empty_docids_chunks.merge(merge_deladd_cbo_roaring_bitmaps, &indexer) {
|
||||||
Ok(reader) => {
|
Ok(reader) => {
|
||||||
let _ = lmdb_writer_sx.send(Ok(TypedChunk::FieldIdFacetIsEmptyDocids(reader)));
|
let _ = lmdb_writer_sx.send(Ok(TypedChunk::FieldIdFacetIsEmptyDocids(reader)));
|
||||||
@@ -257,13 +258,22 @@ fn spawn_extraction_task<FE, FS, M>(
|
|||||||
M: MergeableReader + FromParallelIterator<M::Output> + Send + 'static,
|
M: MergeableReader + FromParallelIterator<M::Output> + Send + 'static,
|
||||||
M::Output: Send,
|
M::Output: Send,
|
||||||
{
|
{
|
||||||
|
let current_span = tracing::Span::current();
|
||||||
|
|
||||||
rayon::spawn(move || {
|
rayon::spawn(move || {
|
||||||
puffin::profile_scope!("extract_multiple_chunks", name);
|
let child_span =
|
||||||
|
tracing::trace_span!(target: "", parent: ¤t_span, "extract_multiple_chunks");
|
||||||
|
let _entered = child_span.enter();
|
||||||
|
puffin::profile_scope!("extract_multiple_chunksdexing::details, ", name);
|
||||||
let chunks: Result<M> =
|
let chunks: Result<M> =
|
||||||
chunks.into_par_iter().map(|chunk| extract_fn(chunk, indexer)).collect();
|
chunks.into_par_iter().map(|chunk| extract_fn(chunk, indexer)).collect();
|
||||||
|
let current_span = tracing::Span::current();
|
||||||
|
|
||||||
rayon::spawn(move || match chunks {
|
rayon::spawn(move || match chunks {
|
||||||
Ok(chunks) => {
|
Ok(chunks) => {
|
||||||
debug!("merge {} database", name);
|
let child_span = tracing::trace_span!(target: "", parent: ¤t_span, "merge_multiple_chunks");
|
||||||
|
let _entered = child_span.enter();
|
||||||
|
debug!(database = name, "merge");
|
||||||
puffin::profile_scope!("merge_multiple_chunks", name);
|
puffin::profile_scope!("merge_multiple_chunks", name);
|
||||||
let reader = chunks.merge(merge_fn, &indexer);
|
let reader = chunks.merge(merge_fn, &indexer);
|
||||||
let _ = lmdb_writer_sx.send(reader.map(serialize_fn));
|
let _ = lmdb_writer_sx.send(reader.map(serialize_fn));
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ use super::{ClonableMmap, MergeFn};
|
|||||||
use crate::update::index_documents::valid_lmdb_key;
|
use crate::update::index_documents::valid_lmdb_key;
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
|
|
||||||
|
/// This is something reasonable given the fact
|
||||||
|
/// that there is one grenad sorter by thread.
|
||||||
|
const MAX_GRENAD_SORTER_USAGE: usize = 500 * 1024 * 1024; // 500 MiB
|
||||||
|
|
||||||
pub type CursorClonableMmap = io::Cursor<ClonableMmap>;
|
pub type CursorClonableMmap = io::Cursor<ClonableMmap>;
|
||||||
|
|
||||||
pub fn create_writer<R: io::Write>(
|
pub fn create_writer<R: io::Write>(
|
||||||
@@ -24,6 +28,9 @@ pub fn create_writer<R: io::Write>(
|
|||||||
builder.build(BufWriter::new(file))
|
builder.build(BufWriter::new(file))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A helper function that creates a grenad sorter
|
||||||
|
/// with the given parameters. The max memory is
|
||||||
|
/// clamped to something reasonable.
|
||||||
pub fn create_sorter(
|
pub fn create_sorter(
|
||||||
sort_algorithm: grenad::SortAlgorithm,
|
sort_algorithm: grenad::SortAlgorithm,
|
||||||
merge: MergeFn,
|
merge: MergeFn,
|
||||||
@@ -41,7 +48,7 @@ pub fn create_sorter(
|
|||||||
builder.max_nb_chunks(nb_chunks);
|
builder.max_nb_chunks(nb_chunks);
|
||||||
}
|
}
|
||||||
if let Some(memory) = max_memory {
|
if let Some(memory) = max_memory {
|
||||||
builder.dump_threshold(memory);
|
builder.dump_threshold(memory.min(MAX_GRENAD_SORTER_USAGE));
|
||||||
builder.allow_realloc(false);
|
builder.allow_realloc(false);
|
||||||
}
|
}
|
||||||
builder.sort_algorithm(sort_algorithm);
|
builder.sort_algorithm(sort_algorithm);
|
||||||
@@ -49,6 +56,7 @@ pub fn create_sorter(
|
|||||||
builder.build()
|
builder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "trace", skip_all, target = "indexing::grenad")]
|
||||||
pub fn sorter_into_reader(
|
pub fn sorter_into_reader(
|
||||||
sorter: grenad::Sorter<MergeFn>,
|
sorter: grenad::Sorter<MergeFn>,
|
||||||
indexer: GrenadParameters,
|
indexer: GrenadParameters,
|
||||||
@@ -187,10 +195,15 @@ impl Default for GrenadParameters {
|
|||||||
|
|
||||||
impl GrenadParameters {
|
impl GrenadParameters {
|
||||||
/// This function use the number of threads in the current threadpool to compute the value.
|
/// This function use the number of threads in the current threadpool to compute the value.
|
||||||
|
///
|
||||||
/// This should be called inside of a rayon thread pool,
|
/// This should be called inside of a rayon thread pool,
|
||||||
/// Otherwise, it will take the global number of threads.
|
/// otherwise, it will take the global number of threads.
|
||||||
|
///
|
||||||
|
/// The max memory cannot exceed a given reasonable value.
|
||||||
pub fn max_memory_by_thread(&self) -> Option<usize> {
|
pub fn max_memory_by_thread(&self) -> Option<usize> {
|
||||||
self.max_memory.map(|max_memory| max_memory / rayon::current_num_threads())
|
self.max_memory.map(|max_memory| {
|
||||||
|
(max_memory / rayon::current_num_threads()).min(MAX_GRENAD_SORTER_USAGE)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,6 +253,7 @@ pub fn grenad_obkv_into_chunks<R: io::Read + io::Seek>(
|
|||||||
|
|
||||||
/// Write provided sorter in database using serialize_value function.
|
/// Write provided sorter in database using serialize_value function.
|
||||||
/// merge_values function is used if an entry already exist in the database.
|
/// merge_values function is used if an entry already exist in the database.
|
||||||
|
#[tracing::instrument(level = "trace", skip_all, target = "indexing::grenad")]
|
||||||
pub fn write_sorter_into_database<K, V, FS, FM>(
|
pub fn write_sorter_into_database<K, V, FS, FM>(
|
||||||
sorter: Sorter<MergeFn>,
|
sorter: Sorter<MergeFn>,
|
||||||
database: &heed::Database<K, V>,
|
database: &heed::Database<K, V>,
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ use std::result::Result as StdResult;
|
|||||||
use crossbeam_channel::{Receiver, Sender};
|
use crossbeam_channel::{Receiver, Sender};
|
||||||
use heed::types::Str;
|
use heed::types::Str;
|
||||||
use heed::Database;
|
use heed::Database;
|
||||||
use log::debug;
|
|
||||||
use rand::SeedableRng;
|
use rand::SeedableRng;
|
||||||
use roaring::RoaringBitmap;
|
use roaring::RoaringBitmap;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use slice_group_by::GroupBy;
|
use slice_group_by::GroupBy;
|
||||||
|
use tracing::debug_span;
|
||||||
use typed_chunk::{write_typed_chunk_into_index, TypedChunk};
|
use typed_chunk::{write_typed_chunk_into_index, TypedChunk};
|
||||||
|
|
||||||
use self::enrich::enrich_documents_batch;
|
use self::enrich::enrich_documents_batch;
|
||||||
@@ -134,6 +134,7 @@ where
|
|||||||
/// return an error and not the `IndexDocuments` struct as it is invalid to use it afterward.
|
/// return an error and not the `IndexDocuments` struct as it is invalid to use it afterward.
|
||||||
///
|
///
|
||||||
/// Returns the number of documents added to the builder.
|
/// Returns the number of documents added to the builder.
|
||||||
|
#[tracing::instrument(level = "trace", skip_all, target = "indexing::documents")]
|
||||||
pub fn add_documents<R: Read + Seek>(
|
pub fn add_documents<R: Read + Seek>(
|
||||||
mut self,
|
mut self,
|
||||||
reader: DocumentsBatchReader<R>,
|
reader: DocumentsBatchReader<R>,
|
||||||
@@ -179,6 +180,7 @@ where
|
|||||||
/// Remove a batch of documents from the current builder.
|
/// Remove a batch of documents from the current builder.
|
||||||
///
|
///
|
||||||
/// Returns the number of documents deleted from the builder.
|
/// Returns the number of documents deleted from the builder.
|
||||||
|
#[tracing::instrument(level = "trace", skip_all, target = "indexing::documents")]
|
||||||
pub fn remove_documents(
|
pub fn remove_documents(
|
||||||
mut self,
|
mut self,
|
||||||
to_delete: Vec<String>,
|
to_delete: Vec<String>,
|
||||||
@@ -214,6 +216,7 @@ where
|
|||||||
/// - No batching using the standards `remove_documents` and `add_documents` took place
|
/// - No batching using the standards `remove_documents` and `add_documents` took place
|
||||||
///
|
///
|
||||||
/// TODO: make it impossible to call `remove_documents` or `add_documents` on an instance that calls this function.
|
/// TODO: make it impossible to call `remove_documents` or `add_documents` on an instance that calls this function.
|
||||||
|
#[tracing::instrument(level = "trace", skip_all, target = "indexing::details")]
|
||||||
pub fn remove_documents_from_db_no_batch(
|
pub fn remove_documents_from_db_no_batch(
|
||||||
mut self,
|
mut self,
|
||||||
to_delete: &RoaringBitmap,
|
to_delete: &RoaringBitmap,
|
||||||
@@ -237,7 +240,12 @@ where
|
|||||||
Ok((self, deleted_documents))
|
Ok((self, deleted_documents))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[logging_timer::time("IndexDocuments::{}")]
|
#[tracing::instrument(
|
||||||
|
level = "trace"
|
||||||
|
skip_all,
|
||||||
|
target = "indexing::documents",
|
||||||
|
name = "index_documents"
|
||||||
|
)]
|
||||||
pub fn execute(mut self) -> Result<DocumentAdditionResult> {
|
pub fn execute(mut self) -> Result<DocumentAdditionResult> {
|
||||||
puffin::profile_function!();
|
puffin::profile_function!();
|
||||||
|
|
||||||
@@ -273,7 +281,12 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the total number of documents in the index after the update.
|
/// Returns the total number of documents in the index after the update.
|
||||||
#[logging_timer::time("IndexDocuments::{}")]
|
#[tracing::instrument(
|
||||||
|
level = "trace",
|
||||||
|
skip_all,
|
||||||
|
target = "profile::indexing::details",
|
||||||
|
name = "index_documents_raw"
|
||||||
|
)]
|
||||||
pub fn execute_raw(self, output: TransformOutput) -> Result<u64>
|
pub fn execute_raw(self, output: TransformOutput) -> Result<u64>
|
||||||
where
|
where
|
||||||
FP: Fn(UpdateIndexingStep) + Sync,
|
FP: Fn(UpdateIndexingStep) + Sync,
|
||||||
@@ -374,8 +387,12 @@ where
|
|||||||
|
|
||||||
let cloned_embedder = self.embedders.clone();
|
let cloned_embedder = self.embedders.clone();
|
||||||
|
|
||||||
|
let current_span = tracing::Span::current();
|
||||||
|
|
||||||
// Run extraction pipeline in parallel.
|
// Run extraction pipeline in parallel.
|
||||||
pool.install(|| {
|
pool.install(|| {
|
||||||
|
let child_span = tracing::trace_span!(target: "indexing::details", parent: ¤t_span, "extract_and_send_grenad_chunks");
|
||||||
|
let _enter = child_span.enter();
|
||||||
puffin::profile_scope!("extract_and_send_grenad_chunks");
|
puffin::profile_scope!("extract_and_send_grenad_chunks");
|
||||||
// split obkv file into several chunks
|
// split obkv file into several chunks
|
||||||
let original_chunk_iter =
|
let original_chunk_iter =
|
||||||
@@ -489,10 +506,7 @@ where
|
|||||||
documents_seen: documents_seen_count as usize,
|
documents_seen: documents_seen_count as usize,
|
||||||
total_documents: documents_count,
|
total_documents: documents_count,
|
||||||
});
|
});
|
||||||
debug!(
|
debug_span!("Seen", documents = documents_seen_count, total = documents_count);
|
||||||
"We have seen {} documents on {} total document so far",
|
|
||||||
documents_seen_count, documents_count
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if is_merged_database {
|
if is_merged_database {
|
||||||
databases_seen += 1;
|
databases_seen += 1;
|
||||||
@@ -522,12 +536,8 @@ where
|
|||||||
pool.install(|| {
|
pool.install(|| {
|
||||||
let writer_index = (embedder_index as u16) << 8;
|
let writer_index = (embedder_index as u16) << 8;
|
||||||
for k in 0..=u8::MAX {
|
for k in 0..=u8::MAX {
|
||||||
let writer = arroy::Writer::prepare(
|
let writer =
|
||||||
wtxn,
|
arroy::Writer::new(vector_arroy, writer_index | (k as u16), dimension)?;
|
||||||
vector_arroy,
|
|
||||||
writer_index | (k as u16),
|
|
||||||
dimension,
|
|
||||||
)?;
|
|
||||||
if writer.is_empty(wtxn)? {
|
if writer.is_empty(wtxn)? {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -547,7 +557,12 @@ where
|
|||||||
Ok(number_of_documents)
|
Ok(number_of_documents)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[logging_timer::time("IndexDocuments::{}")]
|
#[tracing::instrument(
|
||||||
|
level = "trace",
|
||||||
|
skip_all,
|
||||||
|
target = "indexing::prefix",
|
||||||
|
name = "index_documents_prefix_databases"
|
||||||
|
)]
|
||||||
pub fn execute_prefix_databases(
|
pub fn execute_prefix_databases(
|
||||||
self,
|
self,
|
||||||
word_docids: Option<grenad::Reader<CursorClonableMmap>>,
|
word_docids: Option<grenad::Reader<CursorClonableMmap>>,
|
||||||
@@ -602,6 +617,8 @@ where
|
|||||||
let del_prefix_fst_words;
|
let del_prefix_fst_words;
|
||||||
|
|
||||||
{
|
{
|
||||||
|
let span = tracing::trace_span!(target: "indexing::details", "compute_prefix_diffs");
|
||||||
|
let _entered = span.enter();
|
||||||
puffin::profile_scope!("compute_prefix_diffs");
|
puffin::profile_scope!("compute_prefix_diffs");
|
||||||
|
|
||||||
current_prefix_fst = self.index.words_prefixes_fst(self.wtxn)?;
|
current_prefix_fst = self.index.words_prefixes_fst(self.wtxn)?;
|
||||||
@@ -726,6 +743,12 @@ where
|
|||||||
|
|
||||||
/// Run the word prefix docids update operation.
|
/// Run the word prefix docids update operation.
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
#[tracing::instrument(
|
||||||
|
level = "trace",
|
||||||
|
skip_all,
|
||||||
|
target = "indexing::prefix",
|
||||||
|
name = "index_documents_word_prefix_docids"
|
||||||
|
)]
|
||||||
fn execute_word_prefix_docids(
|
fn execute_word_prefix_docids(
|
||||||
txn: &mut heed::RwTxn,
|
txn: &mut heed::RwTxn,
|
||||||
reader: grenad::Reader<Cursor<ClonableMmap>>,
|
reader: grenad::Reader<Cursor<ClonableMmap>>,
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ impl<'a, 'i> Transform<'a, 'i> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[logging_timer::time]
|
#[tracing::instrument(level = "trace", skip_all, target = "indexing::documents")]
|
||||||
pub fn read_documents<R, FP, FA>(
|
pub fn read_documents<R, FP, FA>(
|
||||||
&mut self,
|
&mut self,
|
||||||
reader: EnrichedDocumentsBatchReader<R>,
|
reader: EnrichedDocumentsBatchReader<R>,
|
||||||
@@ -359,7 +359,7 @@ impl<'a, 'i> Transform<'a, 'i> {
|
|||||||
/// - If the document to remove was inserted by the `read_documents` method before but was NOT present in the db,
|
/// - If the document to remove was inserted by the `read_documents` method before but was NOT present in the db,
|
||||||
/// it's added into the grenad to ensure we don't insert it + removed from the list of new documents ids.
|
/// it's added into the grenad to ensure we don't insert it + removed from the list of new documents ids.
|
||||||
/// - If the document to remove was not present in either the db or the transform we do nothing.
|
/// - If the document to remove was not present in either the db or the transform we do nothing.
|
||||||
#[logging_timer::time]
|
#[tracing::instrument(level = "trace", skip_all, target = "indexing::documents")]
|
||||||
pub fn remove_documents<FA>(
|
pub fn remove_documents<FA>(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut to_remove: Vec<String>,
|
mut to_remove: Vec<String>,
|
||||||
@@ -450,7 +450,7 @@ impl<'a, 'i> Transform<'a, 'i> {
|
|||||||
/// - No batching using the standards `remove_documents` and `add_documents` took place
|
/// - No batching using the standards `remove_documents` and `add_documents` took place
|
||||||
///
|
///
|
||||||
/// TODO: make it impossible to call `remove_documents` or `add_documents` on an instance that calls this function.
|
/// TODO: make it impossible to call `remove_documents` or `add_documents` on an instance that calls this function.
|
||||||
#[logging_timer::time]
|
#[tracing::instrument(level = "trace", skip_all, target = "indexing::details")]
|
||||||
pub fn remove_documents_from_db_no_batch<FA>(
|
pub fn remove_documents_from_db_no_batch<FA>(
|
||||||
&mut self,
|
&mut self,
|
||||||
to_remove: &RoaringBitmap,
|
to_remove: &RoaringBitmap,
|
||||||
@@ -541,6 +541,7 @@ impl<'a, 'i> Transform<'a, 'i> {
|
|||||||
|
|
||||||
// Flatten a document from the fields ids map contained in self and insert the new
|
// Flatten a document from the fields ids map contained in self and insert the new
|
||||||
// created fields. Returns `None` if the document doesn't need to be flattened.
|
// created fields. Returns `None` if the document doesn't need to be flattened.
|
||||||
|
#[tracing::instrument(level = "trace", skip(self, obkv), target = "indexing::transform")]
|
||||||
fn flatten_from_fields_ids_map(&mut self, obkv: KvReader<FieldId>) -> Result<Option<Vec<u8>>> {
|
fn flatten_from_fields_ids_map(&mut self, obkv: KvReader<FieldId>) -> Result<Option<Vec<u8>>> {
|
||||||
if obkv
|
if obkv
|
||||||
.iter()
|
.iter()
|
||||||
@@ -661,7 +662,7 @@ impl<'a, 'i> Transform<'a, 'i> {
|
|||||||
/// Generate the `TransformOutput` based on the given sorter that can be generated from any
|
/// Generate the `TransformOutput` based on the given sorter that can be generated from any
|
||||||
/// format like CSV, JSON or JSON stream. This sorter must contain a key that is the document
|
/// format like CSV, JSON or JSON stream. This sorter must contain a key that is the document
|
||||||
/// id for the user side and the value must be an obkv where keys are valid fields ids.
|
/// id for the user side and the value must be an obkv where keys are valid fields ids.
|
||||||
#[logging_timer::time]
|
#[tracing::instrument(level = "trace", skip_all, target = "indexing::transform")]
|
||||||
pub(crate) fn output_from_sorter<F>(
|
pub(crate) fn output_from_sorter<F>(
|
||||||
self,
|
self,
|
||||||
wtxn: &mut heed::RwTxn,
|
wtxn: &mut heed::RwTxn,
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ impl TypedChunk {
|
|||||||
|
|
||||||
/// Write typed chunk in the corresponding LMDB database of the provided index.
|
/// Write typed chunk in the corresponding LMDB database of the provided index.
|
||||||
/// Return new documents seen.
|
/// Return new documents seen.
|
||||||
|
#[tracing::instrument(level = "trace", skip_all, target = "indexing::write_db")]
|
||||||
pub(crate) fn write_typed_chunk_into_index(
|
pub(crate) fn write_typed_chunk_into_index(
|
||||||
typed_chunk: TypedChunk,
|
typed_chunk: TypedChunk,
|
||||||
index: &Index,
|
index: &Index,
|
||||||
@@ -126,6 +127,8 @@ pub(crate) fn write_typed_chunk_into_index(
|
|||||||
let mut is_merged_database = false;
|
let mut is_merged_database = false;
|
||||||
match typed_chunk {
|
match typed_chunk {
|
||||||
TypedChunk::Documents(obkv_documents_iter) => {
|
TypedChunk::Documents(obkv_documents_iter) => {
|
||||||
|
let span = tracing::trace_span!(target: "indexing::write_db", "documents");
|
||||||
|
let _entered = span.enter();
|
||||||
let mut operations: Vec<DocumentOperation> = Default::default();
|
let mut operations: Vec<DocumentOperation> = Default::default();
|
||||||
|
|
||||||
let mut docids = index.documents_ids(wtxn)?;
|
let mut docids = index.documents_ids(wtxn)?;
|
||||||
@@ -172,6 +175,9 @@ pub(crate) fn write_typed_chunk_into_index(
|
|||||||
index.put_documents_ids(wtxn, &docids)?;
|
index.put_documents_ids(wtxn, &docids)?;
|
||||||
}
|
}
|
||||||
TypedChunk::FieldIdWordCountDocids(fid_word_count_docids_iter) => {
|
TypedChunk::FieldIdWordCountDocids(fid_word_count_docids_iter) => {
|
||||||
|
let span =
|
||||||
|
tracing::trace_span!(target: "indexing::write_db", "field_id_word_count_docids");
|
||||||
|
let _entered = span.enter();
|
||||||
append_entries_into_database(
|
append_entries_into_database(
|
||||||
fid_word_count_docids_iter,
|
fid_word_count_docids_iter,
|
||||||
&index.field_id_word_count_docids,
|
&index.field_id_word_count_docids,
|
||||||
@@ -187,6 +193,8 @@ pub(crate) fn write_typed_chunk_into_index(
|
|||||||
exact_word_docids_reader,
|
exact_word_docids_reader,
|
||||||
word_fid_docids_reader,
|
word_fid_docids_reader,
|
||||||
} => {
|
} => {
|
||||||
|
let span = tracing::trace_span!(target: "indexing::write_db", "word_docids");
|
||||||
|
let _entered = span.enter();
|
||||||
let word_docids_iter = unsafe { as_cloneable_grenad(&word_docids_reader) }?;
|
let word_docids_iter = unsafe { as_cloneable_grenad(&word_docids_reader) }?;
|
||||||
append_entries_into_database(
|
append_entries_into_database(
|
||||||
word_docids_iter.clone(),
|
word_docids_iter.clone(),
|
||||||
@@ -230,6 +238,8 @@ pub(crate) fn write_typed_chunk_into_index(
|
|||||||
is_merged_database = true;
|
is_merged_database = true;
|
||||||
}
|
}
|
||||||
TypedChunk::WordPositionDocids(word_position_docids_iter) => {
|
TypedChunk::WordPositionDocids(word_position_docids_iter) => {
|
||||||
|
let span = tracing::trace_span!(target: "indexing::write_db", "word_position_docids");
|
||||||
|
let _entered = span.enter();
|
||||||
append_entries_into_database(
|
append_entries_into_database(
|
||||||
word_position_docids_iter,
|
word_position_docids_iter,
|
||||||
&index.word_position_docids,
|
&index.word_position_docids,
|
||||||
@@ -241,16 +251,25 @@ pub(crate) fn write_typed_chunk_into_index(
|
|||||||
is_merged_database = true;
|
is_merged_database = true;
|
||||||
}
|
}
|
||||||
TypedChunk::FieldIdFacetNumberDocids(facet_id_number_docids_iter) => {
|
TypedChunk::FieldIdFacetNumberDocids(facet_id_number_docids_iter) => {
|
||||||
|
let span =
|
||||||
|
tracing::trace_span!(target: "indexing::write_db","field_id_facet_number_docids");
|
||||||
|
let _entered = span.enter();
|
||||||
let indexer = FacetsUpdate::new(index, FacetType::Number, facet_id_number_docids_iter);
|
let indexer = FacetsUpdate::new(index, FacetType::Number, facet_id_number_docids_iter);
|
||||||
indexer.execute(wtxn)?;
|
indexer.execute(wtxn)?;
|
||||||
is_merged_database = true;
|
is_merged_database = true;
|
||||||
}
|
}
|
||||||
TypedChunk::FieldIdFacetStringDocids(facet_id_string_docids_iter) => {
|
TypedChunk::FieldIdFacetStringDocids(facet_id_string_docids_iter) => {
|
||||||
|
let span =
|
||||||
|
tracing::trace_span!(target: "indexing::write_db", "field_id_facet_string_docids");
|
||||||
|
let _entered = span.enter();
|
||||||
let indexer = FacetsUpdate::new(index, FacetType::String, facet_id_string_docids_iter);
|
let indexer = FacetsUpdate::new(index, FacetType::String, facet_id_string_docids_iter);
|
||||||
indexer.execute(wtxn)?;
|
indexer.execute(wtxn)?;
|
||||||
is_merged_database = true;
|
is_merged_database = true;
|
||||||
}
|
}
|
||||||
TypedChunk::FieldIdFacetExistsDocids(facet_id_exists_docids) => {
|
TypedChunk::FieldIdFacetExistsDocids(facet_id_exists_docids) => {
|
||||||
|
let span =
|
||||||
|
tracing::trace_span!(target: "indexing::write_db", "field_id_facet_exists_docids");
|
||||||
|
let _entered = span.enter();
|
||||||
append_entries_into_database(
|
append_entries_into_database(
|
||||||
facet_id_exists_docids,
|
facet_id_exists_docids,
|
||||||
&index.facet_id_exists_docids,
|
&index.facet_id_exists_docids,
|
||||||
@@ -262,6 +281,9 @@ pub(crate) fn write_typed_chunk_into_index(
|
|||||||
is_merged_database = true;
|
is_merged_database = true;
|
||||||
}
|
}
|
||||||
TypedChunk::FieldIdFacetIsNullDocids(facet_id_is_null_docids) => {
|
TypedChunk::FieldIdFacetIsNullDocids(facet_id_is_null_docids) => {
|
||||||
|
let span =
|
||||||
|
tracing::trace_span!(target: "indexing::write_db", "field_id_facet_is_null_docids");
|
||||||
|
let _entered = span.enter();
|
||||||
append_entries_into_database(
|
append_entries_into_database(
|
||||||
facet_id_is_null_docids,
|
facet_id_is_null_docids,
|
||||||
&index.facet_id_is_null_docids,
|
&index.facet_id_is_null_docids,
|
||||||
@@ -273,6 +295,8 @@ pub(crate) fn write_typed_chunk_into_index(
|
|||||||
is_merged_database = true;
|
is_merged_database = true;
|
||||||
}
|
}
|
||||||
TypedChunk::FieldIdFacetIsEmptyDocids(facet_id_is_empty_docids) => {
|
TypedChunk::FieldIdFacetIsEmptyDocids(facet_id_is_empty_docids) => {
|
||||||
|
let span = tracing::trace_span!(target: "profile::indexing::write_db", "field_id_facet_is_empty_docids");
|
||||||
|
let _entered = span.enter();
|
||||||
append_entries_into_database(
|
append_entries_into_database(
|
||||||
facet_id_is_empty_docids,
|
facet_id_is_empty_docids,
|
||||||
&index.facet_id_is_empty_docids,
|
&index.facet_id_is_empty_docids,
|
||||||
@@ -284,6 +308,9 @@ pub(crate) fn write_typed_chunk_into_index(
|
|||||||
is_merged_database = true;
|
is_merged_database = true;
|
||||||
}
|
}
|
||||||
TypedChunk::WordPairProximityDocids(word_pair_proximity_docids_iter) => {
|
TypedChunk::WordPairProximityDocids(word_pair_proximity_docids_iter) => {
|
||||||
|
let span =
|
||||||
|
tracing::trace_span!(target: "indexing::write_db", "word_pair_proximity_docids");
|
||||||
|
let _entered = span.enter();
|
||||||
append_entries_into_database(
|
append_entries_into_database(
|
||||||
word_pair_proximity_docids_iter,
|
word_pair_proximity_docids_iter,
|
||||||
&index.word_pair_proximity_docids,
|
&index.word_pair_proximity_docids,
|
||||||
@@ -295,6 +322,9 @@ pub(crate) fn write_typed_chunk_into_index(
|
|||||||
is_merged_database = true;
|
is_merged_database = true;
|
||||||
}
|
}
|
||||||
TypedChunk::FieldIdDocidFacetNumbers(fid_docid_facet_number) => {
|
TypedChunk::FieldIdDocidFacetNumbers(fid_docid_facet_number) => {
|
||||||
|
let span =
|
||||||
|
tracing::trace_span!(target: "indexing::write_db", "field_id_docid_facet_numbers");
|
||||||
|
let _entered = span.enter();
|
||||||
let index_fid_docid_facet_numbers =
|
let index_fid_docid_facet_numbers =
|
||||||
index.field_id_docid_facet_f64s.remap_types::<Bytes, Bytes>();
|
index.field_id_docid_facet_f64s.remap_types::<Bytes, Bytes>();
|
||||||
let mut cursor = fid_docid_facet_number.into_cursor()?;
|
let mut cursor = fid_docid_facet_number.into_cursor()?;
|
||||||
@@ -315,6 +345,9 @@ pub(crate) fn write_typed_chunk_into_index(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
TypedChunk::FieldIdDocidFacetStrings(fid_docid_facet_string) => {
|
TypedChunk::FieldIdDocidFacetStrings(fid_docid_facet_string) => {
|
||||||
|
let span =
|
||||||
|
tracing::trace_span!(target: "indexing::write_db", "field_id_docid_facet_strings");
|
||||||
|
let _entered = span.enter();
|
||||||
let index_fid_docid_facet_strings =
|
let index_fid_docid_facet_strings =
|
||||||
index.field_id_docid_facet_strings.remap_types::<Bytes, Bytes>();
|
index.field_id_docid_facet_strings.remap_types::<Bytes, Bytes>();
|
||||||
let mut cursor = fid_docid_facet_string.into_cursor()?;
|
let mut cursor = fid_docid_facet_string.into_cursor()?;
|
||||||
@@ -335,6 +368,8 @@ pub(crate) fn write_typed_chunk_into_index(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
TypedChunk::GeoPoints(geo_points) => {
|
TypedChunk::GeoPoints(geo_points) => {
|
||||||
|
let span = tracing::trace_span!(target: "indexing::write_db", "geo_points");
|
||||||
|
let _entered = span.enter();
|
||||||
let mut rtree = index.geo_rtree(wtxn)?.unwrap_or_default();
|
let mut rtree = index.geo_rtree(wtxn)?.unwrap_or_default();
|
||||||
let mut geo_faceted_docids = index.geo_faceted_documents_ids(wtxn)?;
|
let mut geo_faceted_docids = index.geo_faceted_documents_ids(wtxn)?;
|
||||||
|
|
||||||
@@ -365,6 +400,8 @@ pub(crate) fn write_typed_chunk_into_index(
|
|||||||
expected_dimension,
|
expected_dimension,
|
||||||
embedder_name,
|
embedder_name,
|
||||||
} => {
|
} => {
|
||||||
|
let span = tracing::trace_span!(target: "indexing::write_db", "vector_points");
|
||||||
|
let _entered = span.enter();
|
||||||
let embedder_index = index.embedder_category_id.get(wtxn, &embedder_name)?.ok_or(
|
let embedder_index = index.embedder_category_id.get(wtxn, &embedder_name)?.ok_or(
|
||||||
InternalError::DatabaseMissingEntry { db_name: "embedder_category_id", key: None },
|
InternalError::DatabaseMissingEntry { db_name: "embedder_category_id", key: None },
|
||||||
)?;
|
)?;
|
||||||
@@ -372,8 +409,7 @@ pub(crate) fn write_typed_chunk_into_index(
|
|||||||
// FIXME: allow customizing distance
|
// FIXME: allow customizing distance
|
||||||
let writers: std::result::Result<Vec<_>, _> = (0..=u8::MAX)
|
let writers: std::result::Result<Vec<_>, _> = (0..=u8::MAX)
|
||||||
.map(|k| {
|
.map(|k| {
|
||||||
arroy::Writer::prepare(
|
arroy::Writer::new(
|
||||||
wtxn,
|
|
||||||
index.vector_arroy,
|
index.vector_arroy,
|
||||||
writer_index | (k as u16),
|
writer_index | (k as u16),
|
||||||
expected_dimension,
|
expected_dimension,
|
||||||
@@ -407,7 +443,7 @@ pub(crate) fn write_typed_chunk_into_index(
|
|||||||
// code error if we somehow got the wrong dimension
|
// code error if we somehow got the wrong dimension
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
if embeddings.embedding_count() > u8::MAX.into() {
|
if embeddings.embedding_count() > usize::from(u8::MAX) {
|
||||||
let external_docid = if let Ok(Some(Ok(index))) = index
|
let external_docid = if let Ok(Some(Ok(index))) = index
|
||||||
.external_id_of(wtxn, std::iter::once(docid))
|
.external_id_of(wtxn, std::iter::once(docid))
|
||||||
.map(|it| it.into_iter().next())
|
.map(|it| it.into_iter().next())
|
||||||
@@ -481,9 +517,11 @@ pub(crate) fn write_typed_chunk_into_index(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log::debug!("Finished vector chunk for {}", embedder_name);
|
tracing::debug!("Finished vector chunk for {}", embedder_name);
|
||||||
}
|
}
|
||||||
TypedChunk::ScriptLanguageDocids(sl_map) => {
|
TypedChunk::ScriptLanguageDocids(sl_map) => {
|
||||||
|
let span = tracing::trace_span!(target: "indexing::write_db", "script_language_docids");
|
||||||
|
let _entered = span.enter();
|
||||||
for (key, (deletion, addition)) in sl_map {
|
for (key, (deletion, addition)) in sl_map {
|
||||||
let mut db_key_exists = false;
|
let mut db_key_exists = false;
|
||||||
let final_value = match index.script_language_docids.get(wtxn, &key)? {
|
let final_value = match index.script_language_docids.get(wtxn, &key)? {
|
||||||
@@ -537,6 +575,7 @@ fn merge_word_docids_reader_into_fst(
|
|||||||
|
|
||||||
/// Write provided entries in database using serialize_value function.
|
/// Write provided entries in database using serialize_value function.
|
||||||
/// merge_values function is used if an entry already exist in the database.
|
/// merge_values function is used if an entry already exist in the database.
|
||||||
|
#[tracing::instrument(level = "trace", skip_all, target = "indexing::write_db")]
|
||||||
fn write_entries_into_database<R, K, V, FS, FM>(
|
fn write_entries_into_database<R, K, V, FS, FM>(
|
||||||
data: grenad::Reader<R>,
|
data: grenad::Reader<R>,
|
||||||
database: &heed::Database<K, V>,
|
database: &heed::Database<K, V>,
|
||||||
@@ -583,6 +622,7 @@ where
|
|||||||
/// merge_values function is used if an entry already exist in the database.
|
/// merge_values function is used if an entry already exist in the database.
|
||||||
/// All provided entries must be ordered.
|
/// All provided entries must be ordered.
|
||||||
/// If the index is not empty, write_entries_into_database is called instead.
|
/// If the index is not empty, write_entries_into_database is called instead.
|
||||||
|
#[tracing::instrument(level = "trace", skip_all, target = "indexing::write_db")]
|
||||||
fn append_entries_into_database<R, K, V, FS, FM>(
|
fn append_entries_into_database<R, K, V, FS, FM>(
|
||||||
data: grenad::Reader<R>,
|
data: grenad::Reader<R>,
|
||||||
database: &heed::Database<K, V>,
|
database: &heed::Database<K, V>,
|
||||||
|
|||||||
@@ -372,6 +372,11 @@ impl<'a, 't, 'i> Settings<'a, 't, 'i> {
|
|||||||
self.embedder_settings = Setting::Reset;
|
self.embedder_settings = Setting::Reset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(
|
||||||
|
level = "trace"
|
||||||
|
skip(self, progress_callback, should_abort, old_fields_ids_map),
|
||||||
|
target = "indexing::documents"
|
||||||
|
)]
|
||||||
fn reindex<FP, FA>(
|
fn reindex<FP, FA>(
|
||||||
&mut self,
|
&mut self,
|
||||||
progress_callback: &FP,
|
progress_callback: &FP,
|
||||||
@@ -974,6 +979,9 @@ impl<'a, 't, 'i> Settings<'a, 't, 'i> {
|
|||||||
crate::vector::settings::EmbeddingSettings::apply_default_source(
|
crate::vector::settings::EmbeddingSettings::apply_default_source(
|
||||||
&mut setting,
|
&mut setting,
|
||||||
);
|
);
|
||||||
|
crate::vector::settings::EmbeddingSettings::apply_default_openai_model(
|
||||||
|
&mut setting,
|
||||||
|
);
|
||||||
let setting = validate_embedding_settings(setting, &name)?;
|
let setting = validate_embedding_settings(setting, &name)?;
|
||||||
changed = true;
|
changed = true;
|
||||||
new_configs.insert(name, setting);
|
new_configs.insert(name, setting);
|
||||||
@@ -1119,6 +1127,14 @@ pub fn validate_embedding_settings(
|
|||||||
let Setting::Set(settings) = settings else { return Ok(settings) };
|
let Setting::Set(settings) = settings else { return Ok(settings) };
|
||||||
let EmbeddingSettings { source, model, revision, api_key, dimensions, document_template } =
|
let EmbeddingSettings { source, model, revision, api_key, dimensions, document_template } =
|
||||||
settings;
|
settings;
|
||||||
|
|
||||||
|
if let Some(0) = dimensions.set() {
|
||||||
|
return Err(crate::error::UserError::InvalidSettingsDimensions {
|
||||||
|
embedder_name: name.to_owned(),
|
||||||
|
}
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
let Some(inferred_source) = source.set() else {
|
let Some(inferred_source) = source.set() else {
|
||||||
return Ok(Setting::Set(EmbeddingSettings {
|
return Ok(Setting::Set(EmbeddingSettings {
|
||||||
source,
|
source,
|
||||||
@@ -1132,14 +1148,34 @@ pub fn validate_embedding_settings(
|
|||||||
match inferred_source {
|
match inferred_source {
|
||||||
EmbedderSource::OpenAi => {
|
EmbedderSource::OpenAi => {
|
||||||
check_unset(&revision, "revision", inferred_source, name)?;
|
check_unset(&revision, "revision", inferred_source, name)?;
|
||||||
check_unset(&dimensions, "dimensions", inferred_source, name)?;
|
|
||||||
if let Setting::Set(model) = &model {
|
if let Setting::Set(model) = &model {
|
||||||
crate::vector::openai::EmbeddingModel::from_name(model.as_str()).ok_or(
|
let model = crate::vector::openai::EmbeddingModel::from_name(model.as_str())
|
||||||
crate::error::UserError::InvalidOpenAiModel {
|
.ok_or(crate::error::UserError::InvalidOpenAiModel {
|
||||||
embedder_name: name.to_owned(),
|
embedder_name: name.to_owned(),
|
||||||
model: model.clone(),
|
model: model.clone(),
|
||||||
},
|
})?;
|
||||||
)?;
|
if let Setting::Set(dimensions) = dimensions {
|
||||||
|
if !model.supports_overriding_dimensions()
|
||||||
|
&& dimensions != model.default_dimensions()
|
||||||
|
{
|
||||||
|
return Err(crate::error::UserError::InvalidOpenAiModelDimensions {
|
||||||
|
embedder_name: name.to_owned(),
|
||||||
|
model: model.name(),
|
||||||
|
dimensions,
|
||||||
|
expected_dimensions: model.default_dimensions(),
|
||||||
|
}
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
if dimensions > model.default_dimensions() {
|
||||||
|
return Err(crate::error::UserError::InvalidOpenAiModelDimensionsMax {
|
||||||
|
embedder_name: name.to_owned(),
|
||||||
|
model: model.name(),
|
||||||
|
dimensions,
|
||||||
|
max_dimensions: model.default_dimensions(),
|
||||||
|
}
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EmbedderSource::HuggingFace => {
|
EmbedderSource::HuggingFace => {
|
||||||
|
|||||||
@@ -39,7 +39,12 @@ impl<'t, 'i> WordPrefixDocids<'t, 'i> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[logging_timer::time("WordPrefixDocids::{}")]
|
#[tracing::instrument(
|
||||||
|
level = "trace",
|
||||||
|
skip_all,
|
||||||
|
target = "indexing::prefix",
|
||||||
|
name = "word_prefix_docids"
|
||||||
|
)]
|
||||||
pub fn execute(
|
pub fn execute(
|
||||||
self,
|
self,
|
||||||
mut new_word_docids_iter: grenad::ReaderCursor<CursorClonableMmap>,
|
mut new_word_docids_iter: grenad::ReaderCursor<CursorClonableMmap>,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use std::str;
|
|||||||
use grenad::CompressionType;
|
use grenad::CompressionType;
|
||||||
use heed::types::Bytes;
|
use heed::types::Bytes;
|
||||||
use heed::{BytesDecode, BytesEncode, Database};
|
use heed::{BytesDecode, BytesEncode, Database};
|
||||||
use log::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::error::SerializationError;
|
use crate::error::SerializationError;
|
||||||
use crate::heed_codec::StrBEU16Codec;
|
use crate::heed_codec::StrBEU16Codec;
|
||||||
@@ -44,7 +44,12 @@ impl<'t, 'i> WordPrefixIntegerDocids<'t, 'i> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[logging_timer::time("WordPrefixIntegerDocids::{}")]
|
#[tracing::instrument(
|
||||||
|
level = "trace",
|
||||||
|
skip_all,
|
||||||
|
target = "indexing::prefix",
|
||||||
|
name = "words_prefix_integer_docids"
|
||||||
|
)]
|
||||||
pub fn execute(
|
pub fn execute(
|
||||||
self,
|
self,
|
||||||
new_word_integer_docids: grenad::Reader<CursorClonableMmap>,
|
new_word_integer_docids: grenad::Reader<CursorClonableMmap>,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user