Slide 1

Slide 1 text

Robert Egorov Личная боль при работе с БД в Go

Slide 2

Slide 2 text

Тернистый карьерный путь - Кодер - Программист - Архитектор - Продуктовый менеджер - Директор продукта - CTO - Мама! Я стартапер!

Slide 3

Slide 3 text

О компании «АБИЭС эскос» Умное управление уличным освещением - Стартап - Разрабатываем облачное ПО на Go+PostgreSQL+ReactJS - Разрабатываем ПО контроллеров группы светильников на Go - Разрабатываем собственную MESH радио сеть для передачи данных в диапазоне 868 MHz - Делаем дизайн и производим собственное железо в Китае - Зарабатываем на аутсорсе, всё заработанное вкладываем в стартап Lighting Management System

Slide 4

Slide 4 text

Выбор языка программирования Java, Питон 2.х-3.х, D, Ruby, Node, .NET, Go - Компилируемый и межплатформенный x86, AMD64, ARM - Windows и Linux - Статическая типизация - Автоматическое управление памятью - Богатая стандартная библиотека - Простой язык - Принудительная обработка ошибок - Быстрая компиляция - Встроенный тулинг - Статические зависимости Отсутствие дженериков Отсутствие дженериков

Slide 5

Slide 5 text

package main import “fmt” func main() { fmt.Println("hello world") } $ go run hello-world.go hello world $ go build hello-world.go $ ls hello-world hello-world.go $ ./hello-world hello world

Slide 6

Slide 6 text

