{{announcement.body}}
{{announcement.title}}

Comparing Apache Ignite In-Memory Cache Performance With Hazelcast In-Memory Cache and Java Native Hashmap

DZone 's Guide to

Comparing Apache Ignite In-Memory Cache Performance With Hazelcast In-Memory Cache and Java Native Hashmap

This article compares different options for the in-memory maps and their performances in order for an application to move away from traditional RDBMS.

· Performance Zone ·
Free Resource

Overview

This article compares different options for the in-memory maps and their performances in order for an application to move away from traditional RDBMS tables for frequently accessed data.  In this case, for the sake of demonstration, I have taken  2 million dummy physician records that reside in the database table and migrated them to in-memory maps. The migration will enable the application to quickly lookup in the map and vet the physician rather than querying the database table for vetting.

You may also like: Hazelcast With Spring Boot on Kubernetes

Source code for the Java clients to create these read-only distributed maps has been added to this article as well. All the Java clients used would create an in-memory map to save the physician objects with the physician's NPI as the key in the map. I have created an XML to export all the 2 million physicians for this purpose and below is the sample of that XML.

XML
 






 

All the 2 million physician records have been loaded to native Java HashMap, Apache Ignite's Distributed Cache and Hazlecast's Distributed Cache to measured and compare the key performance metrics like the memory and CPU utilization along with load and read times.

The first option I have tried is native Java HashMap to load all the 2 million physician records. Below is the code to parse through the XML file and create a HashMap at the application startup.

Domain Class Used

Java
 




xxxxxxxxxx
1
55


1
package prototype.domain;
2
 
          
3
import java.io.Serializable;
4
import java.util.ArrayList;
5
import java.util.List;
6
 
          
7
import com.google.common.collect.LinkedListMultimap;
8
import com.google.common.collect.ListMultimap;
9
 
          
10
public class PhysicianProfile implements Serializable {
11
 
          
12
    private static final long serialVersionUID = 3056557315467894990L;
13
    private String npi;
14
    private String profileId;
15
    private ListMultimap<String, String> nameMultiMap = LinkedListMultimap.create();
16
    private List<String> licenseList = new ArrayList<String>();
17
    private ListMultimap<String, String> licenseExpirationMultiMap;
18
 
          
19
    public String getNpi() {
20
        return npi;
21
    }
22
    public void setNpi(String npi) {
23
        this.npi = npi;
24
    }
25
    public String getProfileId() {
26
        return profileId;
27
    }
28
    public void setProfileId(String profileId) {
29
        this.profileId = profileId;
30
    }
31
    public ListMultimap<String, String> getNameMultiMap() {
32
        return nameMultiMap;
33
    }
34
    public void setNameMultiMap(ListMultimap<String, String> nameMultiMap) {
35
        this.nameMultiMap = nameMultiMap;
36
    }
37
    public List<String> getLicenseList() {
38
        return licenseList;
39
    }
40
    public void setLicenseList(List<String> licenseList) {
41
        this.licenseList = licenseList;
42
    }
43
    public ListMultimap<String, String> getLicenseExpirationMultiMap() {
44
        if(null != licenseExpirationMultiMap){
45
            return licenseExpirationMultiMap;
46
        }else{
47
            return LinkedListMultimap.create();
48
        }
49
    }
50
    public void setLicenseExpirationMultiMap(ListMultimap<String, String> licenseExpirationMultiMap) {
51
        this.licenseExpirationMultiMap = licenseExpirationMultiMap;
52
    }
53
 
          
54
}
55
 
          



Java HashMap Loader

Java
 




x


1
package prototype.physician.cache;
2
 
          
3
import java.io.BufferedInputStream;
4
import java.io.FileInputStream;
5
import java.io.IOException;
6
import java.util.HashMap;
7
import java.util.List;
8
 
          
9
import javax.xml.parsers.ParserConfigurationException;
10
import javax.xml.parsers.SAXParser;
11
import javax.xml.parsers.SAXParserFactory;
12
 
          
13
import org.apache.commons.lang3.StringUtils;
14
import org.apache.commons.lang3.time.StopWatch;
15
import org.apache.log4j.Logger;
16
import org.xml.sax.Attributes;
17
import org.xml.sax.SAXException;
18
import org.xml.sax.helpers.DefaultHandler;
19
 
          
20
import prototype.domain.PhysicianProfile;
21
 
          
22
public class PhysicianProfileXMLUtil extends DefaultHandler {
23
    
24
    static private Logger logger = Logger.getLogger(PhysicianProfileXMLUtil.class);
25
    
26
    private StopWatch stopWatch = new StopWatch();
27
    private String npi = null;
28
    private String profileId = null;
29
    private String firstName = null;
30
    private String lastName = null;
31
    private String alternateFirstName = null;
32
    private String alternateLastName = null;
33
    private PhysicianProfile physicianProfile = null;
34
    private String licenseState = null;
35
    private String licenseNumber = null;
36
     
37
    private static final long MEGABYTE = 1024L * 1024L;
38
 
          
39
    /**
40
     * @param bytes
41
     * @return
42
     */
43
    public static long bytesToMegabytes(long bytes) {
44
        return bytes / MEGABYTE;
45
    }
46
    
47
    public static HashMap<String, PhysicianProfile> inMemoryPhysicianMap = new HashMap<String, PhysicianProfile>();
48
    private StringBuilder textBuilder;
49
    private boolean isTextField;
50
    /* (non-Javadoc)
51
     * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
52
     */
53
    @Override
54
    public void startElement(String uri, String localName, String qName,
55
            Attributes attributes) throws SAXException {
56
        switch (qName) {
57
            case "physicianProfiles":
58
                stopWatch.start();
59
                break;
60
            case "physicianProfile":
61
                physicianProfile = new PhysicianProfile();
62
                break;
63
            case "firstName":
64
                isTextField = true;
65
                textBuilder = new StringBuilder();
66
                break;
67
            case "lastName":
68
                isTextField = true;
69
                textBuilder = new StringBuilder();
70
                break;
71
            case "alternateFirstName":
72
                isTextField = true;
73
                textBuilder = new StringBuilder();
74
                break;
75
            case "alternateLastName":
76
                isTextField = true;
77
                textBuilder = new StringBuilder();
78
                break;
79
            case "npi":
80
                isTextField = true;
81
                textBuilder = new StringBuilder();
82
                break;
83
            case "profileId":
84
                isTextField = true;
85
                textBuilder = new StringBuilder();
86
                break;
87
            case "licenseState":
88
                isTextField = true;
89
                textBuilder = new StringBuilder();
90
                break;
91
            case "licenseNumber":
92
                isTextField = true;
93
                textBuilder = new StringBuilder();
94
                break;
95
            default: 
96
                break;
97
        }
98
                
99
    }
100
    
101
    /* (non-Javadoc)
102
     * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
103
     */
104
    @Override
105
    public void endElement(String uri, String localName, String qName) throws SAXException {
106
        switch (qName) {
107
            case "physicianProfiles":
108
                stopWatch.stop();
109
                System.out.println("Time Taken to complete :: "+stopWatch.getTime());
110
                System.out.println("In-Memory List Size :: "+inMemoryPhysicianMap.size());
111
                break;
112
            case "physicianProfile":
113
                physicianProfile.setNpi(npi.trim());
114
                physicianProfile.setProfileId(profileId.trim());
115
                if(null != npi){
116
                    inMemoryPhysicianMap.put(npi, physicianProfile);
117
                }               
118
                clearAttributes();
119
                break;
120
            case "name":
121
                if(StringUtils.isNotBlank(firstName)){
122
                    physicianProfile.getNameMultiMap().put(firstName, lastName);
123
                }
124
                if(StringUtils.isNotBlank(alternateFirstName)){
125
                    physicianProfile.getNameMultiMap().put(alternateFirstName, alternateLastName);
126
                }
127
                clearNameAttributes();
128
                break;
129
            case "firstName":
130
                firstName = this.textBuilder.toString().toUpperCase();
131
                this.textBuilder = null;
132
                isTextField = false;
133
                break;
134
            case "lastName":
135
                lastName = this.textBuilder.toString().toUpperCase();
136
                this.textBuilder = null;
137
                isTextField = false;
138
                break;
139
            case "alternateFirstName":
140
                alternateFirstName = this.textBuilder.toString().toUpperCase();
141
                this.textBuilder = null;
142
                isTextField = false;
143
                break;
144
            case "alternateLastName":
145
                alternateLastName = this.textBuilder.toString().toUpperCase();
146
                this.textBuilder = null;
147
                isTextField = false;
148
                break;
149
            case "npi":
150
                npi = this.textBuilder.toString();
151
                this.textBuilder = null;
152
                isTextField = false;
153
                break;
154
            case "profileId":
155
                profileId = this.textBuilder.toString();
156
                this.textBuilder = null;
157
                isTextField = false;
158
                break;
159
            case "licenseState":
160
                licenseState = this.textBuilder.toString();
161
                this.textBuilder = null;
162
                isTextField = false;
163
                break;
164
            case "licenseNumber":
165
                licenseNumber = this.textBuilder.toString();
166
                this.textBuilder = null;
167
                isTextField = false;
168
                break;
169
            case "license":
170
                if(StringUtils.isNotBlank(licenseState) && StringUtils.isNotBlank(licenseNumber)
171
                        && !physicianProfile.getLicenseList().contains(licenseState.toUpperCase()+"-"+licenseNumber.toUpperCase())){
172
                     physicianProfile.getLicenseList().add(licenseState.toUpperCase()+"-"+licenseNumber.toUpperCase());
173
                }
174
                clearLicenseAttributes();
175
                break;
176
            default: 
177
                break;
178
            
179
        }
180
    }
181
    
182
    /**
183
     * 
184
     */
185
    private void clearLicenseAttributes() {
186
        licenseState = null;
187
        licenseNumber = null;
188
    }
189
 
          
190
    /**
191
     * 
192
     */
193
    private void clearNameAttributes() {
194
        firstName = null;
195
        lastName = null;
196
        alternateFirstName = null;
197
        alternateLastName = null;       
198
    }
199
 
          
200
    /**
201
     * 
202
     */
203
    private void clearAttributes() {
204
        npi = null;
205
        profileId = null;
206
        clearNameAttributes();
207
        clearLicenseAttributes();
208
    }
209
 
          
210
    /* (non-Javadoc)
211
     * @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int)
212
     */
213
    @Override
214
    public void characters(char[] chars, int start, int length) throws SAXException {
215
        if(isTextField) {
216
            textBuilder.append(chars, start, length);
217
        }
218
    }
219
    
220
    
221
    
222
    /**
223
     * @param args
224
     * @throws SAXException 
225
     * @throws ParserConfigurationException 
226
     * @throws IOException 
227
     */
228
    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
229
        DefaultHandler handler = new PhysicianProfileXMLUtil();
230
         
231
        SAXParserFactory factory = SAXParserFactory.newInstance();
232
 
233
        factory.setValidating(false);
234
 
235
        SAXParser parser = factory.newSAXParser();
236
  
237
        FileInputStream fin=new FileInputStream("C:\\testfile.xml");   
238
        
239
        BufferedInputStream bin=new BufferedInputStream(fin);    
240
        
241
        parser.parse(bin, handler);
242
        StopWatch stopWatch = new StopWatch();
243
        stopWatch.start();
244
        //get random profile 
245
        String profileId = PhysicianProfileXMLUtil.inMemoryPhysicianMap.get("3001999993").getProfileId();
246
        stopWatch.stop();
247
        System.out.println("PhysicianProfileXMLUtil.inMemoryPhysicianMap.get(\"3001999993\").getProfileId() takes " + 
248
                stopWatch.getNanoTime() + " nano seconds"); 
249
        System.out.println(profileId);
250
        stopWatch.reset();
251
        
252
        stopWatch.start();
253
        //get name map from random profile 
254
        String nameMap = PhysicianProfileXMLUtil.inMemoryPhysicianMap.get("3000999993").getNameMultiMap().asMap().toString();
255
        stopWatch.stop();
256
        System.out.println("PhysicianProfileXMLUtil.inMemoryPhysicianMap.get(\"3000999993\").getNameMultiMap().asMap().toString() " + 
257
                stopWatch.getNanoTime() + " nano seconds"); 
258
        System.out.println(nameMap);
259
        stopWatch.reset();
260
        
261
        stopWatch.start();
262
        //get name list from random profile 
263
        List<String> nameList = PhysicianProfileXMLUtil.inMemoryPhysicianMap.get("3000099993").getNameMultiMap().get("AltFirstName2");
264
        stopWatch.stop();
265
        System.out.println("PhysicianProfileXMLUtil.inMemoryPhysicianMap.get(\"3000099993\").getNameMultiMap().get(\"AltFirstName2\") takes " + 
266
                stopWatch.getNanoTime() + " nano seconds"); 
267
        System.out.println(nameList);
268
        stopWatch.reset();
269
        
270
        stopWatch.start(); 
271
        boolean flag = PhysicianProfileXMLUtil.inMemoryPhysicianMap.get("3000009993").getLicenseList().contains("STATE-LCNS5678");
272
        stopWatch.stop();
273
        System.out.println("PhysicianProfileXMLUtil.inMemoryPhysicianMap.get(\"3000009993\").getLicenseList().contains(\"STATE-LCNS5678\") takes " + 
274
                stopWatch.getNanoTime() + " nano seconds"); 
275
        System.out.println(flag);
276
        stopWatch.reset();
277
        
278
        stopWatch.start();
279
        flag = PhysicianProfileXMLUtil.inMemoryPhysicianMap.get("3000000993").getLicenseList().contains("STATE-LCNS23123");
280
        stopWatch.stop();
281
        System.out.println("PhysicianProfileXMLUtil.inMemoryPhysicianMap.get(\"3000000993\").getLicenseList().contains(\"STATE-LCNS23123\") takes " + 
282
                stopWatch.getNanoTime() + " nano seconds"); 
283
        System.out.println(flag);
284
        
285
        // Get the Java runtime
286
        Runtime runtime = Runtime.getRuntime();
287
        System.out.println(bytesToMegabytes(runtime.totalMemory()));
288
        // Run the garbage collector
289
        runtime.gc();
290
        System.out.println(bytesToMegabytes(runtime.freeMemory()));
291
        // Calculate the used memory
292
        long memory = runtime.totalMemory() - runtime.freeMemory();
293
        System.out.println("Used memory is bytes: " + memory);
294
        System.out.println("Used memory is megabytes: " + bytesToMegabytes(memory));
295
       
296
    }
