Skip to content

Commit

Permalink
Merge pull request #8 from rv404674/concurrency_control
Browse files Browse the repository at this point in the history
Concurrency control
  • Loading branch information
rv404674 committed Sep 20, 2020
2 parents ac58aaf + 3837434 commit 6b6b3ef
Show file tree
Hide file tree
Showing 9 changed files with 186 additions and 71 deletions.
88 changes: 88 additions & 0 deletions benchmarking/benchmarking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Benchmarks
NOTE: These load tests are done on a single machine having 8GB Ram, a Dual-Core Intel Core i5 Processor.

## Concurrency Control

Using a global mutex, on request level.
```bash
mutex.lock()
CS
mutex.unlock()
```

### Url Redirection (Load Tested Using Docker Containers)
[Graph - Url Redirection (no of Concurrent Users vs Response Time)](url_redirection_graph.png)

for 10 users, 120s
```bash
k6 run -d 120s -u 10 ./load_test_url_redirection.js
http_req_duration..........: avg=21.98ms min=2.21ms med=17.89ms max=527.23ms p(90)=39.74ms p(95)=50.01ms
http_reqs..................: 53675 447.291485/s
```

for 100 users, 120s
```bash
k6 run -d 120s -u 100 ./load_test_url_redirection.js
http_req_duration..........: avg=161.85ms min=2.81ms med=149.48ms max=956.05ms p(90)=284.61ms p(95)=331.39ms
http_reqs..................: 73807 615.056893/s
```

for 1000 users, 120s
```bash
k6 run -d 120s -u 1000 ./load_test_url_redirection.js
http_req_duration..........: avg=2.39s min=0s med=2.27s max=7.51s p(90)=3.65s p(95)=4.16s
http_reqs..................: 49587 413.224692/s
```

### Url Shortening (Load Tested using Docker Containers)

for 10 users, 120s
```bash
k6 run -d 120s -u 10 ./load_test_shorten_url.js
http_req_duration..........: avg=85.44ms min=7.95ms med=83.36ms max=263.38ms p(90)=102.51ms p(95)=111.68ms
http_reqs..................: 13991 116.591622/s
```

for 100 users, 120s
```bash
k6 run -d 120s -u 100 ./load_test_shorten_url.js
http_req_duration..........: avg=901.78ms min=11.5ms med=848.51ms max=2.21s p(90)=1.07s p(95)=1.41s
http_reqs..................: 13262 110.516569/s
```

for 1000 users, 120s
```bash
k6 run -d 120s -u 1000 ./load_test_shorten_url.js
http_req_duration..........: avg=8.6s min=0s med=8.6s max=19.03s p(90)=10.33s p(95)=10.69s
http_reqs..................: 13345 111.207074/s`
```

## After Using Caching

Comparisons of **Read Latency** between Mongo and after using a Cache (Memcached).

```json
memcached mongodb
1 561.478µs 874.359µs
2 27.991374ms 3.377816ms
3 2.901262ms 4.669834ms
4 2.721016ms 3.289583ms
5 2.120171ms 76.469257ms
Avg 7.258ms 17.57ms
```
> NOTE - Around **200** percent decrease in read latency.

## Using MultiStage Build in Docker

Reduction in Size of Docker image by around **900%**

**Orig**
```bash
gorubu_app latest 9dbe1cf26c39 About an hour ago 1.49GB
```

**After using Multistage build**
```bash
gorubu_docker_final latest 912d533a9a52 4 minutes ago 25.4MB
```
Binary file added benchmarking/url_redirection_graph.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions cache/cacheConnection.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os"
"strconv"
"strings"
"time"

"github.com/bradfitz/gomemcache/memcache"
"github.com/joho/godotenv"
Expand Down Expand Up @@ -33,6 +34,8 @@ func init() {

func tryMemcached(domain string) *memcache.Client {
mc := memcache.New(domain)
mc.MaxIdleConns = 100
mc.Timeout = 1000 * time.Millisecond

inputUrl := "https://stackoverflow.com/questions/58442596/golang-base64-to-hex-conversion"
newUrl := "https://goRubu/MTAyNDE="
Expand Down
2 changes: 1 addition & 1 deletion daos/mainDao.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func InsertInShortenedUrl(urlModel model.UrlModel) {
insertResult, err := collection.InsertOne(context.Background(), urlModel)

if err != nil {
log.Fatal("Error while writing to shortened_url collection", err)
log.Fatal("Error while writing to shortened_url collection ", err)
}

log.Println("InsertedId", insertResult.InsertedID)
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ services:
- type: volume
source: cachedata
target: /data/db
entrypoint: "memcached -vv"
ports:
- '11211:11211'
networks:
Expand Down
28 changes: 0 additions & 28 deletions commands_benchmarks.md → frequent_commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,31 +45,3 @@ docker exec -it container_id /bin/sh
```bash
docker build -t gorubuimage .
```

## Benchmarks

1. Comparisons of **Read Latency** between Mongo and after using a Cache (Memcached).

```json
memcached mongodb
1 561.478µs 874.359µs
2 27.991374ms 3.377816ms
3 2.901262ms 4.669834ms
4 2.721016ms 3.289583ms
5 2.120171ms 76.469257ms

