Full Blog TOC

Full Blog Table Of Content with Keywords Available HERE

Wednesday, June 17, 2020

Deploy Apache Zookeeper on Kubernetes



In this post we'll review how to deploy Apache ZooKeeper on kubernetes.

Zookeeper manifest is:

"Apache ZooKeeper is an effort to develop and maintain an open-source server which enables highly reliable distributed coordination."

It can be used for cluster coordination actions. For example, it used by kafka for the cluster's brokers election.

To deploy ZooKeeper on kubernetes, we need the following:
  • A ConfigMap with its related configuration files
  • An exposed service enabling clients to access the ZooKeeper
  • A headless service enabling ZooKeeper instances coordination
  • A StatefulSet to run the ZooKeeper instances
  • An init container to update the ZooKeeper instances configuration
  • An updated ZooKeeper container to run the ZooKeeper instances


You might also find the post Deploy Apache Kafka on Kubernetes relevant.


The ConfigMap


The ConfigMap holds two files:
  • The logger configuration file
  • The ZooKeeper configuration file

Notice that the ZooKeeper configuration file is a template, that will be later updated by the init container to include the list of the ZooKeeper instances.


apiVersion: v1
kind: ConfigMap
metadata:
name: zookeeper-config
data:
log4j.properties: |-
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[%d] %p %m (%c)%n

# Suppress connection log messages, three lines per livenessProbe execution
log4j.logger.org.apache.zookeeper.server.NIOServerCnxnFactory=WARN
log4j.logger.org.apache.zookeeper.server.NIOServerCnxn=WARN
zookeeper.properties: |-
4lw.commands.whitelist=*
tickTime=2000
dataDir=/var/lib/zookeeper/data
dataLogDir=/var/lib/zookeeper/log
clientPort=2181
maxClientCnxns=2
initLimit=5
syncLimit=2


The Exposed Service


The exposed service is the service used by the ZooKeeper clients.


apiVersion: v1
kind: Service
metadata:
name: zookeeper-service
spec:
selector:
configid: zookeeper-container
ports:
- port: 80
targetPort: 2181



The Headless Service


The headless service is used only by the ZooKeeper instances, and is not used by the ZooKeeper clients. Its purpose is coordination between the ZooKeeper cluster instances.


apiVersion: v1
kind: Service
metadata:
name: zookeeper-internal-service
spec:
selector:
configid: zookeeper-container
type: ClusterIP
clusterIP: None
publishNotReadyAddresses: true
ports:
- port: 2888
name: peer
- port: 3888
name: election


The StatefulSet


The StatefulSet creates the actual instances of the ZooKeeper cluster.
It contains an init container which updates the ZooKeeper instances configuration, and the actual ZooKeeper container.


apiVersion: apps/v1
kind: StatefulSet
metadata:
name: zookeeper-statefulset
spec:
serviceName: zookeeper-internal-service
selector:
matchLabels:
configid: zookeeper-container
replicas:
podManagementPolicy: Parallel
template:
metadata:
labels:
configid: zookeeper-container
spec:
terminationGracePeriodSeconds: 10
initContainers:
- name: init
image: my-registry/zookeeper-init:latest
env:
- name: ZOO_REPLICAS
value: "3"
volumeMounts:
- name: configmap
mountPath: /etc/kafka-configmap
- name: config
mountPath: /etc/kafka
- name: data
mountPath: /var/lib/zookeeper
containers:
- name: zookeeper
image: my-registry/zookeeper:latest
env:
- name: KAFKA_LOG4J_OPTS
value: -Dlog4j.configuration=file:/etc/kafka/log4j.properties
command:
- ./bin/zookeeper-server-start.sh
- /etc/kafka/zookeeper.properties
lifecycle:
preStop:
exec:
command:
- "/bin/bash"
- "/pre_stop.sh"
readinessProbe:
exec:
command:
- "/bin/bash"
- "/readiness_probe.sh"
volumeMounts:
- name: config
mountPath: /etc/kafka
- name: data
mountPath: /var/lib/zookeeper
volumes:
- name: configmap
configMap:
name: zookeeper-config
- name: config
emptyDir: {}
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "hostpath"
resources:
requests:
storage: 500Mi


