Benchmark concepts

This notebook goes through all concepts and runs the query using EXPLAIN ANALYZE. This is useful for informing how long the queries will take and for benchmarking performance improvements.


In [68]:
import os
import re
import psycopg2
import getpass

from collections import OrderedDict

# database config
sqluser=getpass.getuser()
# keep sqlpass blank if using peer authentication
sqlpass=''
# database
sqldb='mimic'
sqlschema='public,mimiciii'

query_schema = 'set search_path to ' + sqlschema + ';'

In [32]:
if (not sqlpass) & (sqlpass != ''):
    con = psycopg2.connect(user=sqluser, password=sqlpass, database=sqldb)
else:
    con = psycopg2.connect(user=sqluser, database=sqldb)
print('Connected!')


Connected!

In [24]:
# function to read a single script
def read_script(base_path, script_name):
    query = ''
    with open(os.path.join(base_path,script_name)) as f:
        for line in f.readlines():
            line = line.lstrip(' ').rstrip(' ')
            if len(line)<1:
                continue
            elif len(line)<2:
                query += line
            else:
                # ignore comments
                if '--' in line:
                    line = line[0:line.index('--')]
                query += line
    # replace double newlines with single newline
    query = query.replace('\n\n','\n')
    return query

In [89]:
def extract_drop_line(query):
    # hack out the drop materialized view/drop table statement
    query_drop = []
    if 'drop materialized view ' in query.lower():
        query_drop.extend(re.findall('drop materialized view [A-z0-9_ ]+;\n',query,re.I))
    if 'drop table ' in query.lower():
        query_drop.extend(re.findall('drop table [A-z0-9_ ]+;\n',query,re.I))

    if not query_drop:
        query_drop = ''
    elif len(query_drop)==1:
        query = query.replace(query_drop[0], '')
        query = [query]
    else:
        # have multiple drop/create statements
        query_parts = list() #query.split(query_drop[1])[0]

        for i, q in enumerate(query_drop):
            # get first part of query
            query_split = query.split(q)
            query_parts.append(query_split[0])
            query = query_split[1]

        # now append the final table created in the full query
        query_parts.append(query)
        # remove the first element
        query_parts = query_parts[1:]
        
        query = query_parts
    
    return query, query_drop

In [96]:
# benchmark query
def benchmark_query(con, query, query_schema=query_schema, query_drop=query_drop, parallel_workers=None):
    cur = con.cursor()
    cur.execute(query_schema)
    if parallel_workers:
        cur.execute('SET max_parallel_workers_per_gather TO {};'.format(parallel_workers))
    else:
        cur.execute('SET max_parallel_workers_per_gather TO DEFAULT;')
    cur.execute(query_drop)
    cur.execute('explain analyze ' + query)
    result = cur.fetchall()
    cur.execute('commit;')
    cur.close()

    query_plan = [item[0] for item in result]
    time = float(query_plan[-1].replace('Execution time: ', '').replace(' ms', ''))
    
    return time, query_plan

In [86]:
# example on a single concept
base_path = '/home/alistairewj/git/mimic-code/concepts'
script_name = 'demographics/icustay_detail.sql'

print(script_name, end='...')

# read the script's query
query = read_script(base_path, script_name)
# returns a list of queries/drop statements
query, query_drop = extract_drop_line(query)

if len(query)==1:
    # most of the time each script only creates a single view/table
    query = query[0]
    query_drop = query_drop[0]
    time, query_plan = benchmark_query(con, query, query_schema=query_schema, query_drop=query_drop)
    print('{:6.1f}s'.format(time/1000))
else:
    print('')
    for i in range(len(query)):
        time, query_plan = benchmark_query(con, query[i], query_schema=query_schema, query_drop=query_drop[i])
        print('  part {} - {:6.1f}s'.format(i, time/1000))


demographics/icustay_detail.sql...

   0.7s

Benchmark all concepts in make-concepts.sql

Uses parallel querying available in PostgreSQL 10.


In [100]:
query_plans = OrderedDict()
query_times = OrderedDict()

base_path = '/home/alistairewj/git/mimic-code/concepts'

# read through all make concepts
with open(os.path.join(base_path,'make-concepts.sql')) as fp:
    for line in fp.readlines():
        if len(line)<2:
            continue
        elif line[0:2] != '\\i':
            continue
        elif 'ccs_diagnosis_table.sql' in line:
            continue
            
        # get the name of the script
        script_name = line[3:].rstrip('\n')
        print('{:40s}'.format(script_name), end='... ')
        
        # read the script's query
        query = read_script(base_path, script_name)
        query, query_drop = extract_drop_line(query)


        if len(query)==1:
            # most of the time each script only creates a single view/table
            q = query[0]
            qd = query_drop[0]
            time, query_plan = benchmark_query(con, q, query_schema=query_schema, query_drop=qd)
            print('{:6.1f}s'.format(time/1000))
        else:
            query_plans[script_name] = list()
            query_times[script_name] = list()
            for i in range(len(query)):
                time, query_plan = benchmark_query(con, query[i], query_schema=query_schema, query_drop=query_drop[i])
                print('')
                print('  part {}...{:18s}{:6.1f}s'.format(i, '', time/1000))

                query_plans[script_name].append(query_plan)
                query_times[script_name].append(time)


code-status.sql                         ...   51.4s
echo-data.sql                           ...   24.8s
durations/ventilation-durations.sql     ... 
  part 0...                    43.7s

  part 1...                    22.0s
durations/crrt-durations.sql            ...   14.3s
durations/adenosine-durations.sql       ...    0.0s
durations/dobutamine-durations.sql      ...    0.5s
durations/dopamine-durations.sql        ...   14.6s
durations/epinephrine-durations.sql     ...    6.9s
durations/isuprel-durations.sql         ...    0.0s
durations/milrinone-durations.sql       ...    6.1s
durations/norepinephrine-durations.sql  ...   16.5s
durations/phenylephrine-durations.sql   ...   13.3s
durations/vasopressin-durations.sql     ...    2.5s
durations/vasopressor-durations.sql     ...   23.0s
durations/weight-durations.sql          ...    9.6s
comorbidity/elixhauser-ahrq-v37-with-drg.sql...   12.9s
comorbidity/elixhauser-ahrq-v37-no-drg.sql...   11.6s
comorbidity/elixhauser-ahrq-v37-no-drg-all-icd.sql...   12.5s
comorbidity/elixhauser-quan.sql         ...    8.6s
comorbidity/elixhauser-score-ahrq.sql   ...    0.2s
comorbidity/elixhauser-score-quan.sql   ...    0.1s
demographics/HeightWeightQuery.sql      ...   14.7s
demographics/icustay_detail.sql         ...    0.7s
firstday/blood-gas-first-day.sql        ...   32.7s
firstday/blood-gas-first-day-arterial.sql...   36.7s
firstday/gcs-first-day.sql              ...    7.9s
firstday/height-first-day.sql           ...    4.0s
firstday/labs-first-day.sql             ...   55.0s
firstday/rrt-first-day.sql              ...   16.0s
firstday/urine-output-first-day.sql     ...    3.2s
firstday/ventilation-first-day.sql      ...    0.2s
firstday/vitals-first-day.sql           ...  129.1s
firstday/weight-first-day.sql           ...    1.3s
fluid-balance/urine-output.sql          ...   18.2s
sepsis/angus.sql                        ...    2.8s
sepsis/martin.sql                       ...    2.3s
sepsis/explicit.sql                     ...    0.4s
organfailure/kdigo-creatinine.sql       ...   30.8s
organfailure/kdigo-uo.sql               ...  194.7s
organfailure/kdigo-stages-7day.sql      ...   10.9s
organfailure/kdigo-stages-48hr.sql      ...    6.2s
organfailure/meld.sql                   ...    0.7s
severityscores/oasis.sql                ...    1.5s
severityscores/sofa.sql                 ...   35.8s
severityscores/saps.sql                 ...    4.7s
severityscores/sapsii.sql               ...    7.8s
severityscores/apsiii.sql               ...    3.6s
severityscores/lods.sql                 ...    3.7s
severityscores/sirs.sql                 ...    0.5s

Benchmark all concepts in make-concepts.sql

Does not use parallel querying.


In [99]:
# same thing, but test parallel
query_plans_single_core = OrderedDict()
query_times_single_core = OrderedDict()
parallel_workers = 0

base_path = '/home/alistairewj/git/mimic-code/concepts'

