You are viewing a plain text version of this content. The canonical link for it is here.
Posted to derby-commits@db.apache.org by kr...@apache.org on 2008/10/08 14:13:22 UTC

svn commit: r702818 - /db/derby/code/trunk/java/testing/org/apache/derbyTesting/perf/basic/jdbc/ClobAccessTest.java

Author: kristwaa
Date: Wed Oct  8 05:13:21 2008
New Revision: 702818

URL: http://svn.apache.org/viewvc?rev=702818&view=rev
Log:
DERBY-3810: Create a simple Clob performance regression test.
Added initial version of a Clob performance regression test.
Currently, the embedded driver is used and there are no tests for encrypted Clobs.
There are a few properties that can be used to control the test (see JavaDoc).
Patch file: derby-3810-1b-clobaccesstest.diff

Added:
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/perf/basic/jdbc/ClobAccessTest.java   (with props)

Added: db/derby/code/trunk/java/testing/org/apache/derbyTesting/perf/basic/jdbc/ClobAccessTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/perf/basic/jdbc/ClobAccessTest.java?rev=702818&view=auto
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/perf/basic/jdbc/ClobAccessTest.java (added)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/perf/basic/jdbc/ClobAccessTest.java Wed Oct  8 05:13:21 2008
@@ -0,0 +1,594 @@
+/*
+
+   Derby - Class org.apache.derbyTesting.perf.basic.jdbc.ClobAccessTest
+
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to you under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+ */
+package org.apache.derbyTesting.perf.basic.jdbc;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.sql.Clob;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.derbyTesting.functionTests.util.streams.LoopingAlphabetReader;
+import org.apache.derbyTesting.junit.CleanDatabaseTestSetup;
+import org.apache.derbyTesting.junit.JDBCPerfTestCase;
+import org.apache.derbyTesting.perf.clients.BackToBackLoadGenerator;
+import org.apache.derbyTesting.perf.clients.Client;
+import org.apache.derbyTesting.perf.clients.DBFiller;
+import org.apache.derbyTesting.perf.clients.LoadGenerator;
+import org.apache.derbyTesting.perf.clients.SingleRecordFiller;
+import org.apache.derbyTesting.perf.clients.SingleRecordSelectClient;
+
+/**
+ * A series of tests accessing Clobs in various ways.
+ * <p>
+ * These tests are intended to detect Clob performance regressions. Before
+ * committing a patch that might change the Clob performance characteristics,
+ * first run these tests on a clean build and then with the patch applied. The
+ * results can only be compared when both runs are done on the same machine.
+ * <p>
+ * The results are time taken to execute the test. Lower duration is better
+ * (improvement). Currently the results are printed to standard out. There is
+ * one exception, which is {@code testConcurrency}. For this test, the
+ * throughput is printed and it will always run for a fixed amount of time.
+ * <p>
+ * The tests are written with two axis in mind: read-only vs update and small vs
+ * large. These axis were chosen based on the Clob implementation at the time.
+ * In the context of this test, small means the Clob is represented as a string
+ * by the Derby store and large means the Clob is represtend as a stream into
+ * the Derby store. When a Clob is modified, an in-memory or on disk temporary
+ * copy is created. The performance of these temporary representations are
+ * tested with the tests that modify the Clob content.
+ * <p>
+ * System properties controlling test behavior:
+ * <lu><li>derby.tests.disableSmallClobs</li>
+ *     <li>derby.tests.disableLargeClobs</li>
+ *     <li>derby.tests.disableConcurrencyTest</li>
+ *     <li>derby.tests.largeClobSize (in MB, 15 is the default)</li>
+ * </ul>
+ *
+ * <p>
+ * <b>NOTE</b>: Currently there are no tests for the client driver (network)
+ * or for encrypted Clobs.
+ */
+public class ClobAccessTest
+        extends JDBCPerfTestCase {
+
+    private static final boolean disableSmallClobs =
+            Boolean.getBoolean("derby.tests.disableSmallClobs");
+    private static final boolean disableLargeClobs =
+            Boolean.getBoolean("derby.tests.disableLargeClobs");
+    private static final boolean disableConcurrencyTest =
+            Boolean.getBoolean("derby.tests.disableConcurrencyTest");
+    private static final int largeClobSizeMB =
+            Integer.getInteger("derby.tests.largeClobSize", 15).intValue();
+
+
+    /** Maximum buffer size to use. */
+    private static final int MAX_BSIZE = 32676;
+
+    /**
+     * Instantiates a new test that will be run the specified number of
+     * iterations and repeated as specified.
+     *
+     * @param name name of the test to instantiate
+     * @param iterations number of iterations per repetition
+     * @param repeats number of repetitions
+     */
+    public ClobAccessTest(String name, int iterations, int repeats) {
+        super(name, iterations, repeats);
+    }
+
+    /**
+     * Set autocommit to false by default.
+     */
+    public void initializeConnection(Connection conn)
+            throws SQLException {
+        conn.setAutoCommit(false);
+    }
+
+    public static Test suite() {
+        TestSuite mainSuite = new TestSuite("ClobAccessTest suite");
+        if (!disableSmallClobs) {
+            int iters = 50;
+            int reps = 1;
+            println("Adding small Clob tests.");
+            TestSuite smallSuite = new TestSuite("Small Clob suite");
+            smallSuite.addTest(new ClobAccessTest(
+                    "testFetchSmallClobs", iters, reps));
+            smallSuite.addTest(new ClobAccessTest(
+                    "testFetchSmallClobsInaccurateLength", iters, reps));
+            smallSuite.addTest(new ClobAccessTest(
+                    "testModifySmallClobs", iters, reps));
+            mainSuite.addTest(smallSuite);
+        }
+        if (!disableLargeClobs) {
+            int iters = 5;
+            int reps = 1;
+            println("Adding large Clob tests.");
+            TestSuite largeSuite = new TestSuite("Large Clob suite");
+            largeSuite.addTest(new ClobAccessTest(
+                    "testFetchLargeClobs", iters, reps));
+            largeSuite.addTest(new ClobAccessTest(
+                    "testFetchLargeClobsModified", iters, reps));
+            largeSuite.addTest(new ClobAccessTest(
+                    "testFetchLargeClobWithStream", iters, reps));
+            largeSuite.addTest(new ClobAccessTest(
+                    "testFetchLargeClobOneByOneCharBaseline", iters, reps));
+            largeSuite.addTest(new ClobAccessTest(
+                    "testFetchLargeClobOneByOneCharModified", iters, reps));
+            largeSuite.addTest(new ClobAccessTest(
+                    "testFetchLargeClobOneByOneChar", iters, reps));
+            largeSuite.addTest(new ClobAccessTest(
+                    "testFetchLargeClobPieceByPiece", iters, reps));
+            largeSuite.addTest(new ClobAccessTest(
+                    "testFetchLargeClobPieceByPieceModified", iters, reps));
+            largeSuite.addTest(new ClobAccessTest(
+                    "testLargeClobGetLength", iters, reps));
+            largeSuite.addTest(new ClobAccessTest(
+                    "testFetchLargeClobPieceByPieceBackwards", iters, reps));
+            mainSuite.addTest(largeSuite);
+        }
+        if (!disableConcurrencyTest) {
+            mainSuite.addTest(new ClobAccessTest("testConcurrency", 1, 1));
+        }
+        return new CleanDatabaseTestSetup(mainSuite) {
+            protected void decorateSQL(Statement stmt)
+                    throws SQLException {
+                initializeClobData(stmt);
+            }
+        };
+    }
+
+    /**
+     * Fetches a number of small Clobs, getting the content using getSubString.
+     * <p>
+     * The exact length of the clob is used when getting the string.
+     */
+    public void testFetchSmallClobs()
+            throws SQLException {
+        PreparedStatement ps = prepareStatement(
+                "select dClob, length from smallClobs");
+        ResultSet rs = ps.executeQuery();
+        while (rs.next()) {
+            Clob clob = rs.getClob(1);
+            int clobLength = rs.getInt(2);
+            String content = clob.getSubString(1, clobLength);
+        }
+        rs.close();
+    }
+
+    /**
+     * Fetches a number of small Clobs, getting the content using getSubString.
+     * <p>
+     * A too long length of the clob is used when getting the string.
+     */
+    public void testFetchSmallClobsInaccurateLength()
+            throws SQLException {
+        PreparedStatement ps = prepareStatement(
+                "select dClob, length from smallClobs");
+        ResultSet rs = ps.executeQuery();
+        while (rs.next()) {
+            Clob clob = rs.getClob(1);
+            int unusedLength = rs.getInt(2);
+            String content = clob.getSubString(1, 100);
+        }
+        rs.close();
+    }
+
+    /**
+     * Test fetching the content after adding a single character at the end.
+     */
+    public void testModifySmallClobs()
+            throws SQLException {
+        PreparedStatement ps = prepareStatement(
+                "select dClob, length from smallClobs");
+        ResultSet rs = ps.executeQuery();
+        while (rs.next()) {
+            Clob clob = rs.getClob(1);
+            int length = rs.getInt(2);
+            clob.setString(length, "X");
+            String content = clob.getSubString(1, 100);
+        }
+        rs.close();
+    }
+
+    public void testFetchLargeClobs()
+            throws IOException, SQLException {
+        PreparedStatement ps = prepareStatement(
+                "select dClob, length from largeClobs");
+        ResultSet rs = ps.executeQuery();
+        char[] charBuf = new char[16*1024]; // 16 KB
+        while (rs.next()) {
+            Clob clob = rs.getClob(1);
+            Reader content = clob.getCharacterStream();
+            long remaining = rs.getInt(2);
+            while (remaining > 0) {
+                remaining -= content.read(charBuf);
+            }
+            content.close();
+        }
+        rs.close();
+    }
+
+    public void testFetchLargeClobsModified()
+            throws IOException, SQLException {
+        PreparedStatement ps = prepareStatement(
+                "select dClob, length from largeClobs");
+        ResultSet rs = ps.executeQuery();
+        char[] charBuf = new char[16*1024]; // 16 KB
+        while (rs.next()) {
+            Clob clob = rs.getClob(1);
+            clob.setString(1, "X");
+            Reader content = clob.getCharacterStream();
+            long remaining = rs.getInt(2);
+            while (remaining > 0) {
+                remaining -= content.read(charBuf);
+            }
+            content.close();
+        }
+        rs.close();
+    }
+
+    /**
+     * Fetches a single Clob and reads it char by char, but utilizing a
+     * buffered stream to get a lower time bound on the read operation.
+     */
+    public void testFetchLargeClobOneByOneCharBaseline()
+            throws IOException, SQLException {
+        // Select just one Clob.
+        PreparedStatement ps = prepareStatement(
+                "select dClob, length from largeClobs where id = 4");
+        ResultSet rs = ps.executeQuery();
+        while (rs.next()) {
+            Clob clob = rs.getClob(1);
+            Reader content = clob.getCharacterStream();
+            BufferedReader bufferedContent = new BufferedReader(content);
+            long remaining = rs.getInt(2);
+            while (bufferedContent.read() != -1) {
+                remaining--;
+            }
+            content.close();
+            assertEquals(0, remaining);
+        }
+        rs.close();
+    }
+
+    public void testFetchLargeClobOneByOneChar()
+            throws IOException, SQLException {
+        // Select just one Clob.
+        PreparedStatement ps = prepareStatement(
+                "select dClob, length from largeClobs where id = 4");
+        ResultSet rs = ps.executeQuery();
+        while (rs.next()) {
+            Clob clob = rs.getClob(1);
+            Reader content = clob.getCharacterStream();
+            long remaining = rs.getInt(2);
+            while (content.read() != -1) {
+                remaining--;
+            }
+            content.close();
+            assertEquals(0, remaining);
+        }
+        rs.close();
+    }
+
+    public void testFetchLargeClobOneByOneCharModified()
+            throws IOException, SQLException {
+        // Select just one Clob.
+        PreparedStatement ps = prepareStatement(
+                "select dClob, length from largeClobs where id = 4");
+        ResultSet rs = ps.executeQuery();
+        while (rs.next()) {
+            Clob clob = rs.getClob(1);
+            long remaining = rs.getInt(2);
+            clob.setString(++remaining, "X");
+            Reader content = clob.getCharacterStream();
+            while (content.read() != -1) {
+                remaining --;
+            }
+            content.close();
+            assertEquals(0, remaining);
+        }
+        rs.close();
+    }
+
+    /**
+     * Tests that repositioning within the current internal character buffer is
+     * cheap.
+     * <p>
+     * Note that the positions used in this test have been chosen based on the
+     * internal buffer size (8KB), which is an implementation detail.
+     *
+     * @throws SQLException if the test fails
+     */
+    public void testFetchLargeClobPieceByPieceBackwards()
+            throws IOException, SQLException {
+        boolean modifyClob = false;
+        final int intBufSize = 8192; // Implementation detail.
+        // Select just one Clob.
+        PreparedStatement ps = prepareStatement(
+                "select dClob, length from largeClobs where id = 4");
+        ResultSet rs = ps.executeQuery();
+        while (rs.next()) {
+            Clob clob = rs.getClob(1);
+            int remaining = rs.getInt(2);
+            if (modifyClob) {
+                // Modify the Clob to create a temporary copy in memory or on
+                // disk (depends on the Clob size).
+                long modifyStart = System.currentTimeMillis();
+                clob.setString(++remaining, "X");
+                println("Clob modification duration: " +
+                        (System.currentTimeMillis() - modifyStart) + " ms");
+            }
+            // Go close to the middle of the Clob on a buffer border, then
+            // subtract the piece size to avoid repositioning.
+            final int pieceSize = 10;
+            final long pos = (remaining / 2 / intBufSize) *
+                    intBufSize - pieceSize;
+            for (int i=0; i < intBufSize; i += pieceSize) {
+                String str = clob.getSubString(
+                        pos -i, pieceSize);
+            }
+        }
+        rs.close();
+    }
+
+    /**
+     * Fetches a "large" Clob piece by piece using getSubString.
+     */
+    public void testFetchLargeClobPieceByPiece()
+            throws IOException, SQLException {
+        fetchPieceByPiece(false);
+    }
+
+    /**
+     * Fetches a "large" Clob piece by piece using getSubString.
+     * <p>
+     * The Clob is modified before fetched to create a temporary Clob
+     * representation in memory / on disk.
+     */
+    public void testFetchLargeClobPieceByPieceModified()
+            throws IOException, SQLException {
+        fetchPieceByPiece(true);
+    }
+    
+    /**
+     * Fetches a "large" Clob piece by piece using getSubString.
+     *
+     * @param modifyClob whether to modify the Clob before fetching it
+     *      (determines the internal Derby Clob representation)
+     */
+    private void fetchPieceByPiece(final boolean modifyClob)
+            throws IOException, SQLException {
+        // Select just one Clob.
+        PreparedStatement ps = prepareStatement(
+                "select dClob, length from largeClobs where id = 4");
+        ResultSet rs = ps.executeQuery();
+        while (rs.next()) {
+            Clob clob = rs.getClob(1);
+            int remaining = rs.getInt(2);
+            Reader myReader = new LoopingAlphabetReader(remaining);
+            if (modifyClob) {
+                // Modify the Clob to create a temporary copy in memory or on
+                // disk (depends on the Clob size).
+                long modifyStart = System.currentTimeMillis();
+                clob.setString(++remaining, "X");
+                println("Clob modification duration: " +
+                        (System.currentTimeMillis() - modifyStart) + " ms");
+            }
+            long pos = 1;
+            while (remaining > 0) {
+                String str = clob.getSubString(
+                        pos, Math.min(MAX_BSIZE, remaining));
+                myReader.skip(Math.min(MAX_BSIZE, remaining) -1);
+                pos += str.length();
+                remaining -= str.length();
+                // Avoid failure on the last char when Clob is modified.
+                if (!modifyClob || remaining != 0) {
+                    assertEquals(myReader.read(), str.charAt(str.length() -1));
+                }
+            }
+        }
+        rs.close();
+    }
+
+    public void testFetchLargeClobWithStream()
+            throws IOException, SQLException {
+        boolean modifyClob = false;
+        // Select just one Clob.
+        PreparedStatement ps = prepareStatement(
+                "select dClob, length from largeClobs where id = 5");
+        ResultSet rs = ps.executeQuery();
+        while (rs.next()) {
+            Clob clob = rs.getClob(1);
+            int remaining = rs.getInt(2);
+            Reader myReader = new LoopingAlphabetReader(remaining);
+            if (modifyClob) {
+                // Modify the Clob to create a temporary copy in memory or on
+                // disk (depends on the Clob size).
+                long modifyStart = System.currentTimeMillis();
+                clob.setString(++remaining, "X");
+                println("Clob modification duration: " +
+                        (System.currentTimeMillis() - modifyStart) + " ms");
+            }
+            Reader clobReader = clob.getCharacterStream();
+            char[] buf = new char[MAX_BSIZE];
+            while (remaining > 0) {
+                int read = clobReader.read(buf, 0, Math.min(MAX_BSIZE, remaining));
+                myReader.skip(read -1);
+                remaining -= read;
+                assertEquals(myReader.read(), buf[read -1]);
+            }
+        }
+        rs.close();
+        
+    }
+
+    /**
+     * Tests if the Clob length is cached.
+     */
+    public void testLargeClobGetLength() throws SQLException {
+        // Select just one Clob.
+        PreparedStatement ps = prepareStatement(
+                "select dClob, length from largeClobs where id = 7");
+        ResultSet rs = ps.executeQuery();
+        while (rs.next()) {
+            Clob clob = rs.getClob(1);
+            long remaining = rs.getInt(2);
+            // This should be cached. Lots of data have to be skipped otherwise.
+            for (int i=0; i < 50; i++) {
+                assertEquals(remaining, clob.length());
+            }
+        }
+        rs.close();
+    }
+
+    /**
+     * Tests if the Clob length is cached.
+     */
+    public void testLargeClobGetLengthModified() throws SQLException {
+        // Select just one Clob.
+        PreparedStatement ps = prepareStatement(
+                "select dClob, length from largeClobs where id = 7");
+        ResultSet rs = ps.executeQuery();
+        while (rs.next()) {
+            Clob clob = rs.getClob(1);
+            clob.setString(1, "X");
+            long remaining = rs.getInt(2);
+            // This should be cached. Lots of data have to be skipped otherwise.
+            for (int i=0; i < 50; i++) {
+                assertEquals(remaining, clob.length());
+            }
+        }
+        rs.close();
+    }
+
+    /**
+     * Runs a test using multiple threads.
+     * <p>
+     * This test intends to detect problems with small Clobs and general
+     * problems with concurrency.
+     * <p>
+     * <b>NOTE</b>: To produce more reliable numbers, please run the performance
+     * client independently outside this JUnit test framework. Performance also
+     * suffers greatly with SANE builds.
+     */
+    public void testConcurrency()
+            throws InterruptedException, SQLException {
+
+        final int records = 100000;
+        final int tables = 1;
+        final int threads = 16;
+        DBFiller filler = new SingleRecordFiller(
+                records, tables, java.sql.Types.CLOB, false, false);
+        Connection conn = getConnection();
+        println("initializing database...");
+        filler.fill(conn);
+        conn.close();
+
+        Client[] clients = new Client[threads];
+        for (int i = 0; i < clients.length; i++) {
+            Connection c = openDefaultConnection();
+            c.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
+            clients[i] = new SingleRecordSelectClient(
+                    records, tables, java.sql.Types.CLOB, false, false);
+            clients[i].init(c);
+        }
+
+        final int warmupSec = 30;
+        final int steadySec = 60;
+        LoadGenerator gen = new BackToBackLoadGenerator();
+        gen.init(clients);
+        println("starting warmup...");
+        gen.startWarmup();
+        Thread.sleep(1000L * warmupSec);
+        println("entering steady state...");
+        gen.startSteadyState();
+        Thread.sleep(1000L * steadySec);
+        println("stopping threads...");
+        gen.stop();
+        // Should get the printstream used by the test harness here.
+        gen.printReport(System.out);
+    }
+
+    /**
+     * Generates test data.
+     */
+    private static void initializeClobData(Statement stmt)
+            throws SQLException {
+        Connection con = stmt.getConnection();
+        con.setAutoCommit(false);
+        if (!disableSmallClobs) {
+            println("Generating small Clobs test data.");
+            // Insert small Clob data.
+            try {
+                stmt.executeUpdate("drop table smallClobs");
+            } catch (SQLException sqle) {
+                assertSQLState("42Y55", sqle);
+            }
+            stmt.executeUpdate(
+                    "create table smallClobs (dClob clob, length int)");
+            PreparedStatement smallClobInsert = con.prepareStatement(
+                    "insert into smallClobs values (?,?)");
+            // Insert 15 000 small clobs.
+            for (int clobCounter = 1; clobCounter < 15001; clobCounter++) {
+                String content = Integer.toString(clobCounter);
+                smallClobInsert.setString(1, content);
+                smallClobInsert.setInt(2, content.length());
+                smallClobInsert.executeUpdate();
+                if (clobCounter % 1000 == 0) {
+                    con.commit();
+                }
+            }
+            con.commit();
+        }
+
+        if (!disableLargeClobs) {
+            println("Generating large Clobs test data.");
+            // Insert large Clob data.
+            try {
+                stmt.executeUpdate("drop table largeClobs");
+            } catch (SQLException sqle) {
+                assertSQLState("42Y55", sqle);
+            }
+            stmt.executeUpdate("create table largeClobs (" +
+                    "id int unique not null, dClob clob, length int)");
+            PreparedStatement largeClobInsert = con.prepareStatement(
+                    "insert into largeClobs values (?,?,?)");
+            // Insert some large Clobs.
+            final int size = largeClobSizeMB*1024*1024; // 15 MB default
+            for (int clobCounter = 1; clobCounter < 11; clobCounter++) {
+                largeClobInsert.setInt(1, clobCounter);
+                largeClobInsert.setCharacterStream(
+                        2, new LoopingAlphabetReader(size), size);
+                largeClobInsert.setInt(3, size);
+                largeClobInsert.executeUpdate();
+                println("Inserted large Clob #" + (clobCounter -1));
+            }
+            con.commit();
+        }
+    }
+}

Propchange: db/derby/code/trunk/java/testing/org/apache/derbyTesting/perf/basic/jdbc/ClobAccessTest.java
------------------------------------------------------------------------------
    svn:eol-style = native