Avg 7.258ms 17.57ms
```
Around **200** percent decrease in read latency.

2. Reduction in Size of Docker image by around **900%**

**Orig**
```bash
gorubu_app latest 9dbe1cf26c39 About an hour ago 1.49GB
```

**After using Multistage build**
```bash
gorubu_docker_final latest 912d533a9a52 4 minutes ago 25.4MB
```
27 changes: 27 additions & 0 deletions loadtesting/load_test_url_redirection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { check } from "k6";
import http from "k6/http";
import { Rate } from 'k6/metrics';


export let errorRate = new Rate('errors');

export default function() {
var url = "http://localhost:8080/all/redirect";
var params = {
headers: {
'Content-Type': 'application/json'
}
};

var data = JSON.stringify({
"Url": "https://goRubu/Nzg0ODA="
});

check(http.post(url, data, params), {
'status is 20': r => r.status == 200
}) || errorRate.add(1);

// check(res, {
// "is status 200": (r) => r.status === 200
// });
}
90 changes: 59 additions & 31 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,66 @@

<img style="float: right;" width="600" src="./assets/goRubu.png">

This repo contains implementation of a Url Shortner written in [Go](https://golang.org/).
This repo contains implementation of a **Url Shortner** written in [Go](https://golang.org/).

[![Build Status](https://travis-ci.com/rv404674/goRubu.svg?branch=master)](https://travis-ci.org/rv404674/goRubu)
[![Coverage Status](https://coveralls.io/repos/github/rv404674/goRubu/badge.svg?branch=master)](https://coveralls.io/github/rv404674/goRubu?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/rv404674/goRubu)](https://goreportcard.com/report/github.com/rv404674/goRubu)
[![Stars](https://img.shields.io/github/stars/rv404674/goRubu)](https://github.com/rv404674/goRubu/stargazers)
[![MIT License](https://img.shields.io/github/license/rv404674/goRubu)](https://github.com/rv404674/goRubu/blob/master/LICENSE)

## Contents

- What is goRubu
- Monitoring
- Why goRubu
- Running Server
- Docker
- Local
- Api's
- Contributing
- Maintainer
- License
- [What is goRubu?](#what-is-gorubu-rocket)
- [Why goRubu?](#why-gorubu-dog)
- [BenchMarking](#benchmarking)
- [Running Server](#running-rerver-gear)
- [Docker](#docker)
- [Local](#local)
- [Api's](#apis-computer)
- [Monitoring](#monitoring-microscope)
- [Contributing](#contributing-beers)
- [Future Todo](#future-todo-notebook)
- [Maintainer](#maintainer-sunglasses)
- [License](#license-scroll)

## What is goRubu? :rocket:

1. A Url Shortner written in **Go**, with Mongo based backend.
1. A Url Shortner written in **Go**, with **MongoDb based** backend.
2. Supports Caching for Hot urls, with Memcached, using a LRU based eviction
strategy, and **write through type** of caching mechanism. Saw **[200%](commands_benchmarks.md)** decrease in Read Latency for URL redirection, after caching.
strategy, and **write through type** of caching mechanism. Saw **[200%](/benchmarking/benchmarking.md)** decrease in Read Latency for URL redirection, after caching.
3. Used Travis CI for adding a **CI/CD** pipeline.
4. Dockerized the whole application. Used **Docker compose** for tying up different containers and **multi-stage build** for reducing the size of docker image by **[900%](commands_benchmarks.md)**.
4. Dockerized the whole application. Used **Docker compose** for tying up different containers and **multi-stage build** for reducing the size of docker image by **[900%](/benchmarking/benchmarking.md)**.
4. **Prometheus and Grafana based monitoring**, to get an overall picture of the
system and application metrics.
5. Contains Api Validation and Logging Middlewares, along with Swagger based documentation

## Monitoring:
## Why goRubu? :dog:

<img style="float: left;" width="600" src="./assets/application_metrics.png">
<p align="left"> Grafana on Top of Prometheus </p>
Wanted to Learn Go and system design, by building a project. Hence goRubu.

<img style="float: left;" width="600" src="./assets/prometheus_targets.png">
<p align="left"> Prometheus </p>
## BenchMarking
> NOTE - Url Shortner is a read heavy system (read:write = 100:1), and these load tests are done on a single Machine (8Gb Ram, i5 Processor).
## Why goRubu? :dog:
Check [this](/benchmarking/benchmarking.md) out for more info.

1. For **Url Redirection**, with **1000 Concurrent Users**, bombarding the app server for **2mins**:
```bash
http_req_duration..........: avg=2.39s min=0s med=2.27s max=7.51s p(90)=3.65s p(95)=4.16s
http_reqs..................: 49587 413.224692/s
```

Wanted to Learn Go and system design by building a project. Hence goRubu.
2. For **Url Shortening**, for same specs
```
http_req_duration..........: avg=8.6s min=0s med=8.6s max=19.03s p(90)=10.33s p(95)=10.69s
http_reqs..................: 13345 111.207074/s
```

## Running Server :gear:

### DOCKER
### Docker

1. You need to have [docker](https://www.docker.com/) and **docker-compose** installed. After that just to
1. You need to have [docker](https://www.docker.com/) and **docker-compose** installed. After that just do
```bash
make docker
```
Expand All @@ -57,7 +70,7 @@ make docker
Check the Api's Section afterwards.

### LOCAL
### Local

### Prerequisites ✅

Expand Down Expand Up @@ -121,9 +134,10 @@ make execute
```
> **Note**: To see what these commands do check out this [makefile](Makefile)
## API :computer:

