diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index c1c09fdf..757d73d1 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -131,7 +131,7 @@ jobs: TEST_AZURITE_IMAGE: mcr.microsoft.com/azure-storage/azurite:latest TEST_AEROSPIKE_IMAGE: aerospike/aerospike-server:latest TEST_CLICKHOUSE_IMAGE: "clickhouse/clickhouse-server:23-alpine" - TEST_CASSANDRA_IMAGE: "cassandra:4.1.3" + TEST_CASSANDRA_IMAGE: "cassandra:latest" TEST_COUCHBASE_IMAGE: "couchbase:enterprise-7.6.5" TEST_DYNAMODB_IMAGE: amazon/dynamodb-local:latest TEST_MINIO_IMAGE: "docker.io/minio/minio:RELEASE.2024-08-17T01-24-54Z" diff --git a/.github/workflows/test-cassandra.yml b/.github/workflows/test-cassandra.yml index 06687007..e9d6dfdc 100644 --- a/.github/workflows/test-cassandra.yml +++ b/.github/workflows/test-cassandra.yml @@ -26,5 +26,5 @@ jobs: go-version: '${{ matrix.go-version }}' - name: Run Test env: - TEST_CASSANDRA_IMAGE: cassandra:4.1.3 + TEST_CASSANDRA_IMAGE: cassandra:latest run: cd ./cassandra && go clean -testcache && go test ./... -v -race diff --git a/cassandra/README.md b/cassandra/README.md index 1380aa52..b68699d7 100644 --- a/cassandra/README.md +++ b/cassandra/README.md @@ -49,7 +49,7 @@ necessary for the client to operate correctly. To start Cassandra using Docker, issue the following: ```bash -docker run --name cassandra --network host -d cassandra:tag +docker run --name cassandra --network host -d cassandra:latest ``` After running this command you're ready to start using the storage and connecting to the database. @@ -82,7 +82,7 @@ type Config struct { // Keyspace is the name of the Cassandra keyspace to use. Keyspace string // Optional. Default is kv_store - /// Table is the name of the Cassandra table to use. + // Table is the name of the Cassandra table to use. Table string // Optional. Default is Quorum // Consistency is the Cassandra consistency level. diff --git a/cassandra/cassandra_test.go b/cassandra/cassandra_test.go index 09109c59..a983db08 100644 --- a/cassandra/cassandra_test.go +++ b/cassandra/cassandra_test.go @@ -8,44 +8,48 @@ import ( "time" "github.com/gocql/gocql" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" cassandracontainer "github.com/testcontainers/testcontainers-go/modules/cassandra" ) const ( // cassandraImage is the default image used for running cassandra in tests. - cassandraImage = "cassandra:4.1.3" + cassandraImage = "cassandra:latest" cassandraImageEnvVar string = "TEST_CASSANDRA_IMAGE" cassandraPort = "9042/tcp" ) -// setupCassandraContainer creates a Cassandra container using the official module -func setupCassandraContainer(ctx context.Context) (*cassandracontainer.CassandraContainer, string, error) { +// newTestStore creates a Cassandra container using the official module +func newTestStore(t testing.TB) (*cassandracontainer.CassandraContainer, string, error) { + t.Helper() img := cassandraImage if imgFromEnv := os.Getenv(cassandraImageEnvVar); imgFromEnv != "" { img = imgFromEnv } - cassandraContainer, err := cassandracontainer.Run(ctx, img) + ctx := context.Background() + + c, err := cassandracontainer.Run(ctx, img) + testcontainers.CleanupContainer(t, c) if err != nil { return nil, "", err } // Get connection parameters - host, err := cassandraContainer.Host(ctx) + host, err := c.Host(ctx) if err != nil { return nil, "", err } - mappedPort, err := cassandraContainer.MappedPort(ctx, cassandraPort) + mappedPort, err := c.MappedPort(ctx, cassandraPort) if err != nil { return nil, "", err } connectionURL := host + ":" + mappedPort.Port() - return cassandraContainer, connectionURL, nil + return c, connectionURL, nil } // TestCassandraStorage tests the Cassandra storage implementation @@ -53,7 +57,7 @@ func TestCassandraStorage(t *testing.T) { ctx := context.Background() // Start Cassandra container - cassandraContainer, connectionURL, err := setupCassandraContainer(ctx) + cassandraContainer, connectionURL, err := newTestStore(t) if err != nil { t.Fatalf("Failed to start Cassandra container: %v", err) } @@ -185,15 +189,15 @@ func testExpirableKeys(t *testing.T, connectionURL string) { // Verify all keys exist initially value, err := store.Get("key_default_ttl") require.NoError(t, err) - assert.Equal(t, []byte("value1"), value) + require.Equal(t, []byte("value1"), value) value, err = store.Get("key_specific_ttl") require.NoError(t, err) - assert.Equal(t, []byte("value2"), value) + require.Equal(t, []byte("value2"), value) value, err = store.Get("key_no_ttl") require.NoError(t, err) - assert.Equal(t, []byte("value3"), value) + require.Equal(t, []byte("value3"), value) // Wait for specific TTL to expire time.Sleep(1500 * time.Millisecond) @@ -201,15 +205,15 @@ func testExpirableKeys(t *testing.T, connectionURL string) { // Specific TTL key should be gone, others should remain value, err = store.Get("key_specific_ttl") require.NoError(t, err) - assert.Nil(t, value, "Key with 1s TTL should have expired") + require.Nil(t, value, "Key with 1s TTL should have expired") value, err = store.Get("key_default_ttl") require.NoError(t, err) - assert.Equal(t, []byte("value1"), value, "Key with default TTL should still exist") + require.Equal(t, []byte("value1"), value, "Key with default TTL should still exist") value, err = store.Get("key_no_ttl") require.NoError(t, err) - assert.Equal(t, []byte("value3"), value, "Key with no TTL should still exist") + require.Equal(t, []byte("value3"), value, "Key with no TTL should still exist") // Wait for default TTL to expire time.Sleep(4 * time.Second) @@ -217,11 +221,11 @@ func testExpirableKeys(t *testing.T, connectionURL string) { // Default TTL key should be gone, no TTL key should remain value, err = store.Get("key_default_ttl") require.NoError(t, err) - assert.Nil(t, value, "Key with default TTL should have expired") + require.Nil(t, value, "Key with default TTL should have expired") value, err = store.Get("key_no_ttl") require.NoError(t, err) - assert.Equal(t, []byte("value3"), value, "Key with no TTL should still exist") + require.Equal(t, []byte("value3"), value, "Key with no TTL should still exist") } // / testReset tests the Reset method. @@ -326,3 +330,96 @@ func testConcurrentAccess(t *testing.T, connectionURL string) { <-done } } + +func Benchmark_Cassandra_Set(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + + c, connectionURL, err := newTestStore(b) + if err != nil { + b.Fatalf("Failed to start Cassandra container: %v", err) + } + defer func() { + if err := c.Terminate(context.TODO()); err != nil { + b.Logf("Failed to terminate container: %v", err) + } + }() + require.NoError(b, err) + + // Create new storage + store := New(Config{ + Hosts: []string{connectionURL}, + Keyspace: "test_concurrent", + Table: "test_kv", + }) + defer store.Close() + + for i := 0; i < b.N; i++ { + err = store.Set("john", []byte("doe"), 0) + } + + require.NoError(b, err) +} + +func Benchmark_Cassandra_Get(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + + c, connectionURL, err := newTestStore(b) + if err != nil { + b.Fatalf("Failed to start Cassandra container: %v", err) + } + defer func() { + if err := c.Terminate(context.TODO()); err != nil { + b.Logf("Failed to terminate container: %v", err) + } + }() + require.NoError(b, err) + + // Create new storage + client := New(Config{ + Hosts: []string{connectionURL}, + Keyspace: "test_concurrent", + Table: "test_kv", + }) + defer client.Close() + + err = client.Set("john", []byte("doe"), 0) + + for i := 0; i < b.N; i++ { + _, err = client.Get("john") + } + + require.NoError(b, err) +} + +func Benchmark_Cassandra_Set_And_Delete(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + + c, connectionURL, err := newTestStore(b) + if err != nil { + b.Fatalf("Failed to start Cassandra container: %v", err) + } + defer func() { + if err := c.Terminate(context.TODO()); err != nil { + b.Logf("Failed to terminate container: %v", err) + } + }() + require.NoError(b, err) + + // Create new storage + client := New(Config{ + Hosts: []string{connectionURL}, + Keyspace: "test_concurrent", + Table: "test_kv", + }) + defer client.Close() + + for i := 0; i < b.N; i++ { + _ = client.Set("john", []byte("doe"), 0) + err = client.Delete("john") + } + + require.NoError(b, err) +} diff --git a/cassandra/config.go b/cassandra/config.go index 0705e303..3fd122a1 100644 --- a/cassandra/config.go +++ b/cassandra/config.go @@ -15,7 +15,7 @@ type Config struct { // Keyspace is the name of the Cassandra keyspace to use. Keyspace string // Optional. Default is kv_store - /// Table is the name of the Cassandra table to use. + // Table is the name of the Cassandra table to use. Table string // Optional. Default is Quorum // Consistency is the Cassandra consistency level. diff --git a/cassandra/go.mod b/cassandra/go.mod index 557997c5..34d282ae 100644 --- a/cassandra/go.mod +++ b/cassandra/go.mod @@ -1,4 +1,4 @@ -module github.com/gofiber/storage/cassandra/v2 +module github.com/gofiber/storage/cassandra go 1.23.0