Skip to content

Commit ae1c7b8

Browse files
chore(indexes): clarify vector index constraints and algorithms (#212)
Co-authored-by: hotdata-automation[bot] <267177015+hotdata-automation[bot]@users.noreply.github.com>
1 parent 43afdb4 commit ae1c7b8

5 files changed

Lines changed: 15 additions & 10 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Changed
1111

12+
- chore(indexes): clarify vector index constraints and algorithms
1213
- chore(indexes): clarify vector precision documentation
1314
- chore: clarify sorted table and index creation constraints
1415
- feat(indexes): add nlist field to index responses

‎docs/CreateIndexRequest.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
# CreateIndexRequest
22

3-
Request body for POST .../indexes Two constraints span the whole table rather than this request alone. A table holds at most one vector index, whichever algorithm it uses. A second vector index is refused until the first is dropped. And a vector index that generates its own embeddings — that is, one created with `embedding_provider_id` — has to be the only index on its table. So a table that already carries any index (sorted, full-text, or vector) will not accept an embedding-backed vector index, and a table that already carries an embedding-backed vector index will not accept any further index of any type. To move between the two arrangements, drop what is there first. Plan for it when designing a table: combining full-text search with generated embeddings on one table is not possible, so use a separate table for the second index, or supply the embeddings yourself. A vector index over a column that already holds vectors — no `embedding_provider_id` — is not affected by that second rule and coexists with sorted and full-text indexes normally. Embedding generation also rewrites the table to add its generated column, and that rewrite cannot preserve a declared partition. An embedding-backed vector index is therefore refused on a partitioned table. No index of any type can be created on a table that declares a sort order (`sorted_by`). Such a request is refused with 400.
3+
Request body for POST .../indexes Two constraints span the whole table rather than this request alone. A table holds at most one vector index, whichever algorithm it uses. A second vector index is refused until the first is dropped. And a vector index that generates its own embeddings — that is, one created with `embedding_provider_id` — has to be the only index on its table. So a table that already carries any index (sorted, full-text, or vector) will not accept an embedding-backed vector index, and a table that already carries an embedding-backed vector index will not accept any further index of any type. This applies to both vector algorithms. To move between the two arrangements, drop what is there first. Plan for it when designing a table: combining full-text search with generated embeddings on one table is not possible, so use a separate table for the second index, or supply the embeddings yourself. A vector index over a column that already holds vectors — no `embedding_provider_id` — is not affected by that second rule and coexists with sorted and full-text indexes normally. Embedding generation also rewrites the table to add its generated column, and that rewrite cannot preserve a declared partition. An embedding-backed vector index is therefore refused on a partitioned table. No index of any type can be created on a table that declares a sort order (`sorted_by`). Such a request is refused with 400.
44

55
## Properties
66

77
Name | Type | Description | Notes
88
------------ | ------------- | ------------- | -------------
9-
**algorithm** | **str** | How a vector index organises the vectors it searches. Omit this field for &#x60;hnsw&#x60;, which is the default. &#x60;hnsw&#x60; — builds a graph of the vectors and keeps it in memory. Searches are very fast, and the memory a search needs grows with the whole table, so a large enough table cannot be served at all. &#x60;ivf&#x60; — groups the vectors into clusters and reads only the clusters nearest the search. Searches are considerably slower than &#x60;hnsw&#x60;, and the memory a search needs follows how much of the index it reads rather than the size of the table, so a table far too large for &#x60;hnsw&#x60; can still be searched. It keeps a copy of the table&#39;s rows beside the vectors so a search is answered without reading the table; that copy is extra storage, and how much depends on &#x60;vector_precision&#x60;, which decides how compactly the copied vectors are held. Available for columns that already hold vectors, with the &#x60;l2&#x60; and &#x60;cosine&#x60; metrics. | [optional]
9+
**algorithm** | **str** | How a vector index organises the vectors it searches. Omit this field for &#x60;hnsw&#x60;, which is the default. &#x60;hnsw&#x60; — builds a graph of the vectors and keeps it in memory. Searches are very fast, and the memory a search needs grows with the whole table, so a large enough table cannot be served at all. &#x60;ivf&#x60; — groups the vectors into clusters and reads only the clusters nearest the search. Searches are considerably slower than &#x60;hnsw&#x60;, and the memory a search needs follows how much of the index it reads rather than the size of the table, so a table far too large for &#x60;hnsw&#x60; can still be searched. It keeps a copy of the table&#39;s rows beside the vectors so a search is answered without reading the table; that copy is extra storage, and how much depends on &#x60;vector_precision&#x60;, which decides how compactly the copied vectors are held. Available with the &#x60;l2&#x60; and &#x60;cosine&#x60; metrics, for columns that already hold vectors and for text columns with an embedding provider, which generates the vectors. Search an &#x60;ivf&#x60; index with &#x60;ORDER BY vector_distance(column, &#39;&lt;query&gt;&#39;) LIMIT &lt;k&gt;&#x60; (or the matching distance function and a query vector), naming the columns to return rather than selecting &#x60;*&#x60;; &#x60;vector_search&#x60; works with &#x60;hnsw&#x60; indexes only. Generating embeddings for a very large table (tens of millions of rows) is not supported yet. | [optional]
1010
**var_async** | **bool** | When true, create the index as a background job and return a job ID for polling. | [optional] [default to False]
1111
**async_after_ms** | **int** | If set (requires &#x60;async&#x60; &#x3D; true), wait up to this many milliseconds for the index build to finish: if it completes in time the index is returned (201), otherwise a 202 with a job ID to poll. Must be between 1000 and the server maximum; a value out of that range, or set without &#x60;async&#x60; &#x3D; true, is rejected with 400. | [optional]
1212
**columns** | **List[str]** | Columns to index. Required for all index types. |
1313
**description** | **str** | User-facing description of the embedding (e.g., \&quot;product descriptions\&quot;). | [optional]
1414
**dimensions** | **int** | Output vector dimensions. Some models support multiple dimension sizes (e.g., OpenAI text-embedding-3-small supports 512 or 1536). If omitted, the model&#39;s default dimensions are used | [optional]
15-
**embedding_provider_id** | **str** | Embedding provider ID. When set for a vector index, the source column is treated as text and embeddings are generated automatically. The vector index is then built on the generated embedding column (&#x60;{column}_embedding&#x60; by default). | [optional]
15+
**embedding_provider_id** | **str** | Embedding provider ID. When set for a vector index, the source column is treated as text and embeddings are generated automatically. The vector index is then built on the generated embedding column (&#x60;{column}_embedding&#x60; by default), with either algorithm. For an &#x60;ivf&#x60; index the metric must be &#x60;l2&#x60; or &#x60;cosine&#x60;: a provider whose metric is &#x60;dot&#x60; is refused unless &#x60;metric&#x60; names one of those. Every later load and text search embeds with the same provider, and is refused if the provider&#39;s model or endpoint has changed since the index was created; changing its API key is fine. Dropping the index keeps the generated column as an ordinary column, which loads may leave out (its rows get no vector) and which a new embedding index can generate into again. | [optional]
1616
**index_name** | **str** | |
1717
**index_type** | **str** | Index type. &#x60;sorted&#x60; supports range queries, &#x60;bm25&#x60; full-text search, and &#x60;vector&#x60; similarity search. | [optional] [default to 'sorted']
1818
**metric** | **str** | Distance metric for vector indexes: \&quot;l2\&quot;, \&quot;cosine\&quot;, or \&quot;dot\&quot;. When omitted, defaults to \&quot;l2\&quot; for float array columns or the provider&#39;s preferred metric for text columns with auto-embedding. | [optional]

‎docs/IndexesApi.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ Name | Type | Description | Notes
111111
112112
Delete an index
113113

114-
Delete a specific index from a table.
114+
Delete a specific index from a table. An index cannot be deleted while a load into its table is running; that request gets 409 and can be retried once the load finishes. Deleting a vector index that generated its own embeddings keeps the generated column as an ordinary column of the table.
115115

116116
### Example
117117

@@ -192,6 +192,7 @@ void (empty response body)
192192
|-------------|-------------|------------------|
193193
**204** | Index deleted | - |
194194
**404** | Index not found | - |
195+
**409** | A load into the table is running; retry once it finishes | - |
195196
**500** | Internal server error | - |
196197

197198
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)

‎hotdata/api/indexes_api.py‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ def delete_index(
396396
) -> None:
397397
"""Delete an index
398398
399-
Delete a specific index from a table.
399+
Delete a specific index from a table. An index cannot be deleted while a load into its table is running; that request gets 409 and can be retried once the load finishes. Deleting a vector index that generated its own embeddings keeps the generated column as an ordinary column of the table.
400400
401401
:param connection_id: The database's `default_connection_id`, as returned when the database is created or fetched (required)
402402
:type connection_id: str
@@ -442,6 +442,7 @@ def delete_index(
442442
_response_types_map: Dict[str, Optional[str]] = {
443443
'204': None,
444444
'404': "ApiErrorResponse",
445+
'409': "ApiErrorResponse",
445446
'500': "ApiErrorResponse",
446447
}
447448
response_data = self.api_client.call_api(
@@ -477,7 +478,7 @@ def delete_index_with_http_info(
477478
) -> ApiResponse[None]:
478479
"""Delete an index
479480
480-
Delete a specific index from a table.
481+
Delete a specific index from a table. An index cannot be deleted while a load into its table is running; that request gets 409 and can be retried once the load finishes. Deleting a vector index that generated its own embeddings keeps the generated column as an ordinary column of the table.
481482
482483
:param connection_id: The database's `default_connection_id`, as returned when the database is created or fetched (required)
483484
:type connection_id: str
@@ -523,6 +524,7 @@ def delete_index_with_http_info(
523524
_response_types_map: Dict[str, Optional[str]] = {
524525
'204': None,
525526
'404': "ApiErrorResponse",
527+
'409': "ApiErrorResponse",
526528
'500': "ApiErrorResponse",
527529
}
528530
response_data = self.api_client.call_api(
@@ -558,7 +560,7 @@ def delete_index_without_preload_content(
558560
) -> RESTResponseType:
559561
"""Delete an index
560562
561-
Delete a specific index from a table.
563+
Delete a specific index from a table. An index cannot be deleted while a load into its table is running; that request gets 409 and can be retried once the load finishes. Deleting a vector index that generated its own embeddings keeps the generated column as an ordinary column of the table.
562564
563565
:param connection_id: The database's `default_connection_id`, as returned when the database is created or fetched (required)
564566
:type connection_id: str
@@ -604,6 +606,7 @@ def delete_index_without_preload_content(
604606
_response_types_map: Dict[str, Optional[str]] = {
605607
'204': None,
606608
'404': "ApiErrorResponse",
609+
'409': "ApiErrorResponse",
607610
'500': "ApiErrorResponse",
608611
}
609612
response_data = self.api_client.call_api(

‎hotdata/models/create_index_request.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@
2626

2727
class CreateIndexRequest(BaseModel):
2828
"""
29-
Request body for POST .../indexes Two constraints span the whole table rather than this request alone. A table holds at most one vector index, whichever algorithm it uses. A second vector index is refused until the first is dropped. And a vector index that generates its own embeddings — that is, one created with `embedding_provider_id` — has to be the only index on its table. So a table that already carries any index (sorted, full-text, or vector) will not accept an embedding-backed vector index, and a table that already carries an embedding-backed vector index will not accept any further index of any type. To move between the two arrangements, drop what is there first. Plan for it when designing a table: combining full-text search with generated embeddings on one table is not possible, so use a separate table for the second index, or supply the embeddings yourself. A vector index over a column that already holds vectors — no `embedding_provider_id` — is not affected by that second rule and coexists with sorted and full-text indexes normally. Embedding generation also rewrites the table to add its generated column, and that rewrite cannot preserve a declared partition. An embedding-backed vector index is therefore refused on a partitioned table. No index of any type can be created on a table that declares a sort order (`sorted_by`). Such a request is refused with 400.
29+
Request body for POST .../indexes Two constraints span the whole table rather than this request alone. A table holds at most one vector index, whichever algorithm it uses. A second vector index is refused until the first is dropped. And a vector index that generates its own embeddings — that is, one created with `embedding_provider_id` — has to be the only index on its table. So a table that already carries any index (sorted, full-text, or vector) will not accept an embedding-backed vector index, and a table that already carries an embedding-backed vector index will not accept any further index of any type. This applies to both vector algorithms. To move between the two arrangements, drop what is there first. Plan for it when designing a table: combining full-text search with generated embeddings on one table is not possible, so use a separate table for the second index, or supply the embeddings yourself. A vector index over a column that already holds vectors — no `embedding_provider_id` — is not affected by that second rule and coexists with sorted and full-text indexes normally. Embedding generation also rewrites the table to add its generated column, and that rewrite cannot preserve a declared partition. An embedding-backed vector index is therefore refused on a partitioned table. No index of any type can be created on a table that declares a sort order (`sorted_by`). Such a request is refused with 400.
3030
""" # noqa: E501
31-
algorithm: Optional[StrictStr] = Field(default=None, description="How a vector index organises the vectors it searches. Omit this field for `hnsw`, which is the default. `hnsw` — builds a graph of the vectors and keeps it in memory. Searches are very fast, and the memory a search needs grows with the whole table, so a large enough table cannot be served at all. `ivf` — groups the vectors into clusters and reads only the clusters nearest the search. Searches are considerably slower than `hnsw`, and the memory a search needs follows how much of the index it reads rather than the size of the table, so a table far too large for `hnsw` can still be searched. It keeps a copy of the table's rows beside the vectors so a search is answered without reading the table; that copy is extra storage, and how much depends on `vector_precision`, which decides how compactly the copied vectors are held. Available for columns that already hold vectors, with the `l2` and `cosine` metrics.")
31+
algorithm: Optional[StrictStr] = Field(default=None, description="How a vector index organises the vectors it searches. Omit this field for `hnsw`, which is the default. `hnsw` — builds a graph of the vectors and keeps it in memory. Searches are very fast, and the memory a search needs grows with the whole table, so a large enough table cannot be served at all. `ivf` — groups the vectors into clusters and reads only the clusters nearest the search. Searches are considerably slower than `hnsw`, and the memory a search needs follows how much of the index it reads rather than the size of the table, so a table far too large for `hnsw` can still be searched. It keeps a copy of the table's rows beside the vectors so a search is answered without reading the table; that copy is extra storage, and how much depends on `vector_precision`, which decides how compactly the copied vectors are held. Available with the `l2` and `cosine` metrics, for columns that already hold vectors and for text columns with an embedding provider, which generates the vectors. Search an `ivf` index with `ORDER BY vector_distance(column, '<query>') LIMIT <k>` (or the matching distance function and a query vector), naming the columns to return rather than selecting `*`; `vector_search` works with `hnsw` indexes only. Generating embeddings for a very large table (tens of millions of rows) is not supported yet.")
3232
var_async: Optional[StrictBool] = Field(default=False, description="When true, create the index as a background job and return a job ID for polling.", alias="async")
3333
async_after_ms: Optional[Annotated[int, Field(strict=True, ge=1000)]] = Field(default=None, description="If set (requires `async` = true), wait up to this many milliseconds for the index build to finish: if it completes in time the index is returned (201), otherwise a 202 with a job ID to poll. Must be between 1000 and the server maximum; a value out of that range, or set without `async` = true, is rejected with 400.")
3434
columns: List[StrictStr] = Field(description="Columns to index. Required for all index types.")
3535
description: Optional[StrictStr] = Field(default=None, description="User-facing description of the embedding (e.g., \"product descriptions\").")
3636
dimensions: Optional[Annotated[int, Field(strict=True, ge=0)]] = Field(default=None, description="Output vector dimensions. Some models support multiple dimension sizes (e.g., OpenAI text-embedding-3-small supports 512 or 1536). If omitted, the model's default dimensions are used")
37-
embedding_provider_id: Optional[StrictStr] = Field(default=None, description="Embedding provider ID. When set for a vector index, the source column is treated as text and embeddings are generated automatically. The vector index is then built on the generated embedding column (`{column}_embedding` by default).")
37+
embedding_provider_id: Optional[StrictStr] = Field(default=None, description="Embedding provider ID. When set for a vector index, the source column is treated as text and embeddings are generated automatically. The vector index is then built on the generated embedding column (`{column}_embedding` by default), with either algorithm. For an `ivf` index the metric must be `l2` or `cosine`: a provider whose metric is `dot` is refused unless `metric` names one of those. Every later load and text search embeds with the same provider, and is refused if the provider's model or endpoint has changed since the index was created; changing its API key is fine. Dropping the index keeps the generated column as an ordinary column, which loads may leave out (its rows get no vector) and which a new embedding index can generate into again.")
3838
index_name: StrictStr
3939
index_type: Optional[StrictStr] = Field(default='sorted', description="Index type. `sorted` supports range queries, `bm25` full-text search, and `vector` similarity search.")
4040
metric: Optional[StrictStr] = Field(default=None, description="Distance metric for vector indexes: \"l2\", \"cosine\", or \"dot\". When omitted, defaults to \"l2\" for float array columns or the provider's preferred metric for text columns with auto-embedding.")

0 commit comments

Comments
 (0)