codenotary / immudb

immudb - immutable database based on zero trust, SQL/Key-Value/Document model, tamperproof, data change history
https://immudb.io
Other
8.55k stars 343 forks source link

CRASH: Immudb crashes when modifying the same table in the same transaction #2003

Closed felipelalli closed 1 month ago

felipelalli commented 1 month ago

What happened

The immudb server crashes miserably when attempting to modify the same table more than once in the same transaction. It is understandable that modifying the same table in the same transaction might not be allowed, but a crash resulting from this is unforgivable.

Apparently, the problem only occurs in columns that are indexed.

immudb 2024/07/16 01:27:15 INFO: indexing in progress at '/var/lib/immudb/defaultdb'
panic: runtime error: index out of range [5] with length 3

goroutine 921 [running]:
github.com/codenotary/immudb/embedded/store.(*OngoingTx).set(0xc000952540, {0xc02b10b6c8, 0x16, 0x16}, 0xc02b3fee10, {0xc02b638000, 0x46e, 0x5b8}, {0x0, 0x0, ...}, ...)
        /src/embedded/store/ongoing_tx.go:343 +0xbca
github.com/codenotary/immudb/embedded/store.(*OngoingTx).Set(...)
        /src/embedded/store/ongoing_tx.go:369
github.com/codenotary/immudb/embedded/sql.(*SQLTx).set(...)
        /src/embedded/sql/sql_tx.go:108
github.com/codenotary/immudb/embedded/sql.(*SQLTx).deprecateIndexEntries(0xc01ea75a00, {0xc02ad033b0, 0x45, 0x45}, 0x6ec657?, 0xc000436970?, 0xc02b2acfc0)
        /src/embedded/sql/stmt.go:1148 +0x534
github.com/codenotary/immudb/embedded/sql.(*SQLTx).doUpsert(0xc01ea75a00, {0x25eee88, 0xc02b3e3d40}, {0xc02ad033b0, 0x45, 0x45}, 0xc0000300a0?, 0xc02b2acfc0, 0x50?)
        /src/embedded/sql/stmt.go:940 +0x126b
github.com/codenotary/immudb/embedded/sql.(*UpdateStmt).execAt(0xc02b0f9f10, {0x25eee88, 0xc02b3e3d40}, 0xc01ea75a00, 0x25ea520?)
        /src/embedded/sql/stmt.go:1312 +0x6ee
github.com/codenotary/immudb/embedded/sql.(*Engine).execPreparedStmts(0xc01939ea00, {0x25eee88, 0xc02b3e3d40}, 0xc01ea75a00, {0xc02b3fe840?, 0x1, 0x1}, 0x0?)
        /src/embedded/sql/engine.go:467 +0x48e
github.com/codenotary/immudb/embedded/sql.(*Engine).ExecPreparedStmts(0xc01939ea00, {0x25eee88, 0xc02b3e3d40}, 0xc01ea75a00, {0xc02b3fe840?, 0x0?, 0x40afab?}, 0xc027501320?)
        /src/embedded/sql/engine.go:389 +0x5b
github.com/codenotary/immudb/pkg/database.(*db).SQLExecPrepared(0xc0002c4000, {0x25eee88, 0xc02b3e3d40}, 0x9?, {0xc02b3fe840, 0x1, 0x1}, 0xc02b3e3c50?)
        /src/pkg/database/sql.go:362 +0x1be
github.com/codenotary/immudb/pkg/database.(*db).SQLExec(0x25f35c0?, {0x25eee88, 0xc02b3e3d40}, 0xc02b2acb40?, 0xc02b2fff80)
        /src/pkg/database/sql.go:347 +0x23c
github.com/codenotary/immudb/pkg/server/sessions/internal/transactions.(*transaction).SQLExec(0xc02b598af0, {0x25eee88, 0xc02b3e3d40}, 0xc02b3e3c50?)
        /src/pkg/server/sessions/internal/transactions/transactions.go:124 +0xba
github.com/codenotary/immudb/pkg/server.(*ImmuServer).TxSQLExec(0xc0002d2a00, {0x25eee88, 0xc02b3e3d40}, 0xc02b2fff80)
        /src/pkg/server/transaction.go:126 +0x92
github.com/codenotary/immudb/pkg/api/schema._ImmuService_TxSQLExec_Handler.func1({0x25eee88, 0xc02b3e3d40}, {0xe2b960?, 0xc02b2fff80})
        /src/pkg/api/schema/schema_grpc.pb.go:1682 +0x7b
github.com/codenotary/immudb/pkg/server.(*ImmuServer).SessionAuthInterceptor(0xc0002d2a00, {0x25eee88, 0xc02b3e3d40}, {0xe2b960, 0xc02b2fff80}, 0xc02b0f7180, 0xc02b3becc0)
        /src/pkg/server/session_auth_interceptor.go:37 +0xd5
github.com/grpc-ecosystem/go-grpc-middleware.ChainUnaryServer.func1.1.1({0x25eee88?, 0xc02b3e3d40?}, {0xe2b960?, 0xc02b2fff80?})
        /go/pkg/mod/github.com/grpc-ecosystem/go-grpc-middleware@v1.3.0/chain.go:25 +0x3a
github.com/codenotary/immudb/pkg/auth.ServerUnaryInterceptor({0x25eee88, 0xc02b3e3d40}, {0xe2b960, 0xc02b2fff80}, 0x0?, 0xc02b0f71a0)
        /src/pkg/auth/serverinterceptors.go:72 +0xa2
github.com/grpc-ecosystem/go-grpc-middleware.ChainUnaryServer.func1.1.1({0x25eee88?, 0xc02b3e3d40?}, {0xe2b960?, 0xc02b2fff80?})
        /go/pkg/mod/github.com/grpc-ecosystem/go-grpc-middleware@v1.3.0/chain.go:25 +0x3a
github.com/grpc-ecosystem/go-grpc-prometheus.(*ServerMetrics).UnaryServerInterceptor.func1({0x25eee88, 0xc02b3e3d40}, {0xe2b960, 0xc02b2fff80}, 0xd3c840?, 0xc02b0f71c0)
        /go/pkg/mod/github.com/grpc-ecosystem/go-grpc-prometheus@v1.2.0/server_metrics.go:107 +0x87
github.com/grpc-ecosystem/go-grpc-middleware.ChainUnaryServer.func1.1.1({0x25eee88?, 0xc02b3e3d40?}, {0xe2b960?, 0xc02b2fff80?})
        /go/pkg/mod/github.com/grpc-ecosystem/go-grpc-middleware@v1.3.0/chain.go:25 +0x3a
github.com/codenotary/immudb/pkg/server.(*uuidContext).UUIDContextSetter(0x30?, {0x25eee88, 0xc02b3e3d40}, {0xe2b960, 0xc02b2fff80}, 0xc0275017f0?, 0xc02b0f71e0)
        /src/pkg/server/uuid.go:137 +0x14b
github.com/grpc-ecosystem/go-grpc-middleware.ChainUnaryServer.func1.1.1({0x25eee88?, 0xc02b3e3d40?}, {0xe2b960?, 0xc02b2fff80?})
        /go/pkg/mod/github.com/grpc-ecosystem/go-grpc-middleware@v1.3.0/chain.go:25 +0x3a
github.com/codenotary/immudb/pkg/server.(*ImmuServer).KeepAliveSessionInterceptor(0x4a9c3a?, {0x25eee88, 0xc02b3e3d40}, {0xe2b960, 0xc02b2fff80}, 0xc02b0f7180, 0xc02b0f7200)
        /src/pkg/server/keep_alive_session_interceptor.go:44 +0xb6
github.com/grpc-ecosystem/go-grpc-middleware.ChainUnaryServer.func1.1.1({0x25eee88?, 0xc02b3e3d40?}, {0xe2b960?, 0xc02b2fff80?})
        /go/pkg/mod/github.com/grpc-ecosystem/go-grpc-middleware@v1.3.0/chain.go:25 +0x3a
github.com/codenotary/immudb/pkg/server.ErrorMapper({0x25eee88?, 0xc02b3e3d40?}, {0xe2b960?, 0xc02b2fff80?}, 0x7fd59bf6f888?, 0xc02b0f7180?)
        /src/pkg/server/error_mapper_interceptor.go:33 +0x30
github.com/grpc-ecosystem/go-grpc-middleware.ChainUnaryServer.func1.1.1({0x25eee88?, 0xc02b3e3d40?}, {0xe2b960?, 0xc02b2fff80?})
        /go/pkg/mod/github.com/grpc-ecosystem/go-grpc-middleware@v1.3.0/chain.go:25 +0x3a
github.com/grpc-ecosystem/go-grpc-middleware.ChainUnaryServer.func1({0x25eee88, 0xc02b3e3d40}, {0xe2b960, 0xc02b2fff80}, 0xc02a226a08?, 0xd91020?)
        /go/pkg/mod/github.com/grpc-ecosystem/go-grpc-middleware@v1.3.0/chain.go:34 +0xbf