package main import ( "fmt" "net/http" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, you've requested: %s\n", r.URL.Path) }) http.ListenAndServe(":8081", nil) } $curl http://localhost:8081/ Hello, you've requested: /

Slide 7

Slide 7 text

И понеслось - Монолит или микросервисы? Ложиться под полноценный фреймворк или создавать свое? Beego или go-kit? - Как организовать AAA - Какой выбрать router? - Что передавать в контекстах запросов - Где начинаются и заканчиваются принципы 12 fapp - Какой логгер выбрать? - Стандартного типа error не достаточно - Вебсокеты - Graceful shutdown - Generics - И куча других вопросов

Slide 8

Slide 8 text

А работа с БД? - Есть пакет database/sql в стандартной библиотеке!

Slide 9

Slide 9 text

import ( "database/sql" “github.com/lib/pq" ) //... db, err := sql.Open("postgres", "user=robert password=12345678 dbname=pg sslmode=verify-full") if err != nil { panic(err) } … . defer db.Close()

Slide 10

Slide 10 text

type Person struct { ID int Name string LastName string } var person Person sqlStatement := `SELECT id, name, last_name FROM persons WHERE id=$1` personID := 1 row := db.QueryRow(sqlStatement, personID) err := row.Scan(&peron.ID, &person.Name, &person.LastName) if err != nil { if err == sql.ErrNoRows { fmt.Println("Zero rows found") } else { panic(err) } }

Slide 11

Slide 11 text

type Person struct { ID int Name string LastName string } var ( person Person arr []Person ) rows, err := db.Query("SELECT id, name, last_name FROM persons") defer rows.Close() for rows.Next() { err = rows.Scan(&person.ID, &person.Name, &person.LastName) if err != nil { // обработка ошибки при чтении данных в переменные } arr = append(arr, row) } err = rows.Err() if err != nil { // обработка ошибки которая возникла в результате итерации по записям }

Slide 12

Slide 12 text

Многовато писанины?

Slide 13

Slide 13 text

Ищем ORM

Slide 14

Slide 14 text

УРА! 11 тысяч разрабов не могут ошибаться!

Slide 15

Slide 15 text

Библиотека - огонь! - Есть все! - Отличная документация - Если чего то нет, то может вам это не надо  - Одна строчка заменяет «портянку» при использовании прямого database/sql! - БЕРЕМ!

Slide 16

Slide 16 text

type Person struct { ID int Name string LastName string } var ( person Person arr []Person ) db, err := gorm.Open("postgres", "user=robert password=12345678 dbname=pg") personID := 1 db.Where(“id = $1", personID).First(&person) db.Find(&arr) defer db.Close()

Slide 17

Slide 17 text

Кажется мы нашли счастье! - Лаконично - Расширенные тэги для обозначения значений по умолчанию - Поддержка миграций - JOIN-ы - Хуки перед CRUD операциями - Поддержка особенностей PG. Например ON CONFLICT при INSERT - Chaining: db.Where("name LIKE $1", “Rob %").Find(&persons, "id IN ($2)", []int{1, 2, 3}).Count(&count)

Slide 18

Slide 18 text

Отрезвление - Если нужно считать все поля таблицы кроме одного - Если нужно изменить только некоторые поля таблицы - Много разных способов добиться одинакового результата - Сложные SQL запросы сначала пишутся и отлаживаются в SQL Manager, потом приходится разбивать на цепочки функций gorm - Админы жалуются на «мусорность» генерируемых SQL запросов - Нагрузка на Garbage collector

Slide 19

Slide 19 text

GORM – огонь? Да. Сжечь! - Ищем альтернативы - Не смотрим на гитхаб звезды - Рыщем в интернетах как голодные …

Slide 20

Slide 20 text

Да их здесь сотни! - Классические ORM: gorp, beego orm, go-pg, xorm, qbs*, hood*, dbr, buffalo pop** - ORM с кодогенерацией : reform, kallax - Микро ORM: sqlx, dat*, genmai*, scaneo* - REST2SQL конвертер: prest

Slide 21

Slide 21 text

Пишем тесты бенчмарков на SELECT - gorm - database/sql - sqlx - reform - dbr - В таблице ~160 тысяч записей - Выбираем два полня ID, NAME из таблицы с 20 полями

Slide 22

Slide 22 text

func BenchmarkGormSelectAll(b *testing.B) { db, err := gorm.Open("postgres", "host=127.0.0.1 port=5432 dbname=etsoft sslmode='disable’) if err != nil { b.Fatal(err) } cnt := 0 b.ResetTimer() for i := 0; i < b.N; i++ { var arr []Patient err := db.Model(&Patient{}).Find(&arr).Error if err != nil { b.Fatal(err) } cnt += len(arr) } b.StopTimer() } $ go test -bench . --benchmem

Slide 23

Slide 23 text

$ go test -bench . –benchmem BenchmarkGormSelectAll-8 1 1306587788 ns/op 430774992 B/op 10007 590 allocs/op BenchmarkSQLSelectAll-8 10 124044727 ns/op 26426735 B/op 754 311 allocs/op BenchmarkSqlXSelectAll-8 10 172360602 ns/op 38596978 B/op 1056 045 allocs/op BenchmarkReformSelectAll-8 10 141932390 ns/op 32363486 B/op 1056 070 allocs/op BenchmarkDBRSelectAll-8 5 260183127 ns/op 38598811 B/op 1056 075 allocs/op

Slide 24

Slide 24 text

Готовим требования и кальян - Database model first - Миграции не нужны - Не писать SQL для типовых CRUD операций - Минимизировать нагрузку на GC - Уметь возможность выборочно игнорировать поля в разных запросах без создания промежуточных структур - Регистрировать время получения первой записи от СУБД и длительность чтения всех записей - Для CRU* операций отправлять в базу данных запросы содержащие только требуемые поля - В журнале помечать запросы выполняющиеся в рамках одной транзакции

Slide 25

Slide 25 text

Принцип Database Model First - Описываем базу данных в дизайнере - Отрисовываем все Foreign Keys! - Группируем таблицы по функциональным группам (микросервисам) для наглядности - Генерируем SQL для создания таблиц и других объектов БД - Автоматически заполняем собственную таблицу с метаданными - Генерируем описание типов данных Go из метаданных таблиц

Slide 26

Slide 26 text

Тэги Как способ внесения управляемой декларативности и магии

Slide 27

Slide 27 text

-- create_objects.sql CREATE TABLE persons ( id int8 not null, name varchar(32), last_name text not null, blob bytea, CONSTRAINT persons_pk PRIMARY KEY (id) comment on column persons.name is ‘Имя. Правила: maxlen:32, trim'; // domain.go type Person struct { ID int `json:”id”` Name string `json:”name” dbw:”maxlen:32,trim”` LastName string `json:”last_name”` Blob []byte `json:”blob” dbw:”nocache,blob”` } .. . type PersonBalance struct { ID int `json:”id” dbw:”noseq”` // не нашли sequence который имеет имя person_balances_seq Balance int `json:”balance”` } type PersonWallet struct { ID string `json:”id”` Name string `json:”name” dbw:”maxlen:20”` Reserved int `json:”reserved” dbw:”noins”` Available int `json:”available” dbw:”min:0”` }

Slide 28

Slide 28 text

Дорисовываем оставшуюся часть совы! - Обертка вокруг стандартного пакета database/sql - Говно и палки! - Сразу вкатываем в реальные проекты, что бы библиотека быстрее приобрела форму - Небольшие неудобства в использовании без ущерба производительности допустимо!

Slide 29

Slide 29 text

type Person struct { ID int `json:”id”` Name string `json:”name”` LastName string `json:”last_name”` Blob []byte `json:”blob” dbw:”nocache,blob”` } var ( row Patient arr []Patient ) tbl := table.NewSimple(db, "patients", &Patient{}) // чтение всех полей таблицы err := tbl.DoSelect (func() error { arr = append(arr, row); return nil }, &row); // обработка ошибки // чтение всех полей, кроме полей имеющих тэг “blob” err := tbl.DoSelectEx (“blob”, dbw.Exclude, func() error { arr = append(arr, row); return nil }, &row); // чтение только полей имеющих тэг blob err := tbl.DoSelectEx (“blob”, dbw.Include, func() error { arr = append(arr, row); return nil }, &row); // чтени полей, которые имеют тэг “blob” и обработка в процессе выборки записей err := tbl.DoSelectEx (“blob”, dbw.Include, func() error { // делаем что то, с полем row.Blob без сохранения return nil }, &row);

Slide 30

Slide 30 text

func BenchmarkDBWSelectAll(b *testing.B) { db, err := dbw.Open("postgres", "host=127.0.0.1 port=5432 dbname=etsoft sslmode='disable') if err != nil { b.Fatal(err) } cnt := 0 b.ResetTimer() for i := 0; i < b.N; i++ { var ( row Patient arr []Patient ) tbl := table.NewSimple(db, "patients", &Patient{}) if err := tbl.DoSelect(func() error { arr = append(arr, row); return nil }, &row); err != nil { b.Fatal(err) } cnt += len(arr) } b.StopTimer() } //func (t *Table) DoSelect(f func() error, row interface{}) error { // cols := t.fieldAddrsSelect(row, dbw.TagNoCache, dbw.Exclude) // return t.db.QueryContext(t.ctx, t.SQL.SelectCache).Fetch(f, cols...).Err() //}

Slide 31

Slide 31 text

$ go test -bench . –benchmem BenchmarkGormSelectAll-8 1 1306587788 ns/op 430774992 B/op 10007 590 allocs/op BenchmarkSQLSelectAll-8 10 124044727 ns/op 26426735 B/op 754 311 allocs/op BenchmarkSqlXSelectAll-8 10 172360602 ns/op 38596978 B/op 1056 045 allocs/op BenchmarkReformSelectAll-8 10 141932390 ns/op 32363486 B/op 1056 070 allocs/op BenchmarkDBRSelectAll-8 5 260183127 ns/op 38598811 B/op 1056 075 allocs/op BenchmarkDBWSelectAll-8 10 122501868 ns/op 25435496 B/op 603 512 allocs/op

Slide 32

Slide 32 text

Присоединяйтесь, будет интересно - Ищем единомышленников - Палок уже меньше, другое же еще остается - Будем отдавать в open source - Студенты в поисках open source проекта? - Испытали похожие боли при выборе ORM?

Slide 33

Slide 33 text

Егоров Роберт, ABIES escos Twitter: @robert_egorov Telegram: @regorov Спасибо