diff --git a/configs/docker-compose.yml b/configs/docker-compose.yml index 5956f6e..2b2c716 100644 --- a/configs/docker-compose.yml +++ b/configs/docker-compose.yml @@ -2,7 +2,7 @@ version: '3.2' services: prometheus: - image: prom/prometheus:v2.37.9 + image: prom/prometheus:v2.45.4 container_name: prometheus ports: - 9090:9090 @@ -12,25 +12,25 @@ services: - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro grafana: - image: grafana/grafana:9.5.13 + image: grafana/grafana:10.0.12 depends_on: - prometheus ports: - 3000:3000 restart: always - ydb: - image: cr.yandex/yc/yandex-docker-local-ydb:23.3 - container_name: ydb-local - restart: always - hostname: localhost - ports: - - 2135:2135 - - 2136:2136 - - 8765:8765 - environment: - - GRPC_TLS_PORT=2135 - - GRPC_PORT=2136 - - MON_PORT=8765 -# - YDB_USE_IN_MEMORY_PDISKS=true - - YDB_DEFAULT_LOG_LEVEL=NOTICE +# ydb: +# image: cr.yandex/yc/yandex-docker-local-ydb:23.3 +# container_name: ydb-local +# restart: always +# hostname: localhost +# ports: +# - 2135:2135 +# - 2136:2136 +# - 8765:8765 +# environment: +# - GRPC_TLS_PORT=2135 +# - GRPC_PORT=2136 +# - MON_PORT=8765 +## - YDB_USE_IN_MEMORY_PDISKS=true +# - YDB_DEFAULT_LOG_LEVEL=NOTICE diff --git a/internal/cmd/bench/native/README.md b/internal/cmd/bench/native/README.md deleted file mode 100644 index b9f7788..0000000 --- a/internal/cmd/bench/native/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Benchmark for tests prometheus metrics - -## Run - -```shell -% go build -o bench . -% ./bench -ydb-url=grpc://localhost:2136/local -prometheus-url=http://localhost:8080 -threads=50 -``` \ No newline at end of file diff --git a/internal/cmd/bench/native/query/README.md b/internal/cmd/bench/native/query/README.md new file mode 100644 index 0000000..ce2b2f0 --- /dev/null +++ b/internal/cmd/bench/native/query/README.md @@ -0,0 +1,8 @@ +# Benchmark for tests prometheus metrics + +## Run + +```shell +% go build -o bench-table . +% ./bench-table -ydb-url=grpc://localhost:2136/local -prometheus-url=http://localhost:8080 -threads=50 +``` \ No newline at end of file diff --git a/internal/cmd/bench/native/query/data.go b/internal/cmd/bench/native/query/data.go new file mode 100644 index 0000000..cbdbde9 --- /dev/null +++ b/internal/cmd/bench/native/query/data.go @@ -0,0 +1,208 @@ +package main + +import ( + "time" + + "github.com/google/uuid" + "github.com/ydb-platform/ydb-go-sdk/v3/table/types" +) + +func seriesData(id string, released time.Time, title, info, comment string) types.Value { + var commentv types.Value + if comment == "" { + commentv = types.NullValue(types.TypeUTF8) + } else { + commentv = types.OptionalValue(types.TextValue(comment)) + } + + return types.StructValue( + types.StructFieldValue("series_id", types.BytesValueFromString(id)), + types.StructFieldValue("release_date", types.DateValueFromTime(released)), + types.StructFieldValue("title", types.TextValue(title)), + types.StructFieldValue("series_info", types.TextValue(info)), + types.StructFieldValue("comment", commentv), + ) +} + +func seasonData(seriesID, seasonID, title string, first, last time.Time) types.Value { + return types.StructValue( + types.StructFieldValue("series_id", types.BytesValueFromString(seriesID)), + types.StructFieldValue("season_id", types.BytesValueFromString(seasonID)), + types.StructFieldValue("title", types.TextValue(title)), + types.StructFieldValue("first_aired", types.DateValueFromTime(first)), + types.StructFieldValue("last_aired", types.DateValueFromTime(last)), + ) +} + +func episodeData(seriesID, seasonID, episodeID, title string, date time.Time) types.Value { + return types.StructValue( + types.StructFieldValue("series_id", types.BytesValueFromString(seriesID)), + types.StructFieldValue("season_id", types.BytesValueFromString(seasonID)), + types.StructFieldValue("episode_id", types.BytesValueFromString(episodeID)), + types.StructFieldValue("title", types.TextValue(title)), + types.StructFieldValue("air_date", types.DateValueFromTime(date)), + ) +} + +func getData() (series, seasons, episodes []types.Value) { + for seriesID, fill := range map[string]func(seriesID string) ( + seriesData types.Value, seasons []types.Value, episodes []types.Value, + ){ + uuid.New().String(): getDataForITCrowd, + uuid.New().String(): getDataForSiliconValley, + } { + seriesData, seasonsData, episodesData := fill(seriesID) + series = append(series, seriesData) + seasons = append(seasons, seasonsData...) + episodes = append(episodes, episodesData...) + } + + return +} + +func getDataForITCrowd(seriesID string) (series types.Value, seasons, episodes []types.Value) { + series = seriesData( + seriesID, date("2006-02-03"), "IT Crowd", ""+ + "The IT Crowd is a British sitcom produced by Channel 4, written by Graham Linehan, produced by "+ + "Ash Atalla and starring Chris O'Dowd, Richard Ayoade, Katherine Parkinson, and Matt Berry.", + "", // NULL comment. + ) + for _, season := range []struct { //nolint:gocritic + title string + first time.Time + last time.Time + episodes map[string]time.Time + }{ + {"Season 1", date("2006-02-03"), date("2006-03-03"), map[string]time.Time{ + "Yesterday's Jam": date("2006-02-03"), + "Calamity Jen": date("2006-02-03"), + "Fifty-Fifty": date("2006-02-10"), + "The Red Door": date("2006-02-17"), + "The Haunting of Bill Crouse": date("2006-02-24"), + "Aunt Irma Visits": date("2006-03-03"), + }}, + {"Season 2", date("2007-08-24"), date("2007-09-28"), map[string]time.Time{ + "The Work Outing": date("2006-08-24"), + "Return of the Golden Child": date("2007-08-31"), + "Moss and the German": date("2007-09-07"), + "The Dinner Party": date("2007-09-14"), + "Smoke and Mirrors": date("2007-09-21"), + "Men Without Women": date("2007-09-28"), + }}, + {"Season 3", date("2008-11-21"), date("2008-12-26"), map[string]time.Time{ + "From Hell": date("2008-11-21"), + "Are We Not Men?": date("2008-11-28"), + "Tramps Like Us": date("2008-12-05"), + "The Speech": date("2008-12-12"), + "Friendface": date("2008-12-19"), + "Calendar Geeks": date("2008-12-26"), + }}, + {"Season 4", date("2010-06-25"), date("2010-07-30"), map[string]time.Time{ + "Jen The Fredo": date("2010-06-25"), + "The Final Countdown": date("2010-07-02"), + "Something Happened": date("2010-07-09"), + "Italian For Beginners": date("2010-07-16"), + "Bad Boys": date("2010-07-23"), + "Reynholm vs Reynholm": date("2010-07-30"), + }}, + } { + seasonID := uuid.New().String() + seasons = append(seasons, seasonData(seriesID, seasonID, season.title, season.first, season.last)) + for title, date := range season.episodes { + episodes = append(episodes, episodeData(seriesID, seasonID, uuid.New().String(), title, date)) + } + } + + return series, seasons, episodes +} + +func getDataForSiliconValley(seriesID string) (series types.Value, seasons, episodes []types.Value) { + series = seriesData( + seriesID, date("2014-04-06"), "Silicon Valley", ""+ + "Silicon Valley is an American comedy television series created by Mike Judge, John Altschuler and "+ + "Dave Krinsky. The series focuses on five young men who founded a startup company in Silicon Valley.", + "Some comment here", + ) + for _, season := range []struct { //nolint:gocritic + title string + first time.Time + last time.Time + episodes map[string]time.Time + }{ + {"Season 1", date("2006-02-03"), date("2006-03-03"), map[string]time.Time{ + "Minimum Viable Product": date("2014-04-06"), + "The Cap Table": date("2014-04-13"), + "Articles of Incorporation": date("2014-04-20"), + "Fiduciary Duties": date("2014-04-27"), + "Signaling Risk": date("2014-05-04"), + "Third Party Insourcing": date("2014-05-11"), + "Proof of Concept": date("2014-05-18"), + "Optimal Tip-to-Tip Efficiency": date("2014-06-01"), + }}, + {"Season 2", date("2007-08-24"), date("2007-09-28"), map[string]time.Time{ + "Sand Hill Shuffle": date("2015-04-12"), + "Runaway Devaluation": date("2015-04-19"), + "Bad Money": date("2015-04-26"), + "The Lady": date("2015-05-03"), + "Server Space": date("2015-05-10"), + "Homicide": date("2015-05-17"), + "Adult Content": date("2015-05-24"), + "White Hat/Black Hat": date("2015-05-31"), + "Binding Arbitration": date("2015-06-07"), + "Two Days of the Condor": date("2015-06-14"), + }}, + {"Season 3", date("2008-11-21"), date("2008-12-26"), map[string]time.Time{ + "Founder Friendly": date("2016-04-24"), + "Two in the Box": date("2016-05-01"), + "Meinertzhagen's Haversack": date("2016-05-08"), + "Maleant Data Systems Solutions": date("2016-05-15"), + "The Empty Chair": date("2016-05-22"), + "Bachmanity Insanity": date("2016-05-29"), + "To Build a Better Beta": date("2016-06-05"), + "Bachman's Earnings Over-Ride": date("2016-06-12"), + "Daily Active Users": date("2016-06-19"), + "The Uptick": date("2016-06-26"), + }}, + {"Season 4", date("2010-06-25"), date("2010-07-30"), map[string]time.Time{ + "Success Failure": date("2017-04-23"), + "Terms of Service": date("2017-04-30"), + "Intellectual Property": date("2017-05-07"), + "Teambuilding Exercise": date("2017-05-14"), + "The Blood Boy": date("2017-05-21"), + "Customer Service": date("2017-05-28"), + "The Patent Troll": date("2017-06-04"), + "The Keenan Vortex": date("2017-06-11"), + "Hooli-Con": date("2017-06-18"), + "Server Error": date("2017-06-25"), + }}, + {"Season 5", date("2018-03-25"), date("2018-05-13"), map[string]time.Time{ + "Grow Fast or Die Slow": date("2018-03-25"), + "Reorientation": date("2018-04-01"), + "Chief Operating Officer": date("2018-04-08"), + "Tech Evangelist": date("2018-04-15"), + "Facial Recognition": date("2018-04-22"), + "Artificial Emotional Intelligence": date("2018-04-29"), + "Initial Coin Offering": date("2018-05-06"), + "Fifty-One Percent": date("2018-05-13"), + }}, + } { + seasonID := uuid.New().String() + seasons = append(seasons, seasonData(seriesID, seasonID, season.title, season.first, season.last)) + for title, date := range season.episodes { + episodes = append(episodes, episodeData(seriesID, seasonID, uuid.New().String(), title, date)) + } + } + + return series, seasons, episodes +} + +const dateISO8601 = "2006-01-02" + +func date(date string) time.Time { + t, err := time.Parse(dateISO8601, date) + if err != nil { + panic(err) + } + + return t +} diff --git a/internal/cmd/bench/native/query/main.go b/internal/cmd/bench/native/query/main.go new file mode 100644 index 0000000..e7daeab --- /dev/null +++ b/internal/cmd/bench/native/query/main.go @@ -0,0 +1,151 @@ +package main + +import ( + "context" + "flag" + "fmt" + environ "github.com/ydb-platform/ydb-go-sdk-auth-environ" + "log" + "math/rand" + "net/http" + "os" + "os/signal" + "path" + "runtime" + "strings" + "sync" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + metrics "github.com/ydb-platform/ydb-go-sdk-prometheus/v2" + "github.com/ydb-platform/ydb-go-sdk/v3" +) + +var ( + ydbURL = "grpc://localhost:2136/local" + threads = 500 +) + +func init() { + flag.StringVar(&ydbURL, "ydb", ydbURL, "connection string for connect to YDB") + flag.IntVar(&threads, "threads", threads, "concurrency factor for upsert and read data") +} + +func isYdbVersionHaveQueryService() error { + minYdbVersion := strings.Split("24.1", ".") + ydbVersion := strings.Split(os.Getenv("YDB_VERSION"), ".") + for i, component := range ydbVersion { + if i < len(minYdbVersion) { + if r := strings.Compare(component, minYdbVersion[i]); r < 0 { + return fmt.Errorf("example '%s' run on minimal YDB version '%v', but current version is '%s'", + os.Args[0], + strings.Join(minYdbVersion, "."), + func() string { + if len(ydbVersion) > 0 && ydbVersion[0] != "" { + return strings.Join(ydbVersion, ".") + } + + return "undefined" + }(), + ) + } else if r > 0 { + return nil + } + } + } + + return nil +} + +func main() { + if err := isYdbVersionHaveQueryService(); err != nil { + fmt.Println(err.Error()) + + return + } + + flag.Parse() + + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) + defer cancel() + + registry := prometheus.NewRegistry() + + go func() { + mux := http.NewServeMux() + mux.Handle("/metrics", + promhttp.HandlerFor( + registry, + promhttp.HandlerOpts{ + Registry: registry, + }, + ), + ) + if err := http.ListenAndServe(":8080", mux); err != nil { + log.Fatal(err) + } + }() + + connectCtx, connectCancel := context.WithTimeout(ctx, 500*time.Second) + defer connectCancel() + + db, err := ydb.Open(connectCtx, ydbURL, + environ.WithEnvironCredentials(ctx), + ydb.WithSessionPoolSizeLimit(threads), + metrics.WithTraces(registry), + ) + if err != nil { + panic(err) + } + defer func() { + _ = db.Close(ctx) + }() + + cfg := metrics.Config(registry).WithSystem("app") + goro := cfg.GaugeVec("goroutines") + memo := cfg.GaugeVec("memory") + go func() { + for { + time.Sleep(time.Second) + goro.With(nil).Set(float64(runtime.NumGoroutine())) + var m runtime.MemStats + runtime.ReadMemStats(&m) + memo.With(nil).Set(float64(m.HeapSys)) + } + }() + + prefix := path.Join(db.Name(), "native/query") + + err = createTables(ctx, db.Query(), prefix) + if err != nil { + panic(fmt.Errorf("create tables error: %w", err)) + } + + err = fillTablesWithData(ctx, db.Query(), prefix) + if err != nil { + panic(fmt.Errorf("fill tables with data error: %w", err)) + } + if err != nil { + panic(fmt.Errorf("fill data failed: %w", err)) + } + + wg := &sync.WaitGroup{} + wg.Add(threads) + + for i := 0; i < threads; i++ { + go func() { + defer wg.Done() + + for { + switch rand.Int31() % 2 { + case 0: + _ = read(ctx, db.Query(), prefix) + case 1: + _ = readTx(ctx, db.Query(), prefix) + } + } + }() + } + wg.Wait() +} diff --git a/internal/cmd/bench/native/query/series.go b/internal/cmd/bench/native/query/series.go new file mode 100644 index 0000000..6d9e09e --- /dev/null +++ b/internal/cmd/bench/native/query/series.go @@ -0,0 +1,260 @@ +package main + +import ( + "context" + "errors" + "fmt" + "io" + "log" + "path" + "time" + + ydb "github.com/ydb-platform/ydb-go-sdk/v3" + "github.com/ydb-platform/ydb-go-sdk/v3/query" +) + +func read(ctx context.Context, c query.Client, prefix string) error { + return c.Do(ctx, + func(ctx context.Context, s query.Session) (err error) { + _, result, err := s.Execute(ctx, fmt.Sprintf(` + PRAGMA TablePathPrefix("%s"); + DECLARE $seriesID AS Uint64; + SELECT + series_id, + title, + release_date + FROM + series + `, prefix), + query.WithTxControl(query.TxControl(query.BeginTx(query.WithOnlineReadOnly()))), + query.WithStatsMode(query.StatsModeBasic), + ) + if err != nil { + return err + } + + defer func() { + _ = result.Close(ctx) + }() + + for { + resultSet, err := result.NextResultSet(ctx) + if err != nil { + if errors.Is(err, io.EOF) { + return result.Err() + } + + return err + } + for { + row, err := resultSet.NextRow(ctx) + if err != nil { + if errors.Is(err, io.EOF) { + return result.Err() + } + + return err + } + + var info struct { + SeriesID string `sql:"series_id"` + Title string `sql:"title"` + ReleaseDate time.Time `sql:"release_date"` + } + err = row.ScanStruct(&info) + if err != nil { + return err + } + log.Printf("%+v", info) + } + } + }, + ) +} + +func readTx(ctx context.Context, c query.Client, prefix string) error { + return c.DoTx(ctx, + func(ctx context.Context, tx query.TxActor) (err error) { + result, err := tx.Execute(ctx, fmt.Sprintf(` + PRAGMA TablePathPrefix("%s"); + DECLARE $seriesID AS Uint64; + SELECT + series_id, + title, + release_date + FROM + series + `, prefix), + query.WithStatsMode(query.StatsModeBasic), + ) + if err != nil { + return err + } + + defer func() { + _ = result.Close(ctx) + }() + + for { + resultSet, err := result.NextResultSet(ctx) + if err != nil { + if errors.Is(err, io.EOF) { + return result.Err() + } + + return err + } + for { + row, err := resultSet.NextRow(ctx) + if err != nil { + if errors.Is(err, io.EOF) { + return result.Err() + } + + return err + } + + var info struct { + SeriesID string `sql:"series_id"` + Title string `sql:"title"` + ReleaseDate time.Time `sql:"release_date"` + } + err = row.ScanStruct(&info) + if err != nil { + return err + } + log.Printf("%+v", info) + } + } + }, + ) +} + +func fillTablesWithData(ctx context.Context, c query.Client, prefix string) error { + series, seasons, episodes := getData() + + return c.Do(ctx, + func(ctx context.Context, s query.Session) (err error) { + _, _, err = s.Execute(ctx, + fmt.Sprintf(` + PRAGMA TablePathPrefix("%s"); + + DECLARE $seriesData AS List>>; + + DECLARE $seasonsData AS List>; + + DECLARE $episodesData AS List>; + + REPLACE INTO series + SELECT + series_id, + title, + series_info, + release_date, + comment + FROM AS_TABLE($seriesData); + + REPLACE INTO seasons + SELECT + series_id, + season_id, + title, + first_aired, + last_aired + FROM AS_TABLE($seasonsData); + + REPLACE INTO episodes + SELECT + series_id, + season_id, + episode_id, + title, + air_date + FROM AS_TABLE($episodesData); + `, prefix), + query.WithParameters(ydb.ParamsBuilder(). + Param("$seriesData").BeginList().AddItems(series...).EndList(). + Param("$seasonsData").BeginList().AddItems(seasons...).EndList(). + Param("$episodesData").BeginList().AddItems(episodes...).EndList(). + Build(), + ), + ) + + return err + }, + ) +} + +func createTables(ctx context.Context, c query.Client, prefix string) error { + return c.Do(ctx, + func(ctx context.Context, s query.Session) error { + _, _, err := s.Execute(ctx, fmt.Sprintf(` + CREATE TABLE IF NOT EXISTS %s ( + series_id Bytes, + title Text, + series_info Text, + release_date Date, + comment Text, + + PRIMARY KEY(series_id) + ) + `, "`"+path.Join(prefix, "series")+"`"), + query.WithTxControl(query.NoTx()), + ) + if err != nil { + return err + } + + _, _, err = s.Execute(ctx, fmt.Sprintf(` + CREATE TABLE IF NOT EXISTS %s ( + series_id Bytes, + season_id Bytes, + title Text, + first_aired Date, + last_aired Date, + + PRIMARY KEY(series_id,season_id) + ) + `, "`"+path.Join(prefix, "seasons")+"`"), + query.WithTxControl(query.NoTx()), + ) + if err != nil { + return err + } + + _, _, err = s.Execute(ctx, fmt.Sprintf(` + CREATE TABLE IF NOT EXISTS %s ( + series_id Bytes, + season_id Bytes, + episode_id Bytes, + title Text, + air_date Date, + + PRIMARY KEY(series_id,season_id,episode_id) + ) + `, "`"+path.Join(prefix, "episodes")+"`"), + query.WithTxControl(query.NoTx()), + ) + if err != nil { + return err + } + + return nil + }, + ) +} diff --git a/internal/cmd/bench/native/table/README.md b/internal/cmd/bench/native/table/README.md new file mode 100644 index 0000000..81eb2a4 --- /dev/null +++ b/internal/cmd/bench/native/table/README.md @@ -0,0 +1,8 @@ +# Benchmark for tests prometheus metrics + +## Run + +```shell +% go build -o bench-query . +% ./bench-query -ydb-url=grpc://localhost:2136/local -prometheus-url=http://localhost:8080 -threads=50 +``` \ No newline at end of file diff --git a/internal/cmd/bench/native/main.go b/internal/cmd/bench/native/table/main.go similarity index 100% rename from internal/cmd/bench/native/main.go rename to internal/cmd/bench/native/table/main.go