hello_postgres.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. package main
  2. import (
  3. "database/sql"
  4. "encoding/json"
  5. "flag"
  6. "fmt"
  7. "html/template"
  8. "log"
  9. "math/rand"
  10. "net/http"
  11. "os"
  12. "sort"
  13. "strconv"
  14. _ "github.com/lib/pq"
  15. )
  16. type Message struct {
  17. Message string `json:"message"`
  18. }
  19. type World struct {
  20. Id uint16 `json:"id"`
  21. RandomNumber uint16 `json:"randomNumber"`
  22. }
  23. type Fortune struct {
  24. Id uint16 `json:"id"`
  25. Message string `json:"message"`
  26. }
  27. const (
  28. // Content
  29. fortuneHTML = `<!DOCTYPE html>
  30. <html>
  31. <head>
  32. <title>Fortunes</title>
  33. </head>
  34. <body>
  35. <table>
  36. <tr>
  37. <th>id</th>
  38. <th>message</th>
  39. </tr>
  40. {{range .}}
  41. <tr>
  42. <td>{{.Id}}</td>
  43. <td>{{.Message}}</td>
  44. </tr>
  45. {{end}}
  46. </table>
  47. </body>
  48. </html>`
  49. // Database
  50. connectionString = "postgres://benchmarkdbuser:benchmarkdbpass@%s/hello_world?sslmode=disable"
  51. worldSelect = "SELECT id, randomNumber FROM World WHERE id = $1"
  52. worldUpdate = "UPDATE World SET randomNumber = $1 WHERE id = $2"
  53. fortuneSelect = "SELECT id, message FROM Fortune"
  54. worldRowCount = 10000
  55. maxConnections = 256
  56. )
  57. var (
  58. // Templates
  59. tmpl = template.Must(template.New("fortune.html").Parse(fortuneHTML))
  60. // Database
  61. db *sql.DB
  62. worldSelectPrepared *sql.Stmt
  63. worldUpdatePrepared *sql.Stmt
  64. fortuneSelectPrepared *sql.Stmt
  65. )
  66. func initDB() {
  67. var err error
  68. var dbhost = os.Getenv("DBHOST")
  69. if dbhost == "" {
  70. dbhost = "localhost"
  71. }
  72. db, err = sql.Open("postgres", fmt.Sprintf(connectionString, dbhost))
  73. if err != nil {
  74. log.Fatalf("Error opening database: %v", err)
  75. }
  76. db.SetMaxIdleConns(maxConnections)
  77. db.SetMaxOpenConns(maxConnections)
  78. worldSelectPrepared, err = db.Prepare(worldSelect)
  79. if err != nil {
  80. log.Fatal(err)
  81. }
  82. worldUpdatePrepared, err = db.Prepare(worldUpdate)
  83. if err != nil {
  84. log.Fatal(err)
  85. }
  86. fortuneSelectPrepared, err = db.Prepare(fortuneSelect)
  87. if err != nil {
  88. log.Fatal(err)
  89. }
  90. }
  91. func main() {
  92. flag.Parse()
  93. initDB()
  94. http.HandleFunc("/db", dbHandler)
  95. http.HandleFunc("/queries", queriesHandler)
  96. http.HandleFunc("/fortune", fortuneHandler)
  97. http.HandleFunc("/update", updateHandler)
  98. log.Fatal(http.ListenAndServe(":8080", nil))
  99. }
  100. func getQueriesParam(r *http.Request) int {
  101. n := 1
  102. if nStr := r.URL.Query().Get("queries"); len(nStr) > 0 {
  103. n, _ = strconv.Atoi(nStr)
  104. }
  105. if n < 1 {
  106. n = 1
  107. } else if n > 500 {
  108. n = 500
  109. }
  110. return n
  111. }
  112. // Test 2: Single database query
  113. func dbHandler(w http.ResponseWriter, r *http.Request) {
  114. var world World
  115. err := worldSelectPrepared.QueryRow(rand.Intn(worldRowCount)+1).Scan(&world.Id, &world.RandomNumber)
  116. if err != nil {
  117. log.Fatalf("Error scanning world row: %s", err.Error())
  118. }
  119. w.Header().Set("Server", "Go")
  120. w.Header().Set("Content-Type", "application/json")
  121. json.NewEncoder(w).Encode(&world)
  122. }
  123. // Test 3: Multiple database queries
  124. func queriesHandler(w http.ResponseWriter, r *http.Request) {
  125. n := getQueriesParam(r)
  126. world := make([]World, n)
  127. for i := 0; i < n; i++ {
  128. err := worldSelectPrepared.QueryRow(rand.Intn(worldRowCount)+1).Scan(&world[i].Id, &world[i].RandomNumber)
  129. if err != nil {
  130. log.Fatalf("Error scanning world row: %v", err)
  131. }
  132. }
  133. w.Header().Set("Server", "Go")
  134. w.Header().Set("Content-Type", "application/json")
  135. json.NewEncoder(w).Encode(world)
  136. }
  137. // Test 4: Fortunes
  138. func fortuneHandler(w http.ResponseWriter, r *http.Request) {
  139. rows, err := fortuneSelectPrepared.Query()
  140. if err != nil {
  141. log.Fatalf("Error preparing statement: %v", err)
  142. }
  143. defer rows.Close()
  144. fortunes := fetchFortunes(rows)
  145. fortunes = append(fortunes, &Fortune{Message: "Additional fortune added at request time."})
  146. sort.Sort(ByMessage{fortunes})
  147. w.Header().Set("Server", "Go")
  148. w.Header().Set("Content-Type", "text/html; charset=utf-8")
  149. if err := tmpl.Execute(w, fortunes); err != nil {
  150. http.Error(w, err.Error(), http.StatusInternalServerError)
  151. }
  152. }
  153. func fetchFortunes(rows *sql.Rows) Fortunes {
  154. fortunes := make(Fortunes, 0, 16)
  155. for rows.Next() { // Fetch rows
  156. fortune := Fortune{}
  157. if err := rows.Scan(&fortune.Id, &fortune.Message); err != nil {
  158. log.Fatalf("Error scanning fortune row: %s", err.Error())
  159. }
  160. fortunes = append(fortunes, &fortune)
  161. }
  162. return fortunes
  163. }
  164. // Test 5: Database updates
  165. func updateHandler(w http.ResponseWriter, r *http.Request) {
  166. n := getQueriesParam(r)
  167. world := make([]World, n)
  168. for i := 0; i < n; i++ {
  169. if err := worldSelectPrepared.QueryRow(rand.Intn(worldRowCount)+1).Scan(&world[i].Id, &world[i].RandomNumber); err != nil {
  170. log.Fatalf("Error scanning world row: %v", err)
  171. }
  172. world[i].RandomNumber = uint16(rand.Intn(worldRowCount) + 1)
  173. if _, err := worldUpdatePrepared.Exec(world[i].RandomNumber, world[i].Id); err != nil {
  174. log.Fatalf("Error updating world row: %v", err)
  175. }
  176. }
  177. w.Header().Set("Server", "Go")
  178. w.Header().Set("Content-Type", "application/json")
  179. encoder := json.NewEncoder(w)
  180. encoder.Encode(world)
  181. }
  182. type Fortunes []*Fortune
  183. func (s Fortunes) Len() int { return len(s) }
  184. func (s Fortunes) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
  185. type ByMessage struct{ Fortunes }
  186. func (s ByMessage) Less(i, j int) bool { return s.Fortunes[i].Message < s.Fortunes[j].Message }