Tuesday, February 23, 2021

Retry

 The case for Retry in JavaScript errors: 

 

Problem statement: Scripts written for DevOps are susceptible to intermittent failures that can be overcome with retries. For example, a database may not be reachable within the limited timeout that is usually set for scripts. Nested commands can be called that might each take their own timeout. Such errors do not occur under normal conditions, so they escape the functional tests. If a limited number of retries were to be attempted, the code executes successfully, and the issue does not recur. Retry also has the side-effect that it reduces the cost of detection and investigation to other spurious errors from logs, incidents and false defects. How do we handle intermittent failures is the subject of this article. 

 

Solution: The implementation of the retry is independent of the callee. Scripts are not written in programming languages like Java where the callee can be standardized to a Runnable. Scripts also do not mandate a conformance to a convention across their usages. The most common form of a callee is a typical JavaScript function. The invocation of the function could result in a timeout exception, so the retry only works with the use of try-catch handler. 

The catch handler could specifically look for the timeout exceptions and handle it accordingly where the callee is retried. The number of retries for the callee is set to a small and finite number such as ten. 

The retry logic ensures that the result returned from the retry is that of the callee otherwise it propagates unknown exception. The use of retry is only for known exceptions and the unknown exceptions pass through to the caller. Therefore, the behavior is the same as that of the callee. If the results are returned as undefined in error conditions by the callee, it must also be returned as undefined by the retry. 

The Retry script can optionally log the number of attempts to help find the root cause for the intermittent errors and to ensure that they do not go undetected for a long time. Such automatic handling of intermittent failures along with the transparency of what those errors were will likely bring down the overall number of exceptions encountered in a system from operations. This will improve the health of the system and the metrics as well as reports that are used in the monitoring dashboards.  

The retry logic can be made flexible and reusable to include numRetries and delayMillis between the retries on the calleeThese parameters can be used with the initialization of the retry function object or passed in as parameters. 

Since the retry addresses a broad range of callee and their intermittent failures, it could be put in a library with the callee passed as parameter. The reuse of the retry logic brings consistency and possible instrumentation for future investigations and their benefits. 

 

Conclusion: Retry logic is a simple and effective technique to improve the DevOps and bring down the number of errors and exceptions substantially. 

 

// Sample code 

// for retries with exponential backoff 

var RetryUtil = Class.create(); 

RetryUtil.prototype = { 

initialize: function() { 

this.numRetries  = 10; 

this.delayMillis = 0; 

this.toleratedExceptions = []; 

}, 

  

retryOperation: function (fnnumRetriesdelayMillistoleratedExceptions) { 

if (numRetries) { this.numRetries = numRetries; } 

if (delayMillis) {this.delayMillis = delayMillis; } 

if (toleratedExceptions && toleratedExceptions.length == 0) {this.toleratedExceptions = toleratedExceptions;} 

for (var r = 0; r < this.numRetries; r++) { 

try { 

return fn.call(); 

} catch (e) { 

for (var i = 0; i < this.toleratedExceptions.lengthi++) { 

if (e instanceof this.toleratedExceptions[i]) { 

if (this.delayMillis > 0) { 

gs.sleep(delayMillis * Math.pow(2, r)); 

} 

continue; 

} 

} 

throw e; 

} 

} 

}, 

  

type: 'RetryUtil' 

}; 

Monday, February 22, 2021

Analytical applications

 Problem statement: 

Analytical applications that perform queries on data are best visualized via catchy charts and graphs. Most applications using storage and analytical products have a user interface that lets them create containers for their data but have very little leverage for the customer to author streaming queries. Even the option to generate a query is nothing more than the uploading of a program using a compiled language artifact. The application reuses a template to make requests and responses, but this is very different from long running and streaming responses that are typical for these queries. A solution is presented in this document. 


Description 

An application can choose to offer a set of packaged queries available for the user to choose from a dropdown menu while internalizing all upload of corresponding programs, their execution and the return of the results. One of the restrictions that comes with packaged queries exported via REST APIs is their ability to scale since they consume significant resources on the backend and continue to run for a long time. These restrictions cannot be relaxed without some reduction on their resource usage. The API must provide a way for consumers to launch several queries with trackers and they should be completed reliably even if they are done one by one.  This is facilitated with the help of a reference to the query and a progress indicator. The reference is merely an opaque identifier that only the system issues and uses to look up the status. The indicator could be another api that takes the reference and returns the status. It is relatively easy for the system to separate read-only status information from read-write operations so the number of times the status indicator is called has no degradation on the rest of the system. There is a clean separation of the status information part of the system which is usually periodically collected or pushed from the rest of the system. The separation of read-write from read-only also helps with their treatment differently. For example, it is possible to replace the technology for the read-only separately from the technology for read-write. Even the technology for read-only can be swapped from one to another for improvements on this side. 

The design of all REST APIs generally follows a convention. This practice gives well recognized uri qualifier patterns, query parameters and methods. Exceptions, errors and logging are typically done with the best usage of http protocol. The 1.1 version of that protocol is revised so it helps with the  

The SQL query can also be translated from REST APIs with the help of engines like Presto or LogParser. 

Tools like LogParser allow sql queries to be executed over enumerable. SQL has been supporting user defined operators for a while now. Aggregation operations have had the benefit that they can support both batch and streaming mode operations. These operations therefore can operate on large datasets because they view only a portion at a time. The batch operation can be parallelized to several processors. This has generally been the case with Big Data and cluster mode operations. Both batch and stream processing can operate on unstructured data.  

Presto from Facebook is a distributed SQL query engine can operate on streams from various data source supporting adhoc queries in near real-time. 
Just like the standard query operators of .NET the FLink SQL layer is merely a convenience over the table APIs. On the other hand, Presto offers to run over any kind of data source not just Table APIs. 
Although Apache Spark query code and Apache Flink query code look very much similar, the former uses stream processing as a special case of batch processing while the latter does just the reverse. 
Also, Apache Flink provides a SQL abstraction over its Table API 
While Apache Spark provided ". map(...)" and ". reduce(...)" programmability syntax to support batch-oriented processing, Apache Flink provides Table APIs with ".groupby(...)" and ". order(...)" syntax. It provides SQL abstraction and supports steam processing as the norm. 

The APIs vary when the engines change even though they form an interface such that any virtualized query can be executed, and the APIs form their own query processing syntax. They vary because the requests and responses vary, and their size and content vary. Some APIs are better suited for stream processing and others for batch processing. 

 

Sunday, February 21, 2021

Sample code to retrieve scripts from a SQL database for use with a source control software:

package main

import (
        "archive/zip"
        "database/sql"
        "fmt"
        guuid "github.com/google/uuid"
        "io"
        "io/ioutil"
        "log"
        "net/http"
        "os"
        "text/template"

        _ "github.com/go-sql-driver/mysql"
)

type Script struct {
        Id         int
        Name       string
        ScriptText string
        Flag       int
}

func dbConn() (db *sql.DB) {
        dbDriver := "mysql"
        dbUser := "root"
        dbPass := "root"
        dbName := "scripts"
        db, err := sql.Open(dbDriver, dbUser+":"+dbPass+"@/"+dbName)
        if err != nil {
                panic(err.Error())
        }
        return db
}

var tmpl = template.Must(template.ParseGlob("form/*"))

func Index(w http.ResponseWriter, r *http.Request) {
        db := dbConn()
        selDB, err := db.Query("SELECT * FROM script ORDER BY id DESC")
        if err != nil {
                panic(err.Error())
        }
        script := Script{}
        res := []Script{}
        for selDB.Next() {
                var id int
                var name string
                buf := make([]byte, 65535)
                var flag int
                err = selDB.Scan(&id, &name, &buf, &flag)
                if err != nil {
                        panic(err.Error())
                }
                script.Id = id
                script.Name = name
                script.ScriptText = string(buf)
                script.Flag = flag
                log.Println(script)
                res = append(res, script)
        }
        tmpl.ExecuteTemplate(w, "Index", res)
        defer db.Close()
}
func Show(w http.ResponseWriter, r *http.Request) {
        db := dbConn()
        nId := r.URL.Query().Get("id")
        selDB, err := db.Query("SELECT * FROM script WHERE id=?", nId)
        if err != nil {
                panic(err.Error())
        }
        script := Script{}
        for selDB.Next() {
                var id int
                var name string
                buf := make([]byte, 65535)
                var flag int
                err = selDB.Scan(&id, &name, &buf, &flag)
                if err != nil {
                        panic(err.Error())
                }
                script.Id = id
                script.Name = name
                script.ScriptText = string(buf)
                script.Flag = flag
        }
        tmpl.ExecuteTemplate(w, "Show", script)
        defer db.Close()
}

func New(w http.ResponseWriter, r *http.Request) {
        tmpl.ExecuteTemplate(w, "New", nil)
}

func Edit(w http.ResponseWriter, r *http.Request) {
        db := dbConn()
        nId := r.URL.Query().Get("id")
        selDB, err := db.Query("SELECT * FROM script WHERE id=?", nId)
        if err != nil {
                panic(err.Error())
        }
        script := Script{}
        for selDB.Next() {
                var id int
                var name string
                buf := make([]byte, 65535)
                var flag int
                err = selDB.Scan(&id, &name, &buf, &flag)
                if err != nil {
                        panic(err.Error())
                }
                script.Id = id
                script.Name = name
                script.ScriptText = string(buf)
                script.Flag = flag
        }
        tmpl.ExecuteTemplate(w, "Edit", script)
        defer db.Close()
}

func Insert(w http.ResponseWriter, r *http.Request) {
        db := dbConn()
        if r.Method == "POST" {
                id := 0
                name := r.FormValue("name")
                script := r.FormValue("script")
                flag := r.FormValue("flag")
                insForm, err := db.Prepare("INSERT INTO script(id, name, script, flag) VALUES(?,?,?,?)")
                if err != nil {
                        panic(err.Error())
                }
                insForm.Exec(id, name, script, flag)
                log.Println("INSERT: Name: " + name + " | script: " + script)
        }
        defer db.Close()
        http.Redirect(w, r, "/", 301)
}

func Update(w http.ResponseWriter, r *http.Request) {
        db := dbConn()
        if r.Method == "POST" {
                name := r.FormValue("name")
                script := r.FormValue("script")
                id := r.FormValue("uid")
                flag := r.FormValue("flag")
                insForm, err := db.Prepare("UPDATE script SET name=?, script=? , flag=? WHERE id=?")
                if err != nil {
                        panic(err.Error())
                }
                insForm.Exec(name, script, id, flag)
                log.Println("UPDATE: Name: " + name + " | script: " + script)
        }
        defer db.Close()
        http.Redirect(w, r, "/", 301)
}

func Delete(w http.ResponseWriter, r *http.Request) {
        db := dbConn()
        script := r.URL.Query().Get("id")
        delForm, err := db.Prepare("DELETE FROM script WHERE id=?")
        if err != nil {
                panic(err.Error())
        }
        delForm.Exec(script)
        log.Println("DELETE")
        defer db.Close()
        http.Redirect(w, r, "/", 301)
}

func Export(w http.ResponseWriter, r *http.Request) {
        db := dbConn()
        selDB, err := db.Query("SELECT * FROM script ORDER BY id DESC")
        if err != nil {
                panic(err.Error())
        }
        script := Script{}
        res := []Script{}
        files := []string{}
        guid := guuid.NewString()
        for selDB.Next() {
                var id int
                var name string
                buf := make([]byte, 65535)
                var flag int
                err = selDB.Scan(&id, &name, &buf, &flag)
                if err != nil {
                        panic(err.Error())
                }
                script.Id = id
                script.Name = name
                script.ScriptText = string(buf)
                script.Flag = flag
                writeFile(script.ScriptText, guid+"/"+name+".txt")
                files = append(files, guid+"/"+name+".txt")
                log.Println(script)
                res = append(res, script)
        }
        defer db.Close()
        output := guid + ".zip"

        if err := ZipFiles(output, files); err != nil {
                panic(err)
        }
        fmt.Println("Zipped File:", output)
        http.ServeFile(w, r, output)
}

// writes message into a file
func writeFile(msg string, filename string) {
        bytes := []byte(msg)
        ioutil.WriteFile(filename, bytes, 0644)
}

func createFolder(location string) {
        err := os.Mkdir("/Users/temp", 0755)
        if err != nil {
                panic(err.Error())
        }
}

func ZipFiles(filename string, files []string) error {

        newZipFile, err := os.Create(filename)
        if err != nil {
                return err
        }
        defer newZipFile.Close()

        zipWriter := zip.NewWriter(newZipFile)
        defer zipWriter.Close()

        for _, file := range files {
                if err = AddFileToZip(zipWriter, file); err != nil {
                        return err
                }
        }
        return nil
}

func AddFileToZip(zipWriter *zip.Writer, filename string) error {

        fileToZip, err := os.Open(filename)
        if err != nil {
                return err
        }
        defer fileToZip.Close()

        info, err := fileToZip.Stat()
        if err != nil {
                return err
        }

        header, err := zip.FileInfoHeader(info)
        if err != nil {
                return err
        }
        header.Name = filename

        header.Method = zip.Deflate

        writer, err := zipWriter.CreateHeader(header)
        if err != nil {
                return err
        }
        _, err = io.Copy(writer, fileToZip)
        return err
}

func main() {
        log.Println("Server started on: http://localhost:8080")
        http.HandleFunc("/", Index)
        http.HandleFunc("/show", Show)
        http.HandleFunc("/new", New)
        http.HandleFunc("/edit", Edit)
        http.HandleFunc("/insert", Insert)
        http.HandleFunc("/update", Update)
        http.HandleFunc("/delete", Delete)
        http.HandleFunc("/export", Export)
        http.ListenAndServe(":8444", nil)
        log.Println("END")
}