implement Scylla database

This commit is contained in:
2025-05-17 21:45:18 -04:00
parent 252b49ae6a
commit f8a550883d
199 changed files with 71243 additions and 424 deletions

22
vendor/github.com/hailocab/go-hostpool/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,22 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe

0
vendor/github.com/hailocab/go-hostpool/.travis.yml generated vendored Normal file
View File

21
vendor/github.com/hailocab/go-hostpool/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Bitly
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

17
vendor/github.com/hailocab/go-hostpool/README.md generated vendored Normal file
View File

@@ -0,0 +1,17 @@
go-hostpool
===========
A Go package to intelligently and flexibly pool among multiple hosts from your Go application.
Host selection can operate in round robin or epsilon greedy mode, and unresponsive hosts are
avoided.
Usage example:
```go
hp := hostpool.NewEpsilonGreedy([]string{"a", "b"}, 0, &hostpool.LinearEpsilonValueCalculator{})
hostResponse := hp.Get()
hostname := hostResponse.Host()
err := _ // (make a request with hostname)
hostResponse.Mark(err)
```
View more detailed documentation on [godoc.org](http://godoc.org/github.com/bitly/go-hostpool)

View File

@@ -0,0 +1,220 @@
package hostpool
import (
"log"
"math/rand"
"time"
)
type epsilonHostPoolResponse struct {
standardHostPoolResponse
started time.Time
ended time.Time
}
func (r *epsilonHostPoolResponse) Mark(err error) {
r.Do(func() {
r.ended = time.Now()
doMark(err, r)
})
}
type epsilonGreedyHostPool struct {
standardHostPool // TODO - would be nifty if we could embed HostPool and Locker interfaces
epsilon float32 // this is our exploration factor
decayDuration time.Duration
EpsilonValueCalculator // embed the epsilonValueCalculator
timer
quit chan bool
}
// Construct an Epsilon Greedy HostPool
//
// Epsilon Greedy is an algorithm that allows HostPool not only to track failure state,
// but also to learn about "better" options in terms of speed, and to pick from available hosts
// based on how well they perform. This gives a weighted request rate to better
// performing hosts, while still distributing requests to all hosts (proportionate to their performance).
// The interface is the same as the standard HostPool, but be sure to mark the HostResponse immediately
// after executing the request to the host, as that will stop the implicitly running request timer.
//
// A good overview of Epsilon Greedy is here http://stevehanov.ca/blog/index.php?id=132
//
// To compute the weighting scores, we perform a weighted average of recent response times, over the course of
// `decayDuration`. decayDuration may be set to 0 to use the default value of 5 minutes
// We then use the supplied EpsilonValueCalculator to calculate a score from that weighted average response time.
func NewEpsilonGreedy(hosts []string, decayDuration time.Duration, calc EpsilonValueCalculator) HostPool {
if decayDuration <= 0 {
decayDuration = defaultDecayDuration
}
stdHP := New(hosts).(*standardHostPool)
p := &epsilonGreedyHostPool{
standardHostPool: *stdHP,
epsilon: float32(initialEpsilon),
decayDuration: decayDuration,
EpsilonValueCalculator: calc,
timer: &realTimer{},
quit: make(chan bool),
}
// allocate structures
for _, h := range p.hostList {
h.epsilonCounts = make([]int64, epsilonBuckets)
h.epsilonValues = make([]int64, epsilonBuckets)
}
go p.epsilonGreedyDecay()
return p
}
func (p *epsilonGreedyHostPool) Close() {
// No need to do p.quit <- true as close(p.quit) does the trick.
close(p.quit)
}
func (p *epsilonGreedyHostPool) SetEpsilon(newEpsilon float32) {
p.Lock()
defer p.Unlock()
p.epsilon = newEpsilon
}
func (p *epsilonGreedyHostPool) SetHosts(hosts []string) {
p.Lock()
defer p.Unlock()
p.standardHostPool.setHosts(hosts)
for _, h := range p.hostList {
h.epsilonCounts = make([]int64, epsilonBuckets)
h.epsilonValues = make([]int64, epsilonBuckets)
}
}
func (p *epsilonGreedyHostPool) epsilonGreedyDecay() {
durationPerBucket := p.decayDuration / epsilonBuckets
ticker := time.NewTicker(durationPerBucket)
for {
select {
case <-p.quit:
ticker.Stop()
return
case <-ticker.C:
p.performEpsilonGreedyDecay()
}
}
}
func (p *epsilonGreedyHostPool) performEpsilonGreedyDecay() {
p.Lock()
for _, h := range p.hostList {
h.epsilonIndex += 1
h.epsilonIndex = h.epsilonIndex % epsilonBuckets
h.epsilonCounts[h.epsilonIndex] = 0
h.epsilonValues[h.epsilonIndex] = 0
}
p.Unlock()
}
func (p *epsilonGreedyHostPool) Get() HostPoolResponse {
p.Lock()
defer p.Unlock()
host := p.getEpsilonGreedy()
if host == "" {
return nil
}
started := time.Now()
return &epsilonHostPoolResponse{
standardHostPoolResponse: standardHostPoolResponse{host: host, pool: p},
started: started,
}
}
func (p *epsilonGreedyHostPool) getEpsilonGreedy() string {
var hostToUse *hostEntry
// this is our exploration phase
if rand.Float32() < p.epsilon {
p.epsilon = p.epsilon * epsilonDecay
if p.epsilon < minEpsilon {
p.epsilon = minEpsilon
}
return p.getRoundRobin()
}
// calculate values for each host in the 0..1 range (but not ormalized)
var possibleHosts []*hostEntry
now := time.Now()
var sumValues float64
for _, h := range p.hostList {
if h.canTryHost(now) {
v := h.getWeightedAverageResponseTime()
if v > 0 {
ev := p.CalcValueFromAvgResponseTime(v)
h.epsilonValue = ev
sumValues += ev
possibleHosts = append(possibleHosts, h)
}
}
}
if len(possibleHosts) != 0 {
// now normalize to the 0..1 range to get a percentage
for _, h := range possibleHosts {
h.epsilonPercentage = h.epsilonValue / sumValues
}
// do a weighted random choice among hosts
ceiling := 0.0
pickPercentage := rand.Float64()
for _, h := range possibleHosts {
ceiling += h.epsilonPercentage
if pickPercentage <= ceiling {
hostToUse = h
break
}
}
}
if hostToUse == nil {
if len(possibleHosts) != 0 {
log.Println("Failed to randomly choose a host, Dan loses")
}
return p.getRoundRobin()
}
if hostToUse.dead {
hostToUse.willRetryHost(p.maxRetryInterval)
}
return hostToUse.host
}
func (p *epsilonGreedyHostPool) markSuccess(hostR HostPoolResponse) {
// first do the base markSuccess - a little redundant with host lookup but cleaner than repeating logic
p.standardHostPool.markSuccess(hostR)
eHostR, ok := hostR.(*epsilonHostPoolResponse)
if !ok {
log.Printf("Incorrect type in eps markSuccess!") // TODO reflection to print out offending type
return
}
host := eHostR.host
duration := p.between(eHostR.started, eHostR.ended)
p.Lock()
defer p.Unlock()
h, ok := p.hosts[host]
if !ok {
log.Fatalf("host %s not in HostPool %v", host, p.Hosts())
}
h.epsilonCounts[h.epsilonIndex]++
h.epsilonValues[h.epsilonIndex] += int64(duration.Seconds() * 1000)
}
// --- timer: this just exists for testing
type timer interface {
between(time.Time, time.Time) time.Duration
}
type realTimer struct{}
func (rt *realTimer) between(start time.Time, end time.Time) time.Duration {
return end.Sub(start)
}

View File

@@ -0,0 +1,40 @@
package hostpool
// --- Value Calculators -----------------
import (
"math"
)
// --- Definitions -----------------------
// Structs implementing this interface are used to convert the average response time for a host
// into a score that can be used to weight hosts in the epsilon greedy hostpool. Lower response
// times should yield higher scores (we want to select the faster hosts more often) The default
// LinearEpsilonValueCalculator just uses the reciprocal of the response time. In practice, any
// decreasing function from the positive reals to the positive reals should work.
type EpsilonValueCalculator interface {
CalcValueFromAvgResponseTime(float64) float64
}
type LinearEpsilonValueCalculator struct{}
type LogEpsilonValueCalculator struct{ LinearEpsilonValueCalculator }
type PolynomialEpsilonValueCalculator struct {
LinearEpsilonValueCalculator
Exp float64 // the exponent to which we will raise the value to reweight
}
// -------- Methods -----------------------
func (c *LinearEpsilonValueCalculator) CalcValueFromAvgResponseTime(v float64) float64 {
return 1.0 / v
}
func (c *LogEpsilonValueCalculator) CalcValueFromAvgResponseTime(v float64) float64 {
// we need to add 1 to v so that this will be defined on all positive floats
return c.LinearEpsilonValueCalculator.CalcValueFromAvgResponseTime(math.Log(v + 1.0))
}
func (c *PolynomialEpsilonValueCalculator) CalcValueFromAvgResponseTime(v float64) float64 {
return c.LinearEpsilonValueCalculator.CalcValueFromAvgResponseTime(math.Pow(v, c.Exp))
}

62
vendor/github.com/hailocab/go-hostpool/host_entry.go generated vendored Normal file
View File

@@ -0,0 +1,62 @@
package hostpool
import (
"time"
)
// --- hostEntry - this is due to get upgraded
type hostEntry struct {
host string
nextRetry time.Time
retryCount int16
retryDelay time.Duration
dead bool
epsilonCounts []int64
epsilonValues []int64
epsilonIndex int
epsilonValue float64
epsilonPercentage float64
}
func (h *hostEntry) canTryHost(now time.Time) bool {
if !h.dead {
return true
}
if h.nextRetry.Before(now) {
return true
}
return false
}
func (h *hostEntry) willRetryHost(maxRetryInterval time.Duration) {
h.retryCount += 1
newDelay := h.retryDelay * 2
if newDelay < maxRetryInterval {
h.retryDelay = newDelay
} else {
h.retryDelay = maxRetryInterval
}
h.nextRetry = time.Now().Add(h.retryDelay)
}
func (h *hostEntry) getWeightedAverageResponseTime() float64 {
var value float64
var lastValue float64
// start at 1 so we start with the oldest entry
for i := 1; i <= epsilonBuckets; i += 1 {
pos := (h.epsilonIndex + i) % epsilonBuckets
bucketCount := h.epsilonCounts[pos]
// Changing the line below to what I think it should be to get the weights right
weight := float64(i) / float64(epsilonBuckets)
if bucketCount > 0 {
currentValue := float64(h.epsilonValues[pos]) / float64(bucketCount)
value += currentValue * weight
lastValue = currentValue
} else {
value += lastValue * weight
}
}
return value
}

243
vendor/github.com/hailocab/go-hostpool/hostpool.go generated vendored Normal file
View File

@@ -0,0 +1,243 @@
// A Go package to intelligently and flexibly pool among multiple hosts from your Go application.
// Host selection can operate in round robin or epsilon greedy mode, and unresponsive hosts are
// avoided. A good overview of Epsilon Greedy is here http://stevehanov.ca/blog/index.php?id=132
package hostpool
import (
"log"
"sync"
"time"
)
// Returns current version
func Version() string {
return "0.1"
}
// --- Response interfaces and structs ----
// This interface represents the response from HostPool. You can retrieve the
// hostname by calling Host(), and after making a request to the host you should
// call Mark with any error encountered, which will inform the HostPool issuing
// the HostPoolResponse of what happened to the request and allow it to update.
type HostPoolResponse interface {
Host() string
Mark(error)
hostPool() HostPool
}
type standardHostPoolResponse struct {
host string
sync.Once
pool HostPool
}
// --- HostPool structs and interfaces ----
// This is the main HostPool interface. Structs implementing this interface
// allow you to Get a HostPoolResponse (which includes a hostname to use),
// get the list of all Hosts, and use ResetAll to reset state.
type HostPool interface {
Get() HostPoolResponse
// keep the marks separate so we can override independently
markSuccess(HostPoolResponse)
markFailed(HostPoolResponse)
ResetAll()
// ReturnUnhealthy when called with true will prevent an unhealthy node from
// being returned and will instead return a nil HostPoolResponse. If using
// this feature then you should check the result of Get for nil
ReturnUnhealthy(v bool)
Hosts() []string
SetHosts([]string)
// Close the hostpool and release all resources.
Close()
}
type standardHostPool struct {
sync.RWMutex
hosts map[string]*hostEntry
hostList []*hostEntry
returnUnhealthy bool
initialRetryDelay time.Duration
maxRetryInterval time.Duration
nextHostIndex int
}
// ------ constants -------------------
const epsilonBuckets = 120
const epsilonDecay = 0.90 // decay the exploration rate
const minEpsilon = 0.01 // explore one percent of the time
const initialEpsilon = 0.3
const defaultDecayDuration = time.Duration(5) * time.Minute
// Construct a basic HostPool using the hostnames provided
func New(hosts []string) HostPool {
p := &standardHostPool{
returnUnhealthy: true,
hosts: make(map[string]*hostEntry, len(hosts)),
hostList: make([]*hostEntry, len(hosts)),
initialRetryDelay: time.Duration(30) * time.Second,
maxRetryInterval: time.Duration(900) * time.Second,
}
for i, h := range hosts {
e := &hostEntry{
host: h,
retryDelay: p.initialRetryDelay,
}
p.hosts[h] = e
p.hostList[i] = e
}
return p
}
func (r *standardHostPoolResponse) Host() string {
return r.host
}
func (r *standardHostPoolResponse) hostPool() HostPool {
return r.pool
}
func (r *standardHostPoolResponse) Mark(err error) {
r.Do(func() {
doMark(err, r)
})
}
func doMark(err error, r HostPoolResponse) {
if err == nil {
r.hostPool().markSuccess(r)
} else {
r.hostPool().markFailed(r)
}
}
// return an entry from the HostPool
func (p *standardHostPool) Get() HostPoolResponse {
p.Lock()
defer p.Unlock()
host := p.getRoundRobin()
if host == "" {
return nil
}
return &standardHostPoolResponse{host: host, pool: p}
}
func (p *standardHostPool) getRoundRobin() string {
now := time.Now()
hostCount := len(p.hostList)
for i := range p.hostList {
// iterate via sequenece from where we last iterated
currentIndex := (i + p.nextHostIndex) % hostCount
h := p.hostList[currentIndex]
if !h.dead {
p.nextHostIndex = currentIndex + 1
return h.host
}
if h.nextRetry.Before(now) {
h.willRetryHost(p.maxRetryInterval)
p.nextHostIndex = currentIndex + 1
return h.host
}
}
// all hosts are down and returnUnhealhy is false then return no host
if !p.returnUnhealthy {
return ""
}
// all hosts are down. re-add them
p.doResetAll()
p.nextHostIndex = 0
return p.hostList[0].host
}
func (p *standardHostPool) ResetAll() {
p.Lock()
defer p.Unlock()
p.doResetAll()
}
func (p *standardHostPool) SetHosts(hosts []string) {
p.Lock()
defer p.Unlock()
p.setHosts(hosts)
}
func (p *standardHostPool) ReturnUnhealthy(v bool) {
p.Lock()
defer p.Unlock()
p.returnUnhealthy = v
}
func (p *standardHostPool) setHosts(hosts []string) {
p.hosts = make(map[string]*hostEntry, len(hosts))
p.hostList = make([]*hostEntry, len(hosts))
for i, h := range hosts {
e := &hostEntry{
host: h,
retryDelay: p.initialRetryDelay,
}
p.hosts[h] = e
p.hostList[i] = e
}
}
// this actually performs the logic to reset,
// and should only be called when the lock has
// already been acquired
func (p *standardHostPool) doResetAll() {
for _, h := range p.hosts {
h.dead = false
}
}
func (p *standardHostPool) Close() {
for _, h := range p.hosts {
h.dead = true
}
}
func (p *standardHostPool) markSuccess(hostR HostPoolResponse) {
host := hostR.Host()
p.Lock()
defer p.Unlock()
h, ok := p.hosts[host]
if !ok {
log.Fatalf("host %s not in HostPool %v", host, p.Hosts())
}
h.dead = false
}
func (p *standardHostPool) markFailed(hostR HostPoolResponse) {
host := hostR.Host()
p.Lock()
defer p.Unlock()
h, ok := p.hosts[host]
if !ok {
log.Fatalf("host %s not in HostPool %v", host, p.Hosts())
}
if !h.dead {
h.dead = true
h.retryCount = 0
h.retryDelay = p.initialRetryDelay
h.nextRetry = time.Now().Add(h.retryDelay)
}
}
func (p *standardHostPool) Hosts() []string {
hosts := make([]string, 0, len(p.hosts))
for host := range p.hosts {
hosts = append(hosts, host)
}
return hosts
}