The init container


The init container purpose is to update the ZooKeeper instances in the ZooKeeper configuration file.

It is based on the following Dockerfile:


FROM ubuntu:18.04
COPY files /
ENTRYPOINT /entrypoint.sh


And on the following script, which adds the list of the ZooKeeper pods to the configuration file.


#!/bin/bash
set -e

[[ -d /var/lib/zookeeper/data ]] || mkdir /var/lib/zookeeper/data
export ZOOKEEPER_SERVER_ID=${HOSTNAME##*-}
echo "my server id is ${ZOOKEEPER_SERVER_ID}"
echo "${ZOOKEEPER_SERVER_ID}" > /var/lib/zookeeper/data/myid

cp -Lur /etc/kafka-configmap/* /etc/kafka/
sed -i "/^server\\./d" /etc/kafka/zookeeper.properties

# ensure new line in file
echo "" >> /etc/kafka/zookeeper.properties

for N in $(seq ${ZOO_REPLICAS})
do
index=$(( $N - 1 ))
serverName="zookeeper-statefulset-${index}.zookeeper-internal-service"
echo "server.${index}=${serverName}:2888:3888:participant" >> /etc/kafka/zookeeper.properties
done

sed -i "s/server\.$ZOOKEEPER_SERVER_ID\=[a-z0-9.-]*/server.$ZOOKEEPER_SERVER_ID=0.0.0.0/" /etc/kafka/zookeeper.properties




The updated ZooKeeper container


The updated ZooKeeper container is created using the following Dockerfile:

FROM solsson/kafka:2.4.1@sha256:79761e15919b4fe9857ec00313c9df799918ad0340b684c0163ab7035907bb5a
RUN apt update
RUN apt install -y net-tools
RUN apt install -y curl

COPY files /
ENTRYPOINT /entrypoint.sh


And includes a cleanup script: pre_stop.sh


#!/bin/bash
kill -s TERM 1

while $(kill -0 1 2>/dev/null)
do
sleep 1
done



And also includes a readiness probe script: readiness_probe.sh


#!/bin/bash
set -e
response=$(echo ruok | nc -w 1 -q 1 127.0.0.1 2181)

if [[ "$response" == "imok" ]]
then
exit 0
fi

exit 1


Final Notes

In this post we have reviewed deploying a ZooKeeper cluster on kubernetes.
In case setting the ZOO_REPLICAS environment variable to "1", the ZooKeeper will run in a standalone mode.
In case setting the ZOO_REPLICAS environment variable to "3" or more, the ZooKeeper will run in a cluster mode.


Report a Grafana HeatMap graph from a GO application




In the previous post Report prometheus metrics from a GO application, we've created a simple counters report from a GO application.
In this post we will review an HeapMap reporting from a GO application.

The thing to notice is that Prometheus standard for a pre-bucket counters is that each bucket contains all the smaller buckets, while Grafana expect each bucket to include only the bucket range.

For example, assume we have the following statistics of response time per request:
  • 10 requests had a response time of 100 ms
  • 10 requests had a response time of 200 ms
  • 10 requests had a response time of 300 ms
  • 10 requests had a response time of 400 ms

Prometheus pre-buckets counters will be:
  • response_time{le="100"} 10
  • response_time{le="200"} 20
  • response_time{le="300"} 30
  • response_time{le="400"} 40
While Grafana expects:
  • response_time{le="100"} 10
  • response_time{le="200"} 10
  • response_time{le="300"} 10
  • response_time{le="400"} 10

To solve this, we implement the non cumulative buckets on our own.
First, we configure an array of 10 buckets.
Each bucket width is 500ms, so we actually represent a heatmap of 0-5000ms, using steps of 500ms.


func main() {
heatmap = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "response_time",
Help: "response time for each HTTP handler",
},
[]string{"le"},
)

buckets = make([]int64, 10)
var total int64
for i := 0; i < len(buckets); i++ {
total += 500
buckets[i] = total
}

http.Handle("/metrics", promhttp.Handler())
addHandlerFunc("/foo", fooHandler)
addHandlerFunc("/bar", barHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}


