Full Blog TOC

Full Blog Table Of Content with Keywords Available HERE

Monday, October 16, 2023

Lazy Update - Reduce Redis Load Method

 



In the following post we will review a method to reduce load on a Redis. I've have used this method in several projects, and it make the difference between a non working project with too high costs to a functioning project with reasonable costs.

When working with Redis, there are usually several pods processing work tasks, and updating the Redis with the results. There are several gradual steps in the process of a product maturity until it settles on the best method to use the Redis.

The Steps

The first an naive step is to update the Redis for each work task. The work task might be a user action, a web transaction, or a system event, and hence we expect huge amount of work tasks, and the implication is huge amount of Redis updates.

Trying to reduce the Redis updates, usually leads to memory state. We have multiple kubernetes pods as part of the same kubernetes deployment. Each of the pods keeps its own memory state, where all updates are done, and then once in a short period (for example once in 5 seconds), the state is saved back to the Redis. Why is this better? We usually increment the same counters and set value for the save Redis key for each work task. Instead, we can do this in memory, and only update the Redis once, while converting multiple increment operations to a single operation. This reduces the complexity of the Redis updates from O(N) where N in the work tasks number to ~O(time period).

The next step is reducing the updates even more. Part of the in-memory state that we keep in the pods is saved only for cases the the pod is terminated, and we need to reload the state. Do we really need to save it every 5 seconds? How critical would it be if we lose some of the updates? If some updates are not critical, we can save these using higher interval, for example once in 10 minutes. Notice that it is important to use random interval to prevent parallel save of all the pods exactly in the same time, and hence loading the Redis and slowing the system. An example of time interval calculation is:

10 minutes + random(10 minutes)

Final Note

In this post we've reviewed methods to reduce the load on Redis. 
We have reviewed state save methods. The same methods can be used also for load state. 
Using these methods should be the one of the first attempts to solve Redis stress issue, before jumping into conclusion that the Redis cluster should be upgraded to use more CPU and memory resource, and hence reducing costs in an effective way.









Monday, September 25, 2023

Go End The Loop Bug

 


Finally, it is solved!

The Go maintainers have finally took the correct actions to fix the loop variable bug. This bug is very annoying, and I have personally made it several times.

Let's examine a simple example of this bug.


func main() {
var waitGroup sync.WaitGroup
for i := 0; i < 10; i++ {
waitGroup.Add(1)
go func() {
defer waitGroup.Done()
fmt.Printf("%v\n", i)
}()
}
waitGroup.Wait()
}


We would expect this simple loop to print the numbers zero to nine, but actually the result is unexpected:


10
10
10
4
10
10
10
10
10
10


Why? This is due to the fact that the scope of the loop variable `i` is the loop itself, so it is created only once, and referred by the iterations. To avoid this, we used to "clone" the loop variable, for example:


func main() {
var waitGroup sync.WaitGroup
for i := 0; i < 10; i++ {
waitGroup.Add(1)
clonedI := i
go func() {
defer waitGroup.Done()
fmt.Printf("%v\n", clonedI)
}()
}
waitGroup.Wait()
}


and the output:


8
9
2
3
0
5
4
1
7
6


So this works, but you always need to remember this. In the upcoming Go 1.22 version, the scope of the loop variable is changed into the iteration, so there is no longer need of the clone trick. We can even force it into Go version 1.21 using the environment variable: 

GOEXPERIMENT=loopvar


Good move GO! (better late than never)






Sunday, September 17, 2023

Using mirrord JetBrains plugin for development within Kubernetes


 


I this post we will review how to use the mirrord plugin within JetBrains IDE.

Mirrord is a new open source tool enabling developer to run processes on the local development machine as if the process is running on a remote kubernetes cluster. It actually works by connecting to a pod, and capturing network and IO requests on the pod, mirroring these to the local development machine. See the architecture flow from the mirrord sire:



