Async vs Sync Client Implementations in OpenViking: Complete Technical Comparison
OpenViking provides AsyncOpenViking for non-blocking coroutines and SyncOpenViking for blocking synchronous operations, with both clients sharing the same underlying singleton instance and core functionality while differing in execution models, initialization patterns, and concurrency capabilities.
The volcengine/OpenViking repository delivers dual API surfaces to accommodate diverse programming paradigms. Understanding the architectural distinctions between these async and sync client implementations enables developers to select the optimal interface for their specific concurrency requirements and application architecture.
Programming Model and API Design
Method Signatures and Execution
The fundamental distinction lies in how operations are exposed to callers. In openviking/async_client.py, AsyncOpenViking declares all public methods using async def, requiring callers to await each operation. Conversely, openviking/sync_client.py implements SyncOpenViking with ordinary blocking function signatures that internally delegate to the async client.
For example, while the async client defines async def search(self, ...) -> List[...], the sync client defines def search(self, ...) -> List[...] and internally executes return run_async(self._async_client.search(...)). This wrapping strategy allows synchronous codebases to consume OpenViking functionality without introducing async/await syntax throughout the application.
The Bridge: run_async Utility
The synchronous client relies on a bridging utility defined in openviking_cli/utils.py. The run_async function accepts an awaitable, creates a temporary event loop, and runs the coroutine to completion before returning the result. This mechanism provides the blocking behavior that synchronous callers expect while leveraging the same underlying asynchronous implementation used by AsyncOpenViking.
Initialization and Lifecycle Management
Client Initialization Patterns
Both clients require explicit initialization before performing operations, but the invocation differs significantly. The async client requires await client.initialize(), which starts the underlying LocalClient from openviking/client.py and sets an internal _initialized flag upon completion.
The sync client offers client.initialize() as a blocking method that internally calls run_async(self._async_client.initialize()). While the async client tracks initialization state directly, the sync client maintains its own _initialized flag and only delegates to the async counterpart's initialization logic.
Singleton Implementation and Thread Safety
AsyncOpenViking implements a true singleton pattern using a class-level _instance variable protected by threading.Lock in its __new__ method. This ensures only one async client exists per process, preventing resource duplication.
SyncOpenViking does not implement its own singleton logic. Instead, its __init__ method creates an AsyncOpenViking instance, meaning the sync client indirectly shares the async client's singleton. Consequently, all sync client instances across a process reference the same underlying async singleton, inheriting its thread-safety guarantees without additional locking.
Reset and Cleanup Behavior
Resource cleanup follows the same async/sync dichotomy. AsyncOpenViking.reset() is an async class method that closes the singleton instance and clears the _instance variable. SyncOpenViking.reset() provides a blocking wrapper that invokes the async reset via run_async, ensuring synchronous callers can trigger cleanup without managing coroutines.
Operational Differences
Lazy Health Checks and Status Accessors
Three specific accessors exhibit different initialization behavior between the clients: get_status(), is_healthy(), and observer().
In AsyncOpenViking, these methods are plain coroutines that forward directly to the async implementation. In SyncOpenViking, these methods first ensure the client is initialized by calling initialize() if the _initialized flag is false, then delegate to the async client. This lazy initialization pattern in the sync client prevents errors when status checks occur before explicit initialization.
Error Handling and Exception Propagation
Exception propagation mirrors the execution model of each client. With AsyncOpenViking, errors propagate as regular async exceptions that callers catch using try/except blocks around awaited calls. SyncOpenViking surfaces identical errors as normal synchronous exceptions because run_async executes the full coroutine before returning control, translating async failures into standard Python exceptions suitable for synchronous error handling.
Performance Characteristics
AsyncOpenViking suits high-concurrency workloads such as web servers handling many parallel requests, as the event loop can interleave I/O operations without blocking threads. SyncOpenViking provides simplicity for scripts, Jupyter notebooks, or environments where async/await introduces unnecessary complexity, though each call blocks the current thread until the underlying async operation completes.
Code Examples: Async and Sync Usage
The following examples demonstrate identical workflows using both client types:
# Async usage (requires an async context)
import asyncio
from openviking import AsyncOpenViking
async def async_demo():
client = AsyncOpenViking(path="./data")
await client.initialize()
# Create a session and add a message
session = await client.create_session()
await client.add_message(session["session_id"], role="user", content="Hello Viking!")
# Perform a semantic search
results = await client.search("How to use OpenViking?")
print(results)
await client.close()
asyncio.run(async_demo())
# Sync usage (no async/await needed)
from openviking import SyncOpenViking
def sync_demo():
client = SyncOpenViking(path="./data")
client.initialize() # blocks until ready
# Create a session and add a message
session = client.create_session()
client.add_message(session["session_id"], role="user", content="Hello Viking!")
# Perform a semantic search
results = client.search("How to use OpenViking?")
print(results)
client.close()
sync_demo()
Summary
- Execution Model:
AsyncOpenVikingexposesasync defcoroutines whileSyncOpenVikingprovides blocking wrappers viarun_asyncfromopenviking_cli/utils.py. - Singleton Architecture: The async client enforces a process-wide singleton using
threading.Lockinopenviking/async_client.py; the sync client inherits this singleton by instantiatingAsyncOpenVikinginternally. - Initialization Differences: Async initialization requires
await, while sync initialization blocks viarun_async; sync client additionally performs lazy initialization forget_status,is_healthy, andobserver. - Shared Core: Both clients utilize
LocalClientfromopenviking/client.pyfor storage-backed operations, ensuring functional parity regardless of API style. - Concurrency Suitability: Choose
AsyncOpenVikingfor high-throughput concurrent applications; useSyncOpenVikingfor straightforward, sequential execution contexts.
Frequently Asked Questions
Can I use both async and sync clients in the same application?
Yes. Since SyncOpenViking creates an AsyncOpenViking instance internally and the async client implements a true singleton pattern, both interfaces share the same underlying state and resources. You can mix both styles, though you should avoid calling sync methods from async contexts to prevent event loop blocking.
Does the sync client create a separate connection pool?
No. The sync client does not implement independent connection management. It delegates all operations to the shared AsyncOpenViking singleton, which manages the connection pool through LocalClient in openviking/client.py. Therefore, connection pools, sessions, and resources remain shared across both client types.
Which client should I choose for high-throughput applications?
Select AsyncOpenViking for high-throughput or high-concurrency scenarios. The async implementation allows the event loop to interleave multiple I/O operations without blocking threads, maximizing resource utilization. SyncOpenViking blocks the calling thread per operation, making it unsuitable for concurrent request handling.
How does error handling differ between the two clients?
AsyncOpenViking propagates exceptions as async exceptions that you catch within try/except blocks surrounding awaited calls. SyncOpenViking surfaces the same underlying errors as standard synchronous exceptions because run_async executes coroutines to completion before returning. The error types and messages remain identical; only the catching mechanism differs based on your code's sync or async context.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →