Tag Archives: Java

Token-based Authentication Plugin for ActiveMQ

This post is a part of ActiveMQ Custom Security Plugins series.

 

Similarly to how we did in case of the IP-based Authentication Plugin for ActiveMQ, in order to limit the connectivity to the ActiveMQ server based on Token (assuming the connecting client, eg. a browser through a JavaScript over STOMP protocol) is providing such token when trying to establish a connection with the broker), we’ll need to override the addConnection() method of the BrokerFilter.class.

 

For the purpose of this example, i’ll be using Redis as the data store against which i’ll be checking the Tokens of connecting clients; to make a decision whether a client is allowed to establish a connection with the broker (Token exists in Redis) or not (otherwise). To hit Redis from Java i’ll be using the Jedis driver.

 

Step1: Implementation of the plugin logic:

import org.apache.activemq.broker.Broker;
import org.apache.activemq.broker.BrokerFilter;
import org.apache.activemq.broker.ConnectionContext;
import org.apache.activemq.command.ConnectionInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import java.util.Map;

public class TokenAuthenticationBroker extends BrokerFilter {

  private final Logger logger = LoggerFactory.getLogger(getClass());
  public final static String REDIS_KEY = "authentication:activemq:tokens";

  Map<String, String> redisConfig;

  public TokenAuthenticationBroker(Broker next, Map<String, String> redisConfig) {
    super(next);
    this.redisConfig = redisConfig;
  }

  @Override
  public void addConnection(ConnectionContext context, ConnectionInfo info) throws Exception {
    String host = redisConfig.get("host");
    int port = Integer.parseInt(redisConfig.get("port"));

    logger.debug("Establishing Redis connection using [host={}, port={}] ", host, port);
    Jedis jedis = new Jedis(host, port);

    String token = context.getUserName();

    logger.debug("Querying Redis using [key={}, token={}] ", REDIS_KEY, token);
    String response = jedis.hget(REDIS_KEY, token);

    if(response == null) {
      throw new SecurityException("Token not not found in the data store");
    } else {
      logger.debug("Found token [{}] belonging to user: {}. Allowing connection", token, response);
    super.addConnection(context, info);
    }
  }
}

 

As you can see in the example above, the token provided by the connecting client can be read in ActiveMQ directly from the context (by using the getUserName() method; assuming the client is sending the token as a query parameter named “username”). Having the token, next thing we need to do is to query the Redis store (under the REDIS_KEY) and check whether the token exists (hget() method invoked on jedis object/driver). Depending on the value of response, we’re making the decision whether to addConnection() or throw an SecurityException.

 

Also, after the actual plug-in logic has been implemented, the plug-in must be configured and installed. For this purpose, we need an implementation of the BrokerPlugin.class, which is used to expose the configuration of a plug-in and to install the plug-in into the ActiveMQ broker.

 

Step2: Implementation of the plugin “installer”:

import org.apache.activemq.broker.Broker;
import org.apache.activemq.broker.BrokerPlugin;
import java.util.Map;

public class TokenAuthenticationPlugin implements BrokerPlugin {

  Map<String, String> redisConfig;

  @Override
  public Broker installPlugin(Broker broker) throws Exception {
    return new TokenAuthenticationBroker(broker, redisConfig);
  }

  public Map<String, String> getRedisConfig() {
    return redisConfig;
  }

  public void setRedisConfig(Map<String, String> redisConfig) {
    this.redisConfig = redisConfig;
  }
}

 

The installPlugin() method above is used to instantiate the plug-in and return a new intercepted broker for the next plug-in in the chain. The TokenAuthenticationPlugin.class also contains getter and setter methods used to configure the TokenAuthenticationBroker. These setter and getter methods are available via a Spring beans–style XML configuration in the ActiveMQ XML configuration file (example below).

 

Step3: Configuring the plugin in activemq.xml:

// "/apache-activemq/conf/activemq.xml"
<broker brokerName="localhost" dataDirectory="${activemq.base}/data" xmlns="http://activemq.apache.org/schema/core">
  <plugins>
    <bean id="tokenAuthenticationPlugin" class="com.mycompany.mysystem.activemq.TokenAuthenticationPlugin" xmlns="http://www.springframework.org/schema/beans">
      <property name="redisConfig">
        <map>
          <entry key="host" value="localhost" />
          <entry key="port" value="6379" />
        </map>
      </property>
    </bean>
  </plugins>