297
 
          
298
}
299
 
          



The second option used is Apache Ignite's Key-Value store. Below is the code for parsing the physician XML and load it to the Ignite's data grid.

Apache Ignite Data Grid Loader

In order to use Apache Ignite's cache, I have replaced below line of code in the Java class

XML
 




xxxxxxxxxx
1


 
1
public static HashMap<String, PhysicianProfile> inMemoryPhysicianMap = new HashMap<String, PhysicianProfile>();
2
 
          



with below code

Java
 







HazelCast Map Loader

For HazelCast Map, I have replaced the below line of code

XML
 




xxxxxxxxxx
1


 
1
public static HashMap<String, PhysicianProfile> inMemoryPhysicianMap = new 
2
  HashMap<String, PhysicianProfile>();



with below code

Java
 




x


 
1
    public static HazelcastInstance hz = Hazelcast.newHazelcastInstance();
2
    public static IMap<String, PhysicianProfile> inMemoryPhysicianMap = hz.getMap(CACHE_NAME);



Results

All the loader classes were run on a machine with Windows 10 64-bit Operating System using Amazon Corretto JDK 11. The computer has 16GB ram with Intel Core i7-6820HQ 2.70GHz Processor.  Both Apache Ignite and Hazelcast nodes were created with out-of-the-box configuration. I have run all three programs 10 times and taken an average of the results. Below are the results of the three maps when installed as a single node. 


