https://issues.apache.org/jira/browse/AMQ-3374 - first stab at fixing long kahadb tx oom problem

git-svn-id: https://svn.apache.org/repos/asf/activemq/trunk@1138442 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Bosanac Dejan 2011-06-22 12:55:36 +00:00
parent 37384a1ad1
commit 40ae055f37
5 changed files with 191 additions and 117 deletions

View File

@ -19,6 +19,7 @@ package org.apache.activemq.usecases;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
import javax.jms.Connection; import javax.jms.Connection;
import javax.jms.MessageProducer;
import javax.jms.Session; import javax.jms.Session;
import javax.management.MBeanServer; import javax.management.MBeanServer;
import javax.management.ObjectName; import javax.management.ObjectName;
@ -43,7 +44,15 @@ public class DurableUnsubscribeTest extends org.apache.activemq.TestSupport {
Destination d = broker.getDestination(topic); Destination d = broker.getDestination(topic);
assertEquals("Subscription is missing.", 1, d.getConsumers().size()); assertEquals("Subscription is missing.", 1, d.getConsumers().size());
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(topic);
for (int i = 0; i < 1000; i++) {
producer.send(session.createTextMessage("text"));
}
Thread.sleep(1000);
session.unsubscribe("SubsId"); session.unsubscribe("SubsId");
session.close(); session.close();
@ -92,7 +101,7 @@ public class DurableUnsubscribeTest extends org.apache.activemq.TestSupport {
private void createBroker() throws Exception { private void createBroker() throws Exception {
broker = BrokerFactory.createBroker("broker:(vm://localhost)"); broker = BrokerFactory.createBroker("broker:(vm://localhost)");
broker.setPersistent(false); //broker.setPersistent(false);
broker.setUseJmx(true); broker.setUseJmx(true);
broker.setBrokerName(getName()); broker.setBrokerName(getName());
broker.start(); broker.start();

View File

@ -61,7 +61,8 @@ import org.slf4j.LoggerFactory;
* stored inside a HttpSession TODO controls to prevent DOS attacks with users * stored inside a HttpSession TODO controls to prevent DOS attacks with users
* requesting many consumers TODO configure consumers with small prefetch. * requesting many consumers TODO configure consumers with small prefetch.
* *
* *
*
*/ */
public class WebClient implements HttpSessionActivationListener, HttpSessionBindingListener, Externalizable { public class WebClient implements HttpSessionActivationListener, HttpSessionBindingListener, Externalizable {

View File

@ -345,7 +345,7 @@ public class HashIndex<Key,Value> implements Index<Key,Value> {
tx.store(metadata.page, metadataMarshaller, true); tx.store(metadata.page, metadataMarshaller, true);
calcThresholds(); calcThresholds();
LOG.debug("Resizing done. New bins start at: "+metadata.binPageId); LOG.debug("Resizing done. New bins start at: "+metadata.binPageId);
resizeCapacity=0; resizeCapacity=0;
resizePageId=0; resizePageId=0;
} }

View File

@ -138,15 +138,35 @@ public class PageFile {
Page page; Page page;
byte[] current; byte[] current;
byte[] diskBound; byte[] diskBound;
int currentLocation = -1;
int diskBoundLocation = -1;
File tmpFile;
int length;
public PageWrite(Page page, byte[] data) { public PageWrite(Page page, byte[] data) {
this.page=page; this.page=page;
current=data; current=data;
} }
public PageWrite(Page page, int currentLocation, int length, File tmpFile) {
this.page = page;
this.currentLocation = currentLocation;
this.tmpFile = tmpFile;
this.length = length;
}
public void setCurrent(Page page, byte[] data) { public void setCurrent(Page page, byte[] data) {
this.page=page; this.page=page;
current=data; current=data;
currentLocation = -1;
diskBoundLocation = -1;
}
public void setCurrentLocation(Page page, int location, int length) {
this.page = page;
this.currentLocation = location;
this.length = length;
this.current = null;
} }
@Override @Override
@ -158,22 +178,42 @@ public class PageFile {
public Page getPage() { public Page getPage() {
return page; return page;
} }
public byte[] getDiskBound() throws IOException {
if (diskBound == null && diskBoundLocation != -1) {
diskBound = new byte[length];
RandomAccessFile file = new RandomAccessFile(tmpFile, "r");
file.seek(diskBoundLocation);
int readNum = file.read(diskBound);
file.close();
diskBoundLocation = -1;
}
return diskBound;
}
void begin() { void begin() {
diskBound = current; if (currentLocation != -1) {
current = null; diskBoundLocation = currentLocation;
currentLocation = -1;
current = null;
} else {
diskBound = current;
current = null;
currentLocation = -1;
}
} }
/** /**
* @return true if there is no pending writes to do. * @return true if there is no pending writes to do.
*/ */
boolean done() { boolean done() {
diskBoundLocation = -1;
diskBound=null; diskBound=null;
return current == null; return current == null || currentLocation == -1;
} }
boolean isDone() { boolean isDone() {
return diskBound == null && current == null; return diskBound == null && diskBoundLocation == -1 && current == null && currentLocation == -1;
} }
} }
@ -470,7 +510,7 @@ public class PageFile {
return new File(directory, IOHelper.toFileSystemSafeName(name)+RECOVERY_FILE_SUFFIX); return new File(directory, IOHelper.toFileSystemSafeName(name)+RECOVERY_FILE_SUFFIX);
} }
private long toOffset(long pageId) { public long toOffset(long pageId) {
return PAGE_FILE_HEADER_SIZE+(pageId*pageSize); return PAGE_FILE_HEADER_SIZE+(pageId*pageSize);
} }
@ -823,6 +863,8 @@ public class PageFile {
} }
} }
boolean longTx = false;
for (Map.Entry<Long, PageWrite> entry : updates) { for (Map.Entry<Long, PageWrite> entry : updates) {
Long key = entry.getKey(); Long key = entry.getKey();
PageWrite value = entry.getValue(); PageWrite value = entry.getValue();
@ -830,12 +872,20 @@ public class PageFile {
if( write==null ) { if( write==null ) {
writes.put(key, value); writes.put(key, value);
} else { } else {
write.setCurrent(value.page, value.current); if (value.currentLocation != -1) {
write.setCurrentLocation(value.page, value.currentLocation, value.length);
write.tmpFile = value.tmpFile;
longTx = true;
} else {
write.setCurrent(value.page, value.current);
}
} }
} }
// Once we start approaching capacity, notify the writer to start writing // Once we start approaching capacity, notify the writer to start writing
if( canStartWriteBatch() ) { // sync immediately for long txs
if( longTx || canStartWriteBatch() ) {
if( enabledWriteThread ) { if( enabledWriteThread ) {
writes.notify(); writes.notify();
} else { } else {
@ -919,115 +969,90 @@ public class PageFile {
} }
} }
/** private void writeBatch() throws IOException {
*
* @return true if there are still pending writes to do. CountDownLatch checkpointLatch;
* @throws InterruptedException ArrayList<PageWrite> batch;
* @throws IOException synchronized( writes ) {
*/
private void writeBatch() throws IOException {
CountDownLatch checkpointLatch;
ArrayList<PageWrite> batch;
synchronized( writes ) {
// If there is not enough to write, wait for a notification... // If there is not enough to write, wait for a notification...
batch = new ArrayList<PageWrite>(writes.size()); batch = new ArrayList<PageWrite>(writes.size());
// build a write batch from the current write cache. // build a write batch from the current write cache.
for (PageWrite write : writes.values()) { for (PageWrite write : writes.values()) {
batch.add(write); batch.add(write);
// Move the current write to the diskBound write, this lets folks update the // Move the current write to the diskBound write, this lets folks update the
// page again without blocking for this write. // page again without blocking for this write.
write.begin(); write.begin();
if (write.diskBound == null) { if (write.diskBound == null && write.diskBoundLocation == -1) {
batch.remove(write); batch.remove(write);
} }
} }
// Grab on to the existing checkpoint latch cause once we do this write we can // Grab on to the existing checkpoint latch cause once we do this write we can
// release the folks that were waiting for those writes to hit disk. // release the folks that were waiting for those writes to hit disk.
checkpointLatch = this.checkpointLatch; checkpointLatch = this.checkpointLatch;
this.checkpointLatch=null; this.checkpointLatch=null;
} }
try {
if (enableRecoveryFile) {
// Using Adler-32 instead of CRC-32 because it's much faster and Checksum checksum = new Adler32();
// it's recoveryFile.seek(RECOVERY_FILE_HEADER_SIZE);
// weakness for short messages with few hundred bytes is not a for (PageWrite w : batch) {
// factor in this case since we know if (enableRecoveryFile) {
// our write batches are going to much larger. try {
Checksum checksum = new Adler32(); checksum.update(w.getDiskBound(), 0, pageSize);
for (PageWrite w : batch) { } catch (Throwable t) {
try { throw IOExceptionSupport.create("Cannot create recovery file. Reason: " + t, t);
checksum.update(w.diskBound, 0, pageSize); }
} catch (Throwable t) { recoveryFile.writeLong(w.page.getPageId());
throw IOExceptionSupport.create( recoveryFile.write(w.getDiskBound(), 0, pageSize);
"Cannot create recovery file. Reason: " + t, t); }
}
}
// Can we shrink the recovery buffer?? writeFile.seek(toOffset(w.page.getPageId()));
if (recoveryPageCount > recoveryFileMaxPageCount) { writeFile.write(w.getDiskBound(), 0, pageSize);
int t = Math.max(recoveryFileMinPageCount, batch.size()); w.done();
recoveryFile.setLength(recoveryFileSizeForPages(t)); }
}
// Record the page writes in the recovery buffer. try {
recoveryFile.seek(0); if (enableRecoveryFile) {
// Store the next tx id... // Can we shrink the recovery buffer??
recoveryFile.writeLong(nextTxid.get()); if (recoveryPageCount > recoveryFileMaxPageCount) {
// Store the checksum for thw write batch so that on recovery we int t = Math.max(recoveryFileMinPageCount, batch.size());
// know if we have a consistent recoveryFile.setLength(recoveryFileSizeForPages(t));
// write batch on disk. }
recoveryFile.writeLong(checksum.getValue());
// Write the # of pages that will follow
recoveryFile.writeInt(batch.size());
// Write the pages. // Record the page writes in the recovery buffer.
recoveryFile.seek(RECOVERY_FILE_HEADER_SIZE); recoveryFile.seek(0);
// Store the next tx id...
recoveryFile.writeLong(nextTxid.get());
// Store the checksum for thw write batch so that on recovery we
// know if we have a consistent
// write batch on disk.
recoveryFile.writeLong(checksum.getValue());
// Write the # of pages that will follow
recoveryFile.writeInt(batch.size());
}
for (PageWrite w : batch) { if (enableDiskSyncs) {
recoveryFile.writeLong(w.page.getPageId()); // Sync to make sure recovery buffer writes land on disk..
recoveryFile.write(w.diskBound, 0, pageSize); recoveryFile.getFD().sync();
} writeFile.getFD().sync();
}
} finally {
synchronized (writes) {
for (PageWrite w : batch) {
// If there are no more pending writes, then remove it from
// the write cache.
if (w.isDone()) {
writes.remove(w.page.getPageId());
}
}
}
if (enableDiskSyncs) { if (checkpointLatch != null) {
// Sync to make sure recovery buffer writes land on disk.. checkpointLatch.countDown();
recoveryFile.getFD().sync(); }
} }
}
recoveryPageCount = batch.size();
}
for (PageWrite w : batch) {
writeFile.seek(toOffset(w.page.getPageId()));
writeFile.write(w.diskBound, 0, pageSize);
w.done();
}
// Sync again
if (enableDiskSyncs) {
writeFile.getFD().sync();
}
} finally {
synchronized (writes) {
for (PageWrite w : batch) {
// If there are no more pending writes, then remove it from
// the write cache.
if (w.isDone()) {
writes.remove(w.page.getPageId());
}
}
}
if( checkpointLatch!=null ) {
checkpointLatch.countDown();
}
}
}
private long recoveryFileSizeForPages(int pageCount) { private long recoveryFileSizeForPages(int pageCount) {
return RECOVERY_FILE_HEADER_SIZE+((pageSize+8)*pageCount); return RECOVERY_FILE_HEADER_SIZE+((pageSize+8)*pageCount);
@ -1135,4 +1160,7 @@ public class PageFile {
return getMainPageFile(); return getMainPageFile();
} }
public File getDirectory() {
return directory;
}
} }

View File

@ -16,22 +16,11 @@
*/ */
package org.apache.kahadb.page; package org.apache.kahadb.page;
import java.io.DataInputStream; import java.io.*;
import java.io.EOFException; import java.util.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.NoSuchElementException;
import org.apache.kahadb.page.PageFile.PageWrite; import org.apache.kahadb.page.PageFile.PageWrite;
import org.apache.kahadb.util.ByteSequence; import org.apache.kahadb.util.*;
import org.apache.kahadb.util.DataByteArrayInputStream;
import org.apache.kahadb.util.DataByteArrayOutputStream;
import org.apache.kahadb.util.Marshaller;
import org.apache.kahadb.util.Sequence;
import org.apache.kahadb.util.SequenceSet;
/** /**
* The class used to read/update a PageFile object. Using a transaction allows you to * The class used to read/update a PageFile object. Using a transaction allows you to
@ -39,6 +28,11 @@ import org.apache.kahadb.util.SequenceSet;
*/ */
public class Transaction implements Iterable<Page> { public class Transaction implements Iterable<Page> {
private RandomAccessFile tmpFile;
private File txfFile;
private int nextLocation = 0;
/** /**
* The PageOverflowIOException occurs when a page write is requested * The PageOverflowIOException occurs when a page write is requested
* and it's data is larger than what would fit into a single page. * and it's data is larger than what would fit into a single page.
@ -91,12 +85,16 @@ public class Transaction implements Iterable<Page> {
// If this transaction is updating stuff.. this is the tx of // If this transaction is updating stuff.. this is the tx of
private long writeTransactionId=-1; private long writeTransactionId=-1;
// List of pages that this transaction has modified. // List of pages that this transaction has modified.
private HashMap<Long, PageWrite> writes=new HashMap<Long, PageWrite>(); private TreeMap<Long, PageWrite> writes=new TreeMap<Long, PageWrite>();
// List of pages allocated in this transaction // List of pages allocated in this transaction
private final SequenceSet allocateList = new SequenceSet(); private final SequenceSet allocateList = new SequenceSet();
// List of pages freed in this transaction // List of pages freed in this transaction
private final SequenceSet freeList = new SequenceSet(); private final SequenceSet freeList = new SequenceSet();
private long maxTransactionSize = 10485760;
private long size = 0;
Transaction(PageFile pageFile) { Transaction(PageFile pageFile) {
this.pageFile = pageFile; this.pageFile = pageFile;
} }
@ -650,7 +648,16 @@ public class Transaction implements Iterable<Page> {
allocateList.clear(); allocateList.clear();
writes.clear(); writes.clear();
writeTransactionId = -1; writeTransactionId = -1;
if (tmpFile != null) {
tmpFile.close();
if (!getTempFile().delete()) {
throw new IOException("Can't delete temporary KahaDB transaction file:" + getTempFile());
}
tmpFile = null;
txfFile = null;
}
} }
size = 0;
} }
/** /**
@ -665,7 +672,16 @@ public class Transaction implements Iterable<Page> {
allocateList.clear(); allocateList.clear();
writes.clear(); writes.clear();
writeTransactionId = -1; writeTransactionId = -1;
if (tmpFile != null) {
tmpFile.close();
if (getTempFile().delete()) {
throw new IOException("Can't delete temporary KahaDB transaction file:" + getTempFile());
}
tmpFile = null;
txfFile = null;
}
} }
size = 0;
} }
private long getWriteTransactionId() { private long getWriteTransactionId() {
@ -675,16 +691,36 @@ public class Transaction implements Iterable<Page> {
return writeTransactionId; return writeTransactionId;
} }
protected File getTempFile() {
if (txfFile == null) {
txfFile = new File(getPageFile().getDirectory(), IOHelper.toFileSystemSafeName(Long.toString(getWriteTransactionId())) + ".tmp");
}
return txfFile;
}
/** /**
* Queues up a page write that should get done when commit() gets called. * Queues up a page write that should get done when commit() gets called.
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void write(final Page page, byte[] data) throws IOException { private void write(final Page page, byte[] data) throws IOException {
Long key = page.getPageId(); Long key = page.getPageId();
// TODO: if a large update transaction is in progress, we may want to move size += data.length;
// all the current updates to a temp file so that we don't keep using
// up memory. PageWrite write;
writes.put(key, new PageWrite(page, data)); if (size > maxTransactionSize) {
if (tmpFile == null) {
tmpFile = new RandomAccessFile(getTempFile(), "rw");
}
int location = nextLocation;
tmpFile.seek(nextLocation);
tmpFile.write(data);
nextLocation = location + data.length;
write = new PageWrite(page, location, data.length, getTempFile());
} else {
write = new PageWrite(page, data);
}
writes.put(key, write);
} }
/** /**