The mirrord plugin for JetBrains IDEs simplifies development to a whole new level. Let have a quick walk-though of the steps to use it. 


For this example, I have a deployment and a service named "guibackend" on the remote kubernetes cluster, and I want to run it locally, but I need to use other services to run it. The redis service is part of the kubernetes cluster, so I cannot access it from my local development machine. Let use mirrord to run it as if it is located on the remote kubernetes cluster.


Install the mirrord plugin


In the GoLand IDE, double click the Shift key, type "Plugins", and open the plugins window:


Click on Marketplace, search for mirrord plugin, and click the Install button:


That's all, mirrord is installed.


Using the mirrord plugin

We now have a new button on the toolbar to enable/disable run and debug through mirrord:



Next we can easily run the service locally on the machine as if it was remotely installed on the kubernetes cluster. When mirrord is enabled, running the program displays a popup to select the target pod/deployment:



Once we select the guibackend deployment, we now run on local development machine, but we get the IO and network of the remote pod. Pretty amazing!

This mirrord can be configured to match our requirement, for example, I want always to select the guibackend deployment without the selection popup, and I want not just to mirror the network requests, but instead I want to steal them, hence I use the following configuration:




For complete configuration options, see here.


Final Note

The mirrord is doing a real great job at simplify development and debugging processes on a kubernetes cluster environment. I find it very easy to use, and I highly recommend using it!



Sunday, September 3, 2023

GOMEMLIMIT - Mandatory To Use

 



In this post we will review the usage of the Go environment variable GOMEMLIMIT, and explain how to use it.


First, let's understand the core behavior of the garbage collection on a Go process runtime. By default, the garbage collection runs whenever the process allocated memory doubles. 

See for example, the following memory chart:




This chart displays 2 event of garbage collection, each occurring once the process memory doubles: 

  • The memory reaches 200M, grabage collection cleaning 20M, and the process memory drops to 180M
  • The memory reaches 360M, grabage collection cleaning 60M, and the process memory drops to 300M

This might look suitable on first sight, but it might cause the process to fail on out of memory error when running on kubernetes.  For example:

  • The process memory limit is 3G RAM
  • The garbage collection last run had finished with 2G RAM
  • The next planned garbage collection run is at 4G RAM
  • The process terminates a 3G RAM with out of memory error.
One way to solve this is to use the GOGC environment variable which can configure when to run the garbage collector. The default is GOGC=100, which mean to run the garbage collector when the memory rises by 100%. We could set it to GOGC=10, but then the garbage collector would run (and delay the process performance) even when the memory consumption is low.

Here is where the GOMEMLIMIT environment variable comes to help. It sets up a soft limit for a process runtime. This means that the garbage collection is run more often when the process memory is above this threshold. Notice that the GOMEMLIMIT does not include some OS related memory, so we would usually set it to a smaller value than the actual memory limit.


How do we actually use this?

We should set the GOMEMLIMIT to a value close the the actual limit of memory by kubernetes. For example, if the kubernetes pod memory limit is 5G, we will set the GOMEMLIMIT to ~4G.






Monday, August 21, 2023

Publish Android Library AAR to Maven Central


 

In this post we will review the steps required to publish an Android library as an artifact in maven central. This is required when we create an Android library that we want our customers to use, without the need to manually download files, and also allows the customers to enjoy the maven dependency management.


The procedure below includes 3 steps:

1. Create a local maven artifact

2. Create a new project in maven central

3. Manually upload the artifact to maven central


Create A Local Maven Artifact

Open the project in Android Studio, make sure the gradle version is at least 7.1.

This is visible through the File menu, Project Structure, Project (on the right bar).



Now, open the build.gradle under the library folder, and add maven-publish plugin right below the existing android library plugin:



Next, in the same file, right after the android root element, add the publishing:



The full text of the publishing element is below. Notice that I've used my-company and my-library, but feel free to replace with the text that describes the relevant company/library. Notice that this company name must be under your ownership at github.




