Skip to content

Instantly share code, notes, and snippets.

@yanmhlv
Last active January 19, 2017 10:21
Show Gist options
  • Save yanmhlv/6704f195a1c185d458e5 to your computer and use it in GitHub Desktop.
Save yanmhlv/6704f195a1c185d458e5 to your computer and use it in GitHub Desktop.
go run get_banners.go; wrk -s wrk.lua -d30s -t10 -c100 http://localhost:3000/
package main
import (
"errors"
"log"
"net/http"
"strconv"
"sync"
//"sync/atomic"
"encoding/json"
_ "net/http/pprof"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/postgres"
"github.com/pborman/uuid"
)
type (
// Zone модель
Zone struct {
ZID int `gorm:"primary_key;column:zid"`
}
// Assoc модель
Assoc struct {
BID int `gorm:"column:bid"`
ZID int `gorm:"column:zid"`
ECPM float64 `gorm:"column:ecpm"`
}
// User - модель пользователя
User struct {
UUID string //
Zones map[int]int // current index in zone; zoneID: currentBanner
M sync.RWMutex
}
// Banner модель
Banner struct {
BID int `gorm:"primary_key;column:bid"`
Context string `gorm:"column:text;column:context"`
}
)
// TableName - название таблицы для модельки Assoc
func (a *Assoc) TableName() string {
return "assoc"
}
// TableName - название таблицы для модельки Banner
func (b *Banner) TableName() string {
return "banners"
}
// TableName - название таблицы для модельки Zone
func (z *Zone) TableName() string {
return "zones"
}
var (
// БД
db *gorm.DB
// буфер банеров
bannerBuffer map[int]string = make(map[int]string)
// association buffer, sorted by eCPM
assocBuffer map[int][]int = make(map[int][]int)// next banner
// Буфер пользователей
users map[string]*User = make(map[string]*User) // userID: *User
)
// GetUser - получение пользователя или генерация, если отсутствует в буфере
func GetUser(rw *sync.RWMutex, id string) *User {
rw.RLock()
user, ok := users[id]
rw.RUnlock()
if ok {
return user
}
user = &User{
UUID: uuid.New(),
Zones: make(map[int]int),
M: sync.RWMutex{},
}
rw.Lock()
users[user.UUID] = user
rw.Unlock()
return user
}
func initDatabase() {
var err error
db, err = gorm.Open("postgres", "user=yan password=123123 dbname=test sslmode=disable")
if err != nil {
log.Fatalf("cant connect to database: %+v", err)
}
db.AutoMigrate(
&Banner{},
&Zone{},
&Assoc{},
)
db.Model(&Assoc{}).AddIndex("idx_zid_ecpm", "zid", "ecpm")
}
func updateBanners() {
var banners []Banner
db.Raw(`SELECT * FROM banners;`).Scan(&banners)
for _, banner := range banners {
bannerBuffer[banner.BID] = banner.Context
}
}
func updateAssoc() {
var assoc []Assoc
db.Raw(`SELECT * FROM assoc ORDER BY zid ASC, ecpm DESC;`).Scan(&assoc)
for _, value := range assoc {
assocBuffer[value.ZID] = append(assocBuffer[value.ZID], value.BID)
}
}
// получение баннера для пользователя по запрошенной зоне
func getBanner(user *User, strZoneID string) (string, error) {
zoneID, err := strconv.Atoi(strZoneID)
if err != nil {
return "", err
}
user.M.RLock()
currentIndex := user.Zones[zoneID]
if currentIndex >= len(assocBuffer[zoneID]) {
user.M.RUnlock()
return "", errors.New("current zone is empty")
}
user.M.RUnlock()
defer func() {
user.M.Lock()
user.Zones[zoneID] = currentIndex + 1
user.M.Unlock()
}()
if context, ok := bannerBuffer[assocBuffer[zoneID][currentIndex]]; ok {
return context, nil
}
return "", errors.New("not found context")
}
func main() {
log.Println("init database...")
initDatabase()
log.Println("clean fixtures into database...")
db.Exec(`
DELETE FROM zones;
DELETE FROM banners;
DELETE FROM assoc;
`)
log.Println("insert fixtures into database...")
db.Exec(`
INSERT INTO zones(zid) SELECT generate_series(1,10000) as zid;
INSERT INTO banners(bid,context) SELECT generate_series(1,100000) as bid, (random()::text) as context;
INSERT INTO assoc(zid,bid,ecpm) (SELECT round(random() * 10000) as zid, round(random() * 100000) as bid, random() as ecpm FROM generate_series(1,100000));
`)
log.Println("update cache...")
updateBanners()
updateAssoc()
log.Printf("size of assoc: %d", len(assocBuffer))
log.Printf("size of banners: %d", len(bannerBuffer))
m := sync.RWMutex{}
mux := http.NewServeMux()
mux.HandleFunc("/info", func(w http.ResponseWriter, req *http.Request) {
json.NewEncoder(w).Encode(map[string]int{
"bannerBuffer": len(bannerBuffer),
"assocBuffer": len(assocBuffer),
"users": len(users),
})
})
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
cookie, err := req.Cookie("userid")
if err != nil {
cookie = &http.Cookie{Name: "userid"}
}
user := GetUser(&m, cookie.Value)
cookie.Value = user.UUID
http.SetCookie(w, cookie)
zoneID := req.URL.Query().Get("zid")
banner, err := getBanner(user, zoneID)
json.NewEncoder(w).Encode(map[string]interface{}{
"error": err,
"banner": banner,
"zid": zoneID,
})
})
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
log.Println("start http server...")
(&http.Server{
Handler: mux,
Addr: ":3000",
}).ListenAndServe()
}
cookie = nil
path = "/"
request = function()
if cookie then
wrk.headers["Cookie"] = cookie
end
current_path = path .. "?zid=" .. math.random(1,10000)
return wrk.format("GET", current_path)
end
response = function(status, headers, body)
if not cookie and status == 200 then
cookie = headers['Set-Cookie']
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment