Using the gRPC Client¶
The SDK includes a GrpcIndex client that routes data-plane operations through a gRPC
transport backed by a native Rust extension (pinecone._grpc). For bulk upsert and
high-throughput workloads, gRPC typically delivers better performance than the default
REST client because it uses binary serialization and HTTP/2 multiplexing.
gRPC transport is included in the base pinecone package. There is no grpc extra to
install, and no grpcio dependency to add.
Creating a GrpcIndex¶
You can obtain a GrpcIndex in two ways.
Via Pinecone.index() with grpc=True (recommended, since it resolves the host automatically):
from pinecone import Pinecone
pc = Pinecone()
index = pc.index(name="product-search", grpc=True)
Directly (when you already know the host):
from pinecone.grpc import GrpcIndex
index = GrpcIndex(
host="product-search-abc123.svc.pinecone.io",
api_key="YOUR_API_KEY", # or set PINECONE_API_KEY env var
)
Every argument is keyword-only, and a missing API key raises before the channel is
built. GrpcIndex is the canonical name; GRPCIndex is a legacy capitalisation alias
for the same class.
GrpcIndex is a context manager; always close it when finished:
with GrpcIndex(host="product-search-abc123.svc.pinecone.io") as index:
index.upsert(vectors=[("product-42", [0.012, -0.087, 0.153])])
Endpoint scheme¶
The channel dials https. A data plane reached over something else — a plaintext
gateway, an egress proxy fronting a private endpoint, or a local simulator — needs
grpc_scheme set, on the client or on the index:
pc = Pinecone(grpc_scheme="http")
index = pc.index(name="product-search", grpc=True)
index = GrpcIndex(host="http://10.0.0.7:50051", grpc_scheme="http")
PINECONE_GRPC_SCHEME sets the same thing through the environment; the keyword
argument wins over it.
The scheme decides whether the wire carries TLS, and secure supplies the material
for the handshake, so grpc_scheme="http" is plaintext whatever secure says.
grpc_scheme="https" with secure=False cannot connect and is refused when the
index is built. Leaving grpc_scheme unset takes the scheme from secure.
Basic Operations¶
GrpcIndex carries the same data-plane methods as the HTTP Index — with one
exception, documents, covered under Limitations:
# Upsert
response = index.upsert(
vectors=[
("product-42", [0.012, -0.087, 0.153]),
("product-99", [0.045, 0.021, -0.064]),
],
namespace="catalog",
)
print(response.upserted_count)
# Query
results = index.query(
top_k=10,
vector=[0.012, -0.087, 0.153],
namespace="catalog",
)
for match in results.matches:
print(match.id, match.score)
The vector literals here are three floats so they fit on the page; a real one carries as many values as the index’s vector field declares.
Not every method actually travels over gRPC. upsert_records, search, search_records
and the bulk-import methods (start_import, describe_import, cancel_import,
list_imports, list_imports_paginated) go out over REST from the same client, because
the Pinecone gRPC API exposes no equivalent. They work; they just do not get the
transport’s benefit.
Retries and Timeouts¶
gRPC and REST share the retry shape; Retries and Resilience owns the comparison. What follows is only where gRPC diverges.
What it retries. Exactly three gRPC status codes: UNAVAILABLE,
RESOURCE_EXHAUSTED and ABORTED. The defaults differ from REST’s —
max_retries=5, backoff_factor=0.1 — and a retry_config left unset on
Pinecone() is not inherited here, precisely so those defaults survive. Pass one
explicitly, to Pinecone() or straight to GrpcIndex(), and it applies.
DEADLINE_EXCEEDED is not one of the three, and max_retries is the wrong knob for
it. Raising retries is the intuitive response and it does nothing: a call whose
deadline expires raises PineconeTimeoutError after a single attempt, having never
been retried. The knob is a longer deadline.
from pinecone import RetryConfig
from pinecone.grpc import GrpcIndex
host = "product-search-abc123.svc.pinecone.io"
# Does nothing for a deadline — the call was never retried.
index = GrpcIndex(host=host, retry_config=RetryConfig(max_retries=20))
# This. `timeout` is the per-attempt deadline; the index-level default is 20 seconds.
index = GrpcIndex(host=host, timeout=120.0)
A per-call timeout= does not replace the index-level one — both apply, and the
shorter fires. For a bulk method, total_timeout bounds the whole job rather than one
attempt.
Backoff is decorrelated jitter, uniform(backoff_factor, previous_delay * 3)
capped at max_wait — not an exponential curve. The first retry’s window is seeded at
ten times backoff_factor rather than at backoff_factor itself: a backend restart
returns UNAVAILABLE to every client at once, and a narrow first window is where a
thundering herd forms.
A server pushback hint wins over the computed backoff. When a response carries
grpc-retry-pushback-ms (or retry-after), that value is used instead, clamped to
max_wait and smeared so concurrent clients do not all wake together. A negative
value is the server saying do not retry at all, and the call fails immediately.
The retry budget is per channel, not per process — one GrpcIndex holds one
budget. Sustained retrying drains it, after which retryable failures fail fast instead
of retrying, so a struggling backend is not hammered by a client that is only ever
retrying.
Two things you cannot configure. RetryConfig.retryable_status_codes carries HTTP
statuses and is deliberately not forwarded to this transport; the gRPC code set is
fixed and not reachable from Python. And a retry_config given to GrpcIndex reaches
only the gRPC channel — the REST client it keeps alongside for upsert_records,
search and the import methods stays on REST’s defaults, so max_retries does not
cover everything on the object.
Async (Non-Blocking) Operations with PineconeFuture¶
upsert, query, fetch, delete, update, and query_namespaces each have an
_async variant that hands the call to a background thread and returns a
PineconeFuture immediately:
from concurrent.futures import as_completed
futures = [
index.upsert_async(vectors=[("product-42", [0.012, -0.087, 0.153])]),
index.upsert_async(vectors=[("product-99", [0.045, 0.021, -0.064])]),
]
# Collect results as they complete
for future in as_completed(futures):
result = future.result() # blocks up to the default 5-second timeout
print(result.upserted_count)
Issuing several at once is the reason to prefer these over the blocking methods: the requests overlap instead of queueing. Nothing is cancelled if you never collect a future — the request still reaches the server.
This is threads, not await. A PineconeFuture is not awaitable, and the function
holding it does not need to be async. There is no async gRPC client: AsyncPinecone.index()
takes no grpc argument, and gRPC has no asyncio twin at all. Code already running under
asyncio should use AsyncIndex over REST, whose methods are coroutines and yield to the
event loop rather than parking a worker thread — see
Sync vs Async.
PineconeFuture reference¶
Method |
Description |
|---|---|
|
Block until the result is ready; raises |
|
The exception the call raised, or |
|
|
|
|
|
Attempt to cancel the operation; |
|
|
|
Call |
Pass timeout=None to result() to block indefinitely:
result = future.result(timeout=None)
PineconeFuture subclasses concurrent.futures.Future and works with
concurrent.futures.as_completed() and concurrent.futures.wait(), so it integrates
naturally with thread-pool patterns. from pinecone.grpc import PineconeGrpcFuture also
resolves to it, under the pre-rewrite name.
Bulk Upsert from a DataFrame¶
For large-scale ingestion, upsert_from_dataframe() splits a pandas DataFrame into
batches and submits them concurrently from a thread pool. pandas is not a dependency
of the SDK. Install it yourself (pip install pandas) before using this method:
import pandas as pd
from pinecone.grpc import GrpcIndex
df = pd.DataFrame([
{"id": "product-42", "values": [0.012, -0.087, 0.153]},
{"id": "product-99", "values": [0.045, 0.021, -0.064]},
])
with GrpcIndex(host="product-search-abc123.svc.pinecone.io") as index:
response = index.upsert_from_dataframe(df, namespace="catalog", batch_size=500)
print(response.upserted_count)
For large or slow ingests, pass timeout to raise the server-side deadline
applied to each batch (it bounds each batch, not the whole DataFrame). None,
the default, uses the client’s configured request timeout (20s unless you set
GrpcIndex(..., timeout=...)):
with GrpcIndex(host="product-search-abc123.svc.pinecone.io") as index:
response = index.upsert_from_dataframe(
df, namespace="catalog", batch_size=200, timeout=120.0
)
print(response.upserted_count)
A batch that fails does not abort the ingest: the returned UpsertResponse reports what
landed and what did not. See
gRPC upsert_from_dataframe reports partial failures
for the response fields and a bounded retry loop. total_timeout bounds the whole
ingest, as opposed to timeout, which bounds one attempt of one batch.
When to Prefer gRPC¶
Scenario |
Recommendation |
|---|---|
Bulk upsert (thousands of vectors) |
gRPC, lower per-call overhead |
High-throughput query loops |
gRPC with |
Async Python frameworks (FastAPI, asyncio) |
Use |
Schema-based indexes, via |
HTTP |
Simple scripts and CLI tools |
Either works; HTTP |
Limitations¶
No
documentsnamespace.index.documents, the entry point for document operations on a schema-based index, exists only on the HTTPIndex. It is the one data-plane method setGrpcIndexdoes not carry.Sync only.
GrpcIndexdoes not supportasync/await, and no async gRPC client exists. For overlapping requests use the*_async()methods andPineconeFuture; for asyncio, useAsyncIndex.No
async_req=Trueorpool_threads. Those are legacy shims on the HTTPIndex.pc.index(..., grpc=True)ignorespool_threads; the*_async()methods are the gRPC equivalent.upsert_records,search,search_recordsand the bulk-import methods travel over REST, because the Pinecone gRPC API does not expose those endpoints.Retries are less configurable than on REST. The retried code set is fixed, and a
retry_configreaches only the gRPC channel — see Retries and Timeouts.The native extension is platform-specific.
pinecone._grpcis a compiled Rust extension bundled in the base package. If installation fails on your platform, check the package’s supported-platform list for available wheels.