Skip to content

Commit

Permalink
Merge branch 'main' into bump-meilisearch-v1.11.0
Browse files Browse the repository at this point in the history
  • Loading branch information
curquiza authored Oct 3, 2024
2 parents e6ba2cd + 4f9f232 commit 4ebf623
Show file tree
Hide file tree
Showing 18 changed files with 1,822 additions and 490 deletions.
18 changes: 17 additions & 1 deletion .code-samples.meilisearch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ multi_search_1: |-
},
},
})
get_experimental_features_1: |-
client.ExperimentalFeatures().Get()
update_experimental_features_1: |-
client.ExperimentalFeatures().SetMetrics(true).Update()
facet_search_1: |-
client.Index("books").FacetSearch(&meilisearch.FacetSearchRequest{
FacetQuery: "fiction",
Expand Down Expand Up @@ -919,7 +923,7 @@ update_separator_tokens_1: |-
"…",
})
reset_separator_tokens_1: |-
client.Index("articles").ResetSeparatorTokens()
client.Index("articles").ResetSeparatorTokens()
get_non_separator_tokens_1: |-
client.Index("articles").GetNonSeparatorTokens()
update_non_separator_tokens_1: |-
Expand All @@ -935,3 +939,15 @@ update_proximity_precision_settings_1: |-
client.Index("books").UpdateProximityPrecision(ByAttribute)
reset_proximity_precision_settings_1: |-
client.Index("books").ResetProximityPrecision()
search_parameter_reference_locales_1: |-
client.index("INDEX_NAME").Search("進撃の巨人", &meilisearch.SearchRequest{
Locates: []string{"jpn"}
})
get_localized_attribute_settings_1: |-
client.index("INDEX_NAME").GetLocalizedAttributes()
update_localized_attribute_settings_1: |-
client.index("INDEX_NAME").UpdateLocalizedAttributes([]*LocalizedAttributes{
{ AttributePatterns: ["*_ja"], Locales: ["jpn"] },
})
reset_localized_attribute_settings_1: |-
client.index("INDEX_NAME").ResetLocalizedAttributes()
68 changes: 61 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,19 @@