After updating the gradle, we need to rebuild the library.



And finally, run the publish: under the Android Studio gradle tab, select the library project, then under tasks, publishing, select publishReleasePublicationToMavenLocal.



The built maven artifact is now available in ~/.m2/repository/io/github/my-company/my-library


Create a New Project in Maven Central

1. Create new github user by the company name. For example:

https://github.com/my-company/my-library


2. Create new user in Sonatype: https://issues.sonatype.org/secure/Signup!default.jspa


3. Create ticket to create new project: https://issues.sonatype.org/browse/OSSRH-94213

You must prove ownership on the project ID, so either own the DNS or use GitHub and prove ownership on the GitHub user. I have chosen github. Once the ticket is open, follow the instructions in the ticket to prove ownership.


4. Create gpg keys:

gpg --full-gen-key

# select 1

# select 4096

# select 0

# enter name and email

# comment can be empty

gpg --list-keys

gpg --keyserver keyserver.ubuntu.com --send-keys THE_KEY_ID_SHOWN_IN_THE_LIST_KEYS

gpg --export-secret-keys THE_KEY_ID_SHOWN_IN_THE_LIST_KEYS| base64



Manually Upload The Artifact To Maven Central

Basically this steps can be automated, see steps here:

However, I would not recommend doing this automatically, as it is very complicated process, and not expected to be run many times in most scenarios.

The manual steps are listed here:
But we provide details below, so keep on reading below.


1. Run gpg for each file:

cd ~/.m2/repository/io/github/my-compant/my-library/1.0.0/
rm -f my-library-1.0.0.aar.asc
rm -f my-library-1.0.0.pom.asc
rm -f my-library-1.0.0.module.asc
gpg -ab my-library-1.0.0.aar
gpg -ab my-library-1.0.0.pom
gpg -ab my-library-1.0.0.module
rm -f bundle.jar
jar -cvf bundle.jar *


3. Select “Staging Upload” on the left bar




4. Select Artifact bundle

5. Select the file ~/.m2/repository/io/github/my-company/my-library/1.0.0/bundle.jar and upload


6. Select “Staging Repositories” on the left bar



7. Select the uploaded repository, and click Close button on the top



8. In case of failures, the reasons appears on the bottom under the activity tab:



9. Finally, release the library using the release button


Monday, August 14, 2023

Compacting JSON Representation in GoLang



In this post we will review a method to shrink JSON representation of Go structures. This is critical in case we need to save the state by marshaling the state objects and save them in Redis, which works really bad with large bulk of strings.

Let dive in quickly with an example. Let assume our state is represented by a struct, and see the JSON representation of it:


package main

import (
"encoding/json"
"fmt"
"time"
)

type DetectorConfig struct {
AnomalyThreshold float32
AnomalyPattern string
EnableDetection bool
}

type State struct {
Counter int
Stations []string
Detector DetectorConfig
LastUpdateEpoch int64
}

func ProduceDefaultState() *State {
return &State{
Counter: 0,
Stations: []string{"load", "build", "deploy", "test", "deliver"},
Detector: DetectorConfig{
AnomalyThreshold: 5.55,
AnomalyPattern: ".*",
EnableDetection: true,
},
LastUpdateEpoch: time.Now().Unix(),
}
}

func main() {

state := ProduceDefaultState()
bytes, err := json.Marshal(state)
if err != nil {
panic(err)
}

jsonText := string(bytes)
fmt.Printf("JSON length is: %v, JSON text is: %v", len(jsonText), jsonText)
}


And the output is:


JSON length is: 178, JSON text is: {"Counter":0,"Stations":["load","build","deploy","test","deliver"],"Detector":{"AnomalyThreshold":5.55,"AnomalyPattern":".*","EnableDetection":true},"LastUpdateEpoch":1691996599}


How can we compact it?

