diff --git a/src/main/java/edu/caltech/nanodb/server/NanoDBServer.java b/src/main/java/edu/caltech/nanodb/server/NanoDBServer.java index a9cd65576b3f315923996b1f2640862f7bb2cfd4..0034fbde2f64a778e03b754f90dc9d288fdb48fd 100644 --- a/src/main/java/edu/caltech/nanodb/server/NanoDBServer.java +++ b/src/main/java/edu/caltech/nanodb/server/NanoDBServer.java @@ -72,69 +72,6 @@ public class NanoDBServer implements ServerProperties { private ReentrantReadWriteLock schemaLock; - /** - * Initialize all of the properties that the database server knows about. - * Some of these properties are read-write, and others are read-only and - * are initialized right at database startup, before any other steps - * occur. - */ - private void initProperties() { - propertyRegistry.addProperty(PROP_BASE_DIRECTORY, - new StringValueValidator(), DEFAULT_BASE_DIRECTORY, - /* readonly */ true); - - propertyRegistry.addProperty(PROP_PAGECACHE_SIZE, - new IntegerValueValidator(MIN_PAGECACHE_SIZE, MAX_PAGECACHE_SIZE), - DEFAULT_PAGECACHE_SIZE); - - propertyRegistry.addProperty(PROP_PAGECACHE_POLICY, - new StringEnumValidator(PAGECACHE_POLICY_VALUES), - DEFAULT_PAGECACHE_POLICY); - - propertyRegistry.addProperty(PROP_PAGE_SIZE, - new IntegerValueValidator(DBFile::isValidPageSize, - "Specified page-size %d is invalid."), DEFAULT_PAGE_SIZE); - - propertyRegistry.addProperty(PROP_ENABLE_TRANSACTIONS, - new BooleanFlagValidator(), false, /* readonly */ true); - - propertyRegistry.addProperty(PROP_ENFORCE_KEY_CONSTRAINTS, - new BooleanFlagValidator(), true); - - propertyRegistry.addProperty(PROP_ENABLE_INDEXES, - new BooleanFlagValidator(), false, /* reaadonly */ true); - - propertyRegistry.addProperty(PROP_CREATE_INDEXES_ON_KEYS, - new BooleanFlagValidator(), false); - - propertyRegistry.addProperty(PROP_PLANNER_CLASS, - new PlannerClassValidator(), DEFAULT_PLANNER_CLASS); - - propertyRegistry.addProperty(PROP_FLUSH_AFTER_CMD, - new BooleanFlagValidator(), true); - } - - - /** - * This helper function sets the NanoDB server properties based on the - * contents of a Java {@code Properties} object. This allows us to set - * NanoDB properties from system properties and/or other sources of - * properties. - * - * @param properties the properties to apply to NanoDB's configuration. - */ - private void setProperties(Properties properties) { - if (properties == null) - throw new IllegalArgumentException("properties cannot be null"); - - for (String name : properties.stringPropertyNames()) { - if (propertyRegistry.hasProperty(name)) - propertyRegistry.setPropertyValue(name, - properties.getProperty(name)); - } - } - - /** * This static method encapsulates all of the operations necessary for * cleanly starting the NanoDB server. Database server properties are @@ -166,13 +103,12 @@ public class NanoDBServer implements ServerProperties { // Everything needs configuration. propertyRegistry = new PropertyRegistry(); - initProperties(); // Apply system properties first (populated from e.g. command line), // then override with any arguments to this function. - setProperties(System.getProperties()); + propertyRegistry.setProperties(System.getProperties()); if (initialProperties != null) - setProperties(initialProperties); + propertyRegistry.setProperties(initialProperties); propertyRegistry.setupCompleted(); diff --git a/src/main/java/edu/caltech/nanodb/server/properties/PropertyObserver.java b/src/main/java/edu/caltech/nanodb/server/properties/PropertyObserver.java new file mode 100644 index 0000000000000000000000000000000000000000..b783c7ef09db6c0e9771a2ec86d384cfd5791310 --- /dev/null +++ b/src/main/java/edu/caltech/nanodb/server/properties/PropertyObserver.java @@ -0,0 +1,18 @@ +package edu.caltech.nanodb.server.properties; + + +/** + * An interface that components can implement to be notified of changes to + * property-values during system operation. + */ +public interface PropertyObserver { + /** + * This method is called on all property observers when a given property + * is changed to a new value. + * + * @param propertyName the name of the property that was changed + * + * @param newValue the new value of the property + */ + void propertyChanged(String propertyName, Object newValue); +} diff --git a/src/main/java/edu/caltech/nanodb/server/properties/PropertyRegistry.java b/src/main/java/edu/caltech/nanodb/server/properties/PropertyRegistry.java index 2fd22b628e3d630ed2e410d7d298f186a0c3312e..734c40e67288d1caf04fb8b3d2c927b3a90ca222 100644 --- a/src/main/java/edu/caltech/nanodb/server/properties/PropertyRegistry.java +++ b/src/main/java/edu/caltech/nanodb/server/properties/PropertyRegistry.java @@ -1,10 +1,14 @@ package edu.caltech.nanodb.server.properties; +import java.util.ArrayList; import java.util.Collections; +import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import edu.caltech.nanodb.storage.DBFile; + /** * This is the central location where all properties exposed by the database @@ -12,7 +16,7 @@ import java.util.concurrent.ConcurrentHashMap; * configurable properties with the registry, and then they can be accessed * and/or set from the SQL prompt. */ -public class PropertyRegistry { +public class PropertyRegistry implements ServerProperties { private class PropertyDescriptor { /** The name of the property. */ @@ -69,6 +73,13 @@ public class PropertyRegistry { new ConcurrentHashMap<>(); + /** + * A collection of property observers to be informed when property values + * change. + */ + private ArrayList<PropertyObserver> observers = new ArrayList<>(); + + /** * This flag indicates whether the database is still in "start-up mode" * or not. During start-up mode, all read-only properties may also be @@ -77,6 +88,75 @@ public class PropertyRegistry { private boolean setup = true; + public PropertyRegistry() { + initProperties(); + } + + + /** + * Initialize all of the properties that the database server knows about. + * Some of these properties are read-write, and others are read-only and + * are initialized right at database startup, before any other steps + * occur. + * + * @review (donnie) It's not great that this configuration is in here. + * Someday, should probably migrate it back into + * {@code NanoDBServer}. + */ + private void initProperties() { + addProperty(PROP_BASE_DIRECTORY, + new StringValueValidator(), DEFAULT_BASE_DIRECTORY, + /* readonly */ true); + + addProperty(PROP_PAGECACHE_SIZE, + new IntegerValueValidator(MIN_PAGECACHE_SIZE, MAX_PAGECACHE_SIZE), + DEFAULT_PAGECACHE_SIZE); + + addProperty(PROP_PAGECACHE_POLICY, + new StringEnumValidator(PAGECACHE_POLICY_VALUES), + DEFAULT_PAGECACHE_POLICY, /* readonly */ true); + + addProperty(PROP_PAGE_SIZE, + new IntegerValueValidator(DBFile::isValidPageSize, + "Specified page-size %d is invalid."), DEFAULT_PAGE_SIZE); + + addProperty(PROP_ENABLE_TRANSACTIONS, + new BooleanFlagValidator(), false, /* readonly */ true); + + addProperty(PROP_ENFORCE_KEY_CONSTRAINTS, + new BooleanFlagValidator(), true); + + addProperty(PROP_ENABLE_INDEXES, + new BooleanFlagValidator(), false, /* reaadonly */ true); + + addProperty(PROP_CREATE_INDEXES_ON_KEYS, + new BooleanFlagValidator(), false); + + addProperty(PROP_PLANNER_CLASS, + new PlannerClassValidator(), DEFAULT_PLANNER_CLASS); + + addProperty(PROP_FLUSH_AFTER_CMD, new BooleanFlagValidator(), true); + } + + + /** + * This helper function sets the server properties based on the contents + * of a Java {@code Properties} object. This allows us to set NanoDB + * properties from system properties and/or other sources of properties. + * + * @param properties the properties to apply to NanoDB's configuration. + */ + public void setProperties(Properties properties) { + if (properties == null) + throw new IllegalArgumentException("properties cannot be null"); + + for (String name : properties.stringPropertyNames()) { + if (hasProperty(name)) + setPropertyValue(name, properties.getProperty(name)); + } + } + + /** * Records that setup has been completed, and read-only properties * should no longer be allowed to change. @@ -86,6 +166,19 @@ public class PropertyRegistry { } + /** + * Records a property-change observer on the property registry. + * + * @param observer the observer to receive property-change notifications + */ + public void addObserver(PropertyObserver observer) { + if (observer == null) + throw new IllegalArgumentException("observer cannot be null"); + + observers.add(observer); + } + + /** * Add a read-only or read-write property to the registry, along with a * type and an initial value. @@ -168,6 +261,9 @@ public class PropertyRegistry { } properties.get(name).setValue(value); + + for (PropertyObserver obs : observers) + obs.propertyChanged(name, value); } diff --git a/src/main/java/edu/caltech/nanodb/storage/BufferManager.java b/src/main/java/edu/caltech/nanodb/storage/BufferManager.java index cb738e0d712665b1014d08b54c8e2470bcc6925a..e8b33c85a70bf094ab80baef0edecaccbfbfd466 100644 --- a/src/main/java/edu/caltech/nanodb/storage/BufferManager.java +++ b/src/main/java/edu/caltech/nanodb/storage/BufferManager.java @@ -14,6 +14,9 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; import edu.caltech.nanodb.server.SessionState; +import edu.caltech.nanodb.server.properties.PropertyObserver; +import edu.caltech.nanodb.server.properties.PropertyRegistry; +import edu.caltech.nanodb.server.properties.ServerProperties; /** @@ -29,35 +32,15 @@ import edu.caltech.nanodb.server.SessionState; */ public class BufferManager { - /** The default page-cache size is defined to be 20MiB. */ - public static final int DEFAULT_PAGECACHE_SIZE = 20 * 1024 * 1024; - - /** - * The minimum page-cache size is defined to be 32KiB, which is really - * only sufficient to run smaller queries when page-pinning is working - * properly. - */ - public static final int MIN_PAGECACHE_SIZE = 32 * 1024; - - /** - * The system property that can be used to specify the page replacement - * policy in the buffer manager. - */ - public static final String PROP_PAGECACHE_POLICY = "nanodb.pagecache.policy"; - - /** The default page-cache policy is LRU. */ - public static final String DEFAULT_PAGECACHE_POLICY = "lru"; - - /** * This helper class keeps track of a data page that is currently cached. */ private static class CachedPageInfo { - public DBFile dbFile; + DBFile dbFile; - public int pageNo; + int pageNo; - public CachedPageInfo(DBFile dbFile, int pageNo) { + CachedPageInfo(DBFile dbFile, int pageNo) { if (dbFile == null) throw new IllegalArgumentException("dbFile cannot be null"); @@ -154,34 +137,39 @@ public class BufferManager { /** * A string indicating the buffer manager's page replacement policy. - * Currently it can be "lru" or "fifo". + * Currently it can be "LRU" or "FIFO". */ private String replacementPolicy; - public BufferManager(FileManager fileManager) { - this.fileManager = fileManager; + private class BufferPropertyObserver + implements PropertyObserver, ServerProperties { + public void propertyChanged(String propertyName, Object newValue) { + // We only care about the pagecache-size value. + if (PROP_PAGECACHE_SIZE.equals(propertyName)) { + setMaxCacheSize((Integer) newValue); + } + } + } - maxCacheSize = DEFAULT_PAGECACHE_SIZE; - /* TODO: Factor out the replacement policy implementation so that - * it's easier to replace/configure in the future. - */ - replacementPolicy = configureReplacementPolicy(); - cachedPages = new LinkedHashMap<>(16, 0.75f, "lru".equals(replacementPolicy)); - totalBytesCached = 0; + public BufferManager(FileManager fileManager, + PropertyRegistry propertyRegistry) { + this.fileManager = fileManager; + propertyRegistry.addObserver(new BufferPropertyObserver()); - /* TODO: Get rid of this. Something outside of this class should - * configure it when it is initialized. + maxCacheSize = propertyRegistry.getIntProperty( + ServerProperties.PROP_PAGECACHE_SIZE); - if (server != null) { - // Register properties that the Buffer Manager exposes. - server.getPropertyRegistry().registerProperties( - new BufferManagerPropertyHandler(), - PROP_PAGECACHE_POLICY, PROP_PAGECACHE_SIZE); - } - */ + // TODO: Factor out the replacement policy implementation so that + // it's easier to replace/configure in the future. + replacementPolicy = propertyRegistry.getStringProperty( + ServerProperties.PROP_PAGECACHE_POLICY); + cachedPages = + new LinkedHashMap<>(16, 0.75f, "LRU".equals(replacementPolicy)); + + totalBytesCached = 0; } @@ -193,9 +181,10 @@ public class BufferManager { * @param maxCacheSize the maximum size for the buffer cache. */ public void setMaxCacheSize(int maxCacheSize) { - if (maxCacheSize < MIN_PAGECACHE_SIZE) { + if (maxCacheSize < ServerProperties.MIN_PAGECACHE_SIZE) { throw new IllegalArgumentException( - "maxCacheSize must be at least " + MIN_PAGECACHE_SIZE); + "maxCacheSize must be at least " + + ServerProperties.MIN_PAGECACHE_SIZE); } synchronized (guard) { @@ -223,21 +212,6 @@ public class BufferManager { } - private String configureReplacementPolicy() { - String str = DEFAULT_PAGECACHE_POLICY; - str = str.trim().toLowerCase(); - - if (!("lru".equals(str) || "fifo".equals(str))) { - logger.error(String.format( - "Unrecognized value \"%s\" for page-cache replacement " + - "policy; using default value of LRU.", - System.getProperty(PROP_PAGECACHE_POLICY))); - } - - return str; - } - - /** * Add another observer to the buffer manager. * diff --git a/src/main/java/edu/caltech/nanodb/storage/StorageManager.java b/src/main/java/edu/caltech/nanodb/storage/StorageManager.java index e795de0a4ae57f40d3d67dad5e6e882c6d42dabe..a4f65ce659c80132137053cfedd60561c529f9e4 100755 --- a/src/main/java/edu/caltech/nanodb/storage/StorageManager.java +++ b/src/main/java/edu/caltech/nanodb/storage/StorageManager.java @@ -13,7 +13,6 @@ import edu.caltech.nanodb.indexes.IndexManager; import edu.caltech.nanodb.indexes.IndexUpdater; import edu.caltech.nanodb.relations.DatabaseConstraintEnforcer; import edu.caltech.nanodb.server.EventDispatcher; -import edu.caltech.nanodb.server.NanoDBException; import edu.caltech.nanodb.server.NanoDBServer; import edu.caltech.nanodb.server.properties.PropertyRegistry; import edu.caltech.nanodb.server.properties.ServerProperties; @@ -22,7 +21,8 @@ import edu.caltech.nanodb.transactions.TransactionManager; /** - * + * The Storage Manager provides facilities for managing files of tuples, + * including in-memory buffering of data pages and support for transactions. * * @todo This class requires synchronization, once we support multiple clients. */ @@ -32,11 +32,6 @@ public class StorageManager { private static Logger logger = LogManager.getLogger(StorageManager.class); - /*======================================================================== - * STATIC FIELDS AND METHODS - */ - - /*======================================================================== * NON-STATIC FIELDS AND METHODS */ @@ -139,7 +134,7 @@ public class StorageManager { logger.info("Using base directory " + baseDir); fileManager = new FileManagerImpl(baseDir); - bufferManager = new BufferManager(fileManager); + bufferManager = new BufferManager(fileManager, serverProps); tupleFileManagers.put(DBFileType.HEAP_TUPLE_FILE, new HeapTupleFileManager(this)); diff --git a/src/test/java/edu/caltech/test/nanodb/storage/TestBufferManager.java b/src/test/java/edu/caltech/test/nanodb/storage/TestBufferManager.java index 5f8cb76f1406fad0a54cf2de6a0338b5c4c36904..88f168a421554ce10f904f817cd89221fe1b9f7f 100644 --- a/src/test/java/edu/caltech/test/nanodb/storage/TestBufferManager.java +++ b/src/test/java/edu/caltech/test/nanodb/storage/TestBufferManager.java @@ -9,6 +9,7 @@ import org.apache.commons.io.FileUtils; import static org.mockito.Mockito.*; +import edu.caltech.nanodb.server.properties.PropertyRegistry; import edu.caltech.test.nanodb.framework.Concurrent; import org.testng.annotations.Test; @@ -34,7 +35,8 @@ public class TestBufferManager extends StorageTestCase { FileUtils.cleanDirectory(testBaseDir); FileManager fileMgr = spy(new FileManagerImpl(testBaseDir)); - BufferManager bufMgr = new BufferManager(fileMgr); + BufferManager bufMgr = + new BufferManager(fileMgr, new PropertyRegistry()); DBFile file = fileMgr.createDBFile("TestBufferManager_testBuffering", DBFileType.TEST_FILE, 4096); @@ -72,7 +74,8 @@ public class TestBufferManager extends StorageTestCase { FileUtils.cleanDirectory(testBaseDir); FileManager fileMgr = spy(new FileManagerImpl(testBaseDir)); - BufferManager bufMgr = new BufferManager(fileMgr); + BufferManager bufMgr = + new BufferManager(fileMgr, new PropertyRegistry()); // Set the Buffer Manager's pool size to 4MiB. This will force a // significant number of pool evictions during the test. diff --git a/src/test/java/edu/caltech/test/nanodb/storage/TestDBPage.java b/src/test/java/edu/caltech/test/nanodb/storage/TestDBPage.java index 01b5157816c21be24ca83272280d3939301c8998..fbabf6a99e566f2703c8405bf80b3fa1aa82d6fa 100644 --- a/src/test/java/edu/caltech/test/nanodb/storage/TestDBPage.java +++ b/src/test/java/edu/caltech/test/nanodb/storage/TestDBPage.java @@ -1,6 +1,7 @@ package edu.caltech.test.nanodb.storage; +import edu.caltech.nanodb.server.properties.PropertyRegistry; import org.testng.annotations.Test; import org.testng.annotations.BeforeClass; import org.testng.annotations.AfterClass; @@ -48,7 +49,8 @@ public class TestDBPage extends StorageTestCase { public void beforeClass() { fileMgr = new FileManagerImpl(testBaseDir); - BufferManager bufMgr = new BufferManager(fileMgr); + BufferManager bufMgr = + new BufferManager(fileMgr, new PropertyRegistry()); // Get DBFile DBFileType type = DBFileType.HEAP_TUPLE_FILE; diff --git a/src/test/java/edu/caltech/test/nanodb/storage/TestFileManager.java b/src/test/java/edu/caltech/test/nanodb/storage/TestFileManager.java index f9365a867ca87efb47e1dd4ff496f3dcd648cc63..8fd10d045c87ad0597bfd64b986fbc97b531032d 100644 --- a/src/test/java/edu/caltech/test/nanodb/storage/TestFileManager.java +++ b/src/test/java/edu/caltech/test/nanodb/storage/TestFileManager.java @@ -4,6 +4,7 @@ package edu.caltech.test.nanodb.storage; import java.io.File; import java.io.IOException; +import edu.caltech.nanodb.server.properties.PropertyRegistry; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@ -29,7 +30,7 @@ public class TestFileManager extends StorageTestCase { @BeforeClass public void beforeClass() { fileMgr = new FileManagerImpl(testBaseDir); - bufMgr = new BufferManager(fileMgr); + bufMgr = new BufferManager(fileMgr, new PropertyRegistry()); }