Merge branch 'master' into feat/mfa

This commit is contained in:
Ivan Vasilov committed 2023-09-06 13:59:52 +02:00
commit d2cb7ddd72
93 files changed
+888 -404

No files matched your search

@@ -378,11 +378,13 @@ The OpenAI API supports [completion streaming](https://platform.openai.com/docs/
Storing embeddings in Postgres opens a world of possibilities. You can combine your search function with telemetry functions, add an user-provided feedback (thumbs up/down), and make your search feel more integrated with your products.
The [pgvector extension](https://supabase.com/docs/guides/ai/vector-columns) is available on all new Supabase projects today. If you want to try it out, launch a new Postgres database today: [database.new](https://database.new)
The [pgvector extension](https://supabase.com/docs/guides/ai/vector-columns) is available on all new Supabase projects today. To try it out, launch a new Postgres database: [database.new](https://database.new)
## More pgvector and ChatGPT resources
## More pgvector and AI resources
- [Supabase Clippy: ChatGPT for Supabase Docs](https://supabase.com/blog/chatgpt-supabase-docs)
- [A ChatGPT Plugins Template built with Supabase Edge Runtime](https://supabase.com/blog/building-chatgpt-plugins-template)
- [Template for building your own custom ChatGPT style doc search](https://github.com/supabase-community/nextjs-openai-doc-search)
- [Supabase + LangChain Starter Template](https://blog.langchain.dev/langchain-x-supabase/)
- [Hugging Face is now supported in Supabase](https://supabase.com/blog/hugging-face-supabase)
- [How to build ChatGPT Plugin from scratch with Supabase Edge Runtime](https://supabase.com/blog/building-chatgpt-plugins-template)
- [Docs pgvector: Embeddings and vector similarity](https://supabase.com/docs/guides/database/extensions/pgvector)
- [Choosing Compute Add-on for AI workloads](https://supabase.com/docs/guides/ai/choosing-compute-addon)
- [pgvector v0.5.0: Faster semantic search with HNSW indexes](https://supabase.com/blog/increase-performance-pgvector-hnsw)
@@ -123,7 +123,7 @@ Want to try Supabase Clippy? It's a hidden feature while in MVP - visit [supabas
## More pgvector and ChatGPT resources
- [pgvector docs]([https://supabase.com/blog/chatgpt-supabase-docs](https://supabase.com/docs/guides/getting-started/openai/vector-search)
- [AI docs](https://supabase.com/docs/guides/ai)
- [A ChatGPT Plugins Template built with Supabase Edge Runtime](https://supabase.com/blog/building-chatgpt-plugins-template)
- [Template for building your own custom ChatGPT style doc search](https://github.com/supabase-community/nextjs-openai-doc-search)
- [Supabase + LangChain Starter Template](https://blog.langchain.dev/langchain-x-supabase/)
@@ -274,8 +274,7 @@ In a next step you can add authentication to your plugin, let us know on [Twitte
## More AI resources
- [Hugging Face is now supported in Supabase](https://supabase.com/blog/hugging-face-supabase)
- [pgvector v0.5.0: Faster semantic search with HNSW indexes](https://supabase.com/blog/pgvector-v0-5-0-hnsw)
- [OpenAI ChatGPT Plugin docs](https://platform.openai.com/docs/plugins/introduction)
- [Advanced plugin template](https://github.com/openai/chatgpt-retrieval-plugin)
- Learn to build your own ChatGPT-style docs search with [Deno Fresh](https://deno.com/blog/build-chatgpt-doc-search-with-supabase-fresh) or [Next.js](https://vercel.com/templates/next.js/nextjs-openai-doc-search-starter)
- [How we builtChatGPT for the Supabase Docs](https://supabase.com/blog/chatgpt-supabase-docs).
- [Docs pgvector: Embeddings and vector similarity](https://supabase.com/docs/guides/database/extensions/pgvector)
@@ -1,6 +1,6 @@
---
title: 'pgvector 0.4.0 performance'
description: There's been lot of talk about pgvector performance lately, so we took some datasets and pushed pgvector to the limits to find out its strengths and limitations.
description: There's been a lot of talk about pgvector performance lately, so we took some datasets and pushed pgvector to the limits to find out its strengths and limitations.
categories:
- engineering
tags:
@@ -15,6 +15,13 @@ image: 2023-07-13-pgvector-performance/vector-benchmarks-og.jpeg
thumb: 2023-07-13-pgvector-performance/vector-benchmarks-thumb.jpeg
---
<div className="bg-gray-300 rounded-lg px-6 py-2 bold">
🚀 The incorporation of the HNSW index in pgvector v0.5.0 ensures lightning-fast vector searches. We tested it, benchmarked it, and shared everything.
[Read the new post](https://supabase.com/blog/increase-performance-pgvector-hnsw)
</div>
There are a few pgvector benchmarks floating around the internet, most recently a [pgvector vs Qdrant](https://nirantk.com/writing/pgvector-vs-qdrant/) comparison by NirantK. We wanted to reproduce (or improve!) the results.
There is an obvious bias here: we're a Postgres company. It's not our goal to prove that pgvector is better than Qdrant for running vector workloads. From everything we hear about Qdrant, it's fantastic.
@@ -23,7 +30,7 @@ Our goals in this article are:
1. To show the strengths and limitations of the _current version_ of pgvector.
2. Highlight some improvements that are coming to pgvector.
3. Prove to you that it's completely viable for production workloads and give you some tips on using it at scale. We'll show you how to run 1 million Open AI embeddings at ~1800 requests per second with 91% precision, or 670 requests per second with 98% precision.
3. Prove to you that it's completely viable for production workloads and give you some tips on using it at scale. We'll show you how to run 1 million Open AI embeddings at ~1800 queries per second with 91% accuracy, or 670 queries per second with 98% accuracy.
## Benchmark Methodology
@@ -32,7 +39,7 @@ We've used the [ANN Benchmarks](https://github.com/erikbern/ann-benchmarks) meth
The key elements are:
- **Helper scripts:** a Python test runner which is responsible for data upload, index creation, and query execution. This uses [qdrant's vector-db-benchmark](https://github.com/qdrant/vector-db-benchmark) repo. The “engine” in this repo uses [Vecs](https://github.com/supabase/vecs), a Python client for pgvector.
- **Runtime:** Each test runs for at least 30-40 minutes and included a series of experiments executed at various concurrency levels. This process allowed us to gauge the engine's performance under different load types. Subsequently, we averaged the results.
- **Runtime:** Each test runs for at least 30-40 minutes and includes a series of experiments executed at various concurrency levels. This process allowed us to gauge the engine's performance under different load types. Subsequently, we averaged the results.
- **Pre-warming RAM:** We executed 10,000 to 50,000 “warm-up” queries before each benchmark, matching the number of probes as the benchmark. Additionally, we executed about 1,000 queries with probes ranging from three to ten times the benchmark's probes. Both of these help with RAM utilization.
<div>
@@ -87,7 +94,7 @@ Let's start with NirantK's results as a baseline:
/>
</div>
They aren't very flattering! Repeating our statements above, these benchmarks are using the defaults for both engines. Our goal now is to replicate the results, and then see what improvements need to be made as developers scale up their workload.
They aren't very flattering! Repeating our statements above, these benchmarks use the defaults for both engines. Our goal now is to replicate the results, and then see what improvements need to be made as developers scale up their workload.
## Results
@@ -111,8 +118,8 @@ The resulting figures were significantly different after these changes.
With the changes above and probes set to 10, pgvector was faster and more accurate:
- precision@10 of 0.91
- RPS (requests per second) of 380
- accuracy@10 of 0.91
- QPS (queries per second) of 380
<div>
<img
@@ -129,10 +136,10 @@ With the changes above and probes set to 10, pgvector was faster and more accura
### PROBES = 40
If we increase the probes from 10 to 40, pgvector was not just substantially faster but also boasted almost the same precision as Qdrant:
If we increase the probes from 10 to 40, pgvector was not just substantially faster but also boasted almost the same accuracy as Qdrant:
- precision@10 of 0.98
- RPS of 140
- accuracy@10 of 0.98
- QPS of 140
<div>
<img
@@ -149,7 +156,7 @@ If we increase the probes from 10 to 40, pgvector was not just substantially fas
### Scaling the database
Another key takeaway is that the performance scales predictably with the size of the database. For instance, a 4XL instance achieves precision@10 of 0.98 and RPS of 270 with probes set to 40. Moreover, an 8XL compute add-on analogously obtains precision@10 of 0.98 and an RPS of 470, surpassing the results of Qdrant.
Another key takeaway is that the performance scales predictably with the size of the database. For instance, a 4XL instance achieves accuracy@10 of 0.98 and QPS of 270 with probes set to 40. Moreover, an 8XL compute add-on analogously obtains accuracy@10 of 0.98 and an QPS of 470, surpassing the results of Qdrant.
<div className="bg-gray-300 rounded-lg px-6 py-2 italic">
@@ -170,13 +177,13 @@ The Qdrant benchmark uses “default” configuration and is in not indicative o
/>
</div>
Although more compute is required to match Qdrant's precision and RPS levels concurrently, this is still a satisfying outcome. It means that it's not a _necessity_ to use another vector database. You can put everything in Postgres to lower your operational complexity.
Although more compute is required to match Qdrant's accuracy and QPS levels concurrently, this is still a satisfying outcome. It means that it's not a _necessity_ to use another vector database. You can put everything in Postgres to lower your operational complexity.
### Final results: pgvector performance
Putting it all together, we find that we can predictably scale our database to match the performance we need.
With a 64-core, 256 GB server we achieve ~1800 RPS and 0.91 precision. This is for pgvector 0.4.0, and we've heard that the latest version (0.4.4) already has significant improvements. We'll release those benchmarks as soon as we have them.
With a 64-core, 256 GB server we achieve ~1800 QPS and 0.91 accuracy. This is for pgvector 0.4.0, and we've heard that the latest version (0.4.4) already has significant improvements. We'll release those benchmarks as soon as we have them.
<div>
<img
@@ -201,7 +208,7 @@ Another way to improve performance without throwing more compute would be to inc
We ran a test to measure the impact of list size: we uploaded 90,000 vectors from the Wikipedia dataset and then queried 10,000 vectors from the same dataset. The documentation recommends to use `lists` constant of `number of vectors / 1000`. In this case, it would be 90.
But as our experiment shows, we can improve RPS if we increase `lists` (i.e. with more lists in the index we need to get less index data to get the same precision). So for 95% precision, we can take any of:
But as our experiment shows, we can improve QPS if we increase `lists` (i.e. with more lists in the index we need to get less index data to get the same accuracy). So for 95% accuracy, we can take any of:
- 3% of index data = 270 lists
- 6% of index data = 90 lists
@@ -249,11 +256,11 @@ SET maintenance_work_mem TO '7168 MB';
Keeping in mind that the overall index size is almost the same, and only index build time increases, we can say that it's better to use more lists for better select queries speed.
### Real data has higher precision than random data
### Real data has higher accuracy than random data
Embeddings created from “real” data are more likely to be clustered together, whereas random embeddings are more likely to be scattered. In other words, real embeddings are very far from being randomly distributed. This might seem obvious, but it's an important call-out for benchmarks.
Embeddings generated for similarity search using “real world data” will be more correlated, so the precision will be higher as well. You can see the difference in this chart using 10,000 Wikipedia embeddings, vs 10,000 randomly-generated embeddings:
Embeddings generated for similarity search using “real world data” will be more correlated, so the accuracy will be higher as well. You can see the difference in this chart using 10,000 Wikipedia embeddings, vs 10,000 randomly-generated embeddings:
<div>
<img
@@ -280,7 +287,7 @@ First, a few generic tips which you can pick and choose from:
2. Prefer `inner-product` to `L2` or `Cosine` distances if your vectors are normalized (like `text-embedding-ada-002`). If embeddings are not normalized, `Cosine` distance should give the best results with an index.
3. **Pre-warm your database.** Implement the warm-up technique we described earlier before transitioning to production.
4. **Establish your workload.** Increasing the lists constant for the pgvector index can accelerate your queries (at the expense of a slower build). For instance, for benchmarks with OpenAI embeddings, we employed a `lists` constant of 2000 (`number of vectors / 500`) as opposed to the suggested 1000 (`number of vectors / 1000`).
5. **Benchmark your own specific workloads.** Doing this during cache warm-up helps gauge the best value for the `probes` constant, balancing precision with RPS.
5. **Benchmark your own specific workloads.** Doing this during cache warm-up helps gauge the best value for the `probes` constant, balancing accuracy with QPS.
### Going into production
@@ -288,12 +295,12 @@ Before running your pgvector workload in production, here are a few steps you ca
1. Over-provision RAM during preparation. You can scale down in step `5`, but it's better to start with a larger size to get the best results for RAM requirements. (We'd recommend at least 8XL if you're using Supabase.)
2. Upload your data to the database. If you use [`vecs`](https://supabase.com/docs/guides/ai/python/api) library, it will automatically generate an index with default parameters.
3. Run a benchmark using randomly generated queries and see the results. Again, you can use `vecs` library with the `ann-benchmarks` tool. Do it with probes set to 10 (default) and then with probes set to 100 or more, so RPS will be lower than 10.
3. Run a benchmark using randomly generated queries and see the results. Again, you can use `vecs` library with the `ann-benchmarks` tool. Do it with probes set to 10 (default) and then with probes set to 100 or more, so QPS will be lower than 10.
4. Take a look at the RAM usage, and save it as a note for yourself. You would likely want to use compute add-on in the future that would have the same amount of RAM as used at the moment (both actual RAM usage and RAM used for cache and buffers).
5. Scale down your compute add-on to the one that would have the same amount of RAM as used at the moment.
6. Repeat step 3. to load the data into RAM. You should see that RPS is increased on subsequent runs, and stop when it no longer increases. Then repeat the benchmark with probes set to a higher value as well if you didn't do it before on that compute add-on size.
7. Run a benchmark using real queries and see the results. You can use `vecs` library for that as well with `ann-benchmarks` tool. Do it with probes set to 10 (default) and then gradually increase/decrease probes value until you see that both precision and RPS match your requirements.
8. If you want higher RPS and you don't expect to have frequent inserts and reindexing, you can increase `lists` constantly. You have to rebuild the index with higher lists value and repeat steps 6-7 to find the best combination of `lists` and `probes` constants to achieve the best RPS and precision values. Higher `lists` mean that index will build slower, but you can achieve better RPS and precision. Higher probes mean that select queries will be slower, but you can achieve better precision.
6. Repeat step 3. to load the data into RAM. You should see that QPS is increased on subsequent runs, and stop when it no longer increases. Then repeat the benchmark with probes set to a higher value as well if you didn't do it before on that compute add-on size.
7. Run a benchmark using real queries and see the results. You can use `vecs` library for that as well with `ann-benchmarks` tool. Do it with probes set to 10 (default) and then gradually increase/decrease probes value until you see that both accuracy and QPS match your requirements.
8. If you want higher QPS and you don't expect to have frequent inserts and reindexing, you can increase `lists` constantly. You have to rebuild the index with higher lists value and repeat steps 6-7 to find the best combination of `lists` and `probes` constants to achieve the best QPS and accuracy values. Higher `lists` mean that index will build slower, but you can achieve better QPS and accuracy. Higher probes mean that select queries will be slower, but you can achieve better accuracy.
## The pgvector roadmap
@@ -301,7 +308,8 @@ pgvector is still early in development. As with any open source tool, it needs t
What's next on the roadmap? Andrew has an impressive list of features [planned for v0.5.0](https://github.com/pgvector/pgvector/issues/27):
- Adding HNSW: an index with better speed & precision than IVFFlat (at a higher memory cost)
✅ [Adding HNSW](https://supabase.com/blog/increase-performance-pgvector-hnsw): an index with better speed & accuracy than IVFFlat (at a higher memory cost)
- Product quantization: better storage for IVFFLAT, improving speed and recall
- Parallel index builds: building your IVFFLAT indexes will be much faster
@@ -311,3 +319,4 @@ What's next on the roadmap? Andrew has an impressive list of features [planned f
- [Docs pgvector: Embeddings and vector similarity](https://supabase.com/docs/guides/database/extensions/pgvector)
- [pgvector vs Qdrant](https://nirantk.com/writing/pgvector-vs-qdrant)
- [Choosing Compute Add-on for AI workloads](https://supabase.com/docs/guides/ai/choosing-compute-addon)
- [pgvector v0.5.0: Faster semantic search with HNSW indexes](https://supabase.com/blog/pgvector-v0-5-0-hnsw)
@@ -25,7 +25,7 @@ At Supabase, we support storing embeddings in Postgres using the [pgvector](http
## Challenges with pgvector
Without indexes, pgvector performs a full table scan when you run a similarity query. This means distance has to be computed against every row in your table. This is manageable at a small scale, but becomes problematic as your table grows.
Without indexes, pgvector performs a full table scan when you run a similarity query. This means distance has to be computed against every row in your table. This is manageable at a small scale but becomes problematic as your table grows.
To solve this, pgvector offers indexes. Indexes reorganize the data into data structures that exploit internal structure and enable approximate similarity search without referring to every record. Currently, pgvector supports an IVF index, with HNSW expected in the next release.
@@ -35,7 +35,7 @@ IVF [indexes](https://supabase.com/docs/guides/ai/managing-indexes) work by clus
98% of our customers are generating text embeddings using OpenAI's `text-embedding-ada-002` model. At an initial glance, there's good reason for this - these embeddings perform quite well for information retrieval and are economical to produce. `text-embedding-ada-002` produces vectors with 1536 dimensions which is among the largest in the industry. IVF indexes help address some scaling challenges, but there are still some pitfalls.
First, vectors are large. A 1536 dimensional vector is ~12.3 kilobytes. Scaling that up to 1M vectors, the raw data tops 11 gigabytes. Experienced SQL users know that for best performance, indexes should fit within system memory. Moreover unlike traditional workloads, there is a significant compute component when performing vector similarity queries.
First, vectors are large. A 1536 dimensional vector is ~12.3 kilobytes. Scaling that up to 1M vectors, the raw data tops 11 gigabytes. Experienced SQL users know that for best performance, indexes should fit within system memory. Moreover, unlike traditional workloads, there is a significant compute component when performing vector similarity queries.
In the real-world it's common for an index to reduce the number of distance computations needed to estimate nearest neighbors from 100% of the dataset to 5-20%. At 1M records, that's still 50k-200k distance calculations being performed for a single query. Given how different the [resource requirements](https://supabase.com/docs/guides/ai/choosing-compute-addon) are to support heavy vector workloads, it's not surprising that one of the most common issues we see is significant under provisioning of hardware.
@@ -54,7 +54,7 @@ Text embeddings are one of the most common types of embeddings today. Our friend
Models are ranked per task by taking their average score produced over each task's datasets. Each model also has an overall (general purpose) score, calculated by taking the average score produced across all datasets. You can find the results on their [MTEB leaderboard](https://huggingface.co/spaces/mteb/leaderboard).
It's worth pointing out that each model's dimension size has little-to-no correlation with its performance. In fact there are a number of models that perform comparably with`text-embedding-ada-002`, all of which produce embeddings with fewer dimensions than 1536.
It's worth pointing out that each model's dimension size has little-to-no correlation with its performance. In fact, there are a number of models that perform comparably with`text-embedding-ada-002`, all of which produce embeddings with fewer dimensions than 1536.
| Rank | Model | Dimensions | Average | Model Size (GB) |
| ---- | -------------------------------------------------------------------------------------------------- | ---------- | ------- | --------------- |
@@ -68,7 +68,7 @@ It's worth pointing out that each model's dimension size has little-to-no correl
## Benefits of fewer dimensions
What specifically do we gain when we have fewer dimensions? Faster queries and less RAM: Fewer dimensions means less computation while more of the dataset or index is able to fit in memory.
What specifically do we gain when we have fewer dimensions? Faster queries and less RAM: Fewer dimensions mean less computation while more of the dataset or index is able to fit in memory.
Take a look at dot product for example:
@@ -85,9 +85,9 @@ Take a look at dot product for example:
/>
</div>
Dot product is the product of each vector element pair summed together into a single result. Fewer dimensions in the vector means fewer calculations for every computed distance .
Dot product is the product of each vector element pair summed together into a single result. Fewer dimensions in the vector means fewer calculations for every computed distance.
We compared the performance of `text-embedding-ada-002` from OpenAI (1536 dimensions) with open-source `all-MiniLM-L6-v2` (384 dimensions) by measuring requests per second at a constant precision and configuration:
We compared the performance of `text-embedding-ada-002` from OpenAI (1536 dimensions) with open-source `all-MiniLM-L6-v2` (384 dimensions) by measuring queries per second at a constant accuracy and configuration:
- **Database size:** with 2vCPU (ARM) and 8GB RAM - `large` add-on for Supabase project.
- **Version:** Postgres v15 and pgvector v0.4.0
@@ -95,7 +95,7 @@ We compared the performance of `text-embedding-ada-002` from OpenAI (1536 dimens
- **Index:** The index was generated for `inner-product` (dot product) distance function with `lists=1000`.
- **Process:** We followed [our optimization guide and tips](https://supabase.com/docs/guides/ai/going-to-prod#performance-tips-when-using-indexes).
We observed pgvector with `all-MiniLM-L6-v2` outperforming `text-embedding-ada-002` by 78% when holding the precision@10 constant at 0.99. This gap increases as you lower the precision. Postgres was using just 4GB of RAM with 384d vectors generated by `all-MiniLM-L6-v2` compared to 7.5GB with `text-embedding-ada-002`.
We observed pgvector with `all-MiniLM-L6-v2` outperforming `text-embedding-ada-002` by 78% when holding the accuracy@10 constant at 0.99. This gap increases as you lower the accuracy. Postgres was using just 4GB of RAM with 384d vectors generated by `all-MiniLM-L6-v2` compared to 7.5GB with `text-embedding-ada-002`.
<div>
<img
@@ -123,7 +123,7 @@ We observed pgvector with `all-MiniLM-L6-v2` outperforming `text-embedding-ada-0
/>
</div>
After that, we decided to try the recently published [gte-small](https://huggingface.co/thenlper/gte-small) (also 384 dimensions), and the results were even more astonishing. With `gte-small`, we could set `probes=10` to achieve the same level of `precision@10 = 0.99`. Consequently, we observed more than a 200% improvement in requests per second for pgvector with embeddings generated by `gte-small` compared to `all-MiniLM-L6-v2`.
After that, we decided to try the recently published [gte-small](https://huggingface.co/thenlper/gte-small) (also 384 dimensions), and the results were even more astonishing. With `gte-small`, we could set `probes=10` to achieve the same level of `accuracy@10 = 0.99`. Consequently, we observed more than a 200% improvement in queries per second for pgvector with embeddings generated by `gte-small` compared to `all-MiniLM-L6-v2`.
<div>
<img
@@ -148,7 +148,7 @@ Ultimately the choice of embedding model will depend on your specific use case a
- **Dimension size:** What is the operational cost of storing and comparing embeddings?
- **Language:** Which written languages do you need to support? Many of the smaller models only support English.
The goal is to maximize similarity performance and sequence length, while minimizing model size and dimension size. Notably, if we can find a model that performs well while producing as few dimensions as possible, we can immediately improve many of the above challenges developers are facing with pgvector.
The goal is to maximize similarity performance and sequence length while minimizing model size and dimension size. Notably, if we can find a model that performs well while producing as few dimensions as possible, we can immediately improve many of the above challenges developers are facing with pgvector.
For example [gte-small](https://huggingface.co/thenlper/gte-small), a model recently trained by the [Alibaba DAMO Academy,](https://damo.alibaba.com/) produces only 384 dimensions while ranking higher than `text-embedding-ada-002` on the MTEB leaderboard.
@@ -162,10 +162,18 @@ It's important to keep in mind that using an alternative embedding model doesn't
While dimensionality reduction techniques like Principal Component Analysis ([PCA](https://en.wikipedia.org/wiki/Principal_component_analysis)) or t-Distributed Stochastic Neighbor Embedding ([t-SNE](https://en.wikipedia.org/wiki/T-distributed_stochastic_neighbor_embedding)) may seem like a good alternative to trim down the size of text embedding vectors, there is some risk to consider.
First, these tools may not appropriately model the relationships in high dimensional space. Both PCA and t-SNE are likely to oversimplify the multivariate relationships among different dimensions, leading to loss of important semantic information. This is particularly the case with PCA, which assumes linearity and would fail to preserve nonlinear interactions found in high-dimensional text embeddings.
First, these tools may not appropriately model the relationships in high-dimensional space. Both PCA and t-SNE are likely to oversimplify the multivariate relationships among different dimensions, leading to the loss of important semantic information. This is particularly the case with PCA, which assumes linearity and would fail to preserve nonlinear interactions found in high-dimensional text embeddings.
Additionally, the validity of the dimensionality reduction models could change over time if the distribution of text data shifts. If the model was trained on older data, it might not accurately reflect or accommodate these shifts. For example, if your company gets a large new customer in the legal field and your embedding dataset contained no legal documents when the dimensionality reduction model was trained, otherwise exploitable structure may be destructively compressed. Both PCA and t-SNE are not inherently adaptable to such changes, which could result in an increasingly poor fit over time. In summary, the derived low-dimensional representations may not maintain semantic coherence of the original high-dimensional vectors.
## What's next?
At Supabase we are working on ways to make it easy to generate embeddings using open source, high performance, and low dimension embedding models. Stay tuned for [Launch Week 8](/launch-week) next week!
## More pgvector and AI resources
- [Hugging Face is now supported in Supabase](https://supabase.com/blog/hugging-face-supabase)
- [How to build ChatGPT Plugin from scratch with Supabase Edge Runtime](https://supabase.com/blog/building-chatgpt-plugins-template)
- [Docs pgvector: Embeddings and vector similarity](https://supabase.com/docs/guides/database/extensions/pgvector)
- [Choosing Compute Add-on for AI workloads](https://supabase.com/docs/guides/ai/choosing-compute-addon)
- [pgvector v0.5.0: Faster semantic search with HNSW indexes](https://supabase.com/blog/increase-performance-pgvector-hnsw)
@@ -0,0 +1,266 @@
---
title: 'pgvector v0.5.0: Faster semantic search with HNSW indexes'
description: Increase performance in pgvector using HNSW indexes
author: gregnr
categories:
- engineering
tags:
- AI
- performance
- postgres
- planetpg
date: '2023-09-06'
toc_depth: 3
image: 2023-09-06-increase-performance-pgvector-hnsw/pgvector-v0-5-0-og.png
thumb: 2023-09-06-increase-performance-pgvector-hnsw/pgvector-v0-5-0-thumb.png
---
_Contributed by: [Egor Romanov](https://github.com/egor-romanov)_
[Supabase Vector](https://supabase.com/vector) is about to get a lot faster. Starting today, new Supabase databases will ship with pgvector v0.5.0 which adds a new type of index: Hierarchical Navigable Small World (HNSW).
HNSW is an algorithm for approximate nearest neighbor search, often used in high-dimensional spaces like those found in embeddings.
With this update, you can take advantage of the new HNSW index on your column using the following:
```sql
-- Add a HNSW index for the inner product distance function
CREATE INDEX ON documents
USING hnsw (embedding vector_ip_ops);
```
<aside className="bg-gray-300 rounded-lg px-6 py-2 bold">
💡 If you have an existing database that was created before this release, please reach out to [support](https://supabase.com/dashboard/support/new) and we'll assist you in an upgrade to pgvector v0.5.0.
</aside>
## How does HNSW work?
Compared to inverted file (IVF) indexes which use [clusters](https://supabase.com/docs/guides/ai/managing-indexes#ivfflat-indexes) to approximate nearest-neighbor search, HNSW uses proximity graphs (graphs connecting nodes based on distance between them). To understand HNSW, we can break it down into 2 parts:
- **Hierarchical (H):** The algorithm operates over multiple layers
- **Navigable Small World (NSW):** Each vector is a node within a graph and is connected to several other nodes
### Hierarchical
The hierarchical aspect of HNSW builds off of the idea of skip lists.
Skip lists are multi-layer linked lists. The bottom layer is a regular linked list connecting an ordered sequence of elements. Each new layer above removes some elements from the underlying layer (based on a fixed probability), producing a sparser subsequence that “skips” over elements.
<div>
<img
alt="visual of an example skip list (light)"
className="dark:hidden"
src="/images/blog/2023-09-06-increase-performance-pgvector-hnsw/pgvector-v0-5-0-skip-list--light.png"
/>
<img
alt="visual of an example skip list (dark)"
className="hidden dark:block"
src="/images/blog/2023-09-06-increase-performance-pgvector-hnsw/pgvector-v0-5-0-skip-list--dark.png"
/>
</div>
When searching for an element, the algorithm begins at the top layer and traverses its linked list horizontally. If the target element is found, the algorithm stops and returns it. Otherwise if the next element in the list is greater than the target (or NIL), the algorithm drops down to the next layer below. Since each layer below is less sparse than the layer above (with the bottom layer connecting all elements), the target will eventually be found. Skip lists offer O(log n) average complexity for both search and insertion/deletion.
### Navigable Small World
A navigable small world (NSW) is a special type of proximity graph that also includes long-range connections between nodes. These long-range connections support the “small world” property of the graph, meaning almost every node can be reached from any other node within a few hops. Without these additional long-range connections, many hops would be required to reach a far-away node.
<Img
alt="visual of an example navigable small world graph"
src="/images/blog/2023-09-06-increase-performance-pgvector-hnsw/pgvector-v0-5-0-nsw.png"
/>
The “navigable” part of NSW specifically refers to the ability to logarithmically scale the greedy search algorithm on the graph, an algorithm that attempts to make only the locally optimal choice at each hop. Without this property, the graph may still be considered a small world with short paths between far-away nodes, but the greedy algorithm tends to miss them. Greedy search is ideal for NSW because it is quick to navigate and has low computational costs.
### **Hierarchical +** Navigable Small World
HNSW combines these two concepts. From the hierarchical perspective, the bottom layer consists of a NSW made up of short links between nodes. Each layer above “skips” elements and creates longer links between nodes further away from each other.
Just like skip lists, search starts at the top layer and works its way down until it finds the target element. However instead of comparing a scalar value at each layer to determine whether or not to descend to the layer below, a multi-dimensional distance measure (such as Euclidean distance) is used.
## HNSW performance: 1536 dimensions
To understand the performance improvements that HNSW offers, we decided to expand upon our previous benchmarks and include results for the HNSW index in addition to IVF and compare the queries per second (QPS) for both.
### wikipedia-en-embeddings
In the first test, we used [224,482 vectors by OpenAI](https://huggingface.co/datasets/Supabase/wikipedia-en-embeddings) (1536 dimensions). You can find our previous benchmark with additional information on how vector dimensions may affect performance in [pgvector: Fewer dimensions are better](https://supabase.com/blog/pgvector-performance).
In this test, we used a Supabase project with a large compute add-on (2-core CPU and 8GB RAM) and built the HNSW index with the following parameters:
- `m`: 32
- `ef_construction`: 64
<div>
<img
alt="wikipedia embeddings comparing ivfflat and hnsw queries-per-second (light)"
className="dark:hidden"
src="/images/blog/2023-09-06-increase-performance-pgvector-hnsw/wikipedia-ivfflat-vs-hnsw--light.png"
/>
<img
alt="wikipedia embeddings comparing ivfflat and hnsw queries-per-second (dark)"
className="hidden dark:block"
src="/images/blog/2023-09-06-increase-performance-pgvector-hnsw/wikipedia-ivfflat-vs-hnsw--dark.png"
/>
</div>
HNSW has 3 times better performance than IVFFlat and with better accuracy.
### dbpedia-entities-openai-1M
Next, we took the setup from our benchmarks with [1,000,000 vectors by OpenAI](https://huggingface.co/datasets/KShivendu/dbpedia-entities-openai-1M) (1536 dimensions). If you want to find out more about pgvector 0.4.0 IVFFlat performance and our load testing methodology, check out [pgvector 0.4.0 performance](https://supabase.com/blog/pgvector-performance) blogpost.
Here we used the Supabase project with a 2XL compute add-on (8-core CPU and 32GB RAM) and built the HNSW index with the same parameters:
- `m`: 24
- `ef_construction`: 56
<div>
<img
alt="dbpedia embeddings comparing ivfflat and hnsw queries-per-second (light)"
className="dark:hidden"
src="/images/blog/2023-09-06-increase-performance-pgvector-hnsw/dbpedia-ivfflat-vs-hnsw--light.png"
/>
<img
alt="dbpedia embeddings comparing ivfflat and hnsw queries-per-second (dark)"
className="hidden dark:block"
src="/images/blog/2023-09-06-increase-performance-pgvector-hnsw/dbpedia-ivfflat-vs-hnsw--dark.png"
/>
</div>
When we maintain fixed HNSW build parameters, we can adjust the select query parameter `ef_search` to balance query speed and accuracy. To achieve accuracy@10 of 0.98, we increased it from the default 40 to 100. For accuracy@10 of 0.99, we further raised it to 250. Remarkably, HNSW demonstrated over six times better performance while maintaining the same level of accuracy. With higher accuracy@10 of 0.99 HNSW even outperforms [qdrant](https://nirantk.com/writing/pgvector-vs-qdrant/) on equivalent compute resources.
<div>
<img
alt="dbpedia embeddings comparing qdrant and hnsw queries-per-second (light)"
className="dark:hidden"
src="/images/blog/2023-09-06-increase-performance-pgvector-hnsw/dbpedia-qdrant-vs-hnsw--light.png"
/>
<img
alt="dbpedia embeddings comparing qdrant and hnsw queries-per-second (dark)"
className="hidden dark:block"
src="/images/blog/2023-09-06-increase-performance-pgvector-hnsw/dbpedia-qdrant-vs-hnsw--dark.png"
/>
</div>
### Scaling the database
Performance scales predictably with the size of the database for the HNSW index, just as [it does for the IVFFlat index](https://supabase.com/blog/pgvector-performance#scaling-the-database). The difference in performance between IVFFlat and HNSW remains consistent after a compute upgrade.
<div>
<img
alt="dbpedia embeddings comparing ivfflat and hnsw queries-per-second using the 4XL compute addon (light)"
className="dark:hidden"
src="/images/blog/2023-09-06-increase-performance-pgvector-hnsw/dbpedia-ivfflat-vs-hnsw-4xl--light.png"
/>
<img
alt="dbpedia embeddings comparing ivfflat and hnsw queries-per-second using the 4XL compute addon (dark)"
className="hidden dark:block"
src="/images/blog/2023-09-06-increase-performance-pgvector-hnsw/dbpedia-ivfflat-vs-hnsw-4xl--dark.png"
/>
</div>
Switching to a 4XL compute add-on (with a 16-core CPU and 64GB of RAM) resulted in a 69% increase in QPS for an accuracy@10 of 0.99 compared to the 2XL instance.
<div>
<img
alt="dbpedia embeddings comparing hnsw queries-per-second using the 2XL vs 4XL compute addon (light)"
className="dark:hidden"
src="/images/blog/2023-09-06-increase-performance-pgvector-hnsw/dbpedia-hnsw-2xl-vs-4xl--light.png"
/>
<img
alt="dbpedia embeddings comparing hnsw queries-per-second using the 2XL vs 4XL compute addon (dark)"
className="hidden dark:block"
src="/images/blog/2023-09-06-increase-performance-pgvector-hnsw/dbpedia-hnsw-2xl-vs-4xl--dark.png"
/>
</div>
Furthermore, having 64GB of RAM is better suited for this dataset because Postgres uses approximately 30-35GB of RAM to achieve optimal performance. With a 4XL setup, you not only have the capacity to store 1,000,000 vectors but also the flexibility to accommodate additional data or increase the number of vectors as needed.
### HNSW build parameters
We conducted a small experiment to assess the impact of HNSW index parameters on accuracy and QPS. In this experiment, we utilized the same 4XL instance as in the previous test but modified the building parameters:
- `m`: 32
- `ef_construction`: 80
This adjustment enabled us to achieve the same level of accuracy@10 of 0.99 with an `ef_search` = 100 instead of the previous 250, resulting in a 35% increase in QPS.
<div>
<img
alt="dbpedia embeddings comparing hnsw queries-per-second using different build parameters (light)"
className="dark:hidden"
src="/images/blog/2023-09-06-increase-performance-pgvector-hnsw/dbpedia-hnsw-build-parameters--light.png"
/>
<img
alt="dbpedia embeddings comparing hnsw queries-per-second using different build parameters (dark)"
className="hidden dark:block"
src="/images/blog/2023-09-06-increase-performance-pgvector-hnsw/dbpedia-hnsw-build-parameters--dark.png"
/>
</div>
## Other HNSW features
In addition to the above query performance improvements, HNSW offers another advantage: you don't need to fully fill your table before building the index.
With IVFFlat indexes, the clusters (lists) are constructed based on the distribution of existing data in the table. This means that IVF indexes built on an empty table would produce completely suboptimal centers. This is why pgvector recommends building IVF indexes only once sufficient data exists in the table and rebuilding them any time the distribution of data changes significantly.
HNSW indexes use graphs which inherently are not affected by this limitation, so you are safe to create your HNSW index immediately after the table is created. As new data is added to the table, the index will be filled automatically and the index structure will remain optimal.
## Improvements to IVF indexes
IVFFlat indexes also saw some improvements in v0.5.0. Index build times are now significantly faster for large datasets thanks to 2 new improvements:
- Parallelization during the assignment build step (assigning records to lists)
- Switching from [double to float](https://github.com/pgvector/pgvector/pull/180) accuracy for select distance calculations which unlocks the [fused multiply-add](https://en.wikipedia.org/wiki/Multiply%E2%80%93accumulate_operation#Fused_multiply%E2%80%93add) instruction on CPUs
Below we compare the index build times between v0.4.0 and v0.5.0 for the inner product distance measure over 1,000,000 vectors on a Supabase project with 4XL compute add-on (with a 16-core CPU and 64GB of RAM).
- `lists = 1000`
<div>
<img
alt="dbpedia embeddings comparing ivfflat index build time between v0.4.0 and v0.5.0 using 1000 lists (light)"
className="dark:hidden"
src="/images/blog/2023-09-06-increase-performance-pgvector-hnsw/dbpedia-ivfflat-0-4-0-vs-0-5-0-lists-1000--light.png"
/>
<img
alt="dbpedia embeddings comparing ivfflat index build time between v0.4.0 and v0.5.0 using 1000 lists (dark)"
className="hidden dark:block"
src="/images/blog/2023-09-06-increase-performance-pgvector-hnsw/dbpedia-ivfflat-0-4-0-vs-0-5-0-lists-1000--dark.png"
/>
</div>
- `lists = 5000`
<div>
<img
alt="dbpedia embeddings comparing ivfflat index build time between v0.4.0 and v0.5.0 using 5000 lists (light)"
className="dark:hidden"
src="/images/blog/2023-09-06-increase-performance-pgvector-hnsw/dbpedia-ivfflat-0-4-0-vs-0-5-0-lists-5000--light.png"
/>
<img
alt="dbpedia embeddings comparing ivfflat index build time between v0.4.0 and v0.5.0 using 5000 lists (dark)"
className="hidden dark:block"
src="/images/blog/2023-09-06-increase-performance-pgvector-hnsw/dbpedia-ivfflat-0-4-0-vs-0-5-0-lists-5000--dark.png"
/>
</div>
The index build time has decreased by over 50%, and this ratio remains consistent when you adjust the index build parameters ([specifically, the `lists` value](https://supabase.com/blog/pgvector-performance#other-performance-factors)).
## When should you use HNSW vs IVF?
In most cases today, HNSW offers a more performant and robust index over IVFFlat. It's worth noting though that HNSW indexes will almost always be slower to build and use more memory than IVFFlat, so if your system is memory-constrained and you don't foresee the need to rebuild your index often, you may find IVFFlat to be more suitable. It's also worth noting that product quantization (compressing index entries for vectors) is expected for IVF in the next versions of pgvector which should significantly improve performance and lower resource requirements. Regardless of the type of index you use, we still recommend [reducing the number of dimensions](https://supabase.com/blog/fewer-dimensions-are-better-pgvector) in your embeddings when possible.
## Start using HNSW
All [new Supabase databases](https://database.new/) will automatically ship with pgvector v0.5.0 which includes the new HNSW indexes. If you have an existing database, please reach out to [support](https://supabase.com/dashboard/support/new) and we'll be more than happy to assist you with an upgrade.
## More pgvector and AI resources
- [How to build ChatGPT Plugin from scratch with Supabase Edge Runtime](https://supabase.com/blog/building-chatgpt-plugins-template)
- [Docs pgvector: Embeddings and vector similarity](https://supabase.com/docs/guides/database/extensions/pgvector)
- [Choosing Compute Add-on for AI workloads](https://supabase.com/docs/guides/ai/choosing-compute-addon)
- [pgvector: Fewer dimensions are better](https://supabase.com/blog/fewer-dimensions-are-better-pgvector)
@@ -57,6 +57,7 @@ function GithubExamples() {
<div className={'lg:-mr-32 lg:-ml-32'}>
<Swiper
style={{ overflow: 'visible' }}
loop={true}
initialSlide={3}
spaceBetween={0}
slidesPerView={4}
+3 -2
View File
@@ -12,6 +12,7 @@ import {
CollapsibleTrigger_Shadcn_,
CollapsibleContent_Shadcn_,
IconTriangle,
cn,
} from 'ui'
import ImageFadeStack from '~/components/ImageFadeStack'
import ZoomableImg from '~/components/ZoomableImg/ZoomableImg'
@@ -91,8 +92,8 @@ export default function mdxComponents(type?: 'blog' | 'lp' | undefined) {
}
return <img {...props} />
},
Img: ({ zoomable = true, ...props }: any) => (
<figure className="m-0">
Img: ({ zoomable = true, className, ...props }: any) => (
<figure className={cn('m-0', className)}>
<ZoomableImg zoomable={zoomable}>
<span
className={[
+5
View File
@@ -2288,4 +2288,9 @@ module.exports = [
source: '/docs/guides/database/managing-passwords',
destination: '/docs/guides/database/postgres/roles#passwords',
},
{
permanent: true,
source: '/blog/pgvector-v0-5-0-hnsw',
destination: '/blog/increase-performance-pgvector-hnsw',
},
]
Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 292 KiB

After

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 KiB

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 239 KiB

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 308 KiB

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 251 KiB

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 496 KiB

After

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

@@ -5,35 +5,35 @@
<link>https://supabase.com/blog</link>
<description>Latest Postgres news from Angelico de los Reyes at Supabase</description>
<language>en</language>
<lastBuildDate>Thu, 15 Dec 2022 22:00:00 GMT</lastBuildDate>
<lastBuildDate>Thu, 15 Dec 2022 07:00:00 GMT</lastBuildDate>
<atom:link href="https://supabase.com/planetpg-angelico_de_los_reyes-rss.xml" rel="self" type="application/rss+xml"/>
<item>
<guid>https://supabase.com/blog/postgres-point-in-time-recovery</guid>
<title>Point in Time Recovery is now available for Pro projects</title>
<link>https://supabase.com/blog/postgres-point-in-time-recovery</link>
<description>We&apos;re making PITR available for more projects, with a new Dashboard UI that makes it simple to use.</description>
<pubDate>Thu, 15 Dec 2022 22:00:00 GMT</pubDate>
<pubDate>Thu, 15 Dec 2022 07:00:00 GMT</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/continuous-postgresql-backup-walg</guid>
<title>Continuous PostgreSQL Backups using WAL-G</title>
<link>https://supabase.com/blog/continuous-postgresql-backup-walg</link>
<description>Have you ever wanted to restore your database&apos;s state to a particular moment in time? This post explains how, using WAL-G.</description>
<pubDate>Sat, 01 Aug 2020 21:00:00 GMT</pubDate>
<pubDate>Sun, 02 Aug 2020 06:00:00 GMT</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/postgresql-templates</guid>
<title>What are PostgreSQL Templates?</title>
<link>https://supabase.com/blog/postgresql-templates</link>
<description>What are PostgreSQL templates and what are they used for?</description>
<pubDate>Wed, 08 Jul 2020 21:00:00 GMT</pubDate>
<pubDate>Thu, 09 Jul 2020 06:00:00 GMT</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/postgresql-physical-logical-backups</guid>
<title>Physical vs Logical Backups in PostgreSQL</title>
<link>https://supabase.com/blog/postgresql-physical-logical-backups</link>
<description>What are physical and logical backups in Postgres?</description>
<pubDate>Mon, 06 Jul 2020 21:00:00 GMT</pubDate>
<pubDate>Tue, 07 Jul 2020 06:00:00 GMT</pubDate>
</item>
</channel>
+2 -2
View File
@@ -5,14 +5,14 @@
<link>https://supabase.com/blog</link>
<description>Latest Postgres news from Ant Wilson at Supabase</description>
<language>en</language>
<lastBuildDate>Fri, 26 Feb 2021 22:00:00 GMT</lastBuildDate>
<lastBuildDate>Sat, 27 Feb 2021 07:00:00 GMT</lastBuildDate>
<atom:link href="https://supabase.com/planetpg-ant_wilson-rss.xml" rel="self" type="application/rss+xml"/>
<item>
<guid>https://supabase.com/blog/cracking-postgres-interview</guid>
<title>Cracking PostgreSQL Interview Questions</title>
<link>https://supabase.com/blog/cracking-postgres-interview</link>
<description>Understand the top PostgreSQL Interview Questions</description>
<pubDate>Fri, 26 Feb 2021 22:00:00 GMT</pubDate>
<pubDate>Sat, 27 Feb 2021 07:00:00 GMT</pubDate>
</item>
</channel>
+2 -2
View File
@@ -5,14 +5,14 @@
<link>https://supabase.com/blog</link>
<description>Latest Postgres news from Bo Lu at Supabase</description>
<language>en</language>
<lastBuildDate>Wed, 14 Dec 2022 22:00:00 GMT</lastBuildDate>
<lastBuildDate>Wed, 14 Dec 2022 07:00:00 GMT</lastBuildDate>
<atom:link href="https://supabase.com/planetpg-bo_lu-rss.xml" rel="self" type="application/rss+xml"/>
<item>
<guid>https://supabase.com/blog/postgres-foreign-data-wrappers-rust</guid>
<title>Supabase Wrappers, a Postgres FDW framework written in Rust</title>
<link>https://supabase.com/blog/postgres-foreign-data-wrappers-rust</link>
<description>A framework for building Postgres Foreign Data Wrappers which connects to Stripe, Firebase, Clickhouse, and more.</description>
<pubDate>Wed, 14 Dec 2022 22:00:00 GMT</pubDate>
<pubDate>Wed, 14 Dec 2022 07:00:00 GMT</pubDate>
</item>
</channel>
+3 -3
View File
@@ -5,21 +5,21 @@
<link>https://supabase.com/blog</link>
<description>Latest Postgres news from Mark Burggraf at Supabase</description>
<language>en</language>
<lastBuildDate>Wed, 23 Nov 2022 22:00:00 GMT</lastBuildDate>
<lastBuildDate>Wed, 23 Nov 2022 07:00:00 GMT</lastBuildDate>
<atom:link href="https://supabase.com/planetpg-burggraf-rss.xml" rel="self" type="application/rss+xml"/>
<item>
<guid>https://supabase.com/blog/sql-or-nosql-both-with-postgresql</guid>
<title>SQL or NoSQL? Why not use both (with PostgreSQL)?</title>
<link>https://supabase.com/blog/sql-or-nosql-both-with-postgresql</link>
<description>How to turn Postgres into an easy-to-use NoSQL database that retains all the power of SQL</description>
<pubDate>Wed, 23 Nov 2022 22:00:00 GMT</pubDate>
<pubDate>Wed, 23 Nov 2022 07:00:00 GMT</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/postgres-wasm</guid>
<title>Postgres WASM by Snaplet and Supabase</title>
<link>https://supabase.com/blog/postgres-wasm</link>
<description>We&apos;re open sourcing postgres-wasm, a PostgresQL server that runs inside a browser, with our friends at Snaplet.</description>
<pubDate>Sun, 02 Oct 2022 21:00:00 GMT</pubDate>
<pubDate>Sun, 02 Oct 2022 06:00:00 GMT</pubDate>
</item>
</channel>
@@ -5,21 +5,21 @@
<link>https://supabase.com/blog</link>
<description>Latest Postgres news from Egor Romanov at Supabase</description>
<language>en</language>
<lastBuildDate>Wed, 02 Aug 2023 21:00:00 GMT</lastBuildDate>
<lastBuildDate>Wed, 02 Aug 2023 06:00:00 GMT</lastBuildDate>
<atom:link href="https://supabase.com/planetpg-egor_romanov-rss.xml" rel="self" type="application/rss+xml"/>
<item>
<guid>https://supabase.com/blog/fewer-dimensions-are-better-pgvector</guid>
<title>pgvector: Fewer dimensions are better</title>
<link>https://supabase.com/blog/fewer-dimensions-are-better-pgvector</link>
<description>Increase performance in pgvector by using embedding vectors with fewer dimensions</description>
<pubDate>Wed, 02 Aug 2023 21:00:00 GMT</pubDate>
<pubDate>Wed, 02 Aug 2023 06:00:00 GMT</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/pgvector-performance</guid>
<title>pgvector 0.4.0 performance</title>
<link>https://supabase.com/blog/pgvector-performance</link>
<description>There&apos;s been lot of talk about pgvector performance lately, so we took some datasets and pushed pgvector to the limits to find out its strengths and limitations.</description>
<pubDate>Wed, 12 Jul 2023 21:00:00 GMT</pubDate>
<pubDate>Wed, 12 Jul 2023 06:00:00 GMT</pubDate>
</item>
</channel>
+10 -3
View File
@@ -5,21 +5,28 @@
<link>https://supabase.com/blog</link>
<description>Latest Postgres news from Greg Richardson at Supabase</description>
<language>en</language>
<lastBuildDate>Wed, 02 Aug 2023 21:00:00 GMT</lastBuildDate>
<lastBuildDate>Tue, 05 Sep 2023 06:00:00 GMT</lastBuildDate>
<atom:link href="https://supabase.com/planetpg-gregnr-rss.xml" rel="self" type="application/rss+xml"/>
<item>
<guid>https://supabase.com/blog/increase-performance-pgvector-hnsw</guid>
<title>pgvector v0.5.0: Faster semantic search with HNSW indexes</title>
<link>https://supabase.com/blog/increase-performance-pgvector-hnsw</link>
<description>Increase performance in pgvector using HNSW indexes</description>
<pubDate>Tue, 05 Sep 2023 06:00:00 GMT</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/fewer-dimensions-are-better-pgvector</guid>
<title>pgvector: Fewer dimensions are better</title>
<link>https://supabase.com/blog/fewer-dimensions-are-better-pgvector</link>
<description>Increase performance in pgvector by using embedding vectors with fewer dimensions</description>
<pubDate>Wed, 02 Aug 2023 21:00:00 GMT</pubDate>
<pubDate>Wed, 02 Aug 2023 06:00:00 GMT</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/openai-embeddings-postgres-vector</guid>
<title>Storing OpenAI embeddings in Postgres with pgvector</title>
<link>https://supabase.com/blog/openai-embeddings-postgres-vector</link>
<description>An example of how to build an AI-powered search engine using OpenAI&apos;s embeddings and PostgreSQL.</description>
<pubDate>Sun, 05 Feb 2023 22:00:00 GMT</pubDate>
<pubDate>Sun, 05 Feb 2023 07:00:00 GMT</pubDate>
</item>
</channel>
+3 -3
View File
@@ -5,21 +5,21 @@
<link>https://supabase.com/blog</link>
<description>Latest Postgres news from Michel Pelletier at Supabase</description>
<language>en</language>
<lastBuildDate>Thu, 15 Dec 2022 22:00:00 GMT</lastBuildDate>
<lastBuildDate>Thu, 15 Dec 2022 07:00:00 GMT</lastBuildDate>
<atom:link href="https://supabase.com/planetpg-michel-rss.xml" rel="self" type="application/rss+xml"/>
<item>
<guid>https://supabase.com/blog/vault-now-in-beta</guid>
<title>Supabase Vault is now in Beta</title>
<link>https://supabase.com/blog/vault-now-in-beta</link>
<description>A Postgres extension to store encrypted secrets and encrypt data.</description>
<pubDate>Thu, 15 Dec 2022 22:00:00 GMT</pubDate>
<pubDate>Thu, 15 Dec 2022 07:00:00 GMT</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/transparent-column-encryption-with-postgres</guid>
<title>Transparent Column Encryption with Postgres</title>
<link>https://supabase.com/blog/transparent-column-encryption-with-postgres</link>
<description>Using pgsodium&apos;s Transparent Column Encryption to encrypt data and provide your users with row-level encryption.</description>
<pubDate>Wed, 30 Nov 2022 22:00:00 GMT</pubDate>
<pubDate>Wed, 30 Nov 2022 07:00:00 GMT</pubDate>
</item>
</channel>
+8 -8
View File
@@ -5,56 +5,56 @@
<link>https://supabase.com/blog</link>
<description>Latest Postgres news from Oliver Rice at Supabase</description>
<language>en</language>
<lastBuildDate>Wed, 02 Aug 2023 21:00:00 GMT</lastBuildDate>
<lastBuildDate>Wed, 02 Aug 2023 06:00:00 GMT</lastBuildDate>
<atom:link href="https://supabase.com/planetpg-oli_rice-rss.xml" rel="self" type="application/rss+xml"/>
<item>
<guid>https://supabase.com/blog/fewer-dimensions-are-better-pgvector</guid>
<title>pgvector: Fewer dimensions are better</title>
<link>https://supabase.com/blog/fewer-dimensions-are-better-pgvector</link>
<description>Increase performance in pgvector by using embedding vectors with fewer dimensions</description>
<pubDate>Wed, 02 Aug 2023 21:00:00 GMT</pubDate>
<pubDate>Wed, 02 Aug 2023 06:00:00 GMT</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/whats-new-in-pg-graphql-v1-2</guid>
<title>What&apos;s New in pg_graphql v1.2</title>
<link>https://supabase.com/blog/whats-new-in-pg-graphql-v1-2</link>
<description>New Features in the v1.2 release of pg_graphql</description>
<pubDate>Thu, 20 Apr 2023 21:00:00 GMT</pubDate>
<pubDate>Thu, 20 Apr 2023 06:00:00 GMT</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/type-constraints-in-65-lines-of-sql</guid>
<title>Type Constraints in 65 lines of SQL</title>
<link>https://supabase.com/blog/type-constraints-in-65-lines-of-sql</link>
<description>Creating validated data types in Postgres</description>
<pubDate>Thu, 16 Feb 2023 22:00:00 GMT</pubDate>
<pubDate>Thu, 16 Feb 2023 07:00:00 GMT</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/pg-graphql-v1</guid>
<title>pg_graphql v1.0</title>
<link>https://supabase.com/blog/pg-graphql-v1</link>
<description>Announcing the v1.0 release of pg_graphql</description>
<pubDate>Thu, 15 Dec 2022 22:00:00 GMT</pubDate>
<pubDate>Thu, 15 Dec 2022 07:00:00 GMT</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/postgres-foreign-data-wrappers-rust</guid>
<title>Supabase Wrappers, a Postgres FDW framework written in Rust</title>
<link>https://supabase.com/blog/postgres-foreign-data-wrappers-rust</link>
<description>A framework for building Postgres Foreign Data Wrappers which connects to Stripe, Firebase, Clickhouse, and more.</description>
<pubDate>Wed, 14 Dec 2022 22:00:00 GMT</pubDate>
<pubDate>Wed, 14 Dec 2022 07:00:00 GMT</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/pg-jsonschema-a-postgres-extension-for-json-validation</guid>
<title>pg_jsonschema: JSON Schema support for Postgres</title>
<link>https://supabase.com/blog/pg-jsonschema-a-postgres-extension-for-json-validation</link>
<description>Today we&apos;re releasing pg_jsonschema, a Postgres extension for JSON validation.</description>
<pubDate>Thu, 18 Aug 2022 21:00:00 GMT</pubDate>
<pubDate>Thu, 18 Aug 2022 06:00:00 GMT</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/postgres-audit</guid>
<title>Postgres Auditing in 150 lines of SQL</title>
<link>https://supabase.com/blog/postgres-audit</link>
<description>PostgreSQL has a robust set of features which we can leverage to create a generic auditing solution in 150 lines of SQL.</description>
<pubDate>Mon, 07 Mar 2022 22:00:00 GMT</pubDate>
<pubDate>Mon, 07 Mar 2022 07:00:00 GMT</pubDate>
</item>
</channel>
@@ -5,49 +5,49 @@
<link>https://supabase.com/blog</link>
<description>Latest Postgres news from Paul Copplestone at Supabase</description>
<language>en</language>
<lastBuildDate>Sun, 30 Apr 2023 21:00:00 GMT</lastBuildDate>
<lastBuildDate>Sun, 30 Apr 2023 06:00:00 GMT</lastBuildDate>
<atom:link href="https://supabase.com/planetpg-paul_copplestone-rss.xml" rel="self" type="application/rss+xml"/>
<item>
<guid>https://supabase.com/blog/postgres-pluggable-strorage</guid>
<title>Next steps for Postgres pluggable storage</title>
<link>https://supabase.com/blog/postgres-pluggable-strorage</link>
<description>Exploring history of Postgres pluggable storage and the possibility of landing it in the Postgres core.</description>
<pubDate>Sun, 30 Apr 2023 21:00:00 GMT</pubDate>
<pubDate>Sun, 30 Apr 2023 06:00:00 GMT</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/postgres-foreign-data-wrappers-rust</guid>
<title>Supabase Wrappers, a Postgres FDW framework written in Rust</title>
<link>https://supabase.com/blog/postgres-foreign-data-wrappers-rust</link>
<description>A framework for building Postgres Foreign Data Wrappers which connects to Stripe, Firebase, Clickhouse, and more.</description>
<pubDate>Wed, 14 Dec 2022 22:00:00 GMT</pubDate>
<pubDate>Wed, 14 Dec 2022 07:00:00 GMT</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/postgres-crdt</guid>
<title>pg_crdt - an experimental CRDT extension for Postgres</title>
<link>https://supabase.com/blog/postgres-crdt</link>
<description>Embedding Yjs and Automerge into Postgres for collaborative applications.</description>
<pubDate>Fri, 09 Dec 2022 22:00:00 GMT</pubDate>
<pubDate>Fri, 09 Dec 2022 07:00:00 GMT</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/partial-postgresql-data-dumps-with-rls</guid>
<title>Partial data dumps using Postgres Row Level Security</title>
<link>https://supabase.com/blog/partial-postgresql-data-dumps-with-rls</link>
<description>Using RLS to create seed files for local PostgreSQL testing.</description>
<pubDate>Mon, 27 Jun 2022 21:00:00 GMT</pubDate>
<pubDate>Mon, 27 Jun 2022 06:00:00 GMT</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/postgres-as-a-cron-server</guid>
<title>Postgres as a CRON Server</title>
<link>https://supabase.com/blog/postgres-as-a-cron-server</link>
<description>Running repetitive tasks with your Postgres database.</description>
<pubDate>Thu, 04 Mar 2021 22:00:00 GMT</pubDate>
<pubDate>Fri, 05 Mar 2021 07:00:00 GMT</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/postgresql-views</guid>
<title>Postgres Views</title>
<link>https://supabase.com/blog/postgresql-views</link>
<description>Creating and using a view in PostgreSQL.</description>
<pubDate>Tue, 17 Nov 2020 22:00:00 GMT</pubDate>
<pubDate>Wed, 18 Nov 2020 07:00:00 GMT</pubDate>
</item>
</channel>
+4 -4
View File
@@ -5,28 +5,28 @@
<link>https://supabase.com/blog</link>
<description>Latest Postgres news from Pavel Borisov at Supabase</description>
<language>en</language>
<lastBuildDate>Wed, 12 Jul 2023 21:00:00 GMT</lastBuildDate>
<lastBuildDate>Wed, 12 Jul 2023 06:00:00 GMT</lastBuildDate>
<atom:link href="https://supabase.com/planetpg-pavel-rss.xml" rel="self" type="application/rss+xml"/>
<item>
<guid>https://supabase.com/blog/pgvector-performance</guid>
<title>pgvector 0.4.0 performance</title>
<link>https://supabase.com/blog/pgvector-performance</link>
<description>There&apos;s been lot of talk about pgvector performance lately, so we took some datasets and pushed pgvector to the limits to find out its strengths and limitations.</description>
<pubDate>Wed, 12 Jul 2023 21:00:00 GMT</pubDate>
<pubDate>Wed, 12 Jul 2023 06:00:00 GMT</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/new-in-postgres-15</guid>
<title>What&apos;s new in Postgres 15?</title>
<link>https://supabase.com/blog/new-in-postgres-15</link>
<description>Describes the release of Postgres 15, new features and reasons to use it</description>
<pubDate>Thu, 15 Dec 2022 22:00:00 GMT</pubDate>
<pubDate>Thu, 15 Dec 2022 07:00:00 GMT</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/postgresql-commitfest</guid>
<title>What is PostgreSQL commitfest and how to contribute</title>
<link>https://supabase.com/blog/postgresql-commitfest</link>
<description>A time-tested method for contributing to the core Postgres code</description>
<pubDate>Wed, 26 Oct 2022 21:00:00 GMT</pubDate>
<pubDate>Wed, 26 Oct 2022 06:00:00 GMT</pubDate>
</item>
</channel>
@@ -5,21 +5,21 @@
<link>https://supabase.com/blog</link>
<description>Latest Postgres news from Steve Chavez at Supabase</description>
<language>en</language>
<lastBuildDate>Thu, 15 Dec 2022 22:00:00 GMT</lastBuildDate>
<lastBuildDate>Thu, 15 Dec 2022 07:00:00 GMT</lastBuildDate>
<atom:link href="https://supabase.com/planetpg-steve_chavez-rss.xml" rel="self" type="application/rss+xml"/>
<item>
<guid>https://supabase.com/blog/postgrest-11-prerelease</guid>
<title>PostgREST 11 pre-release</title>
<link>https://supabase.com/blog/postgrest-11-prerelease</link>
<description>Describes new features of PostgREST 11 pre-release</description>
<pubDate>Thu, 15 Dec 2022 22:00:00 GMT</pubDate>
<pubDate>Thu, 15 Dec 2022 07:00:00 GMT</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/roles-postgres-hooks</guid>
<title>Protecting reserved roles with PostgreSQL Hooks</title>
<link>https://supabase.com/blog/roles-postgres-hooks</link>
<description>Using Postgres Hooks to protect functionality in your Postgres database.</description>
<pubDate>Thu, 01 Jul 2021 21:00:00 GMT</pubDate>
<pubDate>Thu, 01 Jul 2021 06:00:00 GMT</pubDate>
</item>
</channel>
+4 -4
View File
@@ -5,28 +5,28 @@
<link>https://supabase.com/blog</link>
<description>Latest Postgres news from Victor at Supabase</description>
<language>en</language>
<lastBuildDate>Thu, 13 Oct 2022 21:00:00 GMT</lastBuildDate>
<lastBuildDate>Thu, 13 Oct 2022 06:00:00 GMT</lastBuildDate>
<atom:link href="https://supabase.com/planetpg-victor-rss.xml" rel="self" type="application/rss+xml"/>
<item>
<guid>https://supabase.com/blog/postgres-full-text-search-vs-the-rest</guid>
<title>Postgres Full Text Search vs the rest</title>
<link>https://supabase.com/blog/postgres-full-text-search-vs-the-rest</link>
<description>Comparing one of the most popular Postgres features against alternatives</description>
<pubDate>Thu, 13 Oct 2022 21:00:00 GMT</pubDate>
<pubDate>Thu, 13 Oct 2022 06:00:00 GMT</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/choosing-a-postgres-primary-key</guid>
<title>Choosing a Postgres Primary Key</title>
<link>https://supabase.com/blog/choosing-a-postgres-primary-key</link>
<description>Turns out the question of which identifier to use as a Primary Key is complicated -- we&apos;re going to dive into some of the complexity and inherent trade-offs, and figure things out</description>
<pubDate>Wed, 07 Sep 2022 21:00:00 GMT</pubDate>
<pubDate>Wed, 07 Sep 2022 06:00:00 GMT</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/seen-by-in-postgresql</guid>
<title>Implementing &quot;seen by&quot; functionality with Postgres</title>
<link>https://supabase.com/blog/seen-by-in-postgresql</link>
<description>Different approaches for tracking visitor counts with PostgreSQL.</description>
<pubDate>Sun, 17 Jul 2022 21:00:00 GMT</pubDate>
<pubDate>Sun, 17 Jul 2022 06:00:00 GMT</pubDate>
</item>
</channel>
+226 -205
View File
File diff suppressed because it is too large. Load diff
+7
View File
@@ -296,6 +296,13 @@ functions:
```js
const { data, error } = await supabase.auth.verifyOtp({ email, token, type: 'signup'})
```
- id: verify-email-auth(tokenhash)
name: Verify Email Auth (Token Hash)
isSpotlight: false
code: |
```js
const { data, error } = await supabase.auth.verifyOtp({ token_hash: tokenHash, type: 'email'})
```
- id: auth.getSession()
title: 'getSession()'
$ref: '@supabase/gotrue-js.GoTrueClient.getSession'
+8
View File
@@ -415,6 +415,7 @@ functions:
notes: |
- The `verifyOtp` method takes in different verification types. If a phone number is used, the type can either be `sms` or `phone_change`. If an email address is used, the type can be one of the following: `email`, `recovery`, `invite` or `email_change` (`signup` and `magiclink` types are deprecated).
- The verification type used should be determined based on the corresponding auth method called before `verifyOtp` to sign up / sign-in a user.
- The `TokenHash` is contained in the [email templates](/docs/guides/auth/auth-email-templates) and can be used to sign in. You may wish to use the hash with Magic Links for the PKCE flow for Server Side Auth. See [this guide](/docs/guides/auth/server-side/email-based-auth-with-pkce-flow-for-ssr) for more details.
examples:
- id: verify-sms-one-time-password(otp)
name: Verify Sms One-Time Password (OTP)
@@ -430,6 +431,13 @@ functions:
```js
const { data, error } = await supabase.auth.verifyOtp({ email, token, type: 'email'})
```
- id: verify-email-auth(tokenhash)
name: Verify Email Auth (Token Hash)
isSpotlight: false
code: |
```js
const { data, error } = await supabase.auth.verifyOtp({ token_hash: tokenHash, type: 'email'})
```
- id: get-session
title: 'getSession()'
$ref: '@supabase/gotrue-js.GoTrueClient.getSession'
@@ -2,7 +2,7 @@ import * as Tooltip from '@radix-ui/react-tooltip'
import type { PostgresPolicy } from '@supabase/postgres-meta'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { noop } from 'lodash'
import { Button, Dropdown, IconEdit, IconMoreVertical, IconTrash } from 'ui'
import { Button, Dropdown, IconEdit, IconMoreVertical, IconTrash, ScrollArea } from 'ui'
import Panel from 'components/ui/Panel'
import { useCheckPermissions } from 'hooks'
@@ -34,11 +34,35 @@ const PolicyRow = ({
</div>
<div className="flex items-center space-x-2">
<p className="text-scale-1000 text-sm">Applied to:</p>
{policy.roles.map((role, i) => (
{policy.roles.slice(0, 3).map((role, i) => (
<code key={`policy-${role}-${i}`} className="text-scale-1000 text-xs">
{role}
</code>
))}
<Tooltip.Root delayDuration={0}>
<Tooltip.Trigger>
{policy.roles.length > 3 && (
<code key={`policy-etc`} className="text-scale-1000 text-xs">
+ {policy.roles.length - 3} more roles
</code>
)}
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200 max-w-[220px] text-center',
].join(' ')}
>
<span className="text-xs text-scale-1200">
{policy.roles.slice(3).join(', ')}
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</div>
</div>
<div>
@@ -128,7 +128,7 @@ const UserDropdown = ({ user, canRemoveUser, canRemoveMFAFactors }: UserDropdown
) : null}
<Dropdown.Separator />
<Tooltip.Root delayDuration={0}>
<Tooltip.Trigger>
<Tooltip.Trigger asChild>
<Dropdown.Item
onClick={handleDeleteFactors}
icon={<IconShieldOff size="tiny" />}
@@ -160,7 +160,7 @@ const UserDropdown = ({ user, canRemoveUser, canRemoveMFAFactors }: UserDropdown
)}
</Tooltip.Root>
<Tooltip.Root delayDuration={0}>
<Tooltip.Trigger>
<Tooltip.Trigger asChild>
<Dropdown.Item
onClick={handleDelete}
icon={<IconTrash size="tiny" />}
@@ -67,15 +67,20 @@ const BackupItem = ({ projectRef, backup, index }: BackupItemProps) => {
return <Badge color="yellow">Backup In Progress...</Badge>
}
const generateBackupName = (backup: any) => {
if (backup.status == 'COMPLETED') {
return `${dayjs(backup.inserted_at).format('DD MMM YYYY HH:mm:ss')} UTC`
}
return dayjs(backup.inserted_at).format('DD MMM YYYY')
}
return (
<div
className={`flex h-12 items-center justify-between px-6 ${
index ? 'border-t dark:border-dark' : ''
}`}
>
<p className="text-sm text-scale-1200 ">
{dayjs(backup.inserted_at).format('DD MMM YYYY HH:mm:ss')}
</p>
<p className="text-sm text-scale-1200 ">{generateBackupName(backup)}</p>
<div className="">{generateSideButtons(backup)}</div>
</div>
)
@@ -21,7 +21,7 @@ const BackupsList = () => {
'queue_job.restore.prepare'
)
const isPitrEnabled = backups?.configuration?.walg_enabled
const isPitrEnabled = backups?.configuration?.pitr_enabled
if (backups.isLoading) return <Loading />
if (backups.error) return <BackupsError />
@@ -1,10 +1,8 @@
import { useParams } from 'common'
import { OrganizationPaymentMethod } from 'data/organizations/organization-payment-methods-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import {
SubscriptionTier,
useOrgSubscriptionUpdateMutation,
} from 'data/subscriptions/org-subscription-update-mutation'
import { useOrgSubscriptionUpdateMutation } from 'data/subscriptions/org-subscription-update-mutation'
import { SubscriptionTier } from 'data/subscriptions/types'
import { useStore } from 'hooks'
import { Button, Modal } from 'ui'
@@ -7,10 +7,7 @@ import { useEffect, useState } from 'react'
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
import { useOrgPlansQuery } from 'data/subscriptions/org-plans-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import {
SubscriptionTier,
useOrgSubscriptionUpdateMutation,
} from 'data/subscriptions/org-subscription-update-mutation'
import { useOrgSubscriptionUpdateMutation } from 'data/subscriptions/org-subscription-update-mutation'
import { useCheckPermissions, useSelectedOrganization, useStore } from 'hooks'
import { PRICING_TIER_PRODUCT_IDS } from 'lib/constants'
import Telemetry from 'lib/telemetry'
@@ -27,6 +24,7 @@ import { useFreeProjectLimitCheckQuery } from 'data/organizations/free-project-l
import { useOrganizationBillingSubscriptionPreview } from 'data/organizations/organization-billing-subscription-preview'
import InformationBox from 'components/ui/InformationBox'
import AlertError from 'components/ui/AlertError'
import { SubscriptionTier } from 'data/subscriptions/types'
// [Joshen TODO] Need to remove all contexts of "projects"
@@ -266,7 +264,7 @@ const PlanUpdateSidePanel = () => {
<div className="border-t my-6" />
<ul role="list">
{(plan.features).map((feature) => (
{plan.features.map((feature) => (
<li key={feature} className="flex py-2">
<div className="w-[12px]">
<IconCheck
@@ -304,7 +302,7 @@ const PlanUpdateSidePanel = () => {
<Modal
loading={isUpdating}
alignFooter="right"
className="!w-[550px]"
size="large"
visible={selectedTier !== undefined && selectedTier !== 'tier_free'}
onCancel={() => setSelectedTier(undefined)}
onConfirm={onUpdateSubscription}
@@ -37,7 +37,7 @@ const MigrateOrganizationBillingButton = observer(() => {
const [isOpen, setIsOpen] = useState(false)
const [tier, setTier] = useState('')
const [showSpendCapHelperModal, setShowSpendCapHelperModal] = useState(false)
const [isSpendCapEnabled, setIsSpendCapEnabled] = useState(true)
const [isSpendCapEnabled, setIsSpendCapEnabled] = useState(false)
const [paymentMethodId, setPaymentMethodId] = useState('')
const dbTier = useMemo(() => {
@@ -95,6 +95,23 @@ const MigrateOrganizationBillingButton = observer(() => {
const canMigrateOrganization = useCheckPermissions(PermissionAction.UPDATE, 'organizations')
const selectedLimitedUsage = useMemo(
() => tier === 'PRO' && isSpendCapEnabled,
[tier, isSpendCapEnabled]
)
const downgradingToLimitedUsage = useMemo(() => {
if (migrationPreviewData) {
const hadUsageBillingEnabled = migrationPreviewData.old_tiers.some((it) =>
['tier_payg', 'tier_team', 'tier_enterprise'].includes(it)
)
return hadUsageBillingEnabled && selectedLimitedUsage
} else {
return false
}
}, [migrationPreviewData, selectedLimitedUsage])
const toggle = () => {
setIsOpen(!isOpen)
}
@@ -297,8 +314,8 @@ const MigrateOrganizationBillingButton = observer(() => {
<div className="col-span-12">
<p className="text-sm text-scale-1000">
When enabled, usage is limited to the plan's quota, with restrictions when
limits are exceeded. To scale beyond Pro limits without restrictions, disable
the spend cap and pay for over-usage beyond the quota.
limits are exceeded. When disabled, you scale beyond Pro limits without
restrictions and pay for over-usage beyond the quota.
</p>
</div>
@@ -326,15 +343,46 @@ const MigrateOrganizationBillingButton = observer(() => {
<Modal.Content>
<Loading active={tier !== '' && migrationPreviewIsLoading}>
{migrationPreviewError && (
<Alert_Shadcn_ variant="destructive">
<IconAlertCircle strokeWidth={2} />
<AlertTitle_Shadcn_>Organization cannot be migrated</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
{migrationPreviewError.message}
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
)}
<div className="space-y-3">
{migrationPreviewError && (
<Alert_Shadcn_ variant="destructive">
<IconAlertCircle strokeWidth={2} />
<AlertTitle_Shadcn_>Organization cannot be migrated</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
{migrationPreviewError.message}
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
)}
{(downgradingToLimitedUsage ||
(!downgradingToLimitedUsage && selectedLimitedUsage)) && (
<Alert_Shadcn_ variant="warning">
<IconAlertCircle strokeWidth={2} />
<AlertTitle_Shadcn_>Spend Cap is enabled</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
<div className="space-y-2">
{downgradingToLimitedUsage ? (
<p>
You previously had the spend cap disabled to scale seamlessly beyond the
included quota. You will run into restrictions in case you exceed the
included quota.
</p>
) : (
<p>
With the Spend Cap enabled, your projects will be restricted once you
exhaust the included quota. To scale seamlessly beyond the included
quota, disable the Spend Cap.
</p>
)}
<Button type="outline" onClick={() => setIsSpendCapEnabled(false)}>
Disable Spend Cap
</Button>
</div>
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
)}
</div>
</Loading>
{migrationError && (
@@ -88,13 +88,7 @@ const ConnectionPooling = () => {
) : (
<PgbouncerConfig
projectRef={projectRef}
// [Joshen TODO] remove this check once API PR has been deployed:
// https://github.com/supabase/infrastructure/pull/14173
bouncerInfo={{
...bouncerInfo,
supavisor_enabled:
poolingConfiguration.connectionString.includes('pooler.supabase.com'),
}}
bouncerInfo={bouncerInfo}
connectionInfo={connectionInfo}
/>
)}
@@ -144,7 +144,13 @@ const TransferProjectButton = () => {
<Modal.Content>
<p className="text-sm">
To transfer projects, the owner must be a member of both the source and target
organizations.
organizations. For further information see our{' '}
<Link href="https://supabase.com/docs/guides/platform/project-transfer">
<a className="text-brand hover:underline" target="_blank" rel="noreferrer">
Documentation
</a>
</Link>
.
</p>
<p className="font-bold mt-6 text-sm">Transferring considerations:</p>
@@ -38,7 +38,7 @@ const LayoutHeader = ({ customHeaderComponents, breadcrumbs = [], headerBorder =
{ enabled: selectedOrganization && !selectedOrganization.subscription_id }
)
const projectHasNoLimits = subscription?.usage_billing_enabled === false
const projectHasNoLimits = subscription?.usage_billing_enabled === true
const showOverUsageBadge =
useFlag('overusageBadge') &&
@@ -96,9 +96,7 @@ const HeaderBreadcrumbs = ({ loading, breadcrumbs, selectBreadcrumb }: any) => {
}
interface FileExplorerHeader {
isSearching: boolean
itemSearchString: string
setIsSearching: (value: boolean) => void
setItemSearchString: (value: string) => void
onFilesUpload: (event: any, columnIndex: number) => void
}
@@ -423,7 +423,7 @@ const FileExplorerRow = ({
overlay={(option?.children ?? [])?.map((child) => {
return (
<Dropdown.Item key={child.name} onClick={child.onClick}>
{child.name}
<p className="text-xs">{child.name}</p>
</Dropdown.Item>
)
})}
@@ -436,7 +436,7 @@ const FileExplorerRow = ({
>
<div className="flex items-center space-x-2">
{option.icon}
<p>{option.name}</p>
<p className="text">{option.name}</p>
</div>
<IconChevronRight size="tiny" />
</div>
@@ -451,7 +451,7 @@ const FileExplorerRow = ({
icon={option.icon || <></>}
onClick={option.onClick}
>
{option.name}
<p className="text-xs">{option.name}</p>
</Dropdown.Item>
)
}
@@ -210,6 +210,7 @@ const PreviewPane = ({ onCopyUrl }: PreviewPaneProps) => {
overlay={[
<Dropdown.Item
key="expires-one-week"
className="text-xs"
onClick={async () =>
onCopyUrl(file.name, await getFileUrl(file, URL_EXPIRY_DURATION.WEEK))
}
@@ -218,6 +219,7 @@ const PreviewPane = ({ onCopyUrl }: PreviewPaneProps) => {
</Dropdown.Item>,
<Dropdown.Item
key="expires-one-month"
className="text-xs"
onClick={async () =>
onCopyUrl(file.name, await getFileUrl(file, URL_EXPIRY_DURATION.MONTH))
}
@@ -226,6 +228,7 @@ const PreviewPane = ({ onCopyUrl }: PreviewPaneProps) => {
</Dropdown.Item>,
<Dropdown.Item
key="expires-one-year"
className="text-xs"
onClick={async () =>
onCopyUrl(file.name, await getFileUrl(file, URL_EXPIRY_DURATION.YEAR))
}
@@ -234,6 +237,7 @@ const PreviewPane = ({ onCopyUrl }: PreviewPaneProps) => {
</Dropdown.Item>,
<Dropdown.Item
key="custom-expiry"
className="text-xs"
onClick={() => setSelectedFileCustomExpiry(file)}
>
Custom expiry
@@ -5,6 +5,7 @@ import { useEffect, useRef, useState } from 'react'
import { useProjectSettingsQuery } from 'data/config/project-settings-query'
import { useCustomDomainsQuery } from 'data/custom-domains/custom-domains-query'
import { Bucket } from 'data/storage/buckets-query'
import { useStore } from 'hooks'
import { DEFAULT_PROJECT_API_SERVICE_ID, IS_PLATFORM } from 'lib/constants'
import { copyToClipboard } from 'lib/helpers'
@@ -18,7 +19,11 @@ import FileExplorerHeaderSelection from './FileExplorerHeaderSelection'
import MoveItemsModal from './MoveItemsModal'
import PreviewPane from './PreviewPane'
const StorageExplorer = observer(({ bucket }) => {
interface StorageExplorerProps {
bucket: Bucket
}
const StorageExplorer = ({ bucket }: StorageExplorerProps) => {
const storageExplorerStore = useStorageStore()
const {
columns,
@@ -70,30 +75,35 @@ const StorageExplorer = observer(({ bucket }) => {
// Requires a fixed height to ensure that explorer is constrained to the viewport
const fileExplorerHeight = window.innerHeight - 122
useEffect(async () => {
const currentFolderIdx = openedFolders.length - 1
const currentFolder = openedFolders[currentFolderIdx]
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => {
const fetchContents = async () => {
const currentFolderIdx = openedFolders.length - 1
const currentFolder = openedFolders[currentFolderIdx]
if (itemSearchString) {
if (!currentFolder) {
// At root of bucket
await fetchFolderContents(bucket.id, bucket.name, -1, itemSearchString)
if (itemSearchString) {
if (!currentFolder) {
// At root of bucket
await fetchFolderContents(bucket.id, bucket.name, -1, itemSearchString)
} else {
await fetchFolderContents(
currentFolder.id,
currentFolder.name,
currentFolderIdx,
itemSearchString
)
}
} else {
await fetchFolderContents(
currentFolder.id,
currentFolder.name,
currentFolderIdx,
itemSearchString
)
}
} else {
if (!currentFolder) {
// At root of bucket
await fetchFolderContents(bucket.id, bucket.name, -1)
} else {
await fetchFolderContents(currentFolder.id, currentFolder.name, currentFolderIdx)
if (!currentFolder) {
// At root of bucket
await fetchFolderContents(bucket.id, bucket.name, -1)
} else {
await fetchFolderContents(currentFolder.id, currentFolder.name, currentFolderIdx)
}
}
}
fetchContents()
}, [itemSearchString])
useEffect(() => {
@@ -108,18 +118,22 @@ const StorageExplorer = observer(({ bucket }) => {
/** Checkbox selection methods */
/** [Joshen] We'll only support checkbox selection for files ONLY */
const onSelectAllItemsInColumn = (columnIndex) => {
const onSelectAllItemsInColumn = (columnIndex: number) => {
const columnFiles = columns[columnIndex].items
.filter((item) => item.type === STORAGE_ROW_TYPES.FILE)
.map((item) => {
.filter((item: any) => item.type === STORAGE_ROW_TYPES.FILE)
.map((item: any) => {
return { ...item, columnIndex }
})
const columnFilesId = compact(columnFiles.map((item) => item.id))
const selectedItemsFromColumn = selectedItems.filter((item) => columnFilesId.includes(item.id))
const columnFilesId = compact(columnFiles.map((item: any) => item.id))
const selectedItemsFromColumn = selectedItems.filter((item: any) =>
columnFilesId.includes(item.id)
)
if (selectedItemsFromColumn.length === columnFiles.length) {
// Deselect all items from column
const updatedSelectedItems = selectedItems.filter((item) => !columnFilesId.includes(item.id))
const updatedSelectedItems = selectedItems.filter(
(item: any) => !columnFilesId.includes(item.id)
)
setSelectedItems(updatedSelectedItems)
} else {
// Select all items from column
@@ -130,7 +144,7 @@ const StorageExplorer = observer(({ bucket }) => {
/** File manipulation methods */
const onFilesUpload = async (event, columnIndex = -1) => {
const onFilesUpload = async (event: any, columnIndex = -1) => {
event.persist()
const items = event.target.files || event.dataTransfer.items
const isDrop = !isEmpty(get(event, ['dataTransfer', 'items'], []))
@@ -138,7 +152,7 @@ const StorageExplorer = observer(({ bucket }) => {
event.target.value = ''
}
const onMoveSelectedFiles = async (newPath) => {
const onMoveSelectedFiles = async (newPath: string) => {
await moveFiles(newPath)
}
@@ -161,14 +175,14 @@ const StorageExplorer = observer(({ bucket }) => {
}
/** Misc UI methods */
const onSelectColumnEmptySpace = (columnIndex) => {
const onSelectColumnEmptySpace = (columnIndex: number) => {
popColumnAtIndex(columnIndex)
popOpenedFoldersAtIndex(columnIndex - 1)
closeFilePreview()
clearSelectedItems()
}
const onCopyUrl = (name, url) => {
const onCopyUrl = (name: string, url: string) => {
const formattedUrl =
customDomainData?.customDomain?.status === 'active'
? url.replace(apiUrl, `https://${customDomainData.customDomain.hostname}`)
@@ -232,7 +246,7 @@ const StorageExplorer = observer(({ bucket }) => {
<CustomExpiryModal onCopyUrl={onCopyUrl} />
</div>
)
})
}
StorageExplorer.displayName = 'StorageExplorer'
export default StorageExplorer
export default observer(StorageExplorer)
+3 -15
View File
@@ -40,21 +40,9 @@ export const useProjectReadOnlyQuery = (
return data.result[0]?.default_transaction_read_only === 'on'
},
enabled: typeof projectRef !== 'undefined' && typeof connectionString !== 'undefined',
retry: (failureCount) => {
return failureCount < 3
},
...options,
}
)
export const useProjectReadOnlyPrefetch = () => {
const prefetch = useExecuteSqlPrefetch()
return useCallback(
({ projectRef, connectionString }: ProjectReadOnlyVariables) =>
prefetch({
projectRef,
connectionString,
sql: getProjectReadOnlySql(),
queryKey: ['project-read-only'],
}),
[prefetch]
)
}
@@ -14,6 +14,15 @@ export type BackupRestoreVariables = {
export type Backup = components['schemas']['Backup']
export async function restoreFromBackup({ ref, backup }: BackupRestoreVariables) {
if (backup.isPhysicalBackup) {
const { data, error } = await post('/platform/database/{ref}/backups/restore-physical', {
params: { path: { ref } },
body: { id: backup.id, recovery_time_target: backup.inserted_at },
})
if (error) throw error
return data
}
const { data, error } = await post('/platform/database/{ref}/backups/restore', {
params: { path: { ref } },
body: { id: backup.id },
@@ -21,7 +21,7 @@ export type PoolingConfiguration = {
inserted_at: string
max_client_conn: number | null
pgbouncer_enabled: boolean
supavisor_enabled?: boolean
supavisor_enabled: boolean
pgbouncer_status: string
pool_mode: string
connectionString: string
@@ -2,10 +2,11 @@ import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { post } from 'data/fetchers'
import { organizationKeys } from './keys'
import { ResponseError } from 'types'
import { SubscriptionTier } from 'data/subscriptions/types'
export type OrganizationBillingSubscriptionPreviewVariables = {
organizationSlug?: string
tier?: 'tier_payg' | 'tier_pro' | 'tier_free' | 'tier_team' | 'tier_enterprise'
tier?: SubscriptionTier
}
export type OrganizationBillingSubscriptionPreviewResponse = {
@@ -2,6 +2,7 @@ import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { post } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { organizationKeys } from './keys'
import { SubscriptionTier } from 'data/subscriptions/types'
export type OrganizationBillingMigrationPreviewVariables = {
organizationSlug?: string
@@ -21,6 +22,7 @@ export type OrganizationBillingMigrationPreviewResponse = {
quantity: number
total_price: number
}[]
old_tiers: SubscriptionTier[]
}
export async function previewOrganizationBillingMigration(
@@ -5,13 +5,7 @@ import { toast } from 'react-hot-toast'
import { ResponseError } from 'types/base'
import { subscriptionKeys } from './keys'
import { usageKeys } from 'data/usage/keys'
export type SubscriptionTier =
| 'tier_free'
| 'tier_pro'
| 'tier_payg'
| 'tier_team'
| 'tier_enterprise'
import { SubscriptionTier } from './types'
export type OrgSubscriptionUpdateVariables = {
slug: string
+6
View File
@@ -0,0 +1,6 @@
export type SubscriptionTier =
| 'tier_free'
| 'tier_pro'
| 'tier_payg'
| 'tier_team'
| 'tier_enterprise'
@@ -33,6 +33,13 @@ const handleGet = async (req: NextApiRequest, res: NextApiResponse) => {
const handlePost = async (req: NextApiRequest, res: NextApiResponse) => {
const { id, public: isPublicBucket } = req.body
// To validate bucket name, can be removed once the issue is fixed in supabase lib
const regex = /^[a-z0-9.-]+$/
if (!regex.test(id)) {
return res.status(400).json({ error: { message: 'Bucket name invalid' } })
}
// Bucket name validation ends here
const { data, error } = await supabase.storage.createBucket(id, { public: isPublicBucket })
if (error) {
return res.status(400).json({ error: { message: error.message } })
+53 -1
View File
@@ -496,7 +496,14 @@ const Wizard: NextPageWithLayout = () => {
</div>
)}
<div>
<div className="space-x-3">
<Link href="https://supabase.com/blog/organization-based-billing">
<a target="_blank" rel="noreferrer">
<Button type="default" icon={<IconExternalLink strokeWidth={1.5} />}>
Announcement
</Button>
</a>
</Link>
<Link href="https://supabase.com/docs/guides/platform/org-based-billing">
<a target="_blank" rel="noreferrer">
<Button type="default" icon={<IconExternalLink strokeWidth={1.5} />}>
@@ -573,6 +580,51 @@ const Wizard: NextPageWithLayout = () => {
</Panel.Content>
)}
{!billedViaOrg && (
<Panel.Content>
<InformationBox
icon={<IconInfo size="large" strokeWidth={1.5} />}
defaultVisibility={true}
hideCollapse
title="Legacy Billing"
description={
<div className="space-y-3">
<p className="text-sm leading-normal">
This organization uses the legacy project-based billing. We’ve recently made
some big improvements to our billing system. To opt-in to the new
organization-based billing, head over to your{' '}
<Link href={`/org/${slug}/billing`}>
<a>
<span className="text-sm text-green-900 transition hover:text-green-1000">
organization billing settings
</span>
</a>
</Link>
.
</p>
<div className="space-x-3">
<Link href="https://supabase.com/blog/organization-based-billing">
<a target="_blank" rel="noreferrer">
<Button type="default" icon={<IconExternalLink strokeWidth={1.5} />}>
Announcement
</Button>
</a>
</Link>
<Link href="https://supabase.com/docs/guides/platform/org-based-billing">
<a target="_blank" rel="noreferrer">
<Button type="default" icon={<IconExternalLink strokeWidth={1.5} />}>
Documentation
</Button>
</a>
</Link>
</div>
</div>
}
/>
</Panel.Content>
)}
{!billedViaOrg && !isSelectFreeTier && (
<>
<Panel.Content className="border-b border-panel-border-interior-light dark:border-panel-border-interior-dark">
@@ -18,7 +18,7 @@ const DatabaseScheduledBackups: NextPageWithLayout = () => {
const { project } = useProjectContext()
const ref = project?.ref
const isPitrEnabled = backups?.configuration?.walg_enabled
const isPitrEnabled = backups?.configuration?.pitr_enabled
const canReadScheduledBackups = useCheckPermissions(PermissionAction.READ, 'back_ups')
@@ -1,5 +1,4 @@
import { useParams } from 'common'
import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, Alert_Shadcn_, IconAlertCircle } from 'ui'
import { LogsTableName } from 'components/interfaces/Settings/Logs'
import LogsPreviewer from 'components/interfaces/Settings/Logs/LogsPreviewer'
@@ -18,10 +17,7 @@ export const LogPage: NextPageWithLayout = () => {
if (isLoading) {
return <Connecting />
}
const isSupavisorEnabled =
poolingConfiguration?.supavisor_enabled ??
poolingConfiguration?.connectionString.includes('pooler.supabase.com') ??
false
const isSupavisorEnabled = poolingConfiguration?.supavisor_enabled ?? false
return (
<LogsPreviewer
+8 -2
View File
@@ -92,9 +92,15 @@ export default class ProjectBackupsStore implements IProjectBackupsStore {
list(filter?: any) {
const arr = Object.values(this.data)
if (!!filter) {
return arr.filter(filter).sort((a: any, b: any) => b.id - a.id)
return arr
.filter(filter)
.sort(
(a: any, b: any) => new Date(b.inserted_at).valueOf() - new Date(a.inserted_at).valueOf()
)
} else {
return arr.sort((a: any, b: any) => b.id - a.id)
return arr.sort(
(a: any, b: any) => new Date(b.inserted_at).valueOf() - new Date(a.inserted_at).valueOf()
)
}
}