</broker>

 

That’s all there is to it 🙂

 

Happy Coding!

 

 

Resources:

IP-based Authentication Plugin for ActiveMQ

To limit the connectivity to the ActiveMQ server based on IP address, we’ll need to override the addConnection() method of the BrokerFilter.class, mentioned in my initial post on ActiveMQ Custom Security Plugins.

 

Example implementation (from the book “ActiveMQ in Action”):

import org.apache.activemq.broker.Broker;
import org.apache.activemq.broker.BrokerFilter;
import org.apache.activemq.broker.ConnectionContext;
import org.apache.activemq.command.ConnectionInfo;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class IPAuthenticationBroker extends BrokerFilter {

  List<String> allowedIPAddresses;
  Pattern pattern = Pattern.compile("^/([0-9\\.]*):(.*)");

  public IPAuthenticationBroker(Broker next, List<String> allowedIPAddresses) {
    super(next);
    this.allowedIPAddresses = allowedIPAddresses;
  }

  public void addConnection(ConnectionContext context, ConnectionInfo info) throws Exception {
    String remoteAddress = context.getConnection().getRemoteAddress();
    Matcher matcher = pattern.matcher(remoteAddress);
    if (matcher.matches()) {
      String ip = matcher.group(1);
        if (!allowedIPAddresses.contains(ip)) {
          throw new SecurityException("Connecting from IP address " + ip + " is not allowed" );
        }
    } else {
      throw new SecurityException("Invalid remote address " + remoteAddress);
    }
    super.addConnection(context, info);
  }
}

As you can see, the implementation above performs a simple check of the IP address using a regular expression to determine the ability to connect. If that IP address is allowed to connect, the call is delegated to the BrokerFilter.addConnection() method. If that IP address isn’t allowed to connect, an exception is thrown.

 

After the actual plug-in logic has been implemented, the plug-in must be configured and installed. For this purpose, we need an implementation of the BrokerPlugin.class, which is used to expose the configuration of a plug-in and to install the plug-in into the ActiveMQ broker.

 

import org.apache.activemq.broker.Broker;
import org.apache.activemq.broker.BrokerPlugin;
import java.util.List;

public class IPAuthenticationPlugin implements BrokerPlugin {

  List<String> allowedIPAddresses;

  public Broker installPlugin(Broker broker) throws Exception {
    return new IPAuthenticationBroker(broker, allowedIPAddresses);
  }

  public List<String> getAllowedIPAddresses() {
    return allowedIPAddresses;
  }

  public void setAllowedIPAddresses(List<String> allowedIPAddresses) {
    this.allowedIPAddresses = allowedIPAddresses;
  }
}

The installPlugin() method above is used to instantiate the plug-in and return a new intercepted broker for the next plug-in in the chain. The IPAuthenticationPlugin.class also contains getter and setter methods used to configure the IPAuthenticationBroker. These setter and getter methods are available via a Spring beans–style XML configuration in the ActiveMQ XML configuration file (example below).

 

// "\apache-activemq\conf\activemq.xml"
<broker brokerName="localhost" dataDirectory="${activemq.base}/data" xmlns="http://activemq.apache.org/schema/core">
  <plugins>
    <bean id="ipAuthenticationPlugin" class="com.mycompany.mysystem.activemq.IPAuthenticationPlugin" xmlns="http://www.springframework.org/schema/beans">
      <property name="allowedIPAddresses">
        <list>
          <value>127.0.0.1</value>
        </list>
      </property>
    </bean>
  </plugins>
</broker>

To summarize, creating custom security plugins using ActiveMQ plugin API, consists of following three steps:

  1. Implementing the plugin logic (overriding methods of the BrokerFilter.class – first code snippet above)
  2. Coding the plugin “installer” (implementing the BrokerPlugin.class – second code snippet)
  3. Configuring the plugin in activemq.xml file (Spring beans-style XML – third code snippet)

 

Happy coding!

 

 

Resources:

ActiveMQ Custom Security Plugins

With this post i’m starting a short series of articles on creating custom security plugin’s for ActiveMQ server (probably the most flexible MOM/messaging solution around; imho).

 

