| commit 747: | 3079a9923d75 |
| parent 746: | df23217d5e87 |
| branch: | default |
Bug #3624: History cache should be stored in Java DB
Performance optimization: Make sure that the index cardinality
statistics in the database are up to date after inserting lots of
data. Without this change, we have seen that get() could be very slow
because the optimizer picks a bad plan based on stale statistics.
This adds a call to a system procedure introduced in Derby 10.5 (which
is still not released). On older versions of Derby we don't call the
procedure and we may therefore still see the performance problems.
19 months ago
Changed (Δ3.2 KB):
raw changeset »
src/org/opensolaris/opengrok/history/JDBCHistoryCache.java (54 lines added, 0 lines removed)
src/org/opensolaris/opengrok/jdbc/ConnectionResource.java (26 lines added, 0 lines removed)
Up to file-list src/org/opensolaris/opengrok/history/JDBCHistoryCache.java:
| … | … | @@ -44,6 +44,11 @@ class JDBCHistoryCache implements Histor |
44 |
44 |
|
45 |
45 |
private static final String SCHEMA = "APP"; |
46 |
46 |
|
47 |
/** The names of all the tables created by this class. */ |
|
48 |
private static final String[] TABLES = { |
|
49 |
"REPOSITORIES", "FILES", "AUTHORS", "CHANGESETS", "FILECHANGES" |
|
50 |
}; |
|
51 |
||
47 |
52 |
private ConnectionManager connectionManager; |
48 |
53 |
|
49 |
54 |
// Many of the tables contain columns with identical names and types, |
| … | … | @@ -263,6 +268,13 @@ class JDBCHistoryCache implements Histor |
263 |
268 |
connectionManager.getConnectionResource(); |
264 |
269 |
try { |
265 |
270 |
storeHistory(conn, history, repository); |
271 |
// The tables may have grown significantly and made the |
|
272 |
// index cardinality statistics outdated. Call this method |
|
273 |
// as a workaround to keep the statistics up to date. |
|
274 |
// Without this, we have observed very bad performance when |
|
275 |
// calling get(), because the outdated statistics can make |
|
276 |
// the optimizer choose a sub-optimal join strategy. |
|
277 |
updateIndexCardinalityStatistics(conn); |
|
266 |
278 |
} finally { |
267 |
279 |
connectionManager.releaseConnection(conn); |
268 |
280 |
} |
| … | … | @@ -315,6 +327,48 @@ class JDBCHistoryCache implements Histor |
315 |
327 |
} |
316 |
328 |
|
317 |
329 |
/** |
330 |
* <p> |
|
331 |
* Make sure Derby's index cardinality statistics are up to date. |
|
332 |
* Otherwise, the optimizer may choose a bad execution strategy for |
|
333 |
* some queries. This method should be called if the size of the tables |
|
334 |
* has changed significantly. |
|
335 |
* </p> |
|
336 |
* |
|
337 |
* <p> |
|
338 |
* This is a workaround for the problems described in |
|
339 |
* <a href="https://issues.apache.org/jira/browse/DERBY-269">DERBY-269</a> and |
|
340 |
* <a href="https://issues.apache.org/jira/browse/DERBY-3788">DERBY-3788</a>. |
|
341 |
* When automatic update of index cardinality statistics has been |
|
342 |
* implemented in Derby, the workaround may be removed. |
|
343 |
* </p> |
|
344 |
* |
|
345 |
* <p> |
|
346 |
* Note that this method uses a system procedure introduced in Derby 10.5. |
|
347 |
* If the Derby version used is less than 10.5, this method is a no-op. |
|
348 |
* </p> |
|
349 |
*/ |
|
350 |
private void updateIndexCardinalityStatistics(ConnectionResource conn) |
|
351 |
throws SQLException { |
|
352 |
DatabaseMetaData dmd = conn.getMetaData(); |
|
353 |
int major = dmd.getDatabaseMajorVersion(); |
|
354 |
int minor = dmd.getDatabaseMinorVersion(); |
|
355 |
if (major > 10 || (major == 10 && minor >= 5)) { |
|
356 |
PreparedStatement ps = conn.prepareStatement( |
|
357 |
"CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS(?, ?, NULL)"); |
|
358 |
try { |
|
359 |
ps.setString(1, SCHEMA); |
|
360 |
for (String table : TABLES) { |
|
361 |
ps.setString(2, table); |
|
362 |
ps.execute(); |
|
363 |
} |
|
364 |
conn.commit(); |
|
365 |
} finally { |
|
366 |
ps.close(); |
|
367 |
} |
|
368 |
} |
|
369 |
} |
|
370 |
||
371 |
/** |
|
318 |
372 |
* Get the id of a repository in the database. If the repository is not |
319 |
373 |
* stored in the database, add it and return its id. |
320 |
374 |
* |
Up to file-list src/org/opensolaris/opengrok/jdbc/ConnectionResource.java:
25 |
25 |
package org.opensolaris.opengrok.jdbc; |
26 |
26 |
|
27 |
27 |
import java.sql.Connection; |
28 |
import java.sql.DatabaseMetaData; |
|
28 |
29 |
import java.sql.PreparedStatement; |
29 |
30 |
import java.sql.SQLException; |
30 |
31 |
import java.sql.Statement; |
| … | … | @@ -104,4 +105,29 @@ public class ConnectionResource { |
104 |
105 |
public Statement createStatement() throws SQLException { |
105 |
106 |
return conn.createStatement(); |
106 |
107 |
} |
108 |
||
109 |
/** |
|
110 |
* Prepare a statement. This method should only be used to prepare |
|
111 |
* statements that are used infrequently. If a statement is likely to |
|
112 |
* be used frequently, a {@code StatementCreator} object should be |
|
113 |
* created for it, and the method {@link #getStatement(StatementCreator)} |
|
114 |
* should be used instead. |
|
115 |
* |
|
116 |
* @param sql the SQL text to compile |
|
117 |
* @return a prepared statement |
|
118 |
* @throws SQLException if a database error occurs |
|
119 |
*/ |
|
120 |
public PreparedStatement prepareStatement(String sql) throws SQLException { |
|
121 |
return conn.prepareStatement(sql); |
|
122 |
} |
|
123 |
||
124 |
/** |
|
125 |
* Get a meta-data object for the underlying connection. |
|
126 |
* |
|
127 |
* @return a {@code DatabaseMetaData} object |
|
128 |
* @throws SQLException if a database error occurs |
|
129 |
*/ |
|
130 |
public DatabaseMetaData getMetaData() throws SQLException { |
|
131 |
return conn.getMetaData(); |
|
132 |
} |
|
107 |
133 |
} |
