Summary: I am facing a contention problem with ODBC on the client side. strace
and perf top show we are serializing over what appears to be accesses to the
ODBC statement handle. Contention goes down if I use multiple processes instead
of multiple threads within a process. Also, all the threads get the same
connection handle number and the same statement handle number. Is there a way
to force different connection and statement handles? I have asked this question
on the ODBC mailing list, and they suggested it could be something in the
postgresql driver.
Details: Running the TPCx-V benchmark, we hit a performance bottleneck as the
load increases. We have plenty of CPU and disk resources available in our
driver VM, client VM, and database backend VM (all with high vCPU counts) on a
dedicated server. When we increase the number of threads of execution, not only
doesn’t throughput go up, it actually degrades. I am running with 80 threads in
one process. When I divide these threads into 5 processes, performance nearly
doubles. So, the problem is not in the database backend. Each thread has its
own database connection and its own statement handle.
Looking more closely at the client, this is what I see in the strace output
when everything flows through one process:
17:52:52.762491 futex(0x7fae351c5100, FUTEX_WAIT_PRIVATE, 2, NULL)
= -1 EAGAIN (Resource temporarily unavailable) <0.000102>
17:52:52.762635 futex(0x7fae351c5100, FUTEX_WAIT_PRIVATE, 2, NULL)
= 0 <0.000664>
17:52:52.763540 futex(0x7fae351c5100, FUTEX_WAIT_PRIVATE, 2, NULL)
= -1 EAGAIN (Resource temporarily unavailable) <0.000027>
17:52:52.763616 futex(0x7fae351c5100, FUTEX_WAKE_PRIVATE, 1)
= 1 <0.000076>
17:52:52.763738 futex(0x7fae463a9f00, FUTEX_WAIT_PRIVATE, 2, NULL)
= -1 EAGAIN (Resource temporarily unavailable) <0.000016>
17:52:52.763793 futex(0x7fae463a9f00, FUTEX_WAKE_PRIVATE, 1)
= 1 <0.000010>
17:52:52.763867 futex(0x7fae351c5100, FUTEX_WAIT_PRIVATE, 2, NULL)
= -1 EAGAIN (Resource temporarily unavailable) <0.000038>
17:52:52.763982 futex(0x7fae351c5100, FUTEX_WAKE_PRIVATE, 1)
= 1 <0.000037>
17:52:52.764078 futex(0x7fae18000020, FUTEX_WAKE_PRIVATE, 1)
= 0 <0.000010>
17:52:52.764182 futex(0x7fae351c5100, FUTEX_WAIT_PRIVATE, 2, NULL)
= -1 EAGAIN (Resource temporarily unavailable) <0.000030>
17:52:52.764264 futex(0x7fae351c5100, FUTEX_WAKE_PRIVATE, 1)
= 1 <0.000075>
17:52:52.764401 futex(0x7fae351c5100, FUTEX_WAKE_PRIVATE, 1)
= 1 <0.000014>
17:52:52.764455 futex(0x7fae351c5100, FUTEX_WAIT_PRIVATE, 2, NULL)
= -1 EAGAIN (Resource temporarily unavailable) <0.000011>
17:52:52.764507 futex(0x7fae351c5100, FUTEX_WAKE_PRIVATE, 1)
= 1 <0.000025>
17:52:52.764579 futex(0x7fae351c5100, FUTEX_WAKE_PRIVATE, 1)
= 1 <0.000010>
17:52:52.764821 sendto(227, "\x51\x00\x00\x00\x0b\x43\x4f\x4d\x4d\x49\x54\x00",
12, MSG_NOSIGNAL, NULL, 0) = 12 <0.000029>
17:52:52.764911 recvfrom(227, 0x7fae18058760, 4096, 16384, 0, 0)
= -1 EAGAIN (Resource temporarily unavailable) <0.000107>
17:52:52.765065 poll([{fd=227, events=POLLIN}], 1, 4294967295)
= 1 ([{fd=227, revents=POLLIN}]) <0.000017>
17:52:52.765185 recvfrom(227,
"\x43\x00\x00\x00\x0b\x43\x4f\x4d\x4d\x49\x54\x00\x5a\x00\x00\x00\x05\x49",
4096, MSG_NOSIGNAL, NULL, NULL) = 18 <0.000018>
17:52:52.765258 futex(0x7fae351c5100, FUTEX_WAIT_PRIVATE, 2, NULL)
= 0 <0.000470>
17:52:52.765764 futex(0x7fae351c5100, FUTEX_WAIT_PRIVATE, 2, NULL)
= -1 EAGAIN (Resource temporarily unavailable) <0.000052>
17:52:52.765908 futex(0x7fae351c5100, FUTEX_WAKE_PRIVATE, 1)
= 1 <0.000073>
17:52:52.766045 futex(0x7fae351c5100, FUTEX_WAKE_PRIVATE, 1)
= 1 <0.000040>
17:52:52.766246 futex(0x7fae351c5100, FUTEX_WAIT_PRIVATE, 2, NULL)
= -1 EAGAIN (Resource temporarily unavailable) <0.000026>
And perf top shows:
9.89% [kernel] [k] _raw_spin_unlock_irqrestore
4.86% [kernel] [k] finish_task_switch
3.53% [kernel] [k] _raw_spin_lock
3.00% libodbc.so.2.0.0 [.] __check_stmt_from_dbc_v
2.80% [kernel] [k] __do_softirq
2.43% psqlodbcw.so [.] 0x000000000003b146
1.95% libodbc.so.2.0.0 [.] __validate_stmt
1.93% libc-2.17.so [.] _IO_vfscanf
1.91% libodbc.so.2.0.0 [.] __validate_dbc
1.90% psqlodbcw.so [.] copy_and_convert_field
1.80% libc-2.17.so [.] _int_malloc
1.58% libc-2.17.so [.] malloc
1.42% libc-2.17.so [.] _int_free
1.36% libodbc.so.2.0.0 [.] __release_desc
1.26% libc-2.17.so [.] __strncpy_sse2_unaligned
1.25% psqlodbcw.so [.] mylog
1.17% libc-2.17.so [.] __offtime
1.05% [kernel] [k] vmxnet3_xmit_frame
1.03% psqlodbcw.so [.] SC_fetch
0.91% libc-2.17.so [.] __memcpy_ssse3_back
0.89% libc-2.17.so [.] __strcpy_sse2_unaligned
0.87% libc-2.17.so [.] __GI_____strtod_l_internal
0.86% libc-2.17.so [.] __tz_convert
0.76% libc-2.17.so [.] __tzfile_compute
0.76% psqlodbcw.so [.] convert_linefeeds
0.71% libpthread-2.17.so [.] pthread_mutex_lock
0.69% [kernel] [k] vmxnet3_poll_rx_only
0.66% [kernel] [k] tick_nohz_idle_enter
0.64% libc-2.17.so [.] __strcmp_sse2
0.63% libc-2.17.so [.] malloc_consolidate
0.63% libodbc.so.2.0.0 [.] __set_stmt_state
0.62% [kernel] [k] __audit_syscall_exit
0.60% libc-2.17.so [.] __GI_____strtoll_l_internal
0.59% libc-2.17.so [.] __memset_sse2
0.56% libpthread-2.17.so [.] pthread_mutex_unlock
0.54% psqlodbcw.so [.] PGAPI_ExtendedFetch
0.53% [kernel] [k] ipt_do_table
So, we have severe contention, but not over actual database accesses. If I look
at the PGSQL backend, I see many transactions in flight; i.e., transactions
aren’t serialized. The contention appears to be over accesses to data
structures:
- With 5 processes and 80 threads of execution, the response time of
the TRADE_STATUS transaction is 6.3ms @ 2718 total transactions/sec
- With 1 process and 80 threads of execution, TRADE_STATUS response
time is 44ms @ 1376 total transactions/sec
- TRADE_STATUS has a number of function calls that reference the
“stmt” handle returned by SQLAllocStmt:
o 6 to allocate the statement handle, run the query, bind input parameter,
free the handle, …
o 13 SQLBindCol()
o 50 SQLFetch()
- If I replace the 50 SQLFetch() calls with a single
SQLExtendedFetch() call, response time drops to 20ms
- If I comment out the 13 SQLBindCol() calls (which means the
benchmark isn’t working right but the query still runs), it drops to 13.9ms
- If by dropping these calls we were simply saving the execution time
of their pathlength, we would have saved at most the 6.3ms we measured with 5
processes. So, the improvements we saw by avoiding the SQLBindCol() and
SQLFetch() calls weren’t simply due to doing less work. They were due to
avoiding contention inside those routines
Something that puzzles me is that all the calls to
SQLAllocHandle(SQL_HANDLE_DBC, env_handle, &dbc_handle) set the dbc_handle to
19290 for all the threads within the process, and all the calls to
SQLAllocHandle(SQL_HANDLE_STMT, dbc_handle, &stmt_handle) set the stmt_handle
to 19291. Even if I call SQLAllocHandle(SQL_HANDLE_STMT, , ) multiple times to
get multiple statement handles, all the handles get set to the same 19291 (and
furthermore, performance gets worse even if I don’t use the new statement
handles; just creating them impacts performance).
Is there a way to avoid all this contention, and allow multiple threads to
behave like multiple processes? The threads do not share connections,
statements, etc. There should be no contention between them, and as far as I
can tell, there isn’t any contention over actual resources. The contention
appears to be over just accessing the ODBC data structures
We have unixODBC-2.3.4, built with:
./configure --prefix=/usr --sysconfdir=/etc --enable-threads=yes
--enable-drivers=yes --enable-driver-conf=yes --enable-stats=no
--enable-fastvalidate=yes
make
make install
My config files look like:
# cat /etc/odbcinst.ini
# Example driver definitions
# Driver from the postgresql-odbc package
# Setup from the unixODBC package
[PostgreSQL]
Description = ODBC for PostgreSQL
Driver = /usr/lib/psqlodbcw.so
Setup = /usr/lib/libodbcpsqlS.so
Driver64 = /usr/pgsql-9.3/lib/psqlodbcw.so
Setup64 = /usr/lib64/libodbcpsqlS.so
FileUsage = 1
Threading = 0
#
# cat /etc/odbc.ini
[PSQL2]
Description = PostgreSQL
Driver = PostgreSQL
Database = tpcv
ServerName = w1-tpcv-vm-50
UserName = tpcv
Password = tpcv
Port = 5432
[PSQL5]
Description = PostgreSQL
Driver = PostgreSQL
Database = tpcv1
ServerName = w1-tpcv-vm-60
UserName = tpcv
Password = tpcv
Port = 5432