Next we do the actual assignment of the request to the related bucket according to the response time:


func addHandlerFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) {
http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
startTime:= time.Now()
handler(w, r)
labels := make(map[string]string)
labels["handler"] = pattern
passedTime:= time.Since(startTime)
labels["le"] = getBucket(passedTime)
heatmap.With(labels).Inc()
})
}



and the actual bucket locate function is:


func getBucket(passedTime time.Duration) string {
millis := passedTime.Microseconds()
for i := 0; i < len(buckets); i++ {
if millis <= buckets[i] {
return strconv.FormatInt(buckets[i], 10)
}
}
return "INF"
}


And so, we get a nice heatmap in Grafana, indicating the what is the histogram of the response time:





Final Notes


In this post we have reviewed bypassing a compatibility issue of Prometheus and Grafana by using our own buckets implementation.

Maybe in one of the next versions this issue will be solve by one of the Prometheus and Grafana parties.

Wednesday, June 10, 2020

Report prometheus metrics from a GO application




In this post we'll review adding a simple counter metric to a GO application.
See also the related posts:

Let's assume we have a GO application providing two services over HTTP: /foo and /bar.
For this example, the services implementation is sleep for a random time, and return a string in the response.


package main

import (
"log"
"math/rand"
"net/http"
"time"
)

func main() {
http.HandleFunc("/foo", fooHandler)
http.HandleFunc("/bar", barHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}

func fooHandler(w http.ResponseWriter, _ *http.Request) {
randomTime := time.Duration(int(rand.Float32() * 1000))
time.Sleep(time.Millisecond * randomTime)
_, _ = w.Write([]byte("foo is done"))
}
func barHandler(w http.ResponseWriter, _ *http.Request) {
randomTime := time.Duration(int(rand.Float32() * 1000))
time.Sleep(time.Millisecond * randomTime)
_, _ = w.Write([]byte("bar is done"))
}


First we want to integrate the /metrics URL with the prometheus handler.
We add the following code:


package main

import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
)