To get a quick overview of how powerful ActiveMQ plugin API really is, let’s start with some basic background information:

  • The flexibility of ActiveMQ plugin API comes from the BrokerFilter class
  • BrokerFilter class provides the ability to intercept many of the available broker-level operations, such as:
    • adding consumers to the broker
    • adding producers to the broker
    • committing transactions in the broker
    • adding connections to the broker
    • removing connections from the broker
  • Custom functionality can be added by extending the BrokerFilter class and overriding a method for a given operation

 

Using the ActiveMQ plugins API is one way to approach broker security; used often for requirements (security, among others) that can’t be met using either:

  • ActiveMQ’s native Simple Authentication Plugin (which handles credentials directly in XML configuration file or in a properties file)
    or
  • JAAS-based pluggable security modules (JAAS stands for Java Authentication and Authorization Service). What is worth mention is that ActiveMQ comes with JAAS-based implementations of some modules that can authenticate users using properties files, LDAP, and SSL certificates; which will be enough for many use cases.

 

OK, having said the above, let’s move on and study following example implementations:

 

 

Resources:

Displaying GIT build number (hash) in your REST API

The product i’m working on currently (a PaaS cloud offering) had a requirement to provide an API resource (GET call) throughout which a user could obtain basic details about the actual version of the API exposed (the api version, build time, corresponding git repo build number (hash id) and the jvm version used to compile the API). Except for the git repo hash part, everything else seemed to be quite easy to obtain. Below you’ll find the solution (step-by-step guide) i came up with.

 

End result (goal):

> curl http://api.my-system.company.com/1.0/
{
  "Implementation-Build" : "2951e7e",
  "Implementation-Build-Time" : "2013/09/17 12:40:02 AM,UTC",
  "Implementation-Jdk" : "1.7.0_15",
  "Implementation-Version" : "1.0-SNAPSHOT",
  "Implementation-Vendor" : "My Company, Inc.",
  "Implementation-Title" : "My System"
}

 

Technologies used:

 

Steps required: 

1. First let’s add the <scm> configuration tag to your master pom.xml file. The connection string represents the repository for which the buildnumber-maven-plugin will obtain the git hash id.

<scm>
    <!-- Replace the connection below with your project connection -->
    <connection>scm:git:git://github.com/mariuszprzydatek/hyde-park.git</connection>
</scm>

 

2. configure the maven-war-plugin to generate project’s MANIFEST.MF file, where the git hash id will be stored. Also, the Spring MVC controller will read this file in order to return its content as a result of GET call.

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-war-plugin</artifactId>
    <version>2.3</version>
    <configuration>
        <archive>
            <addMavenDescriptor>false</addMavenDescriptor>
            <manifest>
                <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
            </manifest>
        </archive>
        <warName>1.0</warName>
    </configuration>
</plugin>

 

3. In the <properties> section of the pom we can define the format for the date timestamp that will be returned as the value of “Implementation-Build-Time” attribute.

<properties>
    <maven.build.timestamp.format>yyyy/MM/dd hh:mm:ss a,z</maven.build.timestamp.format>
</properties>

 

4. Next, let’s add the remaining pom sections that we’ll be storing in the MANIFEST.MF file for further read:

    <version>1.0-SNAPSHOT</version>
    <organization>
        <name>My Company, Inc.</name>
    </organization>
    <name>My System</name>

 

5. within the <archive> key of the maven-war-plugin <configuration> section, we need to add additional manifest entries including the one (<Implementation-Build>) that will be generated by the buildnumber-maven-plugin:

<archive>
    ...
    <manifestEntries>
        <Implementation-Build>${buildNumber}</Implementation-Build>
        <Implementation-Build-Time>${maven.build.timestamp}</Implementation-Build-Time>
    </manifestEntries>
</archive>

 

6. Add the buildnumber-maven-plugin itself which will do the hard work:

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>buildnumber-maven-plugin</artifactId>
    <version>1.1</version>
    <executions>
        <execution>
            <phase>validate</phase>
            <goals>
                <goal>create</goal>
            </goals>
        </execution>
    </executions>
</plugin>

 

7. Finally, add the <configuration> section to the buildnumber-maven-plugin together with the <shortRevisionLength> key that is responsible for the length of git hash id we want to export:

<configuration>
    <shortRevisionLength>7</shortRevisionLength>
</configuration>

 

 

Now, let’s create the Spring MVC controller that will be handling the MANIFEST.FM file read and returning its content to the presentation layer.

@Controller
@RequestMapping
public class ApiController {

