Fix creation of redundant pods on heavy load cold start (#232)
Modifies function specialization so that concurrent requests for the same function cause only one pod to be created.
This commit is contained in:
+89
-12
@@ -24,6 +24,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/handlers"
|
"github.com/gorilla/handlers"
|
||||||
@@ -44,22 +45,89 @@ type funcSvc struct {
|
|||||||
atime time.Time
|
atime time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type createFuncServiceRequest struct {
|
||||||
|
funcMeta *fission.Metadata
|
||||||
|
respChan chan *createFuncServiceResponse
|
||||||
|
}
|
||||||
|
type createFuncServiceResponse struct {
|
||||||
|
address string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
type API struct {
|
type API struct {
|
||||||
poolMgr *GenericPoolManager
|
poolMgr *GenericPoolManager
|
||||||
functionEnv *cache.Cache // map[fission.Metadata]fission.Environment
|
functionEnv *cache.Cache // map[fission.Metadata]fission.Environment
|
||||||
fsCache *functionServiceCache
|
fsCache *functionServiceCache
|
||||||
controller *controllerclient.Client
|
controller *controllerclient.Client
|
||||||
|
fsCreateChannels map[fission.Metadata]*sync.WaitGroup
|
||||||
|
requestChan chan *createFuncServiceRequest
|
||||||
|
|
||||||
//functionService *cache.Cache // map[fission.Metadata]*funcSvc
|
//functionService *cache.Cache // map[fission.Metadata]*funcSvc
|
||||||
//urlFuncSvc *cache.Cache // map[string]*funcSvc
|
//urlFuncSvc *cache.Cache // map[string]*funcSvc
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeAPI(gpm *GenericPoolManager, controller *controllerclient.Client, fsCache *functionServiceCache) *API {
|
func MakeAPI(gpm *GenericPoolManager, controller *controllerclient.Client, fsCache *functionServiceCache) *API {
|
||||||
return &API{
|
api := API{
|
||||||
poolMgr: gpm,
|
poolMgr: gpm,
|
||||||
functionEnv: cache.MakeCache(time.Minute, 0),
|
functionEnv: cache.MakeCache(time.Minute, 0),
|
||||||
fsCache: fsCache,
|
fsCache: fsCache,
|
||||||
controller: controller,
|
controller: controller,
|
||||||
|
fsCreateChannels: make(map[fission.Metadata]*sync.WaitGroup),
|
||||||
|
requestChan: make(chan *createFuncServiceRequest),
|
||||||
|
}
|
||||||
|
go api.serveCreateFuncServices()
|
||||||
|
return &api
|
||||||
|
}
|
||||||
|
|
||||||
|
// All non-cached function service requests go through this goroutine
|
||||||
|
// serially. It parallelizes requests for different functions, and
|
||||||
|
// ensures that for a given function, only one request causes a pod to
|
||||||
|
// get specialized. In other words, it ensures that when there's an
|
||||||
|
// ongoing request for a certain function, all other requests wait for
|
||||||
|
// that request to complete.
|
||||||
|
func (api *API) serveCreateFuncServices() {
|
||||||
|
for {
|
||||||
|
req := <-api.requestChan
|
||||||
|
m := req.funcMeta
|
||||||
|
|
||||||
|
// Cache miss -- is this first one to request the func?
|
||||||
|
wg, found := api.fsCreateChannels[*m]
|
||||||
|
if !found {
|
||||||
|
// create a waitgroup for other requests for
|
||||||
|
// the same function to wait on
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
wg.Add(1)
|
||||||
|
api.fsCreateChannels[*m] = wg
|
||||||
|
|
||||||
|
// launch a goroutine for each request, to parallelize
|
||||||
|
// the specialization of different functions
|
||||||
|
go func() {
|
||||||
|
address, err := api.createServiceForFunction(m)
|
||||||
|
req.respChan <- &createFuncServiceResponse{
|
||||||
|
address: address,
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
|
delete(api.fsCreateChannels, *m)
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
} else {
|
||||||
|
// There's an existing request for this function, wait for it to finish
|
||||||
|
go func() {
|
||||||
|
log.Printf("Waiting for concurrent request for the same function: %v", m)
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
// get the function service from the cache
|
||||||
|
fsvc, err := api.fsCache.GetByFunction(m)
|
||||||
|
address := ""
|
||||||
|
if err == nil {
|
||||||
|
address = fsvc.address
|
||||||
|
}
|
||||||
|
req.respChan <- &createFuncServiceResponse{
|
||||||
|
address: address,
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,6 +203,16 @@ func (api *API) getServiceForFunction(m *fission.Metadata) (string, error) {
|
|||||||
return fsvc.address, nil
|
return fsvc.address, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
respChan := make(chan *createFuncServiceResponse)
|
||||||
|
api.requestChan <- &createFuncServiceRequest{
|
||||||
|
funcMeta: m,
|
||||||
|
respChan: respChan,
|
||||||
|
}
|
||||||
|
resp := <-respChan
|
||||||
|
return resp.address, resp.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) createServiceForFunction(m *fission.Metadata) (string, error) {
|
||||||
// None exists, so create a new funcSvc:
|
// None exists, so create a new funcSvc:
|
||||||
log.Printf("[%v] No cached function service found, creating one", m.Name)
|
log.Printf("[%v] No cached function service found, creating one", m.Name)
|
||||||
|
|
||||||
@@ -155,12 +233,11 @@ func (api *API) getServiceForFunction(m *fission.Metadata) (string, error) {
|
|||||||
// from GenericPool -> get one function container
|
// from GenericPool -> get one function container
|
||||||
// (this also adds to the cache)
|
// (this also adds to the cache)
|
||||||
log.Printf("[%v] getting function service from pool", m.Name)
|
log.Printf("[%v] getting function service from pool", m.Name)
|
||||||
funcSvc, err := pool.GetFuncSvc(m)
|
fsvc, err := pool.GetFuncSvc(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
return fsvc.address, nil
|
||||||
return funcSvc.address, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// find funcSvc and update its atime
|
// find funcSvc and update its atime
|
||||||
|
|||||||
+2
-3
@@ -438,7 +438,7 @@ func (gp *GenericPool) GetFuncSvc(m *fission.Metadata) (*funcSvc, error) {
|
|||||||
if gp.useSvc {
|
if gp.useSvc {
|
||||||
svcName := fmt.Sprintf("svc-%v", m.Name)
|
svcName := fmt.Sprintf("svc-%v", m.Name)
|
||||||
if len(m.Uid) > 0 {
|
if len(m.Uid) > 0 {
|
||||||
svcName += ("-" + m.Uid)
|
svcName += "-" + m.Uid
|
||||||
}
|
}
|
||||||
|
|
||||||
labels := gp.labelsForFunction(m)
|
labels := gp.labelsForFunction(m)
|
||||||
@@ -472,8 +472,7 @@ func (gp *GenericPool) GetFuncSvc(m *fission.Metadata) (*funcSvc, error) {
|
|||||||
err, existingFsvc := gp.fsCache.Add(*fsvc)
|
err, existingFsvc := gp.fsCache.Add(*fsvc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Some other thread beat us to it -- return the other thread's fsvc and clean up
|
// Some other thread beat us to it -- return the other thread's fsvc and clean up
|
||||||
// our own. TODO: this is grossly inefficient, improve it with some sort of state
|
// our own.
|
||||||
// machine
|
|
||||||
log.Printf("func svc already exists: %v", existingFsvc.podName)
|
log.Printf("func svc already exists: %v", existingFsvc.podName)
|
||||||
go func() {
|
go func() {
|
||||||
gp.kubernetesClient.Core().Pods(gp.namespace).Delete(fsvc.podName, nil)
|
gp.kubernetesClient.Core().Pods(gp.namespace).Delete(fsvc.podName, nil)
|
||||||
|
|||||||
@@ -45,9 +45,12 @@ func (fmap *functionServiceMap) lookup(f *fission.Metadata) (*url.URL, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (fmap *functionServiceMap) assign(f *fission.Metadata, serviceUrl *url.URL) {
|
func (fmap *functionServiceMap) assign(f *fission.Metadata, serviceUrl *url.URL) {
|
||||||
err, _ := fmap.cache.Set(*f, serviceUrl)
|
err, old := fmap.cache.Set(*f, serviceUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error caching service url for function: %v", err)
|
if *serviceUrl == *(old.(*url.URL)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("error caching service url for function with a different value: %v", err)
|
||||||
// ignore error
|
// ignore error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user