1. Hit **http://localhost:8080/all/shorten_url** with any url as key.
## Api's :computer:

1. Hit **localhost:8080/all/shorten_url** with any url as key.
```json
{
"Url": "https://www.redditgifts.com/exchanges/manage"
Expand All @@ -139,19 +153,33 @@ The endpoint will return a shortened URL.
}
```

3. Hit **http://localhost:9090/targets** (its where prometheus server will be running).
If everything is working fine, The UI should look something like this
<img style="float: right;" width="500" src="./assets/prometheus_targets.png">
## Monitoring :microscope:

# Contributing :beers:
> Working:
1. Prometheus follows a Pull based Mechanism instead of Push Based.
2. goRubu exposes an HTTP endpoint exposing monitoring metrics.
3. Prometheus then periodically download the metrics. For UI, prometheus is used as a data source for Grafana.

<img style="float: left;" width="600" src="./assets/application_metrics.png">
<p align="left"> Grafana on Top of Prometheus </p>

## Contributing :beers:

Peformance Improvements, bug fixes, better design approaches are welcome. Please Discuss your change by raising an issue, beforehand.

## Future Todo :notebook:

1. Use Promtheus and Grafana as Docker Containers instead of Local Installation.
2. Use Kubernetes instead of Docker Compose.
3. Increase Coverage to more than 90%.
4. Add Monitoring in Prometheus for Number of 2xx, 3xx and 4xx.


# Maintainer :sunglasses:

[Rahul Verma Linkedin](https://www.linkedin.com/in/rahul-verma-8aa59b116/)
[Email](rv404674@gmail.com)

## License
## License :scroll:

[MIT](LICENSE) © Rahul Verma
18 changes: 7 additions & 11 deletions services/mainService.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"os"
"strconv"
"strings"
"sync"
"time"

"github.com/bradfitz/gomemcache/memcache"
Expand All @@ -24,6 +25,7 @@ var err error

// EXPIRY_TIME - TTL for an item int cache
var EXPIRY_TIME int
var mutex sync.Mutex

func init() {
dir, _ := os.Getwd()
Expand All @@ -42,8 +44,11 @@ func init() {

// CreateShortenedUrl - This service shortens a give url.
func CreateShortenedUrl(inputUrl string) string {

mutex.Lock()
counterVal := dao.GetCounterValue()
dao.UpdateCounter()
mutex.Unlock()

newUrl := GenerateShortenedUrl(counterVal)
inputModel := model.UrlModel{UniqueId: counterVal, Url: inputUrl, CreatedAt: time.Now()}

Expand All @@ -58,16 +63,7 @@ func CreateShortenedUrl(inputUrl string) string {
log.Printf("Error in setting memcached value:%v", err)
}

// FIXME:
// Race Condition - Undesirable condition where o/p of a program depends on the seq of execution of go routines

// To prevent this use Mutex - a locking mechanism, to ensure only one Go routine
// is running in the CS at any point of time

// TODO handle Race Conditions. Also use transaction to enable consistency.
// You could have mutexes as well, but mutex would have guaranteed consistency.
dao.InsertInShortenedUrl(inputModel)
dao.UpdateCounter()
return newUrl
}

Expand All @@ -81,7 +77,7 @@ func UrlRedirection(inputUrl string) string {
log.Println("Shortened url found in cache", string(url.Value))
return string(url.Value)
} else if err != memcache.ErrCacheMiss {
log.Fatal("Memcached error ", err)
log.Println("Memcached error ", err)
}

// if its a cache miss, fetch the value from db and update the cache.
Expand Down

0 comments on commit 6b6b3ef

Please sign in to comment.