We could use the `json` annotation to use shorter names for the elements, but then we will not be able to display a clear and user friendly JSON to the system admin. A better method would be to decide upon need whether to use clear and user friendly JSON representation when displaying the state to a human, and whether to use a compact JSON representation when saving the state to a DBMS such as redis.

Here is an example of using the compact form:


package main

import (
"fmt"
jsoniter "github.com/json-iterator/go"
"time"
)

type DetectorConfig struct {
AnomalyThreshold float32 `compact:"a"`
AnomalyPattern string `compact:"b"`
EnableDetection bool `compact:"c"`
}

type State struct {
Counter int `compact:"a"`
Stations []string `compact:"b"`
Detector DetectorConfig `compact:"c"`
LastUpdateEpoch int64 `compact:"d"`
}

func ProduceDefaultState() *State {
return &State{
Counter: 0,
Stations: []string{"load", "build", "deploy", "test", "deliver"},
Detector: DetectorConfig{
AnomalyThreshold: 5.55,
AnomalyPattern: ".*",
EnableDetection: true,
},
LastUpdateEpoch: time.Now().Unix(),
}
}

func main() {
state := ProduceDefaultState()
jsonCompact := jsoniter.Config{TagKey: "compact"}.Froze()
bytes, err := jsonCompact.Marshal(state)
if err != nil {
panic(err)
}

jsonText := string(bytes)
fmt.Printf("Compact JSON length is: %v, compact JSON text is: %v", len(jsonText), jsonText)
}


and the output is:


Compact JSON length is: 102, compact JSON text is: {"a":0,"b":["load","build","deploy","test","deliver"],"c":{"a":5.55,"b":".*","c":true},"d":1691996466}


But can we do better?

What if out state is mostly static, and only a few fields are changing? Then we can list only the fields that change, and merge them in to the default config.


package main

import (
"encoding/json"
"fmt"
"radware.com/proximity/commons/global/reflectionapi"
"time"
)

type DetectorConfig struct {
AnomalyThreshold float32 `compact:"a"`
AnomalyPattern string `compact:"b"`
EnableDetection bool `compact:"c"`
}

type State struct {
Counter int `compact:"a"`
Stations []string `compact:"b"`
Detector DetectorConfig `compact:"c"`
LastUpdateEpoch int64 `compact:"d"`
}

func ProduceDefaultState() *State {
return &State{
Counter: 0,
Stations: []string{"load", "build", "deploy", "test", "deliver"},
Detector: DetectorConfig{
AnomalyThreshold: 5.55,
AnomalyPattern: ".*",
EnableDetection: true,
},
LastUpdateEpoch: time.Now().Unix(),
}
}

func main() {
state := ProduceDefaultState()
state.LastUpdateEpoch = time.Now().Add(time.Second).Unix()
state.Detector.EnableDetection = false

defaultState := ProduceDefaultState()
diffMap := reflectionapi.CreateDiffMap("compact", defaultState, state)
bytes, err := json.Marshal(diffMap)
if err != nil {
panic(err)
}

jsonText := string(bytes)
fmt.Printf("Diff JSON length is: %v, diff JSON text is: %v", len(jsonText), jsonText)
}


And the output is:

Diff JSON length is: 32, diff JSON text is: {"c":{"c":false},"d":1691996360}


Of course the length had significantly reduced, and will be reduced much further the bigger is our state, and the less updated fields it includes.


The reflection library is below:


package reflectionapi

import (
"fmt"
"reflect"
"strings"
)

type DiffHandler func(
elementPath string,
value1 interface{},
value2 interface{},
)

func FindDiff(
tagForName string,
item1 interface{},
item2 interface{},
handler DiffHandler,
) {
findDiffRecursive(
tagForName,
"",
item1,
item2,
handler,
)
}

