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

Fix for #88021 high GC pressure when driver configured with serversid… #25

Closed
wants to merge 1 commit into
base: release/5.1
from

Conversation

Projects
None yet
2 participants
@johnou

johnou commented Oct 7, 2017

…eprepared statements.

While profiling our application I found high GC pressure coming from the mysql driver [1]. The issue arises when the driver is configured to use serverside prepared statements and the driver recreates the cache key for every query (sb.toString() invoking Arrays.copyOfRange) [2].

[1]

Stack Trace    TLABs    Total TLAB Size(bytes)    Pressure(%)
com.mysql.jdbc.ConnectionImpl.makePreparedStatementCacheKey(String, String)    16,810    1,519,493,352

[2]

private String makePreparedStatementCacheKey(String catalog, String query) {
    StringBuilder key = new StringBuilder();
    key.append("/*").append(catalog).append("*/");
    key.append(query);
    return key.toString();
}
@johnou

This comment has been minimized.

Show comment
Hide comment
@johnou

johnou Oct 8, 2017

Here is the benchmark code I used to verify increased throughput with the improved CompoundCacheKey (removed string concat from the constructor) vs the String key constructed with a StringBuilder.

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;

/**
 * @author Johno Crawford (johno.crawford@gmail.com)
 */
@State(Scope.Thread)
@Fork(5)
public class AllocationTest {

    private String makePreparedStatementCacheKey(String catalog, String query) {
        StringBuilder key = new StringBuilder();
        key.append("/*").append(catalog).append("*/");
        key.append(query);
        return key.toString();
    }

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    public void testCompoundCacheKeyNewHashCode() throws Exception {
        new CompoundCacheKey("test", "select this_.id as id1_13_0_, this_.cost as cost2_13_0_, this_.local_currency as local_cu3_13_0_, this_.player_id as player_i4_13_0_, this_.receipt_data as receipt_5_13_0_, this_.retry_attempts as retry_at6_13_0_, this_.transaction_id as transact7_13_0_ from appstore_retry_queue this_");
    }

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    public void testCompoundCacheKeyOldHashCode() throws Exception {
        new CompoundCacheKeyOldHashCode("test", "select this_.id as id1_13_0_, this_.cost as cost2_13_0_, this_.local_currency as local_cu3_13_0_, this_.player_id as player_i4_13_0_, this_.receipt_data as receipt_5_13_0_, this_.retry_attempts as retry_at6_13_0_, this_.transaction_id as transact7_13_0_ from appstore_retry_queue this_");
    }

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    public void testSimpleKey() throws Exception {
        new SimpleKey("test", "select this_.id as id1_13_0_, this_.cost as cost2_13_0_, this_.local_currency as local_cu3_13_0_, this_.player_id as player_i4_13_0_, this_.receipt_data as receipt_5_13_0_, this_.retry_attempts as retry_at6_13_0_, this_.transaction_id as transact7_13_0_ from appstore_retry_queue this_");
    }

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    public void testStringBuilder() throws Exception {
        makePreparedStatementCacheKey("test", "select this_.id as id1_13_0_, this_.cost as cost2_13_0_, this_.local_currency as local_cu3_13_0_, this_.player_id as player_i4_13_0_, this_.receipt_data as receipt_5_13_0_, this_.retry_attempts as retry_at6_13_0_, this_.transaction_id as transact7_13_0_ from appstore_retry_queue this_");
    }

    static class CompoundCacheKeyOldHashCode {
        String componentOne;

        String componentTwo;

        int hashCode;

        CompoundCacheKeyOldHashCode(String partOne, String partTwo) {
            this.componentOne = partOne;
            this.componentTwo = partTwo;
            // Handle first component (in most cases, currentCatalog being NULL....
            this.hashCode = (((this.componentOne != null) ? this.componentOne : "") + this.componentTwo).hashCode();
        }

