Working with Serverless Indexes¶
Serverless indexes scale automatically: you pay for storage and queries without managing infrastructure. Pinecone handles capacity, replication, and availability.
Create a serverless index¶
An index’s fields are declared as a schema; deployment picks the cloud and region:
from pinecone import Pinecone
pc = Pinecone(api_key="your-api-key")
pc.indexes.create(
name="product-search",
schema={"fields": {"embedding": {"type": "dense_vector", "dimension": 1536, "metric": "cosine"}}},
deployment={"deployment_type": "managed", "cloud": "aws", "region": "us-east-1"},
)
"deployment_type": "managed" is what makes an index serverless. Omit deployment
entirely and you get a managed index on AWS us-east-1.
Only fields that get searched go in the schema: dense_vector, sparse_vector, or
string carrying a full_text_search config. Metadata you merely filter on stays out —
put it on the records you upsert and it is indexed for filtering automatically. Declaring
a metadata-only field here is rejected server-side, and one rejected field fails the whole
schema. A hybrid index has to declare its sparse_vector field up front, because
configure cannot add one later.
create polls until the index is ready by default, with no upper time bound. Pass a
positive timeout to bound the wait — PineconeTimeoutError
if the index is still not ready when it elapses — or timeout=-1 to return as soon as the
create request is accepted.
dimension=, metric=, vector_type=, and spec= remain as deprecated keyword-only
sugar, translated into schema=/deployment= before the request is sent. Each one is
mutually exclusive with the argument it translates to, so combining them raises
PineconeValueError. Write new code against schema=
and deployment=; see the v10 migration guide for the
before/after.
Clouds and regions¶
CloudProvider and the per-cloud region enums
AwsRegion, GcpRegion, and AzureRegion — all in pinecone.models.enums,
and all re-exported from pinecone — give you tab-completion and typo safety:
from pinecone import Pinecone
from pinecone.models.enums import AwsRegion, CloudProvider
pc = Pinecone(api_key="your-api-key")
pc.indexes.create(
name="product-search",
schema={"fields": {"embedding": {"type": "dense_vector", "dimension": 1536, "metric": "cosine"}}},
deployment={
"deployment_type": "managed",
"cloud": CloudProvider.AWS,
"region": AwsRegion.US_EAST_1,
},
)
The values those enums carry:
AWS: us-east-1, us-west-2, eu-west-1, eu-central-1, ap-southeast-1
GCP: us-central1, europe-west4
Azure: eastus2, germanywestcentral
cloud and region are sent as plain strings and the SDK does not check them against the
enums, so a region Pinecone adds later works as a string literal before an enum member
names it. Nothing checks the value before the request goes out, so a typo comes back as a
server error rather than a local one.
Enable deletion protection¶
Add deletion_protection="enabled" to prevent accidental deletes:
from pinecone import Pinecone
from pinecone.models.enums import DeletionProtection
pc = Pinecone(api_key="your-api-key")
pc.indexes.create(
name="product-search",
schema={"fields": {"embedding": {"type": "dense_vector", "dimension": 1536, "metric": "cosine"}}},
deployment={"deployment_type": "managed", "cloud": "aws", "region": "us-east-1"},
deletion_protection=DeletionProtection.ENABLED,
)
Choose read capacity¶
read_capacity decides how a managed index serves reads and how they are billed. Omit it
and the index comes up on on-demand capacity, billed per operation:
pc.indexes.create(
name="product-search",
schema={"fields": {"embedding": {"type": "dense_vector", "dimension": 1536, "metric": "cosine"}}},
deployment={"deployment_type": "managed", "cloud": "aws", "region": "us-east-1"},
read_capacity={"mode": "OnDemand"},
)
Dedicated capacity provisions read nodes you size yourself:
pc.indexes.create(
name="product-search",
schema={"fields": {"embedding": {"type": "dense_vector", "dimension": 1536, "metric": "cosine"}}},
deployment={"deployment_type": "managed", "cloud": "aws", "region": "us-east-1"},
read_capacity={
"mode": "Dedicated",
"dedicated": {
"node_type": "t1",
"scaling": "Manual",
"manual": {"shards": 2, "replicas": 2},
},
},
)
node_type is "b1" or "t1", where t1 carries more processing power and memory.
scaling is "Manual", and manual names the shards and replicas to provision.
Setting replicas to 0 disables the index, which is a way to cut cost while a workload
is paused without dropping the data.
The same argument moves an existing index between modes. Read capacity applies
asynchronously, so configure returns while the change is still in flight:
pc.indexes.configure("product-search", read_capacity={"mode": "OnDemand"})
Read it back on the index model. It decodes as
ReadCapacityOnDemandResponse or
ReadCapacityDedicatedResponse, and is
None when the response carries no read capacity. There is no mode attribute on
either — mode is the wire discriminator that picked the class, so branch on the type
instead:
from pinecone import ReadCapacityDedicatedResponse
rc = pc.indexes.describe("product-search").read_capacity
if rc is not None:
print(type(rc).__name__) # 'ReadCapacityOnDemandResponse'
print(rc.status.state) # 'Ready'
if isinstance(rc, ReadCapacityDedicatedResponse):
print(rc.dedicated.node_type, rc.dedicated.manual.shards)
Reaching for rc.mode raises AttributeError; the mode is the class you got back.
status.state is where an asynchronous change shows up: "Ready" when settled,
"Scaling" after a replica/shard change, "Migrating" while moving to another node
type, and "Error" with the reason in status.error_message. status.current_shards
and status.current_replicas report what is actually running, and are both None on
on-demand capacity, which has no fixed counts.
Read capacity applies to managed and BYOC indexes. Pod-based indexes size reads with
pod_type, replicas, and shards instead — see
pod-based indexes.
Check index status¶
describe returns an IndexModel with the current state:
desc = pc.indexes.describe("product-search")
print(desc.status.state) # e.g. "Ready"
print(desc.status.ready) # True when ready to accept requests
Poll manually when you passed timeout=-1 to create:
import time
while not pc.indexes.describe("product-search").status.ready:
time.sleep(5)
List indexes¶
list returns a Paginator you can iterate:
for idx in pc.indexes.list():
print(idx.name, idx.status.state)
# Just the names
names = [idx.name for idx in pc.indexes.list()]
The server returns every index in one page today, so the paginator yields once and stops. It exposes the paginator interface anyway, so a call site written against it keeps working if that changes.
Describe an index¶
schema.fields maps each field name to a typed field model, so the dimension and metric
live on the field rather than on the index:
idx = pc.indexes.describe("product-search")
print(idx.name)
print(idx.host)
for field_name, field in idx.schema.fields.items():
print(field_name, field)
print(idx.deployment.cloud)
print(idx.deployment.region)
Reach a field you declared by name — idx.schema.fields["embedding"].dimension and
.metric on a dense_vector field. idx.dimension and idx.metric are gone: reading
either raises AttributeError naming the field access to use instead, because an
index with several vector fields has no single dimension to report.
Delete an index¶
pc.indexes.delete("product-search")
delete polls until the index is gone, with no upper time bound. Pass a positive
timeout to bound the wait, or timeout=-1 to return as soon as the request is accepted
— the index is still being torn down when the call returns.
If deletion protection is enabled, the delete is refused with
ForbiddenError. Clear it first:
pc.indexes.configure("product-search", deletion_protection="disabled")
pc.indexes.delete("product-search")
See also¶
IndexModel: full index response modelPaginator:listresponse wrapperPod-based indexes: the legacy deployment type
Backups and restore: snapshot and restore a serverless index
Concepts: how deployments, schemas, and namespaces fit together