    /**
     * Handling GET request to retrieve details from MANIFEST.MF file
     * @return implementation details
     */
    @RequestMapping(method = RequestMethod.GET)
    public @ResponseBody Map<String, String> getBuildNumber(HttpServletRequest request) throws IOException {

        ServletContext context = request.getSession().getServletContext();
        InputStream manifestStream = context.getResourceAsStream("/META-INF/MANIFEST.MF");
        Manifest manifest = new Manifest(manifestStream);

        Map<String, String> response = new HashMap<>();
        response.put("Implementation-Vendor", manifest.getMainAttributes().getValue("Implementation-Vendor"));
        response.put("Implementation-Title", manifest.getMainAttributes().getValue("Implementation-Title"));
        response.put("Implementation-Version", manifest.getMainAttributes().getValue("Implementation-Version"));
        response.put("Implementation-Jdk", manifest.getMainAttributes().getValue("Build-Jdk"));
        response.put("Implementation-Build", manifest.getMainAttributes().getValue("Implementation-Build"));
        response.put("Implementation-Build-Time", manifest.getMainAttributes().getValue("Implementation-Build-Time"));

        return response;

    }

}

 

 

Hope you enjoyed this post.

Take care!

Redis data sharding – part 2 – hash-based keys

In my previous post on Redis data sharding i introduced the concept of data sharding/partitioning and provided a small Java code example to illustrate the idea. As you noticed i was creating fixed-size “emailbuckets”, containing 1024 emails each. An email address was the key of my hash, while user id was the value. For a shard identifier i used a simple integer value (shardKey) obtained as a result of “i mod shardSize” operation.

Whereas such approach illustrates the concept well, it’s impractical in “real life” applications. This is due to a simple reason – knowing the email address alone (which may often be the only thing you’d know at some point of the app execution flow; for example when you’re using Spring Security and requesting emails “as usernames” during sign-in process), you wouldn’t be able to retrieve the corresponding userId. If you would know the algorithm by which shardKey was generated, in this case – yes – you would be able to traverse emailbuckets one-by-one looking for the appropriate email, but without that knowledge you wouldn’t be able to tell which shard the email address you’re looking for, ended up in.

One solution to that problem is to use a different key for sharding data; something that is computed directly based on the data you’re interested in partitioning. An obvious candidate here is the email address itself. If you’d be able to generate shardKey based on email address you could reproduce the same scenario every time a user provides you with his email during signing in and retrieve his userId (which you could use later on (for example) to further query another hash in Redis – “users:id” – that stores complete user profile).

 

This seems like an ideal task for a hash function… Let’s start first with some background on hashing. According to Neil Coffey’s Javamex article Introduction to hashing:

  • Hashing means using some function or algorithm to map object data (eg. content of a String object) to some representative integer value. This so-called hash code (or simply hash) can then be used as a way to narrow down our search when looking for the item…

 

Also, when you search Wikipedia after Java hashCode() function, you’ll get the following definition:

  • In the Java programming language, every class must provide a hashCode() method which digests the data stored in an instance of the class into a single hash value (a 32-bit signed integer). This hash is used by other code when storing or manipulating the instance – the values are intended to be evenly distributed for varied inputs in order to use in clustering. This property is important to the performance of hash tables and other data structures that store objects in groups (“buckets”) based on their computed hash values.

 

Looks like this is exactly what we’re interested in – …evenly distributed values for varied inputs…, which …is important to the performance of data structures that store objects in groups (shards in our case) based on their computed hash values.

Conclusion: Java hashCode() function is what we’ll proceed with.

 

More from Wikipedia on Java hashCode():

  • Starting with Java version 1.2, the java.lang.String class implements its hashCode() using a product sum algorithm over the entire text of the string.
  • An instance s of the java.lang.String class, would have a hash code h(s) defined by:
    Java String hashCode()

     

  • where terms are summed using Java 32-bit int addition, s[i] denotes the i-th character of the string, and n is the length of s.

 

Now, looking at Java docs on String.hashCode() function we can read:

  • The hash code for a String object is computed as
    s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]

     

  • using int arithmetic, where s[i] is the ith character of the string, n is the length of the string, and ^ indicates exponentiation. (The hash value of the empty string is zero.)

 

Finally let’s take a look at some Java code of String object showing how hashCode() has actually been implemented:

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

 

An alternative (faster) implementation may look like this (from Apache Harmony JDK):