        /*
         * (non-Javadoc)
         *
         * @see java.lang.Object#equals(java.lang.Object)
         */
        @Override
        public boolean equals(Object obj) {
            if (obj instanceof CompoundCacheKeyOldHashCode) {
                CompoundCacheKeyOldHashCode another = (CompoundCacheKeyOldHashCode) obj;

                boolean firstPartEqual = false;

                if (this.componentOne == null) {
                    firstPartEqual = (another.componentOne == null);
                } else {
                    firstPartEqual = this.componentOne.equals(another.componentOne);
                }

                return (firstPartEqual && this.componentTwo.equals(another.componentTwo));
            }

            return false;
        }

        /*
         * (non-Javadoc)
         *
         * @see java.lang.Object#hashCode()
         */
        @Override
        public int hashCode() {
            return this.hashCode;
        }
    }

    static class CompoundCacheKey {
        final String componentOne;

        final String componentTwo;

        final int hashCode;

        CompoundCacheKey(String partOne, String partTwo) {
            this.componentOne = partOne;
            this.componentTwo = partTwo;
            // Handle first component (in most cases, currentCatalog being NULL....
            this.hashCode = 31 * (componentOne != null ? componentOne.hashCode() : 0) + (componentTwo != null ? componentTwo.hashCode() : 0);
        }

        /*
         * (non-Javadoc)
         *
         * @see java.lang.Object#equals(java.lang.Object)
         */
        @Override
        public boolean equals(Object obj) {
            if (obj instanceof CompoundCacheKey) {
                CompoundCacheKey another = (CompoundCacheKey) obj;

                boolean firstPartEqual = false;

                if (this.componentOne == null) {
                    firstPartEqual = (another.componentOne == null);
                } else {
                    firstPartEqual = this.componentOne.equals(another.componentOne);
                }

                return (firstPartEqual && this.componentTwo.equals(another.componentTwo));
            }

            return false;
        }

        /*
         * (non-Javadoc)
         *
         * @see java.lang.Object#hashCode()
         */
        @Override
        public int hashCode() {
            return this.hashCode;
        }
    }

    private final class SimpleKey {
        private final String catalog;
        private final String query;

        public SimpleKey(String catalog, String query) {
            this.catalog = catalog;
            this.query = query;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            SimpleKey simpleKey = (SimpleKey) o;

            if (catalog != null ? !catalog.equals(simpleKey.catalog) : simpleKey.catalog != null) return false;
            return query != null ? query.equals(simpleKey.query) : simpleKey.query == null;
        }

        @Override
        public int hashCode() {
            int result = catalog != null ? catalog.hashCode() : 0;
            result = 31 * result + (query != null ? query.hashCode() : 0);
            return result;
        }
    }
}
Benchmark                                        Mode  Cnt           Score          Error  Units
AllocationTest.testCompoundCacheKeyNewHashCode  thrpt  100  1471082435.003 ± 30337517.290  ops/s
AllocationTest.testCompoundCacheKeyOldHashCode  thrpt  100     2950018.596 ±     8728.735  ops/s
AllocationTest.testSimpleKey                    thrpt  100  3778832816.385 ± 18671795.473  ops/s
AllocationTest.testStringBuilder                thrpt  100     5881551.927 ±    47208.040  ops/s

johnou commented Oct 8, 2017

Here is the benchmark code I used to verify increased throughput with the improved CompoundCacheKey (removed string concat from the constructor) vs the String key constructed with a StringBuilder.

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;

/**
 * @author Johno Crawford (johno.crawford@gmail.com)
 */
@State(Scope.Thread)
@Fork(5)
public class AllocationTest {

