Browse Source

Merge pull request #1873 from methane/go-prepared

Add go-prepared test
Mike Smith 9 years ago
parent
commit
12d51833a0
2 changed files with 168 additions and 54 deletions
  1. 22 1
      frameworks/Go/go-raw/benchmark_config.json
  2. 146 53
      frameworks/Go/go-raw/src/hello/hello.go

+ 22 - 1
frameworks/Go/go-raw/benchmark_config.json

@@ -24,6 +24,27 @@
       "notes": "",
       "versus": "go"
     },
+    "interpolate": {
+      "setup_file": "setup",
+      "db_url": "/dbInterpolate",
+      "query_url": "/queriesInterpolate?queries=",
+      "fortune_url": "/fortuneInterpolate",
+      "update_url": "/updateInterpolate?queries=",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Platform",
+      "database": "MySQL",
+      "framework": "go",
+      "language": "Go",
+      "orm": "Raw",
+      "platform": "Go",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "go-interpolate",
+      "notes": "",
+      "versus": "go"
+    },
     "prefork": {
       "setup_file": "setup_prefork",
       "json_url": "/json",
@@ -43,7 +64,7 @@
       "webserver": "None",
       "os": "Linux",
       "database_os": "Linux",
-      "display_name": "go-raw",
+      "display_name": "go-prefork",
       "notes": "",
       "versus": "go"
     }

+ 146 - 53
frameworks/Go/go-raw/src/hello/hello.go

@@ -32,42 +32,62 @@ type Fortune struct {
 	Message string `json:"message"`
 }
 
-// Databases
 const (
-	// Go 1.4's sql.DB has scalability problem when using (explicitly reused) prepared statement.
-	// https://github.com/golang/go/issues/9484
+	helloWorldString = "Hello, World!"
+
+	// Databases
 	//
-	// Using db.Query() instead of stmt.Query() avoid the issue.
-	// But it makes 3 round trips per query: prepare, execute and close.
 	// `interpolateParams=true` enables client side parameter interpolation.
 	// It reduces round trips without prepared statement.
 	//
-	// Before Go 1.5 is released, we can see real power of Go with this benchmark.
-	// After Go 1.5 is released, we can see prepared statement vs interpolation by comparing
-	// this and another lightweight Go framework.
-	connectionString   = "benchmarkdbuser:benchmarkdbpass@tcp(localhost:3306)/hello_world?interpolateParams=true"
-	worldSelect        = "SELECT id, randomNumber FROM World WHERE id = ?"
-	worldUpdate        = "UPDATE World SET randomNumber = ? WHERE id = ?"
-	fortuneSelect      = "SELECT id, message FROM Fortune;"
-	worldRowCount      = 10000
-	maxConnectionCount = 256
+	// We can see difference between prepared statement and interpolation by comparing go-raw and go-raw-interpolate
+	connectionString = "benchmarkdbuser:benchmarkdbpass@tcp(localhost:3306)/hello_world?interpolateParams=true"
+	worldSelect      = "SELECT id, randomNumber FROM World WHERE id = ?"
+	worldUpdate      = "UPDATE World SET randomNumber = ? WHERE id = ?"
+	fortuneSelect    = "SELECT id, message FROM Fortune"
+	worldRowCount    = 10000
+	maxConnections   = 256
 )
 
-const helloWorldString = "Hello, World!"
-
 var (
+	helloWorldBytes = []byte(helloWorldString)
+
 	// Templates
 	tmpl = template.Must(template.ParseFiles("templates/layout.html", "templates/fortune.html"))
 
 	// Database
-	db *sql.DB
-
-	helloWorldBytes = []byte(helloWorldString)
+	db                    *sql.DB
+	worldSelectPrepared   *sql.Stmt
+	worldUpdatePrepared   *sql.Stmt
+	fortuneSelectPrepared *sql.Stmt
 )
 
 var prefork = flag.Bool("prefork", false, "use prefork")
 var child = flag.Bool("child", false, "is child proc")
 
+func initDB() {
+	var err error
+	db, err = sql.Open("mysql", connectionString)
+	if err != nil {
+		log.Fatalf("Error opening database: %v", err)
+	}
+	db.SetMaxIdleConns(maxConnections)
+	db.SetMaxOpenConns(maxConnections)
+
+	worldSelectPrepared, err = db.Prepare(worldSelect)
+	if err != nil {
+		log.Fatal(err)
+	}
+	worldUpdatePrepared, err = db.Prepare(worldUpdate)
+	if err != nil {
+		log.Fatal(err)
+	}
+	fortuneSelectPrepared, err = db.Prepare(fortuneSelect)
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
 func main() {
 	var listener net.Listener
 	flag.Parse()
@@ -77,18 +97,17 @@ func main() {
 		listener = doPrefork()
 	}
 
-	var err error
-	db, err = sql.Open("mysql", connectionString)
-	if err != nil {
-		log.Fatalf("Error opening database: %v", err)
-	}
-	db.SetMaxIdleConns(maxConnectionCount)
+	initDB()
 
+	http.HandleFunc("/json", jsonHandler)
 	http.HandleFunc("/db", dbHandler)
+	http.HandleFunc("/dbInterpolate", dbInterpolateHandler)
 	http.HandleFunc("/queries", queriesHandler)
-	http.HandleFunc("/json", jsonHandler)
+	http.HandleFunc("/queriesInterpolate", queriesInterpolateHandler)
 	http.HandleFunc("/fortune", fortuneHandler)
+	http.HandleFunc("/fortuneInterpolate", fortuneInterpolateHandler)
 	http.HandleFunc("/update", updateHandler)
+	http.HandleFunc("/updateInterpolate", updateInterpolateHandler)
 	http.HandleFunc("/plaintext", plaintextHandler)
 	if !*prefork {
 		http.ListenAndServe(":8080", nil)
@@ -144,56 +163,123 @@ func doPrefork() (listener net.Listener) {
 	return listener
 }
 
+func getQueriesParam(r *http.Request) int {
+	n := 1
+	if nStr := r.URL.Query().Get("queries"); len(nStr) > 0 {
+		n, _ = strconv.Atoi(nStr)
+	}
+
+	if n < 1 {
+		n = 1
+	} else if n > 500 {
+		n = 500
+	}
+	return n
+}
+
 // Test 1: JSON serialization
 func jsonHandler(w http.ResponseWriter, r *http.Request) {
+	w.Header().Set("Server", "Go")
 	w.Header().Set("Content-Type", "application/json")
 	json.NewEncoder(w).Encode(&Message{helloWorldString})
 }
 
 // Test 2: Single database query
 func dbHandler(w http.ResponseWriter, r *http.Request) {
+	var world World
+	err := worldSelectPrepared.QueryRow(rand.Intn(worldRowCount)+1).Scan(&world.Id, &world.RandomNumber)
+	if err != nil {
+		log.Fatalf("Error scanning world row: %s", err.Error())
+	}
+
+	w.Header().Set("Server", "Go")
+	w.Header().Set("Content-Type", "application/json")
+	json.NewEncoder(w).Encode(&world)
+}
+
+func dbInterpolateHandler(w http.ResponseWriter, r *http.Request) {
 	var world World
 	err := db.QueryRow(worldSelect, rand.Intn(worldRowCount)+1).Scan(&world.Id, &world.RandomNumber)
 	if err != nil {
 		log.Fatalf("Error scanning world row: %s", err.Error())
 	}
 
+	w.Header().Set("Server", "Go")
 	w.Header().Set("Content-Type", "application/json")
 	json.NewEncoder(w).Encode(&world)
 }
 
 // Test 3: Multiple database queries
 func queriesHandler(w http.ResponseWriter, r *http.Request) {
-	n := 1
-	if nStr := r.URL.Query().Get("queries"); len(nStr) > 0 {
-		n, _ = strconv.Atoi(nStr)
-	}
+	n := getQueriesParam(r)
 
-	if n < 1 {
-		n = 1
-	} else if n > 500 {
-		n = 500
+	world := make([]World, n)
+	for i := 0; i < n; i++ {
+		err := worldSelectPrepared.QueryRow(rand.Intn(worldRowCount)+1).Scan(&world[i].Id, &world[i].RandomNumber)
+		if err != nil {
+			log.Fatalf("Error scanning world row: %v", err)
+		}
 	}
 
+	w.Header().Set("Server", "Go")
+	w.Header().Set("Content-Type", "application/json")
+	json.NewEncoder(w).Encode(world)
+}
+
+func queriesInterpolateHandler(w http.ResponseWriter, r *http.Request) {
+	n := getQueriesParam(r)
+
 	world := make([]World, n)
 	for i := 0; i < n; i++ {
 		err := db.QueryRow(worldSelect, rand.Intn(worldRowCount)+1).Scan(&world[i].Id, &world[i].RandomNumber)
 		if err != nil {
-			log.Fatalf("Error scanning world row: %s", err.Error())
+			log.Fatalf("Error scanning world row: %v", err)
 		}
 	}
 
+	w.Header().Set("Server", "Go")
 	w.Header().Set("Content-Type", "application/json")
 	json.NewEncoder(w).Encode(world)
 }
 
 // Test 4: Fortunes
 func fortuneHandler(w http.ResponseWriter, r *http.Request) {
+	rows, err := fortuneSelectPrepared.Query()
+	if err != nil {
+		log.Fatalf("Error preparing statement: %v", err)
+	}
+
+	fortunes := fetchFortunes(rows)
+	fortunes = append(fortunes, &Fortune{Message: "Additional fortune added at request time."})
+
+	sort.Sort(ByMessage{fortunes})
+	w.Header().Set("Server", "Go")
+	w.Header().Set("Content-Type", "text/html")
+	if err := tmpl.Execute(w, fortunes); err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+	}
+}
+
+func fortuneInterpolateHandler(w http.ResponseWriter, r *http.Request) {
 	rows, err := db.Query(fortuneSelect)
 	if err != nil {
 		log.Fatalf("Error preparing statement: %v", err)
 	}
 
+	fortunes := fetchFortunes(rows)
+	fortunes = append(fortunes, &Fortune{Message: "Additional fortune added at request time."})
+
+	sort.Sort(ByMessage{fortunes})
+	w.Header().Set("Server", "Go")
+	w.Header().Set("Content-Type", "text/html")
+	if err := tmpl.Execute(w, fortunes); err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+	}
+}
+
+func fetchFortunes(rows *sql.Rows) Fortunes {
+	defer rows.Close()
+
 	fortunes := make(Fortunes, 0, 16)
 	for rows.Next() { //Fetch rows
 		fortune := Fortune{}
@@ -202,46 +288,53 @@ func fortuneHandler(w http.ResponseWriter, r *http.Request) {
 		}
 		fortunes = append(fortunes, &fortune)
 	}
-	rows.Close()
-	fortunes = append(fortunes, &Fortune{Message: "Additional fortune added at request time."})
-
-	sort.Sort(ByMessage{fortunes})
-	w.Header().Set("Content-Type", "text/html")
-	if err := tmpl.Execute(w, fortunes); err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-	}
+	return fortunes
 }
 
 // Test 5: Database updates
 func updateHandler(w http.ResponseWriter, r *http.Request) {
-	n := 1
-	if nStr := r.URL.Query().Get("queries"); len(nStr) > 0 {
-		n, _ = strconv.Atoi(nStr)
+	n := getQueriesParam(r)
+
+	world := make([]World, n)
+	for i := 0; i < n; i++ {
+		if err := worldSelectPrepared.QueryRow(rand.Intn(worldRowCount)+1).Scan(&world[i].Id, &world[i].RandomNumber); err != nil {
+			log.Fatalf("Error scanning world row: %v", err)
+		}
+		world[i].RandomNumber = uint16(rand.Intn(worldRowCount) + 1)
+		if _, err := worldUpdatePrepared.Exec(world[i].RandomNumber, world[i].Id); err != nil {
+			log.Fatalf("Error updating world row: %v", err)
+		}
 	}
 
+	w.Header().Set("Server", "Go")
 	w.Header().Set("Content-Type", "application/json")
 	encoder := json.NewEncoder(w)
+	encoder.Encode(world)
+}
+
+func updateInterpolateHandler(w http.ResponseWriter, r *http.Request) {
+	n := getQueriesParam(r)
 
-	if n < 1 {
-		n = 1
-	} else if n > 500 {
-		n = 500
-	}
 	world := make([]World, n)
 	for i := 0; i < n; i++ {
 		if err := db.QueryRow(worldSelect, rand.Intn(worldRowCount)+1).Scan(&world[i].Id, &world[i].RandomNumber); err != nil {
-			log.Fatalf("Error scanning world row: %s", err.Error())
+			log.Fatalf("Error scanning world row: %v", err)
 		}
 		world[i].RandomNumber = uint16(rand.Intn(worldRowCount) + 1)
 		if _, err := db.Exec(worldUpdate, world[i].RandomNumber, world[i].Id); err != nil {
-			log.Fatalf("Error updating world row: %s", err.Error())
+			log.Fatalf("Error updating world row: %v", err)
 		}
 	}
+
+	w.Header().Set("Server", "Go")
+	w.Header().Set("Content-Type", "application/json")
+	encoder := json.NewEncoder(w)
 	encoder.Encode(world)
 }
 
 // Test 6: Plaintext
 func plaintextHandler(w http.ResponseWriter, r *http.Request) {
+	w.Header().Set("Server", "Go")
 	w.Header().Set("Content-Type", "text/plain")
 	w.Write(helloWorldBytes)
 }