func main() {
http.Handle("/metrics", promhttp.Handler())


Once this code is added, we immediately get some defaults GO related counters in the /metrics URL call:

$ curl localhost:8080/metrics

# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 0
go_gc_duration_seconds{quantile="0.25"} 0
go_gc_duration_seconds{quantile="0.5"} 0
go_gc_duration_seconds{quantile="0.75"} 0
go_gc_duration_seconds{quantile="1"} 0
go_gc_duration_seconds_sum 0
go_gc_duration_seconds_count 0
# HELP go_goroutines Number of goroutines that currently exist.
# TYPE go_goroutines gauge
go_goroutines 7
# HELP go_info Information about the Go environment.
# TYPE go_info gauge
go_info{version="go1.14.2"} 1
# HELP go_memstats_alloc_bytes Number of bytes allocated and still in use.
# TYPE go_memstats_alloc_bytes gauge
go_memstats_alloc_bytes 639848
# HELP go_memstats_alloc_bytes_total Total number of bytes allocated, even if freed.
# TYPE go_memstats_alloc_bytes_total counter
go_memstats_alloc_bytes_total 639848
# HELP go_memstats_buck_hash_sys_bytes Number of bytes used by the profiling bucket hash table.
# TYPE go_memstats_buck_hash_sys_bytes gauge
go_memstats_buck_hash_sys_bytes 3836
# HELP go_memstats_frees_total Total number of frees.
# TYPE go_memstats_frees_total counter
go_memstats_frees_total 112
# HELP go_memstats_gc_cpu_fraction The fraction of this program's available CPU time used by the GC since the program started.
# TYPE go_memstats_gc_cpu_fraction gauge
go_memstats_gc_cpu_fraction 0
# HELP go_memstats_gc_sys_bytes Number of bytes used for garbage collection system metadata.
# TYPE go_memstats_gc_sys_bytes gauge
go_memstats_gc_sys_bytes 3.436808e+06
# HELP go_memstats_heap_alloc_bytes Number of heap bytes allocated and still in use.
# TYPE go_memstats_heap_alloc_bytes gauge
go_memstats_heap_alloc_bytes 639848
# HELP go_memstats_heap_idle_bytes Number of heap bytes waiting to be used.
# TYPE go_memstats_heap_idle_bytes gauge
go_memstats_heap_idle_bytes 6.5093632e+07
# HELP go_memstats_heap_inuse_bytes Number of heap bytes that are in use.
# TYPE go_memstats_heap_inuse_bytes gauge
go_memstats_heap_inuse_bytes 1.589248e+06
# HELP go_memstats_heap_objects Number of allocated objects.
# TYPE go_memstats_heap_objects gauge
go_memstats_heap_objects 2260
# HELP go_memstats_heap_released_bytes Number of heap bytes released to OS.
# TYPE go_memstats_heap_released_bytes gauge
go_memstats_heap_released_bytes 6.5093632e+07
# HELP go_memstats_heap_sys_bytes Number of heap bytes obtained from system.
# TYPE go_memstats_heap_sys_bytes gauge
go_memstats_heap_sys_bytes 6.668288e+07
# HELP go_memstats_last_gc_time_seconds Number of seconds since 1970 of last garbage collection.
# TYPE go_memstats_last_gc_time_seconds gauge
go_memstats_last_gc_time_seconds 0
# HELP go_memstats_lookups_total Total number of pointer lookups.
# TYPE go_memstats_lookups_total counter
go_memstats_lookups_total 0
# HELP go_memstats_mallocs_total Total number of mallocs.
# TYPE go_memstats_mallocs_total counter
go_memstats_mallocs_total 2372
# HELP go_memstats_mcache_inuse_bytes Number of bytes in use by mcache structures.
# TYPE go_memstats_mcache_inuse_bytes gauge
go_memstats_mcache_inuse_bytes 13888
# HELP go_memstats_mcache_sys_bytes Number of bytes used for mcache structures obtained from system.
# TYPE go_memstats_mcache_sys_bytes gauge
go_memstats_mcache_sys_bytes 16384
# HELP go_memstats_mspan_inuse_bytes Number of bytes in use by mspan structures.
# TYPE go_memstats_mspan_inuse_bytes gauge
go_memstats_mspan_inuse_bytes 37400
# HELP go_memstats_mspan_sys_bytes Number of bytes used for mspan structures obtained from system.
# TYPE go_memstats_mspan_sys_bytes gauge
go_memstats_mspan_sys_bytes 49152
# HELP go_memstats_next_gc_bytes Number of heap bytes when next garbage collection will take place.
# TYPE go_memstats_next_gc_bytes gauge
go_memstats_next_gc_bytes 4.473924e+06
# HELP go_memstats_other_sys_bytes Number of bytes used for other system allocations.
# TYPE go_memstats_other_sys_bytes gauge
go_memstats_other_sys_bytes 1.034244e+06
# HELP go_memstats_stack_inuse_bytes Number of bytes in use by the stack allocator.
# TYPE go_memstats_stack_inuse_bytes gauge
go_memstats_stack_inuse_bytes 425984
# HELP go_memstats_stack_sys_bytes Number of bytes obtained from system for stack allocator.
# TYPE go_memstats_stack_sys_bytes gauge
go_memstats_stack_sys_bytes 425984
# HELP go_memstats_sys_bytes Number of bytes obtained from system.
# TYPE go_memstats_sys_bytes gauge
go_memstats_sys_bytes 7.1649288e+07
# HELP go_threads Number of OS threads created.
# TYPE go_threads gauge
go_threads 7
# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds.
# TYPE process_cpu_seconds_total counter
process_cpu_seconds_total 0.42
# HELP process_max_fds Maximum number of open file descriptors.
# TYPE process_max_fds gauge
process_max_fds 1.048576e+06
# HELP process_open_fds Number of open file descriptors.
# TYPE process_open_fds gauge
process_open_fds 9
# HELP process_resident_memory_bytes Resident memory size in bytes.
# TYPE process_resident_memory_bytes gauge
process_resident_memory_bytes 8.392704e+06
# HELP process_start_time_seconds Start time of the process since unix epoch in seconds.
# TYPE process_start_time_seconds gauge
process_start_time_seconds 1.59184909075e+09
# HELP process_virtual_memory_bytes Virtual memory size in bytes.
# TYPE process_virtual_memory_bytes gauge
process_virtual_memory_bytes 1.112522752e+09
# HELP process_virtual_memory_max_bytes Maximum amount of virtual memory available in bytes.
# TYPE process_virtual_memory_max_bytes gauge
process_virtual_memory_max_bytes -1
# HELP promhttp_metric_handler_requests_in_flight Current number of scrapes being served.
# TYPE promhttp_metric_handler_requests_in_flight gauge
promhttp_metric_handler_requests_in_flight 1
# HELP promhttp_metric_handler_requests_total Total number of scrapes by HTTP status code.
# TYPE promhttp_metric_handler_requests_total counter
promhttp_metric_handler_requests_total{code="200"} 0
promhttp_metric_handler_requests_total{code="500"} 0
promhttp_metric_handler_requests_total{code="503"} 0


Next, we want to add our own metrics. 
Let's add counters for the amount of each service invocation.
To implement this, we add a vector of counters containing an entry per each handler, and we wrap the handler execution with a counter update code.


package main

import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
"log"
"math/rand"
"net/http"
"time"
)