func findDiffRecursive(
tagForName string,
prefix string,
item1 interface{},
item2 interface{},
handler DiffHandler,
) {
reflectType := reflect.TypeOf(item2).Elem()
reflectValue1 := reflect.ValueOf(item1).Elem()
reflectValue2 := reflect.ValueOf(item2).Elem()

for i := 0; i < reflectType.NumField(); i++ {
fieldType := reflectType.Field(i)
useName := fieldType.Name
if tagForName != "" {
useName = fieldType.Tag.Get(tagForName)
}

value1 := reflectValue1.Field(i).Interface()
value2 := reflectValue2.Field(i).Interface()

path := prefix + "/" + useName
switch reflectValue2.Field(i).Kind() {
case reflect.Struct:
interface1 := reflectValue1.Field(i).Addr().Interface()
interface2 := reflectValue2.Field(i).Addr().Interface()
findDiffRecursive(tagForName, path, interface1, interface2, handler)
break
case reflect.Slice:
value1String := fmt.Sprintf("%v", value1)
value2String := fmt.Sprintf("%v", value2)
if value1String != value2String {
handler(path, value1, value2)
}
break
default:
if value2 != value1 {
handler(path, value1, value2)
}
break
}
}
}

func CreateDiffMap(
tagForName string,
itemBaseline interface{},
itemChanged interface{},
) map[string]interface{} {
diffMap := make(map[string]interface{})

handler := func(elementPath string, valueBaseline interface{}, valueChanged interface{}) {
diffMapEntry := diffMap

sections := strings.Split(elementPath, "/")
sections = sections[1:]

for {
sectionName := sections[0]
if len(sections) == 1 {
diffMapEntry[sectionName] = valueChanged
break
} else {
sections = sections[1:]
nextEntry := diffMapEntry[sectionName]
if nextEntry == nil {
nextEntry = make(map[string]interface{})
diffMapEntry[sectionName] = nextEntry
}
diffMapEntry = nextEntry.(map[string]interface{})
}
}
}

FindDiff(tagForName, itemBaseline, itemChanged, handler)

return diffMap
}













Monday, August 7, 2023

Simplifying creation of Go applications on Google Cloud - Post Review


TL;DR

Google seems to stop having new ideas, so it just publishes nonsense as if it was news


Once in a while I read the blogs for some frameworks such as GCP, AWS, and K8s.

Some of the updates are marketing posts, but among them we can find some interesting news. Lat week, checkin gthe GCP blog, I've found the post Simplifying creation of Go applications on Google Cloud. This post seems promising, as a framework for Go applications is something that takes time to build, and I thought I might find interesting ideas there.

The post includes 4 templates:


"

  • httpfn: A basic HTTP handler (Cloud Function)

  • pubsubfn: A function that is subscribed to a PubSub topic handling a Cloud Event (Cloud Function)

  • microservice: An HTTP server that can can be deployed to a serverless runtime (Cloud Run)

  • taskhandler: An basic app that handles tasks from requests (App Engine)

"


So I've checked the templates, and was very disappointed. Most of the templates include less than 10 lines of banal code. It seems that someone in google thought that adding sample code that does almost nothing is good enough to be published in the GCP blog.


The question asked here is: what are the expectations from such a post?


The answer is framework and standards!


Instead of a naive example for HTTP server handler function, add a framework to wrap the HTTP handler, add error handling and logging as part of the HTTP wrapper, add some built-in handlers to the HTTP server, such as pprof profiling capabilities, and setup standards for code design and style.

Most of the projects I've been part of have a major part of the code in the "common" libraries. These common libraries provide a real template for new applications, making them more robust, simple to create, and set code and design standards to the application using the libraries.

A partial list of such libraries is:

  • Logging, log level, logger handler
  • Error handling (panic, recover)
  • HTTP web server wrapper
  • Scheduler wrapper
  • Extend core functionality for: io, slices, strings, parallelism, time
  • Testing wrapper

While adding usage such set of libraries to existing application is almost impossible, this can set a standard to new applications, and this is what I expect from Google - to setup standards. I hope next post would be more in this direction...