Neon (Serverless PostgreSQL)
Neon is a serverless PostgreSQL platform (acquired by Databricks May 2025 for ~$1B). Not a Python package — connect using standard PostgreSQL drivers (psycopg2, psycopg, asyncpg, SQLAlchemy). Two connection string types: pooled (-pooler in hostname, via PgBouncer) and direct. Pooled is now the default in Neon Console. Critical: always include sslmode=require. asyncpg with pooled connections requires statement_cache_size=0.
Warnings
- breaking sslmode=require is mandatory for Neon connections. Omitting it causes SSL errors or connection refusal. Neon enforces TLS.
- breaking asyncpg with Neon pooler (PgBouncer transaction mode) raises 'prepared statement asyncpg_stmt_X does not exist'. Breaks under any concurrent load.
- breaking Schema migrations (Alembic, Django migrate, Prisma Migrate) must use the DIRECT connection string, not the pooled one. PgBouncer transaction mode breaks migration session state.
- gotcha Neon computes scale to zero after inactivity. First connection after idle period has 300-500ms cold start latency. Using the pooler (PgBouncer) mitigates this — PgBouncer maintains warm connections.
- gotcha Pooled connection strings are now the default in Neon Console (since Jan 2025). If you copied a connection string before Jan 2025 it may be a direct connection without -pooler.
- gotcha SQLAlchemy with asyncpg on Neon pooler requires statement_cache_size=0 in connect_args: create_async_engine(url, connect_args={'statement_cache_size': 0})
Install
-
pip install psycopg2-binary -
pip install psycopg -
pip install asyncpg
Imports
- psycopg2 connection
import psycopg2 import os # Pooled connection (default from Neon Console) # hostname includes -pooler conn = psycopg2.connect( os.environ['DATABASE_URL'] # must include sslmode=require ) # DATABASE_URL format: # postgresql://user:pass@ep-xxx-pooler.region.aws.neon.tech/dbname?sslmode=require - asyncpg with Neon pooler
import asyncpg import os async def get_pool(): # Neon pooler uses PgBouncer in transaction mode # Must disable prepared statement cache pool = await asyncpg.create_pool( os.environ['DATABASE_URL'], statement_cache_size=0 # required for Neon pooler ) return pool - direct vs pooled URL
# Pooled — for app traffic (up to 10k concurrent connections) DATABASE_URL = 'postgresql://user:pass@ep-cool-rain-123456-pooler.us-east-2.aws.neon.tech/neondb?sslmode=require' # Direct — for migrations only (alembic, django migrate) DATABASE_URL_UNPOOLED = 'postgresql://user:pass@ep-cool-rain-123456.us-east-2.aws.neon.tech/neondb?sslmode=require'
Quickstart
# pip install psycopg2-binary python-dotenv
import psycopg2
from dotenv import load_dotenv
import os
load_dotenv()
# Use pooled URL from Neon Console (includes -pooler in hostname)
conn = psycopg2.connect(os.environ['DATABASE_URL'])
# DATABASE_URL=postgresql://user:pass@ep-xxx-pooler.region.aws.neon.tech/neondb?sslmode=require
cur = conn.cursor()
cur.execute('SELECT version()')
print(cur.fetchone())
cur.execute(
'SELECT * FROM users WHERE id = %s',
(1,)
)
rows = cur.fetchall()
cur.close()
conn.close()