Limitations of putlfAbsent() and Improvements in Change Detection

Limitations of putlfAbsent() and Improvements in Change Detection

1. Introduction

In real-time monitoring systems, it is often necessary to synchronize the metadata of calculation formulas across multiple services. In this project, we needed a functionality to publish events whenever a calculation formula was created or changed to communicate the changes to other services.

In the initial implementation, to prevent duplicate creation of the same calculation formula, we used ConcurrentHashMap's putIfAbsent() method. By placing putIfAbsent() in the condition, the event to transmit the calculation formula would be issued only when it passed. We assumed that a calculation formula, once created, would generally not change, so we thought it would be sufficient to handle the initial registration only.

However, while reviewing various operational scenarios, we discovered unexpected issues. Some calculation formulas could change based not only on the initially set values but also on the available sensor signal configurations, and the existing structure could not detect these changes.

For example, a specific calculation formula is generated as follows when only a signal called A is present.

 Value = A + 1.01

However, during operation, depending on the true/false status of a signal called B, the calculation formula can change as follows.

 Value = A - 1.01

In other words, even if it's the same calculation formula, the formula itself can vary based on the available signal configurations.

The problem is that putIfAbsent()does not perform any operation for an already registered key. Therefore, even if the calculation formula changes, the existing value remains unchanged, and no change event is published.

As a result, other services may not receive the latest calculation formula, leading to metadata inconsistencies between services.

In this article, I would like to summarize the reasons for choosing putIfAbsent()in the initial design, its limitations, and the process of improving it by utilizing replace()to handle change detection and event synchronization together.

2. Initial Design - Preventing Duplicate Creation Using putIfAbsent()

Before starting the design, I checked the calculation formulas used in the project. When looking at the calculation formulas during the initial design, they were all formulas conditioned on initial settings. I also deemed that once a calculation formula is created, it would not change during operation unless the initial setting values change. Therefore, I thought that the calculation formula only needed to be stored once at first.

I designed a structure where the calculation formula is stored in memory at the time it is first created, and at the same time, an event is published to deliver the calculation formula information to other services.

Additionally, since this function needed to operate in an environment where multiple threads could access it simultaneously, a cache for storing calculation formula information was required.ConcurrentHashMapwas used. The initial implementation was very simple.

image1.png

putIfAbsent()only saves a value if the specified Keydoes not already exist, and returns the existing value if it does. Therefore, events could only be published when the initial calculation formula was created, and duplicate event occurrences for the same calculation formula were naturally prevented.

At that time, it was judged that the calculation formula would not change once created. This was because the initial setting values were fixed values that rarely changed. Even if they did change, it was thought that new calculation formulas could be generated again by restarting the service to reset the memory.

3. Issues discovered during the review of new calculation formula requirements

After completing the initial implementation, I attended a meeting to review the requirements for other calculation functions. Although this function was separate from the current development task, during the review of the requirements, I discovered aspects that could impact the existing design.

Some of the calculation formulas discussed in the meeting could change not only based on initial setting values but also depending on the presence of sensor signals or the values of those signals.

For example, if a specific sensor signal is absent, calculations would be performed using only A, but once the sensor starts collecting normally, the formula had to change to one that uses both A and B. Alternatively, the calculation formula needed to change based on whether the value of a signal B was true/false.

While reviewing these requirements, I took another look at the currently implemented putIfAbsent()based structure. I discovered that the existing structure could not detect changes even if the already created calculation formulas were modified.

4. Redefining requirements for detecting changes in calculation formulas

The existing structure only satisfied the requirement of event publication upon the initial creation of the calculation formula. However, to align with the new requirements, this alone was not sufficient. If the calculation formula could change, not only should the initial creation but also the changes needed to be detected.

In summary, a new structure that satisfies the following conditions was necessary.

1. Event publication upon initial creation of the calculation formula

2. Event publication upon change of the calculation formula

3. No event publication if the calculation formula is the same 

4. Safe operation in a multithreaded environment

putIfAbsent()could only handle the initial creation. For already registered Keyit was impossible to compare or verify changes even if a new calculation formula was introduced. Hence, a separate structure capable of detecting changes to the calculation formula was needed.

5. Change detection using replace() of ConcurrentHashMap

Initially, a method of simply querying the existing value and comparing it to determine if a change had occurred was considered.

image2.png

However, this approach was not safe in a multithreaded environment. For instance, if two threads attempt to modify the same calculation formula information simultaneously, both threads could retrieve the existing value and determine that a change had occurred. In this case, there is a possibility of redundant event publication for the same calculation formula change.

Therefore, to solve this problem, ConcurrentHashMapprovided by replace() method was utilized.

image3.png

replace(key, oldValue, newValue)is currently Mapthe value stored in oldValueonly if it is the same as newValueis replaced with. In other words, it works as follows:

image4.png

If another thread has changed the value first, the current Mapvalue is already oldValuewill not be. In this case replace()is falsedoes not perform value replacement and returns. Therefore, only threads that have successfully changed the calculation expression can issue events. Ultimately, replace()The return value was used to determine whether to publish the event.

This allowed events to be emitted only when a change in the calculation formula occurred, and it also helped prevent the issue of duplicate events that can arise in a multithreaded environment.

6. Conclusion

While developing this feature, I initially focused solely on preventing the duplication of calculation formulas. When considering only the initial requirements,putIfAbsent()It seemed sufficient to use just the structure. This is because we were able to effectively prevent duplicate storage and duplicate event publishing for the same calculation formula.

However, during the process of reviewing the new calculation requirements, we discovered the possibility that the calculation formula could change while in operation, and we confirmed that the existing design could not handle such situations. To resolve this, we improved the structure to detect not only the initial creation of the formula but also any changes to it.ConcurrentHashMapof replace()was implemented to work safely even in a multi-threaded environment.

Through this experience, I once again realized the importance of considering not only the current requirements but also potential scenarios that may arise in the future. Additionally, I learned that the process of reviewing various scenarios and identifying the limitations of the design for improvement is also an important part of development, not just stopping at implementing functionality.

Yang

Site footer