I have the two tables in postgres database, which is inside docker - data_table where is stored usual types and related data_files_table where is stored file data in bytea type. When I'm inserting in data_table and data_files_table in one transaction from golang service using sqlx, sometimes I get an error:
unexpected EOF
on query of insert to data_files_table. Often, it occurs on data bigger than 7 MB.
In postgres logs I found row, that might be relates:
could not receive data from client: Connection reset by peer
Why this occurs? Is it relate to TOAST, tcp connection killing, or something else?
Here is my code:
func (r *repo) CreateGrantWithDocuments(
ctx context.Context,
model models.Grant,
documents []models.GrantDocument,
) (*pgtype.UUID, error) {
tx, err := r.db.Beginx() // sqlx with pgx driver
if err != nil {
return nil, fmt.Errorf("db.Beginx: %w", err)
}
defer helpdb.Rollback(ctx, r.log, tx)
var grantID pgtype.UUID
err = tx.GetContext(
ctx, &grantID, createGrantSQL, args...) // args doesn't matter, it's ok; query returns id
if err != nil {
return nil, fmt.Errorf("tx.GetContext: %w", err)
}
for _, document := range documents {
data, err := io.ReadAll(document.File)
if err != nil {
return nil, fmt.Errorf("io.ReadAll: %w", err)
}
if err = document.File.Close(); err != nil { // file is an io.ReadCloser from multipart/form-data request
return nil, fmt.Errorf("document.File.Close: %w", err)
}
_, err = tx.NamedExec(createGrantDocumentSQL, document) // ERROR: unexpected EOF
if err != nil {
return nil, fmt.Errorf("tx.NamedExec: %w", err)
}
}
if err = tx.Commit(); err != nil {
return nil, fmt.Errorf("tx.Commit: %w", err)
}
return &grantID, nil
}
I'm using dependencies:
go 1.19
github.com/jmoiron/sqlx v1.3.4 // db connects
github.com/jackc/pgx/v4 v4.14.0 // db driver
I already changed the config on app-side: set idle and open connections count and lifetime to different values, disable idle, increase and decrease lifetime, but nothing helps.
Error doesn't occur locally, only on dev-server.
This problem relates limits of docker container by cgroups. docker stats shows that memory limit is not exceeded, but in fact it is exceeded, because this command doesn't see some types of memory that postgres uses, but cgroups see.
Also, this problem may relates to network problems, but not in my case.
Use rss+cache memory types in monitoring for prevent this problem.
Related
I am trying to query a database that I know has data in it from directly querying within pgadmin. When I query using the following code it returns no results:
const DATABATE_URL = "postgres://postgres:pw#localhost:5432/postgresdb"
conn, err := pgx.Connect(context.Background(), DATABATE_URL)
defer conn.Close(context.Background())
if err != nil {
fmt.Printf("Connection failed: %v\n", err)
os.Exit(-1)
}
stmt := "SELECT * FROM nodes"
rows, err := conn.Query(context.Background(), stmt, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err) //error outs here "expected 0 arguments, got 1"
os.Exit(1)
}
for rows.Next() {
var results string
err = rows.Scan(&results)
if err != nil {
fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n %n", err)
os.Exit(1)
}
fmt.Println(results)
}
When I directly connect to the database through goland and pgadmin and query with the same statement I can see all the data. What am I missing here?
The pgx Conn.Query accepts a context, statement and arguments:
func (c *Conn) Query(ctx context.Context, sql string, args ...interface{}) (Rows, error)
You are passing it a context, statement and nil:
rows, err := conn.Query(context.Background(), stmt, nil)
So the nil is treated as an argument but your SQL statement does not contain any argument placeholders (e.g. SELECT * FROM nodes where id=$1) hence the error. To fix this run:
rows, err := conn.Query(context.Background(), stmt)
However it would also be worth editing your sql to specify the column you want (e.g. SELECT nodename FROM nodes).
Note: When raising a question like this please include the error in the question body rather than just as a comment in the code (which is easy to miss).
I am expected to have 80% test coverage even for pushing the basic project structure. I am a bit confused how do I write unit tests for the following code to Connect to postgres db and ping postgres for health check. Can someone help me please.
var postgres *sql.DB
// ConnectToPostgres func to connect to postgres
func ConnectToPostgres(connStr string) (*sql.DB, error) {
db, err := sql.Open("postgres", connStr)
if err != nil {
log.Println("postgres-client ", err)
return nil, err
}
postgres = db
return db, nil
}
// PostgresHealthCheck to ping database and check for errors
func PostgresHealthCheck() error {
if err := postgres.Ping(); err != nil {
return err
}
return nil
}
type PostgresRepo struct {
db *sql.DB
}
// NewPostgresRepo constructor
func NewPostgresRepo(database *sql.DB) *PostgresRepo {
return &PostgresRepo{
db: database,
}
}
You need to use this : https://github.com/DATA-DOG/go-sqlmock
Its very easy to use. Here is an example where a controller is getting tested using a mocked SQL :
Implementation
func (up UserProvider) GetUsers() ([]models.User, error) {
var users = make([]models.User, 0, 10)
rows, err := up.DatabaseProvider.Query("SELECT firstname, lastname, email, age FROM Users;")
if err != nil {
return nil, err
}
for rows.Next() {
var u models.User = models.User{}
err := rows.Scan(&u.Name, &u.Lastname, &u.Email, &u.Age)
if err != nil {
return nil, err
}
users = append(users, u)
}
if err := rows.Err(); err != nil {
return nil, err
}
return users, nil
}
Test
func TestGetUsersOk(t *testing.T) {
db, mock := NewMock()
mock.ExpectQuery("SELECT firstname, lastname, email, age FROM Users;").
WillReturnRows(sqlmock.NewRows([]string{"firstname", "lastname", "email", "age"}).
AddRow("pepe", "guerra", "pepe#gmail.com", 34))
subject := UserProvider{
DatabaseProvider: repositories.NewMockDBProvider(db, nil),
}
resp, err := subject.GetUsers()
assert.Nil(t, err)
assert.NotNil(t, resp)
assert.Equal(t, 1, len(resp))
}
func NewMock() (*sql.DB, sqlmock.Sqlmock) {
db, mock, err := sqlmock.New()
if err != nil {
log.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
return db, mock
}
I find that writing tests against a live database makes for more high quality tests. The challenge with Postgres is that there's no good in-memory fake that you can substitute in.
What I came up with is standing up the postgres Docker container and creating temporary databases in there. The PostgresContainer type in the github.com/bitcomplete/sqltestutil package does exactly this:
# Postgres version is "12"
pg, _ := sqltestutil.StartPostgresContainer(context.Background(), "12")
defer pg.Shutdown(ctx)
db, err := sql.Open("postgres", pg.ConnectionString())
// ... execute SQL
Per the docs, it's a good idea to set up your tests so that the container is only started once, as it can take a few seconds to start up (more if the image needs to be downloaded). It suggests some approaches for mitigating that problem.
I am trying to create a function that InsertOne data by wrap transaction, and I already to replica set in mongodb also, I tried in local and and MongoDB atlas the error were same,
here is the code:
const MONGODB_URI = "mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs"
ctx := context.Background()
client, err := mongo.Connect(ctx, options.Client().ApplyURI(MONGODB_URI))
if err != nil {
panic(err)
}
db := client.Database("production")
defer db.Client().Disconnect(ctx)
col := db.Collection("people")
// transaction
err = db.Client().UseSession(ctx, func(sessionContext mongo.SessionContext) error {
err := sessionContext.StartTransaction()
if err != nil {
fmt.Println(err)
return err
}
_, err = col.InsertOne(sessionContext, req)
if err != nil {
sessionContext.AbortTransaction(sessionContext)
fmt.Println(err)
return err
} else {
sessionContext.CommitTransaction(sessionContext)
}
return nil
})
if err != nil {
return nil, err
}
I have follow the instructions of this question in Stackoverflow and I have tried also ways from this article mongodb developer
what I got is this error:
(NamespaceNotFound) Cannot create namespace
production.people in multi-document transaction.
and
multiple write errors: [{write errors:
[{Cannot create namespace spura.people in multi-document transaction.}]},
{<nil>}]"
it got error when I inserted data , is that something wrong in my code? I have tried look carefully and try the instruction of document or articles and always got that error :(
MongoDB 4.2 and lower does not permit creating collections in transactions. This restriction is lifted in 4.4.
For 4.2 and lower, create the collection ahead of time.
I have created a mongodb replica set. I am able to run transactions from the mongo shell. But when I try to do it using mongo-go-driver I always get this error (IllegalOperation) Transaction numbers are only allowed on a replica set member or mongos. I am not sure where I am going wrong. I am using this as a reference https://github.com/simagix/mongo-go-examples/blob/master/examples/transaction_test.go.
I create the client like this
client, err := mongo.NewClient(options.Client().ApplyURI("mongodb://localhost:27017,localhost:27018,localhost:27019?replicaSet=rs"))
I can connect to the individual mongodb instances, just not the replica set.
This is the transaction that I am trying to run
var session mongo.Session
coll := db.Collection("collectionname")
if session, err = client.StartSession(); err != nil {
return "", fmt.Errorf("Could not start session: %q", err)
}
if err = session.StartTransaction(); err != nil {
return "", fmt.Errorf("Could not start Transaction: %q", err)
}
if err = mongo.WithSession(ctx, session, func(sc md.SessionContext) error {
newVal, err = coll.InsertOne(sc, val) // some val that I have
if err != nil {
sc.AbortTransaction(sc)
return fmt.Errorf("Error during New address creation, aborting: %q", err)
}
if err = sc.CommitTransaction(sc); err != nil {
return fmt.Errorf("Error While commiting New address Transaction: %q", err)
}
return nil
}); err != nil {
return "", err
}
session.EndSession(ctx)
Is there something I am missing. Is there some other example maybe that I can reference. Thanks for the help/suggestions.
This is an issue with your connection code - not your transaction implementation most likely. Try using the more modern connection string for connecting to a replica set:
connectionString := "mongodb+srv://USERNAME:PASSWORD#mongoatlas.1mxpg.mongodb.net/?retryWrites=true&w=majority"
var err error
Client, err = mongo.NewClient(options.Client().ApplyURI(ConnectionString))
if err != nil {
log.Fatal(err)
}
I am working on a go project where I need to serve files stored in mongodb. The files are stored in a GridFs. I use gopkg.in/mgo.v2 as package to connect and query the db.
I can retrieve the file from the db, that is not hard.
f, err := s.files.OpenId(id)
But how can I serve that file with http?
I work with the JulienSchmidt router to handle all the other restfull requests.
The solutions I find always use static files, not files from a db.
Thanks in advance
Tip: Recommended to use github.com/globalsign/mgo instead of gopkg.in/mgo.v2 (the latter is not maintained anymore).
The mgo.GridFile type implements io.Reader, so you could use io.Copy() to copy its content into the http.ResponseWriter.
But since mgo.GridFile also implements io.Seeker, you may take advantage of http.ServeContent(). Quoting its doc:
The main benefit of ServeContent over io.Copy is that it handles Range requests properly, sets the MIME type, and handles If-Match, If-Unmodified-Since, If-None-Match, If-Modified-Since, and If-Range requests.
Example handler serving a file:
func serveFromDB(w http.ResponseWriter, r *http.Request) {
var gridfs *mgo.GridFS // Obtain GridFS via Database.GridFS(prefix)
name := "somefile.pdf"
f, err := gridfs.Open(name)
if err != nil {
log.Printf("Failed to open %s: %v", name, err)
http.Error(w, "something went wrong", http.StatusInternalServerError)
return
}
defer f.Close()
http.ServeContent(w, r, name, time.Now(), f) // Use proper last mod time
}
its old but i got another solution with goMongo driver by importing
"go.mongodb.org/mongo-driver/mongo/gridfs"
var bucket *gridfs.Bucket //creates a bucket
dbConnection, err := db.GetDBCollection() //connect db with your your
if err != nil {
log.Fatal(err)
}
bucket, err = gridfs.NewBucket(dbConnection)
if err != nil {
log.Fatal(err)
}
name := "br100_update.txt"
downloadStream, err := bucket.OpenDownloadStreamByName(name)
if err != nil {
log.Printf("Failed to open %s: %v", name, err)
http.Error(w, "something went wrong", http.StatusInternalServerError)
return
}
defer func() {
if err := downloadStream.Close(); err != nil {
log.Fatal(err)
}
}()
// Use SetReadDeadline to force a timeout if the download does not succeed in
// 2 seconds.
if err = downloadStream.SetReadDeadline(time.Now().Add(2 * time.Second)); err
!= nil {
log.Fatal(err)
}
// this code below use to read the file
fileBuffer := bytes.NewBuffer(nil)
if _, err := io.Copy(fileBuffer, downloadStream); err != nil {
log.Fatal(err)
}