8

I have very large table (hundreds of millions rows, contains numbers and strings) in Oracle and I need to read all content of this table, format it and write to file or any other resource. Generally my solution looks like this:

package my.odp;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.lang.Throwable;
import java.sql.*;


public class Main {
public static volatile boolean finished = false;

public static void main(final String[] args) throws InterruptedException {
    final ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<String>(10000);
    final Thread writeWorker = new Thread("ODP Writer") {
        public void run() {
            try {
                File targetFile = new File(args[0]);
                FileWriter fileWriter = new FileWriter(targetFile);
                BufferedWriter writer = new BufferedWriter(fileWriter);
                String str;
                try {
                    while (!finished) {
                        str = queue.poll(200, TimeUnit.MILLISECONDS);
                        if (str == null) {
                            Thread.sleep(50);
                            continue;
                        }
                        writer.write(str);
                        writer.write('\n');
                    }
                } catch (InterruptedException e) {
                    writer.close();
                    return;
                }
            }
            catch (Throwable e) {
                e.printStackTrace();
                return;
            }
        }
    };

    final Thread readerThread = new Thread("ODP Reader") {
        public void run() {
            try {
                Class.forName("oracle.jdbc.OracleDriver");
                Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@//xxx.xxx.xxx.xxx:1521/orcl", "user", "pass");

                Statement stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
                stmt.setFetchSize(500000);
                ResultSet rs = stmt.executeQuery("select * from src_schema.big_table_view");
                System.out.println("Fetching result");
                while (rs.next()) {
                    StringBuilder sb = new StringBuilder();
                    sb.append(rs.getString(1)).append('\t');//OWNER
                    sb.append(rs.getString(2)).append('\t');//OBJECT_NAME
                    sb.append(rs.getString(3)).append('\t');//SUBOBJECT_NAME
                    sb.append(rs.getLong(4)).append('\t');//OBJECT_ID
                    sb.append(rs.getLong(5)).append('\t');//DATA_OBJECT_ID
                    sb.append(rs.getString(6)).append('\t');//OBJECT_TYPE
                    sb.append(rs.getString(7)).append('\t');//CREATED
                    sb.append(rs.getString(8)).append('\t');//LAST_DDL_TIME
                    sb.append(rs.getString(9)).append('\t');//TIMESTAMP
                    sb.append(rs.getString(10)).append('\t');//STATUS
                    sb.append(rs.getString(11)).append('\t');//TEMPORARY
                    sb.append(rs.getString(12)).append('\t');//GENERATED
                    sb.append(rs.getString(13)).append('\t');//SECONDARY
                    sb.append(rs.getString(14)).append('\t');//NAMESPACE
                    sb.append(rs.getString(15));//EDITION_NAME
                    queue.put(sb.toString());
                }

                rs.close();
                stmt.close();
                conn.close();
                finished = true;
            } catch (Throwable e) {
                e.printStackTrace();
                return;
            }
        }
    };
    long startTime = System.currentTimeMillis();
    writeWorker.start();
    readerThread.start();
    System.out.println("Waiting for join..");
    writeWorker.join();
    System.out.println("Exit:"+ (System.currentTimeMillis() - startTime));
}

}

There're two threads: one for fetching rows from result set and one for writing string values. Measured loading speed was about 10Mb/s and in my case I need to make it 10 times faster. Profiler shows that the most time consuming methods are

oracle.jdbc.driver.OracleResultSetImpl.getString()

and

oracle.net.ns.Packet.receive()

Do you have any ideas how to make jdbc to load data much faster? Any ideas about query optimisation, string loading optimisation, tweaking JDBC driver or using another one, direct using oracle JDBC implementations, tweaking Oracle is appreciated.

