-
Notifications
You must be signed in to change notification settings - Fork 312
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SELECT involving CLOB column is 15X slow than without CLOB column #1755
Comments
I guess that it is due to the number of network round trips. When OCI_ATTR_DEFAULT_LOBPREFETCH_SIZE (the default value seems zero) is changed, the performance will be improved. class OCI8
def default_lobprefetch_size
# OCI_ATTR_DEFAULT_LOBPREFETCH_SIZE = 438 in oci.h
@session_handle.send(:attr_get_ub4, 438)
end
def default_lobprefetch_size=(newsize)
@session_handle.send(:attr_set_ub4, 438, newsize)
end
end
...
conn = ... get ruby-oci8 connection from oracle-enhanced connection ...
conn.default_lobprefetch_size = 8192 # determine proper size by yourself.
...
User.select(User.column_names).all.to_a |
Thanks for the quick reply, @kubo. That looks like it might help. Can you expand on the details of how to get the connection and set the |
I think it is better to set the lobprefetch size just after ruby-oci8 connection is established as prefetch_rows. |
@gyut I'll add |
Thanks, @kubo. That's exactly what we saw last week. We were able to increase and vary the lobprefetch, but no setting improved performance, and some settings degraded performance. We'd love to hear other ideas for improving the performance of tables with CLOB columns. |
There are likely two issues.
|
Thanks for the super-details analysis, @kubo! If you have time in the next few weeks to make improvements, we'd be happy to test them. |
@gyut $ git clone https://github.com/kubo/ruby-oci8.git
$ cd ruby-oci8
$ gem build ruby-oci8.gemspec
$ gem install ./ruby-oci8-2.2.6.gem LOB contents are retrieved as strings, not as LOB locators, by the following code. OCI8::BindType::Mapping[:clob] = OCI8::BindType::Long
OCI8::BindType::Mapping[:nclob] = OCI8::BindType::Long
OCI8::BindType::Mapping[:blob] = OCI8::BindType::LongRaw
OCI8::BindType::Mapping[:bfile] = OCI8::BindType::LongRaw Note that when LOB columns are fetched as LOB locators, empty lobs and NULLs are distinguishable. However when LOB columns are fetched as string, both empty lobs and NULLs are fetched as
They initially allocate a 32-kilobyte buffer for each column. When column data are longer than allocated buffers, they allocate an additional buffer whose size is twice. The size doesn't increase infinitely. The maximum size of one buffer is 8M. For example
When a LOB size is 100K, the initial, second and third buffers contain 32K, 64K, 4K respectively. The initial and maximum sizes are customizable by the following code.
I may change the default sizes later. Array fetching is working locally and has not been committed yet. I'm now writing tests about it. |
@gyut |
@kubo Thanks for the commit. I have not taken a look at the commit in detail yet, let me inform that Oracle enhanced adapter CI uses the master branch of ruby-oci8. The latest CI https://travis-ci.org/rsim/oracle-enhanced/jobs/423702697#L672 works fine using kubo/ruby-oci8@669e29f |
@yahonda Thanks for letting me know the CI. I didn't explain my concern enough. I guess that the oracle-enhanced test works well when |
Understood your concern. |
Unfortunately, oracle-enhanced tests failed when LOBs are fetched as strings. Failures are categorized into two types.
|
@gyut $ git clone https://github.com/kubo/ruby-oci8.git
$ cd ruby-oci8
$ gem build ruby-oci8.gemspec
$ gem install ./ruby-oci8-2.2.6.gem It reduces number of network round trips even when LOB data are fetched as LOB locators. Suppose the following cases.
C is the case just using the latest code in your application.
The following table is the number of network round trips for each query step when one CLOB column is included in a query which returns 5 rows and the setting of prefetch rows is 3.
|
Now that @kubo has done this really nifty rewrite (and informative tables!), you could also look at tuning the Oracle Net SDU, depending on how your LOBs are stored. |
Very nice work, @kubo. I've not yet been able to successfully run the 2.2.6 version. I'll try again tomorrow.
|
Thanks for all of your time and effort, @kubo! I was able to test your latest version using Case C, and it does appear to be twice as fast (35ms vs 70ms) as your analysis predicts. I'll next look more carefully at Case D, fetching LOBs as strings to see if that's viable.
|
I got a new idea.
Though I have not implemented yet, I estimate number of the network round trips as follows:
When a query includes three CLOB clolumns,
|
@kubo, fetching LOBs as strings/bytes is going to perform better than using OCILobArrayRead() since no additional round trips are needed. The only advantage to OCILobArrayRead() (assuming that you have told Oracle to prefetch the LOB length) is that you can allocate the full size up front. If you don't tell Oracle to prefetch the LOB length then you'll either have to use one round-trip to get the length and another one to read, or you'll have to use multiple round-trips to perform the reads. Regarding case D getting the result as nil, presumably you can check for that situation and return an empty string instead? |
Agree about the number of round trips. However case E is better than case D in the point of view about empty LOBs.
I won't use any way you wrote in case E and I didn't in case C. I guess that OCILobArrayRead() works similarly to OCILobRead2(), which is used inside of
I can't. I got no difference between empty_clob() and NULL when they are fetched using |
Well, if you have to differentiate between NULL and empty_clob() then you're out of luck, I guess, since Oracle decided a very long time ago that an empty string is NULL. :-) I'm not sure there's much point differentiating between the two, though! Especially for the additional cost in code and the performance drain that is required to implement your suggested approach. |
Yes. I think that it is impossible as long as they are fetched using |
I tried case E. It worked fine when using local connections.(1, 2) However it stalled when using TNS connections. It may not be available for production. I'll make minimum code in C to check the usage of OCILobArrayRead(). @gyut If you try it even though it may not work, do the followings.
|
As far as I checked, OCILobArrayRead() stalls when (1) empty_clob() is read, (2) TNS connection is used and (3) Oracle server version is 18.3.0.0.0. I tested it for following Oracle client and server combinations by using this test program.
|
@kubo ouch. Send some form of a testcase and I'll pass it onto the LOB developers. |
$ sqlplus username/password
SQL> create table clob_test_1755 (clob_col clob);
SQL> insert into clob_test_1755 values (empty_clob());
SQL> commit;
SQL> exit
$ wget https://gist.githubusercontent.com/kubo/d5aa0ad913a57a9322748b198a4dd5ce/raw/1399c7495b9cbec43f69b48f317a6ee108d220a4/lob-read-test.c
$ vi lob-read-test.c # Change username, password and database variables. database must be `//host/service` or TNS name.
$ gcc -g -Wall -o lob-read-test lob-read-test.c -I$ORACLE_HOME/rdbms/public -L$ORACLE_HOME/lib -lclntsh -Wl,-rpath,$ORACLE_HOME/lib
$ ./lob-read-test
Environment:
|
@kubo I logged Oracle bug 28675041 to track this. Thank you. |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
Steps to reproduce
User.role is a CLOB. Takes 80ms.
User.select(User.column_names).all.to_a
D, [2018-08-13T12:03:31.270377 #56195] DEBUG -- : User Load (78.8ms)
Without User.role it takes 5ms.
User.select(User.column_names - %w(roles)).all.to_a
D, [2018-08-13T12:03:23.412199 #56195] DEBUG -- : User Load (4.5ms)
Expected behavior
Running a SELECT query that includes a CLOB column should be roughly as fast as running a SELECT query without the CLOB column.
Actual behavior
Running a SELECT query that includes a CLOB column is about 15x slower than without the CLOB column.
System configuration
Rails version: 5.2.1
Oracle enhanced adapter version: 5.2.3
Ruby version: 2.4.2p198
Oracle Database version: Oracle Database 11g Release 11.2.0.4.0 - 64bit Production
The text was updated successfully, but these errors were encountered: