Tag: postgres

  • Upper Limit for PreparedStatement Parameters

    Thanks to Lup Peng PostgreSQL JDBC Driver – Upper Limit on Parameters in PreparedStatement I was able to diagnose an upper limit:

    Caused by: java.io.IOException: Tried to send an out-of-range integer as a 2-byte value: 54838
    	at org.postgresql.core.PGStream.sendInteger2(PGStream.java:349)
    	at org.postgresql.core.v3.QueryExecutorImpl.sendParse(QueryExecutorImpl.java:1546)
    	at org.postgresql.core.v3.QueryExecutorImpl.sendOneQuery(QueryExecutorImpl.java:1871)
    	at org.postgresql.core.v3.QueryExecutorImpl.sendQuery(QueryExecutorImpl.java:1432)
    	at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:314)
    	... 96 more
    

    I had 54K parameters on my query. It turns out due to public void sendInteger2(int val) throws IOException PGStream.java has a maximum number of Short.MAX_VALUE – 32767

    Net for others hitting the same issue in different RDBMS systems:

    1. Postgres – 32767 Parameters
    2. IBM Db2 Limit – Maximum number of host variable references in a dynamic SQL statement -> 32,767 32,767 Parameters and 2,097,152 length of the text in the generated sql. Limits
    3. Derby – Storage capacity is the limit https://db.apache.org/derby/docs/10.14/ref/refderby.pdf
  • Checking fillfactor for Postgres Tables

    My teammate implemented Adjust PostgreSQL fillfactor for tables involving updates #1834, which adjusts the amount of data in each storefile.

    Per Cybertec, fillfactor is important to "INSERT operations pack table pages only to the indicated percentage; the remaining space on each page is reserved for updating rows on that page. This gives UPDATE a chance to place the updated copy of a row on the same page as the original, which is more efficient than placing it on a different page." Link as such my teammate implemented in a PR a change to adjust the fillfactor to co-locate INSERT/UPDATES into the same space.

    Query

    If you want to check your fillfactor settings, you can can check the pg_class admin table to see your settings using the following scripts:

    SELECT 
    	pc.relname as "Table Name", 
    	pc.reloptions As "Settings on Table",
    	pc.relkind as "Table Type"
    FROM pg_class AS pc
    INNER JOIN pg_namespace AS pns 
    	ON pns.oid = pc.relnamespace
    WHERE pns.nspname = 'test1234'
    	AND pc.relkind = 'r';
    

    Note

    1. relkind represents the object type char r is a table. A good reference is the following snippet: relkind char r = ordinary table, i = index, S = sequence, v = view, m = materialized view, c = composite type, t = TOAST table, f = foreign table
    2. nspname is the schema you are checking for the fillfactor values.

    Results

    You see the value:

    basic_resources,{autovacuum_vacuum_scale_factor=0.01,autovacuum_vacuum_threshold=1000,autovacuum_vacuum_cost_limit=2000,fillfactor=90},'r'
    

    References

  • Recipe: Azure Postgres with IBM FHIR Server Bulk Data

    One of the prerequisites for setting up IBM FHIR Server Bulk Data is setting up max_prepared_transactions since the IBM FHIR Server leverages Open Liberty Java Batch which uses an XA Transaction.

    If you are using Azure, here are the steps for updating your Postgres resource.

    Navigate to the Azure Portal

    Find your Postgres resource

    Update your Server Parameters max_prepared_transactions to 200 (anything non-zero is recommended to enable XA)

    Click Save

    Click Overview

    Click Restart

    Click On Activity Log

    Wait until Postgres is restarted

    Restart your IBM FHIR Server, and you are ready to use the Bulk Data feature.

    If you don’t do the setup, you’ll see a log like the following:

    [9/2/21, 1:49:38:257 UTC] [step1 partition0] com.ibm.fhir.bulkdata.jbatch.listener.StepChunkListener        StepChunkListener: job[bulkexportfastjob/8/15] --- javax.transaction.RollbackException
                                     com.ibm.jbatch.container.exception.TransactionManagementException: javax.transaction.RollbackException
            at com.ibm.jbatch.container.transaction.impl.JTAUserTransactionAdapter.commit(JTAUserTransactionAdapter.java:108)
            at com.ibm.jbatch.container.controller.impl.ChunkStepControllerImpl.invokeChunk(ChunkStepControllerImpl.java:656)
            at com.ibm.jbatch.container.controller.impl.ChunkStepControllerImpl.invokeCoreStep(ChunkStepControllerImpl.java:795)
            at com.ibm.jbatch.container.controller.impl.BaseStepControllerImpl.execute(BaseStepControllerImpl.java:295)
            at com.ibm.jbatch.container.controller.impl.ExecutionTransitioner.doExecutionLoop(ExecutionTransitioner.java:118)
            at com.ibm.jbatch.container.controller.impl.WorkUnitThreadControllerImpl.executeCoreTransitionLoop(WorkUnitThreadControllerImpl.java:96)
            at com.ibm.jbatch.container.controller.impl.WorkUnitThreadControllerImpl.executeWorkUnit(WorkUnitThreadControllerImpl.java:178)
            at com.ibm.jbatch.container.controller.impl.WorkUnitThreadControllerImpl$AbstractControllerHelper.runExecutionOnThread(WorkUnitThreadControllerImpl.java:503)
            at com.ibm.jbatch.container.controller.impl.WorkUnitThreadControllerImpl.runExecutionOnThread(WorkUnitThreadControllerImpl.java:92)
            at com.ibm.jbatch.container.util.BatchWorkUnit.run(BatchWorkUnit.java:113)
            at com.ibm.ws.context.service.serializable.ContextualRunnable.run(ContextualRunnable.java:79)
            at com.ibm.ws.threading.internal.ExecutorServiceImpl$RunnableWrapper.run(ExecutorServiceImpl.java:238)
            at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
            at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
            at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
            at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
            at java.base/java.lang.Thread.run(Thread.java:866)
    Caused by: javax.transaction.RollbackException
            at com.ibm.tx.jta.impl.TransactionImpl.stage3CommitProcessing(TransactionImpl.java:978)
            at com.ibm.tx.jta.impl.TransactionImpl.processCommit(TransactionImpl.java:778)
            at com.ibm.tx.jta.impl.TransactionImpl.commit(TransactionImpl.java:711)
            at com.ibm.tx.jta.impl.TranManagerImpl.commit(TranManagerImpl.java:165)
            at com.ibm.tx.jta.impl.TranManagerSet.commit(TranManagerSet.java:113)
            at com.ibm.tx.jta.impl.UserTransactionImpl.commit(UserTransactionImpl.java:162)
            at com.ibm.tx.jta.embeddable.impl.EmbeddableUserTransactionImpl.commit(EmbeddableUserTransactionImpl.java:101)
            at com.ibm.ws.transaction.services.UserTransactionService.commit(UserTransactionService.java:72)
            at com.ibm.jbatch.container.transaction.impl.JTAUserTransactionAdapter.commit(JTAUserTransactionAdapter.java:101)

    Go back and enable max_prepared_transactions

    References

    Azure Docs

  • Postgres and Vacuum with the IBM FHIR Server: Debugging Details

    The IBM FHIR Server stores resources in the PostgreSQL database and uses a relational model to store historical FHIR Resource and enable search on the latest FHIR resources. The resource data is spread in a relational model that is occasionally tweaked in order to improve search or optimize the retrieval using the relational model.

    In the IBM FHIR Server Performance Guide, the guide outlines some important alterations to the tables that facilitate an optimized Postgres instance. The guide suggests altering, per your providers recommendation, autovacuum_vacuum_cost_limit, autovacuum_vacuum_scale_factor and autovacuum_vacuum_threshold in order to optimize the VACUUM process. With the IBM FHIR Server fhir-persistence-schema-cli, autovacuum_vacuum_scale_factor is not automatically configured, and not recommended on Databases for Postgres on IBM Cloud.

    As Postgres uses "multi-version concurrency control (MVCC) to ensure that data remains consistent and accessible in high-concurrency environments", each transaction runs on a snapshot, and needs to be reconciled so dead_rows are removed – vacuumed. The VACUUM process manages dead rows and disk usage (reuse). The VACUUM process (autovacuum) frequently runs – gathering statistics and reconciling the maintenance of the table statitstics and data.

    To check for tables that need vacuuming:

     SELECT relname AS "table_name",
            n_tup_ins AS "inserts",
            n_tup_upd AS "updates",
            n_tup_del AS "deletes",
            n_live_tup AS "live_tuples",
            n_dead_tup AS "dead_tuples"
       FROM pg_stat_user_tables
      WHERE schemaname = 'fhirdata'
        AND (relname = 'logical_resources' OR relname LIKE '%_values')
        AND n_dead_tup > 0;
    

    Then a database administrator runs – VACUUM FULL FHIRDATA.PROCEDURE_RESOURCE_TOKEN_REFS; to execute a vacuum, which runs in the background.

    While the VACUUM process is running, the pg_stat_progress_vacuum view can be queried to see worker process.

    SELECT * 
    FROM pg_stat_progress_vacuum;
    

    If you need to update a specific tables settings, you can run with --vacuum-table-name.

    java -jar ./fhir-persistence-schema-${VERSION}-cli.jar \
    --db-type postgresql --prop db.host=localhost --prop db.port=5432 \
    --prop db.database=fhirdb --schema-name fhirdata \
    --prop user=fhiradmin --prop password=passw0rd \
    --update-vacuum --vacuum-cost-limit 2000 --vacuum-threshold 1000 \
    --vacuum-scale-factor 0.01 --vacuum-table-name LOGICAL_RESOURCES
    

    To update all tables in a schema, you can run without the table parameter. If you omit any value, the defaults are picked as described in the Performance guide.

    If you hit a lock (ShareUpdateExclusiveLock), the VACUUM worker process is currently churning on the table, and the ALTER statement is waiting.

    • wait_type = Lock relation Waiting to acquire a lock on a relation.
    • wait_lock_type – ShareUpdateExclusiveLock Acquired by VACUUM and conflicts with ALTER

    CHeck for the Blocking PID, and grab the blocking_pid.

     -- list bad connections
       SELECT blocked_locks.pid     AS blocked_pid,
             blocked_activity.usename  AS blocked_user,
             blocking_locks.pid     AS blocking_pid,
             blocking_activity.usename AS blocking_user,
             blocked_activity.query    AS blocked_statement,
             blocking_activity.query   AS current_statement_in_blocking_process,
             blocked_activity.application_name AS blocked_application,
             blocking_activity.application_name AS blocking_application
       FROM  pg_catalog.pg_locks         blocked_locks
        JOIN pg_catalog.pg_stat_activity blocked_activity  ON blocked_activity.pid = blocked_locks.pid
        JOIN pg_catalog.pg_locks         blocking_locks 
            ON blocking_locks.locktype = blocked_locks.locktype
            AND blocking_locks.DATABASE IS NOT DISTINCT FROM blocked_locks.DATABASE
            AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation
            AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page
            AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple
            AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid
            AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid
            AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid
            AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid
            AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid
            AND blocking_locks.pid != blocked_locks.pid
        JOIN pg_catalog.pg_stat_activity blocking_activity ON blocking_activity.pid = blocking_locks.pid
       WHERE NOT blocked_locks.GRANTED and blocked_activity.usename = 'fhirserver'
    

    Try canceling the PID, SELECT pg_cancel_backend(205384);

    Else, cancel the current Transaction the blocked pid:

    -- cancel the blocking trannsaction/pid (hard stop)
    SELECT pg_cancel_backend(blocked_locks.pid)     AS blocked_pid,
             blocked_activity.usename  AS blocked_user,
             blocking_locks.pid     AS blocking_pid,
             blocking_activity.usename AS blocking_user,
             blocked_activity.query    AS blocked_statement,
             blocking_activity.query   AS current_statement_in_blocking_process,
             blocked_activity.application_name AS blocked_application,
             blocking_activity.application_name AS blocking_application
       FROM  pg_catalog.pg_locks         blocked_locks
        JOIN pg_catalog.pg_stat_activity blocked_activity  ON blocked_activity.pid = blocked_locks.pid
        JOIN pg_catalog.pg_locks         blocking_locks 
            ON blocking_locks.locktype = blocked_locks.locktype
            AND blocking_locks.DATABASE IS NOT DISTINCT FROM blocked_locks.DATABASE
            AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation
            AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page
            AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple
            AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid
            AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid
            AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid
            AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid
            AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid
            AND blocking_locks.pid != blocked_locks.pid
        JOIN pg_catalog.pg_stat_activity blocking_activity ON blocking_activity.pid = blocking_locks.pid
       WHERE NOT blocked_locks.GRANTED and blocked_activity.usename = 'fhirserver'
    

    Now, wait until the VACUUM finishes, and then execute a new ALTER.

    You should be all set at this point.

    Check wait_type

    -- Check Wait Type
    SELECT 
        waiting.locktype           AS waiting_locktype,
        waiting.relation::regclass AS waiting_table,
        waiting_stm.query          AS waiting_query,
        waiting.mode               AS waiting_mode,
        waiting.pid                AS waiting_pid,
        other.locktype             AS other_locktype,
        other.relation::regclass   AS other_table,
        other_stm.query            AS other_query,
        other.mode                 AS other_mode,
        other.pid                  AS other_pid,
        other.granted              AS other_granted
    FROM
        pg_catalog.pg_locks AS waiting
    JOIN
        pg_catalog.pg_stat_activity AS waiting_stm
        ON (
            waiting_stm.pid = waiting.pid
        )
    JOIN
        pg_catalog.pg_locks AS other
        ON (
            (
                waiting."database" = other."database"
            AND waiting.relation  = other.relation
            )
            OR waiting.transactionid = other.transactionid
        )
    JOIN
        pg_catalog.pg_stat_activity AS other_stm
        ON (
            other_stm.pid = other.pid
        )
    WHERE
        NOT waiting.granted
    AND
        waiting.pid <> other.pid   
    
    SELECT
      schemaname, relname,
      last_vacuum, last_autovacuum,
      vacuum_count, autovacuum_count, *
    FROM pg_stat_user_tables
    WHERE schemaname = 'fhirdata' AND relname = 'observation_date_values';
    

    Check with locks

    SELECT now()::time, query, backend_start, xact_start, query_start,
             state_change, state,
             now()::time - state_change::time AS locked_since,
             pid, wait_event_type, wait_event
      FROM pg_stat_activity
      WHERE wait_event_type IS NOT NULL and wait_event_type = 'Lock'
    ORDER BY locked_since DESC;
    

    Check a PID

    SELECT a.usename, a.application_name, a.datname, a.query,
             l.granted, l.mode, transactionid
        FROM pg_locks l
        JOIN pg_stat_activity a ON a.pid = l.pid
        WHERE granted = false AND a.pid = 327589;
    

    Check a tansaction

    SELECT a.usename, a.application_name, a.datname, a.query,
            l.granted, l.mode, transactionid,
            now()::time - a.state_change::time AS acquired_since,
            a.pid
       FROM pg_locks l
       JOIN pg_stat_activity a ON a.pid = l.pid
       WHERE granted = true AND transactionid = 3031;
    

    Reference

  • Determining Function Signatures with Postgres

    I had duplicate Postgres base signatures, and I needed to diagnose the reasons why it was failing to update. The following are helpful for diagnosing functions:

    References

    StackOverflow DROP FUNCTION without knowing the number/type of parameters?

    StackOverflow: pg_proc

  • IBM FHIR Server – Debugging Tips

    Checking a Postgres Function Definition

    I needed to verify my postgres function.

    1. Shows the contents of the functions in the schema.
    SELECT pg_get_functiondef(f.oid)
    FROM pg_catalog.pg_proc f
    INNER JOIN pg_catalog.pg_namespace n ON (f.pronamespace = n.oid)
    WHERE n.nspname = 'fhirdata';
    
    1. Show all the details of the functions in the schema.
    SELECT *
    FROM pg_catalog.pg_proc f
    INNER JOIN pg_catalog.pg_namespace n ON (f.pronamespace = n.oid)
    WHERE n.nspname = 'fhirdata';
    

    Tracing Bulk Data with Cloud Object Storage

    I wanted to figure out why my code was failing to connect to the backend S3 bucket.

    I used the environment variable in my docker image called TRACE_SPEC. TRACE_SPEC is loaded into the logging as the traceSpecification.

    I set this to *=info:com.ibm.cloud.*=FINEST which spits out great detail to S3 using the IBM COS SDK.

    The output looks like:

    With this level of trace, you can really dive into the connection, and determine what is going on.

    Note, if you want the whole picture of what is happening with COS and JavaBatch and BulkData, use the following:

    *=info:com.ibm.fhir.*=finer:RRA=all:WAS.j2c=all:com.ibm.ws.jdbc.*=all:Transaction=all:ConnLeakLogic=all:Transaction=all:com.ibm.ws.transaction.services.WebAppTransactionCollaboratorImpl=all:RRA=all:com.ibm.cloud.*=FINEST