Skip to content

Commit 286315a

Browse files
Refactor async client connection-handling (#280)
Refactor interfaces related to the Redis client in AsyncRedisIndex (and possibly others). ## Constraints - We want to initialize the Redis connection lazily, not at object instantiation or module import. - Python doesn't have async properties, so properties are limited to sync function calls - Therefore, we don't want properties to be part of the interface - For functions we "remove," we'll leave them in place with a deprecation warning - Remaining backwards-compatible until the next major release ## The Design Today - Can create a client and set it using `index.connect()` - Pass in a `redis_url` - Calls `index.set_client()` - Can pass in a client and set it with `index.set_client()` - Pass in a client instance - **Can't** pass in a client instance with `__init__()` - Or anything URL, etc. - Can create a client with `index.from_existing()` - Pass it a `redis_url` - Calls `index.set_client()` - **Can't** use index as an async context manager - Requires explicit resource handling, `atexit` handler - But this is broken - The `setup_async_redis()` decorator creates a function wrapper that calls `validate_async_redis()` with every `set_client()` call - The `RedisConnectionFactory.connect()` returns incompatible types (sync, async `Redis`) - Sync vs. async depends on a parameter ## The Design After Refactor - **Can** use as an async context manager - Either disconnect the index manually with `disconnect()` or use it as a context manager - `async with Index()...` will `disconnect()` after you exit the context block - Lazily instantiate client with `self._get_client()` method ("private") - Remove `set_client()` public interface if possible - Lazily instantiate client - Remove `connect()` public interface if possible - Lazily instantiate client - Leave `from_existing()` - Leave `client()` property, now just returns `._redis_client` and can be None - Call `validate_async_redis()` when setting a new client instance for the first time - But make this an internal check rather than attached to public interface - Allow `redis_url`, `redis_kwargs`, or `redis_client` in `__init__()` ### Examples Post-Refactor ```python #1: New instance with redis URL index = AsyncRedisIndex(redis_url="...") #2: New instance with existing client index = AsyncRedisIndex(redis_client=client) #3: New instance with `from_existing` index = AsyncRedisIndex.from_existing(..., redis_url="...") #4 Passing both a client and a URL is isallowed try: index = AsyncRedisIndex(redis_client=client, redis_url="...") except: pass else: raise RuntimeError("Should have raised!") # The client is lazily connected here, and we send telemetry. await index.something_using_redis() await index.something_else_using_redis() # Close the client. await index.close() async with AsyncRedisIndex(redis_url="...") as index: # Same story: client is lazily created now await index.something_using_redis() await index.something_else_using_redis() # Client is automatically closed # __enter__ is reentrant async with index: # Lazily opens a new client connection. await index.a_third_thing_using_redis() # Client is automatically closed ``` --------- Co-authored-by: Tyler Hutcherson <[email protected]>
1 parent 1172c49 commit 286315a

23 files changed

+1180
-680
lines changed

README.md

+5-6
Original file line numberDiff line numberDiff line change
@@ -121,19 +121,18 @@ Choose from multiple Redis deployment options:
121121
})
122122
```
123123

124-
2. [Create a SearchIndex](https://docs.redisvl.com/en/stable/user_guide/01_getting_started.html#create-a-searchindex) class with an input schema and client connection in order to perform admin and search operations on your index in Redis:
124+
2. [Create a SearchIndex](https://docs.redisvl.com/en/stable/user_guide/01_getting_started.html#create-a-searchindex) class with an input schema to perform admin and search operations on your index in Redis:
125125
```python
126126
from redis import Redis
127127
from redisvl.index import SearchIndex
128128
129-
# Establish Redis connection and define index
130-
client = Redis.from_url("redis://localhost:6379")
131-
index = SearchIndex(schema, client)
129+
# Define the index
130+
index = SearchIndex(schema, redis_url="redis://localhost:6379")
132131
133132
# Create the index in Redis
134133
index.create()
135134
```
136-
> Async compliant search index class also available: [AsyncSearchIndex](https://docs.redisvl.com/en/stable/api/searchindex.html#redisvl.index.AsyncSearchIndex).
135+
> An async-compatible index class also available: [AsyncSearchIndex](https://docs.redisvl.com/en/stable/api/searchindex.html#redisvl.index.AsyncSearchIndex).
137136

138137
3. [Load](https://docs.redisvl.com/en/stable/user_guide/01_getting_started.html#load-data-to-searchindex)
139138
and [fetch](https://docs.redisvl.com/en/stable/user_guide/01_getting_started.html#fetch-an-object-from-redis) data to/from your Redis instance:
@@ -346,7 +345,7 @@ Commands:
346345
stats Obtain statistics about an index
347346
```
348347
349-
> Read more about [using the CLI](https://docs.redisvl.com/en/stable/user_guide/cli.html).
348+
> Read more about [using the CLI](https://docs.redisvl.com/en/latest/overview/cli.html).
350349
351350
## 🚀 Why RedisVL?
352351

docs/user_guide/01_getting_started.ipynb

+56-44
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
},
8282
{
8383
"cell_type": "code",
84-
"execution_count": 1,
84+
"execution_count": 3,
8585
"metadata": {},
8686
"outputs": [],
8787
"source": [
@@ -126,7 +126,7 @@
126126
},
127127
{
128128
"cell_type": "code",
129-
"execution_count": 2,
129+
"execution_count": 4,
130130
"metadata": {},
131131
"outputs": [],
132132
"source": [
@@ -178,7 +178,7 @@
178178
},
179179
{
180180
"cell_type": "code",
181-
"execution_count": 3,
181+
"execution_count": 5,
182182
"metadata": {},
183183
"outputs": [],
184184
"source": [
@@ -195,7 +195,7 @@
195195
"Now we also need to facilitate a Redis connection. There are a few ways to do this:\n",
196196
"\n",
197197
"- Create & manage your own client connection (recommended)\n",
198-
"- Provide a simple Redis URL and let RedisVL connect on your behalf"
198+
"- Provide a Redis URL and let RedisVL connect on your behalf (by default, it will connect to \"redis://localhost:6379\")"
199199
]
200200
},
201201
{
@@ -209,7 +209,7 @@
209209
},
210210
{
211211
"cell_type": "code",
212-
"execution_count": 4,
212+
"execution_count": 6,
213213
"metadata": {},
214214
"outputs": [
215215
{
@@ -227,9 +227,13 @@
227227
"from redis import Redis\n",
228228
"\n",
229229
"client = Redis.from_url(\"redis://localhost:6379\")\n",
230+
"index = SearchIndex.from_dict(schema, redis_client=client)\n",
230231
"\n",
231-
"index.set_client(client)\n",
232-
"# optionally provide an async Redis client object to enable async index operations"
232+
"# alternatively, provide an async Redis client object to enable async index operations\n",
233+
"# from redis.asyncio import Redis\n",
234+
"# from redisvl.index import AsyncSearchIndex\n",
235+
"# client = Redis.from_url(\"redis://localhost:6379\")\n",
236+
"# index = AsyncSearchIndex.from_dict(schema, redis_client=client)\n"
233237
]
234238
},
235239
{
@@ -243,7 +247,7 @@
243247
},
244248
{
245249
"cell_type": "code",
246-
"execution_count": 5,
250+
"execution_count": 7,
247251
"metadata": {},
248252
"outputs": [
249253
{
@@ -258,8 +262,10 @@
258262
}
259263
],
260264
"source": [
261-
"index.connect(\"redis://localhost:6379\")\n",
262-
"# optionally use an async client by passing use_async=True"
265+
"index = SearchIndex.from_dict(schema, redis_url=\"redis://localhost:6379\")\n",
266+
"\n",
267+
"# If you don't specify a client or Redis URL, the index will attempt to\n",
268+
"# connect to Redis at the default address (\"redis://localhost:6379\")."
263269
]
264270
},
265271
{
@@ -273,7 +279,7 @@
273279
},
274280
{
275281
"cell_type": "code",
276-
"execution_count": 6,
282+
"execution_count": 8,
277283
"metadata": {},
278284
"outputs": [],
279285
"source": [
@@ -297,7 +303,7 @@
297303
},
298304
{
299305
"cell_type": "code",
300-
"execution_count": 7,
306+
"execution_count": 9,
301307
"metadata": {},
302308
"outputs": [
303309
{
@@ -315,7 +321,7 @@
315321
},
316322
{
317323
"cell_type": "code",
318-
"execution_count": 8,
324+
"execution_count": 10,
319325
"metadata": {},
320326
"outputs": [
321327
{
@@ -358,7 +364,7 @@
358364
},
359365
{
360366
"cell_type": "code",
361-
"execution_count": 9,
367+
"execution_count": 11,
362368
"metadata": {},
363369
"outputs": [
364370
{
@@ -392,7 +398,7 @@
392398
},
393399
{
394400
"cell_type": "code",
395-
"execution_count": 10,
401+
"execution_count": 12,
396402
"metadata": {},
397403
"outputs": [
398404
{
@@ -429,7 +435,7 @@
429435
},
430436
{
431437
"cell_type": "code",
432-
"execution_count": 11,
438+
"execution_count": 13,
433439
"metadata": {},
434440
"outputs": [],
435441
"source": [
@@ -454,13 +460,20 @@
454460
},
455461
{
456462
"cell_type": "code",
457-
"execution_count": 12,
463+
"execution_count": 14,
458464
"metadata": {},
459465
"outputs": [
466+
{
467+
"name": "stdout",
468+
"output_type": "stream",
469+
"text": [
470+
"*=>[KNN 3 @user_embedding $vector AS vector_distance] RETURN 6 user age job credit_score vector_distance vector_distance SORTBY vector_distance ASC DIALECT 2 LIMIT 0 3\n"
471+
]
472+
},
460473
{
461474
"data": {
462475
"text/html": [
463-
"<table><tr><th>vector_distance</th><th>user</th><th>age</th><th>job</th><th>credit_score</th></tr><tr><td>0</td><td>john</td><td>1</td><td>engineer</td><td>high</td></tr><tr><td>0</td><td>mary</td><td>2</td><td>doctor</td><td>low</td></tr><tr><td>0.0566299557686</td><td>tyler</td><td>9</td><td>engineer</td><td>high</td></tr></table>"
476+
"table><tr><th>vector_distance</th><th>user</th><th>age</th><th>job</th><th>credit_score</th></tr><tr><td>0</td><td>john</td><td>1</td><td>engineer</td><td>high</td></tr><tr><td>0</td><td>mary</td><td>2</td><td>doctor</td><td>low</td></tr><tr><td>0.0566299557686</td><td>tyler</td><td>9</td><td>engineer</td><td>high</td></tr></table>"
464477
],
465478
"text/plain": [
466479
"<IPython.core.display.HTML object>"
@@ -487,7 +500,7 @@
487500
},
488501
{
489502
"cell_type": "code",
490-
"execution_count": 13,
503+
"execution_count": 15,
491504
"metadata": {},
492505
"outputs": [
493506
{
@@ -537,13 +550,12 @@
537550
"\n",
538551
"client = Redis.from_url(\"redis://localhost:6379\")\n",
539552
"\n",
540-
"index = AsyncSearchIndex.from_dict(schema)\n",
541-
"await index.set_client(client)"
553+
"index = AsyncSearchIndex.from_dict(schema, redis_client=client)"
542554
]
543555
},
544556
{
545557
"cell_type": "code",
546-
"execution_count": 15,
558+
"execution_count": 16,
547559
"metadata": {},
548560
"outputs": [
549561
{
@@ -584,7 +596,7 @@
584596
},
585597
{
586598
"cell_type": "code",
587-
"execution_count": 16,
599+
"execution_count": 17,
588600
"metadata": {},
589601
"outputs": [],
590602
"source": [
@@ -609,14 +621,14 @@
609621
},
610622
{
611623
"cell_type": "code",
612-
"execution_count": 16,
624+
"execution_count": 18,
613625
"metadata": {},
614626
"outputs": [
615627
{
616628
"name": "stdout",
617629
"output_type": "stream",
618630
"text": [
619-
"11:53:25 redisvl.index.index INFO Index already exists, overwriting.\n"
631+
"11:28:32 redisvl.index.index INFO Index already exists, overwriting.\n"
620632
]
621633
}
622634
],
@@ -627,13 +639,13 @@
627639
},
628640
{
629641
"cell_type": "code",
630-
"execution_count": 17,
642+
"execution_count": 19,
631643
"metadata": {},
632644
"outputs": [
633645
{
634646
"data": {
635647
"text/html": [
636-
"<table><tr><th>vector_distance</th><th>user</th><th>age</th><th>job</th><th>credit_score</th></tr><tr><td>0</td><td>john</td><td>1</td><td>engineer</td><td>high</td></tr><tr><td>0</td><td>mary</td><td>2</td><td>doctor</td><td>low</td></tr><tr><td>0.0566299557686</td><td>tyler</td><td>9</td><td>engineer</td><td>high</td></tr></table>"
648+
"<table><tr><th>vector_distance</th><th>user</th><th>age</th><th>job</th><th>credit_score</th></tr><tr><td>0</td><td>mary</td><td>2</td><td>doctor</td><td>low</td></tr><tr><td>0</td><td>john</td><td>1</td><td>engineer</td><td>high</td></tr><tr><td>0.0566299557686</td><td>tyler</td><td>9</td><td>engineer</td><td>high</td></tr></table>"
637649
],
638650
"text/plain": [
639651
"<IPython.core.display.HTML object>"
@@ -659,7 +671,7 @@
659671
},
660672
{
661673
"cell_type": "code",
662-
"execution_count": 18,
674+
"execution_count": 20,
663675
"metadata": {},
664676
"outputs": [
665677
{
@@ -677,19 +689,19 @@
677689
"│ num_records │ 22 │\n",
678690
"│ percent_indexed │ 1 │\n",
679691
"│ hash_indexing_failures │ 0 │\n",
680-
"│ number_of_uses │ 5\n",
681-
"│ bytes_per_record_avg │ 50.9091\n",
692+
"│ number_of_uses │ 2\n",
693+
"│ bytes_per_record_avg │ 47.8 \n",
682694
"│ doc_table_size_mb │ 0.000423431 │\n",
683-
"│ inverted_sz_mb │ 0.00106812 \n",
695+
"│ inverted_sz_mb │ 0.000911713\n",
684696
"│ key_table_size_mb │ 0.000165939 │\n",
685-
"│ offset_bits_per_record_avg │ 8 \n",
686-
"│ offset_vectors_sz_mb │ 5.72205e-06\n",
687-
"│ offsets_per_term_avg │ 0.272727\n",
688-
"│ records_per_doc_avg │ 5.5\n",
697+
"│ offset_bits_per_record_avg │ nan\n",
698+
"│ offset_vectors_sz_mb │ 0 \n",
699+
"│ offsets_per_term_avg │ 0 \n",
700+
"│ records_per_doc_avg │ 5 \n",
689701
"│ sortable_values_size_mb │ 0 │\n",
690-
"│ total_indexing_time │ 0.197\n",
691-
"│ total_inverted_index_blocks │ 12\n",
692-
"│ vector_index_sz_mb │ 0.0201416\n",
702+
"│ total_indexing_time │ 0.239\n",
703+
"│ total_inverted_index_blocks │ 11\n",
704+
"│ vector_index_sz_mb │ 0.235603 \n",
693705
"╰─────────────────────────────┴─────────────╯\n"
694706
]
695707
}
@@ -718,7 +730,7 @@
718730
},
719731
{
720732
"cell_type": "code",
721-
"execution_count": 19,
733+
"execution_count": 21,
722734
"metadata": {},
723735
"outputs": [
724736
{
@@ -727,7 +739,7 @@
727739
"4"
728740
]
729741
},
730-
"execution_count": 19,
742+
"execution_count": 21,
731743
"metadata": {},
732744
"output_type": "execute_result"
733745
}
@@ -739,7 +751,7 @@
739751
},
740752
{
741753
"cell_type": "code",
742-
"execution_count": 20,
754+
"execution_count": 22,
743755
"metadata": {},
744756
"outputs": [
745757
{
@@ -748,7 +760,7 @@
748760
"True"
749761
]
750762
},
751-
"execution_count": 20,
763+
"execution_count": 22,
752764
"metadata": {},
753765
"output_type": "execute_result"
754766
}
@@ -760,7 +772,7 @@
760772
},
761773
{
762774
"cell_type": "code",
763-
"execution_count": 21,
775+
"execution_count": 23,
764776
"metadata": {},
765777
"outputs": [],
766778
"source": [
@@ -771,7 +783,7 @@
771783
],
772784
"metadata": {
773785
"kernelspec": {
774-
"display_name": "Python 3",
786+
"display_name": "env",
775787
"language": "python",
776788
"name": "python3"
777789
},

0 commit comments

Comments
 (0)