var counters *prometheus.CounterVec

func main() {
counters = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "invocations",
Help: "counters for each HTTP service",
},
[]string{"handler"},
)

http.Handle("/metrics", promhttp.Handler())
addHandlerFunc("/foo", fooHandler)
addHandlerFunc("/bar", barHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}

func addHandlerFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) {
http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
labels := make(map[string]string)
labels["handler"] = pattern
counters.With(labels).Inc()

handler(w, r)
})
}


Now we get the custom counters in the /metrics HTTP response:


$ curl localhost:8080/metrics | grep invocations

# HELP invocations counters for each HTTP service
# TYPE invocations counter
invocations{handler="/bar"} 2
invocations{handler="/foo"} 1


Great!
Our application is now reporting the custom metrics.
Let run it in a kubernetes, that already has a prometheus installed.
We need to specify the prometheus annotations, to indicate that we want prometheus to scrape the pod:
  1. prometheus.io/scrape: "true"
  2. prometheus.io/path: "/metrics"
  3. prometheus.io/port: "8080"

We also configure the deployment to run 2 replicas of our application.


apiVersion: apps/v1
kind: Deployment
metadata:
name: prom-deployment
spec:
replicas: 2
selector:
matchLabels:
configid: prom-container
template:
metadata:
labels:
configid: prom-container
annotations:
prometheus.io/scrape: "true"
prometheus.io/path: "/metrics"
prometheus.io/port: "8080"
spec:
containers:
- name: prom
image: myregistry:5000/prom/dev:latest
imagePullPolicy: IfNotPresent


Let's view the counters in prometheus GUI.
We search metric by the name invocations, and we find 4 entries, as we have 2 replicas, each running 2 handlers.






Using the metrics collected by the prometheus, we can easily add grafana graphs to view the statistics overtime.

For example, we can view the counters by pod:





and we can view the counters by handler:




Final Notes

In this post we have reviewed how to create an HTTP service based on a GO application, and report metric for prometheus.
We have also see how can we view the reported metrics in prometheus, and create graphs in grafana.


Wednesday, June 3, 2020

Java LinkedList vs ArrayList


Last week I have interviewed a Senior Java Developer, having more than 10 years of experience in the Java domain.
I wanted to ask some "warm-up" questions to break the ice in the beginning of the interview, so I've asked about the differences between LinkedList and ArrayList. 
To my surprise, he had failed providing answers.
If you claim to be a Java expert, you must know the answer to this question.


What is a LinkedList?


A LinkedList, as its name implies, is a list based on list of elements.
Java keep a doubly linked list, which means each element keeps a pointer to its previous element, and its next element.




The upside of using a LinkedList is that we can, with relatively low cost, update it.

For example, adding an element in the middle of the list is simple:
  • Create a new node, and update its pointer to the next element.
  • Update the previous element pointer to point the the new node.

There are several downsides for using a LinkedList.