    private String makePreparedStatementCacheKey(String catalog, String query) {
        StringBuilder key = new StringBuilder();
        key.append("/*").append(catalog).append("*/");
        key.append(query);
        return key.toString();
    }

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    public void testCompoundCacheKeyNewHashCode() throws Exception {
        new CompoundCacheKey("test", "select this_.id as id1_13_0_, this_.cost as cost2_13_0_, this_.local_currency as local_cu3_13_0_, this_.player_id as player_i4_13_0_, this_.receipt_data as receipt_5_13_0_, this_.retry_attempts as retry_at6_13_0_, this_.transaction_id as transact7_13_0_ from appstore_retry_queue this_");
    }

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    public void testCompoundCacheKeyOldHashCode() throws Exception {
        new CompoundCacheKeyOldHashCode("test", "select this_.id as id1_13_0_, this_.cost as cost2_13_0_, this_.local_currency as local_cu3_13_0_, this_.player_id as player_i4_13_0_, this_.receipt_data as receipt_5_13_0_, this_.retry_attempts as retry_at6_13_0_, this_.transaction_id as transact7_13_0_ from appstore_retry_queue this_");
    }

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    public void testSimpleKey() throws Exception {
        new SimpleKey("test", "select this_.id as id1_13_0_, this_.cost as cost2_13_0_, this_.local_currency as local_cu3_13_0_, this_.player_id as player_i4_13_0_, this_.receipt_data as receipt_5_13_0_, this_.retry_attempts as retry_at6_13_0_, this_.transaction_id as transact7_13_0_ from appstore_retry_queue this_");
    }

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    public void testStringBuilder() throws Exception {
        makePreparedStatementCacheKey("test", "select this_.id as id1_13_0_, this_.cost as cost2_13_0_, this_.local_currency as local_cu3_13_0_, this_.player_id as player_i4_13_0_, this_.receipt_data as receipt_5_13_0_, this_.retry_attempts as retry_at6_13_0_, this_.transaction_id as transact7_13_0_ from appstore_retry_queue this_");
    }

    static class CompoundCacheKeyOldHashCode {
        String componentOne;

        String componentTwo;

        int hashCode;

        CompoundCacheKeyOldHashCode(String partOne, String partTwo) {
            this.componentOne = partOne;
            this.componentTwo = partTwo;
            // Handle first component (in most cases, currentCatalog being NULL....
            this.hashCode = (((this.componentOne != null) ? this.componentOne : "") + this.componentTwo).hashCode();
        }

        /*
         * (non-Javadoc)
         *
         * @see java.lang.Object#equals(java.lang.Object)
         */
        @Override
        public boolean equals(Object obj) {
            if (obj instanceof CompoundCacheKeyOldHashCode) {
                CompoundCacheKeyOldHashCode another = (CompoundCacheKeyOldHashCode) obj;

                boolean firstPartEqual = false;

                if (this.componentOne == null) {
                    firstPartEqual = (another.componentOne == null);
                } else {
                    firstPartEqual = this.componentOne.equals(another.componentOne);
                }

                return (firstPartEqual && this.componentTwo.equals(another.componentTwo));
            }

            return false;
        }

        /*
         * (non-Javadoc)
         *
         * @see java.lang.Object#hashCode()
         */
        @Override
        public int hashCode() {
            return this.hashCode;
        }
    }

    static class CompoundCacheKey {
        final String componentOne;

        final String componentTwo;

        final int hashCode;

        CompoundCacheKey(String partOne, String partTwo) {
            this.componentOne = partOne;
            this.componentTwo = partTwo;
            // Handle first component (in most cases, currentCatalog being NULL....
            this.hashCode = 31 * (componentOne != null ? componentOne.hashCode() : 0) + (componentTwo != null ? componentTwo.hashCode() : 0);
        }

        /*
         * (non-Javadoc)
         *
         * @see java.lang.Object#equals(java.lang.Object)
         */
        @Override
        public boolean equals(Object obj) {
            if (obj instanceof CompoundCacheKey) {
                CompoundCacheKey another = (CompoundCacheKey) obj;

                boolean firstPartEqual = false;

                if (this.componentOne == null) {
                    firstPartEqual = (another.componentOne == null);
                } else {
                    firstPartEqual = this.componentOne.equals(another.componentOne);
                }

                return (firstPartEqual && this.componentTwo.equals(another.componentTwo));
            }

            return false;
        }

