{
  "schema_version": 2,
  "id": "develop/ai/redisvl/api/searchindex",
  "title": "Search Index Classes",
  "url": "https://redis.io/docs/latest/develop/ai/redisvl/0.30.0/api/searchindex/",
  "summary": "",
  "content": "\n\n| Class                                     | Description                                                                                                      |\n|-------------------------------------------|------------------------------------------------------------------------------------------------------------------|\n| [SearchIndex](#searchindex-api)           | Primary class to write, read, and search across data structures in Redis.                                        |\n| [AsyncSearchIndex](#asyncsearchindex-api) | Async version of the SearchIndex to write, read, and search across data structures in Redis.                     |\n| [SearchResults](#searchresults-api)       | List of result documents returned by a query, which also reports result completeness.                            |\n| [BulkResult](#bulkresult-api)             | Outcome of a bulk operation: how many documents matched, how many were processed, and whether the run completed. |\n\n\u003ca id=\"searchindex-api\"\u003e\u003c/a\u003e\n\n## SearchIndex\n\n### `class SearchIndex(schema, redis_client=None, redis_url=None, connection_kwargs=None, validate_on_load=False, owns_client=None, **kwargs)`\n\nA search index class for interacting with Redis as a vector database.\n\nThe SearchIndex is instantiated with a reference to a Redis database and an\nIndexSchema (YAML path or dictionary object) that describes the various\nsettings and field configurations.\n\n```python\nfrom redisvl.index import SearchIndex\n\n# initialize the index object with schema from file\nindex = SearchIndex.from_yaml(\n    \"schemas/schema.yaml\",\n    redis_url=\"redis://localhost:6379\",\n    validate_on_load=True\n)\n\n# create the index\nindex.create(overwrite=True, drop=False)\n\n# data is an iterable of dictionaries\nindex.load(data)\n\n# delete index and data\nindex.delete(drop=True)\n```\n\nPass `redis_client` to use a client you configured yourself. The index\nleaves such a client open when it is disconnected or garbage collected;\npass `owns_client=True` to hand that responsibility over.\n\n```python\nfrom redis import Redis\nfrom redisvl.index import SearchIndex\n\nclient = Redis.from_url(\"redis://localhost:6379\", socket_timeout=5)\nindex = SearchIndex.from_yaml(\"schemas/schema.yaml\", redis_client=client)\n```\n\nInitialize the RedisVL search index with a schema and either a Redis\nclient or a URL string with other connection kwargs.\n\n* **Parameters:**\n  * **schema** ([*IndexSchema*](https://redis.io/docs/latest/schema/#indexschema)) – Index schema object.\n  * **redis_client** (*Optional* *[* *Redis* *]*) – An\n    instantiated redis client.\n  * **redis_url** (*Optional* *[* *str* *]*) – The URL of the Redis server to\n    connect to.\n  * **connection_kwargs** (*Dict* *[* *str* *,* *Any* *]* *,* *optional*) – Redis client connection\n    args.\n  * **validate_on_load** (*bool* *,* *optional*) – Whether to validate data against schema\n    when loading. Defaults to False.\n  * **owns_client** (*Optional* *[* *bool* *]* *,* *optional*) – Whether the index closes\n    the Redis client when the index is disconnected or garbage\n    collected. Defaults to None, meaning the index owns a client\n    only if it created one itself. Pass True to hand over a client\n    you created, or False to keep one the index would otherwise\n    close, in which case closing it becomes your responsibility.\n* **Raises:**\n  * **ValueError** – If `schema` is not an IndexSchema.\n  * **TypeError** – If `redis_client` is the wrong flavour, or if a\n        keyword that no longer exists is passed. `connection_args`\n        and `redis_kwargs` are both now `connection_kwargs`.\n\n#### `aggregate(*args, **kwargs)`\n\nPerform an aggregation operation against the index.\n\nWrapper around the aggregation API that adds the index name\nto the query and passes along the rest of the arguments\nto the redis-py ft().aggregate() method.\n\n* **Returns:**\n  Raw Redis aggregation results.\n* **Return type:**\n  Result\n\n#### `batch_query(queries, batch_size=10)`\n\nExecute a batch of queries and process results.\n\n* **Parameters:**\n  * **queries** (*Sequence* *[* *BaseQuery* *]*)\n  * **batch_size** (*int*)\n* **Return type:**\n  list[list[dict[str, *Any*]]]\n\n#### `batch_search(queries, batch_size=10)`\n\nPerform a search against the index for multiple queries.\n\nThis method takes a list of queries and optionally query params and\nreturns a list of Result objects for each query. Results are\nreturned in the same order as the queries.\n\nNOTE: Cluster users may need to incorporate hash tags into their query\nto avoid cross-slot operations.\n\n* **Parameters:**\n  * **queries** (*List* *[* *SearchParams* *]*) – The queries to search for.\n  * **batch_size** (*int* *,* *optional*) – The number of queries to search for at a time.\n    Defaults to 10.\n* **Returns:**\n  The search results for each query.\n* **Return type:**\n  List[Result]\n\n#### `clear()`\n\nClear all keys in Redis associated with the index, leaving the index\navailable and in-place for future insertions or updates.\n\nNOTE: This method requires custom behavior for Redis Cluster because\nhere, we can’t easily give control of the keys we’re clearing to the\nuser so they can separate them based on hash tag.\n\n\u003e [!NOTE]\n\u003e The sweep enumerates through the index, so it removes only what the\n\u003e index currently returns, and it can stop early – against an index\n\u003e still being backfilled, or when a page’s keys cannot be deleted. The\n\u003e returned count is the only signal, and `0` does not distinguish an\n\u003e empty index from a sweep that deleted nothing. Re-running is safe.\n\n* **Returns:**\n  Count of records deleted from Redis.\n* **Return type:**\n  int\n\n#### `create(overwrite=False, drop=False)`\n\nCreate an index in Redis with the current schema and properties.\n\n* **Parameters:**\n  * **overwrite** (*bool* *,* *optional*) – Whether to overwrite the index if it\n    already exists. Defaults to False.\n  * **drop** (*bool* *,* *optional*) – Whether to drop all keys associated with the\n    index in the case of overwriting. Defaults to False.\n* **Raises:**\n  * **RuntimeError** – If the index already exists and ‘overwrite’ is False.\n  * **ValueError** – If no fields are defined for the index.\n* **Return type:**\n  None\n\n```python\n# create an index in Redis; only if one does not exist with given name\nindex.create()\n\n# overwrite an index in Redis without dropping associated data\nindex.create(overwrite=True)\n\n# overwrite an index in Redis; drop associated data (clean slate)\nindex.create(overwrite=True, drop=True)\n```\n\n#### `delete(drop=True)`\n\nDelete the search index while optionally dropping all keys associated\nwith the index.\n\n`FT.DROPINDEX` resolves index aliases, so if this schema’s name happens\nto be an alias, the index the alias points at is dropped instead – along\nwith its documents when `drop` is True. [exists](#exists) reports an alias\nas absent to keep [create](#create) away from this path.\n\n* **Parameters:**\n  **drop** (*bool* *,* *optional*) – Delete the key / documents pairs in the\n  index. Defaults to True.\n* **Raises:**\n  [**RedisSearchError**](https://redis.io/docs/latest/exceptions/#redissearcherror) – If the index cannot be deleted, for example when it\n      does not exist. The original `redis-py` exception is available\n      as `__cause__`.\n\n#### `disconnect()`\n\nClose the Redis client if this index owns it.\n\nAlways invalidates the cached SQL schema. When the index does not own\nthe client (see `owns_client`), the client is left open and the\nindex remains usable.\n\n#### `drop_by_filter(filter_expression, *, batch_size=500, dry_run=False, allow_all=False, on_progress=None)`\n\nDelete every document matching a filter expression.\n\nRedis has no server-side \"delete by query\", so RedisVL resolves the\nmatching document keys and removes them with non-blocking `UNLINK` in\nbatches. Matching documents leave the result set as they are deleted, so\nthis re-queries from offset 0 each round (the same strategy as\n[clear](#clear)) and is not subject to the `MAXSEARCHRESULTS` limit\n(provided `batch_size` itself does not exceed it).\n\n* **Parameters:**\n  * **filter_expression** (*Union* *[* *str* *,* [*FilterExpression*](https://redis.io/docs/latest/filter/#filterexpression) *]*) – Selects the\n    documents to delete. Prefer the escaping builders (`Tag`,\n    `Num`, `Text`…) over raw filter strings; **never\n    string-concatenate untrusted input into a filter** — unlike a\n    read query, an injected predicate here deletes data.\n  * **batch_size** (*int*) – Number of documents to resolve and unlink per\n    round-trip. Defaults to 500.\n  * **dry_run** (*bool*) – If True, report how many documents *would* be\n    deleted (via a count query) without deleting anything.\n  * **allow_all** (*bool*) – Must be True to run against a match-all filter\n    (empty/`\"*\"`/`None`). Prefer [clear](#clear) to intentionally\n    empty the index.\n  * **on_progress** (*Optional* *[* *Callable* *[* *[* *int* *,* *int* *]* *,* *None* *]* *]*) – Called after each\n    batch with `(processed, matched)` — the cumulative documents\n    deleted and the total matched at the start. Invoked\n    synchronously (do not pass a coroutine); raising from it aborts\n    the run (already-deleted documents stay deleted).\n* **Returns:**\n  `matched`/`processed` counts, plus `completed`\n  (False if the runaway backstop tripped, or if the run stopped on a\n  batch it could not remove) and `dry_run`. Read the fields\n  explicitly (`result.processed`, `result.completed`).\n* **Return type:**\n  [BulkResult](#bulkresult)\n\n\u003e [!NOTE]\n\u003e [update_by_filter](#update_by_filter) (bulk partial update), [drop_documents](#drop_documents)\n\u003e / [drop_keys](#drop_keys) (delete by id/key), [clear](#clear) (delete all).\n\n\u003e [!NOTE]\n\u003e This operation is **not atomic** across the match set. Each key is\n\u003e unlinked atomically, but batches are applied incrementally with no\n\u003e rollback, so a crash or connection error mid-run leaves the already\n\u003e -deleted documents gone and the rest in place. Deletes are\n\u003e idempotent: re-running the same call after a failure removes only\n\u003e whatever still matches, converging on the intended state.\n\n#### `drop_documents(ids, batch_size=500)`\n\nRemove documents from the index by their document IDs.\n\nThis method converts document IDs to Redis keys automatically by applying\nthe index’s key prefix and separator configuration.\n\nNOTE: Cluster users will need to incorporate hash tags into their\ndocument IDs and only call this method with documents from a single hash\ntag at a time.\n\n* **Parameters:**\n  * **ids** (*Union* *[* *str* *,* *List* *[* *str* *]* *]*) – The document ID or IDs to remove from the index.\n  * **batch_size** (*int*) – Number of documents to delete per round-trip\n    (standalone Redis only; cluster deletes in a single call after\n    the shared-hash-tag check).\n* **Returns:**\n  Count of documents deleted from Redis.\n* **Return type:**\n  int\n* **Raises:**\n  **ValueError** – On Redis Cluster, if the resolved keys do not all share\n      a hash tag (a cross-slot `DELETE` is not permitted).\n\n#### `drop_keys(keys, batch_size=500)`\n\nRemove a specific entry or entries from the index by it’s key ID.\n\nUses `UNLINK` rather than `DEL` so memory reclamation runs on a\nbackground thread. This avoids blocking the main thread when a large\nnumber of keys are dropped at once (for example, scope-targeted\n`SemanticCache` invalidation). The returned count is unchanged.\n\nLarge key lists are unlinked in chunks of `batch_size`. On Redis\nCluster, keys are unlinked individually so a chunk that spans hash\nslots does not raise `CROSSSLOT`.\n\n* **Parameters:**\n  * **keys** (*Union* *[* *str* *,* *List* *[* *str* *]* *]*) – The document ID or IDs to remove from the index.\n  * **batch_size** (*int*) – Number of keys to unlink per round-trip.\n* **Returns:**\n  Count of records deleted from Redis.\n* **Return type:**\n  int\n\n\u003e [!NOTE]\n\u003e The returned count can be lower than the number of keys requested,\n\u003e either because a key did not exist or, on Redis Cluster, because its\n\u003e `UNLINK` failed — a per-key cluster failure is logged rather than\n\u003e raised so one bad key does not abort the batch, and the count alone\n\u003e cannot tell the two cases apart. A single key passed as a `str` is\n\u003e not batched, so a failure there does propagate to the caller.\n\n#### `exists()`\n\nCheck if the index exists in Redis.\n\n* **Returns:**\n  True if the index exists, False otherwise.\n* **Return type:**\n  bool\n* **Raises:**\n  [**RedisSearchError**](https://redis.io/docs/latest/exceptions/#redissearcherror) – If the check fails for any reason other than the\n      index not existing, such as a connection failure or insufficient\n      permissions. The original `redis-py` exception is available as\n      `__cause__`.\n\n#### `expire_keys(keys, ttl)`\n\nSet the expiration time for a specific entry or entries in Redis.\n\n* **Parameters:**\n  * **keys** (*Union* *[* *str* *,* *List* *[* *str* *]* *]*) – The entry ID or IDs to set the expiration for.\n  * **ttl** (*int*) – The time-to-live in seconds.\n* **Return type:**\n  int | list[int]\n\n#### `fetch(id)`\n\nFetch an object from Redis by id.\n\nThe id is typically either a unique identifier,\nor derived from some domain-specific metadata combination\n(like a document id or chunk id).\n\n* **Parameters:**\n  **id** (*str*) – The specified unique identifier for a particular\n  document indexed in Redis.\n* **Returns:**\n  The fetched object.\n* **Return type:**\n  Dict[str, Any]\n\n#### `classmethod from_dict(schema_dict, **kwargs)`\n\nCreate a SearchIndex from a dictionary.\n\n* **Parameters:**\n  **schema_dict** (*Dict* *[* *str* *,* *Any* *]*) – A dictionary containing the schema.\n* **Returns:**\n  A RedisVL SearchIndex object.\n* **Return type:**\n  [SearchIndex](#searchindex)\n\n```python\nfrom redisvl.index import SearchIndex\n\nindex = SearchIndex.from_dict({\n    \"index\": {\n        \"name\": \"my-index\",\n        \"prefix\": \"rvl\",\n        \"storage_type\": \"hash\",\n    },\n    \"fields\": [\n        {\"name\": \"doc-id\", \"type\": \"tag\"}\n    ]\n}, redis_url=\"redis://localhost:6379\")\n```\n\n#### `classmethod from_existing(name, redis_client=None, redis_url=None, **kwargs)`\n\nInitialize from an existing search index in Redis by index name.\n\n* **Parameters:**\n  * **name** (*str*) – Name of the search index in Redis.\n  * **redis_client** (*Optional* *[* *Redis* *]*) – An\n    instantiated redis client.\n  * **redis_url** (*Optional* *[* *str* *]*) – The URL of the Redis server to\n    connect to.\n  * **owns_client** (*Optional* *[* *bool* *]* *,* *optional*) – Whether the index closes\n    the client. Defaults to True when this method created the\n    client from redis_url, and False when you supplied one.\n  * **connection_kwargs** (*Optional* *[* *Dict* *[* *str* *,* *Any* *]* *]*) – Redis client\n    connection args, used only when this method creates the\n    client from `redis_url`. Other keyword arguments are\n    treated the same way.\n* **Raises:**\n  **ValueError** – If redis_url or redis_client is not provided.\n\n#### `classmethod from_yaml(schema_path, **kwargs)`\n\nCreate a SearchIndex from a YAML schema file.\n\n* **Parameters:**\n  **schema_path** (*str*) – Path to the YAML schema file.\n* **Returns:**\n  A RedisVL SearchIndex object.\n* **Return type:**\n  [SearchIndex](#searchindex)\n\n```python\nfrom redisvl.index import SearchIndex\n\nindex = SearchIndex.from_yaml(\"schemas/schema.yaml\", redis_url=\"redis://localhost:6379\")\n```\n\n#### `info(name=None)`\n\nGet information about the index.\n\n* **Parameters:**\n  **name** (*str* *,* *optional*) – Index name to fetch info about.\n  Defaults to None.\n* **Returns:**\n  A dictionary containing the information about the index.\n* **Return type:**\n  dict\n\n#### `invalidate_sql_schema_cache()`\n\nClear cached sql-redis executors and schema state for this index.\n\n* **Return type:**\n  None\n\n#### `key(id)`\n\nConstruct a redis key as a combination of an index key prefix (optional)\nand specified id.\n\nThe id is typically either a unique identifier, or\nderived from some domain-specific metadata combination (like a document\nid or chunk id).\n\n* **Parameters:**\n  **id** (*str*) – The specified unique identifier for a particular\n  document indexed in Redis.\n* **Returns:**\n  The full Redis key including key prefix and value as a string.\n* **Return type:**\n  str\n\n#### `listall()`\n\nList all search indices in Redis database.\n\n* **Returns:**\n  The list of indices in the database.\n* **Return type:**\n  List[str]\n\n#### `load(data, id_field=None, keys=None, ttl=None, preprocess=None, batch_size=None)`\n\nLoad objects to the Redis database. Returns the list of keys loaded\nto Redis.\n\nRedisVL automatically handles constructing the object keys, batching,\noptional preprocessing steps, and setting optional expiration\n(TTL policies) on keys.\n\n* **Parameters:**\n  * **data** (*Iterable* *[* *Any* *]*) – An iterable of objects to store.\n  * **id_field** (*Optional* *[* *str* *]* *,* *optional*) – Specified field used as the id\n    portion of the redis key (after the prefix) for each\n    object. Defaults to None.\n  * **keys** (*Optional* *[* *Iterable* *[* *str* *]* *]* *,* *optional*) – Optional iterable of keys.\n    Must match the length of objects if provided. Defaults to None.\n  * **ttl** (*Optional* *[* *int* *]* *,* *optional*) – Time-to-live in seconds for each key.\n    Defaults to None.\n  * **preprocess** (*Optional* *[* *Callable* *]* *,* *optional*) – A function to preprocess\n    objects before storage. Defaults to None.\n  * **batch_size** (*Optional* *[* *int* *]* *,* *optional*) – Number of objects to write in\n    a single Redis pipeline execution. Defaults to class’s\n    default batch size.\n* **Returns:**\n  List of keys loaded to Redis.\n* **Return type:**\n  List[str]\n* **Raises:**\n  * [**SchemaValidationError**](https://redis.io/docs/latest/exceptions/#schemavalidationerror) – If validation fails when validate_on_load is enabled.\n  * [**RedisVLError**](https://redis.io/docs/latest/exceptions/#redisvlerror) – If there’s an error loading data to Redis.\n\n#### `paginate(query, page_size=30)`\n\nExecute a given query against the index and return results in\npaginated batches.\n\nThis method accepts a RedisVL query instance, enabling pagination of\nresults which allows for subsequent processing over each batch with a\ngenerator.\n\n* **Parameters:**\n  * **query** (*BaseQuery*) – The search query to be executed.\n  * **page_size** (*int* *,* *optional*) – The number of results to return in each\n    batch. Defaults to 30.\n* **Yields:**\n  A generator yielding non-empty `SearchResults` batches.\n* **Raises:**\n  * **TypeError** – If the page_size argument is not of type int.\n  * **ValueError** – If the page_size argument is less than or equal to zero.\n* **Return type:**\n  *Generator*\n\n```python\n# Iterate over paginated search results in batches of 10\nfor result_batch in index.paginate(query, page_size=10):\n    # Process each batch of results\n    pass\n```\n\n\u003e [!NOTE]\n\u003e The page_size parameter controls the number of items each result\n\u003e batch contains. Adjust this value based on performance\n\u003e considerations and the expected volume of search results.\n\n\u003e [!NOTE]\n\u003e For stable pagination, the query must have a sort_by clause on a\n\u003e **unique** field. Redis documents that `LIMIT` without sorting is\n\u003e non-deterministic, so pages may otherwise repeat or miss documents.\n\u003e Very deep pagination is also bounded server-side by\n\u003e `search-max-search-results` (1,000,000 by default, but 10,000 on\n\u003e some managed tiers), past which the search errors rather than ending.\n\n\u003e [!NOTE]\n\u003e A yielded batch may contain fewer than `page_size` documents, and an\n\u003e empty batch is never yielded. On Redis 8+ a matched document whose\n\u003e data expires while the search is running is skipped; each batch\n\u003e reports how many were skipped via `dropped_count` (and\n\u003e `complete`), including skips inherited from a page that was dropped\n\u003e in its entirety. Pagination still runs to the end of the result set in\n\u003e that case.\n\n#### `query(query)`\n\nExecute a query on the index.\n\nThis method takes a BaseQuery, AggregationQuery, or HybridQuery object directly, and\nhandles post-processing of the search.\n\n* **Parameters:**\n  **query** (*Union* *[* *BaseQuery* *,* *AggregationQuery* *,* [*HybridQuery*](https://redis.io/docs/latest/query/#hybridquery) *]*) – The query to run.\n* **Returns:**\n  A list of search results.\n* **Return type:**\n  List[Result]\n\n```python\nfrom redisvl.query import VectorQuery\n\nquery = VectorQuery(\n    vector=[0.16, -0.34, 0.98, 0.23],\n    vector_field_name=\"embedding\",\n    num_results=3\n)\n\nresults = index.query(query)\n```\n\n#### `search(*args, **kwargs)`\n\nPerform a search against the index.\n\nWrapper around the search API that adds the index name\nto the query and passes along the rest of the arguments\nto the redis-py ft().search() method.\n\n* **Returns:**\n  Raw Redis search results.\n* **Return type:**\n  Result\n\n#### `update_by_filter(filter_expression, values, *, batch_size=500, dry_run=False, allow_all=False, on_progress=None)`\n\nSet `values` on every document matching a filter expression.\n\nThis is a partial update: fields not present in `values` are left\nuntouched. For hash indexes the fields are written with `HSET`; for\nJSON indexes they are merged at the document root (`$`) with\n`JSON.MERGE` (RFC 7396), so nested objects merge recursively, arrays\nare replaced wholesale, and a `None` value deletes that path.\n\nThe read phase (an `FT.AGGREGATE` cursor) is drained in full *before*\nany write, holding every matching key in memory in between. Interleaving\nthe two does not terminate: RediSearch has no in-place update, so each\nwrite appends a new, higher document id ahead of the cursor, which hands\nthat document back to be written again. Memory use is therefore\nproportional to the match count; for very large match sets, narrow the\nfilter and run in partitions (see \"Bulk Delete and Update\" in the\nsearch-and-indexing concepts guide).\n\n* **Parameters:**\n  * **filter_expression** (*Union* *[* *str* *,* [*FilterExpression*](https://redis.io/docs/latest/filter/#filterexpression) *]*) – Selects the\n    documents to update. Prefer the escaping builders (`Tag`,\n    `Num`…) over raw filter strings; **never string-concatenate\n    untrusted input into a filter** — an injected predicate here\n    mutates data.\n  * **values** (*Dict* *[* *str* *,* *Any* *]*) – Field/value pairs to set on each match.\n    Values are written **as-is with no schema validation** (unlike\n    [load](#load)): callers must pre-encode vectors/bytes and format\n    numerics as the schema expects. For JSON indexes, keys must\n    match the document’s JSON **layout**, not the schema field name\n    — a field indexed at a nested path (e.g. `$.metadata.status`)\n    must be passed nested (`{\"metadata\": {\"status\": ...}}`);\n    passing the flat field name writes the wrong path and leaves the\n    indexed field unchanged. Only static values are supported (no\n    callable/expression transforms).\n  * **batch_size** (*int*) – Number of documents to update per round-trip.\n  * **dry_run** (*bool*) – If True, report how many documents *would* be\n    updated without writing anything.\n  * **allow_all** (*bool*) – Must be True to run against a match-all filter.\n  * **on_progress** (*Optional* *[* *Callable* *[* *[* *int* *,* *int* *]* *,* *None* *]* *]*) – Called after each\n    write batch with `(processed, matched)`. Invoked synchronously\n    (do not pass a coroutine); raising from it aborts the run.\n* **Returns:**\n  `matched`/`processed` counts, plus `completed`\n  (always True for update — it runs to completion or raises) and\n  `dry_run`.\n* **Return type:**\n  [BulkResult](#bulkresult)\n\n\u003e [!NOTE]\n\u003e [drop_by_filter](#drop_by_filter), [load](#load) (validated whole-document\n\u003e upsert by key).\n\n\u003e [!NOTE]\n\u003e This operation is **not atomic** across the match set. Each\n\u003e document is updated atomically (one `HSET`/`JSON.MERGE`), but\n\u003e batches use a non-transactional pipeline and are applied\n\u003e incrementally with no rollback, so a crash or connection error\n\u003e mid-run can leave some documents updated and others not. Because\n\u003e the update is a fixed field set, it is idempotent: re-running the\n\u003e same call after a failure converges on the intended state.\n\nKeys are resolved before writing, so a document may be deleted by\nanother client in between. Each write is conditional on the key\nstill existing (applied atomically), so such a document is\n**skipped rather than recreated** as a partial document; it simply\nisn’t counted in `processed`. `processed` therefore reflects the\ndocuments actually written, which can be less than `matched` under\nconcurrent deletion.\n\n`processed` counts **distinct** documents — a document is written\n*at most* once, even though the resolving cursor can emit it on more\nthan one page — but there is no *at least* once guarantee. It can\nexceed `matched` if the match set grew after the initial count, and\nfall short of it either from a concurrent deletion or because the\ncursor itself returned fewer rows than matched (which Redis\ndocuments as possible). A short `processed` is therefore not\nevidence of any particular cause.\n\n#### `property client: Redis | RedisCluster | None`\n\nThe underlying redis-py client object.\n\n#### `property key_separator: str`\n\nThe optional separator between a defined prefix and key value in\nforming a Redis key.\n\n#### `property name: str`\n\nThe name of the Redis search index.\n\n#### `property prefix: str`\n\nThe key prefix used in forming Redis keys.\n\nFor multi-prefix indexes, returns the first prefix.\n\n#### `property prefixes: list[str]`\n\nAll key prefixes configured for this index.\n\n#### `property storage_type: StorageType`\n\nThe underlying storage type for the search index; either\nhash or json.\n\n\u003ca id=\"asyncsearchindex-api\"\u003e\u003c/a\u003e\n\n## AsyncSearchIndex\n\n### `class AsyncSearchIndex(schema, *, redis_url=None, redis_client=None, connection_kwargs=None, validate_on_load=False, owns_client=None, **kwargs)`\n\nA search index class for interacting with Redis as a vector database in\nasync-mode.\n\nThe AsyncSearchIndex is instantiated with a reference to a Redis database\nand an IndexSchema (YAML path or dictionary object) that describes the\nvarious settings and field configurations.\n\n```python\nfrom redisvl.index import AsyncSearchIndex\n\n# initialize the index object with schema from file\nindex = AsyncSearchIndex.from_yaml(\n    \"schemas/schema.yaml\",\n    redis_url=\"redis://localhost:6379\",\n    validate_on_load=True\n)\n\n# create the index\nawait index.create(overwrite=True, drop=False)\n\n# data is an iterable of dictionaries\nawait index.load(data)\n\n# delete index and data\nawait index.delete(drop=True)\n```\n\nPass `redis_client` to use a client you configured yourself. The index\nleaves such a client open when it is disconnected or garbage collected;\npass `owns_client=True` to hand that responsibility over.\n\n```python\nfrom redis.asyncio import Redis\nfrom redisvl.index import AsyncSearchIndex\n\nclient = Redis.from_url(\"redis://localhost:6379\", socket_timeout=5)\nindex = AsyncSearchIndex.from_yaml(\"schemas/schema.yaml\", redis_client=client)\n```\n\nInitialize the RedisVL async search index with a schema.\n\n* **Parameters:**\n  * **schema** ([*IndexSchema*](https://redis.io/docs/latest/schema/#indexschema)) – Index schema object.\n  * **redis_url** (*Optional* *[* *str* *]* *,* *optional*) – The URL of the Redis server to\n    connect to.\n  * **redis_client** (*Optional* *[* *AsyncRedis* *]*) – An\n    instantiated redis client.\n  * **connection_kwargs** (*Optional* *[* *Dict* *[* *str* *,* *Any* *]* *]*) – Redis client connection\n    args.\n  * **validate_on_load** (*bool* *,* *optional*) – Whether to validate data against schema\n    when loading. Defaults to False.\n  * **owns_client** (*Optional* *[* *bool* *]* *,* *optional*) – Whether the index closes\n    the Redis client when the index is disconnected or garbage\n    collected. Defaults to None, meaning the index owns a client\n    only if it created one itself. Pass True to hand over a client\n    you created, or False to keep one the index would otherwise\n    close, in which case closing it becomes your responsibility.\n* **Raises:**\n  * **ValueError** – If `schema` is not an IndexSchema.\n  * **TypeError** – If `redis_client` is the wrong flavour, or if a\n        keyword that no longer exists is passed. `connection_args`\n        and `redis_kwargs` are both now `connection_kwargs`.\n\n#### `async aggregate(*args, **kwargs)`\n\nPerform an aggregation operation against the index.\n\nWrapper around the aggregation API that adds the index name\nto the query and passes along the rest of the arguments\nto the redis-py ft().aggregate() method.\n\n* **Returns:**\n  Raw Redis aggregation results.\n* **Return type:**\n  Result\n\n#### `async batch_query(queries, batch_size=10)`\n\nAsynchronously execute a batch of queries and process results.\n\n* **Parameters:**\n  * **queries** (*list* *[* *BaseQuery* *]*)\n  * **batch_size** (*int*)\n* **Return type:**\n  list[list[dict[str, *Any*]]]\n\n#### `async batch_search(queries, batch_size=10)`\n\nAsynchronously execute a batch of search queries.\n\nThis method takes a list of search queries and executes them in batches\nto improve performance when dealing with multiple queries.\n\nNOTE: Cluster users may need to incorporate hash tags into their query\nto avoid cross-slot operations.\n\n* **Parameters:**\n  * **queries** (*List* *[* *SearchParams* *]*) – A list of search queries to execute.\n    Each query can be either a string or a tuple of (query, params).\n  * **batch_size** (*int* *,* *optional*) – The number of queries to execute in each\n    batch. Defaults to 10.\n* **Returns:**\n  A list of search results corresponding to each query.\n* **Return type:**\n  List[Result]\n\n```python\nqueries = [\n    \"hello world\",\n    (\"goodbye world\", {\"num_results\": 5}),\n]\n\nresults = await index.batch_search(queries)\n```\n\n#### `async clear()`\n\nClear all keys in Redis associated with the index, leaving the index\navailable and in-place for future insertions or updates.\n\nNOTE: This method requires custom behavior for Redis Cluster because here,\nwe can’t easily give control of the keys we’re clearing to the user so they\ncan separate them based on hash tag.\n\nSee [clear](#clear) for the sweep’s caveats, which apply\nidentically here.\n\n* **Returns:**\n  Count of records deleted from Redis.\n* **Return type:**\n  int\n\n#### `async create(overwrite=False, drop=False)`\n\nAsynchronously create an index in Redis with the current schema\n: and properties.\n\n* **Parameters:**\n  * **overwrite** (*bool* *,* *optional*) – Whether to overwrite the index if it\n    already exists. Defaults to False.\n  * **drop** (*bool* *,* *optional*) – Whether to drop all keys associated with the\n    index in the case of overwriting. Defaults to False.\n* **Raises:**\n  * **RuntimeError** – If the index already exists and ‘overwrite’ is False.\n  * **ValueError** – If no fields are defined for the index.\n* **Return type:**\n  None\n\n```python\n# create an index in Redis; only if one does not exist with given name\nawait index.create()\n\n# overwrite an index in Redis without dropping associated data\nawait index.create(overwrite=True)\n\n# overwrite an index in Redis; drop associated data (clean slate)\nawait index.create(overwrite=True, drop=True)\n```\n\n#### `async delete(drop=True)`\n\nDelete the search index.\n\n`FT.DROPINDEX` resolves index aliases, so if this schema’s name happens\nto be an alias, the index the alias points at is dropped instead – along\nwith its documents when `drop` is True. [exists](#exists) reports an alias\nas absent to keep [create](#create) away from this path.\n\n* **Parameters:**\n  **drop** (*bool* *,* *optional*) – Delete the documents in the index.\n  Defaults to True.\n* **Raises:**\n  [**RedisSearchError**](https://redis.io/docs/latest/exceptions/#redissearcherror) – If the index cannot be deleted, for example when it\n      does not exist. The original `redis-py` exception is available\n      as `__cause__`.\n\n#### `async disconnect()`\n\nClose the Redis client if this index owns it.\n\nAlways invalidates the cached SQL schema. When the index does not own\nthe client (see `owns_client`), the client is left open and the\nindex remains usable.\n\n#### `disconnect_sync()`\n\nClose an owned Redis client from synchronous code.\n\nFor callers outside an event loop, such as `__del__` or a shutdown\nhook. Honours `owns_client` exactly as [disconnect](#disconnect) does.\n\nDoes nothing if a loop is already running, since it cannot await the\nclose from inside one. Use [disconnect](#disconnect) there.\n\n#### `async drop_by_filter(filter_expression, *, batch_size=500, dry_run=False, allow_all=False, on_progress=None)`\n\nDelete every document matching a filter expression (async).\n\nSee [drop_by_filter](#drop_by_filter) for full semantics.\n\n* **Parameters:**\n  * **filter_expression** (*str* *|* [*FilterExpression*](https://redis.io/docs/latest/filter/#filterexpression))\n  * **batch_size** (*int*)\n  * **dry_run** (*bool*)\n  * **allow_all** (*bool*)\n  * **on_progress** (*Callable* *[* *[* *int* *,* *int* *]* *,* *None* *]*  *|* *None*)\n* **Return type:**\n  [BulkResult](#bulkresult)\n\n#### `async drop_documents(ids, batch_size=500)`\n\nRemove documents from the index by their document IDs.\n\nThis method converts document IDs to Redis keys automatically by applying\nthe index’s key prefix and separator configuration.\n\nNOTE: Cluster users will need to incorporate hash tags into their\ndocument IDs and only call this method with documents from a single hash\ntag at a time.\n\n* **Parameters:**\n  * **ids** (*Union* *[* *str* *,* *List* *[* *str* *]* *]*) – The document ID or IDs to remove from the index.\n  * **batch_size** (*int*) – Number of documents to delete per round-trip\n    (standalone Redis only; cluster deletes in a single call after\n    the shared-hash-tag check).\n* **Returns:**\n  Count of documents deleted from Redis.\n* **Return type:**\n  int\n* **Raises:**\n  **ValueError** – On Redis Cluster, if the resolved keys do not all share\n      a hash tag (a cross-slot `DELETE` is not permitted).\n\n#### `async drop_keys(keys, batch_size=500)`\n\nRemove a specific entry or entries from the index by it’s key ID.\n\nUses `UNLINK` rather than `DEL` so memory reclamation runs on a\nbackground thread. This avoids blocking the main thread when a large\nnumber of keys are dropped at once (for example, scope-targeted\n`SemanticCache` invalidation). The returned count is unchanged.\n\nLarge key lists are unlinked in chunks of `batch_size`. On Redis\nCluster, keys are unlinked individually so a chunk that spans hash\nslots does not raise `CROSSSLOT`.\n\n* **Parameters:**\n  * **keys** (*Union* *[* *str* *,* *List* *[* *str* *]* *]*) – The document ID or IDs to remove from the index.\n  * **batch_size** (*int*) – Number of keys to unlink per round-trip.\n* **Returns:**\n  Count of records deleted from Redis.\n* **Return type:**\n  int\n\n\u003e [!NOTE]\n\u003e The returned count can be lower than the number of keys requested,\n\u003e either because a key did not exist or, on Redis Cluster, because its\n\u003e `UNLINK` failed — a per-key cluster failure is logged rather than\n\u003e raised so one bad key does not abort the batch, and the count alone\n\u003e cannot tell the two cases apart. A single key passed as a `str` is\n\u003e not batched, so a failure there does propagate to the caller.\n\n#### `async exists()`\n\nCheck if the index exists in Redis.\n\n* **Returns:**\n  True if the index exists, False otherwise.\n* **Return type:**\n  bool\n* **Raises:**\n  [**RedisSearchError**](https://redis.io/docs/latest/exceptions/#redissearcherror) – If the check fails for any reason other than the\n      index not existing, such as a connection failure or insufficient\n      permissions. The original `redis-py` exception is available as\n      `__cause__`.\n\n#### `async expire_keys(keys, ttl)`\n\nSet the expiration time for a specific entry or entries in Redis.\n\n* **Parameters:**\n  * **keys** (*Union* *[* *str* *,* *List* *[* *str* *]* *]*) – The entry ID or IDs to set the expiration for.\n  * **ttl** (*int*) – The time-to-live in seconds.\n* **Return type:**\n  int | list[int]\n\n#### `async fetch(id)`\n\nAsynchronously etch an object from Redis by id. The id is typically\neither a unique identifier, or derived from some domain-specific\nmetadata combination (like a document id or chunk id).\n\n* **Parameters:**\n  **id** (*str*) – The specified unique identifier for a particular\n  document indexed in Redis.\n* **Returns:**\n  The fetched object.\n* **Return type:**\n  Dict[str, Any]\n\n#### `classmethod from_dict(schema_dict, **kwargs)`\n\nCreate a SearchIndex from a dictionary.\n\n* **Parameters:**\n  **schema_dict** (*Dict* *[* *str* *,* *Any* *]*) – A dictionary containing the schema.\n* **Returns:**\n  A RedisVL SearchIndex object.\n* **Return type:**\n  [SearchIndex](#searchindex)\n\n```python\nfrom redisvl.index import SearchIndex\n\nindex = SearchIndex.from_dict({\n    \"index\": {\n        \"name\": \"my-index\",\n        \"prefix\": \"rvl\",\n        \"storage_type\": \"hash\",\n    },\n    \"fields\": [\n        {\"name\": \"doc-id\", \"type\": \"tag\"}\n    ]\n}, redis_url=\"redis://localhost:6379\")\n```\n\n#### `async classmethod* from_existing(name, redis_client=None, redis_url=None, **kwargs)`\n\nInitialize from an existing search index in Redis by index name.\n\n* **Parameters:**\n  * **name** (*str*) – Name of the search index in Redis.\n  * **redis_client** (*Optional* *[* *Redis* *]*) – An\n    instantiated redis client.\n  * **redis_url** (*Optional* *[* *str* *]*) – The URL of the Redis server to\n    connect to.\n  * **owns_client** (*Optional* *[* *bool* *]* *,* *optional*) – Whether the index closes\n    the client. Defaults to True when this method created the\n    client from redis_url, and False when you supplied one.\n  * **connection_kwargs** (*Optional* *[* *Dict* *[* *str* *,* *Any* *]* *]*) – Redis client\n    connection args, used only when this method creates the\n    client from `redis_url`. Other keyword arguments are\n    treated the same way.\n\n#### `classmethod from_yaml(schema_path, **kwargs)`\n\nCreate a SearchIndex from a YAML schema file.\n\n* **Parameters:**\n  **schema_path** (*str*) – Path to the YAML schema file.\n* **Returns:**\n  A RedisVL SearchIndex object.\n* **Return type:**\n  [SearchIndex](#searchindex)\n\n```python\nfrom redisvl.index import SearchIndex\n\nindex = SearchIndex.from_yaml(\"schemas/schema.yaml\", redis_url=\"redis://localhost:6379\")\n```\n\n#### `async info(name=None)`\n\nGet information about the index.\n\n* **Parameters:**\n  **name** (*str* *,* *optional*) – Index name to fetch info about.\n  Defaults to None.\n* **Returns:**\n  A dictionary containing the information about the index.\n* **Return type:**\n  dict\n\n#### `invalidate_sql_schema_cache()`\n\nClear cached sql-redis executors and schema state for this index.\n\n* **Return type:**\n  None\n\n#### `key(id)`\n\nConstruct a redis key as a combination of an index key prefix (optional)\nand specified id.\n\nThe id is typically either a unique identifier, or\nderived from some domain-specific metadata combination (like a document\nid or chunk id).\n\n* **Parameters:**\n  **id** (*str*) – The specified unique identifier for a particular\n  document indexed in Redis.\n* **Returns:**\n  The full Redis key including key prefix and value as a string.\n* **Return type:**\n  str\n\n#### `async listall()`\n\nList all search indices in Redis database.\n\n* **Returns:**\n  The list of indices in the database.\n* **Return type:**\n  List[str]\n\n#### `load(data, id_field=None, keys=None, ttl=None, preprocess=None, concurrency=None, batch_size=None)`\n\nAsynchronously load objects to Redis. Returns the list of keys loaded\nto Redis.\n\nRedisVL automatically handles constructing the object keys, batching,\noptional preprocessing steps, and setting optional expiration\n(TTL policies) on keys.\n\n* **Parameters:**\n  * **data** (*Iterable* *[* *Any* *]*) – An iterable of objects to store.\n  * **id_field** (*Optional* *[* *str* *]* *,* *optional*) – Specified field used as the id\n    portion of the redis key (after the prefix) for each\n    object. Defaults to None.\n  * **keys** (*Optional* *[* *Iterable* *[* *str* *]* *]* *,* *optional*) – Optional iterable of keys.\n    Must match the length of objects if provided. Defaults to None.\n  * **ttl** (*Optional* *[* *int* *]* *,* *optional*) – Time-to-live in seconds for each key.\n    Defaults to None.\n  * **preprocess** (*Optional* *[* *Callable* *]* *,* *optional*) – A function to\n    preprocess objects before storage. Defaults to None.\n  * **batch_size** (*Optional* *[* *int* *]* *,* *optional*) – Number of objects to write in\n    a single Redis pipeline execution. Defaults to class’s\n    default batch size.\n  * **concurrency** (*int* *|* *None*)\n* **Returns:**\n  List of keys loaded to Redis.\n* **Return type:**\n  List[str]\n* **Raises:**\n  * [**SchemaValidationError**](https://redis.io/docs/latest/exceptions/#schemavalidationerror) – If validation fails when validate_on_load is enabled.\n  * [**RedisVLError**](https://redis.io/docs/latest/exceptions/#redisvlerror) – If there’s an error loading data to Redis.\n\n```python\ndata = [{\"test\": \"foo\"}, {\"test\": \"bar\"}]\n\n# simple case\nkeys = await index.load(data)\n\n# set 360 second ttl policy on data\nkeys = await index.load(data, ttl=360)\n\n# load data with predefined keys\nkeys = await index.load(data, keys=[\"rvl:foo\", \"rvl:bar\"])\n\n# load data with preprocessing step\ndef add_field(d):\n    d[\"new_field\"] = 123\n    return d\nkeys = await index.load(data, preprocess=add_field)\n```\n\n#### `async paginate(query, page_size=30)`\n\nExecute a given query against the index and return results in\npaginated batches.\n\nThis method accepts a RedisVL query instance, enabling async pagination\nof results which allows for subsequent processing over each batch with a\ngenerator.\n\n* **Parameters:**\n  * **query** (*BaseQuery*) – The search query to be executed.\n  * **page_size** (*int* *,* *optional*) – The number of results to return in each\n    batch. Defaults to 30.\n* **Yields:**\n  An async generator yielding non-empty `SearchResults` batches.\n* **Raises:**\n  * **TypeError** – If the page_size argument is not of type int.\n  * **ValueError** – If the page_size argument is less than or equal to zero.\n* **Return type:**\n  *AsyncGenerator*\n\n```python\n# Iterate over paginated search results in batches of 10\nasync for result_batch in index.paginate(query, page_size=10):\n    # Process each batch of results\n    pass\n```\n\n\u003e [!NOTE]\n\u003e The page_size parameter controls the number of items each result\n\u003e batch contains. Adjust this value based on performance\n\u003e considerations and the expected volume of search results.\n\n\u003e [!NOTE]\n\u003e For stable pagination, the query must have a sort_by clause on a\n\u003e **unique** field. Redis documents that `LIMIT` without sorting is\n\u003e non-deterministic, so pages may otherwise repeat or miss documents.\n\u003e Very deep pagination is also bounded server-side by\n\u003e `search-max-search-results` (1,000,000 by default, but 10,000 on\n\u003e some managed tiers), past which the search errors rather than ending.\n\n\u003e [!NOTE]\n\u003e A yielded batch may contain fewer than `page_size` documents, and an\n\u003e empty batch is never yielded. On Redis 8+ a matched document whose\n\u003e data expires while the search is running is skipped; each batch\n\u003e reports how many were skipped via `dropped_count` (and\n\u003e `complete`), including skips inherited from a page that was dropped\n\u003e in its entirety. Pagination still runs to the end of the result set in\n\u003e that case.\n\n#### `async query(query)`\n\nAsynchronously execute a query on the index.\n\nThis method takes a BaseQuery, AggregationQuery, HybridQuery, or SQLQuery object\ndirectly, runs the search, and handles post-processing of the search.\n\n* **Parameters:**\n  **query** (*Union* *[* *BaseQuery* *,* *AggregationQuery* *,* [*HybridQuery*](https://redis.io/docs/latest/query/#hybridquery) *,* [*SQLQuery*](https://redis.io/docs/latest/query/#sqlquery) *]*) – The query to run.\n* **Returns:**\n  A list of search results.\n* **Return type:**\n  List[Result]\n\n```python\nfrom redisvl.query import VectorQuery\n\nquery = VectorQuery(\n    vector=[0.16, -0.34, 0.98, 0.23],\n    vector_field_name=\"embedding\",\n    num_results=3\n)\n\nresults = await index.query(query)\n```\n\n#### `async search(*args, **kwargs)`\n\nPerform an async search against the index.\n\nWrapper around the search API that adds the index name\nto the query and passes along the rest of the arguments\nto the redis-py ft().search() method.\n\n* **Returns:**\n  Raw Redis search results.\n* **Return type:**\n  Result\n\n#### `async update_by_filter(filter_expression, values, *, batch_size=500, dry_run=False, allow_all=False, on_progress=None)`\n\nSet `values` on every document matching a filter expression (async).\n\nSee [update_by_filter](#update_by_filter) for full semantics.\n\n* **Parameters:**\n  * **filter_expression** (*str* *|* [*FilterExpression*](https://redis.io/docs/latest/filter/#filterexpression))\n  * **values** (*dict* *[* *str* *,* *Any* *]*)\n  * **batch_size** (*int*)\n  * **dry_run** (*bool*)\n  * **allow_all** (*bool*)\n  * **on_progress** (*Callable* *[* *[* *int* *,* *int* *]* *,* *None* *]*  *|* *None*)\n* **Return type:**\n  [BulkResult](#bulkresult)\n\n#### `property client: Redis | RedisCluster | None`\n\nThe underlying redis-py client object.\n\n#### `property key_separator: str`\n\nThe optional separator between a defined prefix and key value in\nforming a Redis key.\n\n#### `property name: str`\n\nThe name of the Redis search index.\n\n#### `property prefix: str`\n\nThe key prefix used in forming Redis keys.\n\nFor multi-prefix indexes, returns the first prefix.\n\n#### `property prefixes: list[str]`\n\nAll key prefixes configured for this index.\n\n#### `property storage_type: StorageType`\n\nThe underlying storage type for the search index; either\nhash or json.\n\n\u003ca id=\"searchresults-api\"\u003e\u003c/a\u003e\n\n## SearchResults\n\n### `class SearchResults(iterable=(), dropped_count=0)`\n\nA list of result documents that also reports result completeness.\n\nThis is a drop-in `list` — iteration, indexing, `len()`, and every other\nlist operation behave exactly as before, so existing callers need no\nchanges. It additionally carries:\n\n- `dropped_count`: the number of matched documents that were skipped\n  because their field payload came back missing (the Redis 8.8+\n  background-search TTL/expiry race); `0` in the normal case.\n- `complete`: `False` when `dropped_count \u003e 0`, i.e. the result set may\n  be missing one or more matches that the server reported but could not\n  materialize.\n\nCallers that need completeness guarantees — audit/compliance queries, eval\nharnesses, or agents making control-flow decisions — can inspect these to\ndetect and react to a partial result (e.g. retry, reconcile against a\n`CountQuery`, or annotate a downstream answer as incomplete). Everyone else\ncan ignore them. Note that operations returning a new list (slicing,\n`sorted()`, concatenation) yield a plain `list` without this metadata.\n\n* **Parameters:**\n  * **iterable** (*Any*)\n  * **dropped_count** (*int*)\n\n#### `property complete: bool`\n\nTrue when no matched documents were dropped from this result set.\n\n\u003ca id=\"bulkresult-api\"\u003e\u003c/a\u003e\n\n## BulkResult\n\n### `class BulkResult(matched, processed, completed=True, dry_run=False)`\n\nOutcome of a bulk operation ([drop_by_filter](#drop_by_filter) /\n[update_by_filter](#update_by_filter)).\n\nBecause bulk operations are not atomic across the match set, the result\ndistinguishes how many documents matched from how many were actually\nprocessed, and whether the run ran to completion. Read the fields\nexplicitly (`result.processed` / `result.matched` / `result.completed`).\n\n* **Parameters:**\n  * **matched** (*int*)\n  * **processed** (*int*)\n  * **completed** (*bool*)\n  * **dry_run** (*bool*)\n\n#### `matched`\n\nDocuments matching the filter when the run started.\n\n* **Type:**\n  int\n\n#### `processed`\n\nDocuments actually deleted/updated (for `dry_run`,\nthe number that *would* be processed).\n\n* **Type:**\n  int\n\n#### `completed`\n\nFalse if the run stopped early — a delete hit its\nrunaway backstop under heavy concurrent inserts, or a delete batch\ntwice removed nothing (its keys failed to delete, or the index is\nlisting documents whose keys are already gone). Re-run to finish;\ncheck the logged warning first, since the second case will not\nresolve itself.\n\n* **Type:**\n  bool\n\n#### `dry_run`\n\nTrue if this was a preview and nothing was mutated.\n\n* **Type:**\n  bool\n",
  "tags": [],
  "last_updated": "2026-11-09T09:21:02-08:00"
}
