Skip to content

Fix/lazy load local mode#1134

Merged
joein merged 4 commits into
qdrant:devfrom
holyMolyTolli:fix/lazy-load-local-mode
Dec 11, 2025
Merged

Fix/lazy load local mode#1134
joein merged 4 commits into
qdrant:devfrom
holyMolyTolli:fix/lazy-load-local-mode

Conversation

@holyMolyTolli

Copy link
Copy Markdown
Contributor

Description

Fixes issue where the client crashes immediately upon import in read-only Docker containers (such as AWS EKS or Lambda) due to portalocker initialization checks.

The Issue

Previously, qdrant_client.local.qdrant_local imported portalocker at the top level. portalocker immediately checks for a writable temporary directory upon import. In read-only filesystems (where /tmp might not be writable or mounted), this raised a FileNotFoundError, crashing the application even if the user only intended to connect to a remote Qdrant server and never used the local storage.

The Fix

Moved import portalocker from the global scope in qdrant_client/local/qdrant_local.py and qdrant_client/local/async_qdrant_local.py into the specific methods that require file locking (_load and close).

All Submissions:

  • Contributions should target the dev branch. Did you create your branch from dev?
  • Have you followed the guidelines in our Contributing document?
  • Have you checked to ensure there aren't other open Pull Requests for the same update/change?

Changes to Core Features:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your core changes, as applicable? (Verified manually via script, as read-only fs is hard to simulate in standard pytest)
  • Have you successfully ran tests with your changes locally?

@netlify

netlify Bot commented Dec 10, 2025

Copy link
Copy Markdown

Deploy Preview for poetic-froyo-8baba7 ready!

Name Link
🔨 Latest commit 335e27b
🔍 Latest deploy log https://app.netlify.com/projects/poetic-froyo-8baba7/deploys/693a415b5be062000764ad73
😎 Deploy Preview https://deploy-preview-1134--poetic-froyo-8baba7.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@holyMolyTolli holyMolyTolli requested a review from joein December 10, 2025 20:29
@holyMolyTolli holyMolyTolli marked this pull request as draft December 10, 2025 20:35
@coderabbitai

coderabbitai Bot commented Dec 10, 2025

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

The pull request defers portalocker imports from module-global scope to runtime-local scope in both qdrant_client/local/async_qdrant_local.py and qdrant_client/local/qdrant_local.py. The module-level import statement is removed, and portalocker is instead imported locally within the close() method before performing unlock and close operations, and within the _load() method before acquiring the lock. Control flow and error handling remain unchanged; the modification only affects the timing and scope of the import statement.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

  • Changes follow a consistent, repetitive pattern across two similar files (homogeneous edits)
  • Import deferral is a straightforward refactor with no logic changes
  • No behavioral differences; error handling pathways remain identical

Possibly related PRs

  • PR #1134: Applies the identical portalocker import deferral pattern to the same two files in the local module
  • PR #1124: Modifies the _load() method implementation in both async and synchronous local client files, potentially conflicting or complementing this change

Suggested reviewers

  • tbung

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.79% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix/lazy load local mode' directly summarizes the main change: moving portalocker import from module scope to lazy-loaded methods to fix import crashes in read-only environments.
Description check ✅ Passed The description comprehensively explains the issue (portalocker import crash in read-only Docker containers), the root cause, and the solution (lazy loading portalocker into specific methods), with clear relevance to the changeset.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between deef084 and 335e27b.