**Meilisearch** is an open-source search engine. [Learn more about Meilisearch.](https://github.com/meilisearch/Meilisearch)

## Table of Contents <!-- omit in TOC -->
## Table of Contents

- [📖 Documentation](#-documentation)
- [ Supercharge your Meilisearch experience](#-supercharge-your-meilisearch-experience)
- [💫 Supercharge your Meilisearch experience](#-supercharge-your-meilisearch-experience)
- [🔧 Installation](#-installation)
- [🚀 Getting started](#-getting-started)
- [Add documents](#add-documents)
- [Basic search](#basic-search)
- [Custom search](#custom-search)
- [Custom search with filter](#custom-search-with-filters)
- [Customize client](#customize-client)
- [🤖 Compatibility with Meilisearch](#-compatibility-with-meilisearch)
- [⚡️ Benchmark performance](#-benchmark-performance)
- [💡 Learn more](#-learn-more)
- [⚙️ Contributing](#️-contributing)

Expand All @@ -57,7 +63,7 @@ With `go get` in command line:
go get github.com/meilisearch/meilisearch-go
```

### Run Meilisearch <!-- omit in toc -->
### Run Meilisearch

There are many easy ways to [download and run a Meilisearch instance](https://www.meilisearch.com/docs/learn/getting_started/installation).

Expand All @@ -75,7 +81,7 @@ NB: you can also download Meilisearch from **Homebrew** or **APT** or even run i

## 🚀 Getting started

#### Add documents <!-- omit in toc -->
#### Add documents

```go
package main
Expand Down Expand Up @@ -114,7 +120,7 @@ func main() {

With the `taskUID`, you can check the status (`enqueued`, `canceled`, `processing`, `succeeded` or `failed`) of your documents addition using the [task endpoint](https://www.meilisearch.com/docs/reference/api/tasks).

#### Basic Search <!-- omit in toc -->
#### Basic Search

```go
package main
Expand Down Expand Up @@ -156,7 +162,7 @@ JSON output:
}
```

#### Custom Search <!-- omit in toc -->
#### Custom Search

All the supported options are described in the [search parameters](https://www.meilisearch.com/docs/reference/api/search#search-parameters) section of the documentation.

Expand Down Expand Up @@ -196,7 +202,7 @@ JSON output:
}
```

#### Custom Search With Filters <!-- omit in toc -->
#### Custom Search With Filters

If you want to enable filtering, you must add your attributes to the `filterableAttributes` index setting.

Expand Down Expand Up @@ -234,10 +240,58 @@ searchRes, err := index.Search("wonder",
}
```

#### Customize Client

The client supports many customization options:

- `WithCustomClient` sets a custom `http.Client`.
- `WithCustomClientWithTLS` enables TLS for the HTTP client.
- `WithAPIKey` sets the API key or master [key](https://www.meilisearch.com/docs/reference/api/keys).
- `WithContentEncoding` configures [content encoding](https://www.meilisearch.com/docs/reference/api/overview#content-encoding) for requests and responses. Currently, gzip, deflate, and brotli are supported.
- `WithCustomRetries` customizes retry behavior based on specific HTTP status codes (`retryOnStatus`, defaults to 502, 503, and 504) and allows setting the maximum number of retries.
- `DisableRetries` disables the retry logic. By default, retries are enabled.

```go
package main

import (
"net/http"
"github.com/meilisearch/meilisearch-go"
)

func main() {
client := meilisearch.New("http://localhost:7700",
meilisearch.WithAPIKey("foobar"),
meilisearch.WithCustomClient(http.DefaultClient),
meilisearch.WithContentEncoding(meilisearch.GzipEncoding, meilisearch.BestCompression),
meilisearch.WithCustomRetries([]int{502}, 20),
)
}
```

## 🤖 Compatibility with Meilisearch

This package guarantees compatibility with [version v1.x of Meilisearch](https://github.com/meilisearch/meilisearch/releases/latest), but some features may not be present. Please check the [issues](https://github.com/meilisearch/meilisearch-go/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22+label%3Aenhancement) for more info.

## ⚡️ Benchmark Performance

The Meilisearch client performance was tested in [client_bench_test.go](/client_bench_test.go).

```shell
goos: linux
goarch: amd64
pkg: github.com/meilisearch/meilisearch-go
cpu: AMD Ryzen 7 5700U with Radeon Graphics
```

**Results**

```shell
Benchmark_ExecuteRequest-16 10000 105880 ns/op 7241 B/op 87 allocs/op
Benchmark_ExecuteRequestWithEncoding-16 2716 455548 ns/op 1041998 B/op 169 allocs/op
Benchmark_ExecuteRequestWithoutRetries-16 1 3002787257 ns/op 56528 B/op 332 allocs/op
```

## 💡 Learn more

The following sections in our main documentation website may interest you:
Expand Down
102 changes: 93 additions & 9 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"net/http"
"net/url"
"sync"
"time"
)

type client struct {
Expand All @@ -19,6 +20,18 @@ type client struct {
bufferPool *sync.Pool
encoder encoder
contentEncoding ContentEncoding
retryOnStatus map[int]bool
disableRetry bool
maxRetries uint8
retryBackoff func(attempt uint8) time.Duration
}

type clientConfig struct {
contentEncoding ContentEncoding
encodingCompressionLevel EncodingCompressionLevel
retryOnStatus map[int]bool
disableRetry bool
maxRetries uint8
}

type internalRequest struct {
Expand All @@ -34,7 +47,7 @@ type internalRequest struct {
functionName string
}

func newClient(cli *http.Client, host, apiKey string, ce ContentEncoding, cl EncodingCompressionLevel) *client {
func newClient(cli *http.Client, host, apiKey string, cfg clientConfig) *client {
c := &client{
client: cli,
host: host,
Expand All @@ -44,11 +57,28 @@ func newClient(cli *http.Client, host, apiKey string, ce ContentEncoding, cl Enc
return new(bytes.Buffer)
},
},
disableRetry: cfg.disableRetry,
maxRetries: cfg.maxRetries,
retryOnStatus: cfg.retryOnStatus,
}

if c.retryOnStatus == nil {
c.retryOnStatus = map[int]bool{
502: true,
503: true,
504: true,
}
}

if !c.disableRetry && c.retryBackoff == nil {
c.retryBackoff = func(attempt uint8) time.Duration {
return time.Second * time.Duration(attempt)
}
}

if !ce.IsZero() {
c.contentEncoding = ce
c.encoder = newEncoding(ce, cl)
if !cfg.contentEncoding.IsZero() {
c.contentEncoding = cfg.contentEncoding
c.encoder = newEncoding(cfg.contentEncoding, cfg.encodingCompressionLevel)
}

return c
Expand Down Expand Up @@ -197,12 +227,9 @@ func (c *client) sendRequest(

request.Header.Set("User-Agent", GetQualifiedVersion())

resp, err := c.client.Do(request)
resp, err := c.do(request, internalError)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return nil, internalError.WithErrCode(MeilisearchTimeoutError, err)
}
return nil, internalError.WithErrCode(MeilisearchCommunicationError, err)
return nil, err
}

if body != nil {
Expand All @@ -213,6 +240,58 @@ func (c *client) sendRequest(
return resp, nil
}

func (c *client) do(req *http.Request, internalError *Error) (resp *http.Response, err error) {
retriesCount := uint8(0)

for {
resp, err = c.client.Do(req)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return nil, internalError.WithErrCode(MeilisearchTimeoutError, err)
}
return nil, internalError.WithErrCode(MeilisearchCommunicationError, err)
}

// Exit if retries are disabled
if c.disableRetry {
break
}

// Check if response status is retryable and we haven't exceeded max retries
if c.retryOnStatus[resp.StatusCode] && retriesCount < c.maxRetries {
retriesCount++

// Close response body to prevent memory leaks
resp.Body.Close()

// Handle backoff with context cancellation support
backoff := c.retryBackoff(retriesCount)
timer := time.NewTimer(backoff)

select {
case <-req.Context().Done():
err := req.Context().Err()
timer.Stop()
return nil, internalError.WithErrCode(MeilisearchTimeoutError, err)
case <-timer.C:
// Retry after backoff
timer.Stop()
}

continue
}

break
}

// Return error if retries exceeded the maximum limit
if !c.disableRetry && retriesCount >= c.maxRetries {
return nil, internalError.WithErrCode(MeilisearchMaxRetriesExceeded, nil)
}

return resp, nil
}

func (c *client) handleStatusCode(req *internalRequest, statusCode int, body []byte, internalError *Error) error {
if req.acceptedStatusCodes != nil {

Expand Down Expand Up @@ -244,6 +323,11 @@ func (c *client) handleResponse(req *internalRequest, body []byte, internalError
} else {
internalError.ResponseToString = string(body)

if internalError.ResponseToString == nullBody {
req.withResponse = nil
return nil
}

var err error
if resp, ok := req.withResponse.(json.Unmarshaler); ok {
err = resp.UnmarshalJSON(body)
Expand Down
Loading

0 comments on commit 4ebf623

Please sign in to comment.