UPDATE: I compiled and listed discussion results below:

  1. I've no access to DBMS server except connection to Oracle db and server can't connect to any external resource. Any dump and extraction utils which use server's or remote file system can't be applied, also it's impossible to install and use any external java or PL/SQL routines on server. Only connetion to execute queries - thats all.

  2. I used profiler and digged in Oracle JDBC driver. I found out that the most expencive operation is reading data, i.e. Socket.read(). All string fields are represented as one char array and have almost no influence on perfomance. Generally, I checked with profiler the whole app and Socket.read() is definitely the most expensive operation. Extracting fields, building strings, writing data consume almost nothing. The problem is only in reading data.

  3. Any optimisations in data representation on server side don't have real effect. Concatenating strings and converting timestamps have no result for performance.

  4. App was rewritten to have several reader threads which put ready data in writer queue. Each thread has its own connection, no pools are used because they slow down the extraction (I used UCP pool which is recommended by oracle and it consumes about 10% of execution time, so i gave up from it). Also result set fetchSize was increased because switching from default value (10) to 50000 gives up to 50% perfomance growth.

  5. I tested how multithreaded version works with 4 reading threads and found out that increasing readers count only slows the extraction. I tried to launch 2 instances where each of them has two readers and both worked the same time as single instance, i.e. double data extraction requires same time as single. Don't know why this happens, but it looks like oracle driver have some performance limitations. Application with 4 independent connections works slower then 2 App instances with 2 connections. (Profiler was used to ensure that driver's Socket.read() is still the main issue, all other parts works fine in multithreaded mode).

  6. I tried to fetch all data with SAS and it can perform same extraction 2 times faster then JDBC, both used single connection to Oracle and can't use any dump operations. Oracle ensures that JDBC thin driver is as fast as native one..

Maybe Oracle have another ways to perform fast extraction to remote host through ODBC or smth else?

user3007501
  • 283
  • 1
  • 4
  • 13
  • I'm not sure if you're trying to optimize fetching data and writing a file or whether you are trying to optimize loading data into the database. Are you limited to using Java on, presumably, the application server? If not, it's probably more efficient to copy the file to the server and use an external table to load it or to write the file on the server and use operating system utilities to move it wherever you want it to go. – Justin Cave Aug 16 '14 at 00:17
  • 1
    RDBMS isn't the right tool to manipulate *hundreds of millions of rows*, **much less in a single threaded resource like a network socket or file**, what do you expect? With I/O constrained resources more threads == slower not faster. Map Reduce and NoSQL databases were created for a reason. –  Aug 16 '14 at 00:43
  • @JarrodRoberson - That doesn't help if he is starting from an Oracle database and / or his system requires classical RDBMS characteristics for other reasons. – Stephen C Aug 16 '14 at 00:54
  • 1
    There are better ways to fetch all data from Oracle than by using Java. Check the Oracle toolset or ask your DBA. They can blast the data to a file. Then you can copy it where it needs to go. – Tony Ennis Aug 16 '14 at 01:00
  • It would be useful for you to give us a snapshot from your profiling results. Specifically, the ordered "most time consuming methods", and a memory usage snapshot (data structures, etc). – ra2085 Aug 16 '14 at 01:10
  • 2
    This is begging for an ETL process. – chrylis -cautiouslyoptimistic- Aug 16 '14 at 01:20
  • I'm skeptical to post an answer. It seems he really needs to do this in java. What about doing both reading and writing on the same loop? He could use just the JDBC buffer instead of having it repicated on memory in a list. – ra2085 Aug 16 '14 at 01:23
  • 2
    What exactly is the point of polling with a 200ms timeout and then sleeping for 50ms? The sleep part of this is just literally a waste of time: you've already spent 200mS in the `poll()` method, by definition. So you can increase your speed right there. There isn't much point in specifying such a short `poll()` timeout either. A second or two would do. – user207421 Aug 16 '14 at 05:09
  • I dont have any access to DB server filesystem and server dont have direct access to any another host to manually transfer the data, so i cant dump the data to any external resource. – user3007501 Aug 16 '14 at 07:28
  • The problem is to load this data to unix pipe skipping wrting to disk. Its required to load data to another system but target system (Vertica) dont have efficient way to fetch rows from Oracle. SAS can load this data much faster and I want to reach it's speed as close as possible. I've no problems with writing perfomance in my implementation. Profiling shows that writing thread is waiting for data almost all time, so timeout doesn't affect the perfomance. – user3007501 Aug 16 '14 at 07:42
  • 1
    You've already got setFetchSize which is often the missing things that does the trick. A couple of other things to try, first can you change the query so it it does the concatenation so that when you get the data back you only need to do 1 getString? Second, can this process be run on the database server, and then use a BEQUEATH connection, i.e. take the network element out? Are you doing a full table scan on the table (probably yes), so why not add a /*+ PARALLEL */ hint ( the degree depending on spare CPU at the time). – TenG Aug 16 '14 at 09:12
  • Is it possible to do rs.getBytes(int column) instead of rs.getString(..) and then somehow aggregate these bytes in the writer thread and convert the coming byte array of an entire row into a string which should reduce the time. – Manjunath Aug 16 '14 at 09:23
  • I can't launch anything on database server. The only thing I have is connection for performing sql requests. It's all around the security.. Parallel hint is already used in view which represents the data, adding hint to select requests adds some speed but still not enough. – user3007501 Aug 16 '14 at 09:36
  • Are you able to execute top or any diagnostic command on DB server side? Or can you get statspack/awr reported for the query? Many times when Java developers complain about database performance, DBAs find that DB server is doing nothing but waiting for a network roundtrip from client. PS: maybe you should also try a similar query where you fetch only columns of datatype RAW. Conversion from NUMBER to int, or from UTF8 VARCHAR2 into UTF16 Java String. – ibre5041 Aug 20 '14 at 15:57

