Return-Path: Delivered-To: apmail-db-derby-commits-archive@www.apache.org Received: (qmail 48931 invoked from network); 8 Oct 2008 12:14:14 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.2) by minotaur.apache.org with SMTP; 8 Oct 2008 12:14:14 -0000 Received: (qmail 85798 invoked by uid 500); 8 Oct 2008 12:14:13 -0000 Delivered-To: apmail-db-derby-commits-archive@db.apache.org Received: (qmail 85776 invoked by uid 500); 8 Oct 2008 12:14:13 -0000 Mailing-List: contact derby-commits-help@db.apache.org; run by ezmlm Precedence: bulk list-help: list-unsubscribe: List-Post: Reply-To: "Derby Development" List-Id: Delivered-To: mailing list derby-commits@db.apache.org Received: (qmail 85767 invoked by uid 99); 8 Oct 2008 12:14:13 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 08 Oct 2008 05:14:13 -0700 X-ASF-Spam-Status: No, hits=-1999.9 required=10.0 tests=ALL_TRUSTED,DNS_FROM_SECURITYSAGE X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 08 Oct 2008 12:13:16 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id AC9662388882; Wed, 8 Oct 2008 05:13:22 -0700 (PDT) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r702818 - /db/derby/code/trunk/java/testing/org/apache/derbyTesting/perf/basic/jdbc/ClobAccessTest.java Date: Wed, 08 Oct 2008 12:13:22 -0000 To: derby-commits@db.apache.org From: kristwaa@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20081008121322.AC9662388882@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * System properties controlling test behavior: + *

  • derby.tests.disableSmallClobs
  • + *
  • derby.tests.disableLargeClobs
  • + *
  • derby.tests.disableConcurrencyTest
  • + *
  • derby.tests.largeClobSize (in MB, 15 is the default)
  • + * + * + *

    + * NOTE: 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * This test intends to detect problems with small Clobs and general + * problems with concurrency. + *

    + * NOTE: 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