First, the memory footprint is higher, as we need to keep two pointers per each list element (for the previous element, and for the next element). 
Each pointer required additional 4 bytes or 8 bytes on 32 bits JVM or 64 bits JVM.
So in most cases this means 2 X 8 bytes = 16 additional bytes per each element.

Notice that each element is actually a java object:  LinkedList.Node
This means also overhead on the garbage collector per the size of the list.


Second, to get the Nth element, we need to scan all elements until the Nth position. 
This means that data access to the Nth element has cost of O(N).


What is an ArrayList?


An ArrayList is a list that internally uses a dynamic sized array to store the elements.
The dynamic array size is automatically increased when its capacity is reached.
The related size increase is based on this formula (simplified version, see the actual JRE source code for full code):

int newCapacity = oldCapacity + (oldCapacity >> 1);

This means we'll expand the size by 50% upon capacity reach.




There are several upsides for using an ArrayList.

First is the memory usage, which is almost identical to the actual elements size.
Notice that we actually keep an array of pointers, so we do have a pointer to keep, but as we must keep a pointer to access an object, we can ignore this overhead.

Second is the access to the Nth element, which is using a direct access by:
  array start location + N*pointer size

The downside of ArrayList is updates.
To add or remove element from an ArrayList, we need to shift all elements after the added elements.

In addition, add of a new element, would sometime cause inflation of the array, which means, allocation of a new array, and copy of the elements, but in average, over many add operations, this is still O(1).


Summary


We should use ArrayList when:
  • We have a lot of elements, and we want to reduce memory footprint
  • We do not update the list, but only adding elements at the end of the list
We should use LinkedList when:
  • We do not have many elements
  • We update the list a lot


  LinkedList  ArrayList 
 add O(1) O(1) on average
 remove and add in specific index O(1) O(N)
 get specific index O(N) O(1)


Some Performance Tests


I wanted to verify some of the statement made before, and I've run the following test code:


package com.alon.listing;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

public class ListPerf {
private static final int MAX_SIZE = 1_000_000;
private final List<Integer> list;

public ListPerf(List<Integer> list) {
this.list = list;
}

public void stressTests() {
addTests();
fetchTests();
deleteTests();
deleteFirstTests();
}

private void timerRun(int times, String description, Runnable runnable) {
long start = System.currentTimeMillis();
for (int i = 0; i < times; i++) {
runnable.run();
}
float singleTime = System.currentTimeMillis() - start;
singleTime = singleTime * 1000 / times;
System.out.println(description + " timing " + String.format("%.3fms", singleTime));
}

private void addTests() {
Random random = new Random();
timerRun(1_000_000, "add", () -> {
list.add(random.nextInt());
});
}

private void fetchTests() {
Random random = new Random();
timerRun(1000, "fetch", () -> {
list.get(random.nextInt(list.size()));
});
}

private void deleteTests() {
Random random = new Random();
timerRun(1000, "delete", () -> {
list.remove(random.nextInt(list.size()));
});
}

private void deleteFirstTests() {
timerRun(1000, "delete first", () -> {
list.remove(0);
});
}

public static void main(String[] args) {
System.out.println("=== LinkedList tests ===");
new ListPerf(new LinkedList<>()).stressTests();

System.out.println("=== ArrayList tests ===");
new ListPerf(new ArrayList<>()).stressTests();
}
}


and get the following results:


=== LinkedList tests ===
add timing 0.173ms
fetch timing 1534.000ms
delete timing 1317.000ms
delete first timing 0.000ms
=== ArrayList tests ===
add timing 0.060ms
fetch timing 0.000ms
delete timing 133.000ms
delete first timing 261.000ms


Notice that the delete test has better results on the ArrayList, which is opposite than the expected result.
The reason is that removal of an Nth element requires first to locate the element, hence the cost is higher on the LinkedList.

When removing the first element, there is no need to scan the list, and hence the LinkedList performance is much better.





Wednesday, May 27, 2020

Create a Java gRPC client




This post presents the steps required to create a java gRPC client.

The gRPC is a great library/protocol providing inter-microservices communication, with high performance, and multiple programming language support. The definition in the official gRPC site is:


