Skip to content

Commit bef9ef1

Browse files
[Python] Reuse Milvus client in RAG vector sink write path (#39378)
* Reuse Milvus client in vector sink write path _MilvusSink.write() constructed a new MilvusClient on every call, which discarded the retry-wrapped client created in __enter__. This bypassed the connection retry/backoff and left the initial client unclosed (leaked), while __exit__ only closed the last client. The write() docstring also incorrectly claimed the collection was flushed for durability. Reuse the client established in __enter__ for the upsert, and correct the docstring to describe the actual (no-flush) visibility semantics. Add a unit test asserting write() reuses the __enter__ client instead of reconstructing one per call. Co-authored-by: Cursor <cursoragent@cursor.com> * Clear Milvus client after close in sink __exit__ Set self._client to None after closing so a reused sink instance does not keep a stale closed client. Address review feedback. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 0643150 commit bef9ef1

2 files changed

Lines changed: 43 additions & 5 deletions

File tree

sdks/python/apache_beam/ml/rag/ingestion/milvus_search.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -287,16 +287,18 @@ def write(self, documents):
287287
"""Writes a batch of documents to the Milvus collection.
288288
289289
Performs an upsert operation to insert new documents or update existing
290-
ones based on primary key. After the upsert, flushes the collection to
291-
ensure data persistence.
290+
ones based on primary key, reusing the client established in ``__enter__``.
291+
292+
Note:
293+
This submits the upsert to Milvus; it does not call ``flush()`` on the
294+
collection. Newly written rows become queryable according to Milvus'
295+
normal segment-sync behavior. Callers that require immediate
296+
read-after-write visibility should flush the collection explicitly.
292297
293298
Args:
294299
documents: List of dictionaries representing Milvus records to write.
295300
Each dictionary should contain fields matching the collection schema.
296301
"""
297-
self._client = MilvusClient(
298-
**unpack_dataclass_with_kwargs(self._connection_params))
299-
300302
resp = self._client.upsert(
301303
collection_name=self._write_config.collection_name,
302304
partition_name=self._write_config.partition_name,
@@ -346,3 +348,4 @@ def __exit__(self, exc_type, exc_val, exc_tb):
346348
_ = exc_type, exc_val, exc_tb # Unused parameters
347349
if self._client:
348350
self._client.close()
351+
self._client = None

sdks/python/apache_beam/ml/rag/ingestion/milvus_search_test.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,15 @@
1515
# limitations under the License.
1616
#
1717
import unittest
18+
from unittest import mock
1819

1920
from parameterized import parameterized
2021

2122
try:
23+
from apache_beam.ml.rag.ingestion import milvus_search as milvus_search_module
2224
from apache_beam.ml.rag.ingestion.milvus_search import MilvusVectorWriterConfig
2325
from apache_beam.ml.rag.ingestion.milvus_search import MilvusWriteConfig
26+
from apache_beam.ml.rag.ingestion.milvus_search import _MilvusSink
2427
from apache_beam.ml.rag.utils import MilvusConnectionParameters
2528
except ImportError as e:
2629
raise unittest.SkipTest(f'Milvus dependencies not installed: {str(e)}')
@@ -119,5 +122,37 @@ def test_invalid_configuration_parameters(
119122
self.assertIn(expected_error_msg, str(context.exception))
120123

121124

125+
class TestMilvusSinkClientReuse(unittest.TestCase):
126+
"""Unit tests for Milvus sink client reuse.
127+
128+
Verifies that ``_MilvusSink.write`` reuses the client established in
129+
``__enter__`` instead of constructing (and leaking) a new client on every
130+
write.
131+
"""
132+
def _sink(self):
133+
connection_params = MilvusConnectionParameters(uri="http://localhost:19530")
134+
write_config = MilvusWriteConfig(collection_name="test_collection")
135+
return _MilvusSink(connection_params, write_config)
136+
137+
def test_write_reuses_enter_client(self):
138+
"""write() must not construct a new MilvusClient per call."""
139+
with mock.patch.object(milvus_search_module,
140+
'MilvusClient') as mock_client_cls:
141+
with self._sink() as sink:
142+
# One client created on __enter__.
143+
self.assertEqual(mock_client_cls.call_count, 1)
144+
145+
sink.write([{"id": 1}])
146+
sink.write([{"id": 2}])
147+
148+
# write() reuses that client; no new construction.
149+
self.assertEqual(mock_client_cls.call_count, 1)
150+
self.assertEqual(mock_client_cls.return_value.upsert.call_count, 2)
151+
152+
# Client closed once on __exit__, and cleared so the sink can be reused.
153+
self.assertEqual(mock_client_cls.return_value.close.call_count, 1)
154+
self.assertIsNone(sink._client)
155+
156+
122157
if __name__ == '__main__':
123158
unittest.main()

0 commit comments

Comments
 (0)