<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: moritz rieger</title>
    <description>The latest articles on DEV Community by moritz rieger (@moritzrieger).</description>
    <link>https://dev.to/moritzrieger</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F582177%2Fab5460fa-f322-4af5-b61c-62661b0db1d1.png</url>
      <title>DEV Community: moritz rieger</title>
      <link>https://dev.to/moritzrieger</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/moritzrieger"/>
    <language>en</language>
    <item>
      <title>Setup an ingress rate limiter with envoy and istio</title>
      <dc:creator>moritz rieger</dc:creator>
      <pubDate>Sun, 22 Aug 2021 08:26:55 +0000</pubDate>
      <link>https://dev.to/tresmonauten/setup-an-ingress-rate-limiter-with-envoy-and-istio-1i9g</link>
      <guid>https://dev.to/tresmonauten/setup-an-ingress-rate-limiter-with-envoy-and-istio-1i9g</guid>
      <description>&lt;p&gt;With the removal of the mixer component in istio 1.5, the configuration of rate limiting changed. Since istio &amp;gt; 1.5, rate limiting is done with an EnvoyFilter that applies to your proxies. Therefore it makes sense to have a closer look at the istio data plane to gain a better understanding about envoy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fistio.io%2Flatest%2Fdocs%2Fops%2Fdeployment%2Farchitecture%2Farch.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fistio.io%2Flatest%2Fdocs%2Fops%2Fdeployment%2Farchitecture%2Farch.svg" alt="istio architecture"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Image from &lt;a href="https://istio.io/latest/docs/concepts/what-is-istio/" rel="noopener noreferrer"&gt;https://istio.io/latest/docs/concepts/what-is-istio/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As you can see in the above image, the service mesh dataplane consists of envoy proxies sitting in front of each of your services. An Ingressgateway or Egressgateway is also just an envoy proxy at the edge. Because all traffic in the mesh goes through these proxies, you gain observability of your network traffic as well as control by configuring the proxies to your needs. To better understand the possibilites of a service mesh, we should take a deeper look into the envoy proxy itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is envoy?
&lt;/h2&gt;

&lt;p&gt;In the &lt;a href="https://www.envoyproxy.io/docs/envoy/v1.17.0/intro/what_is_envoy" rel="noopener noreferrer"&gt;envoy docs&lt;/a&gt; we read:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Envoy is an L7 proxy and communication bus designed for large modern service oriented architectures. The project was born out of the belief that:&lt;/p&gt;

&lt;p&gt;"The network should be transparent to applications. When network and application problems do occur it should be easy to determine the source of the problem."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Since envoy runs as a sidecar in your pods, you can update the proxy configuration on the fly without touching the different services itself.&lt;/p&gt;

&lt;p&gt;Every Proxy has a built in filter chain. In this chain there are pluggable filters which provide the traffic management capabilites. The filters perform different tasks like buffering, rate limiting and, last but not least routing. You can think of the filter chain like a middleware in your webserver.&lt;/p&gt;

&lt;p&gt;To avoid confusion you should have a look at the &lt;a href="https://www.envoyproxy.io/docs/envoy/v1.17.0/intro/arch_overview/intro/terminology#terminology" rel="noopener noreferrer"&gt;envoy terminology&lt;/a&gt;.&lt;br&gt;
We cited the most important terms for this article from the envoy docs.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Host&lt;/strong&gt;: An entity capable of network communication (application on a mobile phone, server, etc.). In this documentation a host is a logical network application. A physical piece of hardware could possibly have multiple hosts running on it as long as each of them can be independently addressed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Downstream&lt;/strong&gt;: A downstream host connects to Envoy, sends requests, and receives responses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Upstream&lt;/strong&gt;: An upstream host receives connections and requests from Envoy and returns responses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listener&lt;/strong&gt;: A listener is a named network location (e.g., port, unix domain socket, etc.) that can be connected to by downstream clients. Envoy exposes one or more listeners that downstream hosts connect to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cluster&lt;/strong&gt;: A cluster is a group of logically similar upstream hosts that Envoy connects to. Envoy discovers the members of a cluster via service discovery. It optionally determines the health of cluster members via active health checking. The cluster member that Envoy routes a request to is determined by the load balancing policy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, that we have a glimpse of the istio data plane. Let's solve an actual problem with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rate limit a service
&lt;/h2&gt;

&lt;p&gt;Envoy has &lt;a href="https://www.envoyproxy.io/docs/envoy/v1.17.0/intro/arch_overview/other_features/local_rate_limiting" rel="noopener noreferrer"&gt;local (non-distributed)&lt;/a&gt; and global rate limiting capabilities. This article will focus on the &lt;a href="https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/other_features/global_rate_limiting#arch-overview-global-rate-limit" rel="noopener noreferrer"&gt;global rate limiting architecture&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To use global rate limiting you need an external rate limiter service that keeps track of the domains that have to be rate limited. Luckily envoy provides a redis based &lt;a href="https://github.com/envoyproxy/ratelimit" rel="noopener noreferrer"&gt;ratelimit service&lt;/a&gt;. In the following sections you will learn how to configure the redis based ratelimit service and how to rate limit specific routes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foejypcnd24ps50m9c2ra.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foejypcnd24ps50m9c2ra.png" alt="envoy filter chain"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Image created with draw.io by Natalia Sattler and Moritz Rieger&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy the ratelimit service
&lt;/h3&gt;

