Documentation
¶
Overview ¶
Package pgproxy is a Postgres wire-protocol router. Clients connect with database=dbname@branch; the proxy reads the startup message, resolves the branch to its backend address, rewrites the database param back to the real dbname, replays the startup to the branch backend, and then relays bytes transparently in both directions (SCRAM auth flows untouched).
While relaying the backend's startup response the proxy watches the message frames (never altering them) for BackendKeyData, so a later CancelRequest carrying that key can be forwarded to the right backend.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type BranchRefresher ¶
type BranchRefresher interface {
RefreshBranch(ctx context.Context, name string) (addr string, err error)
}
BranchRefresher is optionally implemented by a BranchResolver that can re-read a branch's address from the runtime. When the dial to the resolved address fails, the proxy asks it once and, if the address moved (a pod that came back with a new IP, a container re-published on another port), dials the new address instead of refusing until the next reconcile pass repairs the registry.
type BranchResolver ¶
BranchResolver maps a branch name to the "host:port" address of its Postgres instance. Implementations must only resolve branches that can accept connections.
type Proxy ¶
type Proxy struct {
Resolver BranchResolver
// DialTimeout bounds the backend dial (and a forwarded cancel request's
// exchange with the backend). Defaults to 5s.
DialTimeout time.Duration
// FirstByteTimeout bounds how long a new connection may stay silent before
// sending its first byte. Real clients write immediately after connecting,
// so this is short: it keeps idle sockets from holding a connection slot
// for the whole StartupTimeout. Defaults to 2s.
FirstByteTimeout time.Duration
// StartupTimeout bounds the client's startup packets (SSL/GSS negotiation,
// the TLS handshake and the StartupMessage), measured from accept. A client
// that dribbles bytes is dropped after this. Defaults to 10s.
StartupTimeout time.Duration
// AuthTimeout bounds the rest of the startup exchange: from routing the
// StartupMessage until the backend sends its first ReadyForQuery, which
// covers the whole authentication exchange. The proxy enforces it itself
// instead of relying on the branch's authentication_timeout. Defaults to
// 30s.
AuthTimeout time.Duration
// MaxConns caps the number of connections handled concurrently. When the
// cap is reached, further accepts are refused fast (connection closed)
// rather than queued unbounded. Defaults to 256; a negative value disables
// the cap.
MaxConns int
// MaxStartupsPerIP caps how many connections from one client IP may be in
// the startup phase at once (anything before the backend's first
// ReadyForQuery: TLS, StartupMessage, authentication, and cancel
// requests). Authenticated sessions do not count, so one host running many
// real sessions is not limited, but one address cannot fill MaxConns with
// connections that never authenticate. Over the cap, new connections are
// refused fast. Defaults to 64; a negative value disables the cap.
MaxStartupsPerIP int
// IdleTimeout closes an authenticated session that has seen no bytes in
// either direction for this long, reclaiming abandoned-but-open
// connections. It bounds reads and writes, so a peer that stops reading
// cannot pin the session either. Defaults to 15m.
IdleTimeout time.Duration
// TLSConfig, when set, makes the proxy answer SSLRequest with 'S' and
// upgrade the client connection via a server-side TLS handshake before
// the startup message. When nil (default) SSLRequest is answered 'N' and
// the session stays plaintext. Backend dials are always plaintext
// (branches are local/cluster-internal).
TLSConfig *tls.Config
// contains filtered or unexported fields
}
func New ¶
func New(r BranchResolver) *Proxy
func (*Proxy) Serve ¶
Serve accepts connections until ctx is cancelled (which closes the listener) or Accept fails with a non-temporary error. Temporary Accept errors (EMFILE, ENFILE, ENOBUFS, ...) are retried with backoff, like net/http, so running out of file descriptors under a connection flood does not stop the router. A non-temporary error means the listener itself is broken and is returned. Each connection is handled in its own goroutine.
type RegistryResolver ¶
type RegistryResolver struct {
Reg *registry.Registry
// Refresh, when set, re-reads a ready branch's address from the runtime
// and records it if it moved (branchd wires engine.RefreshBranchEndpoint).
// nil disables the refresh after a failed dial.
Refresh func(ctx context.Context, name string) (string, error)
}
RegistryResolver adapts the registry: only ready branches resolve.
func (*RegistryResolver) RefreshBranch ¶
RefreshBranch implements BranchRefresher through the Refresh hook.
func (*RegistryResolver) ResolveBranch ¶
func (r *RegistryResolver) ResolveBranch(name string) (string, error)