Question

application_name is a PostgreSQL specific connection parameter which each client can (and should) set when connecting. It later on helps DBAs and operations staff correlate misbehaving queries with a particular bit of application code.

This SO question explains how to set application_name using SQLAlchemy, but unfortunately, it doesn't work on Heroku, because setting application_name was added on the PostgreSQL 8.5 branch (which was only released as 9.0) whereas the version of PostgreSQL client library (libpq) installed on Heroku's dynos was built from PostgreSQL 8.4.9.

What, if anything, can be done to set application_name on Heroku?

Was it helpful?

Solution

To the best of my knowledge, the only solution is to vendor a newer version of libpq into your slug.

The packages I'm targeting are libpq5 and libpq-dev, built from the source package postgresql-9.1. To save yourself a lot of work, and if you trust my binaries, you can just skip to step 8 and use the pre-built binary libpq which I made. No no, don't thank me.

  1. Spin up a new VM with vanilla 12.04
  2. sudo apt-get update
  3. apt-get source postgresql-9.1 (you will probably need to sudo apt-get install dpkg-src first)
  4. In the extracted sources, look at the rules file (postgresql-9.1-9.1.8/debian/rules, if precise-updates is still at PostgreSQL 9.1.8).
  5. From the rules file, I believe the configuration options we should use for building our libpq are:

    LDFLAGS="-Wl,--as-needed -Wl,-z,now" \
    CFLAGS="-fPIC -DLINUX_OOM_ADJ=0" \
    ./configure --prefix=/app/vendor \
      --enable-integer-datetimes \
      --enable-thread-safety \
      --enable-debug \
      --disable-rpath \
      --with-gnu-ld \
      --with-pgport=5432 \
      --with-system-tzdata=/usr/share/zoneinfo \
      --without-tcl \
      --without-perl \
      --without-python \
      --with-krb5 \
      --with-gssapi \
      --with-openssl \
      --with-libxml \
      --with-libxslt \
      --with-ldap
    

    Note that I removed/negated --enable-nls --with-ossp-uuid --with-tcl --with-perl --without-python --with-pam, because on the Heroku dyno they lead to configuration/build failure. I don't need any of them, I'm not sure if they affect libpq5 at all, I doubt they're very needed on a dyno, and if you feel otherwise, have fun fixing that and my hat's off to you good sir.

  6. make && make install

  7. Now all of postgresql is installed to /vendor. The files we need to take are specified in postgresql-9.1-9.1.8/debian/libpq5.install and postgresql-9.1-9.1.8/debian/libpq-dev.install. Alas, because we aren't following debian's installation layout, we'll need to adapt these lists to find the files we really need. I came up with this list:

    tar c \
        include/postgresql/internal/* \
        include/libpq-fe.h \
        include/libpq-events.h \
        include/libpq/libpq-fs.h \
        include/pg_config*.h \
        include/postgres_ext.h \
        include/postgresql/9.1/server/catalog/pg_type.h \
        include/postgresql/9.1/server/catalog/genbki.h \
        include/postgresql/9.1/server/nodes/nodes.h \
        include/postgresql/9.1/server/utils/elog.h \
        include/postgresql/9.1/server/utils/errcodes.h \
        include/postgresql/9.1/server/utils/palloc.h \
        include/postgresql/9.1/server/c.h \
        include/postgresql/9.1/server/pg_config.h \
        include/postgresql/9.1/server/pg_config_manual.h \
        include/postgresql/9.1/server/pg_config_os.h \
        include/postgresql/9.1/server/port.h \
        include/postgresql/9.1/server/pg_trace.h \
        include/postgresql/9.1/server/postgres.h \
        include/postgresql/9.1/server/postgres_fe.h \
        include/postgresql/9.1/server/postgres_ext.h \
        include/postgresql/9.1/server/mb/pg_wchar.h \
        lib/libpq.so \
        bin/pg_config \
        lib/libpq.so.5* \
        | gzip --best > /tmp/libpq-5.4_9.1.8.tar.gz
    

    Note that I had to remove the manpage (pg_config.1.gz) and localization messages (LC_MESSAGES/pg_config-9.1.mo LC_MESSAGES/libpq*.mo under share/locale), which, for some reason, weren't built on my dyno. I managed to link Python's psycopg2 against this list of files and use it successfully, but you can figure this out and fix it as well if you feel it's important.

  8. Our troubles are practically over now. You should take the resulting libpq-5.4_9.1.8.tar.gz file and put it somewhere that will be accessibly to your dyno's buildpack (like S3), then vendor it into the slug. If you're not sure how to do that, my suggestion (for the current Python buildpack), is that you create the file 'bin/pre_compile' in your slug, and put this into it:

    #!/bin/sh
    
    vendor() {
        printf "       "
        echo -n "$1 "
        curl --location --fail --silent $1 | tar -zx -C vendor && echo OK
    }
    
    echo "       (lines by the app will be prefixed with ---->>)"
    
    echo "---->> Fetching vendored binaries"
    mkdir -p vendor
    vendor "http://fusic.s3.amazonaws.com/executables/heroku/libpq-5.4_9.1.8.tar.gz"
    
    echo "---->> Injecting shell environment"
    # Even though Heroku's Python buildback has a hook mechanism, hooks can't
    #  change the buildpack's process environment (they are spawned, not
    #  sourced). This makes it possible to write hooks in any language, but
    #  makes vendoring stuff that should be linked against libraries installed
    #  during the rest of the build process harder. The kludge below hijacks
    #  $BIN_DIR/steps/pylibmc, which is sourced after this code and before pip
    #  is ran. Puked a little in my mouth.
    # See also: https://twitter.com/aknin/status/290832546260979712
    cat > $BIN_DIR/steps/pylibmc << EOF
    echo "---->> Injected environment hook executing"
    source .profile.d/activate_vendor_tree.sh
    EOF
    

    and also create .profile.d/activate_vendor_tree.sh and put this into it:

    #!sh
    
    export PATH="/app/vendor/bin:$PATH"
    export CFLAGS="-I/app/vendor/include"
    export LDFLAGS="-L/app/vendor/lib -R/app/vendor/lib"
    export LD_LIBRARY_PATH="/app/vendor/lib:$LD_LIBRARY_PATH"
    

    When compiling your slug, the pre-hook we installed kicks in, downloads our backported libpq and vendors it into the slug. If you're using the Python buildpack or a similarly limited buildpack (see detailed comment above), the ugly but necessary environment injection hook kicks in, and your requirements will be compiled against the new libpq. When you run your app, Heroku conveniently source everything in .profile.d before executing your code, again activating the new libpq. Oh yeah.

  9. At long last, you're done. Deploy your app to Heroku, and your dependencies should link against the new libpq. If your app was already deployed, you'll probably need to clear your slug's cache dir before Heroku's slug compiler will try to reinstall these requirements (so they can be linked to your new libpq; this is a thorny topic, which is outside the scope of this tutorial; the only method I know is to stick something in the pre_compile that will delete the cache). Ugh.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top