&lt;p&gt;For the deployment of the ratelimit service you can use the &lt;a href="https://github.com/istio/istio/blob/master/samples/ratelimit/rate-limit-service.yaml" rel="noopener noreferrer"&gt;ratelimitservice.yaml&lt;/a&gt; as a starting point. If you already have a redis instance in your kubernetes cluster, then feel free to use it for the rate limiter service as well and remove redis related parts. &lt;br&gt;
Adjust the ConfigMap &lt;code&gt;ratelimit-config&lt;/code&gt; with your rate limiting rules and specify the &lt;code&gt;REDIS_URL&lt;/code&gt; in the &lt;code&gt;ratelimit&lt;/code&gt; Deployment. Now you can deploy the ratelimit service to your kubernetes cluster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rate limiting rules
&lt;/h3&gt;

&lt;p&gt;You should check the ratelimit service &lt;a href="https://github.com/envoyproxy/ratelimit#configuration" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; for details on how to configure the rate limiting rules. For now you can just use the example configuration from below and adjust it to your needs.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example configuration:&lt;/em&gt; &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

apiVersion: v1
kind: ConfigMap
metadata:
  name: ratelimit-config
data:
  config.yaml: |
    domain: foo-domain
    descriptors:
      - key: BAR
        value: "/foo"
        rate_limit:
          unit: minute
          requests_per_unit: 1
      - key: BAR
        rate_limit:
          unit: minute
          requests_per_unit: 100


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;From the rate limit docs&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Domain&lt;/strong&gt;: A domain is a container for a set of rate limits. All domains known to the Ratelimit service must be globally unique.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Descriptor&lt;/strong&gt;: A descriptor is a list of key/value pairs owned by a domain that the Ratelimit service uses to select the correct rate limit to use when limiting.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the example we use &lt;code&gt;foo-domain&lt;/code&gt; to group our rate limiting rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;all descriptors with key &lt;code&gt;BAR&lt;/code&gt; and value &lt;code&gt;/foo&lt;/code&gt; will have a rate limit of 1 request per minute&lt;/li&gt;
&lt;li&gt;all other values of the descriptor with key &lt;code&gt;BAR&lt;/code&gt; will have a rate limit of 100 requests per minute.&lt;/li&gt;
&lt;li&gt;all other keys are not rate limited&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The rate limit service determines whether a request should be limited based on the descriptors provided in the check call. If the call is made with &lt;code&gt;("BAR","/foo")&lt;/code&gt;, rule number one of the rate limiter will be used, if it is called with &lt;code&gt;("BAR","/foobar")&lt;/code&gt;, the second rule kicks in. Because there is no special handling for the &lt;code&gt;/foobar&lt;/code&gt; value. If another request with &lt;code&gt;("BAR","/test")&lt;/code&gt; comes in, the rule number two will be used as well. Note, that the rate limit is not shared with &lt;code&gt;("BAR","/foobar")&lt;/code&gt;. Each of the requests can be queried 100 times per minute. The implicit rule number three gets applied if the rate limit service is invoked with something like &lt;code&gt;("FOO","/foo")&lt;/code&gt;. For an unspecified key, no ratelimiting is applied at all.&lt;/p&gt;

&lt;p&gt;One important thing to mention is that the value of the descriptor, though it looks like a part of an URI, does not have to denote the requested URI. It is merely a value of the descriptor and could be anything. In the example it is specified this way because of the envoy filter we configure later on.&lt;/p&gt;

&lt;p&gt;When the configuration of the rate limiter is done and the service is beamed into your kubernetes cluster, it is time to ensure that all incoming requests will be guarded by this service.&lt;/p&gt;

&lt;h3&gt;
  
  
  Introduce the ratelimit service to Envoy
&lt;/h3&gt;

&lt;p&gt;At first, register the ratelimit service as an envoy cluster. This is done via an envoy filter patch which is applied at the cluster level. The config patch specifies the new envoy cluster with a name of your choice and the according endpoints of the rate limiter service (line 11-36).&lt;/p&gt;

&lt;p&gt;This cluster can then be referenced from the ratelimit http filter so that the filter knows where to send the rate limit queries.&lt;/p&gt;

&lt;p&gt;This is exactly what the second path does. We insert the rate limit http filter into to the filter chain of the ingress gateway proxy. In line 55-62 we reference to the previously created cluster and rate limiter domain in the filter definition.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example&lt;/em&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: filter-ratelimit
  namespace: istio-system