public int hashCode() {
    if (hashCode == 0) {
        int hash = 0, multiplier = 1;
        for (int i = offset + count - 1; i >= offset; i--) {
            hash += value[i] * multiplier;
            int shifted = multiplier << 5;
            multiplier = shifted - multiplier;
        }
        hashCode = hash;
    }
    return hashCode;
}

what’s the difference between the two above code snippets? As you can see, multiplication can be replaced by a bitwise shift operation and a subtraction for better performance. “(multiplier << 5) – multiplier” is just 31*multiplier after all (however VMs nowadays do this optimization automatically). If you’re interested in good reading on the subject of Binary numbers i strongly recommend Neil Coffey’s Javamex article: Introduction to binary numbers.

 

OK, applying all this knowledge to our sharding code example results in the following implementation:

while(i<1000000) {
    String userId = String.valueOf(i++);
    String emailAddress = String.format("user_%s@mariuszprzydatek.com", userId);
    int shardKey = emailAddress.hashCode();
    redisTemplate.opsForHash().put(String.format("emailsbucket:%s", shardKey), emailAddress, userId);
}

 

Happy coding! 🙂

 

 

Resources:

Spring REST API hardening – exceptions handling

For some time now there’s a lot of discussion over the internet on the definition of a unified RESTful API standard (architecture, design, interfaces, etc.). RFC’s are being discussed by IETF committees trying to come up with a standard, work continues on re-designing the HTTP protocol itself (HTTPbis, by the IETF HTTPbis WG chaired by Mark Nottingham), but what we have thus far are only good and bad practices…

 

What i’d like to address in this post are good practices related to exceptions handling in a Java Spring MVC based RESTful API application.

 

Let’s start with this: In order to make your REST API more developer-friendly, you may want your MVC controllers to return (within the body of the response), some information that can assist the client developer while using your API.

 

Using a JSON data format, a sample controller response in situation of an exception may look like this:

{
    "code" : "SAE00202",
    "status" : "SECURITY_AUTHORITY_ERROR",
    "errors" : [ "Security Exception: Insufficient Authorization Level. Access denied" ]
}

 

Here’s an example of how you can achieve that easily using Spring MVC’s @ControllerAdvice annotation (which indicates the annotated class assists a “Controller” and serves as a specialization of @Component annotation, allowing implementation classes to be auto-detected through classpath scanning).

@ControllerAdvice
public class BusinessExceptionHandler extends DefaultExceptionHandler {

    @ExceptionHandler(IncompleteUserProfileException.class)
    @ResponseStatus(value = HttpStatus.PRECONDITION_FAILED)
    @ResponseBody DefaultErrorMessage handleIncompleteUserProfileException(IncompleteUserProfileException e) {

        if(debug) logException(e);

        String error = getResourceBundle().getMessage("exception.user.profile.incomplete", null, Locale.getDefault());

        return new DefaultErrorMessage("RS00302", "BUSINESS_ERROR", error);

    }

}

 

Aside from the fact that you may want to write custom handlers like the one above for your application-specific exceptions (Business/Security/Validation related, etc.), you may also want to “go deeper” and extend the ResponseEntityExceptionHandler class (abstract), and override the default implementation of the exceptions below; thrown usually by MVC Controllers:

Spring MVC ResponseEntityExceptionHandler Exceptions

 

In order to do that, first you have to start with a simple POJO representing your default error message:

public class DefaultErrorMessage {
    private String code;
    private String status;
    private List errors = new ArrayList<>();

    public DefaultErrorMessage(String code, String status, String error) {
        this.code = code;
        this.status = status;
        this.errors.add(error);
    }

    public DefaultErrorMessage(String code, String status, List errors) {
        this.code = code;
        this.status = status;
        this.errors = errors;
    }

    // getters and setters omitted
}

 

Having this in place, a custom implementation of BindException may look like this:

