diff --git a/.github/actions/run-tests/action.yml b/.github/actions/run-tests/action.yml index a90d4605..b8e576e7 100644 --- a/.github/actions/run-tests/action.yml +++ b/.github/actions/run-tests/action.yml @@ -25,7 +25,7 @@ runs: # Mapping of redis version to redis testing containers declare -A redis_version_mapping=( - ["8.2.x"]="8.2" + ["8.2.x"]="8.2.1-pre" ["8.0.x"]="8.0.2" ["7.4.x"]="rs-7.4.0-v5" ["7.2.x"]="rs-7.2.0-v17" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cb011248..05f84ef6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,7 +32,7 @@ jobs: go-version: ${{ matrix.go-version }} - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Test environment env: @@ -44,7 +44,7 @@ jobs: # Mapping of redis version to redis testing containers declare -A redis_version_mapping=( - ["8.2.x"]="8.2" + ["8.2.x"]="8.2.1-pre" ["8.0.x"]="8.0.2" ["7.4.x"]="rs-7.4.0-v5" ) @@ -84,7 +84,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Run tests uses: ./.github/actions/run-tests diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 1a803d37..0a62809e 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -35,7 +35,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/doctests.yaml b/.github/workflows/doctests.yaml index 321654fa..ba072454 100644 --- a/.github/workflows/doctests.yaml +++ b/.github/workflows/doctests.yaml @@ -36,7 +36,7 @@ jobs: go-version: ${{ matrix.go-version }} - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Test doc examples working-directory: ./doctests diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index def3eb79..62552abf 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -20,7 +20,7 @@ jobs: name: lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: golangci-lint uses: golangci/golangci-lint-action@v8.0.0 with: diff --git a/.github/workflows/spellcheck.yml b/.github/workflows/spellcheck.yml index 81e73cd4..9daecbc5 100644 --- a/.github/workflows/spellcheck.yml +++ b/.github/workflows/spellcheck.yml @@ -6,7 +6,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Check Spelling uses: rojopolis/spellcheck-github-actions@0.51.0 with: diff --git a/.github/workflows/test-redis-enterprise.yml b/.github/workflows/test-redis-enterprise.yml index 47de6478..a51c9e8c 100644 --- a/.github/workflows/test-redis-enterprise.yml +++ b/.github/workflows/test-redis-enterprise.yml @@ -20,10 +20,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Clone Redis EE docker repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: RedisLabs/redis-ee-docker path: redis-ee diff --git a/README.md b/README.md index f4e73da0..3c6bf743 100644 --- a/README.md +++ b/README.md @@ -301,7 +301,7 @@ func main() { ### Buffer Size Configuration -go-redis uses 0.5MiB read and write buffers by default for optimal performance. For high-throughput applications or large pipelines, you can customize buffer sizes: +go-redis uses 32KiB read and write buffers by default for optimal performance. For high-throughput applications or large pipelines, you can customize buffer sizes: ```go rdb := redis.NewClient(&redis.Options{ @@ -376,7 +376,7 @@ You can find further details in the [query dialect documentation](https://redis. #### Custom buffer sizes Prior to v9.12, the buffer size was the default go value of 4096 bytes. Starting from v9.12, -go-redis uses 256KiB read and write buffers by default for optimal performance. +go-redis uses 32KiB read and write buffers by default for optimal performance. For high-throughput applications or large pipelines, you can customize buffer sizes: ```go diff --git a/commands_test.go b/commands_test.go index c6748316..7e0cdc37 100644 --- a/commands_test.go +++ b/commands_test.go @@ -2412,6 +2412,77 @@ var _ = Describe("Commands", func() { Expect(args).To(Equal(expectedArgs)) }) + + It("should IncrByFloat with edge cases", func() { + // Test with negative increment + set := client.Set(ctx, "key", "10.5", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + + incrByFloat := client.IncrByFloat(ctx, "key", -2.3) + Expect(incrByFloat.Err()).NotTo(HaveOccurred()) + Expect(incrByFloat.Val()).To(BeNumerically("~", 8.2, 0.0001)) + + // Test with zero increment (should return current value) + incrByFloat = client.IncrByFloat(ctx, "key", 0.0) + Expect(incrByFloat.Err()).NotTo(HaveOccurred()) + Expect(incrByFloat.Val()).To(BeNumerically("~", 8.2, 0.0001)) + + // Test with very small increment (precision test) + incrByFloat = client.IncrByFloat(ctx, "key", 0.0001) + Expect(incrByFloat.Err()).NotTo(HaveOccurred()) + Expect(incrByFloat.Val()).To(BeNumerically("~", 8.2001, 0.00001)) + + // Test with non-existent key (should start from 0) + incrByFloat = client.IncrByFloat(ctx, "nonexistent", 5.5) + Expect(incrByFloat.Err()).NotTo(HaveOccurred()) + Expect(incrByFloat.Val()).To(Equal(5.5)) + + // Test with integer value stored as string + set = client.Set(ctx, "intkey", "42", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + + incrByFloat = client.IncrByFloat(ctx, "intkey", 0.5) + Expect(incrByFloat.Err()).NotTo(HaveOccurred()) + Expect(incrByFloat.Val()).To(Equal(42.5)) + + // Test with scientific notation + set = client.Set(ctx, "scikey", "1.5e2", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + + incrByFloat = client.IncrByFloat(ctx, "scikey", 5.0) + Expect(incrByFloat.Err()).NotTo(HaveOccurred()) + Expect(incrByFloat.Val()).To(Equal(155.0)) + + // Test with negative scientific notation + incrByFloat = client.IncrByFloat(ctx, "scikey", -1.5e1) + Expect(incrByFloat.Err()).NotTo(HaveOccurred()) + Expect(incrByFloat.Val()).To(Equal(140.0)) + + // Test error case: non-numeric value + set = client.Set(ctx, "stringkey", "notanumber", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + + incrByFloat = client.IncrByFloat(ctx, "stringkey", 1.0) + Expect(incrByFloat.Err()).To(HaveOccurred()) + Expect(incrByFloat.Err().Error()).To(ContainSubstring("value is not a valid float")) + + // Test with very large numbers + set = client.Set(ctx, "largekey", "1.7976931348623157e+308", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + + // This should work as it's within float64 range + incrByFloat = client.IncrByFloat(ctx, "largekey", -1.0e+308) + Expect(incrByFloat.Err()).NotTo(HaveOccurred()) + Expect(incrByFloat.Val()).To(BeNumerically("~", 7.976931348623157e+307, 1e+300)) + + // Test with very small numbers (near zero) + set = client.Set(ctx, "smallkey", "1e-10", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + + incrByFloat = client.IncrByFloat(ctx, "smallkey", 1e-10) + Expect(incrByFloat.Err()).NotTo(HaveOccurred()) + Expect(incrByFloat.Val()).To(BeNumerically("~", 2e-10, 1e-15)) + }) }) Describe("hashes", func() { diff --git a/internal/pool/buffer_size_test.go b/internal/pool/buffer_size_test.go index f2e4b30c..7f4bd37e 100644 --- a/internal/pool/buffer_size_test.go +++ b/internal/pool/buffer_size_test.go @@ -34,12 +34,12 @@ var _ = Describe("Buffer Size Configuration", func() { Expect(err).NotTo(HaveOccurred()) defer connPool.CloseConn(cn) - // Check that default buffer sizes are used (256KiB) + // Check that default buffer sizes are used (32KiB) writerBufSize := getWriterBufSizeUnsafe(cn) readerBufSize := getReaderBufSizeUnsafe(cn) - Expect(writerBufSize).To(Equal(proto.DefaultBufferSize)) // Default 256KiB buffer size - Expect(readerBufSize).To(Equal(proto.DefaultBufferSize)) // Default 256KiB buffer size + Expect(writerBufSize).To(Equal(proto.DefaultBufferSize)) // Default 32KiB buffer size + Expect(readerBufSize).To(Equal(proto.DefaultBufferSize)) // Default 32KiB buffer size }) It("should use custom buffer sizes when specified", func() { @@ -79,16 +79,16 @@ var _ = Describe("Buffer Size Configuration", func() { Expect(err).NotTo(HaveOccurred()) defer connPool.CloseConn(cn) - // Check that default buffer sizes are used (256KiB) + // Check that default buffer sizes are used (32KiB) writerBufSize := getWriterBufSizeUnsafe(cn) readerBufSize := getReaderBufSizeUnsafe(cn) - Expect(writerBufSize).To(Equal(proto.DefaultBufferSize)) // Default 256KiB buffer size - Expect(readerBufSize).To(Equal(proto.DefaultBufferSize)) // Default 256KiB buffer size + Expect(writerBufSize).To(Equal(proto.DefaultBufferSize)) // Default 32KiB buffer size + Expect(readerBufSize).To(Equal(proto.DefaultBufferSize)) // Default 32KiB buffer size }) - It("should use 256KiB default buffer sizes for standalone NewConn", func() { - // Test that NewConn (without pool) also uses 256KiB buffers + It("should use 32KiB default buffer sizes for standalone NewConn", func() { + // Test that NewConn (without pool) also uses 32KiB buffers netConn := newDummyConn() cn := pool.NewConn(netConn) defer cn.Close() @@ -96,11 +96,11 @@ var _ = Describe("Buffer Size Configuration", func() { writerBufSize := getWriterBufSizeUnsafe(cn) readerBufSize := getReaderBufSizeUnsafe(cn) - Expect(writerBufSize).To(Equal(proto.DefaultBufferSize)) // Default 256KiB buffer size - Expect(readerBufSize).To(Equal(proto.DefaultBufferSize)) // Default 256KiB buffer size + Expect(writerBufSize).To(Equal(proto.DefaultBufferSize)) // Default 32KiB buffer size + Expect(readerBufSize).To(Equal(proto.DefaultBufferSize)) // Default 32KiB buffer size }) - It("should use 256KiB defaults even when pool is created directly without buffer sizes", func() { + It("should use 32KiB defaults even when pool is created directly without buffer sizes", func() { // Test the scenario where someone creates a pool directly (like in tests) // without setting ReadBufferSize and WriteBufferSize connPool = pool.NewConnPool(&pool.Options{ @@ -114,12 +114,12 @@ var _ = Describe("Buffer Size Configuration", func() { Expect(err).NotTo(HaveOccurred()) defer connPool.CloseConn(cn) - // Should still get 256KiB defaults because NewConnPool sets them + // Should still get 32KiB defaults because NewConnPool sets them writerBufSize := getWriterBufSizeUnsafe(cn) readerBufSize := getReaderBufSizeUnsafe(cn) - Expect(writerBufSize).To(Equal(proto.DefaultBufferSize)) // Default 256KiB buffer size - Expect(readerBufSize).To(Equal(proto.DefaultBufferSize)) // Default 256KiB buffer size + Expect(writerBufSize).To(Equal(proto.DefaultBufferSize)) // Default 32KiB buffer size + Expect(readerBufSize).To(Equal(proto.DefaultBufferSize)) // Default 32KiB buffer size }) }) diff --git a/internal/pool/conn.go b/internal/pool/conn.go index edef9e67..8fcdfa67 100644 --- a/internal/pool/conn.go +++ b/internal/pool/conn.go @@ -37,11 +37,11 @@ func NewConnWithBufferSize(netConn net.Conn, readBufSize, writeBufSize int) *Con createdAt: time.Now(), } - // Use specified buffer sizes, or fall back to 0.5MiB defaults if 0 + // Use specified buffer sizes, or fall back to 32KiB defaults if 0 if readBufSize > 0 { cn.rd = proto.NewReaderSize(netConn, readBufSize) } else { - cn.rd = proto.NewReader(netConn) // Uses 0.5MiB default + cn.rd = proto.NewReader(netConn) // Uses 32KiB default } if writeBufSize > 0 { diff --git a/internal/proto/reader.go b/internal/proto/reader.go index c283ae3e..4e60569d 100644 --- a/internal/proto/reader.go +++ b/internal/proto/reader.go @@ -12,8 +12,8 @@ import ( "github.com/redis/go-redis/v9/internal/util" ) -// DefaultBufferSize is the default size for read/write buffers (256 KiB). -const DefaultBufferSize = 256 * 1024 +// DefaultBufferSize is the default size for read/write buffers (32 KiB). +const DefaultBufferSize = 32 * 1024 // redis resp protocol data type. const ( diff --git a/options.go b/options.go index a60519c9..237be6be 100644 --- a/options.go +++ b/options.go @@ -136,14 +136,14 @@ type Options struct { // Larger buffers can improve performance for commands that return large responses. // Smaller buffers can improve memory usage for larger pools. // - // default: 256KiB (262144 bytes) + // default: 32KiB (32768 bytes) ReadBufferSize int // WriteBufferSize is the size of the bufio.Writer buffer for each connection. // Larger buffers can improve performance for large pipelines and commands with many arguments. // Smaller buffers can improve memory usage for larger pools. // - // default: 256KiB (262144 bytes) + // default: 32KiB (32768 bytes) WriteBufferSize int // PoolFIFO type of connection pool. diff --git a/osscluster.go b/osscluster.go index 0453bdd1..ec77a95c 100644 --- a/osscluster.go +++ b/osscluster.go @@ -96,14 +96,14 @@ type ClusterOptions struct { // Larger buffers can improve performance for commands that return large responses. // Smaller buffers can improve memory usage for larger pools. // - // default: 256KiB (262144 bytes) + // default: 32KiB (32768 bytes) ReadBufferSize int // WriteBufferSize is the size of the bufio.Writer buffer for each connection. // Larger buffers can improve performance for large pipelines and commands with many arguments. // Smaller buffers can improve memory usage for larger pools. // - // default: 256KiB (262144 bytes) + // default: 32KiB (32768 bytes) WriteBufferSize int TLSConfig *tls.Config diff --git a/ring.go b/ring.go index 6c740310..3381460a 100644 --- a/ring.go +++ b/ring.go @@ -128,14 +128,14 @@ type RingOptions struct { // Larger buffers can improve performance for commands that return large responses. // Smaller buffers can improve memory usage for larger pools. // - // default: 256KiB (262144 bytes) + // default: 32KiB (32768 bytes) ReadBufferSize int // WriteBufferSize is the size of the bufio.Writer buffer for each connection. // Larger buffers can improve performance for large pipelines and commands with many arguments. // Smaller buffers can improve memory usage for larger pools. // - // default: 256KiB (262144 bytes) + // default: 32KiB (32768 bytes) WriteBufferSize int TLSConfig *tls.Config diff --git a/sentinel.go b/sentinel.go index 2aa61a7e..2509d70f 100644 --- a/sentinel.go +++ b/sentinel.go @@ -97,14 +97,14 @@ type FailoverOptions struct { // Larger buffers can improve performance for commands that return large responses. // Smaller buffers can improve memory usage for larger pools. // - // default: 256KiB (262144 bytes) + // default: 32KiB (32768 bytes) ReadBufferSize int // WriteBufferSize is the size of the bufio.Writer buffer for each connection. // Larger buffers can improve performance for large pipelines and commands with many arguments. // Smaller buffers can improve memory usage for larger pools. // - // default: 256KiB (262144 bytes) + // default: 32KiB (32768 bytes) WriteBufferSize int PoolFIFO bool @@ -204,8 +204,9 @@ func (opt *FailoverOptions) sentinelOptions(addr string) *Options { MinRetryBackoff: opt.MinRetryBackoff, MaxRetryBackoff: opt.MaxRetryBackoff, - ReadBufferSize: opt.ReadBufferSize, - WriteBufferSize: opt.WriteBufferSize, + // The sentinel client uses a 4KiB read/write buffer size. + ReadBufferSize: 4096, + WriteBufferSize: 4096, DialTimeout: opt.DialTimeout, ReadTimeout: opt.ReadTimeout, diff --git a/universal.go b/universal.go index 9b150d7d..02da3be8 100644 --- a/universal.go +++ b/universal.go @@ -61,6 +61,20 @@ type UniversalOptions struct { WriteTimeout time.Duration ContextTimeoutEnabled bool + // ReadBufferSize is the size of the bufio.Reader buffer for each connection. + // Larger buffers can improve performance for commands that return large responses. + // Smaller buffers can improve memory usage for larger pools. + // + // default: 32KiB (32768 bytes) + ReadBufferSize int + + // WriteBufferSize is the size of the bufio.Writer buffer for each connection. + // Larger buffers can improve performance for large pipelines and commands with many arguments. + // Smaller buffers can improve memory usage for larger pools. + // + // default: 32KiB (32768 bytes) + WriteBufferSize int + // PoolFIFO uses FIFO mode for each node connection pool GET/PUT (default LIFO). PoolFIFO bool @@ -143,6 +157,9 @@ func (o *UniversalOptions) Cluster() *ClusterOptions { WriteTimeout: o.WriteTimeout, ContextTimeoutEnabled: o.ContextTimeoutEnabled, + ReadBufferSize: o.ReadBufferSize, + WriteBufferSize: o.WriteBufferSize, + PoolFIFO: o.PoolFIFO, PoolSize: o.PoolSize, @@ -200,6 +217,9 @@ func (o *UniversalOptions) Failover() *FailoverOptions { WriteTimeout: o.WriteTimeout, ContextTimeoutEnabled: o.ContextTimeoutEnabled, + ReadBufferSize: o.ReadBufferSize, + WriteBufferSize: o.WriteBufferSize, + PoolFIFO: o.PoolFIFO, PoolSize: o.PoolSize, PoolTimeout: o.PoolTimeout, @@ -250,6 +270,9 @@ func (o *UniversalOptions) Simple() *Options { WriteTimeout: o.WriteTimeout, ContextTimeoutEnabled: o.ContextTimeoutEnabled, + ReadBufferSize: o.ReadBufferSize, + WriteBufferSize: o.WriteBufferSize, + PoolFIFO: o.PoolFIFO, PoolSize: o.PoolSize, PoolTimeout: o.PoolTimeout,