github.com/codenotary/immudb/pkg/api/schema._ImmuService_TxSQLExec_Handler({0xeaa800?, 0xc0002d2a00}, {0x25eee88, 0xc02b3e3d40}, 0xc02b0f9ea0, 0xc000462870)
        /src/pkg/api/schema/schema_grpc.pb.go:1684 +0x138
google.golang.org/grpc.(*Server).processUnaryRPC(0xc0000001e0, {0x25f3ba0, 0xc02b39ab60}, 0xc02b0eafc0, 0xc000463b30, 0x2c09b38, 0x0)
        /go/pkg/mod/google.golang.org/grpc@v1.57.1/server.go:1358 +0xe13
google.golang.org/grpc.(*Server).handleStream(0xc0000001e0, {0x25f3ba0, 0xc02b39ab60}, 0xc02b0eafc0, 0x0)
        /go/pkg/mod/google.golang.org/grpc@v1.57.1/server.go:1735 +0xa1b
google.golang.org/grpc.(*Server).serveStreams.func1.1()
        /go/pkg/mod/google.golang.org/grpc@v1.57.1/server.go:970 +0xca
created by google.golang.org/grpc.(*Server).serveStreams.func1
        /go/pkg/mod/google.golang.org/grpc@v1.57.1/server.go:981 +0x15c

What you expected to happen

Honestly, I expected immudb to be able to calculate the changes within a transaction and apply them accordingly. If that were not possible, at least it should give an error instead of causing the server to crash.

How to reproduce it

This bug is easily reproducible. To make it easier, I wrote some code here that makes the server crash 100% of the time:

package tools

import (
    "context"
    "os"
    "strconv"
    "testing"

    immudb "github.com/codenotary/immudb/pkg/client"
    "github.com/joho/godotenv"
)

// immuadmin 1.9.3
// Commit  : 5487dd300655083ff68c4a72c2edb38ca84dd1bb
// Built at: Thu, 23 May 2024 10:08:05 UTC
// Static  : true

// CREATE TABLE test_table (
//  id INTEGER,
//  name VARCHAR,
//  age INTEGER,
//  active BOOLEAN,
//  salary FLOAT,
//  created_at TIMESTAMP,
//  data BLOB,
//  notes VARCHAR,
//  PRIMARY KEY (id)
// )

// INSERT INTO test_table (id, name, age, active, salary, created_at, data, notes) VALUES
// (1, 'Alice', 30, true, 1000.50, NOW(), x'010203', 'Note 1'),
// (2, 'Bob', 25, false, 2000.75, NOW(), x'040506', 'Note 2'),
// (3, 'Charlie', NULL, NULL, NULL, NULL, NULL, NULL)

// CREATE INDEX IF NOT EXISTS ON test_table(salary,created_at);

func TestImmudbCrash(t *testing.T) {
    ctx := context.Background()

    err := godotenv.Load("../.env")
    if err != nil {
        t.Fatalf("Error loading .env file: %v", err)
    }

    portAsInt, _ := strconv.Atoi(os.Getenv("IMMUDB_PORT"))

    var opts = immudb.DefaultOptions().
        WithAddress(os.Getenv("IMMUDB_HOST")).
        WithPort(portAsInt)

    var db = immudb.NewClient().WithOptions(opts)
    err = db.OpenSession(
        ctx,
        []byte(os.Getenv("IMMUDB_USER")),
        []byte(os.Getenv("IMMUDB_PWD")),
        os.Getenv("IMMUDB_DB"),
    )

    if err != nil {
        t.Fatalf("Error opening session: %v", err)
    }

    defer func() {
        if err := db.CloseSession(ctx); err != nil {
            t.Fatalf("Failed to close session: %v", err)
        }
    }()

    var dbTx immudb.Tx
    dbTx, err = db.NewTx(ctx)

    if err != nil {
        t.Fatalf("Error creating new transaction: %v", err)
    }

    err = dbTx.SQLExec(ctx, "UPDATE test_table SET salary = 1000.0 WHERE id = 1", nil)

    if err != nil {
        t.Fatalf("Error executing SQL statement: %v", err)
    }

    err = dbTx.SQLExec(ctx, "UPDATE test_table SET salary = 1001.0 WHERE id = 1", nil)

    if err != nil {
        t.Fatalf("Error executing SQL statement: %v", err)
    }

    _, err = dbTx.Commit(ctx)

    if err != nil {
        t.Fatalf("Error committing transaction: %v", err)
    }
}

Environment

immuadmin 1.9.3
Commit  : 5487dd300655083ff68c4a72c2edb38ca84dd1bb
Built at: Thu, 23 May 2024 10:08:05 UTC
Static  : true
ostafen commented 1 month ago

Hey, @felipelalli, thank you for reporting this issue. We will definitely fix this in the next release.