spec:
  workloadSelector:
    labels:
      istio: ingressgateway
  configPatches:
  - applyTo: CLUSTER
    match:
      proxy:
        proxyVersion: ^1\.15.*
      cluster:
        # kubernetes dns of your ratelimit service
        service: ratelimit.default.svc.cluster.local
    patch:
      operation: ADD
      value:
        name: rate_limit_cluster
        type: STRICT_DNS
        connect_timeout: 10s
        lb_policy: ROUND_ROBIN
        http2_protocol_options: {}
        load_assignment:
          # arbitrary  name
          cluster_name: rate_limit_cluster
          endpoints:
          - lb_endpoints:
            - endpoint:
                address:
                  socket_address:
                    # kubernetes dns of your ratelimit service
                    address: ratelimit.default.svc.cluster.local
                    port_value: 8081
  - applyTo: HTTP_FILTER
      match:
        context: GATEWAY
        proxy:
          proxyVersion: ^1\.15.*
        listener:
          filterChain:
            filter:
              name: 'envoy.http_connection_manager'
              subFilter:
                name: 'envoy.router'
      patch:
        operation: INSERT_BEFORE
        value:
          name: envoy.filters.http.ratelimit
          typed_config:
            '@type': type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit
            # arbirary domain, ensure it matches with the domain used in the ratelimit service config
            domain: foo-domain
            failure_mode_deny: true
            rate_limit_service:
              grpc_service:
                envoy_grpc:
                  # must match load_assignment.cluster_name from the patch to the CLUSTER above
                  cluster_name: rate_limit_cluster
                timeout: 10s
              transport_api_version: V3


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Use rate limit filter in an ingress gateway
&lt;/h3&gt;

&lt;p&gt;So far we have configured the rate limit filter and the cluster. Now it is time to use this filter in our ingress gateway to rate limit incoming requests.&lt;br&gt;
In the folowing yaml we attach the rate limit filter to the ingress gateway virtual host filter chain and specify the descriptor to be used with rate limiter service queries.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example&lt;/em&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: filter-ratelimit-svc
  namespace: istio-system
spec:
  workloadSelector:
    labels:
      istio: ingressgateway
  configPatches:
  - applyTo: VIRTUAL_HOST
    match:
      proxy:
        proxyVersion: ^1\.15.*
      context: GATEWAY
      routeConfiguration:
        # Should be in the namespace/name format. Use this field in conjunction with the portNumber and portName to accurately select the Envoy route configuration for a specific HTTPS server within a gateway config object.
        gateway: istio-system/istio-gateway
        portNumber: 443
        portName: https
    patch:
      operation: MERGE
      value:
        rate_limits:
        - actions:
          # This action results in the following descriptor ("BAR","/foo") where "/foo" is the requested path.
          # :path is resolved to the actual requested path at runtime and used as the descriptor value
          - request_headers:
              header_name: ':path'
              descriptor_key: 'BAR'


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Test envoy configuration
&lt;/h3&gt;

&lt;p&gt;Test yor configuration by just issuing the following request to your ingress gateway two times in a row &lt;code&gt;curl -i https://&amp;lt;your host&amp;gt;/foo&lt;/code&gt;. The second request should result in an 429 HTTP error.&lt;/p&gt;

&lt;p&gt;If not, troubleshoot your configuration.&lt;/p&gt;
&lt;h4&gt;
  
  
  1. Check if the rate limiter cluster is registered
&lt;/h4&gt;

&lt;p&gt;Verify that your rate limiter cluster is properly registered&lt;br&gt;
&lt;code&gt;istioctl proxy-config cluster &amp;lt;your-istio-ingressgateway-pod&amp;gt;.istio-system -o json&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This command should produce something like&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

[
  {
    "name": "rate_limit_cluster",
    "type": "STRICT_DNS",
    "connectTimeout": "10s",
    "loadAssignment": {
      "clusterName": "rate_limit_cluster",
      "endpoints": [
        {
          "lbEndpoints": [
            {
              "endpoint": {
                "address": {
                  "socketAddress": {
                    "address": "ratelimit.default.svc.cluster.local",
                    "portValue": 8081
                  }
                }
              }
            }
          ]
        }
      ]
    },
    "http2ProtocolOptions": {}
  },
  ...
]


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If not, use the &lt;code&gt;istioctl proxy-status&lt;/code&gt; command to check the status of the cluster.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;istioctl proxy-status &amp;lt;your-istio-ingressgateway-pod&amp;gt;.istio-system&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Check if the rate limiter filter is attached to the http filter chain
&lt;/h4&gt;

&lt;p&gt;Verify that the rate limiter filter is attached to the http filter chain&lt;br&gt;
&lt;code&gt;istioctl proxy-config listener &amp;lt;your-istio-ingressgateway-pod&amp;gt;.istio-system -o json&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This command should produce something like&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