📒 Files selected for processing (2)
  • qdrant_client/local/async_qdrant_local.py (2 hunks)
  • qdrant_client/local/qdrant_local.py (2 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
qdrant_client/local/async_qdrant_local.py (1)

795-803: Ruff B905 on zip() in upload_collection: consider explicit strict or suppress

Ruff flags the zip() at lines 818–820 (B905: “zip() without an explicit strict=”). Here the behavior intentionally allows truncation using an infinite UUID generator:

for point_id, vector, payload in zip(
    ids or uuid_generator(), iter(vectors), payload or itertools.cycle([{}])
):
    ...

If your minimum supported Python version includes zip(..., strict=...), you can keep semantics and satisfy Ruff by making this explicit:

- for point_id, vector, payload in zip(
+ for point_id, vector, payload in zip(
     ids or uuid_generator(),
     iter(vectors),
     payload or itertools.cycle([{}]),
+    strict=False,
 ):

If you still support Python versions without strict, the alternative is to keep the current behavior and silence B905 for this line with a targeted # noqa: B905 comment.

Please pick whichever matches your Python version policy.

Also applies to: 807-821

qdrant_client/async_qdrant_fastembed.py (1)

738-742: Consider adding strict=True to zip() for clarity.

While the slicing at lines 738-739 ensures dense_responses and sparse_responses have equal lengths, adding strict=True to the zip() call would make this invariant explicit and help catch bugs if the slicing logic changes in the future.

Since this is an autogenerated file, this improvement should be applied to the source file or generation script.

Apply this pattern in the source:

         for dense_response, sparse_response in zip(dense_responses, sparse_responses, strict=True)

Based on static analysis hints.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c701285 and deef084.

📒 Files selected for processing (5)
  • qdrant_client/async_qdrant_client.py (1 hunks)
  • qdrant_client/async_qdrant_fastembed.py (6 hunks)
  • qdrant_client/async_qdrant_remote.py (13 hunks)
  • qdrant_client/local/async_qdrant_local.py (11 hunks)
  • qdrant_client/local/qdrant_local.py (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
qdrant_client/async_qdrant_fastembed.py (8)
qdrant_client/qdrant_fastembed.py (2)
  • _get_model_params (214-231)
  • embedding_model_name (93-96)
qdrant_client/local/qdrant_local.py (1)
  • query_batch_points (426-447)
qdrant_client/async_qdrant_client.py (1)
  • query_batch_points (209-249)
qdrant_client/async_qdrant_remote.py (1)
  • query_batch_points (471-513)
qdrant_client/local/async_qdrant_local.py (1)
  • query_batch_points (386-404)
qdrant_client/async_client_base.py (1)
  • query_batch_points (42-45)
qdrant_client/client_base.py (1)
  • query_batch_points (32-38)
qdrant_client/qdrant_remote.py (1)
  • query_batch_points (549-594)
qdrant_client/async_qdrant_remote.py (1)
qdrant_client/qdrant_remote.py (2)
  • _try_argument_to_grpc_selector (1305-1336)
  • _try_argument_to_rest_points_and_filter (1389-1416)
qdrant_client/local/async_qdrant_local.py (1)
qdrant_client/local/qdrant_local.py (1)
  • _resolve_query_input (238-339)
🪛 GitHub Actions: Integration tests
qdrant_client/async_qdrant_client.py

[error] 107-110: Generated async_qdrant_client.py is not consistent with file in this repository, see diff above.

🪛 Ruff (0.14.8)
qdrant_client/async_qdrant_fastembed.py

742-742: zip() without an explicit strict= parameter

Add explicit value for parameter strict=

(B905)

qdrant_client/local/async_qdrant_local.py

818-820: zip() without an explicit strict= parameter

Add explicit value for parameter strict=

(B905)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Redirect rules - poetic-froyo-8baba7
  • GitHub Check: Header rules - poetic-froyo-8baba7
  • GitHub Check: Pages changed - poetic-froyo-8baba7
🔇 Additional comments (6)
qdrant_client/local/qdrant_local.py (1)

83-89: Lazy portalocker import in local mode is correct and achieves the PR goal

Moving import portalocker inside close() and _load() while guarding on self._flock_file preserves the existing locking behavior for persistent local storage, but prevents portalocker (and its temp-dir checks) from running at module import time. That fixes the read-only-container crash for users who never touch local mode, without changing semantics for those who do.

This looks good as-is.

Also applies to: 132-147

qdrant_client/local/async_qdrant_local.py (2)

84-89: Async local portalocker lazy import and query/prefetch resolution look correct

  • The lazy import portalocker in close() and _load() matches the sync QdrantLocal pattern and keeps portalocker (and its temp-dir checks) off the import path unless persistent local mode is actually used.
  • _resolve_query_input and its use in _resolve_prefetch_input, query_points, and query_points_groups now mirror the synchronous implementation’s behavior for ID→vector resolution and “mentioned ids” filtering.

All of this aligns with the intended semantics; no issues spotted.

Also applies to: 128-138, 224-315, 343-349, 369-373, 435-439


637-641: Comprehension and unpacking style cleanups are safe

The updated comprehensions over self.aliases.items() and self.collections.items() just drop redundant tuple parentheses. Behavior and returned structures are unchanged.

No action needed here.

Also applies to: 648-652, 658-662, 709-712

qdrant_client/async_qdrant_remote.py (1)

87-88: Selector helper usage and unpacking/style changes are consistent and correct

  • The minor unpacking/comprehension tweaks around URL parsing and headers (self._host, self._port, _rest_headers, _parse_url) are purely stylistic and don’t alter behavior.
  • Routing all points/shard-key handling in delete_vectors, delete, set_payload, overwrite_payload, delete_payload, and clear_payload through _try_argument_to_grpc_selector, _try_argument_to_rest_selector, and _try_argument_to_rest_points_and_filter matches the synchronous qdrant_remote logic and makes async behavior consistent for all supported PointsSelector forms (lists, REST/grpc selectors, filters, etc.).
  • The inferred opt_shard_key_selector merge (shard_key_selector = shard_key_selector or opt_shard_key_selector) is the same pattern used in the sync client and looks correct.

No problems found here.

Also applies to: 119-119, 228-233, 1053-1076, 1269-1295, 1318-1357, 1370-1407, 1420-1455, 1467-1475

qdrant_client/async_qdrant_client.py (1)

105-111: This review comment appears to be based on incorrect assumptions about the codebase. After thorough investigation:

  • No tools/generate_async_client.sh script exists in the repository
  • The async_qdrant_client.py file contains no markers, comments, or evidence that it is autogenerated
  • No CI workflow or check for "generated async client is consistent" was found
  • The dict comprehension code at lines 105–111 is identical between the sync (qdrant_client.py) and async (async_qdrant_client.py) clients

The changes to lines 105–111 are standard Python code without any generation mechanism attached. If concerns about client consistency exist, please clarify the specific generation process and CI check being referenced.

Likely an incorrect or invalid review comment.

qdrant_client/async_qdrant_fastembed.py (1)

283-283: LGTM! Style improvements align with Python conventions.

These changes remove redundant parentheses from tuple unpacking, which is the idiomatic Python style. The logic remains unchanged.

Note: This file is autogenerated (see header comments), so these style improvements originate from the source file or generation script.

Also applies to: 397-397, 439-439, 460-460, 654-654, 742-742

@joein joein marked this pull request as ready for review December 11, 2025 04:27
@joein joein merged commit 144f4ea into qdrant:dev Dec 11, 2025
8 checks passed
@holyMolyTolli holyMolyTolli deleted the fix/lazy-load-local-mode branch December 11, 2025 06:45
joein added a commit that referenced this pull request Dec 12, 2025
* Fix: Lazy load QdrantLocal to support read-only envs

* Fix: Lazy load QdrantLocal to support read-only envs

* Fix: Regenerate async client using Python 3.10 to match CI

* chore: add comments to portalocker import

---------

Co-authored-by: Kirstin <[email protected]>
Co-authored-by: George Panchuk <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants