Merge branch 'master' into fix/matching-column-types
No files matched your search
@@ -67,7 +67,7 @@ You can also [self-host](https://supabase.com/docs/guides/hosting/overview) and
|
||||
- [pg_graphql](http://github.com/supabase/pg_graphql/) a PostgreSQL extension that exposes a GraphQL API
|
||||
- [Storage](https://github.com/supabase/storage-api) provides a RESTful interface for managing Files stored in S3, using Postgres to manage permissions.
|
||||
- [postgres-meta](https://github.com/supabase/postgres-meta) is a RESTful API for managing your Postgres, allowing you to fetch tables, add roles, and run queries, etc.
|
||||
- [GoTrue](https://github.com/supabase/gotrue) is an JWT based API for managing users and issuing JWT tokens.
|
||||
- [GoTrue](https://github.com/supabase/gotrue) is a JWT based API for managing users and issuing JWT tokens.
|
||||
- [Kong](https://github.com/Kong/kong) is a cloud-native API gateway.
|
||||
|
||||
#### Client libraries
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Link from 'next/link'
|
||||
import React, { useState } from 'react'
|
||||
import { GlassPanel, IconLink, IconX, Input } from 'ui'
|
||||
import extensions from '../data/extensions.json'
|
||||
import { extensions } from 'shared-data'
|
||||
import { GlassPanel, IconX, Input } from 'ui'
|
||||
|
||||
type Extension = {
|
||||
name: string
|
||||
|
||||
@@ -61,13 +61,16 @@ as $$
|
||||
declare
|
||||
status int;
|
||||
content text;
|
||||
avatar_name text;
|
||||
begin
|
||||
if coalesce(old.avatar_url, '') <> ''
|
||||
and (tg_op = 'DELETE' or (old.avatar_url <> new.avatar_url)) then
|
||||
-- extract avatar name
|
||||
avatar_name := substring(old.avatar_url from '/([^\/]+)\?.*$');
|
||||
select
|
||||
into status, content
|
||||
result.status, result.content
|
||||
from public.delete_avatar(old.avatar_url) as result;
|
||||
from public.delete_avatar(avatar_name) as result;
|
||||
if status <> 200 then
|
||||
raise warning 'Could not delete avatar: % %', status, content;
|
||||
end if;
|
||||
|
||||
@@ -449,6 +449,10 @@ export const auth = {
|
||||
name: 'Overview',
|
||||
url: '/guides/auth',
|
||||
},
|
||||
{
|
||||
name: 'Redirect URLs',
|
||||
url: '/guides/auth/concepts/redirect-urls',
|
||||
},
|
||||
{
|
||||
name: 'Quickstarts',
|
||||
items: [
|
||||
@@ -495,22 +499,36 @@ export const auth = {
|
||||
{ name: 'Managing User Data', url: '/guides/auth/managing-user-data' },
|
||||
{ name: 'Multi-Factor Authentication', url: '/guides/auth/auth-mfa' },
|
||||
{ name: 'Row Level Security', url: '/guides/auth/row-level-security' },
|
||||
{ name: 'Server-side Rendering', url: '/guides/auth/server-side-rendering' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Auth Helpers',
|
||||
name: 'Server-side Auth',
|
||||
url: undefined,
|
||||
items: [
|
||||
{ name: 'Overview', url: '/guides/auth/auth-helpers' },
|
||||
{ name: 'Auth UI', url: '/guides/auth/auth-helpers/auth-ui' },
|
||||
{ name: 'Flutter Auth UI', url: '/guides/auth/auth-helpers/flutter-auth-ui' },
|
||||
{
|
||||
name: 'Next.js',
|
||||
url: '/guides/auth/auth-helpers/nextjs',
|
||||
},
|
||||
{ name: 'Remix', url: '/guides/auth/auth-helpers/remix' },
|
||||
{ name: 'SvelteKit', url: '/guides/auth/auth-helpers/sveltekit' },
|
||||
{ name: 'Server-side Rendering', url: '/guides/auth/server-side-rendering' },
|
||||
{
|
||||
name: 'Email Auth with PKCE flow for SSR',
|
||||
url: '/guides/auth/server-side/email-based-auth-with-pkce-flow-for-ssr',
|
||||
},
|
||||
{
|
||||
name: 'OAuth with PKCE flow for SSR',
|
||||
url: '/guides/auth/server-side/oauth-with-pkce-flow-for-ssr',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Auth UI',
|
||||
url: undefined,
|
||||
items: [
|
||||
{ name: 'Auth UI', url: '/guides/auth/auth-helpers/auth-ui' },
|
||||
{ name: 'Flutter Auth UI', url: '/guides/auth/auth-helpers/flutter-auth-ui' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -923,7 +941,18 @@ export const storage: NavMenuConstant = {
|
||||
],
|
||||
}
|
||||
|
||||
export const ai: NavMenuConstant = {
|
||||
export const vectorIndexItems = [
|
||||
{
|
||||
name: 'HNSW indexes',
|
||||
url: '/guides/ai/vector-indexes/hnsw-indexes',
|
||||
},
|
||||
{
|
||||
name: 'IVFFlat indexes',
|
||||
url: '/guides/ai/vector-indexes/ivf-indexes',
|
||||
},
|
||||
]
|
||||
|
||||
export const ai = {
|
||||
icon: 'ai',
|
||||
title: 'AI & Vectors',
|
||||
url: '/guides/ai',
|
||||
@@ -935,61 +964,34 @@ export const ai: NavMenuConstant = {
|
||||
url: '/guides/ai/structured-unstructured',
|
||||
},
|
||||
{
|
||||
name: 'Quickstarts',
|
||||
name: 'Learn',
|
||||
url: undefined,
|
||||
items: [
|
||||
{ name: 'Developing locally with Vecs', url: '/guides/ai/vecs-python-client' },
|
||||
{ name: 'Creating and managing collections', url: '/guides/ai/quickstarts/hello-world' },
|
||||
{
|
||||
name: 'Generate Embeddings',
|
||||
url: '/guides/ai/quickstarts/generate-text-embeddings',
|
||||
},
|
||||
{ name: 'Text Deduplication', url: '/guides/ai/quickstarts/text-deduplication' },
|
||||
{ name: 'Face similarity search', url: '/guides/ai/quickstarts/face-similarity' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Python Client',
|
||||
url: undefined,
|
||||
items: [
|
||||
{ name: 'API', url: '/guides/ai/python/api' },
|
||||
{ name: 'Collections', url: '/guides/ai/python/collections' },
|
||||
{ name: 'Indexes', url: '/guides/ai/python/indexes' },
|
||||
{ name: 'Metadata', url: '/guides/ai/python/metadata' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Guides',
|
||||
url: undefined,
|
||||
items: [
|
||||
{ name: 'Managing collections', url: '/guides/ai/managing-collections' },
|
||||
{ name: 'Managing indexes', url: '/guides/ai/managing-indexes' },
|
||||
{ name: 'Vector columns', url: '/guides/ai/vector-columns' },
|
||||
{ name: 'Vector indexes', url: '/guides/ai/vector-indexes', items: vectorIndexItems },
|
||||
{ name: 'Engineering for scale', url: '/guides/ai/engineering-for-scale' },
|
||||
{ name: 'Choosing Compute Add-on', url: '/guides/ai/choosing-compute-addon' },
|
||||
{ name: 'Going to Production', url: '/guides/ai/going-to-prod' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Examples',
|
||||
name: 'JavaScript Examples',
|
||||
url: undefined,
|
||||
items: [
|
||||
{
|
||||
name: 'OpenAI completions using Edge Functions',
|
||||
url: '/guides/ai/examples/openai',
|
||||
},
|
||||
{
|
||||
name: 'Image search with OpenAI CLIP',
|
||||
url: '/guides/ai/examples/image-search-openai-clip',
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Generate image captions using Hugging Face',
|
||||
url: '/guides/ai/examples/huggingface-image-captioning',
|
||||
},
|
||||
{
|
||||
name: 'Building ChatGPT Plugins',
|
||||
url: '/guides/ai/examples/building-chatgpt-plugins',
|
||||
name: 'Generate Embeddings',
|
||||
url: '/guides/ai/quickstarts/generate-text-embeddings',
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Adding generative Q&A to your documentation',
|
||||
url: '/guides/ai/examples/headless-vector-search',
|
||||
@@ -1000,6 +1002,36 @@ export const ai: NavMenuConstant = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Python Client',
|
||||
url: undefined,
|
||||
items: [
|
||||
{ name: 'Choosing a Client', url: '/guides/ai/python-clients' },
|
||||
{ name: 'API', url: '/guides/ai/python/api' },
|
||||
{ name: 'Collections', url: '/guides/ai/python/collections' },
|
||||
{ name: 'Indexes', url: '/guides/ai/python/indexes' },
|
||||
{ name: 'Metadata', url: '/guides/ai/python/metadata' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Python Examples',
|
||||
url: undefined,
|
||||
items: [
|
||||
{ name: 'Developing locally with Vecs', url: '/guides/ai/vecs-python-client' },
|
||||
{ name: 'Creating and managing collections', url: '/guides/ai/quickstarts/hello-world' },
|
||||
|
||||
{ name: 'Text Deduplication', url: '/guides/ai/quickstarts/text-deduplication' },
|
||||
{ name: 'Face similarity search', url: '/guides/ai/quickstarts/face-similarity' },
|
||||
{
|
||||
name: 'Image search with OpenAI CLIP',
|
||||
url: '/guides/ai/examples/image-search-openai-clip',
|
||||
},
|
||||
{
|
||||
name: 'Building ChatGPT Plugins',
|
||||
url: '/guides/ai/examples/building-chatgpt-plugins',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Third-Party Tools',
|
||||
url: undefined,
|
||||
|
||||
@@ -49,7 +49,7 @@ custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supab
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
The available modules are: **gotrue-kt**, **realtime-kt**, **storage-kt**, **functions-kt**, **postgrest-kt** and **apollo-graphql**
|
||||
The available modules are: **gotrue-kt**, **realtime-kt**, **storage-kt**, **functions-kt**, **postgrest-kt**, **apollo-graphql**, [**compose-auth**](https://github.com/supabase-community/supabase-kt/tree/master/plugins/ComposeAuth) and [**compose-auth-ui**](https://github.com/supabase-community/supabase-kt/tree/master/plugins/ComposeAuthUI)
|
||||
|
||||
When using multiple modules, you can also use the BOM dependency to ensure that all modules use the same version:
|
||||
|
||||
|
||||
@@ -16,23 +16,43 @@ hideTitle: true
|
||||
|
||||
This reference documents every object and method available in Supabase's Kotlin Multiplatform library, [supabase-kt](https://github.com/supabase-community/supabase-kt). You can use supabase-kt to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
|
||||
|
||||
Supported Kotlin targets:
|
||||
Supported targets:
|
||||
|
||||
| | **GoTrue** | **Realtime** | **Postgrest** | **Storage** | **Functions** | **Apollo-GraphQL** |
|
||||
| ------------------------------------------------------------------ | ---------- | ------------ | ------------- | ----------- | ------------- | ------------------ |
|
||||
| **JVM** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| **Android** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| **JS** _(Browser, NodeJS)_ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| **IOS** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| **tvOS** _(tvosArm64, tvosX64, tvosSimulatorArm64)_ 🚧 | ☑️ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| **watchOS** _(watchosArm64, watchosX64, watchosSimulatorArm64)_ 🚧 | ☑️ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| **MacOS** _(macosX64 & macosArm64)_ 🚧 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| **Windows** _(mingwX64)_ 🚧 | ☑️ | ✅ | ✅ | ✅ | ✅ | ❌ |
|
||||
| **Linux** _(linuxX64)_ 🚧 | ☑️ | ✅ | ✅ | ✅ | ✅ | ❌ |
|
||||
| | **GoTrue** | **Realtime** | **Postgrest** | **Storage** | **Functions** | **Apollo-GraphQL** | **Compose Auth 🚧** | **Compose Auth UI 🚧** |
|
||||
| ----------- | ---------- | ------------ | ------------- | ----------- | ------------- | ------------------ | ------------------- | ---------------------- |
|
||||
| **JVM** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ☑️ | ✅ |
|
||||
| **Android** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| **JS** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ☑️ | ✅ |
|
||||
| **IOS** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| **tvOS** | ☑️ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| **watchOS** | ☑️ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| **MacOS** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| **Windows** | ☑️ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||
| **Linux** | ☑️ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||
|
||||
<details>
|
||||
|
||||
<summary>In-depth Kotlin targets</summary>
|
||||
|
||||
**iOS:** iosArm64, iosSimulatorArm64, iosX64
|
||||
|
||||
**JS**: Browser, NodeJS
|
||||
|
||||
**tvOS**: tvosArm64, tvosX64, tvosSimulatorArm64
|
||||
|
||||
**watchOS**: watchosArm64, watchosX64, watchosSimulatorArm64
|
||||
|
||||
**MacOS**: macosX64, macosArm64
|
||||
|
||||
**Windows**: mingwX64
|
||||
|
||||
**Linux**: linuxX64
|
||||
|
||||
</details>
|
||||
|
||||
✅ = full support
|
||||
|
||||
☑️ = partial support: no built-in OAuth/OTP link handling. Linux also has no persistent storage.
|
||||
☑️ = partial support: no built-in OAuth/OTP link handling. Linux also has no support for persistent storage. For Compose Auth, it relies on GoTrue as fallback.
|
||||
|
||||
🚧 = experimental/needs feedback
|
||||
|
||||
|
||||
@@ -244,7 +244,7 @@ const Container = memo(function Container(props) {
|
||||
className={[
|
||||
// 'overflow-x-auto',
|
||||
'w-full h-screen transition-all ease-out',
|
||||
'absolute lg:relative',
|
||||
// 'absolute lg:relative',
|
||||
mobileMenuOpen
|
||||
? '!w-auto ml-[75%] sm:ml-[50%] md:ml-[33%] overflow-hidden'
|
||||
: 'overflow-auto',
|
||||
|
||||
@@ -17,7 +17,122 @@ You have two options for scaling your vector workload:
|
||||
|
||||
## Dimensionality
|
||||
|
||||
The number of dimensions in your embeddings is the most important factor in choosing the right Compute Add-on. In general, the lower the dimensionality the better the performance. We've provided guidance for some of the more common embedding dimensions below. For each benchmark, we used [Vecs](https://github.com/supabase/vecs) to create a collection, upload the embeddings to a single table, and create an `inner-product` index for the embedding column. We then ran a series of queries to measure the performance of different compute add-ons:
|
||||
The number of dimensions in your embeddings is the most important factor in choosing the right Compute Add-on. In general, the lower the dimensionality the better the performance. We've provided guidance for some of the more common embedding dimensions below. For each benchmark, we used [Vecs](https://github.com/supabase/vecs) to create a collection, upload the embeddings to a single table, and create both the `IVFFlat` and `HNSW` indexes for `inner-product` distance measure for the embedding column. We then ran a series of queries to measure the performance of different compute add-ons:
|
||||
|
||||
## HNSW
|
||||
|
||||
### 1536 Dimensions
|
||||
|
||||
This benchmark uses the [dbpedia-entities-openai-1M](https://huggingface.co/datasets/KShivendu/dbpedia-entities-openai-1M) dataset, which contains 1,000,000 embeddings of text. And 224,482 embeddings from [Wikipedia articles](https://huggingface.co/datasets/Supabase/wikipedia-en-embeddings) for compute add-ons `large` and below. Each embedding is 1536 dimensions created with the [OpenAI Embeddings API](https://platform.openai.com/docs/guides/embeddings).
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="openai1536"
|
||||
>
|
||||
<TabPanel id="openai1536" label="OpenAI-1536">
|
||||
|
||||
| Plan | Vectors | m | ef_construction | ef_search | QPS | Latency Mean | Latency p95 | RAM Usage | RAM |
|
||||
| ------ | --------- | --- | --------------- | --------- | ---- | ------------ | ----------- | ------------------ | ------ |
|
||||
| Free | 15,000 | 16 | 40 | 40 | 480 | 0.011 sec | 0.016 sec | 1 GB + 200 Mb Swap | 1 GB |
|
||||
| Small | 50,000 | 32 | 64 | 100 | 175 | 0.031 sec | 0.051 sec | 2 GB + 200 Mb Swap | 2 GB |
|
||||
| Medium | 100,000 | 32 | 64 | 100 | 240 | 0.083 sec | 0.126 sec | 4 GB | 4 GB |
|
||||
| Large | 224,482 | 32 | 64 | 100 | 280 | 0.017 sec | 0.028 sec | 8 GB | 8 GB |
|
||||
| XL | 500,000 | 24 | 56 | 100 | 360 | 0.055 sec | 0.135 sec | 13 GB | 16 GB |
|
||||
| 2XL | 1,000,000 | 24 | 56 | 250 | 560 | 0.036 sec | 0.058 sec | 32 GB | 32 GB |
|
||||
| 4XL | 1,000,000 | 24 | 56 | 250 | 950 | 0.021 sec | 0.033 sec | 39 GB | 64 GB |
|
||||
| 8XL | 1,000,000 | 24 | 56 | 250 | 1650 | 0.016 sec | 0.023 sec | 40 GB | 128 GB |
|
||||
| 12XL | 1,000,000 | 24 | 56 | 250 | 1900 | 0.015 sec | 0.021 sec | 38 GB | 192 GB |
|
||||
| 16XL | 1,000,000 | 24 | 56 | 250 | 2200 | 0.015 sec | 0.020 sec | 40 GB | 256 GB |
|
||||
|
||||
Accuracy was 0.99 for benchmarks.
|
||||
|
||||
QPS can also be improved by increasing [`m` and `ef_construction`](/docs/guides/ai/going-to-prod#hnsw-understanding-efconstruction--efsearch--and-m). This will allow you to use a smaller value for `ef_search` and increase QPS. For example, increasing `m` to 32 and `ef_construction` to 80 for 4XL will increase QPS to 1280.
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
It is possible to upload more vectors to a single table if Memory allows it (for example, 4XL plan and higher for OpenAI embeddings). But it will affect the performance of the queries: QPS will be lower, and latency will be higher. Scaling should be almost linear, but it is recommended to benchmark your workload to find the optimal number of vectors per table and per database instance.
|
||||
|
||||
</Admonition>
|
||||
|
||||
## IVFFlat
|
||||
|
||||
### 512 Dimensions
|
||||
|
||||
This benchmark uses the [GloVe Reddit comments](https://nlp.stanford.edu/projects/glove/) dataset, which contains 1,623,397 embeddings of text. Each embedding is 512 dimensions. Random vectors were generated for queries.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="glove512"
|
||||
>
|
||||
<TabPanel id="glove512" label="GloVe-512, probes = 10">
|
||||
|
||||
| Plan | Vectors | Lists | QPS | Latency Mean | Latency p95 | RAM Usage | RAM |
|
||||
| ------ | --------- | ----- | ---- | ------------ | ----------- | ------------------ | ------ |
|
||||
| Free | 100,000 | 100 | 250 | 0.395 sec | 0.432 sec | 1 GB + 300 Mb Swap | 1 GB |
|
||||
| Small | 250,000 | 250 | 440 | 0.223 sec | 0.250 sec | 2 GB + 200 Mb Swap | 2 GB |
|
||||
| Medium | 500,000 | 500 | 425 | 0.116 sec | 0.143 sec | 3.7 GB | 4 GB |
|
||||
| Large | 1,000,000 | 1000 | 515 | 0.096 sec | 0.116 sec | 7.5 GB | 8 GB |
|
||||
| XL | 1,623,397 | 1275 | 465 | 0.212 sec | 0.272 sec | 14 GB | 16 GB |
|
||||
| 2XL | 1,623,397 | 1275 | 1400 | 0.061 sec | 0.075 sec | 22 GB | 32 GB |
|
||||
| 4XL | 1,623,397 | 1275 | 1800 | 0.027 sec | 0.043 sec | 20 GB | 64 GB |
|
||||
| 8XL | 1,623,397 | 1275 | 2850 | 0.032 sec | 0.049 sec | 21 GB | 128 GB |
|
||||
| 12XL | 1,623,397 | 1275 | 3700 | 0.020 sec | 0.036 sec | 26 GB | 192 GB |
|
||||
| 16XL | 1,623,397 | 1275 | 3700 | 0.025 sec | 0.042 sec | 29 GB | 256 GB |
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="glove512_60" label="GloVe-512, probes = 60">
|
||||
|
||||
| Plan | Vectors | Lists | QPS | Latency Mean | Latency p95 | RAM Usage | RAM |
|
||||
| ------ | --------- | ----- | --- | ------------ | ----------- | --------- | ------ |
|
||||
| Free | 100,000 | 100 | - | - | - | - | 1 GB |
|
||||
| Small | 250,000 | 250 | - | - | - | - | 2 GB |
|
||||
| Medium | 500,000 | 500 | 75 | 0.656 sec | 0.750 sec | 3.7 GB | 4 GB |
|
||||
| Large | 1,000,000 | 1000 | 102 | 0.488 sec | 0.580 sec | 7.5 GB | 8 GB |
|
||||
| XL | 1,000,000 | 1000 | 188 | 0.525 sec | 0.596 sec | 14 GB | 16 GB |
|
||||
| XL | 1,623,397 | 1275 | 75 | 0.679 sec | 0.798 sec | 14 GB | 16 GB |
|
||||
| 2XL | 1,623,397 | 1275 | 160 | 0.314 sec | 0.384 sec | 22 GB | 32 GB |
|
||||
| 4XL | 1,623,397 | 1275 | 300 | 0.083 sec | 0.113 sec | 20 GB | 64 GB |
|
||||
| 8XL | 1,623,397 | 1275 | 565 | 0.105 sec | 0.141 sec | 21 GB | 128 GB |
|
||||
| 12XL | 1,623,397 | 1275 | 840 | 0.093 sec | 0.124 sec | 26 GB | 192 GB |
|
||||
| 16XL | 1,623,397 | 1275 | 940 | 0.084 sec | 0.108 sec | 29 GB | 256 GB |
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### 960 Dimensions
|
||||
|
||||
This benchmark uses the [gist-960-angular](http://corpus-texmex.irisa.fr/) dataset, which contains 1,000,000 embeddings of images. Each embedding is 960 dimensions.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="gist960"
|
||||
>
|
||||
<TabPanel id="gist960" label="gist-960, probes = 10">
|
||||
|
||||
| Plan | Vectors | Lists | QPS | Latency Mean | Latency p95 | RAM Usage | RAM |
|
||||
| ------ | --------- | ----- | ---- | ------------ | ----------- | ------------------ | ------ |
|
||||
| Free | 30,000 | 30 | 75 | 0.065 sec | 0.088 sec | 1 GB + 100 Mb Swap | 1 GB |
|
||||
| Small | 100,000 | 100 | 78 | 0.064 sec | 0.092 sec | 1.8 GB | 2 GB |
|
||||
| Medium | 250,000 | 250 | 58 | 0.085 sec | 0.129 sec | 3.2 GB | 4 GB |
|
||||
| Large | 500,000 | 500 | 55 | 0.088 sec | 0.140 sec | 5 GB | 8 GB |
|
||||
| XL | 1,000,000 | 1000 | 110 | 0.046 sec | 0.070 sec | 14 GB | 16 GB |
|
||||
| 2XL | 1,000,000 | 1000 | 235 | 0.083 sec | 0.136 sec | 10 GB | 32 GB |
|
||||
| 4XL | 1,000,000 | 1000 | 420 | 0.071 sec | 0.106 sec | 11 GB | 64 GB |
|
||||
| 8XL | 1,000,000 | 1000 | 815 | 0.072 sec | 0.106 sec | 13 GB | 128 GB |
|
||||
| 12XL | 1,000,000 | 1000 | 1150 | 0.052 sec | 0.078 sec | 15.5 GB | 192 GB |
|
||||
| 16XL | 1,000,000 | 1000 | 1345 | 0.072 sec | 0.106 sec | 17.5 GB | 256 GB |
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### 1536 Dimensions
|
||||
|
||||
@@ -31,7 +146,7 @@ This benchmark uses the [dbpedia-entities-openai-1M](https://huggingface.co/data
|
||||
>
|
||||
<TabPanel id="dbpedia1536" label="OpenAI-1536, probes = 10">
|
||||
|
||||
| Plan | Vectors | Lists | RPS | Latency Mean | Latency p95 | RAM Usage | RAM |
|
||||
| Plan | Vectors | Lists | QPS | Latency Mean | Latency p95 | RAM Usage | RAM |
|
||||
| ------ | --------- | ----- | ---- | ------------ | ----------- | ------------------ | ------ |
|
||||
| Free | 20,000 | 40 | 135 | 0.372 sec | 0.412 sec | 1 GB + 200 Mb Swap | 1 GB |
|
||||
| Small | 50,000 | 100 | 140 | 0.357 sec | 0.398 sec | 1.8 GB | 2 GB |
|
||||
@@ -44,12 +159,12 @@ This benchmark uses the [dbpedia-entities-openai-1M](https://huggingface.co/data
|
||||
| 12XL | 1,000,000 | 2000 | 1600 | 0.030 sec | 0.052 sec | 41 GB | 192 GB |
|
||||
| 16XL | 1,000,000 | 2000 | 1790 | 0.029 sec | 0.051 sec | 45 GB | 256 GB |
|
||||
|
||||
For 1,000,000 vectors 10 probes results to precision of 0.91. And for 500,000 vectors and below 10 probes results to precision in the range of 0.95 - 0.99. To increase precision, you need to increase the number of probes.
|
||||
For 1,000,000 vectors 10 probes results to accuracy of 0.91. And for 500,000 vectors and below 10 probes results to accuracy in the range of 0.95 - 0.99. To increase accuracy, you need to increase the number of probes.
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="dbpedia1536_40" label="OpenAI-1536, probes = 40">
|
||||
|
||||
| Plan | Vectors | Lists | RPS | Latency Mean | Latency p95 | RAM Usage | RAM |
|
||||
| Plan | Vectors | Lists | QPS | Latency Mean | Latency p95 | RAM Usage | RAM |
|
||||
| ------ | --------- | ----- | --- | ------------ | ----------- | --------- | ------ |
|
||||
| Free | 20,000 | 40 | - | - | - | - | 1 GB |
|
||||
| Small | 50,000 | 100 | - | - | - | - | 2 GB |
|
||||
@@ -62,7 +177,7 @@ For 1,000,000 vectors 10 probes results to precision of 0.91. And for 500,000 ve
|
||||
| 12XL | 1,000,000 | 2000 | 600 | 0.085 sec | 0.132 sec | 41 GB | 192 GB |
|
||||
| 16XL | 1,000,000 | 2000 | 670 | 0.081 sec | 0.129 sec | 45 GB | 256 GB |
|
||||
|
||||
For 1,000,000 vectors 40 probes results to precision of 0.98. Note that exact values may vary depending on the dataset and queries, we recommend to run benchmarks with your own data to get precise results. Use this table as a reference.
|
||||
For 1,000,000 vectors 40 probes results to accuracy of 0.98. Note that exact values may vary depending on the dataset and queries, we recommend to run benchmarks with your own data to get precise results. Use this table as a reference.
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
@@ -80,82 +195,9 @@ For 1,000,000 vectors 40 probes results to precision of 0.98. Note that exact va
|
||||
/>
|
||||
</div>
|
||||
|
||||
### 960 Dimensions
|
||||
|
||||
This benchmark uses the [gist-960-angular](http://corpus-texmex.irisa.fr/) dataset, which contains 1,000,000 embeddings of images. Each embedding is 960 dimensions.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="gist960"
|
||||
>
|
||||
<TabPanel id="gist960" label="gist-960, probes = 10">
|
||||
|
||||
| Plan | Vectors | Lists | RPS | Latency Mean | Latency p95 | RAM Usage | RAM |
|
||||
| ------ | --------- | ----- | ---- | ------------ | ----------- | ------------------ | ------ |
|
||||
| Free | 30,000 | 30 | 75 | 0.065 sec | 0.088 sec | 1 GB + 100 Mb Swap | 1 GB |
|
||||
| Small | 100,000 | 100 | 78 | 0.064 sec | 0.092 sec | 1.8 GB | 2 GB |
|
||||
| Medium | 250,000 | 250 | 58 | 0.085 sec | 0.129 sec | 3.2 GB | 4 GB |
|
||||
| Large | 500,000 | 500 | 55 | 0.088 sec | 0.140 sec | 5 GB | 8 GB |
|
||||
| XL | 1,000,000 | 1000 | 110 | 0.046 sec | 0.070 sec | 14 GB | 16 GB |
|
||||
| 2XL | 1,000,000 | 1000 | 235 | 0.083 sec | 0.136 sec | 10 GB | 32 GB |
|
||||
| 4XL | 1,000,000 | 1000 | 420 | 0.071 sec | 0.106 sec | 11 GB | 64 GB |
|
||||
| 8XL | 1,000,000 | 1000 | 815 | 0.072 sec | 0.106 sec | 13 GB | 128 GB |
|
||||
| 12XL | 1,000,000 | 1000 | 1150 | 0.052 sec | 0.078 sec | 15.5 GB | 192 GB |
|
||||
| 16XL | 1,000,000 | 1000 | 1345 | 0.072 sec | 0.106 sec | 17.5 GB | 256 GB |
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### 512 Dimensions
|
||||
|
||||
This benchmark uses the [GloVe Reddit comments](https://nlp.stanford.edu/projects/glove/) dataset, which contains 1,623,397 embeddings of text. Each embedding is 512 dimensions. Random vectors were generated for queries.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="glove512"
|
||||
>
|
||||
<TabPanel id="glove512" label="GloVe-512, probes = 10">
|
||||
|
||||
| Plan | Vectors | Lists | RPS | Latency Mean | Latency p95 | RAM Usage | RAM |
|
||||
| ------ | --------- | ----- | ---- | ------------ | ----------- | ------------------ | ------ |
|
||||
| Free | 100,000 | 100 | 250 | 0.395 sec | 0.432 sec | 1 GB + 300 Mb Swap | 1 GB |
|
||||
| Small | 250,000 | 250 | 440 | 0.223 sec | 0.250 sec | 2 GB + 200 Mb Swap | 2 GB |
|
||||
| Medium | 500,000 | 500 | 425 | 0.116 sec | 0.143 sec | 3.7 GB | 4 GB |
|
||||
| Large | 1,000,000 | 1000 | 515 | 0.096 sec | 0.116 sec | 7.5 GB | 8 GB |
|
||||
| XL | 1,623,397 | 1275 | 465 | 0.212 sec | 0.272 sec | 14 GB | 16 GB |
|
||||
| 2XL | 1,623,397 | 1275 | 1400 | 0.061 sec | 0.075 sec | 22 GB | 32 GB |
|
||||
| 4XL | 1,623,397 | 1275 | 1800 | 0.027 sec | 0.043 sec | 20 GB | 64 GB |
|
||||
| 8XL | 1,623,397 | 1275 | 2850 | 0.032 sec | 0.049 sec | 21 GB | 128 GB |
|
||||
| 12XL | 1,623,397 | 1275 | 3700 | 0.020 sec | 0.036 sec | 26 GB | 192 GB |
|
||||
| 16XL | 1,623,397 | 1275 | 3700 | 0.025 sec | 0.042 sec | 29 GB | 256 GB |
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="glove512_60" label="GloVe-512, probes = 60">
|
||||
|
||||
| Plan | Vectors | Lists | RPS | Latency Mean | Latency p95 | RAM Usage | RAM |
|
||||
| ------ | --------- | ----- | --- | ------------ | ----------- | --------- | ------ |
|
||||
| Free | 100,000 | 100 | - | - | - | - | 1 GB |
|
||||
| Small | 250,000 | 250 | - | - | - | - | 2 GB |
|
||||
| Medium | 500,000 | 500 | 75 | 0.656 sec | 0.750 sec | 3.7 GB | 4 GB |
|
||||
| Large | 1,000,000 | 1000 | 102 | 0.488 sec | 0.580 sec | 7.5 GB | 8 GB |
|
||||
| XL | 1,000,000 | 1000 | 188 | 0.525 sec | 0.596 sec | 14 GB | 16 GB |
|
||||
| XL | 1,623,397 | 1275 | 75 | 0.679 sec | 0.798 sec | 14 GB | 16 GB |
|
||||
| 2XL | 1,623,397 | 1275 | 160 | 0.314 sec | 0.384 sec | 22 GB | 32 GB |
|
||||
| 4XL | 1,623,397 | 1275 | 300 | 0.083 sec | 0.113 sec | 20 GB | 64 GB |
|
||||
| 8XL | 1,623,397 | 1275 | 565 | 0.105 sec | 0.141 sec | 21 GB | 128 GB |
|
||||
| 12XL | 1,623,397 | 1275 | 840 | 0.093 sec | 0.124 sec | 26 GB | 192 GB |
|
||||
| 16XL | 1,623,397 | 1275 | 940 | 0.084 sec | 0.108 sec | 29 GB | 256 GB |
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
It is possible to upload more vectors to a single table if Memory allows it (for example, 4XL plan and higher for OpenAI embeddings). But it will affect the performance of the queries: RPS will be lower, and latency will be higher. Scaling should be almost linear, but it is recommended to benchmark your workload to find the optimal number of vectors per table and per database instance.
|
||||
It is possible to upload more vectors to a single table if Memory allows it (for example, 4XL plan and higher for OpenAI embeddings). But it will affect the performance of the queries: QPS will be lower, and latency will be higher. Scaling should be almost linear, but it is recommended to benchmark your workload to find the optimal number of vectors per table and per database instance.
|
||||
|
||||
</Admonition>
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ const { data, error } = await supabase
|
||||
|
||||
## Enterprise workloads
|
||||
|
||||
As you move into production, we recommend running splitting your collections into separate projects. This is because it allows your vector stores to scale independently of your production data. Vectors typically grow faster than operational data, and they have different resource requirements. Running them on separate databases removes the single-point-of-failure.
|
||||
As you move into production, we recommend splitting your collections into separate projects. This is because it allows your vector stores to scale independently of your production data. Vectors typically grow faster than operational data, and they have different resource requirements. Running them on separate databases removes the single-point-of-failure.
|
||||
|
||||
<div>
|
||||
<img
|
||||
|
||||
@@ -8,35 +8,77 @@ export const meta = {
|
||||
sidebar_label: 'Going to Production',
|
||||
}
|
||||
|
||||
This guide will help you to prepare your application for production. We'll provide actionable steps to help you scale your application, ensure that it is reliable, can handle the load, and provide optimal precision for your use case.
|
||||
This guide will help you to prepare your application for production. We'll provide actionable steps to help you scale your application, ensure that it is reliable, can handle the load, and provide optimal accuracy for your use case.
|
||||
|
||||
See our [Engineering for Scale](/docs/guides/ai/engineering-for-scale) guide for more information about engineering at scale.
|
||||
|
||||
## Do you need indexes?
|
||||
|
||||
Sequential scans will result in significantly higher latencies and lower throughput, guaranteeing 100% precision and not being RAM bound.
|
||||
Sequential scans will result in significantly higher latencies and lower throughput, guaranteeing 100% accuracy and not being RAM bound.
|
||||
|
||||
There are a couple of cases where you might not need indexes:
|
||||
|
||||
- You have a small dataset and don't need to scale it.
|
||||
- You are not expecting high amounts of vector search queries per second.
|
||||
- You need to guarantee 100% precision.
|
||||
- You need to guarantee 100% accuracy.
|
||||
|
||||
You don't have to create indexes in these cases and can use sequential scans instead. This type of workload will not be RAM bound and will not require any additional resources but will result in higher latencies and lower throughput. Extra CPU cores may help to improve queries per second, but it will not help to improve latency.
|
||||
|
||||
On the other hand, if you need to scale your application, you will need to create indexes. This will result in lower latencies and higher throughput, but will require additional RAM to make use of Postgres Caching. Also, using indexes will result in lower precision, since you are replacing exact (KNN) search with approximate (ANN) search.
|
||||
On the other hand, if you need to scale your application, you will need to [create indexes](/docs/guides/ai/vector-indexes). This will result in lower latencies and higher throughput, but will require additional RAM to make use of Postgres Caching. Also, using indexes will result in lower accuracy, since you are replacing exact (KNN) search with approximate (ANN) search.
|
||||
|
||||
## Understanding `probes` and `lists`
|
||||
## HNSW vs IVFFlat indexes
|
||||
|
||||
`pgvector` supports two types of indexes: HNSW and IVFFlat. We recommend using [HNSW](/docs/guides/ai/vector-indexes/hnsw-indexes) because of its [performance](https://supabase.com/blog/increase-performance-pgvector-hnsw#hnsw-performance-1536-dimensions) and [robustness against changing data](/docs/guides/ai/vector-indexes/hnsw-indexes#when-should-you-create-hnsw-indexes).
|
||||
|
||||
<div>
|
||||
<img
|
||||
alt="dbpedia embeddings comparing ivfflat and hnsw queries-per-second using the 4XL compute addon (light)"
|
||||
className="dark:hidden"
|
||||
src="/docs/img/ai/going-prod/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="/docs/img/ai/going-prod/dbpedia-ivfflat-vs-hnsw-4xl--dark.png"
|
||||
/>
|
||||
</div>
|
||||
|
||||
## HNSW, understanding `ef_construction`, `ef_search`, and `m`
|
||||
|
||||
Index build parameters:
|
||||
|
||||
- `m` is the number of bi-directional links created for every new element during construction. Higher `m` is suitable for datasets with high dimensionality and/or high accuracy requirements. Reasonable values for `m` are between 2 and 100. Range 12-48 is a good starting point for most use cases (16 is the default value).
|
||||
|
||||
- `ef_construction` is the size of the dynamic list for the nearest neighbors (used during the construction algorithm). Higher `ef_construction` will result in better index quality and higher accuracy, but it will also increase the time required to build the index. `ef_construction` has to be at least 2 \* `m` (64 is the default value). At some point, increasing `ef_construction` does not improve the quality of the index. You can measure accuracy when `ef_search`=`ef_construction`: if accuracy is lower than 0.9, then there is room for improvement.
|
||||
|
||||
Search parameters:
|
||||
|
||||
- `ef_search` is the size of the dynamic list for the nearest neighbors (used during the search). Increasing `ef_search` will result in better accuracy, but it will also increase the time required to execute a query (40 is the default value).
|
||||
|
||||
<div>
|
||||
<img
|
||||
alt="dbpedia embeddings comparing hnsw queries-per-second using different build parameters (light)"
|
||||
className="dark:hidden"
|
||||
src="/docs/img/ai/going-prod/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="/docs/img/ai/going-prod/dbpedia-hnsw-build-parameters--dark.png"
|
||||
/>
|
||||
</div>
|
||||
|
||||
## IVFFlat, understanding `probes` and `lists`
|
||||
|
||||
Indexes used for approximate vector similarity search in pgvector divides a dataset into partitions. The number of these partitions is defined by the `lists` constant. The `probes` controls how many lists are going to be searched during a query.
|
||||
|
||||
The values of lists and probes directly affect precision and requests per second (RPS).
|
||||
The values of lists and probes directly affect accuracy and queries per second (QPS).
|
||||
|
||||
- Higher `lists` means an index will be built slower, but you can achieve better RPS and precision.
|
||||
- Higher `probes` means that select queries will be slower, but you can achieve better precision.
|
||||
- `lists` and `probes` are not independent. Higher `lists` means that you will have to use higher `probes` to achieve the same precision.
|
||||
- Higher `lists` means an index will be built slower, but you can achieve better QPS and accuracy.
|
||||
- Higher `probes` means that select queries will be slower, but you can achieve better accuracy.
|
||||
- `lists` and `probes` are not independent. Higher `lists` means that you will have to use higher `probes` to achieve the same accuracy.
|
||||
|
||||
You can find more examples of how `lists` and `probes` constants affect precision and RPS in [pgvector 0.4.0 performance](https://supabase.com/blog/pgvector-performance) blogpost.
|
||||
You can find more examples of how `lists` and `probes` constants affect accuracy and QPS in [pgvector 0.4.0 performance](https://supabase.com/blog/pgvector-performance) blogpost.
|
||||
|
||||
<div>
|
||||
<img
|
||||
@@ -58,21 +100,22 @@ First, a few generic tips which you can pick and choose from:
|
||||
1. The Supabase managed platform will automatically optimize Postgres configs for you based on your compute addon. But if you self-host, consider **adjusting your Postgres config** based on RAM & CPU cores. See [example optimizations](https://gist.github.com/egor-romanov/323e2847851bbd758081511785573c08) for more details.
|
||||
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 before transitioning to production or running benchmarks.
|
||||
- Execute 10,000 to 50,000 "warm-up" queries before each benchmark, matching the number of `probes` you are going to use in production. Additionally, you can execute about 1,000 queries with probes ranging from three to ten times the prod's probes. Both of these help to increase RAM utilization.
|
||||
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 1,000,000 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 requests per second (RPS).
|
||||
- Use [pg_prewarm](https://www.postgresql.org/docs/current/pgprewarm.html) to load the index into RAM `select pg_prewarm('vecs.docs_vec_idx');`. This will help to avoid cold cache issues.
|
||||
- Execute 10,000 to 50,000 "warm-up" queries before each benchmark/prod. This will help to utilize cache and buffers more efficiently.
|
||||
4. **Establish your workload.** Finetune `m` and `ef_construction` or `lists` constants for the pgvector index to accelerate your queries (at the expense of a slower build times). For instance, for benchmarks with 1,000,000 OpenAI embeddings, we set `m` and `ef_construction` to 32 and 80, and it resulted in 35% higher QPS than 24 and 56 values respectively.
|
||||
5. **Benchmark your own specific workloads.** Doing this during cache warm-up helps gauge the best value for the index build parameters, balancing accuracy with queries per second (QPS).
|
||||
|
||||
## Going into production
|
||||
|
||||
1. Decide if you are going to use indexes or not. You can skip the rest of this guide if you do not use indexes.
|
||||
2. 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.)
|
||||
3. Upload your data to the database. If you use the [`vecs`](/docs/guides/ai/python/api) library, it will automatically generate an index with default parameters.
|
||||
4. Run a benchmark using randomly generated queries and observe the results. Again, you can use the `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.
|
||||
4. Run a benchmark using randomly generated queries and observe the results. Again, you can use the `vecs` library with the `ann-benchmarks` tool. Do it with default values for index build parameters, you can later adjust them to get the best results.
|
||||
5. Monitor the RAM usage, and save it as a note for yourself. You would likely want to use a compute add-on in the future that has the same amount of RAM that was used at the moment (both actual RAM usage and RAM used for cache and buffers).
|
||||
6. Scale down your compute add-on to the one that would have the same amount of RAM used at the moment.
|
||||
7. Repeat step 3 to load the data into RAM. You should see RPS increase on subsequent runs, and stop when it no longer increases. Then repeat the benchmark with probes set to a higher value if you haven't already performed it for that compute add-on size.
|
||||
8. Run a benchmark using real queries and observe the results. You can use the `vecs` library for that as well with `ann-benchmarks` tool. Set probes to 10 (default) and then gradually increase/decrease probes until you see that both precision and RPS match your requirements.
|
||||
9. 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 a 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.
|
||||
7. Repeat step 3 to load the data into RAM. You should see QPS increase on subsequent runs, and stop when it no longer increases.
|
||||
8. Run a benchmark using real queries and observe the results. You can use the `vecs` library for that as well with `ann-benchmarks` tool. Tweak `ef_search` for HNSW or `probes` for IVFFlat until you see that both accuracy and QPS match your requirements.
|
||||
9. If you want higher QPS you can increase `m` and `ef_construction` for HNSW or `lists` for IVFFlat parameters (consider switching from IVF to HNSW). You have to rebuild the index with a higher `m` and `ef_construction` values and repeat steps 6-7 to find the best combination of `m`, `ef_construction` and `ef_search` constants to achieve the best QPS and accuracy values. Higher `m`, `ef_construction` mean that index will build slower, but you can achieve better QPS and accuracy. Higher `ef_search` mean that select queries will be slower, but you can achieve better accuracy.
|
||||
|
||||
## Useful links
|
||||
|
||||
@@ -80,7 +123,7 @@ Don't forget to check out the general [Production Checklist](/docs/guides/platfo
|
||||
|
||||
You can look at our [Choosing Compute Add-on](/docs/guides/ai/choosing-compute-addon) guide to get a basic understanding of how much compute you might need for your workload.
|
||||
|
||||
Or take a look at our [pgvector 0.4.0 performance](https://supabase.com/blog/pgvector-performance) blog post to see what pgvector is capable of and how the above technique can be used to achieve the best results.
|
||||
Or take a look at our [pgvector 0.5.0 performance](https://supabase.com/blog/increase-performance-pgvector-hnsw) and [pgvector 0.4.0 performance](https://supabase.com/blog/pgvector-performance) blog posts to see what pgvector is capable of and how the above technique can be used to achieve the best results.
|
||||
|
||||
<div>
|
||||
<img
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
id: 'ai-collections',
|
||||
title: 'Managing collections',
|
||||
description: 'Learn how to manage groups of vector records using the vecs Python library',
|
||||
sidebar_label: 'Managing collections',
|
||||
}
|
||||
|
||||
A collection is a group of vector records managed by the `vecs` Python library. Records can be added to or updated in a collection. Collections can be queried at any time, but should be indexed for scalable query performance.
|
||||
|
||||
Supabase provides a [Python client](/docs/guides/ai/vecs-python-client) called `vecs` for managing unstructured vector stores in Postgres. If you come from a data science background, this unstructured data approach will feel familiar. If you are more interested in a structured data approach, see [Vector columns](/docs/guides/ai/vector-columns) or read our guide on [Structured & Unstructured Embeddings](/docs/guides/ai/structured-unstructured).
|
||||
|
||||
Under the hood `vecs` will manage the necessary Postgres tables and columns to store and query your collections.
|
||||
|
||||
## API
|
||||
|
||||
Find the full API in the [official API docs](https://supabase.github.io/vecs/api).
|
||||
|
||||
### Connecting
|
||||
|
||||
Before you can interact with vecs, create the client to communicate with Postgres.
|
||||
|
||||
```python
|
||||
import vecs
|
||||
|
||||
DB_CONNECTION = "postgresql://<user>:<password>@<host>:<port>/<db_name>"
|
||||
|
||||
# create vector store client
|
||||
vx = vecs.create_client(DB_CONNECTION)
|
||||
```
|
||||
|
||||
### Create collection
|
||||
|
||||
You can create a collection to store vectors specifying the collection's name and the number of dimensions in the vectors you intend to store.
|
||||
|
||||
```python
|
||||
docs = vx.create_collection(name="docs", dimension=3)
|
||||
```
|
||||
|
||||
If another collection exists with the same name,
|
||||
|
||||
### Get an existing collection
|
||||
|
||||
To access a previously created collection, use `get_collection` to retrieve it by name
|
||||
|
||||
```python
|
||||
docs = vx.get_collection(name="docs")
|
||||
```
|
||||
|
||||
### Upserting vectors
|
||||
|
||||
`vecs` combines the concepts of "insert" and "update" into "upsert". Upserting records adds them to the collection if the `id` is not present, or updates the existing record if the `id` does exist.
|
||||
|
||||
```python
|
||||
# add records to the collection
|
||||
docs.upsert(
|
||||
vectors=[
|
||||
(
|
||||
"vec0", # the vector's identifier
|
||||
[0.1, 0.2, 0.3], # the vector. list or np.array
|
||||
{"year": 1973} # associated metadata
|
||||
),
|
||||
(
|
||||
"vec1",
|
||||
[0.7, 0.8, 0.9],
|
||||
{"year": 2012}
|
||||
)
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
### Create an index
|
||||
|
||||
Collections can be queried immediately after being created.
|
||||
However, for good performance, the collection should be indexed after records have been upserted.
|
||||
|
||||
Indexes should be created **after** the collection has been populated with records. Building an index on an empty collection will significantly reduce recall. Once the index has been created you can still upsert new documents into the collection but you should rebuild the index if the size of the collection more than doubles.
|
||||
|
||||
Only one index may exist per-collection. By default, creating an index will replace any existing index.
|
||||
|
||||
To create an index:
|
||||
|
||||
```python
|
||||
##
|
||||
# INSERT RECORDS HERE
|
||||
##
|
||||
|
||||
# index the collection to be queried by cosine distance
|
||||
docs.create_index(measure=vecs.IndexMeasure.cosine_distance)
|
||||
```
|
||||
|
||||
Available options for query `measure` are:
|
||||
|
||||
- `vecs.IndexMeasure.cosine_distance`
|
||||
- `vecs.IndexMeasure.l2_distance`
|
||||
- `vecs.IndexMeasure.max_inner_product`
|
||||
|
||||
which correspond to different methods for comparing query vectors to the vectors in the database.
|
||||
|
||||
If you aren't sure which to use, stick with the default (cosine_distance) by omitting the parameter i.e.: `docs.create_index()`.
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
The time required to create an index grows with the number of records and size of vectors. For a few thousand records expect sub-minute a response in under a minute. It may take a few minutes for larger collections.
|
||||
|
||||
</Admonition>
|
||||
|
||||
For an in-depth guide on vector indexes, see [Managing indexes](/docs/guides/ai/managing-indexes).
|
||||
|
||||
### Query
|
||||
|
||||
Be aware that indexes are essential for good performance. If you do not create an index, every query will return a warning that includes the `IndexMeasure` you should index.
|
||||
|
||||
#### Basic
|
||||
|
||||
The simplest form of search is to provide a query vector.
|
||||
|
||||
```python
|
||||
docs.query(
|
||||
query_vector=[0.4,0.5,0.6], # required
|
||||
limit=5, # number of records to return
|
||||
filters={}, # metadata filters
|
||||
measure="cosine_distance", # distance measure to use
|
||||
include_value=False, # should distance measure values be returned?
|
||||
include_metadata=False, # should record metadata be returned?
|
||||
)
|
||||
```
|
||||
|
||||
Which returns a list of vector record `ids`.
|
||||
|
||||
#### Metadata Filtering
|
||||
|
||||
The metadata that is associated with each record can also be filtered during a query.
|
||||
|
||||
As an example, `{"year": {"$eq": 2005}}` filters a `year` metadata key to be equal to 2005
|
||||
|
||||
In context:
|
||||
|
||||
```python
|
||||
docs.query(
|
||||
query_vector=[0.4,0.5,0.6],
|
||||
filters={"year": {"$eq": 2012}}, # metadata filters
|
||||
)
|
||||
```
|
||||
|
||||
For a complete reference, see the [metadata guide](https://supabase.github.io/vecs/concepts_metadata/).
|
||||
|
||||
## Resources
|
||||
|
||||
- Official Vecs Documentation: https://supabase.github.io/vecs/api
|
||||
- Source Code: https://github.com/supabase/vecs
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,18 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
id: 'ai-python-clients',
|
||||
title: 'Choosing a Client',
|
||||
description: 'Learn how to manage vectors using Python',
|
||||
sidebar_label: 'Choosing a Client',
|
||||
}
|
||||
|
||||
As described in [Structured & Unstructured Embeddings](/docs/guides/ai/structured-unstructured), AI workloads come in many forms.
|
||||
|
||||
For data science or ephemeral workloads, the [Supabase Vecs](https://supabase.github.io/vecs/) client gets you started quickly. All you need is a connection string and vecs handles setting up your database to store and query vectors with associated metadata.
|
||||
|
||||
For production python applications with version controlled migrations, we recommend adding first class vector support to your toolchain by [registering the vector type with your ORM](https://github.com/pgvector/pgvector-python). pgvector provides bindings for the most commonly used SQL drivers/libraries including Django, SQLAlchemy, SQLModel, psycopg, asyncpg and Peewee.
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -79,7 +79,7 @@ docs.query(
|
||||
|
||||
## Deep Dive
|
||||
|
||||
For a more in-depth guide on `vecs` collections, see [Managing collections](/docs/guides/ai/managing-collections).
|
||||
For a more in-depth guide on `vecs` collections, see [API](/docs/guides/ai/python/api).
|
||||
|
||||
## Resources
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ export const meta = {
|
||||
sidebar_label: 'Vector columns',
|
||||
}
|
||||
|
||||
Supabase offers a number of different ways to store and query vectors within Postgres. If you prefer to use Python to store and query your vectors using collections, see [Managing collections](/docs/guides/ai/managing-collections). If you want more control over vectors within your own Postgres tables or would like to interact with them using a different language like JavaScript, keep reading.
|
||||
Supabase offers a number of different ways to store and query vectors within Postgres. The SQL included in this guide is applicable for clients in all programming languages. If you are a Python user see your [Python client options](/docs/guides/ai/python-clients) after reading the `Learn` section.
|
||||
|
||||
Vectors in Supabase are enabled via [pgvector](https://github.com/pgvector/pgvector/), a PostgreSQL extension for storing and querying vectors in Postgres. It can be used to store [embeddings](/docs/guides/ai/concepts#what-are-embeddings).
|
||||
|
||||
@@ -162,7 +162,7 @@ Vectors and embeddings can be used for much more than search. Learn more about e
|
||||
|
||||
### Indexes
|
||||
|
||||
Once your vector table starts to grow, you will likely want to add an index to speed up queries. See [Managing indexes](/docs/guides/ai/managing-indexes) to learn how vector indexes work and how to create them.
|
||||
Once your vector table starts to grow, you will likely want to add an index to speed up queries. See [Vector indexes](/docs/guides/ai/vector-indexes) to learn how vector indexes work and how to create them.
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
id: 'ai-vector-indexes',
|
||||
title: 'Vector indexes',
|
||||
description: 'Understanding vector indexes',
|
||||
sidebar_label: 'Vector indexes',
|
||||
}
|
||||
|
||||
Once your vector table starts to grow, you will likely want to add an index to speed up queries. Without indexes, you'll be performing a sequential scan which can be a resource-intensive operation when you have many records.
|
||||
|
||||
## Choosing an index
|
||||
|
||||
Today `pgvector` supports two types of indexes:
|
||||
|
||||
- [HNSW](/docs/guides/ai/vector-indexes/hnsw-indexes)
|
||||
- [IVFFlat](/docs/guides/ai/vector-indexes/ivf-indexes)
|
||||
|
||||
In general we recommend using [HNSW](/docs/guides/ai/vector-indexes/hnsw-indexes) because of its [performance](https://supabase.com/blog/increase-performance-pgvector-hnsw#hnsw-performance-1536-dimensions) and [robustness against changing data](/docs/guides/ai/vector-indexes/hnsw-indexes#when-should-you-create-hnsw-indexes).
|
||||
|
||||
## Distance operators
|
||||
|
||||
Indexes can be used to improve performance of nearest neighbor search using various distance measures. `pgvector` includes 3 distance operators:
|
||||
|
||||
| Operator | Description | [**Operator class**](https://www.postgresql.org/docs/current/sql-createopclass.html) |
|
||||
| -------- | ---------------------- | ------------------------------------------------------------------------------------ |
|
||||
| `<->` | Euclidean distance | `vector_l2_ops` |
|
||||
| `<#>` | negative inner product | `vector_ip_ops` |
|
||||
| `<=>` | cosine distance | `vector_cosine_ops` |
|
||||
|
||||
Currently vectors with up to 2,000 dimensions can be indexed.
|
||||
|
||||
## Resources
|
||||
|
||||
Read more about indexing on `pgvector`'s [GitHub page](https://github.com/pgvector/pgvector#indexing).
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,101 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
id: 'ai-hnsw-indexes',
|
||||
title: 'HNSW indexes',
|
||||
description: 'Understanding HNSW indexes in pgvector',
|
||||
sidebar_label: 'HNSW indexes',
|
||||
}
|
||||
|
||||
HNSW is an algorithm for approximate nearest neighbor search. It is a frequently used index type that can improve performance when querying highly-dimensional vectors, like those representing embeddings.
|
||||
|
||||
## Usage
|
||||
|
||||
The way you create an HNSW index depends on the distance operator you are using. `pgvector` includes 3 distance operators:
|
||||
|
||||
| Operator | Description | [**Operator class**](https://www.postgresql.org/docs/current/sql-createopclass.html) |
|
||||
| -------- | ---------------------- | ------------------------------------------------------------------------------------ |
|
||||
| `<->` | Euclidean distance | `vector_l2_ops` |
|
||||
| `<#>` | negative inner product | `vector_ip_ops` |
|
||||
| `<=>` | cosine distance | `vector_cosine_ops` |
|
||||
|
||||
Use the following SQL commands to create an HNSW index for the operator(s) used in your queries.
|
||||
|
||||
### Euclidean L2 distance (`vector_l2_ops`)
|
||||
|
||||
```sql
|
||||
create index on items using hnsw (column_name vector_l2_ops);
|
||||
```
|
||||
|
||||
### Inner product (`vector_ip_ops`)
|
||||
|
||||
```sql
|
||||
create index on items using hnsw (column_name vector_ip_ops);
|
||||
```
|
||||
|
||||
### Cosine distance (`vector_cosine_ops`)
|
||||
|
||||
```sql
|
||||
create index on items using hnsw (column_name vector_cosine_ops);
|
||||
```
|
||||
|
||||
Currently vectors with up to 2,000 dimensions can be indexed.
|
||||
|
||||
## How does HNSW work?
|
||||
|
||||
HNSW uses proximity graphs (graphs connecting nodes based on distance between them) to approximate nearest-neighbor search. 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="/docs/img/ai/vector-indexes/hnsw-indexes/skip-list--light.png"
|
||||
/>
|
||||
<img
|
||||
alt="visual of an example skip list (dark)"
|
||||
className="hidden dark:block"
|
||||
src="/docs/img/ai/vector-indexes/hnsw-indexes/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 `NULL`), 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="/docs/img/ai/vector-indexes/hnsw-indexes/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.
|
||||
|
||||
## When should you create HNSW indexes?
|
||||
|
||||
HNSW should be your default choice when creating a vector index. Add the index when you don't need 100% accuracy and are willing to trade a small amount of accuracy for a lot of throughput.
|
||||
|
||||
Unlike IVFFlat indexes, you are safe to build an HNSW index immediately after the table is created. HNSW indexes are based on graphs which inherently are not affected by the same limitations as IVFFlat. As new data is added to the table, the index will be filled automatically and the index structure will remain optimal.
|
||||
|
||||
## Resources
|
||||
|
||||
Read more about indexing on `pgvector`'s [GitHub page](https://github.com/pgvector/pgvector#indexing).
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -1,17 +1,58 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
id: 'ai-managing-indexes',
|
||||
title: 'Managing indexes',
|
||||
description: 'Understanding vector indexes',
|
||||
sidebar_label: 'Managing indexes',
|
||||
id: 'ai-ivf-indexes',
|
||||
title: 'IVFFlat indexes',
|
||||
description: 'Understanding IVFFlat indexes in pgvector',
|
||||
sidebar_label: 'IVFFlat indexes',
|
||||
}
|
||||
|
||||
Once your vector table starts to grow, you will likely want to add an index to speed up queries. Without indexes, you'll be performing a sequential scan which can be a resource-intensive operation when you have many records.
|
||||
IVFFlat is a type of vector index for approximate nearest neighbor search. It is a frequently used index type that can improve performance when querying highly-dimensional vectors, like those representing embeddings.
|
||||
|
||||
## IVFFlat indexes
|
||||
## Choosing an index
|
||||
|
||||
Today `pgvector` indexes use an algorithm called IVFFlat. IVF stands for 'inverted file indexes'. It works by clustering your vectors in order to reduce the similarity search scope. Rather than comparing a vector to every other vector, the vector is only compared against vectors within the same cell cluster (or nearby clusters, depending on your configuration).
|
||||
Today `pgvector` supports two types of indexes:
|
||||
|
||||
- [HNSW](/docs/guides/ai/vector-indexes/hnsw-indexes)
|
||||
- [IVFFlat](/docs/guides/ai/vector-indexes/ivf-indexes)
|
||||
|
||||
In general we recommend using [HNSW](/docs/guides/ai/vector-indexes/hnsw-indexes) because of its [performance](https://supabase.com/blog/increase-performance-pgvector-hnsw#hnsw-performance-1536-dimensions) and [robustness against changing data](/docs/guides/ai/vector-indexes/hnsw-indexes#when-should-you-create-hnsw-indexes). If you have a special use case that requires IVFFlat instead, keep reading.
|
||||
|
||||
## Usage
|
||||
|
||||
The way you create an IVFFlat index depends on the distance operator you are using. `pgvector` includes 3 distance operators:
|
||||
|
||||
| Operator | Description | [**Operator class**](https://www.postgresql.org/docs/current/sql-createopclass.html) |
|
||||
| -------- | ---------------------- | ------------------------------------------------------------------------------------ |
|
||||
| `<->` | Euclidean distance | `vector_l2_ops` |
|
||||
| `<#>` | negative inner product | `vector_ip_ops` |
|
||||
| `<=>` | cosine distance | `vector_cosine_ops` |
|
||||
|
||||
Use the following SQL commands to create an IVFFlat index for the operator(s) used in your queries.
|
||||
|
||||
### Euclidean L2 distance (`vector_l2_ops`)
|
||||
|
||||
```sql
|
||||
create index on items using ivfflat (column_name vector_l2_ops) with (lists = 100);
|
||||
```
|
||||
|
||||
### Inner product (`vector_ip_ops`)
|
||||
|
||||
```sql
|
||||
create index on items using ivfflat (column_name vector_ip_ops) with (lists = 100);
|
||||
```
|
||||
|
||||
### Cosine distance (`vector_cosine_ops`)
|
||||
|
||||
```sql
|
||||
create index on items using ivfflat (column_name vector_cosine_ops) with (lists = 100);
|
||||
```
|
||||
|
||||
Currently vectors with up to 2,000 dimensions can be indexed.
|
||||
|
||||
## How does IVFFlat work?
|
||||
|
||||
IVF stands for 'inverted file indexes'. It works by clustering your vectors in order to reduce the similarity search scope. Rather than comparing a vector to every other vector, the vector is only compared against vectors within the same cell cluster (or nearby clusters, depending on your configuration).
|
||||
|
||||
### Inverted lists (cell clusters)
|
||||
|
||||
@@ -48,43 +89,9 @@ If the number of probes is the same as the number of lists, exact nearest neighb
|
||||
|
||||
One important note with IVF indexes is that nearest neighbor search is approximate, since exact search on high dimensional data can't be indexed efficiently. This means that similarity results will change (slightly) after you add an index (trading recall for speed).
|
||||
|
||||
## Distance operators
|
||||
## When should you create IVFFlat indexes?
|
||||
|
||||
The type of index required depends on the distance operator you are using. `pgvector` includes 3 distance operators:
|
||||
|
||||
| Operator | Description | [**Operator class**](https://www.postgresql.org/docs/current/sql-createopclass.html) |
|
||||
| -------- | ---------------------- | ------------------------------------------------------------------------------------ |
|
||||
| `<->` | Euclidean distance | `vector_l2_ops` |
|
||||
| `<#>` | negative inner product | `vector_ip_ops` |
|
||||
| `<=>` | cosine distance | `vector_cosine_ops` |
|
||||
|
||||
Use the following SQL commands to create an index for the operator(s) used in your queries.
|
||||
|
||||
### Euclidean L2 distance (`vector_l2_ops`)
|
||||
|
||||
```sql
|
||||
create index on items using ivfflat (column_name vector_l2_ops) with (lists = 100);
|
||||
```
|
||||
|
||||
### Inner product (`vector_ip_ops`)
|
||||
|
||||
```sql
|
||||
create index on items using ivfflat (column_name vector_ip_ops) with (lists = 100);
|
||||
```
|
||||
|
||||
### Cosine distance (`vector_cosine_ops`)
|
||||
|
||||
```sql
|
||||
create index on items using ivfflat (column_name vector_cosine_ops) with (lists = 100);
|
||||
```
|
||||
|
||||
Currently vectors with up to 2,000 dimensions can be indexed.
|
||||
|
||||
If you are using the `vecs` Python library, follow the instructions in [Managing collections](/docs/guides/ai/managing-collections#create-an-index) to create indexes.
|
||||
|
||||
## When should you add indexes?
|
||||
|
||||
`pgvector` recommends adding indexes only after the table has sufficient data, so that the internal IVFFlat cell clusters are based on your data's distribution. Anytime the distribution changes significantly, consider recreating indexes.
|
||||
`pgvector` recommends building IVFFlat indexes only after the table has sufficient data, so that the internal IVFFlat cell clusters are based on your data's distribution. Anytime the distribution changes significantly, consider rebuilding indexes.
|
||||
|
||||
## Resources
|
||||
|
||||
@@ -6,12 +6,10 @@ export const meta = {
|
||||
id: 'auth',
|
||||
title: 'Auth',
|
||||
description: 'Use Supabase to Authenticate and Authorize your users.',
|
||||
sidebar_label: 'Overview',
|
||||
subtitle: 'Use Supabase to authenticate and authorize your users.',
|
||||
video: 'https://www.youtube.com/v/6ow_jW4epf8',
|
||||
}
|
||||
|
||||
## Overview
|
||||
|
||||
There are two parts to every Auth system:
|
||||
|
||||
- **Authentication:** should this person be allowed in? If yes, who are they?
|
||||
@@ -64,56 +62,13 @@ You can enable third-party providers with the click of a button by navigating to
|
||||
|
||||
### Redirect URLs and wildcards
|
||||
|
||||
When using third-party providers, the [Supabase client library](/docs/reference/javascript/auth-signinwithoauth#sign-in-using-a-third-party-provider-with-redirect) redirects the user to the provider. When the third-party provider successfully authenticates the user, the provider redirects the user to the Supabase Auth callback URL where they are further redirected to the URL specified in the `redirectTo` parameter. This parameter defaults to the [`SITE_URL`](/docs/reference/auth/config#site_url). You can modify the `SITE_URL` or add additional [redirect URLs](https://supabase.com/dashboard/project/_/auth/url-configuration).
|
||||
We've moved the guide for setting up redirect URLs [here](/docs/guides/auth/concepts/redirect-urls).
|
||||
|
||||
You can use wildcard match patterns to support preview URLs from providers like Netlify and Vercel. See the [full list of supported patterns](https://pkg.go.dev/github.com/gobwas/glob#Compile). Use [this tool](https://www.digitalocean.com/community/tools/glob?comments=true&glob=http%3A%2F%2Flocalhost%3A3000%2F%2A%2A&matches=false&tests=http%3A%2F%2Flocalhost%3A3000&tests=http%3A%2F%2Flocalhost%3A3000%2F&tests=http%3A%2F%2Flocalhost%3A3000%2F%3Ftest%3Dtest&tests=http%3A%2F%2Flocalhost%3A3000%2Ftest-test%3Ftest%3Dtest&tests=http%3A%2F%2Flocalhost%3A3000%2Ftest%2Ftest%3Ftest%3Dtest) to test your patterns.
|
||||
#### [Netlify preview URLs](/docs/guides/auth/concepts/redirect-urls#netlify-preview-urls)
|
||||
|
||||
<Admonition type="note" label="Recommendation">
|
||||
#### [Vercel preview URLs](/docs/guides/auth/concepts/redirect-urls#vercel-preview-urls)
|
||||
|
||||
While the "globstar" (`**`) is useful for local development and preview URLs, we recommend setting the exact redirect URL path for your site URL in production.
|
||||
|
||||
</Admonition>
|
||||
|
||||
#### Netlify preview URLs
|
||||
|
||||
For deployments with Netlify, set the `SITE_URL` to your official site URL. Add the following additional redirect URLs for local development and deployment previews:
|
||||
|
||||
- `http://localhost:3000/**`
|
||||
- `https://**--my_org.netlify.app/**`
|
||||
|
||||
#### Vercel preview URLs
|
||||
|
||||
For deployments with Vercel, set the `SITE_URL` to your official site URL. Add the following additional redirect URLs for local development and deployment previews:
|
||||
|
||||
- `http://localhost:3000/**`
|
||||
- `https://*-username.vercel.app/**`
|
||||
|
||||
Vercel provides an environment variable for the URL of the deployment called `NEXT_PUBLIC_VERCEL_URL`. See the [Vercel docs](https://vercel.com/docs/concepts/projects/environment-variables#system-environment-variables) for more details. You can use this variable to dynamically redirect depending on the environment. You should also set the value of the environment variable called NEXT_PUBLIC_SITE_URL, this should be set to your site URL in production environment to ensure that redirects function correctly.
|
||||
|
||||
```js
|
||||
const getURL = () => {
|
||||
let url =
|
||||
process?.env?.NEXT_PUBLIC_SITE_URL ?? // Set this to your site URL in production env.
|
||||
process?.env?.NEXT_PUBLIC_VERCEL_URL ?? // Automatically set by Vercel.
|
||||
'http://localhost:3000/'
|
||||
// Make sure to include `https://` when not localhost.
|
||||
url = url.includes('http') ? url : `https://${url}`
|
||||
// Make sure to include a trailing `/`.
|
||||
url = url.charAt(url.length - 1) === '/' ? url : `${url}/`
|
||||
return url
|
||||
}
|
||||
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'github',
|
||||
options: {
|
||||
redirectTo: getURL(),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
#### Mobile deep linking URIs
|
||||
|
||||
For mobile applications you can use deep linking URIs. For example for your `SITE_URL` you can specify something like `com.supabase://login-callback/` and for additional redirect URLs something like `com.supabase.staging://login-callback/` if needed.
|
||||
#### [Mobile deep linking URIs](/docs/guides/auth/concepts/redirect-urls#mobile-deep-linking-uris)
|
||||
|
||||
## Authorization
|
||||
|
||||
|
||||
@@ -2,31 +2,15 @@ import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
id: 'index',
|
||||
title: 'Auth Helpers Overview',
|
||||
description: 'A collection of framework-specific Auth utilities for working with Supabase.',
|
||||
title: 'Server-Side Auth Overview',
|
||||
description: 'Server-Side Auth guides and utilities for working with Supabase.',
|
||||
sidebar_label: 'Overview',
|
||||
}
|
||||
|
||||
A collection of framework-specific Auth utilities for working with Supabase.
|
||||
Working with server-side frameworks is slightly different to client-side frameworks. In this section we cover the various ways of handling server-side authentication and demonstrate how to use the Supabase helper-libraries to make the process more seamless.
|
||||
|
||||
<div className="container" style={{ padding: 0 }}>
|
||||
<div className="grid md:grid-cols-12 gap-4">
|
||||
{/* Auth UI */}
|
||||
<div className="col-span-6">
|
||||
<ButtonCard
|
||||
to={'/guides/auth/auth-helpers/auth-ui'}
|
||||
title={'Auth UI'}
|
||||
description={'A pre-built React component for authenticating users.'}
|
||||
/>
|
||||
</div>
|
||||
{/* Flutter Auth UI */}
|
||||
<div className="col-span-6">
|
||||
<ButtonCard
|
||||
to={'/guides/auth/auth-helpers/flutter-auth-ui'}
|
||||
title={'Flutter Auth UI'}
|
||||
description={'Pre-built Flutter widgets for authenticating users.'}
|
||||
/>
|
||||
</div>
|
||||
{/* Next.js */}
|
||||
<div className="col-span-6">
|
||||
<ButtonCard
|
||||
|
||||
@@ -1868,8 +1868,6 @@ export const GET: RequestHandler = withAuth(async ({ session, getSupabaseClient
|
||||
|
||||
- [Auth Helpers Source code](https://github.com/supabase/auth-helpers)
|
||||
- [SvelteKit example](https://github.com/supabase/auth-helpers/tree/main/examples/sveltekit)
|
||||
- [SvelteKit Email/Password example](https://github.com/supabase/auth-helpers/tree/main/examples/sveltekit-email-password)
|
||||
- [SvelteKit Magiclink example](https://github.com/supabase/auth-helpers/tree/main/examples/sveltekit-magic-link)
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
id: 'redirect-urls',
|
||||
title: 'Redirect URLs',
|
||||
description: 'Set up redirect urls with Supabase Auth.',
|
||||
subtitle: 'Set up redirect urls with Supabase Auth.',
|
||||
}
|
||||
|
||||
## Overview
|
||||
|
||||
When using [passwordless sign-ins](/docs/reference/javascript/auth-signinwithotp) or [third-party providers](/docs/reference/javascript/auth-signinwithoauth#sign-in-using-a-third-party-provider-with-redirect), the Supabase client library methods provide a `redirectTo` parameter to specify where to redirect the user to after authentication. By default, the user will be redirected to the [`SITE_URL`](/docs/reference/auth/config#site_url) but you can modify the `SITE_URL` or add additional redirect URLs to the [allow list](https://supabase.com/dashboard/project/_/auth/url-configuration). Once you've added necessary URLs to the allow list, you can specify the URL you want the user to be redirected to in the `redirectTo` parameter.
|
||||
|
||||
## Use wildcards in redirect URLs
|
||||
|
||||
Supabase allows you to specify wildcards when adding redirect URLs to the [allow list](https://supabase.com/dashboard/project/_/auth/url-configuration). You can use wildcard match patterns to support preview URLs from providers like Netlify and Vercel.
|
||||
|
||||
| Wildcard | Description |
|
||||
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `*` | matches any sequence of non-separator characters |
|
||||
| `**` | matches any sequence of characters |
|
||||
| `?` | matches any single non-separator character |
|
||||
| `c` | matches character c (c != `*`, `**`, `?`, `\`, `[`, `{`, `}`) |
|
||||
| `\c` | matches character c |
|
||||
| `[!{ character-range }]` | matches any sequence of characters not in the `{ character-range }`. For example, `[!a-z]` will not match any characters ranging from a-z. |
|
||||
|
||||
The separator characters in a URL are defined as `.` and `/`. Use [this tool](https://www.digitalocean.com/community/tools/glob?comments=true&glob=http%3A%2F%2Flocalhost%3A3000%2F%2A%2A&matches=false&tests=http%3A%2F%2Flocalhost%3A3000&tests=http%3A%2F%2Flocalhost%3A3000%2F&tests=http%3A%2F%2Flocalhost%3A3000%2F%3Ftest%3Dtest&tests=http%3A%2F%2Flocalhost%3A3000%2Ftest-test%3Ftest%3Dtest&tests=http%3A%2F%2Flocalhost%3A3000%2Ftest%2Ftest%3Ftest%3Dtest) to test your patterns.
|
||||
|
||||
<Admonition type="note" label="Recommendation">
|
||||
|
||||
While the "globstar" (`**`) is useful for local development and preview URLs, we recommend setting the exact redirect URL path for your site URL in production.
|
||||
|
||||
</Admonition>
|
||||
|
||||
### Redirect URL examples with wildcards
|
||||
|
||||
| Redirect URL | Description |
|
||||
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `http://localhost:3000/*` | matches `http://localhost:3000/foo`, `http://localhost:3000/bar` but not `http://localhost:3000/foo/bar` or `http://localhost:3000/foo/` (note the trailing slash) |
|
||||
| `http://localhost:3000/**` | matches `http://localhost:3000/foo`, `http://localhost:3000/bar` and `http://localhost:3000/foo/bar` |
|
||||
| `http://localhost:3000/?` | matches `http://localhost:3000/a` but not `http://localhost:3000/foo` |
|
||||
| `http://localhost:3000/[!a-z]` | matches `http://localhost:3000/1` but not `http://localhost:3000/a` |
|
||||
|
||||
## Netlify preview URLs
|
||||
|
||||
For deployments with Netlify, set the `SITE_URL` to your official site URL. Add the following additional redirect URLs for local development and deployment previews:
|
||||
|
||||
- `http://localhost:3000/**`
|
||||
- `https://**--my_org.netlify.app/**`
|
||||
|
||||
## Vercel preview URLs
|
||||
|
||||
For deployments with Vercel, set the `SITE_URL` to your official site URL. Add the following additional redirect URLs for local development and deployment previews:
|
||||
|
||||
- `http://localhost:3000/**`
|
||||
- `https://*-username.vercel.app/**`
|
||||
|
||||
Vercel provides an environment variable for the URL of the deployment called `NEXT_PUBLIC_VERCEL_URL`. See the [Vercel docs](https://vercel.com/docs/concepts/projects/environment-variables#system-environment-variables) for more details. You can use this variable to dynamically redirect depending on the environment. You should also set the value of the environment variable called NEXT_PUBLIC_SITE_URL, this should be set to your site URL in production environment to ensure that redirects function correctly.
|
||||
|
||||
```js
|
||||
const getURL = () => {
|
||||
let url =
|
||||
process?.env?.NEXT_PUBLIC_SITE_URL ?? // Set this to your site URL in production env.
|
||||
process?.env?.NEXT_PUBLIC_VERCEL_URL ?? // Automatically set by Vercel.
|
||||
'http://localhost:3000/'
|
||||
// Make sure to include `https://` when not localhost.
|
||||
url = url.includes('http') ? url : `https://${url}`
|
||||
// Make sure to include a trailing `/`.
|
||||
url = url.charAt(url.length - 1) === '/' ? url : `${url}/`
|
||||
return url
|
||||
}
|
||||
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'github',
|
||||
options: {
|
||||
redirectTo: getURL(),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Mobile deep linking URIs
|
||||
|
||||
For mobile applications you can use deep linking URIs. For example, for your `SITE_URL` you can specify something like `com.supabase://login-callback/` and for additional redirect URLs something like `com.supabase.staging://login-callback/` if needed.
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -111,7 +111,7 @@ curl -X POST 'https://cvwawazfelidkloqmbma.supabase.co/auth/v1/signup' \
|
||||
|
||||
The user will now receive an SMS with a 6-digit pin that you will need to receive from them within 60-seconds before they can login to their account.
|
||||
|
||||
You should present a form to the user so they can input the 6 digit pin, then send it along with the phone number to `verifyOTP`:
|
||||
You should present a form to the user so they can input the 6 digit pin, then send it along with the phone number to `verifyOtp`:
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -122,7 +122,7 @@ You should present a form to the user so they can input the 6 digit pin, then se
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```js
|
||||
let { session, error } = await supabase.auth.verifyOTP({
|
||||
let { session, error } = await supabase.auth.verifyOtp({
|
||||
phone: '+13334445555',
|
||||
token: '123456',
|
||||
})
|
||||
@@ -237,7 +237,7 @@ The second step is the same as the previous section, you need to collect the 6-d
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```js
|
||||
let { session, error } = await supabase.auth.verifyOTP({
|
||||
let { session, error } = await supabase.auth.verifyOtp({
|
||||
phone: '+13334445555',
|
||||
token: '123456',
|
||||
})
|
||||
|
||||
@@ -157,7 +157,7 @@ curl -X POST 'https://cvwawazfelidkloqmbma.supabase.co/auth/v1/signup' \
|
||||
|
||||
The user will now receive an SMS with a 6-digit pin that you will need to receive from them within 60-seconds before they can login to their account.
|
||||
|
||||
You should present a form to the user so they can input the 6 digit pin, then send it along with the phone number to `verifyOTP`:
|
||||
You should present a form to the user so they can input the 6 digit pin, then send it along with the phone number to `verifyOtp`:
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
|
||||
@@ -104,7 +104,7 @@ curl -X POST 'https://xxx.supabase.co/auth/v1/signup' \
|
||||
|
||||
The user will now receive an SMS with a 6-digit pin that you will need to receive from them within 60-seconds before they can login to their account.
|
||||
|
||||
You should present a form to the user so they can input the 6 digit pin, then send it along with the phone number to `verifyOTP`:
|
||||
You should present a form to the user so they can input the 6 digit pin, then send it along with the phone number to `verifyOtp`:
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -115,7 +115,7 @@ You should present a form to the user so they can input the 6 digit pin, then se
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```js
|
||||
let { session, error } = await supabase.auth.verifyOTP({
|
||||
let { session, error } = await supabase.auth.verifyOtp({
|
||||
phone: '491512223334444',
|
||||
token: '123456',
|
||||
})
|
||||
@@ -230,7 +230,7 @@ The second step is the same as the previous section, you need to collect the 6-d
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```js
|
||||
let { session, error } = await supabase.auth.verifyOTP({
|
||||
let { session, error } = await supabase.auth.verifyOtp({
|
||||
phone: '491512223334444',
|
||||
token: '123456',
|
||||
})
|
||||
|
||||
@@ -0,0 +1,258 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
title: 'Email Auth with PKCE flow for SSR',
|
||||
description:
|
||||
'Learn how to configure email authentication in your server-side rendering (SSR) application to work with the PKCE flow.',
|
||||
subtitle:
|
||||
'Learn how to configure email authentication in your server-side rendering (SSR) application to work with the PKCE flow.',
|
||||
}
|
||||
|
||||
### Install Supabase Auth Helpers
|
||||
|
||||
The Auth Helpers will assist you in implementing user authentication within your server-side rendering (SSR) framework.
|
||||
|
||||
<Tabs scrollable size="small" type="underlined" defaultActiveId="nextjs">
|
||||
|
||||
<TabPanel id="nextjs" label="NextJS">
|
||||
|
||||
```bash
|
||||
npm install @supabase/auth-helpers-nextjs @supabase/supabase-js
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel id="sveltekit" label="SvelteKit">
|
||||
|
||||
```bash
|
||||
npm install @supabase/auth-helpers-sveltekit @supabase/supabase-js
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
|
||||
</Tabs>
|
||||
|
||||
### Set environment variables
|
||||
|
||||
Create an `.env.local` file in your project root directory. You can get your `SITE_URL` and `ANON_KEY` from inside of the [dashboard](https://supabase.com/dashboard/project/_/settings/api).
|
||||
|
||||
<Tabs scrollable size="small" type="underlined" defaultActiveId="nextjs">
|
||||
|
||||
<TabPanel id="nextjs" label="NextJS">
|
||||
|
||||
```bash .env.local
|
||||
NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="sveltekit" label="SvelteKit">
|
||||
|
||||
```bash .env.local
|
||||
PUBLIC_SUPABASE_URL=your_supabase_project_url
|
||||
PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### Setting up the Auth Helpers
|
||||
|
||||
When using the Supabase client on the server, you must perform extra steps to ensure the user's auth session remains active. Since the user's session is tracked in a cookie, we need to read this cookie and update it if necessary.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="nextjs"
|
||||
>
|
||||
<TabPanel id="nextjs" label="NextJS">
|
||||
Next.js Server Components allow you to read a cookie but not write back to it. Middleware on the other hand allow you to both read and write to cookies.
|
||||
|
||||
Next.js [Middleware](https://nextjs.org/docs/app/building-your-application/routing/middleware) runs immediately before each route is rendered. We'll use Middleware to refresh the user's session before loading Server Component routes.
|
||||
|
||||
Create a new `middleware.js` file in the root of your project and populate with the following:
|
||||
|
||||
```js middleware.js
|
||||
import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
export async function middleware(req) {
|
||||
const res = NextResponse.next()
|
||||
const supabase = createMiddlewareClient({ req, res })
|
||||
await supabase.auth.getSession()
|
||||
return res
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="sveltekit" label="SvelteKit">
|
||||
Create a new `hooks.server.js` file in the root of your project and populate with the following:
|
||||
|
||||
```ts src/hooks.server.js
|
||||
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'
|
||||
import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit'
|
||||
import type { Handle } from '@sveltejs/kit'
|
||||
|
||||
export const handle: Handle = async ({ event, resolve }) => {
|
||||
event.locals.supabase = createSupabaseServerClient({
|
||||
supabaseUrl: PUBLIC_SUPABASE_URL,
|
||||
supabaseKey: PUBLIC_SUPABASE_ANON_KEY,
|
||||
event,
|
||||
})
|
||||
|
||||
event.locals.getSession = async () => {
|
||||
const {
|
||||
data: { session },
|
||||
} = await event.locals.supabase.auth.getSession()
|
||||
return session
|
||||
}
|
||||
|
||||
return resolve(event, {
|
||||
filterSerializedResponseHeaders(name) {
|
||||
return name === 'content-range'
|
||||
},
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### Create API endpoint for handling `token_hash`
|
||||
|
||||
In order to use the updated email links we will need to setup a endpoint for verifying the `token_hash` along with the `type` to exchange `token_hash` for the user's `session`, which is set as a cookie for future requests made to Supabase.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="nextjs"
|
||||
>
|
||||
<TabPanel id="nextjs" label="NextJS">
|
||||
Create a new file at `app/auth/confirm/route.js` and populate with the following:
|
||||
|
||||
```js app/auth/confirm/route.js
|
||||
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { cookies } from 'next/headers'
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
export async function GET(req) {
|
||||
const { searchParams } = new URL(req.url)
|
||||
const token_hash = searchParams.get('token_hash')
|
||||
const type = searchParams.get('type')
|
||||
const next = searchParams.get('next') ?? '/'
|
||||
|
||||
if (token_hash && type) {
|
||||
const supabase = createRouteHandlerClient({ cookies })
|
||||
const { error } = await supabase.auth.verifyOtp({ type, token_hash })
|
||||
if (!error) {
|
||||
return NextResponse.redirect(new URL(`/${next.slice(1)}`, req.url))
|
||||
}
|
||||
}
|
||||
|
||||
// return the user to an error page with some instructions
|
||||
return NextResponse.redirect(new URL('/auth/auth-code-error', req.url))
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="sveltekit" label="SvelteKit">
|
||||
Create a new file at `src/routes/auth/confirm/+server.js` and populate with the following:
|
||||
|
||||
```js src/routes/auth/confirm/+server.js
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
|
||||
export const GET = async (event) => {
|
||||
const {
|
||||
url,
|
||||
locals: { supabase }
|
||||
} = event;
|
||||
const token_hash = url.searchParams.get('token_hash') as string;
|
||||
const type = url.searchParams.get('type') as string;
|
||||
const next = url.searchParams.get('next') ?? '/';
|
||||
|
||||
if (token_hash && type) {
|
||||
const { error } = await supabase.auth.verifyOtp({ token_hash, type });
|
||||
if (!error) {
|
||||
throw redirect(303, `/${next.slice(1)}`);
|
||||
}
|
||||
}
|
||||
|
||||
// return the user to an error page with some instructions
|
||||
throw redirect(303, '/auth/auth-code-error');
|
||||
};
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### Update email templates with URL for API endpoint
|
||||
|
||||
Let's update the URL in our email templates to point to our new confirmation endpoint for the user to get confirmed.
|
||||
|
||||
**Confirm signup template**
|
||||
|
||||
```html
|
||||
<h2>Confirm your signup</h2>
|
||||
|
||||
<p>Follow this link to confirm your user:</p>
|
||||
<p>
|
||||
<a href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=email"
|
||||
>Confirm your email</a
|
||||
>
|
||||
</p>
|
||||
```
|
||||
|
||||
**Invite user template**
|
||||
|
||||
```html
|
||||
<h2>You have been invited</h2>
|
||||
|
||||
<p>
|
||||
You have been invited to create a user on {{ .SiteURL }}. Follow this link to accept the invite:
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a
|
||||
href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=invite&next=/path-to-your-update-password-page"
|
||||
>Accept the invite</a
|
||||
>
|
||||
</p>
|
||||
```
|
||||
|
||||
**Magic Link template**
|
||||
|
||||
```html
|
||||
<h2>Magic Link</h2>
|
||||
|
||||
<p>Follow this link to login:</p>
|
||||
<p><a href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=email">Log In</a></p>
|
||||
```
|
||||
|
||||
**Change Email Address template**
|
||||
|
||||
```html
|
||||
<h2>Confirm Change of Email</h2>
|
||||
|
||||
<p>Follow this link to confirm the update of your email from {{ .Email }} to {{ .NewEmail }}:</p>
|
||||
<p><a href="{{ .ConfirmationURL }}">Change Email</a></p>
|
||||
```
|
||||
|
||||
**Reset Password template**
|
||||
|
||||
```html
|
||||
<h2>Reset Password</h2>
|
||||
|
||||
<p>Follow this link to reset the password for your user:</p>
|
||||
<p>
|
||||
<a
|
||||
href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=recovery&next=/path-to-your-update-password-page"
|
||||
>Reset Password</a
|
||||
>
|
||||
</p>
|
||||
```
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,207 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
title: 'OAuth with PKCE flow for SSR',
|
||||
description:
|
||||
'Learn how to configure OAuth authentication in your server-side rendering (SSR) application to work with the PKCE flow.',
|
||||
subtitle:
|
||||
'Learn how to configure OAuth authentication in your server-side rendering (SSR) application to work with the PKCE flow.',
|
||||
}
|
||||
|
||||
### Install Supabase Auth Helpers
|
||||
|
||||
The Auth Helpers assist with user authentication within server-side rendering (SSR) frameworks.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="nextjs"
|
||||
>
|
||||
<TabPanel id="nextjs" label="NextJS">
|
||||
|
||||
```bash
|
||||
npm install @supabase/auth-helpers-nextjs @supabase/supabase-js
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="sveltekit" label="SvelteKit">
|
||||
|
||||
```bash
|
||||
npm install @supabase/auth-helpers-sveltekit @supabase/supabase-js
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### Set environment variables
|
||||
|
||||
Create an `.env.local` file in your project root directory. You can get your `SITE_URL` and `ANON_KEY` from inside of the [dashboard](https://supabase.com/dashboard/project/_/settings/api).
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="nextjs"
|
||||
>
|
||||
<TabPanel id="nextjs" label="NextJS">
|
||||
|
||||
```bash .env.local
|
||||
NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="sveltekit" label="SvelteKit">
|
||||
|
||||
```bash .env.local
|
||||
PUBLIC_SUPABASE_URL=your_supabase_project_url
|
||||
PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### Setting up the Auth Helpers
|
||||
|
||||
For SSR, the Supabase client requires extra steps to ensure the user's auth session remains active. Since the user's session is tracked in a cookie, we need to read this cookie and update it if necessary.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="nextjs"
|
||||
>
|
||||
<TabPanel id="nextjs" label="NextJS">
|
||||
Next.js Server Components allow you to read a cookie but not write back to it. Middleware on the other hand allow you to both read and write to cookies.
|
||||
|
||||
Next.js [Middleware](https://nextjs.org/docs/app/building-your-application/routing/middleware) runs immediately before each route is rendered. We'll use Middleware to refresh the user's session before loading Server Component routes.
|
||||
|
||||
Create a new `middleware.js` file in the root of your project and populate with the following:
|
||||
|
||||
```js middleware.js
|
||||
import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
export async function middleware(req) {
|
||||
const res = NextResponse.next()
|
||||
const supabase = createMiddlewareClient({ req, res })
|
||||
await supabase.auth.getSession()
|
||||
return res
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="sveltekit" label="SvelteKit">
|
||||
Create a new `hooks.server.js` file in the root of your project and populate with the following:
|
||||
|
||||
```ts src/hooks.server.js
|
||||
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'
|
||||
import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit'
|
||||
import type { Handle } from '@sveltejs/kit'
|
||||
|
||||
export const handle: Handle = async ({ event, resolve }) => {
|
||||
event.locals.supabase = createSupabaseServerClient({
|
||||
supabaseUrl: PUBLIC_SUPABASE_URL,
|
||||
supabaseKey: PUBLIC_SUPABASE_ANON_KEY,
|
||||
event,
|
||||
})
|
||||
|
||||
event.locals.getSession = async () => {
|
||||
const {
|
||||
data: { session },
|
||||
} = await event.locals.supabase.auth.getSession()
|
||||
return session
|
||||
}
|
||||
|
||||
return resolve(event, {
|
||||
filterSerializedResponseHeaders(name) {
|
||||
return name === 'content-range'
|
||||
},
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### Create API endpoint for handling the `code` exchange
|
||||
|
||||
In order to use OAuth we will need to setup a endpoint for the `code` exchange, to exchange an auth `code` for the user's `session`, which is set as a cookie for future requests made to Supabase.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="nextjs"
|
||||
>
|
||||
<TabPanel id="nextjs" label="NextJS">
|
||||
Create a new file at `app/auth/callback/route.js` and populate with the following:
|
||||
|
||||
```js app/auth/callback/route.js
|
||||
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { cookies } from 'next/headers'
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
export async function GET(req) {
|
||||
const { searchParams } = new URL(req.url)
|
||||
const code = searchParams.get('code')
|
||||
const next = searchParams.get('next') ?? '/'
|
||||
|
||||
if (code) {
|
||||
const supabase = createRouteHandlerClient({ cookies: () => cookies() })
|
||||
const { error } = await supabase.auth.exchangeCodeForSession(code)
|
||||
if (!error) {
|
||||
return NextResponse.redirect(new URL(`/${next.slice(1)}`, req.url))
|
||||
}
|
||||
}
|
||||
|
||||
// return the user to an error page with instructions
|
||||
return NextResponse.redirect(new URL('/auth/auth-code-error', req.url))
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="sveltekit" label="SvelteKit">
|
||||
Create a new file at `src/routes/auth/callback/+server.js` and populate with the following:
|
||||
|
||||
```js src/routes/auth/callback/+server.js
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
|
||||
export const GET = async (event) => {
|
||||
const {
|
||||
url,
|
||||
locals: { supabase }
|
||||
} = event;
|
||||
const code = url.searchParams.get('code') as string;
|
||||
const next = url.searchParams.get('next') ?? '/';
|
||||
|
||||
if (code) {
|
||||
const { error } = await supabase.auth.exchangeCodeForSession(code)
|
||||
if (!error) {
|
||||
throw redirect(303, `/${next.slice(1)}`);
|
||||
}
|
||||
}
|
||||
|
||||
// return the user to an error page with instructions
|
||||
throw redirect(303, '/auth/auth-code-error');
|
||||
};
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
Let's point our `.signInWithOAuth` method's redirect to the callback route we create above:
|
||||
|
||||
```js
|
||||
await supabase.auth.signInWithOAuth({
|
||||
email,
|
||||
options: {
|
||||
redirectTo: `http://example.com/auth/callback`,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -11,7 +11,7 @@ export const meta = {
|
||||
|
||||
Supabase provides a secure method for encrypting data using [Vault](/docs/guides/database/vault), our Postgres secrets manager. Vault is a Postgres extension with an [integrated UI](https://app.supabase.com/project/_/settings/vault/secrets) intended to act as a secure global secrets management for your project.
|
||||
|
||||
In addition to the Vault secret storage table, Supabase also enables an advanced feature called Transparent Column Encryption (TCE) which provides a safe way to encrypt columns in your own tables so that they doesn't leak into logs and backups. It can also provide row-level authenticated encryption.
|
||||
In addition to the Vault secret storage table, Supabase also enables an advanced feature called Transparent Column Encryption (TCE) which provides a safe way to encrypt columns in your own tables so that they don't leak into logs and backups. It can also provide row-level authenticated encryption.
|
||||
|
||||
Column Encryption comes with tradeoffs that need to be considered before using it.
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ Features:
|
||||
|
||||
## Installation
|
||||
|
||||
index_advisor is a trusted language extension, which means it is directly installable by users from the [database.dev](database.dev) SQL package repository.
|
||||
index_advisor is a trusted language extension, which means it is directly installable by users from the [database.dev](https://database.dev/) SQL package repository.
|
||||
|
||||
To get started, enable the dbdev client by executing the [setup SQL script](https://database.dev/installer).
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ $$;
|
||||
create trigger salary_update_trigger
|
||||
after update on employees
|
||||
for each row
|
||||
exectute function update_salary_log();
|
||||
execute function update_salary_log();
|
||||
```
|
||||
|
||||
### Trigger variables
|
||||
|
||||
@@ -8,21 +8,23 @@ export const meta = {
|
||||
|
||||
Every project on the Supabase Platform comes with its own dedicated Postgres instance running inside a virtual machine (VM). The following table describes the base instance with additional compute add-ons available if you need extra performance when scaling up Supabase.
|
||||
|
||||
| Plan | Pricing | CPU | Memory | Connections: Direct | Connections: Pooler |
|
||||
| --------------- | ------- | ----------------------- | ------ | ------------------- | ------------------- |
|
||||
| Free (Included) | $0 | 2-core ARM (shared) | 1 GB | 60 | 200 |
|
||||
| Small | $5 | 2-core ARM (shared) | 2 GB | 90 | 200 |
|
||||
| Medium | $50 | 2-core ARM (shared) | 4 GB | 120 | 200 |
|
||||
| Large | $100 | 2-core ARM (dedicated) | 8 GB | 160 | 300 |
|
||||
| XL | $200 | 4-core ARM (dedicated) | 16 GB | 240 | 700 |
|
||||
| 2XL | $400 | 8-core ARM (dedicated) | 32 GB | 380 | 1500 |
|
||||
| 4XL | $950 | 16-core ARM (dedicated) | 64 GB | 480 | 3000 |
|
||||
| 8XL | $1,860 | 32-core ARM (dedicated) | 128 GB | 490 | 6000 |
|
||||
| 12XL | $2,790 | 48-core ARM (dedicated) | 192 GB | 500 | 9000 |
|
||||
| 16XL | $3,720 | 64-core ARM (dedicated) | 256 GB | 500 | 12,000 |
|
||||
| Plan | Hourly Price USD | Monthly Price USD | CPU | Memory | Connections: Direct | Connections: Pooler |
|
||||
| ------- | ---------------- | ----------------- | ----------------------- | ------ | ------------------- | ------------------- |
|
||||
| Starter | $0.01344 | ~$10 | 2-core ARM (shared) | 1 GB | 60 | 200 |
|
||||
| Small | $0.0206 | ~$15 | 2-core ARM (shared) | 2 GB | 90 | 200 |
|
||||
| Medium | $0.0822 | ~$60 | 2-core ARM (shared) | 4 GB | 120 | 200 |
|
||||
| Large | $0.1517 | ~$110 | 2-core ARM (dedicated) | 8 GB | 160 | 300 |
|
||||
| XL | $0.2877 | ~$210 | 4-core ARM (dedicated) | 16 GB | 240 | 700 |
|
||||
| 2XL | $0.562 | ~$410 | 8-core ARM (dedicated) | 32 GB | 380 | 1500 |
|
||||
| 4XL | $1.32 | ~$960 | 16-core ARM (dedicated) | 64 GB | 480 | 3000 |
|
||||
| 8XL | $2.562 | ~$1,870 | 32-core ARM (dedicated) | 128 GB | 490 | 6000 |
|
||||
| 12XL | $3.836 | ~$2,800 | 48-core ARM (dedicated) | 192 GB | 500 | 9000 |
|
||||
| 16XL | $5.12 | ~$3,730 | 64-core ARM (dedicated) | 256 GB | 500 | 12,000 |
|
||||
|
||||
Number of connections above are recommended values.
|
||||
|
||||
We charge hourly for additional compute based on your usage. Read more about [usage-based billing for compute](/docs/guides/platform/org-based-billing#usage-based-billing-for-compute).
|
||||
|
||||
[Contact us](https://supabase.com/contact/enterprise) if you require a custom plan.
|
||||
|
||||
## Dedicated vs. shared CPU
|
||||
@@ -37,18 +39,18 @@ When considering compute upgrades, assess whether your bottlenecks are hardware-
|
||||
|
||||
SSD Disks are attached to your servers and the disk performance depends on the compute add-on of your instance. Disk IO refers to two metrics: throughput (Megabits per Second) and IOPS (Input/Output Operations per Second).
|
||||
|
||||
| Plan | Pricing | Max Disk Throughput | Baseline Disk Throughput | Max IOPS | Baseline IOPS |
|
||||
| --------------- | ------- | ------------------- | ------------------------ | ----------- | ------------- |
|
||||
| Free (Included) | $0 | 2,085 Mbps | 87 Mbps | 11,800 IOPS | 500 IOPS |
|
||||
| Small | $5 | 2,085 Mbps | 174 Mbps | 11,800 IOPS | 1,000 IOPS |
|
||||
| Medium | $50 | 2,085 Mbps | 347 Mbps | 11,800 IOPS | 2,000 IOPS |
|
||||
| Large | $100 | 4,750 Mbps | 630 Mbps | 20,000 IOPS | 3,600 IOPS |
|
||||
| XL | $200 | 4,750 Mbps | 1,188 Mbps | 20,000 IOPS | 6,000 IOPS |
|
||||
| 2XL | $400 | 4,750 Mbps | 2,375 Mbps | 20,000 IOPS | 12,000 IOPS |
|
||||
| 4XL | $950 | 4,750 Mbps | 4,750 Mbps | 20,000 IOPS | 20,000 IOPS |
|
||||
| 8XL | $1,860 | 9,500 Mbps | 9,500 Mbps | 40,000 IOPS | 40,000 IOPS |
|
||||
| 12XL | $2,790 | 14,250 Mbps | 14,250 Mbps | 50,000 IOPS | 50,000 IOPS |
|
||||
| 16XL | $3,720 | 19,000 Mbps | 19,000 Mbps | 80,000 IOPS | 80,000 IOPS |
|
||||
| Plan | Max Disk Throughput | Baseline Disk Throughput | Max IOPS | Baseline IOPS |
|
||||
| ------- | ------------------- | ------------------------ | ----------- | ------------- |
|
||||
| Starter | 2,085 Mbps | 87 Mbps | 11,800 IOPS | 500 IOPS |
|
||||
| Small | 2,085 Mbps | 174 Mbps | 11,800 IOPS | 1,000 IOPS |
|
||||
| Medium | 2,085 Mbps | 347 Mbps | 11,800 IOPS | 2,000 IOPS |
|
||||
| Large | 4,750 Mbps | 630 Mbps | 20,000 IOPS | 3,600 IOPS |
|
||||
| XL | 4,750 Mbps | 1,188 Mbps | 20,000 IOPS | 6,000 IOPS |
|
||||
| 2XL | 4,750 Mbps | 2,375 Mbps | 20,000 IOPS | 12,000 IOPS |
|
||||
| 4XL | 4,750 Mbps | 4,750 Mbps | 20,000 IOPS | 20,000 IOPS |
|
||||
| 8XL | 9,500 Mbps | 9,500 Mbps | 40,000 IOPS | 40,000 IOPS |
|
||||
| 12XL | 14,250 Mbps | 14,250 Mbps | 50,000 IOPS | 50,000 IOPS |
|
||||
| 16XL | 19,000 Mbps | 19,000 Mbps | 80,000 IOPS | 80,000 IOPS |
|
||||
|
||||
[Contact us](https://supabase.com/contact/enterprise) if you require a custom plan.
|
||||
|
||||
|
||||
@@ -132,6 +132,10 @@ const NEW_PROJECT_SERVICE_KEY = 'new-project-service-key-yyy'
|
||||
})()
|
||||
```
|
||||
|
||||
### Transfer to a different organization
|
||||
|
||||
Note that project migration is for transferring your projects to different regions. If you need to move your project to a different organization without touching the infrastrusture, see [project transfers](/docs/guides/platform/project-transfer).
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -3,7 +3,7 @@ import Layout from '~/layouts/DefaultGuideLayout'
|
||||
export const meta = {
|
||||
id: 'organization-based-billing',
|
||||
title: 'How billing works',
|
||||
description: 'Learn how organzation-based billing works in Supabase.',
|
||||
description: 'Learn how organization-based billing works in Supabase.',
|
||||
subtitle: 'Learn how organzation-based billing works in Supabase.',
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ If you launch a second or third instance on your paid plan, we add the additiona
|
||||
| 12XL | $3.836 | ~$2800 |
|
||||
| 16XL | $5.12 | ~$3730 |
|
||||
|
||||
With Legacy Billing, when you upgraded the [Compute Add-On](/docs/guides/platform/compute-add-ons), you were immediately charged the prorated amount (days left in your current billing cycle) and when your billing cycle reset you were charged upfront for the entire month. When you downgraded, you got the appropriate credits for unused time.
|
||||
With Legacy Billing, when you upgraded the [Compute Add-On](/docs/guides/platform/compute-add-ons), you were immediately charged the prorated amount (days remaining in your current billing cycle) and when your billing cycle reset you were charged upfront for the entire month. When you downgraded, you got the appropriate credits for unused time.
|
||||
|
||||
### Free plan
|
||||
|
||||
@@ -315,6 +315,15 @@ If you head over to your [organizations' billing settings](https://supabase.com/
|
||||
infrastructure.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
<i>Where do I change my project add-ons such as PITR, Compute and Custom Domain?</i>
|
||||
</summary>
|
||||
|
||||
Head over to your project [Add-ons page](https://supabase.com/dashboard/project/_/settings/addons) to change your compute size, Point-In-Time-Recovery or custom domain.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
<i>I have additional questions/concerns, how do I get help?</i>
|
||||
|
||||
@@ -7,8 +7,8 @@ export const meta = {
|
||||
sidebar_label: 'Videos',
|
||||
}
|
||||
|
||||
In this guide we explore the best ways to receive realtime Postgres changes with your Next.js application.
|
||||
We'll show both client and serverside updates, and explore the which option is best.
|
||||
In this guide, we explore the best ways to receive real-time Postgres changes with your Next.js application.
|
||||
We'll show both client and server side updates, and explore which option is best.
|
||||
|
||||
<div className="video-container">
|
||||
<iframe
|
||||
|
||||
@@ -47,12 +47,12 @@ You can access the Supabase Dashboard through the API gateway on port `8000`. Fo
|
||||
|
||||
You will be prompted for a username and password. By default, the credentials are:
|
||||
|
||||
- Username: `supabase`
|
||||
- Username: `supabase`
|
||||
- Password: `this_password_is_insecure_and_should_be_updated`
|
||||
|
||||
You should change these credentials as soon as possible using the [instructions](#dashboard-authentication) below.
|
||||
|
||||
### Accessing the APIs
|
||||
### Accessing the APIs
|
||||
|
||||
Each of the APIs are available through the the same API gateway:
|
||||
|
||||
@@ -61,9 +61,9 @@ Each of the APIs are available through the the same API gateway:
|
||||
- Storage: `http://<your-domain>:8000/storage/v1/`
|
||||
- Realtime: `http://<your-domain>:8000/realtime/v1/`
|
||||
|
||||
### Accessing your Edge Functions
|
||||
### Accessing your Edge Functions
|
||||
|
||||
Edge Functions are stored in `volumes/functions`. The default setup has a `hello` Function that you can invoke on `http://<your-domain>:8000/functions/v1/hello`.
|
||||
Edge Functions are stored in `volumes/functions`. The default setup has a `hello` Function that you can invoke on `http://<your-domain>:8000/functions/v1/hello`.
|
||||
|
||||
You can add new Functions as `volumes/functions/<FUNCTION_NAME>/index.ts`. Restart the `functions` service to pick up the changes: `docker compose restart functions --no-deps`
|
||||
|
||||
@@ -84,7 +84,7 @@ While we provided you with some example secrets for getting started, you should
|
||||
|
||||
### Generate API Keys
|
||||
|
||||
Create a new `JWT_SECRET` and store it securely.
|
||||
Create a new `JWT_SECRET` and store it securely.
|
||||
|
||||
We can use your JWT Secret to generate new `anon` and `service` API keys using the form below. Update the "JWT Secret" and then run "Generate JWT" once for the `SERVICE_KEY` and once for the `ANON_KEY`:
|
||||
|
||||
@@ -114,8 +114,8 @@ You will need to [restart](#restarting-all-services) the services for the change
|
||||
|
||||
The dashboard is protected with Basic Authentication. The default user and password MUST be updated before using Supabase in production.
|
||||
Update the following values in the `.env` file:
|
||||
- `DASHBOARD_USERNAME`: The default username for the Dashboard
|
||||
- `DASHBOARD_PASSWORD`: The default password for the Dashboard
|
||||
- `DASHBOARD_USERNAME`: The default username for the Dashboard
|
||||
- `DASHBOARD_PASSWORD`: The default password for the Dashboard
|
||||
|
||||
You can also add more credentials in `./docker/volumes/api/kong.yml`. For example:
|
||||
|
||||
@@ -149,13 +149,13 @@ You can stop Supabase by running `docker compose stop` in same directory as your
|
||||
|
||||
You can stop Supabase by running the following in same directory as your `docker-compose.yml` file:
|
||||
|
||||
```sh
|
||||
```sh
|
||||
# Stop docker and remove volumes:
|
||||
docker compose down -v
|
||||
|
||||
# Remove Postgres data:
|
||||
rm -rf volumes/db/data/
|
||||
```
|
||||
```
|
||||
|
||||
This will destroy all data in the database and storage volumes, so be careful!
|
||||
|
||||
@@ -163,7 +163,7 @@ This will destroy all data in the database and storage volumes, so be careful!
|
||||
|
||||
Each system can be [configured](../self-hosting#configuration) independently. Some of the most common configuration options are listed below.
|
||||
|
||||
### Configuring an email server
|
||||
### Configuring an email server
|
||||
|
||||
You will need to use a production-ready SMTP server for sending emails. You can configure the SMTP server by updating the the following environment variables:
|
||||
|
||||
@@ -178,9 +178,9 @@ SMTP_SENDER_NAME=
|
||||
|
||||
We recommend using [AWS SES](https://aws.amazon.com/ses/). It's extremely cheap and reliable. Restart all services to pick up the new configuration.
|
||||
|
||||
### Configuring S3 Storage
|
||||
### Configuring S3 Storage
|
||||
|
||||
By default all files are stored locally on the server. You can connfigure the Storage service to use S3 by updating the following environment variables:
|
||||
By default all files are stored locally on the server. You can configure the Storage service to use S3 by updating the following environment variables:
|
||||
|
||||
```yaml docker-compose.yml
|
||||
storage:
|
||||
@@ -216,7 +216,29 @@ By default, Storage backend is set to `file`, which is to use local files as the
|
||||
|
||||
Additional configuration is required for self-hosting the Analytics server. For the full setup instructions, see [Self Hosting Analytics](https://supabase.com/docs/reference/self-hosting-analytics/introduction#getting-started).
|
||||
|
||||
### Upgrading Analytics
|
||||
|
||||
Due to the changes in the Analytics server, you will need to run the following commands to upgrade your Analytics server:
|
||||
|
||||
<Admonition type="warning">
|
||||
|
||||
- All data in analytics will be deleted when you run the commands below.
|
||||
|
||||
</Admonition>
|
||||
|
||||
|
||||
```sh
|
||||
### Destroy analytics to transition to postgres self hosted solution without other data loss
|
||||
|
||||
# Enter the container and use your .env POSTGRES_PASSWORD value to login
|
||||
docker exec -it $(docker ps | grep supabase-db | awk '{print $1}') psql -U supabase_admin --password
|
||||
# Drop all the data in the _analytics schema
|
||||
DROP PUBLICATION logflare_pub; DROP SCHEMA _analytics CASCADE; CREATE SCHEMA _analytics;\q
|
||||
# Drop the analytics container
|
||||
docker rm supabase-db
|
||||
```
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
|
||||
@@ -82,7 +82,7 @@ on storage.objects
|
||||
for select
|
||||
to public
|
||||
using (
|
||||
storage.filename(name)) = 'favicon.ico'
|
||||
storage.filename(name) = 'favicon.ico'
|
||||
);
|
||||
```
|
||||
|
||||
@@ -100,7 +100,7 @@ on storage.objects
|
||||
for insert
|
||||
to authenticated
|
||||
with check (
|
||||
storage.foldername(name))[1] = 'private'
|
||||
storage.foldername(name)[1] = 'private'
|
||||
);
|
||||
```
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ You'll have to make sure your google app is verified of course in order to reque
|
||||
But all the functionality of gotrue-js is also available in supabase-js, which uses gotrue-js internally when you do things like:
|
||||
|
||||
```jsx
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
const { user, session, error } = await supabase.auth.signInWithPassword({
|
||||
email: 'example@email.com',
|
||||
password: 'example-password',
|
||||
})
|
||||
|
||||
|
After Width: | Height: | Size: 202 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 168 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 17 KiB |
@@ -151,7 +151,7 @@
|
||||
</url>
|
||||
|
||||
<url>
|
||||
<loc>https://supabase.com/docs/guides/ai/managing-indexes</loc>
|
||||
<loc>https://supabase.com/docs/guides/ai/vector-indexes</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<changefreq>0.5</changefreq>
|
||||
</url>
|
||||
|
||||
@@ -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,17 +25,17 @@ 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.
|
||||
|
||||
IVF [indexes](https://supabase.com/docs/guides/ai/managing-indexes) work by clustering vectors into `lists`, and then querying only vectors within the same list (or multiple nearby lists, depending on the value of `probes`).
|
||||
IVF [indexes](https://supabase.com/docs/guides/ai/vector-indexes/ivf-indexes#how-does-ivfflat-work) work by clustering vectors into `lists`, and then querying only vectors within the same list (or multiple nearby lists, depending on the value of `probes`).
|
||||
|
||||
### Scaling indexes
|
||||
|
||||
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)
|
||||
@@ -79,7 +79,7 @@ This should be done before the end of October, but don't worry - we'll give you
|
||||
|
||||
## No more upfront payments for databases
|
||||
|
||||
Currently, plans include one Starter Compute instance. When you upgrade the [Compute Add-On](https://supabase.com/docs/guides/platform/compute-add-ons), you immediately prepay the prorated amount (days left in your current billing cycle), and then pay upfront for the entire month when your billing cycle resets. When you downgrade, you get the appropriate credits for unused time.
|
||||
Currently, plans include one Starter Compute instance. When you upgrade the [Compute Add-On](https://supabase.com/docs/guides/platform/compute-add-ons), you immediately prepay the prorated amount (days remaining in your current billing cycle), and then pay upfront for the entire month when your billing cycle resets. When you downgrade, you get the appropriate credits for unused time.
|
||||
|
||||
Full-month upfront payments are annoying, especially when you just want to do a temporary upgrade. We've heard your feedback and are moving billing for compute to hourly usage.
|
||||
|
||||
|
||||
@@ -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. 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/vector-indexes/ivf-indexes#how-does-ivfflat-work) 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)
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
title: Supabase Beta August 2023
|
||||
description: Launch Week 8 review and more things we shipped 🚀
|
||||
author: ant_wilson
|
||||
image: 2023-09-07-beta-update-august-2023/monthly-update-august-2023.jpg
|
||||
thumb: 2023-09-07-beta-update-august-2023/monthly-update-august-2023.jpg
|
||||
categories:
|
||||
- product
|
||||
tags:
|
||||
- release-notes
|
||||
date: '2023-09-08'
|
||||
toc_depth: 3
|
||||
---
|
||||
|
||||
Launch Week 8 breezed by, leaving behind a trail of new features to explore. Here is a recap of everything and an update of what else we are working on, like pgvector 0.5.0 with HNSW.
|
||||
|
||||
## pgvector v0.5.0: Faster semantic search with HNSW indexes
|
||||
|
||||

|
||||
|
||||
Supabase Vector is about to get a lot faster 💨. pgvector v0.5.0 adds Hierarchical Navigable Small World (HNSW), a new type of index that ensures lightning-fast vector searches, especially in high-dimensional spaces and embeddings.
|
||||
|
||||
[Blog post](https://supabase.com/blog/increase-performance-pgvector-hnsw)
|
||||
|
||||
## Day 1 - Hugging Face is now supported in Supabase
|
||||
|
||||

|
||||
|
||||
We are all about open source collaboration, and Hugging Face is one of the open source communities we admire most. That’s why we've added Hugging Face support in our Python Vector Client and Edge Functions (Javascript) 🤗
|
||||
|
||||
- [Blog post](https://supabase.com/blog/hugging-face-supabase)
|
||||
- [Video announcement](https://www.youtube.com/watch?v=RJccSbJ9Go4)
|
||||
|
||||
## Day 2 - Supabase Local Dev: migrations, branching, and observability
|
||||
|
||||

|
||||
|
||||
The CLI received some serious upgrades including observability tools, streamlined backups, and enhanced migrations. But that's not all – the big game-changer is the introduction of Supabase branching which we’re rolling out to selected customers.
|
||||
|
||||
- [Blog post](https://supabase.com/blog/supabase-local-dev)
|
||||
- [Video announcement](https://www.youtube.com/watch?v=N0Wb85m3YMI)
|
||||
|
||||
## Day 3 - Supabase Studio 3.0
|
||||
|
||||

|
||||
|
||||
Supabase Studio went to [#1 on Product Hunt](https://www.producthunt.com/products/supabase#ai-sql-editor-by-supabase) with some huge new features, including AI SQL editor, Schema diagrams, Wrappers UI, and a lot more!
|
||||
|
||||
- [Blog post](https://supabase.com/blog/supabase-studio-3-0)
|
||||
- [Video announcement](https://www.youtube.com/watch?v=51tCMQPiitQ)
|
||||
|
||||
## Day 4 - Supabase Integrations Marketplace
|
||||
|
||||

|
||||
|
||||
With the release of OAuth2 applications, we've made it easier than ever for our partners to extend the Supabase platform with useful tooling 🙌
|
||||
|
||||
- [Blog post](https://supabase.com/blog/supabase-integrations-marketplace)
|
||||
- [Video announcement](https://www.youtube.com/watch?v=gtJo1lTxHfs)
|
||||
|
||||
## Day 4 - Vercel Integration 2.0 and Next.js App Router Support
|
||||
|
||||

|
||||
|
||||
The New Supabase x Vercel integration streamlines the process of creating, deploying, and maintaining web applications with several enhancements. Plus, it fully supports the App Router in Next.js ▲
|
||||
|
||||
[Blog post](https://supabase.com/blog/using-supabase-with-vercel)
|
||||
|
||||
## Day 5 - Supavisor: Scaling Postgres to 1 Million Connections
|
||||
|
||||

|
||||
|
||||
Supavisor is a scalable, cloud-native Postgres connection pooler written in Elixir. It has been developed with multi-tenancy in mind, handling millions of connections without significant overhead or latency. We’re rolling it out to every database on our platform.
|
||||
|
||||
- [Blog post](https://supabase.com/blog/supavisor-1-million)
|
||||
- [Video announcement](https://www.youtube.com/watch?v=qzxzLSAJDfE)
|
||||
|
||||
## Community Highlights from the past 4 months
|
||||
|
||||

|
||||
|
||||
Launch Week is an event for our community, so it’s a good time to look back at what happened in the last months (spoiler: a lot).
|
||||
|
||||
[Blog post](https://supabase.com/blog/launch-week-8-community-highlights)
|
||||
|
||||
## One more thing: HIPAA and SOC2 Type 2
|
||||
|
||||

|
||||
|
||||
Supabase is officially SOC2 Type 2 and HIPAA compliant! In this write-up, we offer insights into what you can expect if you’re planning to go through the same process.
|
||||
|
||||
[Blog post](https://supabase.com/blog/supabase-soc2-hipaa)
|
||||
|
||||
## Launch Week 8 Hackathon Winners
|
||||
|
||||

|
||||
|
||||
The decision was not easy, but after assessing a record number of submissions, the panel of judges chose [WITAS](https://github.com/alex-streza/witas) as the winner of the Best Overall project. As the name doesn't suggest, it's an acronym for Wait is that a sticker? In a nutshell, it generates stickers with Midjourney. Huge congrats to [Alex Streza](https://twitter.com/alex_streza) and [Catalina Melnic](https://twitter.com/Catalina_Melnic).
|
||||
|
||||
- [Full list of winners](https://t.co/onYiaDmavb)
|
||||
- [All the submissions](https://www.madewithsupabase.com/hackathons/launch-week-8)
|
||||
|
||||
## More product announcements!
|
||||
|
||||
Shipping doesn’t stop here at Supabase! We are back in full shipping mode and already thinking about the next LW. These are some of the things we’ve been working on:
|
||||
|
||||
- Updated and rewrote a bunch of docs: [Row-Level-Security](https://supabase.com/docs/guides/database/postgres/row-level-security), [Postgres Roles,](https://supabase.com/docs/guides/database/postgres/roles) [Database configuration](https://supabase.com/docs/guides/database/postgres/configuration).
|
||||
- Implemented read only UI for indexes. [PR](https://github.com/supabase/supabase/pull/16582)
|
||||
- Organization-based Billing, project transfers, team plan. [Blog post](https://supabase.com/blog/organization-based-billing)
|
||||
|
||||
## Extended Community Highlights
|
||||
|
||||

|
||||
|
||||
- Supabase’s Happy Hour made a comeback! Two new episodes of Alaister, Tyler, and Jon chatting about the latest news while live coding. [Episode #27](https://www.youtube.com/watch?v=OWhKVbg1p7Y) | [Episode #28](https://www.youtube.com/watch?v=_Z2f-gGrYu8)
|
||||
- The State of Postgres 2023 is live. Take the survey and help improve Postgres. [Survey](https://timescale.typeform.com/state-of-pg-23/)
|
||||
- Jon Meyers stopped by the PodRocket podcast to chat about everything we shipped for LW8. [Podcast](https://podrocket.logrocket.com/supabase-launch-week)
|
||||
- Supabase Crash Course for iOS Developers: Mikaela shows how to implement a Postgres database in a Swift project. [Video](https://www.youtube.com/watch?v=XBSiXROUoZk)
|
||||
- The Vite ecosystem conference is back and we are happy to be a Community Partner again. [Get your ticket](https://viteconf.org/23/ecosystem/supabase)
|
||||
- Building a real app with Tamagui and Supabase. [Video](https://www.youtube.com/watch?v=d32F7crxXsY)
|
||||
- Build PostgreSQL Databases Faster With Supabase AI SQL Editor. [Video](https://www.youtube.com/watch?v=ueCECQ24STI)
|
||||
- Creating Customized i18n-Ready Authentication Emails using Supabase Edge Functions, PostgreSQL, and Resend. [Blog post](https://blog.mansueli.com/creating-customized-i18n-ready-authentication-emails-using-supabase-edge-functions-postgresql-and-resend)
|
||||
- Expo Router Tabs with Supabase Authentication. [Video](https://www.youtube.com/watch?v=6IzrH-1M0uE&list=PL2PY2-9rsgl2DikpQG-GgO7TBgRtdB6NT&index=6)
|
||||
- Integrating Supabase with Prisma and TRPC: A Comprehensive Guide. [Tutorial](https://tobicode.hashnode.dev/integrating-supabase-with-prisma-and-trpc-a-comprehensive-guide)
|
||||
- A Supa-Introduction to Supabase. [Blog post](https://medium.com/@alex.streza/a-supa-introduction-to-supabase-e551ea6708e)
|
||||
- Authentication in Next.js with Supabase Auth and PKCE. [Tutorial](https://dev.to/mryechkin/authentication-in-nextjs-with-supabase-auth-and-pkce-45pk)
|
||||
- Implementing OAuth in Nuxt with Supabase. [Tutorial](https://dev.to/jacobandrewsky/implementing-oauth-in-nuxt-with-supabase-4p1k)
|
||||
|
||||
## ⚠️ Baking hot meme zone ⚠️
|
||||
|
||||
If you made it this far in the email you deserve a devilish treat.
|
||||
|
||||

|
||||
|
||||
That's it for now, see you next month 👋
|
||||
@@ -0,0 +1,78 @@
|
||||
---
|
||||
name: Chatbase
|
||||
title: Bootstrapped founder builds an AI app with Supabase and scales to $1M in 5 months
|
||||
# Use meta_title to add a custom meta title. Otherwise it defaults to '{name} | Supabase Customer Stories':
|
||||
meta_title: Bootstrapped founder builds an AI app with Supabase and scales to $1M in 5 months
|
||||
description: How Yasser leveraged Supabase to build Chatbase and became one of the most successful single-founder AI products.
|
||||
# Use meta_description to add a custom meta description. Otherwise it defaults to {description}:
|
||||
meta_description: How Yasser leveraged Supabase to build Chatbase and became one of the most successful single-founder AI products.
|
||||
author: copple
|
||||
author_title: Supabase
|
||||
author_url: https://github.com/kiwicopple
|
||||
author_image_url: https://avatars2.githubusercontent.com/u/10214025?s=400&u=c6775be2ae667e2acae3ccd347fed62bb3f5b3e7&v=4
|
||||
authorURL: https://github.com/kiwicopple
|
||||
logo: /images/customers/logos/chatbase.png
|
||||
logo_inverse: /images/customers/logos/light/chatbase.png
|
||||
og_image: /images/customers/og/chatbase.jpg
|
||||
tags:
|
||||
- supabase
|
||||
date: '2023-10-06'
|
||||
company_url: https://www.chatbase.co/
|
||||
stats: []
|
||||
misc:
|
||||
[
|
||||
{ label: 'Use case', text: 'AI chatbot builder' },
|
||||
{
|
||||
label: 'Solutions',
|
||||
text: 'Supabase Database, Supabase Auth, Supabase Storage, Supabase Realtime',
|
||||
},
|
||||
]
|
||||
about: 'Chatbase is an AI chatbot builder. It trains ChatGPT on your data and lets you add a chat widget to your website. Just upload a document or add a link to your website and get a chatbot that can answer any question about their content.'
|
||||
---
|
||||
|
||||
After getting a new grad offer from his “dream company” rescinded with tech layoffs, Yasser Elsaid decided to embark on a different path and bootstrap his own venture.
|
||||
|
||||
Two months before the release of the ChatGPT API, Yasser explored the GPT3 API and saw its immense potential. Leveraging Supabase as the backend, he built Chatbase in two weeks and launched it in February.
|
||||
|
||||
Just five months after launching, Chatbase reach $1,000,000 annualized revenue, making it one of the most successful single-founder AI products in the industry.
|
||||
|
||||
## The Challenge
|
||||
|
||||
Fascinated by the possibilities of GPT3, Yasser envisioned Chatbase as an AI-driven chatbot capable of handling complex customer queries in real time. However, building a cost-effective and reliable solution to ingest and store data and manage large-scale customer interactions by himself wasn’t trivial.
|
||||
|
||||
Yasser faced another challenge: he needed to rapidly transform his idea into an actual product to maintain a competitive edge, but he didn’t have a team or funding.
|
||||
|
||||
## Choosing Supabase
|
||||
|
||||
Using Supabase for the first time, Yasser was immediately impressed by the developer experience and ease of use. He effortlessly implemented secure user authentication, relieving the pain of building such a system from scratch.
|
||||
|
||||
But the most crucial aspect for us is Supabase's all-in-one solution. Chatbase relies on Supabase for [database](https://supabase.com/database), [authentication](https://supabase.com/auth), [storage](https://supabase.com/storage), and [real-time](https://supabase.com/realtime) functionality. Yasser believes he saved between 100 to 150 hours by using only 1 solution to build almost the entire backend, instead of spending that researching, learning, and implementing individual solutions for each component.
|
||||
|
||||
With Supabase, everything seamlessly came together and he was able to go from idea to MVP in two weeks and launch soon after that.
|
||||
|
||||
<Quote img="yasser-elsaid-chatbase.jpeg" caption="Yasser Elsaid, Founder of Chatbase.">
|
||||
<p>
|
||||
Supabase is great because it has everything. I don’t need a different solution for
|
||||
authentication, a different solution for database, or a different solution for storage.
|
||||
</p>
|
||||
</Quote>
|
||||
|
||||
## What he built
|
||||
|
||||
Chatbase is a powerful AI chatbot builder that enables users to train ChatGPT on their own data, revolutionizing the way businesses and individuals interact with customers. With seamless integration to over 5000 apps through Zapier, including popular platforms like Excel, Notion, Whatsapp, Discord, and Slack, Chatbase empowers everyone to deploy custom AI for their specific workflows.
|
||||
|
||||
By simply uploading a PDF containing the desired information, Chatbase creates intelligent chatbots that automate customer support, handle frequently asked questions, and streamline communication channels, ultimately enhancing overall efficiency and user experience.
|
||||
|
||||
Chatbase became a sensation, crossing 2,000,000 visitors in a matter of months and now has 2,200 paying customers.
|
||||
|
||||
## The Results
|
||||
|
||||
Leveraging Supabase as the backend gave Yasser the speed and efficiency to turn into a significant advantage over the competition, enabling him to gain a strong advantage in the market and establish Chatbase as one of the pioneering AI chatbot builders.
|
||||
|
||||
As Chatbase gained traction and acquired users, Supabase's robust infrastructure seamlessly accommodated the surging user base, ensuring smooth scalability and uninterrupted growth.
|
||||
|
||||
Within just five months, Chatbase grew to an astonishing $1,000,000 in annualize revenue.
|
||||
|
||||
Chatbase is still bootstrapped, but now Yasser has a team of 5 who help with customer support, marketing, and development. He is now exploring Supabase's capabilities for achieving GDPR compliance and implementing Vault for end-to-end encryption.
|
||||
|
||||
> To learn more about how Supabase can help you build and scale AI apps with ease, [reach out to us](https://forms.supabase.com/enterprise).
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Markprompt and Supabase - GDPR-compliant AI chatbots for docs and websites.
|
||||
meta_title: GDPR-compliant AI chatbots for docs and websites.
|
||||
name: Markprompt
|
||||
description: AI-powered chatbot platform, Markprompt, empowers developers to deliver efficient and GDPR-compliant prompt experiences on top of their content, by leveraging Supabase's secure and privacy-focused vector database and authentication solutions.
|
||||
author: paul_copplestone
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name: Mendable
|
||||
title: Mendable switches from Pinecone to Supabase Vector for PostgreSQL vector embeddings.
|
||||
# Use meta_title to add a custom meta title. Otherwise it defaults to '{name} | Supabase Customer Stories':
|
||||
# meta_title: Mendable switches from Pinecone to Supabase Vector for PostgreSQL vector embeddings.
|
||||
meta_title: Mendable switches from Pinecone to Supabase Vector for PostgreSQL vector embeddings.
|
||||
description: How Mendable boosts efficiency and accuracy of chat powered search for documentation using Supabase Vector.
|
||||
# Use meta_description to add a custom meta description. Otherwise it defaults to {description}:
|
||||
meta_description: How Mendable boosts efficiency and accuracy of chat powered search for documentation using Supabase Vector.
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
---
|
||||
name: Quivr
|
||||
title: Quivr launch 5,000 Vector databases on Supabase.
|
||||
# Use meta_title to add a custom meta title. Otherwise it defaults to '{name} | Supabase Customer Stories':
|
||||
meta_title: Quivr launch 5,000 Vector databases on Supabase.
|
||||
description: Learn how one of the most popular Generative AI projects uses Supabase as their Vector Store.
|
||||
# Use meta_description to add a custom meta description. Otherwise it defaults to {description}:
|
||||
meta_description: Learn how one of the most popular Generative AI projects uses Supabase as their Vector Store.
|
||||
author: rory_wilding
|
||||
author_title: Supabase
|
||||
author_url: https://github.com/kiwicopple
|
||||
author_image_url: https://avatars2.githubusercontent.com/u/10214025?s=400&u=c6775be2ae667e2acae3ccd347fed62bb3f5b3e7&v=4
|
||||
authorURL: https://github.com/kiwicopple
|
||||
logo: /images/customers/logos/quivr.png
|
||||
logo_inverse: /images/customers/logos/light/quivr.png
|
||||
og_image: /images/customers/og/quivr.jpg
|
||||
tags:
|
||||
- supabase
|
||||
date: '2023-10-05'
|
||||
company_url: https://quivr.app
|
||||
stats: []
|
||||
misc:
|
||||
[
|
||||
{ label: 'Use case', text: 'Generative AI' },
|
||||
{ label: 'Solutions', text: 'Supabase Vector, Supabase Auth' },
|
||||
]
|
||||
about: "Quivr is an open source 'second brain'. It's like a private ChatGPT, personalized with your own data: you upload your documents and you can then search and ask questions using generative AI."
|
||||
---
|
||||
|
||||
## The challenge: Building a second brain
|
||||
|
||||
In May of 2023, [Stan Girard's](https://twitter.com/_StanGirard) started building small prototypes that allowed him to “chat with documents”. After 2 weeks of research, he settled on an idea - build a “second brain” where a user could dump all their digital knowledge (audio, URLs, text, and code) into a vector store and query it with GPT4.
|
||||
|
||||
He built the first version in a single afternoon, pushed it to GitHub, and then [tweeted about it](https://twitter.com/_StanGirard/status/1657021618571313155?s=20). One viral tweet later, and [Quivr](https://github.com/StanGirard/quivr) was born.
|
||||
|
||||
## Choosing a vector database
|
||||
|
||||
A critical piece of the tech stack was the vector store. Stan needed a place to store millions of embeddings. After comparing between Supabase, Pinecone, and Chroma, he settled on [Supabase Vector](https://supabase.com/vector), our open source vector offering for developing AI applications. The decision was driven largely by his familiarity with Postgres, and the tight integration with Vercel.
|
||||
|
||||
<Quote img="stan-girard-avatar.jpeg" caption="Stan Girard, Founder of Quivr.">
|
||||
<p>
|
||||
Supabase Vector powered by pgvector allowed us to create a simple and efficient product. We are
|
||||
storing over 1.6 million embeddings and the performance and results are great. Open source
|
||||
develop can easily contribute thanks to the SQL syntax known by millions of developers.
|
||||
</p>
|
||||
</Quote>
|
||||
|
||||
## Building an open source community
|
||||
|
||||
It didn't take long for the Quivr community to grow. After the viral launch, the [Quivr repo](https://github.com/StanGirard/quivr) stayed at number 1 on [GitHub Trending](https://github.com/trending) for 4 days. Today, it has over 22,000 GitHub stars and 67 contributors. Supabase has been a key part of the open source stack since the beginning.
|
||||
|
||||
<Quote img="stan-girard-avatar.jpeg" caption="Stan Girard, Founder of Quivr.">
|
||||
<p>
|
||||
Because Supabase is open source, the possibility of running it locally made it a better choice
|
||||
compared with other products like Auth0. Since Auth is integrated with the Vector database it
|
||||
made Quivr much simpler. Features like Storage and Edge Functions allowed us to expand Quivr's
|
||||
functionality while keeping the project simple.
|
||||
</p>
|
||||
</Quote>
|
||||
|
||||
## Launching 5000 databases
|
||||
|
||||
One of the most pivotal growth events was getting picked up by [an influential YouTuber](https://www.youtube.com/watch?v=rFEbz93G9U8). His 11-minute overview of Quivr launched over 2000 Quivr projects on Supabase in one week. There are now 5,100 Quivr databases on Supabase, making it one of the most influential communities on the Supabase platform.
|
||||
|
||||
## Launching a hosted product
|
||||
|
||||
Stan also launched a [hosted version of Quivr](https://www.quivr.app/), for users to sign up and get started immediately, without requiring any self-hosting infrastructure. Quivr's open source success has translated through their hosted platform, with 17,000 signups in just over 2 months, with 200 new users joining every day. The hosted database provides embedding storage for 1.6 million vectors and similarity search for over 100,000 files.
|
||||
|
||||
With 500 daily active users, the [Quivr.app](http://Quivr.app) is becoming the preferred way for users to create a second brain.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- Backend: Fast API + Langchain, hosted on AWS Fargate
|
||||
- Frontend: Next.js, hosted on Vercel
|
||||
- Database: Supabase Vector, using pgvector
|
||||
- LLM: OpenAI, Anthropic, Nomic
|
||||
- Semantic search using GPT For ALL, Anthropic, and OpenAI
|
||||
- Auth: Supabase Auth
|
||||
@@ -60,11 +60,11 @@ const Developers = () => {
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-12">
|
||||
<nav className="col-span-6 py-8" aria-labelledby="developersResources">
|
||||
<div className="m-3 grid grid-cols-12 gap-4 py-4 pr-3">{iconSections}</div>
|
||||
<nav className="col-span-6 py-6" aria-labelledby="developersResources">
|
||||
<div className="m-3 grid grid-cols-12 gap-2 pr-3">{iconSections}</div>
|
||||
</nav>
|
||||
<div className="col-span-6">
|
||||
<div className="m-3 mx-6">
|
||||
<div className="col-span-6 py-8">
|
||||
<div className="py-3 mx-6">
|
||||
<p className="p">Latest announcements</p>
|
||||
<ul className="mt-6 space-y-3">
|
||||
{AnnouncementsData.map((announcement: any, idx: number) => (
|
||||
|
||||
@@ -412,11 +412,6 @@ const Nav = () => {
|
||||
Developers
|
||||
</a>
|
||||
</Link>
|
||||
<Link href="/company">
|
||||
<a className="block py-2 pl-3 pr-4 text-base font-medium rounded-md text-scale-900 dark:hover:bg-scale-600 hover:border-gray-300 hover:bg-gray-50 dark:text-white">
|
||||
Company
|
||||
</a>
|
||||
</Link>
|
||||
<Link href="/pricing">
|
||||
<a className="block py-2 pl-3 pr-4 text-base font-medium rounded-md text-scale-900 dark:hover:bg-scale-600 hover:border-gray-300 hover:bg-gray-50 dark:text-white">
|
||||
Pricing
|
||||
@@ -430,14 +425,6 @@ const Nav = () => {
|
||||
Docs
|
||||
</a>
|
||||
</Link>
|
||||
<Link href="https://github.com/supabase/supabase">
|
||||
<a
|
||||
target="_blank"
|
||||
className="block py-2 pl-3 pr-4 text-base font-medium rounded-md text-scale-900 dark:hover:bg-scale-600 hover:border-gray-300 hover:bg-gray-50 dark:text-white"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
</Link>
|
||||
<Link href="/blog">
|
||||
<a
|
||||
target="_blank"
|
||||
@@ -446,6 +433,11 @@ const Nav = () => {
|
||||
Blog
|
||||
</a>
|
||||
</Link>
|
||||
<Link href="/support">
|
||||
<a className="block py-2 pl-3 pr-4 text-base font-medium rounded-md text-scale-900 dark:hover:bg-scale-600 hover:border-gray-300 hover:bg-gray-50 dark:text-white">
|
||||
Support
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="p-3">
|
||||
<p className="mb-6 text-sm text-scale-900">Products available:</p>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React, { Fragment, useMemo } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useTheme } from 'common/Providers'
|
||||
import { IconCheckCircle, IconXCircle, Modal } from 'ui'
|
||||
import { IconXCircle, Modal } from 'ui'
|
||||
import pricingAddOn from '~/data/PricingAddOnTable.json'
|
||||
import { IconPricingIncludedCheck, IconPricingMinus } from './PricingIcons'
|
||||
import Link from 'next/link'
|
||||
|
||||
interface Props {
|
||||
showComputeModal: boolean
|
||||
@@ -47,12 +48,33 @@ export default function ComputePricingModal({ showComputeModal, setShowComputeMo
|
||||
</div>
|
||||
<div className="max-w-4xl prose">
|
||||
<h2 className="text-lg">Choose best compute setup for you</h2>
|
||||
<p className="text-sm lg:max-w-3xl">
|
||||
<p className="text-sm">
|
||||
Every project on the Supabase Platform comes with its own dedicated Postgres
|
||||
instance running inside a virtual machine (VM). The following table describes the
|
||||
base instance with additional compute add-ons available if you need extra
|
||||
performance when scaling up Supabase.
|
||||
</p>
|
||||
<p className="text-sm">
|
||||
Compute instances are billed hourly and you can scale up or down at any time if you
|
||||
need extra performance. You'll only be charged at the end of the month for the hours
|
||||
you've used. Paid plans come with $10 in Compute Credits to cover one Starter
|
||||
instance or parts of any other instance. Read more on{' '}
|
||||
<Link
|
||||
href="https://supabase.com/docs/guides/platform/org-based-billing#usage-based-billing-for-compute"
|
||||
passHref
|
||||
>
|
||||
<a target="_blank" className="transition text-brand hover:text-brand-600">
|
||||
usage-based billing for compute
|
||||
</a>
|
||||
</Link>{' '}
|
||||
or{' '}
|
||||
<Link href="https://supabase.com/docs/guides/platform/compute-add-ons" passHref>
|
||||
<a target="_blank" className="transition text-brand hover:text-brand-600">
|
||||
Compute Add-ons
|
||||
</a>
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -73,7 +95,7 @@ export default function ComputePricingModal({ showComputeModal, setShowComputeMo
|
||||
{i === 0 && (
|
||||
<tr className="">
|
||||
<td className="pb-1 bg-scale-700 px-3 py-1 -mr-1 border-l-4 border-scale-700">
|
||||
<span className="">Included in Free and Pro plan</span>
|
||||
<span className="">First instance is free on paid plans</span>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useRouter } from 'next/router'
|
||||
import Link from 'next/link'
|
||||
import { useWindowSize } from 'react-use'
|
||||
|
||||
import TweetCard from '~/components/TweetCard'
|
||||
import { TweetCard } from 'ui'
|
||||
import { Swiper, SwiperSlide } from 'swiper/react'
|
||||
import SwiperCore, { Autoplay } from 'swiper'
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -1,31 +1,26 @@
|
||||
import { useRef } from 'react'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
import { Swiper, SwiperSlide } from 'swiper/react'
|
||||
// import Swiper core and required modules
|
||||
import SwiperCore, { Navigation, Pagination } from 'swiper'
|
||||
|
||||
import { Button, IconMessageCircle, IconArrowLeft, IconArrowRight } from 'ui'
|
||||
|
||||
import Examples from '../../data/tweets/Tweets.json'
|
||||
import TweetCard from '../TweetCard'
|
||||
|
||||
// Import Swiper styles
|
||||
import 'swiper/swiper.min.css'
|
||||
import 'swiper/components/navigation/navigation.min.css'
|
||||
import 'swiper/components/pagination/pagination.min.css'
|
||||
import { Button, IconMessageCircle, TweetCard } from 'ui'
|
||||
import Tweets from '../../data/tweets/Tweets.json'
|
||||
import Link from 'next/link'
|
||||
|
||||
// install Swiper modules
|
||||
SwiperCore.use([Navigation, Pagination])
|
||||
|
||||
function TwitterSocialProof() {
|
||||
// base path for images
|
||||
const { basePath } = useRouter()
|
||||
|
||||
const prevRef = useRef(null)
|
||||
const nextRef = useRef(null)
|
||||
const [tweets, setTweets] = useState(Tweets.slice(0, 10))
|
||||
const [showButton, setShowButton] = useState(true)
|
||||
|
||||
const handleShowMore = () => {
|
||||
setTweets((prevTweets) => [
|
||||
...prevTweets,
|
||||
...Tweets.slice(prevTweets.length, prevTweets.length + 10),
|
||||
])
|
||||
|
||||
if (tweets.length >= Tweets.length) {
|
||||
setShowButton(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -54,72 +49,34 @@ function TwitterSocialProof() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<div className="cursor-move lg:-mr-32 lg:-ml-32">
|
||||
<Swiper
|
||||
loop={true}
|
||||
initialSlide={3}
|
||||
spaceBetween={0}
|
||||
slidesPerView={4}
|
||||
speed={300}
|
||||
navigation={{
|
||||
prevEl: prevRef.current,
|
||||
nextEl: nextRef.current,
|
||||
// prevEl: prevRef.current ? prevRef.current : undefined,
|
||||
// nextEl: nextRef.current ? nextRef.current : undefined,
|
||||
}}
|
||||
onInit={(swiper: any) => {
|
||||
swiper.params.navigation.prevEl = prevRef.current
|
||||
swiper.params.navigation.nextEl = nextRef.current
|
||||
// swiper.navigation.update()
|
||||
}}
|
||||
breakpoints={{
|
||||
320: {
|
||||
slidesPerView: 1,
|
||||
},
|
||||
720: {
|
||||
slidesPerView: 2,
|
||||
},
|
||||
920: {
|
||||
slidesPerView: 3,
|
||||
},
|
||||
1024: {
|
||||
slidesPerView: 4,
|
||||
},
|
||||
1208: {
|
||||
slidesPerView: 5,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{Examples.map((tweet: any, i: number) => {
|
||||
return (
|
||||
<SwiperSlide key={i}>
|
||||
<div className="mr-3 ml-3 mt-1">
|
||||
<Link href={tweet.url}>
|
||||
<a
|
||||
target="_blank"
|
||||
className="block cursor-pointer focus:outline-none focus:border-none focus:ring-brand-600 focus:ring-2 focus:rounded-2xl"
|
||||
>
|
||||
<TweetCard
|
||||
key={i}
|
||||
handle={`@${tweet.handle}`}
|
||||
quote={tweet.text}
|
||||
img_url={`${basePath}${tweet.img_url}`}
|
||||
/>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
)
|
||||
})}
|
||||
<div className="container mx-auto mt-3 hidden flex-row justify-between md:flex">
|
||||
<div ref={prevRef} className="p ml-4 cursor-pointer">
|
||||
<IconArrowLeft />
|
||||
</div>
|
||||
<div ref={nextRef} className="p mr-4 cursor-pointer">
|
||||
<IconArrowRight />
|
||||
</div>
|
||||
<div
|
||||
className={`columns-1 sm:columns-2 lg:columns-3 xl:columns-4 gap-4 overflow-hidden relative transition-all`}
|
||||
>
|
||||
{showButton && (
|
||||
<div
|
||||
className={`absolute bottom-0 left-0 z-10 w-full h-[25%] bg-gradient-to-t from-[#1c1c1c] via-[#1c1c1c]`}
|
||||
/>
|
||||
)}
|
||||
{tweets.map((tweet: any, i: number) => (
|
||||
<div className="mb-4 z-0 break-inside-avoid-column" key={i}>
|
||||
<Link href={tweet.url}>
|
||||
<a target="_blank">
|
||||
<TweetCard
|
||||
handle={`@${tweet.handle}`}
|
||||
quote={tweet.text}
|
||||
img_url={`${basePath}${tweet.img_url}`}
|
||||
/>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</Swiper>
|
||||
))}
|
||||
{showButton && (
|
||||
<div className="absolute flex justify-center bottom-0 left-0 right-0 z-20 mb-10">
|
||||
<Button type="default" size="small" onClick={() => handleShowMore()}>
|
||||
Show More
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -1,4 +1,38 @@
|
||||
[
|
||||
{
|
||||
"type": "Customer Story",
|
||||
"title": "Bootstrapped founder builds an AI app with Supabase and scales to $1M in 5 months.",
|
||||
"description": "How Yasser leveraged Supabase to build Chatbase and became one of the most successful single-founder AI products.",
|
||||
"imgUrl": "images/customers/logos/chatbase.png",
|
||||
"logoUrl": "images/customers/logos/chatbase.png",
|
||||
"organization": "Chatbase",
|
||||
"url": "/customers/chatbase",
|
||||
"path": "/customers/chatbase",
|
||||
"postMeta": {
|
||||
"name": "Paul Copplestone",
|
||||
"avatarUrl": "https://avatars0.githubusercontent.com/u/10214025?v=4",
|
||||
"publishDate": "Sep 10, 2023",
|
||||
"readLength": 6
|
||||
},
|
||||
"ctaText": "View story"
|
||||
},
|
||||
{
|
||||
"type": "Customer Story",
|
||||
"title": "Quivr launch 5,000 Vector databases on Supabase.",
|
||||
"description": "Learn how one of the most popular Generative AI projects uses Supabase as their Vector Store.",
|
||||
"imgUrl": "images/customers/logos/quivr.png",
|
||||
"logoUrl": "images/customers/logos/quivr.png",
|
||||
"organization": "Quivr",
|
||||
"url": "/customers/quivr",
|
||||
"path": "/customers/quivr",
|
||||
"postMeta": {
|
||||
"name": "Paul Copplestone",
|
||||
"avatarUrl": "https://avatars0.githubusercontent.com/u/10214025?v=4",
|
||||
"publishDate": "Sep 6, 2023",
|
||||
"readLength": 6
|
||||
},
|
||||
"ctaText": "View story"
|
||||
},
|
||||
{
|
||||
"type": "Customer Story",
|
||||
"title": "Berri AI Boosts Productivity by Migrating from AWS RDS to Supabase with pgvector.",
|
||||
|
||||
@@ -1,38 +1,44 @@
|
||||
export const links = [
|
||||
{
|
||||
text: 'Resources',
|
||||
description: 'View examples, curated content from the community, migration guides, and more.',
|
||||
description: 'View examples, curated content from the community, migration guides, and more',
|
||||
url: '/docs/guides/resources',
|
||||
icon: 'M19.4994 17.9996V7.50107L19.4864 3L14.0723 3L4.5 3L4.5 18.75M4.5 18.75C4.5 19.9926 5.50736 21 6.75 21H19.5V18.5255L19.4871 16.5H14.0728H6.75C5.50736 16.5 4.5 17.5074 4.5 18.75ZM7.50724 3V9L9.75771 6.45713L12.0072 9V3H7.50724Z',
|
||||
},
|
||||
{
|
||||
text: 'Changelog',
|
||||
description: 'See the latest updates and product improvements.',
|
||||
description: 'See the latest updates and product improvements',
|
||||
url: '/changelog',
|
||||
icon: 'M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z',
|
||||
},
|
||||
{
|
||||
text: 'Careers',
|
||||
description: 'Join the Supabase team and get involved.',
|
||||
description: 'Join the Supabase team and get involved',
|
||||
url: '/careers',
|
||||
icon: 'M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z',
|
||||
},
|
||||
{
|
||||
text: 'Open Source',
|
||||
description: 'We support existing open source tools and communities wherever possible.',
|
||||
description: 'We support existing open source tools and communities wherever possible',
|
||||
url: '/open-source',
|
||||
icon: 'M10.7607 3.08859C15.7723 2.38592 20.0295 5.94789 20.8525 10.7642C21.5964 15.1122 19.4634 19.1692 15.8778 20.9967L14.0736 16.084C15.129 15.4346 15.8232 14.4223 16.0198 13.0819C16.3702 10.7219 14.7442 8.47426 12.3801 8.25648C10.2228 8.04047 8.33304 9.61601 7.99823 11.7702L7.99761 11.7743C7.73427 13.5096 8.47082 15.1847 9.92827 16.0841L8.12693 21C6.34571 20.1066 4.88125 18.6129 3.97963 16.7387L3.97879 16.7369C3.15872 15.0395 3.07406 13.7081 3.01524 12.783C3.01012 12.7024 3.0052 12.6249 3 12.5506C3.10941 7.30353 6.59243 3.6764 10.7607 3.08859Z',
|
||||
},
|
||||
{
|
||||
text: 'Partners',
|
||||
description: 'Become a Supabase Partner and enable new business opportunities.',
|
||||
description: 'Become a Supabase Partner and enable new business opportunities',
|
||||
url: '/partners',
|
||||
icon: 'M12.0003 6.1488L10.5812 4.76334C8.68921 2.91613 5.62161 2.91613 3.72957 4.76334C1.98628 6.46532 1.84923 9.14165 3.31842 10.9968C3.44378 11.1551 3.77166 11.514 3.92041 11.6592M12.0003 6.1488L13.4193 4.76334C15.3113 2.91613 18.3789 2.91613 20.271 4.76334C22.163 6.61054 22.163 9.60546 20.271 11.4527L19.2017 12.5186M12.0003 6.1488L10.7542 7.39725C10.0214 8.13141 10.0219 9.32043 10.7554 10.0539C11.4894 10.7879 12.6793 10.7879 13.4133 10.0539L16.0693 7.39787M14.9131 13.4531L17.4954 16.1809M17.4954 16.1809C18.2089 16.9347 18.1764 18.1242 17.4226 18.8378C16.6693 19.5509 15.4807 19.5189 14.7669 18.7663L13.3188 17.2217M17.4954 16.1809C18.2086 16.9344 19.3982 16.9676 20.1516 16.2543C20.9051 15.5411 20.9376 14.3521 20.2244 13.5987L17.6415 10.8703M6.13555 14.7589L7.46387 13.4306M6.13555 14.7589C5.40193 15.4925 4.21251 15.4925 3.47889 14.7589C2.74528 14.0253 2.74528 12.8358 3.47889 12.1022L4.80722 10.7739C5.54084 10.0403 6.73026 10.0403 7.46388 10.7739C8.19749 11.5075 8.19749 12.6969 7.46387 13.4306M6.13555 14.7589C5.40193 15.4925 5.40193 16.6819 6.13555 17.4155C6.86916 18.1492 8.05859 18.1492 8.7922 17.4155M7.46387 13.4306C8.19749 12.6969 9.38691 12.6969 10.1205 13.4306C10.8541 14.1642 10.8541 15.3536 10.1205 16.0872M8.7922 17.4155L10.1205 16.0872M8.7922 17.4155C8.05859 18.1492 8.05859 19.3386 8.7922 20.0722C9.52582 20.8058 10.7152 20.8058 11.4489 20.0722L12.7772 18.7439C13.5108 18.0102 13.5108 16.8208 12.7772 16.0872C12.0436 15.3536 10.8541 15.3536 10.1205 16.0872',
|
||||
},
|
||||
{
|
||||
text: 'Integrations',
|
||||
description: 'Use your favorite tools with Supabase.',
|
||||
description: 'Use your favorite tools with Supabase',
|
||||
url: '/partners/integrations',
|
||||
icon: 'M6.87232 21.5743C9.09669 21.5743 10.8999 19.7711 10.8999 17.5467C10.8999 15.3223 9.09669 13.5191 6.87232 13.5191C4.64794 13.5191 2.84473 15.3223 2.84473 17.5467C2.84473 19.7711 4.64794 21.5743 6.87232 21.5743Z M17.127 3.67236V11.1724M20.877 7.42274H13.377M3.12305 3.67236H10.6231V11.1724H3.12305V3.67236ZM13.377 13.7966H20.877V21.2966H13.377V13.7966ZM10.8999 17.5467C10.8999 19.7711 9.09669 21.5743 6.87232 21.5743C4.64794 21.5743 2.84473 19.7711 2.84473 17.5467C2.84473 15.3223 4.64794 13.5191 6.87232 13.5191C9.09669 13.5191 10.8999 15.3223 10.8999 17.5467Z',
|
||||
},
|
||||
{
|
||||
text: 'Support',
|
||||
description: 'The Supabase Support Team is ready to help',
|
||||
url: '/support',
|
||||
icon: 'M8.72766 9C9.27678 7.83481 10.7584 7 12.5001 7C14.7092 7 16.5001 8.34315 16.5001 10C16.5001 11.3994 15.2224 12.5751 13.4943 12.9066C12.9519 13.0107 12.5001 13.4477 12.5001 14M12.5 17H12.51M21.5 12C21.5 16.9706 17.4706 21 12.5 21C7.52944 21 3.5 16.9706 3.5 12C3.5 7.02944 7.52944 3 12.5 3C17.4706 3 21.5 7.02944 21.5 12Z',
|
||||
},
|
||||
]
|
||||
@@ -63,6 +63,10 @@
|
||||
"text": "Brand Assets / Logos",
|
||||
"url": "/brand-assets"
|
||||
},
|
||||
{
|
||||
"text": "Security and Compliance",
|
||||
"url": "/security"
|
||||
},
|
||||
{
|
||||
"text": "DPA",
|
||||
"url": "/legal/dpa"
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
{
|
||||
"columns": [
|
||||
{ "key": "plan", "title": "Plan", "value": "Starter" },
|
||||
{ "key": "pricing", "title": "Pricing", "value": "$0" },
|
||||
{ "key": "pricing", "title": "Price USD", "value": "$10" },
|
||||
{ "key": "cpu", "title": "CPU", "value": "2-core ARM" },
|
||||
{ "key": "dedicated", "title": "Dedicated", "value": false },
|
||||
{ "key": "memory", "title": "Memory", "value": "1 GB" },
|
||||
@@ -36,7 +36,7 @@
|
||||
{
|
||||
"columns": [
|
||||
{ "key": "plan", "title": "Plan", "value": "Small" },
|
||||
{ "key": "pricing", "title": "Pricing", "value": "$5" },
|
||||
{ "key": "pricing", "title": "Price USD", "value": "$15" },
|
||||
{ "key": "cpu", "title": "CPU", "value": "2-core ARM" },
|
||||
{ "key": "dedicated", "title": "Dedicated", "value": false },
|
||||
{ "key": "memory", "title": "Memory", "value": "2 GB" },
|
||||
@@ -61,7 +61,7 @@
|
||||
{
|
||||
"columns": [
|
||||
{ "key": "plan", "title": "Plan", "value": "Medium" },
|
||||
{ "key": "pricing", "title": "Pricing", "value": "$50" },
|
||||
{ "key": "pricing", "title": "Price USD", "value": "$60" },
|
||||
{ "key": "cpu", "title": "CPU", "value": "2-core ARM" },
|
||||
{ "key": "dedicated", "title": "Dedicated", "value": false },
|
||||
{ "key": "memory", "title": "Memory", "value": "4 GB" },
|
||||
@@ -86,7 +86,7 @@
|
||||
{
|
||||
"columns": [
|
||||
{ "key": "plan", "title": "Plan", "value": "Large" },
|
||||
{ "key": "pricing", "title": "Pricing", "value": "$100" },
|
||||
{ "key": "pricing", "title": "Price USD", "value": "$110" },
|
||||
{ "key": "cpu", "title": "CPU", "value": "2-core ARM" },
|
||||
{ "key": "dedicated", "title": "Dedicated", "value": true },
|
||||
{ "key": "memory", "title": "Memory", "value": "8 GB" },
|
||||
@@ -111,7 +111,7 @@
|
||||
{
|
||||
"columns": [
|
||||
{ "key": "plan", "title": "Plan", "value": "XL" },
|
||||
{ "key": "pricing", "title": "Pricing", "value": "$200" },
|
||||
{ "key": "pricing", "title": "Price USD", "value": "$210" },
|
||||
{ "key": "cpu", "title": "CPU", "value": "4-core ARM" },
|
||||
{ "key": "dedicated", "title": "Dedicated", "value": true },
|
||||
{ "key": "memory", "title": "Memory", "value": "16 GB" },
|
||||
@@ -136,7 +136,7 @@
|
||||
{
|
||||
"columns": [
|
||||
{ "key": "plan", "title": "Plan", "value": "2XL" },
|
||||
{ "key": "pricing", "title": "Pricing", "value": "$400" },
|
||||
{ "key": "pricing", "title": "Price USD", "value": "$410" },
|
||||
{ "key": "cpu", "title": "CPU", "value": "8-core ARM" },
|
||||
{ "key": "dedicated", "title": "Dedicated", "value": true },
|
||||
{ "key": "memory", "title": "Memory", "value": "32 GB" },
|
||||
@@ -161,7 +161,7 @@
|
||||
{
|
||||
"columns": [
|
||||
{ "key": "plan", "title": "Plan", "value": "4XL" },
|
||||
{ "key": "pricing", "title": "Pricing", "value": "$950" },
|
||||
{ "key": "pricing", "title": "Price USD", "value": "$960" },
|
||||
{ "key": "cpu", "title": "CPU", "value": "16-core ARM" },
|
||||
{ "key": "dedicated", "title": "Dedicated", "value": true },
|
||||
{ "key": "memory", "title": "Memory", "value": "64 GB" },
|
||||
@@ -186,7 +186,7 @@
|
||||
{
|
||||
"columns": [
|
||||
{ "key": "plan", "title": "Plan", "value": "8XL" },
|
||||
{ "key": "pricing", "title": "Pricing", "value": "$1,860" },
|
||||
{ "key": "pricing", "title": "Price USD", "value": "$1,870" },
|
||||
{ "key": "cpu", "title": "CPU", "value": "32-core ARM" },
|
||||
{ "key": "dedicated", "title": "Dedicated", "value": true },
|
||||
{ "key": "memory", "title": "Memory", "value": "128 GB" },
|
||||
@@ -211,7 +211,7 @@
|
||||
{
|
||||
"columns": [
|
||||
{ "key": "plan", "title": "Plan", "value": "12XL" },
|
||||
{ "key": "pricing", "title": "Pricing", "value": "$2,790" },
|
||||
{ "key": "pricing", "title": "Price USD", "value": "$2,800" },
|
||||
{ "key": "cpu", "title": "CPU", "value": "48-core ARM" },
|
||||
{ "key": "dedicated", "title": "Dedicated", "value": true },
|
||||
{ "key": "memory", "title": "Memory", "value": "192 GB" },
|
||||
@@ -240,7 +240,7 @@
|
||||
{
|
||||
"columns": [
|
||||
{ "key": "plan", "title": "Plan", "value": "16XL" },
|
||||
{ "key": "pricing", "title": "Pricing", "value": "$3,720" },
|
||||
{ "key": "pricing", "title": "Price USD", "value": "$3,730" },
|
||||
{ "key": "cpu", "title": "CPU", "value": "64-core ARM" },
|
||||
{ "key": "dedicated", "title": "Dedicated", "value": true },
|
||||
{ "key": "memory", "title": "Memory", "value": "256 GB" },
|
||||
|
||||
@@ -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={[
|
||||
|
||||
@@ -2288,4 +2288,14 @@ 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',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/guides/ai/managing-indexes',
|
||||
destination: '/docs/guides/ai/vector-indexes',
|
||||
},
|
||||
]
|
||||
@@ -23,7 +23,7 @@ import ProductIcon from '~/components/ProductIcon'
|
||||
import APISection from '~/components/Sections/APISection'
|
||||
import GithubExamples from '~/components/Sections/GithubExamples'
|
||||
import ProductHeader from '~/components/Sections/ProductHeader'
|
||||
import TweetCard from '~/components/TweetCard'
|
||||
import { TweetCard } from 'ui'
|
||||
|
||||
// install Swiper's Controller component
|
||||
// SwiperCore.use([Controller])
|
||||
|
||||
@@ -41,7 +41,7 @@ const DPA = () => {
|
||||
<SectionContainer>
|
||||
<div className="mx-auto grid max-w-2xl grid-cols-12 rounded-lg">
|
||||
<div className="col-span-12 flex items-center lg:col-span-12">
|
||||
<div className="prose flex flex-col space-y-8 p-16">
|
||||
<div className="prose flex flex-col space-y-8 pb-16">
|
||||
<h1 className="text-center text-5xl">DPA</h1>
|
||||
<p>
|
||||
We have a long-standing commitment to customer privacy and data protection, and as
|
||||
@@ -82,7 +82,7 @@ const DPA = () => {
|
||||
className="mr-1"
|
||||
loading={isSubmitting}
|
||||
>
|
||||
Download DPA document
|
||||
Download DPA
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -90,7 +90,7 @@ function Partner({
|
||||
) : null}
|
||||
<DefaultLayout>
|
||||
<SectionContainer>
|
||||
<div className="col-span-12 mx-auto mb-2 max-w-5xl space-y-12 lg:col-span-2">
|
||||
<div className="col-span-12 mx-auto mb-2 max-w-5xl space-y-10 lg:col-span-2">
|
||||
{/* Back button */}
|
||||
<Link href="/partners/integrations">
|
||||
<a className="text-scale-1200 hover:text-scale-1000 flex cursor-pointer items-center transition-colors">
|
||||
@@ -114,56 +114,66 @@ function Partner({
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="bg-scale-300 py-6"
|
||||
className="bg-gradient-to-t from-scale-100 to-scale-200 border-b p-6 [&_.swiper-container]:overflow-visible"
|
||||
style={{ marginLeft: 'calc(50% - 50vw)', marginRight: 'calc(50% - 50vw)' }}
|
||||
>
|
||||
<Swiper
|
||||
initialSlide={0}
|
||||
spaceBetween={0}
|
||||
slidesPerView={4}
|
||||
speed={300}
|
||||
// slidesOffsetBefore={300}
|
||||
centerInsufficientSlides={true}
|
||||
breakpoints={{
|
||||
320: {
|
||||
slidesPerView: 1,
|
||||
},
|
||||
720: {
|
||||
slidesPerView: 2,
|
||||
},
|
||||
920: {
|
||||
slidesPerView: 3,
|
||||
},
|
||||
1024: {
|
||||
slidesPerView: 4,
|
||||
},
|
||||
1280: {
|
||||
slidesPerView: 5,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{partner.images?.map((image: any, i: number) => {
|
||||
return (
|
||||
<SwiperSlide key={i}>
|
||||
<div className="relative ml-3 mr-3 block cursor-move overflow-hidden rounded-md">
|
||||
<Image
|
||||
layout="responsive"
|
||||
objectFit="contain"
|
||||
width={1460}
|
||||
height={960}
|
||||
src={image}
|
||||
alt={partner.title}
|
||||
onClick={() => setFocusedImage(image)}
|
||||
/>
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
)
|
||||
})}
|
||||
</Swiper>
|
||||
<SectionContainer className="!py-0 !px-3 lg:!px-12 xl:!p-0 mx-auto max-w-5xl">
|
||||
<Swiper
|
||||
initialSlide={0}
|
||||
spaceBetween={20}
|
||||
slidesPerView={4}
|
||||
speed={300}
|
||||
grabCursor
|
||||
centeredSlides={false}
|
||||
centerInsufficientSlides={false}
|
||||
breakpoints={{
|
||||
320: {
|
||||
slidesPerView: 1.25,
|
||||
centeredSlides: false,
|
||||
spaceBetween: 10,
|
||||
},
|
||||
720: {
|
||||
slidesPerView: 2,
|
||||
centeredSlides: false,
|
||||
spaceBetween: 10,
|
||||
},
|
||||
920: {
|
||||
slidesPerView: 3,
|
||||
centeredSlides: false,
|
||||
},
|
||||
1024: {
|
||||
slidesPerView: 4,
|
||||
},
|
||||
1280: {
|
||||
slidesPerView: 5,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{partner.images?.map((image: any, i: number) => {
|
||||
return (
|
||||
<SwiperSlide key={i}>
|
||||
<div className="relative block overflow-hidden rounded-md">
|
||||
<Image
|
||||
layout="responsive"
|
||||
objectFit="contain"
|
||||
placeholder="blur"
|
||||
blurDataURL="/images/blur.png"
|
||||
width={1460}
|
||||
height={960}
|
||||
src={image}
|
||||
alt={partner.title}
|
||||
onClick={() => setFocusedImage(image)}
|
||||
/>
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
)
|
||||
})}
|
||||
</Swiper>
|
||||
</SectionContainer>
|
||||
</div>
|
||||
|
||||
<div className="grid lg:grid-cols-8 lg:space-x-12">
|
||||
<div className="lg:col-span-5">
|
||||
<div className="lg:col-span-5 overflow-hidden">
|
||||
<h2
|
||||
className="text-scale-1200"
|
||||
style={{ fontSize: '1.5rem', marginBottom: '1rem' }}
|
||||
@@ -176,7 +186,7 @@ function Partner({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="lg:col-span-3 order-first lg:order-last pt-16 lg:pt-0">
|
||||
<div className="lg:col-span-3 order-first lg:order-last">
|
||||
<div className="sticky top-20">
|
||||
<h2
|
||||
className="text-scale-1200"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Accordion, Button, IconCheck, Select } from 'ui'
|
||||
import { Accordion, Badge, Button, IconCheck, Select } from 'ui'
|
||||
import Solutions from 'data/Solutions'
|
||||
import { NextSeo } from 'next-seo'
|
||||
import Link from 'next/link'
|
||||
@@ -13,6 +13,7 @@ import pricingFaq from '~/data/PricingFAQ.json'
|
||||
import { useTheme } from 'common/Providers'
|
||||
import ComputePricingModal from '~/components/Pricing/ComputePricingModal'
|
||||
import { plans } from 'shared-data/plans'
|
||||
import { ArrowNarrowRightIcon } from '@heroicons/react/outline'
|
||||
|
||||
export default function IndexPage() {
|
||||
const router = useRouter()
|
||||
@@ -121,7 +122,7 @@ export default function IndexPage() {
|
||||
<p className="p">{priceDescription}</p>
|
||||
</div>
|
||||
<p className="p">{description}</p>
|
||||
<Link href="https://supabase.com/dashboard" passHref>
|
||||
<Link href="https://supabase.com/dashboard/new" passHref>
|
||||
<a>
|
||||
<Button size="medium" block>
|
||||
Get started
|
||||
@@ -158,6 +159,46 @@ export default function IndexPage() {
|
||||
<p className="p text-lg">
|
||||
Start building for free, collaborate with a team, then scale to millions of users.
|
||||
</p>
|
||||
<div className="w-full flex justify-center items-center opacity-0 !animate-[fadeIn_0.5s_cubic-bezier(0.25,0.25,0,1)_0.5s_both]">
|
||||
<Link href="/blog/organization-based-billing" passHref>
|
||||
<a
|
||||
target="_blank"
|
||||
className="
|
||||
group
|
||||
relative
|
||||
flex flex-row
|
||||
items-center
|
||||
pr-3 p-1
|
||||
text-sm
|
||||
w-auto
|
||||
gap-2
|
||||
text-left
|
||||
rounded-full
|
||||
bg-opacity-20
|
||||
border
|
||||
border-background-surface-100
|
||||
hover:border-background-surface-300
|
||||
overflow-hidden
|
||||
focus:outline-none focus:ring-brand-600 focus:ring-2 focus:rounded-full
|
||||
"
|
||||
>
|
||||
<Badge color="brand" size="large" className="py-1">
|
||||
Update
|
||||
</Badge>
|
||||
<span className="text-foreground">Changes to how we bill</span>
|
||||
<ArrowNarrowRightIcon className="h-4 ml-2 -translate-x-1 transition-transform group-hover:translate-x-0" />
|
||||
<div
|
||||
className="absolute inset-0 -z-10 bg-gradient-to-br
|
||||
opacity-70
|
||||
overflow-hidden rounded-full
|
||||
from-background-surface-100
|
||||
to-background-surface-300
|
||||
backdrop-blur-md
|
||||
"
|
||||
/>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -247,7 +288,7 @@ export default function IndexPage() {
|
||||
{plan.priceMonthly}
|
||||
</p>
|
||||
<p className="text-scale-900 mb-1.5 ml-1 text-[13px] leading-4">
|
||||
{plan.costUnit}
|
||||
{plan.costUnitOrg}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -404,7 +445,7 @@ export default function IndexPage() {
|
||||
<p className="mt-3 prose lg:max-w-lg">
|
||||
The Pro plan has a usage quota included and a spend cap turned on by default. If you
|
||||
need to go beyond the inclusive limits, simply switch off your spend cap to pay for
|
||||
additional usage and scale seamlessly. Note that your project will run into
|
||||
additional usage and scale seamlessly. Note that your projects will run into
|
||||
restrictions if you have the spend cap enabled and exhaust your quota.
|
||||
</p>
|
||||
</div>
|
||||
@@ -456,7 +497,7 @@ export default function IndexPage() {
|
||||
<MobileHeader
|
||||
plan="Free"
|
||||
price={'0'}
|
||||
priceDescription={'/mo'}
|
||||
priceDescription={'/month'}
|
||||
description={'Perfect for hobby projects and experiments'}
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
@@ -516,7 +557,7 @@ export default function IndexPage() {
|
||||
plan="Pro"
|
||||
from={false}
|
||||
price={'25'}
|
||||
priceDescription={'/mo + additional use'}
|
||||
priceDescription={'/month + additional use'}
|
||||
description={'Everything you need to scale your project into production'}
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
@@ -568,7 +609,7 @@ export default function IndexPage() {
|
||||
plan="Team"
|
||||
from={false}
|
||||
price={'599'}
|
||||
priceDescription={'/mo + additional use'}
|
||||
priceDescription={'/month + additional use'}
|
||||
description={'Collaborate with different permissions and access patterns'}
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
@@ -744,10 +785,10 @@ export default function IndexPage() {
|
||||
{plan.priceMonthly}
|
||||
</span>
|
||||
{['Pro', 'Free'].includes(plan.name) && (
|
||||
<p className="p text-[13px] leading-4 mt-1">per month</p>
|
||||
<p className="p text-[13px] leading-4 mt-1">/ month / org</p>
|
||||
)}
|
||||
{['Team'].includes(plan.name) && (
|
||||
<p className="p text-[13px] leading-4 mt-1">per month</p>
|
||||
<p className="p text-[13px] leading-4 mt-1">/ month / org</p>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -827,12 +868,12 @@ export default function IndexPage() {
|
||||
|
||||
<td className="px-6 pt-5">
|
||||
<Link
|
||||
href="https://supabase.com/dashboard"
|
||||
as="https://supabase.com/dashboard"
|
||||
href="https://supabase.com/dashboard/new?plan=free"
|
||||
as="https://supabase.com/dashboard/new?plan=free"
|
||||
>
|
||||
<a>
|
||||
<Button size="tiny" type="primary" block>
|
||||
Get started
|
||||
Get Started
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
@@ -840,22 +881,22 @@ export default function IndexPage() {
|
||||
|
||||
<td className="px-6 pt-5">
|
||||
<Link
|
||||
href="https://supabase.com/dashboard"
|
||||
as="https://supabase.com/dashboard"
|
||||
href="https://supabase.com/dashboard/new?plan=pro"
|
||||
as="https://supabase.com/dashboard/new?plan=pro"
|
||||
>
|
||||
<a>
|
||||
<Button size="tiny" type="primary" block>
|
||||
Get started
|
||||
Get Started
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
</td>
|
||||
|
||||
<td className="px-6 pt-5">
|
||||
<Link href="https://forms.supabase.com/team">
|
||||
<Link href="https://supabase.com/dashboard/new?plan=team">
|
||||
<a>
|
||||
<Button size="tiny" type="primary" block>
|
||||
Contact us
|
||||
Get Started
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
@@ -865,7 +906,7 @@ export default function IndexPage() {
|
||||
<Link href="https://forms.supabase.com/enterprise">
|
||||
<a>
|
||||
<Button size="tiny" type="default" block>
|
||||
Contact us
|
||||
Contact Us
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
|
||||
@@ -5,63 +5,77 @@
|
||||
<link>https://supabase.com</link>
|
||||
<description>Latest news from Supabase</description>
|
||||
<language>en</language>
|
||||
<lastBuildDate>Mon, 05 Jun 2023 21:00:00 GMT</lastBuildDate>
|
||||
<lastBuildDate>Thu, 05 Oct 2023 22:00:00 GMT</lastBuildDate>
|
||||
<atom:link href="https://supabase.com/rss.xml" rel="self" type="application/rss+xml"/>
|
||||
<item>
|
||||
<guid>https://supabase.com/customers/chatbase</guid>
|
||||
<title>Bootstrapped founder builds an AI app with Supabase and scales to $1M in 5 months</title>
|
||||
<link>https://supabase.com/customers/chatbase</link>
|
||||
<description>How Yasser leveraged Supabase to build Chatbase and became one of the most successful single-founder AI products.</description>
|
||||
<pubDate>Thu, 05 Oct 2023 22:00:00 GMT</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<guid>https://supabase.com/customers/quivr</guid>
|
||||
<title>Quivr launch 5,000 Vector databases on Supabase.</title>
|
||||
<link>https://supabase.com/customers/quivr</link>
|
||||
<description>Learn how one of the most popular Generative AI projects uses Supabase as their Vector Store.</description>
|
||||
<pubDate>Wed, 04 Oct 2023 22:00:00 GMT</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<guid>https://supabase.com/customers/berriai</guid>
|
||||
<title>Berri AI boosts productivity by migrating from AWS RDS to Supabase Vector</title>
|
||||
<link>https://supabase.com/customers/berriai</link>
|
||||
<description>Learn how Berri AI overcame challenges with self-hosting their vector database on AWS RDS and successfully migrated to Supabase.</description>
|
||||
<pubDate>Mon, 05 Jun 2023 21:00:00 GMT</pubDate>
|
||||
<pubDate>Mon, 05 Jun 2023 22:00:00 GMT</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<guid>https://supabase.com/customers/markprompt</guid>
|
||||
<title>Markprompt and Supabase - GDPR-compliant AI chatbots for docs and websites.</title>
|
||||
<link>https://supabase.com/customers/markprompt</link>
|
||||
<description>AI-powered chatbot platform, Markprompt, empowers developers to deliver efficient and GDPR-compliant prompt experiences on top of their content, by leveraging Supabase's secure and privacy-focused vector database and authentication solutions.</description>
|
||||
<pubDate>Tue, 16 May 2023 21:00:00 GMT</pubDate>
|
||||
<pubDate>Tue, 16 May 2023 22:00:00 GMT</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<guid>https://supabase.com/customers/mendableai</guid>
|
||||
<title>Mendable switches from Pinecone to Supabase Vector for PostgreSQL vector embeddings.</title>
|
||||
<link>https://supabase.com/customers/mendableai</link>
|
||||
<description>How Mendable boosts efficiency and accuracy of chat powered search for documentation using Supabase Vector.</description>
|
||||
<pubDate>Thu, 04 May 2023 21:00:00 GMT</pubDate>
|
||||
<pubDate>Thu, 04 May 2023 22:00:00 GMT</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<guid>https://supabase.com/customers/happyteams</guid>
|
||||
<title>HappyTeams unlocks better performance and reduces cost with Supabase.</title>
|
||||
<link>https://supabase.com/customers/happyteams</link>
|
||||
<description>How a bootstrapped startup migrated from Heroku to Supabase in 30 minutes and never looked back.</description>
|
||||
<pubDate>Wed, 15 Feb 2023 22:00:00 GMT</pubDate>
|
||||
<pubDate>Wed, 15 Feb 2023 23:00:00 GMT</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<guid>https://supabase.com/customers/xendit</guid>
|
||||
<title>Xendit use Supabase and create a full solution shipped to production in less than one week.</title>
|
||||
<link>https://supabase.com/customers/xendit</link>
|
||||
<description>As a payment processor, Xendit are responsible for verifying that all transactions are legal.</description>
|
||||
<pubDate>Mon, 13 Feb 2023 22:00:00 GMT</pubDate>
|
||||
<pubDate>Mon, 13 Feb 2023 23:00:00 GMT</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<guid>https://supabase.com/customers/replenysh</guid>
|
||||
<title>Replenysh uses Supabase to implement OTP in less than 24 hours.</title>
|
||||
<link>https://supabase.com/customers/replenysh</link>
|
||||
<description>With Supabase, Replenysh gets a slick auth experience, reduces DevOps overhead, and continues to scale with Postgres.</description>
|
||||
<pubDate>Mon, 13 Feb 2023 22:00:00 GMT</pubDate>
|
||||
<pubDate>Mon, 13 Feb 2023 23:00:00 GMT</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<guid>https://supabase.com/customers/mobbin</guid>
|
||||
<title>How Mobbin migrated 200,000 users from Firebase for a better authentication experience.</title>
|
||||
<link>https://supabase.com/customers/mobbin</link>
|
||||
<description>Mobbin helps over 200,000 creators globally search and view the latest design patterns from well-known apps.</description>
|
||||
<pubDate>Mon, 13 Feb 2023 22:00:00 GMT</pubDate>
|
||||
<pubDate>Mon, 13 Feb 2023 23:00:00 GMT</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<guid>https://supabase.com/customers/epsilon3</guid>
|
||||
<title>Epsilon3 digitize paper-based procedures in the space industry using telemetry data, simplifying testing and operations.</title>
|
||||
<link>https://supabase.com/customers/epsilon3</link>
|
||||
<description>Epsilon3 uses Supabase to help teams execute secure and reliable operations in an industry where project spend runs into the billions.</description>
|
||||
<pubDate>Mon, 13 Feb 2023 22:00:00 GMT</pubDate>
|
||||
<pubDate>Mon, 13 Feb 2023 23:00:00 GMT</pubDate>
|
||||
</item>
|
||||
|
||||
</channel>
|
||||
|
||||
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 292 KiB After Width: | Height: | Size: 292 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 158 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 278 KiB After Width: | Height: | Size: 278 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 176 KiB After Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 239 KiB After Width: | Height: | Size: 240 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 308 KiB After Width: | Height: | Size: 207 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 251 KiB After Width: | Height: | Size: 162 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 496 KiB After Width: | Height: | Size: 241 KiB |
|
Before Width: | Height: | Size: 166 KiB After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 154 KiB |
|
After Width: | Height: | Size: 36 KiB |