[
  {
    "name": "0.0.0.0_8443",
    "address": {
      "socketAddress": {
        "address": "0.0.0.0",
        "portValue": 8443
      }
    },
    "filterChains": [
      {
        "filterChainMatch": {
          "serverNames": [
            "www.example.com"
          ]
        },
        "filters": [
          {
            "name": "envoy.filters.network.http_connection_manager",
            "typedConfig": {
              "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
              "statPrefix": "outbound_0.0.0.0_8443",
              "rds": {
                "configSource": {
                  "ads": {},
                  "resourceApiVersion": "V3"
                },
                "routeConfigName": "https.443.https.istio-gateway.istio-system"
              },
              "httpFilters": [
                ...
                                {
                  "name": "envoy.filters.http.ratelimit",
                  "typedConfig": {
                    "@type": "type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit",
                    "domain": "foo-domain",
                    "failureModeDeny": true,
                    "rateLimitService": {
                      "grpcService": {
                        "envoyGrpc": {
                          "clusterName": "rate_limit_cluster"
                        },
                        "timeout": "10s"
                      },
                      "transportApiVersion": "V3"
                    }
                  }
                },
                ...


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If not, check the problems with the same command as above.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Check if the rate limiter action configuration is applied to your ingress gateway virtual host configuration
&lt;/h4&gt;

&lt;p&gt;Verify the route configuration with the following command&lt;br&gt;
&lt;code&gt;istioctl proxy-config route &amp;lt;your-istio-ingressgateway-pod&amp;gt;.istio-system -o json&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;This command should produce something like&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

[
  {
    "name": "https.443.https.istio-gateway.istio-system",
    "virtualHosts": [
      {
        "name": "www.example.com:443",
        "domains": [
          "www.example.com",
          "www.example.com:*"
        ],
        "routes": [...],
        "rateLimits": [
          {
            "actions": [
              {
                "requestHeaders": {
                  "headerName": ":path",
                  "descriptorKey": "PATH"
                }
              }
            ]
          }
        ],
...


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If not, check the problems with the same command as above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Be aware of pitfalls!
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Query parameters make a path unique
&lt;/h3&gt;

&lt;p&gt;After you have applied all the configurations from above and verified that ratelimiting works. You may expect to get same rate limiting rules applied to the request &lt;code&gt;https://&amp;lt;your host&amp;gt;/foo?param=value&lt;/code&gt; as to &lt;code&gt;https://&amp;lt;your host&amp;gt;/foo&lt;/code&gt;. But this is not the case. The applied rule will be the second one (with 100 req/min).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed5ssnr5wv9tenvqjy0b.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed5ssnr5wv9tenvqjy0b.gif" alt="ohh really"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Image from giphy.com&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is because the &lt;a href="https://tools.ietf.org/html/rfc7540" rel="noopener noreferrer"&gt;pseudo-header field &lt;code&gt;:path&lt;/code&gt;&lt;/a&gt;, which we used for the descriptor value includes the path and all query parts of the target URI. The ratelimiter service on its part does not detect any special configuration for the descriptor &lt;code&gt;("BAR", "/foo?param=value")&lt;/code&gt; and therfore use the default of key &lt;code&gt;"BAR"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To make the search params work as expected with the rate limiter we have to cut the off the value. This can be done with just another filter &lt;a href="https://www.envoyproxy.io/docs/envoy/latest/api-v2/config/filter/http/header_to_metadata/v2/header_to_metadata.proto" rel="noopener noreferrer"&gt;envoy.filters.http.header_to_metadata&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To use this filter you have to change your filter definitions:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;filter-ratelimit&lt;/em&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: filter-ratelimit
  namespace: istio-system
spec:
  workloadSelector:
    labels:
      istio: ingressgateway
  configPatches:
  - applyTo: CLUSTER
    match:
      proxy:
        proxyVersion: ^1\.15.*
      cluster:
        # kubernetes dns of your ratelimit service
        service: ratelimit.default.svc.cluster.local
    patch:
      operation: ADD
      value:
        name: rate_limit_cluster
        type: STRICT_DNS
        connect_timeout: 10s
        lb_policy: ROUND_ROBIN
        http2_protocol_options: {}
        load_assignment:
          # arbitrary  name
          cluster_name: rate_limit_cluster
          endpoints:
          - lb_endpoints:
            - endpoint:
                address:
                  socket_address:
                    # kubernetes dns of your ratelimit service
                    address: ratelimit.default.svc.cluster.local
                    port_value: 8081
- applyTo: HTTP_FILTER
    match:
      proxy:
        proxyVersion: ^1\.15.*
      context: GATEWAY
      listener:
        filterChain:
          filter:
            name: 'envoy.http_connection_manager'
            subFilter:
              name: 'envoy.router'
    patch:
      operation: INSERT_BEFORE
      value:
        name: envoy.filters.http.header_to_metadata
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.http.header_to_metadata.v3.Config
          request_rules:
          - header: ':path'
            on_header_present:
              # use an arbitary name for the namespace
              # will be used later to extract descriptor value
              metadata_namespace: example
              # use an arbitary key for the metadata
              # will be used later to extract descriptor value
              key: uri
              regex_value_rewrite:
                pattern:
                  # regex matcher
                  google_re2: {}
                  # truncates parameters from path
                  regex: '^(\/[\/\d\w-]+)\??.*$'
                substitution: '\1'
- applyTo: HTTP_FILTER
    match:
      proxy:
        proxyVersion: ^1\.15.*
      context: GATEWAY
      listener:
        filterChain:
          filter:
            name: 'envoy.http_connection_manager'
            subFilter:
              name: 'envoy.filters.http.header_to_metadata'
    patch:
      operation: INSERT_BEFORE
      value:
        name: envoy.filters.http.ratelimit
        typed_config:
          '@type': type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit
          # ensure the domain matches with the domain used in the ratelimit service config
          domain: foo-domain
          failure_mode_deny: true
          rate_limit_service:
            grpc_service:
              envoy_grpc:
                # must match load_assignment.cluster_name from the patch to the CLUSTER above
                cluster_name: rate_limit_cluster
              timeout: 10s
            transport_api_version: V3


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This adds a new filter, which is registered just before the rate limiter filter and which rewrites the &lt;code&gt;:path&lt;/code&gt; header and adds it to the metadata. To use the metadata for the descriptor value update the following envoy filter definition.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;filter-ratelimit-svc&lt;/em&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: filter-ratelimit-svc
  namespace: istio-system
spec:
  workloadSelector:
    labels:
      istio: ingressgateway
  configPatches:
  - applyTo: VIRTUAL_HOST
    match:
      context: GATEWAY
      routeConfiguration:
        # Should be in the namespace/name format. Use this field in conjunction with the portNumber and portName to accurately select the Envoy route configuration for a specific HTTPS server within a gateway config object.
        gateway: istio-system/istio-gateway
        portNumber: 443
        portName: https
    patch:
      operation: MERGE
      value:
        rate_limits:
        - actions:
          - dynamic_metadata:
              descriptor_key: BAR
              metadata_key:
                key: example
                path:
                - key: uri


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;When you now call your service with &lt;code&gt;curl -i https://&amp;lt;your host&amp;gt;/foo?param=value&lt;/code&gt; followed by &lt;code&gt;curl -i https://&amp;lt;your host&amp;gt;/foo&lt;/code&gt;. You should receive 429 on the second call which verifies that both URIs are limited by the same rule.&lt;/p&gt;

&lt;h3&gt;
  
  
  Envoy filter syntax change
&lt;/h3&gt;

&lt;p&gt;When you introduce EnvoyFilter into your istio service mesh configuration, you should pay special attention to Envoy Filters, even on minor upgrades. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;As &lt;a href="https://istio.io/latest/news/releases/1.7.x/announcing-1.7/upgrade-notes/#envoyfilter-syntax-change" rel="noopener noreferrer"&gt;EnvoyFilter is a break glass API without backwards compatibility guarantees&lt;/a&gt;, we recommend users explicitly bind EnvoyFilters to specific versions and appropriately test them prior to upgrading.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Envoy version ≠ istio version
&lt;/h3&gt;

&lt;p&gt;Be aware which version of envoy is deployed in your istio service mesh. The Version of istio and envoy is not in sync.&lt;br&gt;
You can determine your version of envoy with the following command:&lt;br&gt;
&lt;code&gt;kubectl exec -it &amp;lt;PODNAME-WITH-ENVOY-SIDECAR&amp;gt; -c istio-proxy -n istio-system -- pilot-agent request GET server_info&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dc78069b10cc94fa07bb974b7101dd1b42e2e7bf/1.15.1-dev/Clean/RELEASE/BoringSSL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;```&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;span&amp;gt;Title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;image&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;href=&lt;/span&gt;&lt;span class="s2"&gt;"https://unsplash.com/@punttim?utm_source=unsplash&amp;amp;amp;utm_medium=referral&amp;amp;amp;utm_content=creditCopyText"&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;Tim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Gouw&amp;lt;/a&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;href=&lt;/span&gt;&lt;span class="s2"&gt;"https://unsplash.com/?utm_source=unsplash&amp;amp;amp;utm_medium=referral&amp;amp;amp;utm_content=creditCopyText"&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;Unsplash&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;nsattler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;moritzrieger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>servicemesh</category>
      <category>istio</category>
      <category>envoy</category>
      <category>ratelimit</category>
    </item>
    <item>
      <title>Build a fuzzy search with PostgreSQL</title>
      <dc:creator>moritz rieger</dc:creator>
      <pubDate>Fri, 12 Mar 2021 11:01:04 +0000</pubDate>
      <link>https://dev.to/moritzrieger/build-a-fuzzy-search-with-postgresql-2029</link>
      <guid>https://dev.to/moritzrieger/build-a-fuzzy-search-with-postgresql-2029</guid>
      <description>&lt;p&gt;Google made us used to just type what comes into our mind when searching for something.&lt;br&gt;
How can we achieve such a fuzzy search with a Postgres database?&lt;/p&gt;

&lt;p&gt;There are basically three approaches.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pattern matching with &lt;code&gt;ILIKE '%searchterm%'&lt;/code&gt; to find all occurences of &lt;code&gt;searchterm&lt;/code&gt; inside of a string.&lt;/li&gt;
&lt;li&gt;Postgres &lt;a href="https://www.postgresql.org/docs/12/textsearch.html" rel="noopener noreferrer"&gt;full-text search&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Postgres &lt;a href="https://www.postgresql.org/docs/12/pgtrgm.html" rel="noopener noreferrer"&gt;pg_trgrm&lt;/a&gt; (trigram) extension&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this article, I will focus on the third approach, because I want a search so fuzzy that is insensitive to typing errors and it should match even if a word is not complete. These two criteria dismiss the first two approaches.&lt;br&gt;
So let's dive into the third approach, the &lt;em&gt;Trigrams&lt;/em&gt;!&lt;/p&gt;
&lt;h2&gt;
  
  
  Trigrams
&lt;/h2&gt;

&lt;p&gt;Let's see what the &lt;a href="https://www.postgresql.org/docs/12/pgtrgm.html" rel="noopener noreferrer"&gt;postgres documentation&lt;/a&gt; tells us about trigrams:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A trigram is a group of three consecutive characters taken from a string. We can measure the similarity of two strings by counting the number of trigrams they share. This simple idea turns out to be very effective for measuring the similarity of words in many natural languages.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To get a grasp of what this means, check the &lt;code&gt;show_trgrm()&lt;/code&gt; method of the pg_trgrm extension.&lt;br&gt;
But first, we need to enable the trigram extension.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;EXTENSION&lt;/span&gt; &lt;span class="n"&gt;pg_trgm&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;show_trgm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Hello'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;show_trgm&lt;/span&gt;            
&lt;span class="c1"&gt;---------------------------------&lt;/span&gt;
 &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;"  h"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;" he"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;ell&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;hel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;llo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;"lo "&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;show_trgm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Hello World'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                           &lt;span class="n"&gt;show_trgm&lt;/span&gt;                           
&lt;span class="c1"&gt;---------------------------------------------------------------&lt;/span&gt;
 &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;"  h"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;"  w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;" he"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;" wo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;ell&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;hel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;"ld "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;llo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;"lo "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;orl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;rld&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;wor&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  similarity
&lt;/h3&gt;

&lt;p&gt;This similarity method returns a number from 0 to 1, which indicates how similar two strings are. A value of 1 means the strings are identical.&lt;br&gt;
In our case, it is 0.5 since the first string has 6 trigrams in common with the second string, which has a total of 12 trigrams.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;similarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Hello'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Hello world'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="n"&gt;similarity&lt;/span&gt; 
&lt;span class="c1"&gt;------------&lt;/span&gt;
        &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But if you search for a small string in a larger string with the similarity function, the score will drop drastically. Because there are a lot of trigrams that are not in both strings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;similarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Hello'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Hello world, you wonderful planet!'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="n"&gt;similarity&lt;/span&gt; 
&lt;span class="c1"&gt;------------&lt;/span&gt;
 &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;19354838&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Therefore Postgres provides two other functions which respect word boundaries in strings.&lt;/p&gt;

&lt;h3&gt;
  
  
  word similarity
&lt;/h3&gt;

&lt;p&gt;The word similarity score reflects the highest matching similarity of a substring (&lt;em&gt;word_similarity&lt;/em&gt;) respectively of a word (&lt;em&gt;strict_word_similarity&lt;/em&gt;).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; No matter how long the second string gets, the (strict) word similarity will always be 1, because &lt;code&gt;Hello&lt;/code&gt; matches exactly a whole word in the example.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
&lt;span class="n"&gt;word_similarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Hello'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Hello world, you wonderful planet!'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;strict_word_similarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Hello'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Hello world, you wonderful planet!'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="n"&gt;word_similarity&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;strict_word_similarity&lt;/span&gt; 
&lt;span class="c1"&gt;-----------------+------------------------&lt;/span&gt;
               &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;                      &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The &lt;strong&gt;word similarity&lt;/strong&gt; score can be understood as the greatest similarity between the first string and any substring of the second string. So said, it is useful for finding the similarity for parts of words.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;strict word similarity&lt;/strong&gt; score is useful for finding the similarity of whole words.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The cool thing with trigram comparison is, that even if a word is misspelled, it has relatively high similarity.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;similarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'wonderfull'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wonderful'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;strict_word_similarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'wonderfull'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Hello World, you wonderful planet!'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;word_similarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'wonderfull'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Hello World, you wonderful planet!'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="n"&gt;similarity&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;strict_word_similarity&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;word_similarity&lt;/span&gt; 
&lt;span class="c1"&gt;------------+------------------------+-----------------&lt;/span&gt;
       &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;75&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;                   &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;75&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;       &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;8181818&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;

&lt;p&gt;So far so good, let's get our hands dirty.&lt;/p&gt;

&lt;p&gt;Imagine you have a bookstore. Often customers ask you about a book they only know fragments of the title, or you don't know how to spell the author correctly. Sounds like a perfect fit for our fuzzy search!&lt;/p&gt;

&lt;p&gt;For the sake of simplicity, let us generate a dataset. Yeah, I know this is not the most beautiful sample data for a search, but I hope you get the point.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;abstract&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;abstract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="k"&gt;left&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;md5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;left&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;md5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;md5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;concat_ws&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;left&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;md5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;left&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;md5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;generate_series&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
     &lt;span class="n"&gt;id&lt;/span&gt;     &lt;span class="o"&gt;|&lt;/span&gt;      &lt;span class="n"&gt;title&lt;/span&gt;      &lt;span class="o"&gt;|&lt;/span&gt;             &lt;span class="n"&gt;abstract&lt;/span&gt;             &lt;span class="o"&gt;|&lt;/span&gt;        &lt;span class="n"&gt;author&lt;/span&gt;         
&lt;span class="c1"&gt;------------+-----------------+----------------------------------+-----------------------&lt;/span&gt;
 &lt;span class="n"&gt;c4ca4238a0&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;06629&lt;/span&gt;&lt;span class="n"&gt;ea8b04aaa7&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="n"&gt;f0ad3e3542ecf1efbdddd98cca507a6&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;dceb23e53&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f01b57e71&lt;/span&gt;
 &lt;span class="n"&gt;c81e728d9d&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;a284b57f95d997&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;fdd45a7d9eda64c9deb4882ccbb42296&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;f3fbf4ed2a&lt;/span&gt; &lt;span class="n"&gt;ef99e869d2&lt;/span&gt;
 &lt;span class="n"&gt;eccbc87e4b&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;b464a24deba866e&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;eda8641e61327719906b493080cd96f&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;fca64a442c&lt;/span&gt; &lt;span class="n"&gt;db03ca6331&lt;/span&gt;
 &lt;span class="n"&gt;a87ff679a2&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;6275469&lt;/span&gt;&lt;span class="n"&gt;f9e41990&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;f3771fb3afdb463d302d7849c38d5641&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;fbe106a816&lt;/span&gt; &lt;span class="n"&gt;b4313ad5c3&lt;/span&gt;
 &lt;span class="n"&gt;e4da3b7fbb&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;b2e9f3a8bad3aec&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;a9f4b8432c1b6655ae8775dd5b904498&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="n"&gt;df1b43a13&lt;/span&gt; &lt;span class="mi"&gt;87&lt;/span&gt;&lt;span class="n"&gt;c7a303a4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  But wait. How can we search over multiple columns?
&lt;/h3&gt;

&lt;p&gt;Good catch. To search over multiple columns we have to concatenate all columns that should be searched with the &lt;code&gt;concat_ws&lt;/code&gt; function.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: The &lt;code&gt;&amp;lt;%&lt;/code&gt; operator returns &lt;code&gt;true&lt;/code&gt; if the word_similarity is above the &lt;code&gt;pg_trgm.word_similarity_threshold&lt;/code&gt; parameter.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;%&lt;/code&gt; operator has a commutator &lt;code&gt;%&amp;lt;&lt;/code&gt;.&lt;br&gt;
So 'Hello' &amp;lt;% 'Hello World' = 'Hello World' %&amp;lt; 'Hello'&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;pg_trgm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;word_similarity_threshold&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="n"&gt;pg_trgm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;word_similarity_threshold&lt;/span&gt; 
&lt;span class="c1"&gt;-----------------------------------&lt;/span&gt;
 &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="s1"&gt;'9c9f6'&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="n"&gt;concat_ws&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;abstract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
     &lt;span class="n"&gt;id&lt;/span&gt;     &lt;span class="o"&gt;|&lt;/span&gt;      &lt;span class="n"&gt;title&lt;/span&gt;      &lt;span class="o"&gt;|&lt;/span&gt;             &lt;span class="n"&gt;abstract&lt;/span&gt;             &lt;span class="o"&gt;|&lt;/span&gt;        &lt;span class="n"&gt;author&lt;/span&gt;         
&lt;span class="c1"&gt;------------+-----------------+----------------------------------+-----------------------&lt;/span&gt;
 &lt;span class="mi"&gt;2804&lt;/span&gt;&lt;span class="n"&gt;d14b1b&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;0152&lt;/span&gt;&lt;span class="n"&gt;e58b57b94ff&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="n"&gt;c9f468fa58fc140427f7354f1a2b88e&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;b520b29fae&lt;/span&gt; &lt;span class="n"&gt;ae1297ce25&lt;/span&gt;
 &lt;span class="n"&gt;c764c89e73&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f81a4878b09a36&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;667&lt;/span&gt;&lt;span class="n"&gt;ddc5f403d9c96b43912628e1cb73c&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;ebab7d5c7c&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="n"&gt;c9ffbfd82&lt;/span&gt;
 &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="n"&gt;fa8101625&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;30071&lt;/span&gt;&lt;span class="n"&gt;f6f0e9c9f6&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="n"&gt;f3250393ac1b214340af81f239e7ca5&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;ffec138ddb&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;b9060d0c4&lt;/span&gt;
 &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="n"&gt;cb394c278&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;ce5ad25e38577fc&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;805&lt;/span&gt;&lt;span class="n"&gt;d0e2ca8081c6a8a178b1f0650c9f6&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="n"&gt;c5332d434&lt;/span&gt; &lt;span class="n"&gt;cd79b0e802&lt;/span&gt;
 &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="n"&gt;b39d07008&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;a58ecb6f4e41c96&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;df452e5bd0b1102733bbc89dd78e6ee&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="n"&gt;c9fb041fa&lt;/span&gt; &lt;span class="mi"&gt;980&lt;/span&gt;&lt;span class="n"&gt;becdc8f&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1245&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;955&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;246&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ouch, that took waaaaay too long. Now, it's time for GIN!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fte19flnnd1lupwtvjayp.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fte19flnnd1lupwtvjayp.gif" alt="gin" width="500" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  boost your search performance with GIN
&lt;/h3&gt;

&lt;p&gt;Postgres has a special index for this use-case, it's called &lt;a href="https://www.postgresql.org/docs/12/gin.html" rel="noopener noreferrer"&gt;GIN (General Inverted Index)&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"GIN is designed for handling cases where the items to be indexed are composite values, and the queries to be handled by the index need to search for element values that appear within the composite items."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sounds like a perfect fit. But there is a little problem. We cannot create an index on the &lt;em&gt;concat_ws&lt;/em&gt; function, since it's not immutable. We have to build an immutable function wrapper to create an index on the concatenated columns.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- create immutable function wrapper for concat_ws&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;f_immutable_concat_ws&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t1&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t2&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t3&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;concat_ws&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="k"&gt;sql&lt;/span&gt; &lt;span class="k"&gt;IMMUTABLE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- create a GIN index&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;search_gin_trgm_idx&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt;
&lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f_immutable_concat_ws&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;abstract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;gin_trgm_ops&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- validate performance improvements&lt;/span&gt;
&lt;span class="k"&gt;EXPLAIN&lt;/span&gt; &lt;span class="k"&gt;ANALYZE&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="s1"&gt;'9c9f6'&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="n"&gt;f_immutable_concat_ws&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;titale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;abstract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                                                           &lt;span class="n"&gt;QUERY&lt;/span&gt; &lt;span class="n"&gt;PLAN&lt;/span&gt;                                                           
&lt;span class="c1"&gt;--------------------------------------------------------------------------------------------------------------------------------&lt;/span&gt;
 &lt;span class="n"&gt;Bitmap&lt;/span&gt; &lt;span class="n"&gt;Heap&lt;/span&gt; &lt;span class="n"&gt;Scan&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt;  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;68&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;152&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;82&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;937&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;394&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="n"&gt;loops&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'9c9f6'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="n"&gt;f_immutable_concat_ws&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;abstract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
   &lt;span class="k"&gt;Rows&lt;/span&gt; &lt;span class="n"&gt;Removed&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;
   &lt;span class="n"&gt;Heap&lt;/span&gt; &lt;span class="n"&gt;Blocks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;exact&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;
   &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;  &lt;span class="n"&gt;Bitmap&lt;/span&gt; &lt;span class="k"&gt;Index&lt;/span&gt; &lt;span class="n"&gt;Scan&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;search_gin_trgm_idx&lt;/span&gt;  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;65&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;653&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;663&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt; &lt;span class="n"&gt;loops&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
         &lt;span class="k"&gt;Index&lt;/span&gt; &lt;span class="n"&gt;Cond&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f_immutable_concat_ws&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;abstract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'9c9f6'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;Planning&lt;/span&gt; &lt;span class="nb"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;487&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt;
 &lt;span class="n"&gt;Execution&lt;/span&gt; &lt;span class="nb"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;533&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;965&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, this was 125 times faster! 🏎&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to go from here?
&lt;/h2&gt;

&lt;p&gt;You could try:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; &lt;code&gt;strict_word_similarity&lt;/code&gt; with &lt;code&gt;&amp;lt;&amp;lt;%&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;set another similarity threshold value for the &lt;a href="https://www.postgresql.org/docs/12/pgtrgm.html" rel="noopener noreferrer"&gt;GUC Parameters&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;if you search-string contains multiple words, use the &lt;code&gt;WHERE&lt;/code&gt; condition from above for every word and concatenate them with &lt;code&gt;AND&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The syntactic similarity is not enough for you?&lt;br&gt;
Word2vec could be an option to compare the semantic similarity of words. Luckily there already is a &lt;a href="https://github.com/guenthermi/postgres-word2vec" rel="noopener noreferrer"&gt;postgres-word2vec extension&lt;/a&gt; for this.&lt;/p&gt;

&lt;p&gt;I would love to hear about your solution for a fuzzy search on Postgres in the comments.&lt;/p&gt;

&lt;p&gt;Cheers!&lt;/p&gt;

</description>
      <category>database</category>
      <category>postgres</category>
      <category>sql</category>
      <category>search</category>
    </item>
  </channel>
</rss>