"
RPC is a modern open source high performance RPC framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication. 
"


Let's jump directly into the files required to implement the java gRPC client.

The .proto File

The proto file describe the structures used for the communication, as well as the services.


syntax = "proto3";

package api;
option java_package = "org.alon.grpc.generated";


service MyServer {
rpc UpdateStore(Request) returns (Response) {}
}

enum Action {
ADD = 0;
REDUCE = 1;
}

message Request {
string name = 1;
uint64 update = 2;
Action action = 3;
}

message Response{
uint64 price = 1;
}

We have configured the structure of the Request and the structure of the Response.
In addition, we have configured the service API: UpdateStore.


The Maven pom file

The pom.xml is based on a standard java application pom file, and includes the following:
  • protoc and gRPC related dependencies
  • a protobuf-maven-plugin to generate the protoc and gRPC APIs. The following files are generated, and automatically added to the project sources:
    • org.alon.grpc.generated.Api - the request builder class
    • org.alon.grpc.generated.MyServerGrpc - the server communication API


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.alon</groupId>
<artifactId>grpc</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>1.9</maven.compiler.source>
<maven.compiler.target>1.9</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.22.1</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.22.1</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.22.1</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>

<build>
<extensions>
<!--
generates various useful platform-dependent project properties normalized from ${os.name} and ${os.arch}
This is required for running the protobuf plugin
-->
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.5.0.Final</version>
</extension>
</extensions>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>org.alon.grpc.GrpcClient</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id> <!-- this is used for inheritance merges -->
<phase>package</phase> <!-- bind to the packaging phase -->
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<!--
copy the proto files to the local folder.
For actual user: You can manually copy the proto files to the folder: target/proto
and the comment this plugin.
-->
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>copy-protoc-files</id>
<phase>generate-sources</phase>
<configuration>
<tasks>
<copy file="src/main/proto/api.proto"
tofile="target/proto/api.proto"
overwrite="true"
/>
</tasks>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
<!--
run the protoc to generate Java source from the proto files
-->
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.22.1:exe:${os.detected.classifier}</pluginArtifact>
<protoSourceRoot>target/proto</protoSourceRoot>
</configuration>
<executions>
<execution>
<phase>generate-sources</phase>
<id>run-protoc</id>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>


</project>


The Java Source


The last piece is the java source that we create to use the generated gRPC source.


package orig.alon.grpc;

import io.grpc.ManagedChannel;
import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;
import io.grpc.netty.shaded.io.netty.handler.ssl.SslContext;
import org.alon.grpc.generated.Api;
import org.alon.grpc.generated.MyServerGrpc;

import java.io.File;
import java.util.concurrent.TimeUnit;

public class Client {

private final ManagedChannel channel;
private final MyServerGrpc.MyServerBlockingStub serverApi;

public Client(String host, int port, String certificate) throws Exception {
SslContext sslContext = GrpcSslContexts.forClient()
.trustManager(new File(certificate)).build();

channel = NettyChannelBuilder.forAddress(host, port).sslContext(sslContext).build();
serverApi = MyServerGrpc.newBlockingStub(channel);
}

public void shutdown() throws Exception {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}

private long sendRequest(Api.Action action, String name, long price) {
Api.Request request = Api.Request.newBuilder()
.setAction(action)
.setName(name)
.setUpdate(price)
.build();

Api.Response response = serverApi.updateStore(request);
return response.getPrice();
}

public long add(String name, long price) {
return sendRequest(Api.Action.ADD, name, price);
}

public long reduce(String name, long price) {
return sendRequest(Api.Action.REDUCE, name, price);
}

public static void main(String[] args) throws Exception {
System.out.println("Starting");
if (args.length != 3) {
System.err.println("Wrong amount of arguments");
System.err.println("Usage:");
System.err.println("HOST PORT SERVER_CERTIFICATE_FILE_PATH");
return;
}
String host = args[0];
int port = Integer.parseInt(args[1]);
String certificate = args[2];

Client client = new Client(host, port, certificate);
try {
System.out.println(client.add("p1", 1));
System.out.println(client.reduce("p2", 5));
} finally {
client.shutdown();
}
System.out.println("Done");

}
}