3 Answers3

3

Assuming you have already checked the basic network stuff like interfaces, firewalls, proxies, as also the DB server's hardware elements.

Option 1 :

Instead of :

Class.forName("oracle.jdbc.OracleDriver");
Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@//xxx.xxx.xxx.xxx:1521/orcl", "user", "pass");

try using :

OracleDataSource ods = new OracleDataSource();
java.util.Properties prop = new java.util.Properties();
prop.setProperty("MinLimit", "2");
prop.setProperty("MaxLimit", "10");
String url = "jdbc:oracle:oci8:@//xxx.xxx.xxx.xxx:1521/orcl";
ods.setURL(url);
ods.setUser("USER");
ods.setPassword("PWD");
ods.setConnectionCachingEnabled(true);
ods.setConnectionCacheProperties (prop);
ods.setConnectionCacheName("ImplicitCache01");

More details here

Option 2 : Fetchsize

As strongly pointed by Stephen, the fetchsize seems too large.

And, for fetch size of 500,000 what is your -Xms and -Xmx. Also, in profiler, whats the highest heap size?

Option 3 : DB

  • Check indexes and query plan for src_schema.big_table_view

  • Is this a tool or an application system. If just a tool, you could add parallel degrees, index hints, partitioning etc based on DB systems capabilities

Option 4 : Threads

Say n < Number of cores on application server

You can start n Threads of writer, each configured to process a certain bucket e.g. thread1 processes 0 to 10000, writing to n different files, and once all theads done, post join, merge the files together preferably using a low level OS command.

That said, all this should never be pre-defined code like its now. 'n' and the buckets should be calculated at runtime. And creating number of threads more than what your system supports only screws up.

Option 5 :

Instead of

select * from src_schema.big_table_view

You could use

SELECT column1||CHR(9)||column2||CHR(9).....||columnN FROM src_schema.big_table_view

This avoids creating 500000 StringBuilders and Strings. (Assuming no other complex formatting involved). CHR(9) is the tab character.

Option 6 :

Meantime, you could also check with your DBA for any DB system issues and raise an SR with Oracle support.

Rajeev Sreedharan
  • 1,753
  • 4
  • 20
  • 30
  • Thanks for reply. I've already tried some of this optimisations: Option 1: I've tried to conctenate strings same way as you suggest and it doen't give any pefomnance improvements. Actually, the slowes part in this implementation is JDBC driver, the longest operation is Socket read() – user3007501 Aug 20 '14 at 07:48
  • Also I made some investigation and digged in driver implementation. It doesn't use any StringBuilders to create strings, all varchar data is internally represented as single char array and doesn't consume much time for fetching.Using StringBuidler in reading fields requires too little time for work (almost nothing in profiling time). About Option 3 - Writing thread is waiting almost all time because JDBC reader produces data too slow. Actually problem is in JDBC driver reading speed. JDBC reader was rewritten to be multithreaded(each thread extracts its own part of data and writes it in queue) – user3007501 Aug 20 '14 at 08:04
  • I tried to extract data in several parts simultaneously but the more readers I use, the worse performance results I have. I tried to launch 2 instances where each of them has two readers... and both worked the same time as single instance, i.e. double extraction required same time as single. Don't know why this happens, but it looks like oracle driver have some performance limitations. _Application with 4 independent connections works slower then 2 App instances with 2 connections._ – user3007501 Aug 20 '14 at 08:36
  • I checked the code with profiler in situations with 2 and 4 threads and nothing in my code consumes much cpu time. Main consumer is always Socket.read() in JDBC driver... Have no ideas why it behaves this way – user3007501 Aug 20 '14 at 08:37
  • I tried to fetch all data with SAS and it can perform extraction 2 times faster then JDBC, both used single connection to Oracle and can't use any dump operations. Oracle ensures that JDBC thin driver is as fast as native one.. Maybe Oracle have another ways to perform fast extraction to remote host through ODBC or smth else? – user3007501 Aug 20 '14 at 08:50
  • Frankly its difficult to make a virtual guess. Anyway, I've added 2 more options. Hope it helps. – Rajeev Sreedharan Aug 20 '14 at 15:33