@ControllerAdvice
public class CustomResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

    @Override
    protected ResponseEntity<Object> handleBindException(BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {

        List<String> errors = new ArrayList<>(ex.getAllErrors().size());
        List<FieldError> fieldErrors = ex.getFieldErrors();
        StringBuilder sb;

        for (FieldError fieldError : fieldErrors) {
            sb = new StringBuilder();
            sb.append("Field: ").append(fieldError.getField()).append(", ");
            sb.append("Value: ").append(fieldError.getRejectedValue()).append(", ");
            sb.append("Message: ").append(fieldError.getDefaultMessage());
            errors.add(sb.toString());
        }

        List<ObjectError> globalErrors = ex.getGlobalErrors();

        for (ObjectError objectError : globalErrors) {
            sb = new StringBuilder();
            sb.append("Object: ").append(objectError.getObjectName()).append(", ");
            sb.append("Code: ").append(objectError.getCode()).append(", ");
            sb.append("Message: ").append(objectError.getDefaultMessage());
            errors.add(sb.toString());
        }

        DefaultErrorMessage errorMessage = new DefaultErrorMessage("RQ00051", "RQ_BODY_VALIDATION_ERROR", errors);
        return new ResponseEntity(errorMessage, headers, status);

    }

}

 

I think you see the direction it’s heading in…

 

Happy coding 🙂

Cheers!

 

 

Resources:

Redis data sharding – part 1

In one of my previous posts on Redis i provided a definition of data sharding, quoting a great book “Redis in Action” authored by Dr. Josiah L Carlson:

  • “Sharding is a method by which you partition your data into different pieces. In this case, you partition your data based on IDs embedded in the keys, based on the hash of keys, or some combination of the two. Through partitioning your data, you can store and fetch the data from multiple machines, which can allow a linear scaling in performance for certain problem domains.”

 

Today i’d like to elaborate some more on data sharding based on IDs embedded in the keys.

 

Let’s start with an example of a hypothetical data stored in an Redis instance:

redis 127.0.0.1:6379>keys *
(empty list or set)
redis 127.0.0.1:6379>set emails:1 me@mariuszprzydatek.com
OK
redis 127.0.0.1:6379>get emails:1
"me@mariuszprzydatek.com"

what i did here is to use the basic String data type to store an email of a user. As you can see, i embedded user id within the key (’emails:1′). Now, if a front-end application would ask for an email address of a user with id=1, on the back-end side i would concatenate the keyword which i usually use to denote keys i store emails under (ie. ’emails’) with the id of a user (‘1’), add a colon (‘:’) in between, and this way i’ll get the resulting key (’emails:1′) i should look after while making a call to the Redis instance.

 

This solution is nice but if i’ll have 1 million of users registered in my system and use Redis as the data store for keeping mappings between identifier of a user and his email, i will end up with 1 million keys (’emails:1′, ’emails:2′, ’emails:3′, etc.). This is a volume my Redis instance will easily handle (see my previous post on Redis Performance Basics) and it will use little more than 190MB to store everything in the memory (so much due to a lot of overhead when storing small keys and values; the ratio is much better with large keys/values), but this is only one attribute we’re talking about – and what about firstName, lastName, etc.?. Obviously, if my system will have millions of registered users and i’d use Redis as my primary data store for users-related info, i would be running multiple instances of Redis already and based on the id of a user, route the queries to a specific instance, but there’s still a lot we can do to optimize costs prior to thinking about scaling.

 

Small code snippet to generate 1M of emails stored in Redis using String data structure (and Spring Data Redis mentioned in my other post).

int i = 0;
while(i<1000000) {
    redisTemplate.opsForValue().set(String.format("emails:%s", i++), "me@mariuszprzydatek.com");
}

the loop above executes in 2 mins on my Windows 8 64bit i7 laptop and the ‘redis-server’ process allocates ca 190 MB of memory.

 

Now, what will happen if we change the data structure let’s say to a Redis Hash?

Next code snippet and we’re getting just that:

int i = 0;
while(i<1000000) {
    String userId = String.valueOf(i++);
    String emailAddress = String.format("user_%s@mariuszprzydatek.com", userId);
    redisTemplate.opsForHash().put("emails", emailAddress, userId)
}

2 mins and 165 MB of memory allocated – a 15 % gain absolutely for free.

 

Let’s try with data sharding/partitioning. Another code snippet using Redis Hash data structure and there you go:

int shardSize = 1024;
int i = 0;
while(i<1000000) {
    int shardKey = i/shardSize;
    String userId = String.valueOf(i++);
    String emailAddress = String.format("user_%s@mariuszprzydatek.com", userId);
    redisTemplate.opsForHash().put(String.format("emailsbucket:%s", shardKey), emailAddress, userId);
}

2 mins later and… only 30 MB allocated – now you’re talking Mariusz!

Staggering 530 % increase in memory allocation efficiency!

 

Hope you enjoyed the first part of this brief tutorial.

 

Cheers!

 

 

Resources: