Author: reschke
Date: Wed May 13 14:10:36 2015
New Revision: 1679216

URL: http://svn.apache.org/r1679216
Log:
OAK-2860 - handle DATASTORE_DATA/DATASTORE_META inconsistencies more gracefully 
(ported to 1.2)

Modified:
    jackrabbit/oak/branches/1.2/   (props changed)
    
jackrabbit/oak/branches/1.2/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBBlobStore.java
    
jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/blob/RDBBlobStoreTest.java
    
jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBBlobStoreFriend.java

Propchange: jackrabbit/oak/branches/1.2/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Wed May 13 14:10:36 2015
@@ -1,3 +1,3 @@
 /jackrabbit/oak/branches/1.0:1665962
-/jackrabbit/oak/trunk:1672350,1672468,1672537,1672603,1672642,1672644,1672834-1672835,1673351,1673410,1673414,1673436,1673644,1673662-1673664,1673669,1673695,1674046,1674065,1674075,1674107,1674228,1674880,1675055,1675332,1675354,1675357,1675593,1676198,1676237,1676407,1676458,1676539,1676670,1676725,1677939,1678173,1678758,1679165
+/jackrabbit/oak/trunk:1672350,1672468,1672537,1672603,1672642,1672644,1672834-1672835,1673351,1673410,1673414,1673436,1673644,1673662-1673664,1673669,1673695,1674046,1674065,1674075,1674107,1674228,1674880,1675055,1675332,1675354,1675357,1675593,1676198,1676237,1676407,1676458,1676539,1676670,1676725,1677939,1678173,1678758,1678938,1679165
 /jackrabbit/trunk:1345480

Modified: 
jackrabbit/oak/branches/1.2/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBBlobStore.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBBlobStore.java?rev=1679216&r1=1679215&r2=1679216&view=diff
==============================================================================
--- 
jackrabbit/oak/branches/1.2/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBBlobStore.java
 (original)
+++ 
jackrabbit/oak/branches/1.2/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBBlobStore.java
 Wed May 13 14:10:36 2015
@@ -27,6 +27,7 @@ import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
@@ -128,11 +129,11 @@ public class RDBBlobStore extends Cachin
 
     private Exception callStack;
 
-    private RDBConnectionHandler ch;
+    protected RDBConnectionHandler ch;
 
     // from options
-    private String tnData;
-    private String tnMeta;
+    protected String tnData;
+    protected String tnMeta;
     private Set<String> tablesToBeDropped = new HashSet<String>();
 
     private static void versionCheck(DatabaseMetaData md, int xmaj, int xmin, 
String description) throws SQLException {
@@ -367,7 +368,7 @@ public class RDBBlobStore extends Cachin
             }
             if (count == 0) {
                 try {
-                    prep = con.prepareStatement("insert into " + this.tnData + 
"(ID, DATA) values(?, ?)");
+                    prep = con.prepareStatement("insert into " + this.tnData + 
" (ID, DATA) values(?, ?)");
                     try {
                         prep.setString(1, id);
                         prep.setBytes(2, data);
@@ -376,13 +377,39 @@ public class RDBBlobStore extends Cachin
                         prep.close();
                     }
                 } catch (SQLException ex) {
-                    // TODO: this code used to ignore exceptions here, 
assuming that it might be a case where the blob is already in the database 
(maybe this requires inspecting the exception code)
-                    String message = "insert document failed for id " + id + " 
with length " + data.length + " (check max size of datastore_data.data)";
-                    LOG.error(message, ex);
-                    throw new RuntimeException(message, ex);
+                    this.ch.rollbackConnection(con);
+                    // the insert failed although it should have succeeded; 
see whether the blob already exists
+                    prep = con.prepareStatement("select DATA from " + 
this.tnData + " where ID = ?");
+                    byte[] dbdata = null;
+                    try {
+                        prep.setString(1, id);
+                        ResultSet rs = prep.executeQuery();
+                        if (rs.next()) {
+                            dbdata = rs.getBytes(1);
+                        }
+                    } finally {
+                        prep.close();
+                    }
+
+                    if (dbdata == null) {
+                        // insert failed although record isn't there
+                        String message = "insert document failed for id " + id 
+ " with length " + data.length + " (check max size of datastore_data.data)";
+                        LOG.error(message, ex);
+                        throw new RuntimeException(message, ex);
+                    }
+                    else if (!Arrays.equals(data, dbdata)) {
+                        // record is there but contains different data
+                        String message = "DATA table already contains blob for 
id " + id + ", but the actual data differs (lengths: " + data.length + ", " + 
dbdata.length + ")";
+                        LOG.error(message, ex);
+                        throw new RuntimeException(message, ex);
+                    }
+                    else {
+                        // just recover
+                        LOG.info("recovered from DB inconsistency for id " + 
id + ": meta record was missing (impact will be minor performance 
degradation)");
+                    }
                 }
                 try {
-                    prep = con.prepareStatement("insert into " + this.tnMeta + 
"(ID, LVL, LASTMOD) values(?, ?, ?)");
+                    prep = con.prepareStatement("insert into " + this.tnMeta + 
" (ID, LVL, LASTMOD) values(?, ?, ?)");
                     try {
                         prep.setString(1, id);
                         prep.setInt(2, level);
@@ -393,6 +420,7 @@ public class RDBBlobStore extends Cachin
                     }
                 } catch (SQLException e) {
                     // already exists - ok
+                    LOG.debug("inserting meta record for id " + id, e);
                 }
             }
         } finally {

Modified: 
jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/blob/RDBBlobStoreTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/blob/RDBBlobStoreTest.java?rev=1679216&r1=1679215&r2=1679216&view=diff
==============================================================================
--- 
jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/blob/RDBBlobStoreTest.java
 (original)
+++ 
jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/blob/RDBBlobStoreTest.java
 Wed May 13 14:10:36 2015
@@ -46,7 +46,8 @@ public class RDBBlobStoreTest extends Ab
 
     private RDBBlobStore blobStore;
 
-    private static final String URL = System.getProperty("rdb.jdbc-url", 
"jdbc:h2:mem:oakblobs");
+    private static final String URL = System.getProperty("rdb.jdbc-url", 
+                    "jdbc:h2:file:./target/db/RDBBlobStoreTest");
 
     private static final String USERNAME = System.getProperty("rdb.jdbc-user", 
"sa");
 
@@ -57,7 +58,8 @@ public class RDBBlobStoreTest extends Ab
     @Before
     @Override
     public void setUp() throws Exception {
-        blobStore = new RDBBlobStore(RDBDataSourceFactory.forJdbcUrl(URL, 
USERNAME, PASSWD), new 
RDBOptions().tablePrefix("test").dropTablesOnClose(true));
+        blobStore = new RDBBlobStore(RDBDataSourceFactory.forJdbcUrl(URL, 
USERNAME, PASSWD), new RDBOptions().tablePrefix("test")
+                .dropTablesOnClose(true));
         blobStore.setBlockSize(128);
         blobStore.setBlockSizeMin(48);
         this.store = blobStore;
@@ -96,7 +98,7 @@ public class RDBBlobStoreTest extends Ab
             r.nextBytes(data);
             byte[] digest = getDigest(data);
             try {
-                RDBBlobStoreFriend.storeBlock(blobStore, getDigest(data), 0, 
data);
+                RDBBlobStoreFriend.storeBlock(blobStore, digest, 0, data);
                 byte[] data2 = 
RDBBlobStoreFriend.readBlockFromBackend(blobStore, digest);
                 if (!Arrays.equals(data, data2)) {
                     throw new Exception("data mismatch for length " + 
data.length);
@@ -113,6 +115,29 @@ public class RDBBlobStoreTest extends Ab
         assertTrue("expected supported block size is " + expected + ", but 
measured: " + test, test >= expected);
     }
 
+    @Test
+    public void testResilienceMissingMetaEntry() throws Exception {
+        int test = 1024 * 1024;
+        byte[] data = new byte[test];
+        Random r = new Random(0);
+        r.nextBytes(data);
+        byte[] digest = getDigest(data);
+        RDBBlobStoreFriend.storeBlock(blobStore, digest, 0, data);
+        byte[] data2 = RDBBlobStoreFriend.readBlockFromBackend(blobStore, 
digest);
+        if (!Arrays.equals(data, data2)) {
+            throw new Exception("data mismatch");
+        }
+
+        RDBBlobStoreFriend.killMetaEntry(blobStore, digest);
+
+        // retry
+        RDBBlobStoreFriend.storeBlock(blobStore, digest, 0, data);
+        byte[] data3 = RDBBlobStoreFriend.readBlockFromBackend(blobStore, 
digest);
+        if (!Arrays.equals(data, data3)) {
+            throw new Exception("data mismatch");
+        }
+    }
+
     private byte[] getDigest(byte[] bytes) throws IOException {
         MessageDigest messageDigest;
         try {

Modified: 
jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBBlobStoreFriend.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBBlobStoreFriend.java?rev=1679216&r1=1679215&r2=1679216&view=diff
==============================================================================
--- 
jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBBlobStoreFriend.java
 (original)
+++ 
jackrabbit/oak/branches/1.2/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBBlobStoreFriend.java
 Wed May 13 14:10:36 2015
@@ -17,6 +17,10 @@
 package org.apache.jackrabbit.oak.plugins.document.rdb;
 
 import java.io.IOException;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+
+import org.apache.jackrabbit.oak.commons.StringUtils;
 
 public class RDBBlobStoreFriend {
 
@@ -27,4 +31,21 @@ public class RDBBlobStoreFriend {
     public static byte[] readBlockFromBackend(RDBBlobStore ds, byte[] digest) 
throws Exception {
         return ds.readBlockFromBackend(digest);
     }
+
+    public static void killMetaEntry(RDBBlobStore ds, byte[] digest) throws 
Exception {
+        String id = StringUtils.convertBytesToHex(digest);
+        Connection con = ds.ch.getRWConnection();
+        PreparedStatement prepDelMeta = null;
+        try {
+            prepDelMeta = con.prepareStatement("delete from " + ds.tnMeta + " 
where ID = ?");
+            prepDelMeta.setString(1, id);
+            prepDelMeta.execute();
+            prepDelMeta.close();
+            prepDelMeta = null;
+        } finally {
+            ds.ch.closeStatement(prepDelMeta);
+            con.commit();
+            ds.ch.closeConnection(con);
+        }
+    }
 }


Reply via email to