Our code receives the host, port, and the certificate file that are used to create connection to the gRPC server. We nicely wrap the gRPC APIs with out own methods, and activate the gRPC server API.


Monday, May 25, 2020

GO Access Control Best Practice




I've been using GO language for some time now, and I've collected some guidelines and code standards to enable code maintainability and flexibility.

As always, guidelines are only guidelines, and can have exceptions on various usages, but still these guidelines usually do save time on later changes to the code, and reduce coupling.


The Guidelines


1. Create new package for each functional struct

What is a "functional struct"?
A functional struct is a structure that has methods attached to it.

For example, in this code section:


package book

import "fmt"

type author struct {
firstName string
lastName string
}

type Book struct {
name string
author author
}

func Create(name string, authorFirstName string, authorLastName string) *Book {
b := Book{
name: name,
author: author{
firstName: authorFirstName,
lastName: authorLastName,
},
}
return &b
}

func (b *Book) Print() {
fmt.Printf("The book name is: %v\n", b.name)
fmt.Printf("Written by: %v %v\n", b.author.lastName, b.author.firstName)
}

  • The Book struct is a functional structure, as it includes a Print method.
  • The author struct is a data storage structure, and it does not include any related methods.

As seen in this example, the Book structure resides in its own package: the book package.
This means that we only expose public elements (functions and structs starting with upper case) to other packages. 

The important idea to understand is that the book package should include ONLY the Book structure related methods and possibly other data storage structures, but nothing else.

This means that we will have many packages in our code, but it significantly reduces the coupling.

The usage of this structure would be as follow:


func main() {
b := book.Create("Alice in Wonderland", "Lewis", "Carroll")
b.Print()
}


2. Use a Create function to construct the structure


As seen in the previous example, the Book structure is created using the Create function.
While this somehow complicates the new structure creation, it allows great flexibility.

For example, let's assume that now we want to create a map of authors by first name. If we would have directly created the structure, we would have to change all of the struct usages to initialize the map, while in our case, we would change it only in a single location:

func Create(name string, authorFirstName string, authorLastName string) *Book {
b := Book{
name: name,
authors: make(map[string]author),
}
b.authors[authorFirstName] = author{
firstName: authorFirstName,
lastName: authorLastName,
}
return &b
}




3. Expose only public entities


This is obvious but should be mentioned.

You package is your fortress. You should open access to the package only where needed.

Hence we will use GO upper case methods, functions, and variables, only where we need.



4. Wire dependencies instead of creating them


Whenever using one function struct on another functional struct, create the structures from out of the function structures scope. 

For example, let's assume we want a printer class for to print the output:


package printer

import (
"fmt"
"io/ioutil"
)

type Printer struct {
outputFile string
printToStdout bool
}

func Create(outputFile string, printToStdout bool) *Printer {
p := Printer{
outputFile: outputFile,
printToStdout: printToStdout,
}
return &p
}

func (p *Printer) Print(format string, a ...interface{}) {
if p.printToStdout {
fmt.Printf(format, a...)
} else {
data := fmt.Sprintf(format, a...)
ioutil.WriteFile(p.outputFile, []byte(data), 0x555)
}
}


So, we create the Printer structure on its own package, as mentioned in guideline #1.
But we do not construct the Printer structure within the Book structure, but only from the outside.
Hence an example of an update usage is:


func main() {
p := printer.Create("", true)
b := book.Create(p, "Alice in Wonderland", "Lewis", "Carroll")
b.Print()
}

and so the updated Print method is using the Printer structure:

func (b *Book) Print() {
b.printer.Print("The book name is: %v\n", b.name)
for _, a := range b.authors {
b.printer.Print("Written by: %v %v\n", a.lastName, a.firstName)
}
}


This allows use to send additional parameters to the Printer structure without modifications of the Book structure.


Final Notes


In this post we have reviewed some coding guidelines for the GO Access Control.
These guidelines might appear some cumbersome at first sight, but in the long distance save a lot of time in bug fixes, and maintainability. 

If you like the ideas presented here, leave a comment!