# read through all make concepts
with open(os.path.join(base_path,'make-concepts.sql')) as fp:
    for line in fp.readlines():
        if len(line)<2:
            continue
        elif line[0:2] != '\\i':
            continue
        elif 'ccs_diagnosis_table.sql' in line:
            continue
            
        # get the name of the script
        script_name = line[3:].rstrip('\n')
        print('{:40s}'.format(script_name), end='... ')
        
        # read the script's query
        query = read_script(base_path, script_name)
        query, query_drop = extract_drop_line(query)


        if len(query)==1:
            # most of the time each script only creates a single view/table
            q = query[0]
            qd = query_drop[0]
            time, query_plan = benchmark_query(con, q, query_schema=query_schema, query_drop=qd,
                                               parallel_workers=0)
            print('{:6.1f}s'.format(time/1000))
            query_plans_single_core[script_name] = query_plan
            query_times_single_core[script_name] = time
        else:
            query_plans_single_core[script_name] = list()
            query_times_single_core[script_name] = list()
            print('')
            for i in range(len(query)):
                time, query_plan = benchmark_query(con, query[i],
                                                   query_schema=query_schema, query_drop=query_drop[i],
                                                   parallel_workers=0)
                print('  part {}...{:18s}{:6.1f}s'.format(i, '', time/1000))

                query_plans_single_core[script_name].append(query_plan)
                query_times_single_core[script_name].append(time)


code-status.sql                         ...   50.3s
echo-data.sql                           ...   25.2s
durations/ventilation-durations.sql     ... 
  part 0...                    44.1s

  part 1...                    17.6s
durations/crrt-durations.sql            ...   14.5s
durations/adenosine-durations.sql       ...    0.0s
durations/dobutamine-durations.sql      ...    0.5s
durations/dopamine-durations.sql        ...   14.6s
durations/epinephrine-durations.sql     ...    7.0s
durations/isuprel-durations.sql         ...    0.0s
durations/milrinone-durations.sql       ...    6.0s
durations/norepinephrine-durations.sql  ...   16.5s
durations/phenylephrine-durations.sql   ...   13.2s
durations/vasopressin-durations.sql     ...    2.4s
durations/vasopressor-durations.sql     ...   21.5s
durations/weight-durations.sql          ...    9.5s
comorbidity/elixhauser-ahrq-v37-with-drg.sql...   12.4s
comorbidity/elixhauser-ahrq-v37-no-drg.sql...   11.3s
comorbidity/elixhauser-ahrq-v37-no-drg-all-icd.sql...   12.7s
comorbidity/elixhauser-quan.sql         ...    8.6s
comorbidity/elixhauser-score-ahrq.sql   ...    0.2s
comorbidity/elixhauser-score-quan.sql   ...    0.2s
demographics/HeightWeightQuery.sql      ...   14.4s
demographics/icustay_detail.sql         ...    0.7s
firstday/blood-gas-first-day.sql        ...   32.5s
firstday/blood-gas-first-day-arterial.sql...   32.5s
firstday/gcs-first-day.sql              ...    8.0s
firstday/height-first-day.sql           ...    4.1s
firstday/labs-first-day.sql             ...   56.3s
firstday/rrt-first-day.sql              ...   15.9s
firstday/urine-output-first-day.sql     ...    3.3s
firstday/ventilation-first-day.sql      ...    0.1s
firstday/vitals-first-day.sql           ...  123.3s
firstday/weight-first-day.sql           ...    1.3s
fluid-balance/urine-output.sql          ...   16.2s
sepsis/angus.sql                        ...    2.7s
sepsis/martin.sql                       ...    2.3s
sepsis/explicit.sql                     ...    0.3s
organfailure/kdigo-creatinine.sql       ...   33.9s
organfailure/kdigo-uo.sql               ...  181.3s
organfailure/kdigo-stages-7day.sql      ...   10.4s
organfailure/kdigo-stages-48hr.sql      ...    6.1s
organfailure/meld.sql                   ...    0.6s
severityscores/oasis.sql                ...    1.5s
severityscores/sofa.sql                 ...   34.9s
severityscores/saps.sql                 ...    4.7s
severityscores/sapsii.sql               ...    7.7s
severityscores/apsiii.sql               ...    3.5s
severityscores/lods.sql                 ...    3.6s
severityscores/sirs.sql                 ...    0.9s

Compare parallel with no parallel


In [105]:
# first print all queries which used a parallel plan
for q in query_plans:
    for i, l in enumerate(query_plans[q]):
        if 'Parallel' in l:
            print(q)
            break

If the above prints nothing, no queries are using a parallel plan! :(