JDBC připojení do PostgreSQL přes Unix socket

Připojovat se z aplikace do databáze přes její síťové rozhraní je pro většinu lidí taková samozřejmost, že je ani nenapadne, že by to mohlo být jinak. Přitom s sebou tento způsob nese několik nevýhod, které ve velkém procentu deployment scénářů trpíme zcela zbytečně. Předně je to overhead průchodu síťovým stackem, pokud aplikace běží na stejném stroji jako DB. V takovém scénáři je možné použít místo připojení přes localhost nějakou jinou metodu IPC, v unixovém světě klasicky socket.

Samotný výkon se nemusí zdát jako tak závažný důvod, ale jednak je ta síťová vrstva opravdu zcela zbytečná a jednak s sebou nese další nepřímé problémy. Například může při chybě konfigurace vystavit DB na otevřenou síť, kde je snadno napadnutelná. A protože síťové rozhraní nerozlišuje mezi lokálním a vzdáleným přístupem, je nutné i pro lokální přístup autentizace aplikace (nejčastěji jménem a heslem), přestože lokálně takovou autentizaci dokáže dělat lépe operační systém a to navíc bez nějakého pitomého hesla.

Kdo někdy na linuxu používal psql ví, jaká je to pohoda. Prostě v shellu napíšete

psql

a jste přihlášený jako lokální uživatel k jeho výchozí databázi. Chcete-li něco udělat jako privilegovaný databázový uživatel, použijete standardní prostředek OS

sudo -u postgres psql

a jste přihlášeni k databázi jako její správce. Žádná hesla, žádná speciální databázová udělátka.

Ale pak je tu bohužel Java. Unixové sockety nativně neumí a proto to pak JDBC drivery mají každý pes jiná ves, pokud vůbec na ty sockety myslí. Tak jsem dlouho žil s tím, že právě JDBC driver PostgreSQL sockety vůbec neumí, ale nedávno jsem k velké radosti zjistil, že od verze 9.4podporu přidali pomocí url parametrů socketFactory a socketFactoryArg.

Podpora není psaná pro nějakou konkrétní knihovnu přidávající do Java Unix sockety, ale používá jako hlavní rozhraní standardní javovský javax.net.SocketFactory jehož implementací je možné adaptovat libovolnou socketovou knihovnu.

Ono ale bohužel ani ty socketové knihovny nejsou žádná sláva. Jedna z těch, jejíž adaptace by byla nejjednodušší, junixsocket, vypadá mrtvá a navíc se mi nechtělo dělat si separátní projekt pro ten adaptér. Naštěstí je junixsocket forknutý a mimo pár vylepšení již obsahuje právě i adaptaci přímo pro PostgreSQL JDBC driver. (Pozor: neberte to jako doporučení tohoto forku, nemám žádný důvod mu nějak zásadně věřit. Při pochybnostech jako vždy kompilujte sami.)

Tomcat JDBC pool

Protože jsem si chtěl udělat pool konexí přímo v Tomcatu (naštěstí už má Tomcat vlastní implementaci poolu) a sdílet ho přes JNDI mezi více aplikacemi, je použití ještě trochu složitější, než jen přidání závislostí do Mavenu:

  1. Stáhnout jary junix-socket-common a junixsocket-common a native-lib-loader do Tomcatího libu
  2. Přidat do conf/server.xml globální konfiguraci:
    <GlobalNamingResources>
    <Resource type="javax.sql.DataSource"
      name="jdbc/ServerDB"
      description="Default database for general use."
      factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
      driverClassName="org.postgresql.Driver"
      url="jdbc:postgresql://localhost/database?socketFactory=org.newsclub.net.unix.socketfactory.PostgresqlAFUNIXSocketFactory&amp;socketFactoryArg=/var/run/postgresql/.s.PGSQL.5432"
      />
    </GlobalNamingResources>
    
    • localhost v JDBC url nemá žádný význam a může být cokoli, jen to není možné kvůli syntaxi úplně vypustit
    • socketFactoryArg odkazuje na onen kýžený socket. Uvedl jsem jeho typické umístění, ale je možné si to ověřit pomocí grep unix_socket_directories /etc/postgresql/9.6/main/postgresql.conf
  3. Přidat do conf/context.xml link
    <ResourceLink type="javax.sql.DataSource"
      name="jdbc/ServerAppDB"
      global="jdbc/ServerDB"/>
    
  4. Přidat do web.xml aplikace resource ref
    <resource-ref>
      <description>DB Connection</description>
      <res-ref-name>jdbc/ServerAppDB</res-ref-name>
      <res-type>javax.sql.DataSource</res-type>
      <res-auth>Container</res-auth>
    </resource-ref>
    
  5. Ve Spring aplikaci použít data source pomocí nastavení v application.properties
    spring.datasource.jndi-name=jdbc/ServerAppDB