Java Native HashMap Apache Ignite Cache Hazelcast Map
Time consumed to load 2 million records 114.525 Seconds 135.695 Seconds 301.015 Seconds
Average read time from Map 10194 Nanoseconds =  0.010194 Milliseconds 3432403 Nanoseconds = 3.432403 Milliseconds 7979049 Nanoseconds = 7.979049 Milliseconds
Memory consumed to store 2 million physician profiles 2530 MB 24 MB 1448 MB


CPU Utilization Graphs

Java Native HashMap

graph

Apache Ignite

Apache Ignite

Hazelcast 

Hazelcast

Final Verdict

Although Java Native Hashmap read times are 300 times faster when compared to Apache Ignite, in a clustered environment that has resource constraints it is almost impossible to use it due to its resource utilization.  The application will have issues when more records needed to be loaded into the Java Native Hashmap. 

For limited data in a non-clustered environment, Hashmap will be an ideal solution. Whereas in a clustered environment, data will need to be redundantly loaded to all the JVMs, which will not be an ideal way of using the memory across the JVMs. This is not the case with Apache Ignite and Hazelcast as they can be easily deployed in network topologies in order for the application to access the map across the network.

Java Native HashMap has used around 2.5 GB of memory whereas Apache Ignite has only used 24 MB while Hazelcast has used 1448 MB to store all the 2 million physician profiles. CPU utilization of Java Native Hashmap loader kept increasing by the number of records inserted while Apache Ignite and Hazelcast loaders CPU utilization are almost flat. Hazelcast was far behind Apache Ignite in Loading, Reading times and in resource utilization. 

This makes Apache Ignite a clear winner for Distributed Map use cases.


Further Reading

Hazelcast for Go Getters, Part 1

Apache Ignite Cluster Together With Spring Boot

Using Apache Ignite Thin Client

Topics:
distributed cache ,apache ignite ,in-memory caching ,big data ,java ,in-memory data grid ,performance

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}