mirror of
https://github.com/supabase/supabase.git
synced 2026-10-13 13:19:09 +03:00
Formatting
This commit is contained in:
1 parent
234acc3a76
commit
eef12aeb4a
1 file changed
+58
-61
@@ -18,22 +18,22 @@ Many projects function just fine with Postgres Full Text Search and other [built
|
||||
|
||||
## Search Engines: The Next Generation
|
||||
|
||||
These days, the debate isn't *just* Postgres versus Solr or ElasticSearch, a new generation of F/OSS search engines has arrived. To name a few:
|
||||
These days, the debate isn't _just_ Postgres versus Solr or ElasticSearch, a new generation of F/OSS search engines has arrived. To name a few:
|
||||
|
||||
- [MeiliSearch](https://www.meilisearch.com/)
|
||||
- [OpenSearch](https://opensearch.org/)
|
||||
- [SQLite FTS](https://www.sqlite.org/fts5.html)
|
||||
- [Typesense](https://typesense.org/)
|
||||
- [MeiliSearch](https://www.meilisearch.com/)
|
||||
- [OpenSearch](https://opensearch.org/)
|
||||
- [SQLite FTS](https://www.sqlite.org/fts5.html)
|
||||
- [Typesense](https://typesense.org/)
|
||||
|
||||
Well OK, some of these engines aren't *quite* new; SQLite is stable and has supported the search use-case for a while and OpenSearch is a fork of ElasticSearch. While not new, both are worth comparing as choices in front of engineers today (spoiler alert: we're going to compare them).
|
||||
Well OK, some of these engines aren't _quite_ new; SQLite is stable and has supported the search use-case for a while and OpenSearch is a fork of ElasticSearch. While not new, both are worth comparing as choices in front of engineers today (spoiler alert: we're going to compare them).
|
||||
|
||||
These search engines are all impressive, but it's hard to evaluate these options in a `VACUUM`.
|
||||
|
||||
If Postgres is already your main database, then it's probably easier to integrate than anything else. But would it be *better* to take on more complexity by choosing a dedicated search engine?
|
||||
If Postgres is already your main database, then it's probably easier to integrate than anything else. But would it be _better_ to take on more complexity by choosing a dedicated search engine?
|
||||
|
||||
While search is an endlessly complicated problem and all of these tools are configurable, a comparison of minimal setup and tuning should yield some interesting insights.
|
||||
|
||||
Before we get into it though, a *brief* primer on what "Full Text Search" *means*, in Postgres land.
|
||||
Before we get into it though, a _brief_ primer on what "Full Text Search" _means_, in Postgres land.
|
||||
|
||||
## A whirlwind tour through FTS in Postgres
|
||||
|
||||
@@ -46,19 +46,19 @@ Imagine you have some information to search through, stored in your main databas
|
||||
Maybe a `movies` table with a structure like this:
|
||||
|
||||
create table movies (
|
||||
id bigint primary key generated by default as identity,
|
||||
title text not null,
|
||||
original_title text not null,
|
||||
overview text not null,
|
||||
created_at timestamptz not null default now()
|
||||
id bigint primary key generated by default as identity,
|
||||
title text not null,
|
||||
original_title text not null,
|
||||
overview text not null,
|
||||
created_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
While you're inserting data you might perform `INSERTs` like this one:
|
||||
|
||||
```sql
|
||||
insert into movies (
|
||||
title,
|
||||
original_title,
|
||||
title,
|
||||
original_title,
|
||||
overview
|
||||
)
|
||||
values
|
||||
@@ -71,20 +71,20 @@ values
|
||||
|
||||
### Basic searching with select and like
|
||||
|
||||
With nothing but the raw data in your database, we can actually do some *basic* searching using `select` and comparison tools like `like`.
|
||||
With nothing but the raw data in your database, we can actually do some _basic_ searching using `select` and comparison tools like `like`.
|
||||
|
||||
```sql
|
||||
select
|
||||
select
|
||||
*
|
||||
from
|
||||
from
|
||||
movies
|
||||
where
|
||||
title like '%Avengers%'
|
||||
or
|
||||
or
|
||||
overview like '%Avengers%';
|
||||
```
|
||||
|
||||
The `LIKE` [operator](https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-LIKE) as used in the query above will match *only* the term "Avengers", but it will match it in any part of a given row's `title` or `overview`.
|
||||
The `LIKE` [operator](https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-LIKE) as used in the query above will match _only_ the term "Avengers", but it will match it in any part of a given row's `title` or `overview`.
|
||||
|
||||
We can hack around this basic functionality and make it a bit more robust:
|
||||
|
||||
@@ -92,7 +92,7 @@ We can hack around this basic functionality and make it a bit more robust:
|
||||
- Combining the text that will be searched into one column
|
||||
- Using a [GIN index](https://www.postgresql.org/docs/14/indexes-types.html#INDEXES-TYPES-GIN)
|
||||
|
||||
Our "v0" would be better, but using `like` isn't quite what people mean when they ask for *robust* full text search. **Not a bad first version, but Postgres can do much better.**
|
||||
Our "v0" would be better, but using `like` isn't quite what people mean when they ask for _robust_ full text search. **Not a bad first version, but Postgres can do much better.**
|
||||
|
||||
### Robust Full Text Search with Postgres
|
||||
|
||||
@@ -108,36 +108,36 @@ State of the art search engines like [Solr](https://solr.apache.org/) do things
|
||||
- Search by [edit distance](https://en.wikipedia.org/wiki/Levenshtein_distance)
|
||||
- Vector based similarity (which you might use for a basic recommendation system)
|
||||
|
||||
Postgres FTS does not do *all* of the things a purpose-built search engine can do, but Postgres can produce very good results with decent performance.
|
||||
Postgres FTS does not do _all_ of the things a purpose-built search engine can do, but Postgres can produce very good results with decent performance.
|
||||
|
||||
## A working Postgres FTS setup
|
||||
|
||||
Integrating Full Text Search into our imaginary movies table is as simple as executing the following:
|
||||
|
||||
```sql
|
||||
```sql
|
||||
|
||||
-- Add a generated column that contains the search document
|
||||
alter table movies
|
||||
add column fts_doc_en
|
||||
add column fts_doc_en
|
||||
generated always as to_tsvector (
|
||||
'english', title || ' ' || original_title || ' ' || overview
|
||||
)
|
||||
)
|
||||
stored;
|
||||
|
||||
-- Create a GIN index to make our searches faster
|
||||
create index movies_fts_doc_en_idx
|
||||
on movies
|
||||
using gin (fts_doc_en);
|
||||
create index movies_fts_doc_en_idx
|
||||
on movies
|
||||
using gin (fts_doc_en);
|
||||
```
|
||||
|
||||
Postgres supports even more features like [facilities for ranking search results](https://www.postgresql.org/docs/current/textsearch-controls.html#TEXTSEARCH-RANKING) but for now we'll simply use `title`, `original_title`, and `overview` as they are.
|
||||
|
||||
```sql
|
||||
select
|
||||
select
|
||||
* from movies
|
||||
where doc_en @@ websearch_to_tsquery('english', 'Avengers');
|
||||
where doc_en @@ websearch_to_tsquery('english', 'Avengers');
|
||||
```
|
||||
|
||||
|
||||
## Comparable FTS engines
|
||||
|
||||
Now that we've got a grasp on what FTS is and how it's used in Postgres, let's discuss a few of the alternative solutions Postgres can be compared to.
|
||||
@@ -156,27 +156,27 @@ For our experiments we'll use SQLite FTS5.
|
||||
|
||||
### TypeSense
|
||||
|
||||
[TypeSense](https://typesense.org/) is a relatively new project that focuses on *lightning fast*, typo-tolerant open source search. Typesense boasts a high performance C++ codebase, promising nearly instant search results.
|
||||
[TypeSense](https://typesense.org/) is a relatively new project that focuses on _lightning fast_, typo-tolerant open source search. Typesense boasts a high performance C++ codebase, promising nearly instant search results.
|
||||
|
||||
TypeSense provides many demos with example datasets available for query, which you can try out:
|
||||
|
||||
- [32MM songs from MusicBrainz](https://songs-search.typesense.org/)
|
||||
- [28M books from OpenLibrary](https://books-search.typesense.org/)
|
||||
- [XKCD comic search by topic](https://xkcd-search.typesense.org/)
|
||||
- [32MM songs from MusicBrainz](https://songs-search.typesense.org/)
|
||||
- [28M books from OpenLibrary](https://books-search.typesense.org/)
|
||||
- [XKCD comic search by topic](https://xkcd-search.typesense.org/)
|
||||
|
||||
Typesense quite ambitiously brands itself as the "Algolia" and "Elasticsearch" alternative, offering to solve your search problems without requiring a PhD.
|
||||
|
||||
### MeiliSearch
|
||||
|
||||
[MeiliSearch](https://www.meilisearch.com/) is a new search engine written from the ground up in [Rust](https://rust-lang.org/), which also promises *lightning fast* searches, flexibility, and typo tolerance.
|
||||
[MeiliSearch](https://www.meilisearch.com/) is a new search engine written from the ground up in [Rust](https://rust-lang.org/), which also promises _lightning fast_ searches, flexibility, and typo tolerance.
|
||||
|
||||
MeiliSearch has [extensive documentation](https://docs.meilisearch.com/), boasts an easy to use yet extendable architecture, and is well supported by a large community and team.
|
||||
|
||||
### OpenSearch
|
||||
|
||||
AWS [OpenSearch](https://opensearch.org/) is AWS's answer to ElasticSearch's license changes. While opinions are varied, ElasticSearch built a great piece (formerly Apache 2.0 licensed) piece of software, and the AWS fork represents at least a *stale* measure of ElasticSearch's capabilities.
|
||||
AWS [OpenSearch](https://opensearch.org/) is AWS's answer to ElasticSearch's license changes. While opinions are varied, ElasticSearch built a great piece (formerly Apache 2.0 licensed) piece of software, and the AWS fork represents at least a _stale_ measure of ElasticSearch's capabilities.
|
||||
|
||||
OpenSearch is the *same* as ElasticSearch (since the fork) but as it's license is much more permissive, it's the easy choice as a comparison to Postgres.
|
||||
OpenSearch is the _same_ as ElasticSearch (since the fork) but as it's license is much more permissive, it's the easy choice as a comparison to Postgres.
|
||||
|
||||
In production make sure to evaluate ElasticSearch versus OpenSearch (or other solutions) sufficiently for your use case.
|
||||
|
||||
@@ -189,7 +189,7 @@ Any search engine has two jobs:
|
||||
- Ingest information (usually referred to as "documents")
|
||||
- Return documents that match user queries
|
||||
|
||||
Ingesting information *can* be important, but "search engine performance" usually refers to the second step - taking queries and outputting results.
|
||||
Ingesting information _can_ be important, but "search engine performance" usually refers to the second step - taking queries and outputting results.
|
||||
|
||||
That is to say, the primary concern is assuming a cluster already has proper data loaded on to it, how fast can it deliver the relevant results for a user-submitted query?
|
||||
|
||||
@@ -213,15 +213,14 @@ Each system ingests data slightly differently, but with a little scripting we ca
|
||||
|
||||
[The code](https://github.com/VADOSWARE/fts-benchmark) contains a scheme of "drivers" which are minimal ES6 modules.
|
||||
|
||||
Here's an example of the `ingest` function used for Postgres (with `[slonik](https://github.com/gajus/slonik)` as our DB driver):
|
||||
Here's an example of the `ingest` function used for Postgres (with [`slonik`](https://github.com/gajus/slonik) as our DB driver):
|
||||
|
||||
```js
|
||||
```
|
||||
// Ingesting search documents
|
||||
async function ingest({ document, pool }) {
|
||||
// Ignore rows without proper release dates
|
||||
if (document.release_date.trim().length === 0) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
await pool.query(sql`
|
||||
@@ -246,9 +245,10 @@ on conflict (id) do update set
|
||||
overview=${document.overview},
|
||||
released_at=${new Date(document.release_date).toISOString()}::timestamptz
|
||||
;
|
||||
`);
|
||||
`)
|
||||
}
|
||||
```
|
||||
|
||||
The [repo](https://github.com/VADOSWARE/fts-benchmark/) contains the complete code for Postgres as well as the other search engines, written in much this same fashion.
|
||||
|
||||
### Picking a set of queries
|
||||
@@ -278,16 +278,16 @@ Similar to ingesting data, all the search engines take slightly different input
|
||||
|
||||
Here's what that script looks like for Postgres:
|
||||
|
||||
```sql
|
||||
```js
|
||||
// Querying search phrases
|
||||
async function query({ phrase, pool }) {
|
||||
const results = await pool.query(sql`
|
||||
select id, title
|
||||
from movies
|
||||
where fts_doc_en @@ websearch_to_tsquery('english', ${phrase}::text)
|
||||
`);
|
||||
const ids = results.rows.map(r => r.id);
|
||||
return ids;
|
||||
`)
|
||||
const ids = results.rows.map((r) => r.id)
|
||||
return ids
|
||||
}
|
||||
```
|
||||
|
||||
@@ -315,14 +315,14 @@ TIMING=true FTS_ENGINE=pg make query
|
||||
[info] successfully processed [10] lines
|
||||
```
|
||||
|
||||
Right off the bat we can see that Postgres is plenty quick! Some results are quite obvious - as you might expect, "love" is *quite* a common search result.
|
||||
Right off the bat we can see that Postgres is plenty quick! Some results are quite obvious - as you might expect, "love" is _quite_ a common search result.
|
||||
|
||||
## Results
|
||||
|
||||
With the `ingest` and `query` machinery defined for the search engines, it's easy to get some results.
|
||||
|
||||
| engine | query | number of results | latency (ms) | avg result latency (ms) |
|
||||
|---------------|--------------|-------------------|--------------|-------------------------|
|
||||
| ------------- | ------------ | ----------------- | ------------ | ----------------------- |
|
||||
| `pg` | "super hero" | `34` | `3.68` | `0.108` |
|
||||
| `pg` | "superhero" | `86` | `1.59` | `0.0184` |
|
||||
| `pg` | "superman" | `47` | `1.24` | `0.0264` |
|
||||
@@ -386,13 +386,13 @@ With the `ingest` and `query` machinery defined for the search engines, it's eas
|
||||
|
||||
## What does the data tell us?
|
||||
|
||||
There's a *lot* of tuning left undone here, but a few points stand out right away:
|
||||
There's a _lot_ of tuning left undone here, but a few points stand out right away:
|
||||
|
||||
- Even when consuming similar content, engines can produce different results, but generally ratios between queries on the same engine should be consistent.
|
||||
- Postgres FTS is quite close performance-wise to many other solutions, at least in their default configuration.
|
||||
- Only Typesense and MeiliSearch properly handled mis-spellings (the "suprman" query).
|
||||
- Typesense was relatively strict with matches compared to other engines.
|
||||
- OpenSearch was *very* fast with ingest, but also failed with the misspelling out of the box.
|
||||
- OpenSearch was _very_ fast with ingest, but also failed with the misspelling out of the box.
|
||||
- In-memory SQLite is by far the fastest, and PG isn't too far behind for this small data set.
|
||||
|
||||
## Feedback from the other products
|
||||
@@ -401,25 +401,23 @@ Since this is a benchmark, we felt it was fair to give the other products an opp
|
||||
|
||||
### Meilisearch
|
||||
|
||||
> I agree that using PG in simple use cases is an excellent way to be pragmatic.
|
||||
Unfortunately, PG will never be able to offer you a search experience like the one you could have with Meilisearch. Meilisearch has perfect management of typos and searches by prefix, allowing a search at each keystroke. Enhanced relevancy with rules such as the number of words present, the importance of the attribute, the proximity of the terms searched in the document, and custom parameters. The ability to mix textual, geographic, and facet searches. Meilisearch automatically understands almost all languages with the possibility of having synonyms and stop words. Meilisearch has an incredible performance even on large volumes of data, and no backend proxy is needed, thanks to complete API Key management.
|
||||
>
|
||||
> I agree that using PG in simple use cases is an excellent way to be pragmatic.
|
||||
> Unfortunately, PG will never be able to offer you a search experience like the one you could have with Meilisearch. Meilisearch has perfect management of typos and searches by prefix, allowing a search at each keystroke. Enhanced relevancy with rules such as the number of words present, the importance of the attribute, the proximity of the terms searched in the document, and custom parameters. The ability to mix textual, geographic, and facet searches. Meilisearch automatically understands almost all languages with the possibility of having synonyms and stop words. Meilisearch has an incredible performance even on large volumes of data, and no backend proxy is needed, thanks to complete API Key management.
|
||||
|
||||
*Quentin de Quelen, Meilisearch CEO*
|
||||
_Quentin de Quelen, Meilisearch CEO_
|
||||
|
||||
### Typesense
|
||||
|
||||
> Some more context around Typesense's behavior: how loose or exact matches are done are configurable in Typesense. For eg, you can configure the number of typos that are accounted for (num_typos), and when typo tolerance should kick-in, only if a configurable amount of results are not found (typo_tokens_treshold). For multi keyword searches, you can configure Typesense to expand the search by dropping some of the keywords, until at least a configurable number of search results are found (drop_tokens_threshold).
|
||||
We've chosen specifically to keep the search tight because we received feedback from users that some results were confusing if we let the defaults be too loose.
|
||||
>
|
||||
> We've chosen specifically to keep the search tight because we received feedback from users that some results were confusing if we let the defaults be too loose.
|
||||
|
||||
*Jason Bosco, Typesense CEO*
|
||||
_Jason Bosco, Typesense CEO_
|
||||
|
||||
## Wrap-up
|
||||
|
||||
While this certainly isn't "big data", it looks like Postgres has very much held it's own, even without too much wrangling/tuning. Postgres is clearly more than capable of delivering *good enough* search for this very basic case.
|
||||
While this certainly isn't "big data", it looks like Postgres has very much held it's own, even without too much wrangling/tuning. Postgres is clearly more than capable of delivering _good enough_ search for this very basic case.
|
||||
|
||||
Special purpose systems are expected to out-perform a more general system like Postgres, but clearly result quality and search speed provided Postgres is likely to be *good enough* for many use cases, and is “complexity neutral” -- no new systems needed!
|
||||
Special purpose systems are expected to out-perform a more general system like Postgres, but clearly result quality and search speed provided Postgres is likely to be _good enough_ for many use cases, and is “complexity neutral” -- no new systems needed!
|
||||
|
||||
## More Postgres resources
|
||||
|
||||
@@ -429,4 +427,3 @@ Special purpose systems are expected to out-perform a more general system like P
|
||||
- [Partial data dumps using Postgres Row Level Security](https://supabase.com/blog/partial-postgresql-data-dumps-with-rls)
|
||||
- [Postgres Views](https://supabase.com/postgresql-views)
|
||||
- [Realtime Postgres RLS on Supabase](https://supabase.com/blog/realtime-row-level-security-in-postgresql)
|
||||
|
||||
Reference in new issue
Block a user