closes #403 Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org> Reviewed-on: #407 Co-authored-by: Vasiliy Tolstov <v.tolstov@unistack.org> Co-committed-by: Vasiliy Tolstov <v.tolstov@unistack.org>
		
			
				
	
	
		
			114 lines
		
	
	
		
			2.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			114 lines
		
	
	
		
			2.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package sql
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"math"
 | |
| 	"time"
 | |
| 
 | |
| 	"golang.yandex/hasql/v2"
 | |
| )
 | |
| 
 | |
| // compile time guard
 | |
| var _ hasql.NodePicker[Querier] = (*CustomPicker[Querier])(nil)
 | |
| 
 | |
| // CustomPickerOptions holds options to pick nodes
 | |
| type CustomPickerOptions struct {
 | |
| 	MaxLag   int
 | |
| 	Priority map[string]int32
 | |
| 	Retries  int
 | |
| }
 | |
| 
 | |
| // CustomPickerOption func apply option to CustomPickerOptions
 | |
| type CustomPickerOption func(*CustomPickerOptions)
 | |
| 
 | |
| // CustomPickerMaxLag specifies max lag for which node can be used
 | |
| func CustomPickerMaxLag(n int) CustomPickerOption {
 | |
| 	return func(o *CustomPickerOptions) {
 | |
| 		o.MaxLag = n
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // NewCustomPicker creates new node picker
 | |
| func NewCustomPicker[T Querier](opts ...CustomPickerOption) *CustomPicker[Querier] {
 | |
| 	options := CustomPickerOptions{}
 | |
| 	for _, o := range opts {
 | |
| 		o(&options)
 | |
| 	}
 | |
| 	return &CustomPicker[Querier]{opts: options}
 | |
| }
 | |
| 
 | |
| // CustomPicker holds node picker options
 | |
| type CustomPicker[T Querier] struct {
 | |
| 	opts CustomPickerOptions
 | |
| }
 | |
| 
 | |
| // PickNode used to return specific node
 | |
| func (p *CustomPicker[T]) PickNode(cnodes []hasql.CheckedNode[T]) hasql.CheckedNode[T] {
 | |
| 	for _, n := range cnodes {
 | |
| 		fmt.Printf("node %s\n", n.Node.String())
 | |
| 	}
 | |
| 	return cnodes[0]
 | |
| }
 | |
| 
 | |
| func (p *CustomPicker[T]) getPriority(nodeName string) int32 {
 | |
| 	if prio, ok := p.opts.Priority[nodeName]; ok {
 | |
| 		return prio
 | |
| 	}
 | |
| 	return math.MaxInt32 // Default to lowest priority
 | |
| }
 | |
| 
 | |
| // CompareNodes used to sort nodes
 | |
| func (p *CustomPicker[T]) CompareNodes(a, b hasql.CheckedNode[T]) int {
 | |
| 	// Get replication lag values
 | |
| 	aLag := a.Info.(interface{ ReplicationLag() int }).ReplicationLag()
 | |
| 	bLag := b.Info.(interface{ ReplicationLag() int }).ReplicationLag()
 | |
| 
 | |
| 	// First check that lag lower then MaxLag
 | |
| 	if aLag > p.opts.MaxLag && bLag > p.opts.MaxLag {
 | |
| 		return 0 // both are equal
 | |
| 	}
 | |
| 
 | |
| 	// If one node exceeds MaxLag and the other doesn't, prefer the one that doesn't
 | |
| 	if aLag > p.opts.MaxLag {
 | |
| 		return 1 // b is better
 | |
| 	}
 | |
| 	if bLag > p.opts.MaxLag {
 | |
| 		return -1 // a is better
 | |
| 	}
 | |
| 
 | |
| 	// Get node priorities
 | |
| 	aPrio := p.getPriority(a.Node.String())
 | |
| 	bPrio := p.getPriority(b.Node.String())
 | |
| 
 | |
| 	// if both priority equals
 | |
| 	if aPrio == bPrio {
 | |
| 		// First compare by replication lag
 | |
| 		if aLag < bLag {
 | |
| 			return -1
 | |
| 		}
 | |
| 		if aLag > bLag {
 | |
| 			return 1
 | |
| 		}
 | |
| 		// If replication lag is equal, compare by latency
 | |
| 		aLatency := a.Info.(interface{ Latency() time.Duration }).Latency()
 | |
| 		bLatency := b.Info.(interface{ Latency() time.Duration }).Latency()
 | |
| 
 | |
| 		if aLatency < bLatency {
 | |
| 			return -1
 | |
| 		}
 | |
| 		if aLatency > bLatency {
 | |
| 			return 1
 | |
| 		}
 | |
| 
 | |
| 		// If lag and latency is equal
 | |
| 		return 0
 | |
| 	}
 | |
| 
 | |
| 	// If priorities are different, prefer the node with lower priority value
 | |
| 	if aPrio < bPrio {
 | |
| 		return -1
 | |
| 	}
 | |
| 
 | |
| 	return 1
 | |
| }
 |