main.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. package main
  2. import (
  3. "fmt"
  4. "math/rand"
  5. "net/http"
  6. "time"
  7. "github.com/gin-gonic/gin"
  8. postgres "gorm.io/driver/postgres"
  9. "gorm.io/gorm"
  10. "gorm.io/gorm/logger"
  11. )
  12. // World represents an entry int the World table
  13. type World struct {
  14. ID int64 `json:"id"`
  15. RandomNumber int64 `json:"randomNumber" gorm:"column:randomnumber"`
  16. }
  17. // Override GORM convention for table mapping
  18. // TableName overrides the table name used by User to `profiles`
  19. func (World) TableName() string {
  20. return "World"
  21. }
  22. // implements the basic logic behind the query tests
  23. func getWorld(db *gorm.DB) World {
  24. //we could actually precompute a list of random
  25. //numbers and slice them but this makes no sense
  26. //as I expect that this 'random' is just a placeholder
  27. //for an actual business logic
  28. randomId := rand.Intn(10000) + 1
  29. var world World
  30. db.Take(&world, randomId)
  31. return world
  32. }
  33. // implements the logic behind the updates tests
  34. func processWorld(tx *gorm.DB) (World, error) {
  35. //we could actually precompute a list of random
  36. //numbers and slice them but this makes no sense
  37. //as I expect that this 'random' is just a placeholder
  38. //for an actual business logic in a real test
  39. randomId := rand.Intn(10000) + 1
  40. randomId2 := int64(rand.Intn(10000) + 1)
  41. var world World
  42. tx.Take(&world, randomId)
  43. world.RandomNumber = randomId2
  44. err := tx.Save(&world).Error
  45. return world, err
  46. }
  47. func main() {
  48. /* SETUP DB AND WEB SERVER */
  49. dsn := "host=tfb-database user=benchmarkdbuser password=benchmarkdbpass dbname=hello_world port=5432 sslmode=disable"
  50. db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
  51. PrepareStmt: true, // use prep statements
  52. Logger: logger.Default.LogMode(logger.Silent), // new, not inserted in original submission 2x on query
  53. })
  54. if err != nil {
  55. panic("failed to connect database")
  56. }
  57. sqlDB, err := db.DB()
  58. if err != nil {
  59. panic("failed to get underlying db conn pooling struct")
  60. }
  61. // SetMaxIdleConns sets the maximum number of connections in the idle connection pool.
  62. sqlDB.SetMaxIdleConns(500)
  63. //r := gin.Default() // use default middleware - original submission
  64. r := gin.New() // use no middleware, causes 1.83x on plaintext (pure gin still at +14% on both plaintext and json)
  65. // setup middleware to add server header
  66. // this slows things a little bit but it is the best design decision
  67. serverHeader := []string{"Gin-gorm"}
  68. r.Use(func(c *gin.Context) {
  69. c.Writer.Header()["Server"] = serverHeader
  70. })
  71. /* START TESTS */
  72. // JSON TEST
  73. r.GET("/json", func(c *gin.Context) {
  74. //c.Header("Server", "example") - original submission now using middleware
  75. c.JSON(200, gin.H{"message": "Hello, World!"})
  76. })
  77. // PLAINTEXT TEST
  78. r.GET("/plaintext", func(c *gin.Context) {
  79. //c.Header("Server", "example") - original submission now using middleware
  80. c.String(200, "Hello, World!")
  81. })
  82. // SINGLE QUERY
  83. r.GET("/db", func(c *gin.Context) {
  84. world := getWorld(db)
  85. //c.Header("Server", "example") - original submission now using middleware
  86. c.JSON(200, world)
  87. })
  88. type NumOf struct {
  89. Queries int `form:"queries"`
  90. }
  91. // MULTIPLE QUERIES
  92. r.GET("/queries", func(c *gin.Context) {
  93. var numOf NumOf
  94. if c.ShouldBindQuery(&numOf) != nil { // manage missing query num
  95. numOf.Queries = 1
  96. } else if numOf.Queries < 1 { //set at least 1
  97. numOf.Queries = 1
  98. } else if numOf.Queries > 500 { //set no more than 500
  99. numOf.Queries = 500
  100. }
  101. worlds := make([]World, numOf.Queries, numOf.Queries) //prealloc
  102. //original submission with go routines, seems faster then without...
  103. channel := make(chan World, numOf.Queries)
  104. for i := 0; i < numOf.Queries; i++ {
  105. go func() { channel <- getWorld(db) }()
  106. }
  107. for i := 0; i < numOf.Queries; i++ {
  108. worlds[i] = <-channel
  109. }
  110. //c.Header("Server", "example") - original submission now using middleware
  111. c.JSON(200, worlds)
  112. })
  113. // MULTIPLE UPDATES
  114. r.GET("/updates", func(c *gin.Context) {
  115. var numOf NumOf
  116. if c.ShouldBindQuery(&numOf) != nil { // manage missing query num
  117. numOf.Queries = 1
  118. } else if numOf.Queries < 1 { //set at least 1
  119. numOf.Queries = 1
  120. } else if numOf.Queries > 500 { //set no more than 500
  121. numOf.Queries = 500
  122. }
  123. worlds := make([]World, numOf.Queries, numOf.Queries) //prealloc
  124. var err error = nil
  125. //c.Header("Server", "example") - original submission now using middleware
  126. for i := 0; i < numOf.Queries; i++ {
  127. worlds[i], err = processWorld(db)
  128. if err != nil {
  129. fmt.Println(err)
  130. c.JSON(500, gin.H{"error": err})
  131. break
  132. }
  133. }
  134. c.JSON(200, worlds)
  135. })
  136. /* START SERVICE */
  137. s := &http.Server{
  138. Addr: ":8080",
  139. Handler: r,
  140. ReadTimeout: 100000 * time.Second, //increase keepalive
  141. WriteTimeout: 100000 * time.Second,
  142. }
  143. s.ListenAndServe() // listen and serve on 0.0.0.0:8080
  144. }