1

It looks like you have already found and tweaked the row prefetch parameter. However, according to the Oracle documentation:

"There is no maximum prefetch setting, but empirical evidence suggests that 10 is effective. Oracle has never observed a performance benefit to setting prefetch higher than 50. If you do not set the default row-prefetch value for a connection, then 10 is the default."

You are setting it to 500000. Try winding it back to around 50 ... as Oracle recommend. (Why? Well it could be that a hugely excessive prefetch size is causing the server or client to use excessive amounts of memory to buffer the prefetched data. That could have a "knock on effect" on other things, leading to reduced throughput.)

Reference (from the Oracle 10g documentation):


You might be able to get a greater throughput by running simultaneous queries in multiple Java threads (e.g. on separate "sections" of the table), writing each resultset to a separate stream / file. But then you have the problem of stitching the output streams / files together. (And whether you do get an overall improvement will depend on the number of client and server-side cores, network and NIC capacity and disc I/O capacity.)

Apart from that, I can't think of any way to do this faster in Java. But you could try PL/SQL, or something lower level. (I'm not an Oracle expert. Talk to your DBAs.)

A factor of 10 speed up in Java is ... ambitious.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • I'm skeptical to post an answer. It seems he really needs to do this in java. What about doing both reading and writing on the same loop? He could use just the JDBC buffer instead of having it repicated on memory in a list. – ra2085 Aug 16 '14 at 01:22
  • @ra2085 - According to the OP, the bottleneck (as indicated by the profiler) is in reading the data packets from Oracle and turning them into Java strings. He's not creating a list. He's putting Strings (lines to be written) into a queue. This *should* have the effect of allowing database reads and file writes to happen simultaneously ... rather than forcing them to be interleaved. (Whether it is actually effective is hard to predict, but I assume that the OP's interpretation of his profiling results is that it is effective.) – Stephen C Aug 16 '14 at 01:33
  • My case shows that setting FetchSize value to 50000 gives up to 50% performance growth, I think default values are appropriate for smaller result sets and frequent requests – user3007501 Aug 20 '14 at 09:00
1

Your profiling is flawed

The methods you list are most likely highly optimized already. I have analyzed systems where the most called and the most time was spent inside StringBuffer.append() inside the Oracle JDBC code because the entire system used PreparedStatement and it calls that method a lot!. Needless to say, that was a red herring in our case.

Profile your network traffic:

If your connection is saturated that is your bottleneck not the code you listed.

This needs to be done on the server side if it has to be Oracle as the source of the data. you will never pull hundreds of millions of records across a network connection and then back again at 10X the speed you are getting now unless you have 10X the network cards in both endpoints and have all of them bonded. Even then I am skeptical you will get 10X the throughput

If you really are limited to Java and Oracle, the the only way you will get more throughput than you are getting now is to run the Java as a stored procedure on the server(s) generate the files you need and then retrieve them from the remote system.

I have built systems that dealt with millions of transactions as minute, that kind of throughput isn't happening over a single network connection, it happens over a grid of machines with multiple network interfaces on dedicated send/receive switches on a dedicated subnet isolated from the rest of the traffic in the data center.

Also

Your threading code is naive at best. You should never create and manage threads manually. ExecutorService has been around for 10 years, use it! ExecutorCompletionService is what you want to use in this case, actually in almost all cases.

ListenableFuture is an even better choice if you can use Guava.

Community
  • 1
  • 1
  • I can't use any native dump utils because I don't have access to server filesystem. Do you know any way to speed up fetching strings with oracle jdbc driver? – user3007501 Aug 16 '14 at 07:55