Skip to content

RuntimeError: no decoder for OID 3910 #278

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
adamrothman opened this issue Apr 6, 2018 · 1 comment · Fixed by #301
Closed

RuntimeError: no decoder for OID 3910 #278

adamrothman opened this issue Apr 6, 2018 · 1 comment · Fixed by #301

Comments

@adamrothman
Copy link

adamrothman commented Apr 6, 2018

  • asyncpg version 0.15.0
  • PostgreSQL version 9.6.3 (prod), 10.1 (dev)
  • Do you use a PostgreSQL SaaS? If so, which? Can you reproduce the issue with a local PostgreSQL install? Yes, AWS Aurora (PostgreSQL). I can also reproduce it with my local PostgreSQL Docker container.
  • Python version 3.6.5.
  • Platform Docker container based on latest python:3.6 running on AWS ECS (prod); local venv install on macOS (dev).
  • Do you use pgbouncer? No.
  • Did you install asyncpg with pip? Yes.
  • If you built asyncpg locally, which version of Cython did you use? N/A
  • Can the issue be reproduced under both asyncio and
    uvloop?
    We use uvloop, definitely happening there. Haven't tried vanilla asyncio.

What's happening

We are experiencing intermittent RuntimeErrors with the message no decoder for OID 3910. This appears to be similar to #122, #133, and #241.

Per Postgres' pg_type.h, 3910 is the OID for the type tstzrange.

It's not happening constantly – only a couple of times in the last 24 hours – but it is happening. I've provided a bunch of detail below but please let me know if I can add anything else to help track this down.

Environment details

The schema we are using is as follows:

CREATE TYPE region_type AS ENUM (
    'city',
    'college',
    'event',
    'high school',
    'neighborhood',
    'test',
    'university'
);

CREATE TABLE regions (
    id              text PRIMARY KEY,
    bounds          geography(Polygon) NOT NULL,

    type            region_type NOT NULL,

    detailed_name   text NOT NULL,
    display_name    text,
    short_name      text,

    active          tstzrange,

    version         integer NOT NULL DEFAULT 0,
    modified        timestamp with time zone NOT NULL DEFAULT now()
);

CREATE INDEX regions_active_idx ON regions USING GIST (active);
CREATE INDEX regions_bounds_idx ON regions USING GIST (bounds);

And the query that's raising this error every once in a while:

SELECT *, ST_Distance(bounds, $1::geography) AS edge_distance
FROM regions
WHERE (active IS NULL OR now() <@ active) AND ST_DWithin(bounds, $1::geography, $2)
ORDER BY edge_distance;

In this query, $1 is a point representing the user's reported location and $2 is a "fudge factor" in meters.

Usage notes

All of our application's interactions with Postgres are wrapped in transactions using this async context manager:

class PostgresTransactionManager:

    def __init__(self, *, write: bool = False, **kwargs) -> None:
        self.write = write
        self.transaction_kwargs = kwargs

    async def __aenter__(self) -> asyncpg.Connection:
        # Get a shared singleton asyncpg.pool.Pool; write kwarg controls the pool's host (master or read replica)
        pool = await Postgres.get_pool(write=self.write)

        self.connection_manager = pool.acquire()
        connection = await self.connection_manager.__aenter__()

        self.transaction = connection.transaction(**self.transaction_kwargs)
        await self.transaction.__aenter__()

        return connection

    async def __aexit__(self, exc_type, exc, tb):
        await self.transaction.__aexit__(exc_type, exc, tb)
        await self.connection_manager.__aexit__(exc_type, exc, tb)

In practice it looks like this:

async with PostgresTransactionManager() as pg:
    records = await pg.fetch(query, *args)

The method below, which calls set_type_codec a few times, is passed as the init kwarg to asyncpg.create_pool(). This is called lazily by our Postgres class, the first time .get_pool() is called. Subsequent calls to Postgres.get_pool() return this singleton pool.

async def configure_connection(connection):
    await connection.set_type_codec(
        'geography',
        schema='public',  # PostGIS types are installed into public
        encoder=encode_geometry,
        decoder=decode_geometry,
        format='binary',
    )
    await connection.set_type_codec(
        'geometry',
        schema='public',
        encoder=encode_geometry,
        decoder=decode_geometry,
        format='binary',
    )
    await connection.set_type_codec(
        'json',
        schema='pg_catalog',  # Built-in types live in pg_catalog
        encoder=json.dumps,
        decoder=json.loads,
    )
    await connection.set_type_codec(
        'jsonb',
        schema='pg_catalog',
        encoder=json.dumps,
        decoder=json.loads,
    )
@elprans
Copy link
Member

elprans commented Apr 6, 2018

It appears that the codec cache is still racy :-( I'll look into this soon.

elprans added a commit that referenced this issue May 31, 2018
Current global type codec cache works poorly in a pooled environment.
The global nature of the cache makes introspection/cache bust race a
frequent occurrence.  Additionally, busting the codec cache in _all_
connections only because one of them had reconfigured a codec seems
wrong.

The fix is simple: every connection now has its own codec cache.  The
downside is that there will be more introspection queries on fresh
connections, but given that most connections in the field are pooled,
the robustness gains are more important.

Fixes: #278
elprans added a commit that referenced this issue May 31, 2018
Current global type codec cache works poorly in a pooled environment.
The global nature of the cache makes introspection/cache bust race a
frequent occurrence.  Additionally, busting the codec cache in _all_
connections only because one of them had reconfigured a codec seems
wrong.

The fix is simple: every connection now has its own codec cache.  The
downside is that there will be more introspection queries on fresh
connections, but given that most connections in the field are pooled,
the robustness gains are more important.

Fixes: #278
elprans added a commit that referenced this issue Jun 1, 2018
Current global type codec cache works poorly in a pooled environment.
The global nature of the cache makes introspection/cache bust race a
frequent occurrence.  Additionally, busting the codec cache in _all_
connections only because one of them had reconfigured a codec seems
wrong.

The fix is simple: every connection now has its own codec cache.  The
downside is that there will be more introspection queries on fresh
connections, but given that most connections in the field are pooled,
the robustness gains are more important.

Fixes: #278
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants