1
0

api_scale_postgres_test.go 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. package service
  2. import (
  3. "fmt"
  4. "testing"
  5. "time"
  6. "github.com/mhsanaei/3x-ui/v3/internal/database"
  7. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  8. "github.com/mhsanaei/3x-ui/v3/internal/xray"
  9. )
  10. func seedClientTraffics(t *testing.T, inboundId int, clients []model.Client) {
  11. t.Helper()
  12. db := database.GetDB()
  13. rows := make([]xray.ClientTraffic, len(clients))
  14. for i := range clients {
  15. rows[i] = xray.ClientTraffic{
  16. InboundId: inboundId,
  17. Email: clients[i].Email,
  18. Enable: true,
  19. Total: clients[i].TotalGB,
  20. ExpiryTime: clients[i].ExpiryTime,
  21. }
  22. }
  23. if err := db.CreateInBatches(rows, 1000).Error; err != nil {
  24. t.Fatalf("seed client_traffics: %v", err)
  25. }
  26. }
  27. // TestAllAPIsPostgresScale exercises every client/inbound/group service method
  28. // reachable from the REST API at 100k/200k clients, asserting none crash on the
  29. // PostgreSQL bind-parameter ceiling and logging the wall-clock cost of each.
  30. func TestAllAPIsPostgresScale(t *testing.T) {
  31. setupScaleDB(t)
  32. svc := &ClientService{}
  33. inboundSvc := &InboundService{}
  34. settingSvc := &SettingService{}
  35. const userId = 1
  36. const m = 2000
  37. sizes := []int{50000, 100000, 200000}
  38. for _, n := range sizes {
  39. t.Run(fmt.Sprintf("N=%d", n), func(t *testing.T) {
  40. db := database.GetDB()
  41. resetScaleTables(t, db, "inbounds", "clients", "client_inbounds", "client_traffics", "client_groups")
  42. clients := makeScaleClients(n)
  43. exp := time.Now().AddDate(1, 0, 0).UnixMilli()
  44. for i := range clients {
  45. clients[i].ExpiryTime = exp
  46. clients[i].TotalGB = 100 << 30
  47. }
  48. ib := &model.Inbound{UserId: userId, Tag: fmt.Sprintf("all-%d", n), Enable: true, Port: 40000, Protocol: model.VLESS, Settings: clientsSettings(t, clients)}
  49. if err := db.Create(ib).Error; err != nil {
  50. t.Fatalf("create inbound: %v", err)
  51. }
  52. ib2 := &model.Inbound{UserId: userId, Tag: fmt.Sprintf("all2-%d", n), Enable: true, Port: 40001, Protocol: model.VLESS, Settings: `{"clients":[]}`}
  53. if err := db.Create(ib2).Error; err != nil {
  54. t.Fatalf("create inbound2: %v", err)
  55. }
  56. if err := svc.SyncInbound(nil, ib.Id, clients); err != nil {
  57. t.Fatalf("seed SyncInbound: %v", err)
  58. }
  59. run := func(name string, fn func() error) {
  60. start := time.Now()
  61. if err := fn(); err != nil {
  62. t.Fatalf("%s: %v", name, err)
  63. }
  64. t.Logf("N=%-7d %-26s %v", n, name, time.Since(start).Round(time.Millisecond))
  65. }
  66. run("GetInboundDetail(noTraffic)", func() error { _, err := inboundSvc.GetInboundDetail(ib.Id); return err })
  67. seedClientTraffics(t, ib.Id, clients)
  68. db.Exec("ANALYZE")
  69. emails := make([]string, n)
  70. for i := range n {
  71. emails[i] = clients[i].Email
  72. }
  73. emailsM := emails[:m]
  74. run("GetInbounds", func() error { _, err := inboundSvc.GetInbounds(userId); return err })
  75. run("GetInboundsSlim", func() error { _, err := inboundSvc.GetInboundsSlim(userId); return err })
  76. run("GetInboundDetail", func() error { _, err := inboundSvc.GetInboundDetail(ib.Id); return err })
  77. run("GetInboundOptions", func() error { _, err := inboundSvc.GetInboundOptions(userId); return err })
  78. run("ListPaged", func() error {
  79. _, err := svc.ListPaged(inboundSvc, settingSvc, ClientPageParams{Page: 1, PageSize: 25})
  80. return err
  81. })
  82. run("ListPaged+search", func() error {
  83. _, err := svc.ListPaged(inboundSvc, settingSvc, ClientPageParams{Page: 1, PageSize: 25, Search: "user-0012345"})
  84. return err
  85. })
  86. run("GetClientsLastOnline", func() error { _, err := inboundSvc.GetClientsLastOnline(); return err })
  87. run("GetClientTrafficByEmail", func() error { _, err := inboundSvc.GetClientTrafficByEmail(emails[n/2]); return err })
  88. run("GetRecordByEmail", func() error { _, err := svc.GetRecordByEmail(nil, emails[n/2]); return err })
  89. run("ListGroups", func() error { _, err := svc.ListGroups(); return err })
  90. run("AddToGroup(M)", func() error { _, err := svc.AddToGroup(emailsM, "g1"); return err })
  91. run("EmailsByGroup", func() error { _, err := svc.EmailsByGroup("g1"); return err })
  92. run("RenameGroup", func() error { _, err := svc.RenameGroup("g1", "g2"); return err })
  93. run("DeleteGroup", func() error { _, err := svc.DeleteGroup("g2"); return err })
  94. run("ResetInboundTraffic", func() error { return inboundSvc.ResetInboundTraffic(ib.Id) })
  95. run("Inbound.ResetAllTraffics", func() error { return inboundSvc.ResetAllTraffics() })
  96. run("Client.ResetAllTraffics", func() error { _, err := svc.ResetAllTraffics(); return err })
  97. run("BulkResetTraffic(M)", func() error { _, err := svc.BulkResetTraffic(inboundSvc, emailsM); return err })
  98. run("UpdateByEmail", func() error {
  99. upd := clients[n/3]
  100. upd.Comment = "touched"
  101. _, err := svc.UpdateByEmail(inboundSvc, upd.Email, upd)
  102. return err
  103. })
  104. run("AttachByEmail", func() error { _, err := svc.AttachByEmail(inboundSvc, emails[n/3], []int{ib2.Id}); return err })
  105. run("DetachByEmailMany", func() error { _, err := svc.DetachByEmailMany(inboundSvc, emails[n/3], []int{ib2.Id}); return err })
  106. depEmails := emails[:1000]
  107. for _, batch := range chunkStrings(depEmails, sqlInChunk) {
  108. if err := db.Model(xray.ClientTraffic{}).Where("email IN ?", batch).Update("down", int64(200)<<30).Error; err != nil {
  109. t.Fatalf("mark depleted: %v", err)
  110. }
  111. }
  112. run("DelDepleted(1k)", func() error { _, _, err := svc.DelDepleted(inboundSvc); return err })
  113. run("DelInbound(full)", func() error { _, err := inboundSvc.DelInbound(ib.Id); return err })
  114. })
  115. }
  116. }
  117. // TestGetClientTrafficByEmailABScale measures the GetClientTrafficByEmail change:
  118. // old path (GetClientByEmail, which parses the inbound's entire settings JSON to
  119. // find one client) vs new path (UUID/subId read from the indexed clients table).
  120. func TestGetClientTrafficByEmailABScale(t *testing.T) {
  121. setupScaleDB(t)
  122. svc := &ClientService{}
  123. inboundSvc := &InboundService{}
  124. const reps = 10
  125. sizes := []int{50000, 100000, 200000}
  126. oldImpl := func(email string) error {
  127. tr, client, err := inboundSvc.GetClientByEmail(email)
  128. if err != nil {
  129. return err
  130. }
  131. if tr != nil && client != nil {
  132. tr.UUID = client.ID
  133. tr.SubId = client.SubID
  134. }
  135. return nil
  136. }
  137. for _, n := range sizes {
  138. t.Run(fmt.Sprintf("N=%d", n), func(t *testing.T) {
  139. db := database.GetDB()
  140. resetScaleTables(t, db, "inbounds", "clients", "client_inbounds", "client_traffics")
  141. clients := makeScaleClients(n)
  142. ib := &model.Inbound{UserId: 1, Tag: fmt.Sprintf("ctbe-%d", n), Enable: true, Port: 40000, Protocol: model.VLESS, Settings: clientsSettings(t, clients)}
  143. if err := db.Create(ib).Error; err != nil {
  144. t.Fatalf("create inbound: %v", err)
  145. }
  146. if err := svc.SyncInbound(nil, ib.Id, clients); err != nil {
  147. t.Fatalf("seed SyncInbound: %v", err)
  148. }
  149. seedClientTraffics(t, ib.Id, clients)
  150. db.Exec("ANALYZE")
  151. targets := []string{clients[0].Email, clients[n/2].Email, clients[n-1].Email}
  152. start := time.Now()
  153. for i := range reps {
  154. if _, err := inboundSvc.GetClientTrafficByEmail(targets[i%len(targets)]); err != nil {
  155. t.Fatalf("new GetClientTrafficByEmail: %v", err)
  156. }
  157. }
  158. newDur := time.Since(start) / reps
  159. start = time.Now()
  160. for i := range reps {
  161. if err := oldImpl(targets[i%len(targets)]); err != nil {
  162. t.Fatalf("old GetClientTrafficByEmail: %v", err)
  163. }
  164. }
  165. oldDur := time.Since(start) / reps
  166. t.Logf("N=%-7d new=%-9v old=%-9v speedup=%.0fx", n,
  167. newDur.Round(time.Microsecond), oldDur.Round(time.Millisecond),
  168. float64(oldDur)/float64(maxDur(newDur, time.Microsecond)))
  169. })
  170. }
  171. }