        /*
         * (non-Javadoc)
         *
         * @see java.lang.Object#hashCode()
         */
        @Override
        public int hashCode() {
            return this.hashCode;
        }
    }

    private final class SimpleKey {
        private final String catalog;
        private final String query;

        public SimpleKey(String catalog, String query) {
            this.catalog = catalog;
            this.query = query;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            SimpleKey simpleKey = (SimpleKey) o;

            if (catalog != null ? !catalog.equals(simpleKey.catalog) : simpleKey.catalog != null) return false;
            return query != null ? query.equals(simpleKey.query) : simpleKey.query == null;
        }

        @Override
        public int hashCode() {
            int result = catalog != null ? catalog.hashCode() : 0;
            result = 31 * result + (query != null ? query.hashCode() : 0);
            return result;
        }
    }
}
Benchmark                                        Mode  Cnt           Score          Error  Units
AllocationTest.testCompoundCacheKeyNewHashCode  thrpt  100  1471082435.003 ± 30337517.290  ops/s
AllocationTest.testCompoundCacheKeyOldHashCode  thrpt  100     2950018.596 ±     8728.735  ops/s
AllocationTest.testSimpleKey                    thrpt  100  3778832816.385 ± 18671795.473  ops/s
AllocationTest.testStringBuilder                thrpt  100     5881551.927 ±    47208.040  ops/s
@mysql-oca-bot

This comment has been minimized.

Show comment
Hide comment
@mysql-oca-bot

mysql-oca-bot Oct 8, 2017

Hi, thank you for submitting this pull request. In order to consider your code we need you to sign the Oracle Contribution Agreement (OCA). Please review the details and follow the instructions at http://www.oracle.com/technetwork/community/oca-486395.html
Please make sure to include your MySQL bug system user (email) in the returned form.
Thanks

mysql-oca-bot commented Oct 8, 2017

Hi, thank you for submitting this pull request. In order to consider your code we need you to sign the Oracle Contribution Agreement (OCA). Please review the details and follow the instructions at http://www.oracle.com/technetwork/community/oca-486395.html
Please make sure to include your MySQL bug system user (email) in the returned form.
Thanks

@mysql-oca-bot

This comment has been minimized.

Show comment
Hide comment
@mysql-oca-bot

mysql-oca-bot Oct 10, 2017

Hi, thank you for your contribution. Please confirm this code is submitted under the terms of the OCA (Oracle's Contribution Agreement) you have previously signed by cutting and pasting the following text as a comment:
"I confirm the code being submitted is offered under the terms of the OCA, and that I am authorized to contribute it."
Thanks

mysql-oca-bot commented Oct 10, 2017

Hi, thank you for your contribution. Please confirm this code is submitted under the terms of the OCA (Oracle's Contribution Agreement) you have previously signed by cutting and pasting the following text as a comment:
"I confirm the code being submitted is offered under the terms of the OCA, and that I am authorized to contribute it."
Thanks

@johnou

This comment has been minimized.

Show comment
Hide comment
@johnou

johnou Oct 10, 2017

I confirm the code being submitted is offered under the terms of the OCA, and that I am authorized to contribute it.

johnou commented Oct 10, 2017

I confirm the code being submitted is offered under the terms of the OCA, and that I am authorized to contribute it.

@mysql-oca-bot

This comment has been minimized.

Show comment
Hide comment
@mysql-oca-bot

mysql-oca-bot Oct 10, 2017

Hi, thank you for your contribution. Your code has been assigned to an internal queue. Please follow
bug http://bugs.mysql.com/bug.php?id=88021 for updates.
Thanks

mysql-oca-bot commented Oct 10, 2017

Hi, thank you for your contribution. Your code has been assigned to an internal queue. Please follow
bug http://bugs.mysql.com/bug.php?id=88021 for updates.
Thanks

@johnou johnou deleted the johnou:ssps_cache_perf branch Nov 9, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment