Namespace Storage Configuration
Aerospike determines where to store data based on the Storage engine configured for the namespace. These engines determine if the data will be persisted to disk, reside in memory, or both. These decisions will affect the durability, cost, and performance of your cluster.
Storage Engine Configuration Recipes
The following recipes require modifying the Aerospike Server configuration file which is located /etc/aerospike/aerospike.conf.
Recipe for an SSD Storage Engine
The minimal configuration for an SSD namespace requires setting storage-engine to device and adding a device parameter for each SSD to be used by this namespace.
The maximum size of a device is 2 TiB, for larger devices, partiton into multiple equally sized partitions that are less than 2 TiB each.
In addition, memory-size may need to be changed from the default of 4 GiB to a size appropriate for the expected primary index size. Please refer to sizing guide.
namespace{ memory-size G # Maximum memory allocation for primary # and secondary indexes. storage-engine device { # Configure the storage-engine to use persistence device /dev/ # raw device. Maximum size is 2 TiB # device /dev/ # (optional) another raw device. write-block-size 128K # adjust block size to make it efficient for SSDs. } }
The minimal configuration for an HDD with Data-in-Memory namespace involves setting storage-engine to device, setting data-in-memory to true, and finally providing a list of file parameters to indicate where data will be persisted.
Also filesize needs to be large enough to support the size of the data on disk (with a maximum allowed value of 2 TiB). Lastly, memory-size may need adjusted from the default of 4GiB to a size appropriate to handle the expected primary index size and the expected size of the data in memory.
namespace{ memory-size G # Maximum memory allocation for data and # primary and secondary indexes. storage-engine device { # Configure the storage-engine to use # persistence. Maximum size is 2 TiB file /opt/aerospike/ # Location of data file on server. # file /opt/aerospike/ # (optional) Location of data file on server. filesize G # Max size of each file in GiB. data-in-memory true # Indicates that all data should also be # in memory. } }
A data-in-index configuration is a highly specialized namespace for a very niche use case.
The minimal configuration for a data-in-index namespace involves setting single-bin to true, data-in-index to true, and data-in-memory to true.
In addition, storage-engine must be device and file or device parameters need to be configured to map to the persisted storage device to be used by this namespace.
namespace{ memory-size G # Maximum memory allocation for data and # primary and secondary indexes. single-bin true # Required true by data-in-index. data-in-index true # Enables in index integer store. storage-engine device { # Configure the storage-engine to use # persistence. file /opt/aerospike/ # Location of data file on server. # file /opt/aerospike/ # (optimal) Location of data file on server. # device /dev/ # Optional alternative to using files. filesize G # Max size of each file in GiB. Maximum size is 2TiB data-in-memory true # Required true by data-in-index. } }
The minimal configuration for a namespace without persistence is to set storage-engine to memory
namespace{ memory-size G # Maximum memory allocation for data and primary and # secondary indexes. storage-engine memory # Configure the storage-engine to not use persistence. }
public class MyConsole { • public MyConsole() { • Log.SetLevel(Log.Level.INFO); • Log.SetCallback(LogCallback); • } • • public void LogCallback(Log.Level level, String message) • { • // Write log messages to the appropriate place. • } }
• WritePolicy policy = new WritePolicy(); • policy.recordExistsAction = RecordExistsAction.REPLACE; client.put(policy, key, bins);
o ClientPolicy policy = new ClientPolicy(); o policy.readPolicyDefault.timeout = 50; o policy.readPolicyDefault.maxRetries = 1; o policy.readPolicyDefault.sleepBetweenRetries = 10; o policy.writePolicyDefault.timeout = 200; o policy.writePolicyDefault.maxRetries = 1; o policy.writePolicyDefault.sleepBetweenRetries = 50; o o AerospikeClient client = new AerospikeClient(policy, "hostname", 3000); client.Put(null, new Key("test", "set", 1), new Bin("bin", 5)); o Instantiate your own policies once and pass them to each database command: o public MyClass { o private AerospikeClient client; o private WritePolicy writePolicy; o o public MyClass(AerospikeClient client) { o this.client = client; o writePolicy = new WritePolicy(); o writePolicy.timeout = 200; o writePolicy.maxRetries = 1; o writePolicy.sleepBetweenRetries = 50; o } o o public void Put(Key key, Bin... bins) { o client.Put(writePolicy, key, bins); o } } o If a policy needs to change from the default (such as setting an expected generation), instantiate that policy on the fly: o public void PutIfGeneration(Key key, int generation, Bin... bins) { o WritePolicy policy = new WritePolicy(); o policy.generationPolicy = GenerationPolicy.EXPECT_GEN_EQUAL; o policy.generation = generation; o client.Put(policy, key, bins); }
Use the Aerospike C# client library asynchronous API (the AsyncClient class) to place commands on a queue and return control to the application, which allows an executor to asynchronously process the commands.
The AsyncClient instance is thread-safe and can concurrent. A separate thread uses a pool of non-blocking sockets to send the command, process the reply, and notify the caller with the result.
AsyncClient uses less threads and makes efficient use of threads than using the synchronous client. One con of using AsyncClient is that the programming model is difficult to implement, debug, and maintain.
Example
using System; using System.Threading; using Aerospike.Client; namespace Test { public class AsyncTest { private AsyncClient client; private WritePolicy policy; private bool completed; public AsyncTest() { policy = new WritePolicy(); policy.timeout = 50; // 50 millisecond timeout. } public void RunTest() { client = new AsyncClient("127.0.0.1", 3000); try { // Write a single value. Key key = new Key("test", "myset", "mykey"); Bin bin = new Bin("mybin", "myvalue"); Console.WriteLine(string.Format("Write: namespace={0} set={1} key={2} value={3}", key.ns, key.setName, key.userKey, bin.value)); client.Put(policy, new WriteHandler(this, key, bin), key, bin); WaitTillComplete(); } finally { client.Close(); } } private class WriteHandler : WriteListener { private readonly AsyncTest parent; private readonly Key key; private readonly Bin bin; public WriteHandler(AsyncTest parent, Key key, Bin bin) { this.parent = parent; this.key = key; this.bin = bin; } public void OnSuccess(Key key) { try { // Write succeeded. Now call read. parent.client.Get(parent.policy, new RecordHandler(parent, key), key); } catch (Exception e) { Console.WriteLine(string.Format("Failed to get: namespace={0} set={1} key={2} exception={3}", key.ns, key.setName, key.userKey, e.Message)); } } public void OnFailure(AerospikeException e) { Console.WriteLine("Failed to put: namespace={0} set={1} key={2} exception={3}", key.ns, key.setName, key.userKey, e.Message); parent.NotifyCompleted(); } } private class RecordHandler : RecordListener { private readonly AsyncTest parent; private readonly Key key; public RecordHandler(AsyncTest parent, Key key) { this.parent = parent; this.key = key; } public void OnSuccess(Key key, Record record) { // Read completed. object received = (record == null) ? null : record.GetValue("mybin"); Console.WriteLine(string.Format("Received: namespace={0} set={1} key={2} value={3}", key.ns, key.setName, key.userKey, received)); // Notify application that read is complete. parent.NotifyCompleted(); } public void OnFailure(AerospikeException e) { Console.WriteLine("Failed to get: namespace={0} set={1} key={2} exception={3}", key.ns, key.setName, key.userKey, e.Message); parent.NotifyCompleted(); } } private void WaitTillComplete() { lock (this) { while (!completed) { Monitor.Wait(this); } } } private void NotifyCompleted() { lock (this) { completed = true; Monitor.Pulse(this); } } } }
To make the application wait and avoid closing the connection before the write and read commands complete:
private void WaitTillComplete() { lock (this) { while (!completed) { Monitor.Wait(this); } } } private void NotifyCompleted() { lock (this) { completed = true; Monitor.Pulse(this); } }