Comments 2
Это очень плохо и самое ужасное это работа с БД.
Каждый запрос в БД это упрощённо
* отправка запроса по протоколу БД
* парсинг запроса, синтаксический разбор
* поиск запроса в кэше запросов
* если нет, то составление плана запроса
* выделение ресурсов на выполнение запроса
* поиск данных в буферном кэше
* чтение отсутствующих с диска
* отправка запроса по протоколу БД
+ Постгре версионник значит на транзакцию идёт снапшот и чем быстрее завершим её, тем меньше ресурсов потребуется при конкурентной работе
На примере: createDept
C Go не работал, поэтому буду писать на псевдо языке и с PostrgeSQL тоже мало работал, поэтому могут быть синтаксические ошибки, но общий смысл будет понятен
т.е. сначала выполнится запрос «SELECT 1 FROM dept WHERE deptno = $1»
если он не вернёт данные, то вставим
INSERT INTO dept (deptno, dname, loc) VALUES (:deptno, :dname, :loc)
проверим кол-во строк, а потом запросим по уникальности вставленный IDшник
сделаем лучше
идём дальше
т.е. в цикле начинаем выполнять createEmp, который не описан, но я так понимаю тоже предполагается что там выполняются все этапы: проверка, вставка, повторное извлечение. Если у нас 10 дочерних записей, то будет выполнено 30 запросов, хотя раз у нас есть json, просто в тексте, то его легко скормить Постгресу, он ещё оч круто оптимизирован на работу с JSON'ом
Запрос будт что-то вида:
т.е. суммарно заменили 30 запросов к БД 1м запросом, ещё на ходу и JSON распарсили, и кучу всяких не нужных структур из кода можно выкинуть и памяти тонну освободить…
ну и в конце у Вас идёт getDept который двумя запросами возвращает мастера и детеил
ну тут опять можно соптимизить, получить всё одним запросом, да ещё и сразу в готовом JSON'е который тут же и вернуть без всякого дополнительного оверхэда
Дак о чём это я… Я простейшими модификациями, базовым SQL-ем сократил кол-во кода, уменьшил кол-во запросов с условных ~35 до 4х, ну и работать это будет раз в 10-50 быстрее. Это ещё без всяких специфичных для постгреса оптимизаций, хотя он вроде всё это умеет:
строка->JSON->multi_table_insert + returning array->select->agregate_JSON
и всё это в одном запросе! но это уже тонкий тюнинг
В общем напишите 1 такой сервис, выкиньте весь тот оверхэд с типами, надстройками, моделями, убедитесь что всё это будет работать в десятки раз быстрее на более простом железе.
Вот кста хорошая лекция о Постгре, тут немного про другое, про то как хорошо они заточили работу с JSON, но опять же на более простом железе показывают лучшую производительность
www.youtube.com/watch?v=SNzOZKvFZ68
Далее про кэширование, тут я не на 100% уверен, но тоже видится, что команда постгреса над которым работают десятки и сотни человек и не один десяток лет, наверняка лучше справились с этой задачей. Не надо изобретать велосипед, лучше настроить и потюнить инструмент, который специально для этого предназначен
Каждый запрос в БД это упрощённо
* отправка запроса по протоколу БД
* парсинг запроса, синтаксический разбор
* поиск запроса в кэше запросов
* если нет, то составление плана запроса
* выделение ресурсов на выполнение запроса
* поиск данных в буферном кэше
* чтение отсутствующих с диска
* отправка запроса по протоколу БД
+ Постгре версионник значит на транзакцию идёт снапшот и чем быстрее завершим её, тем меньше ресурсов потребуется при конкурентной работе
На примере: createDept
C Go не работал, поэтому буду писать на псевдо языке и с PostrgeSQL тоже мало работал, поэтому могут быть синтаксические ошибки, но общий смысл будет понятен
{ // Проверим, существует ли строка по натуральному уникальному ключу UK
var foo int
exists, myerr := s.db.Get(reqID, tx, "DeptExists", &foo, in.Deptno)
if myerr != nil {
return myerr
}
if exists {
return myerror.New("4004", "Error create - row already exists: reqID, Deptno", reqID, in.Deptno).PrintfInfo()
}
} // Проверим, существует ли строка по натуральному уникальному ключу UK
{ // Выполняем вставку и получим значение суррогатного PK
rows, myerr := s.db.Exec(reqID, tx, "CreateDept", in)
if myerr != nil {
return myerr
}
// проверим количество обработанных строк
if rows != 1 {
return myerror.New("4004", "Error create: reqID, Deptno, rows", reqID, in.Deptno, rows).PrintfInfo()
}
// считаем созданный объект - в БД могли быть триггера, которые меняли данные
// запрос делаем по UK, так как суррогатный PK мы еще не знаем
exists, myerr := s.db.Get(reqID, tx, "GetDeptUK", newDept, in.Deptno)
if myerr != nil {
return myerr
}
if !exists {
return myerror.New("4004", "Row does not exists after creating: reqID, Deptno", reqID, in.Deptno).PrintfInfo()
}
т.е. сначала выполнится запрос «SELECT 1 FROM dept WHERE deptno = $1»
если он не вернёт данные, то вставим
INSERT INTO dept (deptno, dname, loc) VALUES (:deptno, :dname, :loc)
проверим кол-во строк, а потом запросим по уникальности вставленный IDшник
сделаем лучше
{ // Выполняем ВСЁ: вставку, проверку и получим значение суррогатного PK
rows, myerr := s.db.Prepare("
INSERT INTO dept (deptno, dname, loc)
VALUES (:deptno, :dname, :loc)
ON CONFLICT (dept) DO NOTHING
RETURNING id;
")
.QueryRow(in.deptno, in.dname, in.loc)
.Scan(&new_dept_id)
if rows != 1 {
return myerror.New("4004", "Error create - row already exists: reqID, Deptno", reqID, in.Deptno).PrintfInfo()
}
}
идём дальше
{ // Обработаем вложенные объекты в рамках текущей транзакции
if in.Emps != nil {
for _, newEmp := range in.Emps {
// Копируем суррогатный PK во внешний ключ вложенного объекта
newEmp.Deptno = null.Int{sql.NullInt64{int64(newDept.Deptno), true}}
// создаем вложенные объекты
if myerr = s.createEmp(ctx, tx, newEmp, nil); myerr != nil {
return myerr
}
}
}
} // Обработаем вложенные объекты в рамках текущей транзакции
т.е. в цикле начинаем выполнять createEmp, который не описан, но я так понимаю тоже предполагается что там выполняются все этапы: проверка, вставка, повторное извлечение. Если у нас 10 дочерних записей, то будет выполнено 30 запросов, хотя раз у нас есть json, просто в тексте, то его легко скормить Постгресу, он ещё оч круто оптимизирован на работу с JSON'ом
Запрос будт что-то вида:
-- in_json = '[{"empno":"empno1", "ename":"ename1", "job":"job1"....},{"empno":"empno2", "ename":"ename2", "job":"job2"....}....]'
-- new_dept_id
INSERT INTO emp (empno, ename, job, mgr, hiredate, sal, comm, deptno)
WITH d AS (
SELECT json_array_elements(:in_json::json) AS r
)
SELECT r->'empno', r->'ename', r->'job', r->'mgr', r->'hiredate', r->'sal', r->'comm', :deptno FROM d
т.е. суммарно заменили 30 запросов к БД 1м запросом, ещё на ходу и JSON распарсили, и кучу всяких не нужных структур из кода можно выкинуть и памяти тонну освободить…
ну и в конце у Вас идёт getDept который двумя запросами возвращает мастера и детеил
ну тут опять можно соптимизить, получить всё одним запросом, да ещё и сразу в готовом JSON'е который тут же и вернуть без всякого дополнительного оверхэда
Дак о чём это я… Я простейшими модификациями, базовым SQL-ем сократил кол-во кода, уменьшил кол-во запросов с условных ~35 до 4х, ну и работать это будет раз в 10-50 быстрее. Это ещё без всяких специфичных для постгреса оптимизаций, хотя он вроде всё это умеет:
строка->JSON->multi_table_insert + returning array->select->agregate_JSON
и всё это в одном запросе! но это уже тонкий тюнинг
В общем напишите 1 такой сервис, выкиньте весь тот оверхэд с типами, надстройками, моделями, убедитесь что всё это будет работать в десятки раз быстрее на более простом железе.
Вот кста хорошая лекция о Постгре, тут немного про другое, про то как хорошо они заточили работу с JSON, но опять же на более простом железе показывают лучшую производительность
www.youtube.com/watch?v=SNzOZKvFZ68
Далее про кэширование, тут я не на 100% уверен, но тоже видится, что команда постгреса над которым работают десятки и сотни человек и не один десяток лет, наверняка лучше справились с этой задачей. Не надо изобретать велосипед, лучше настроить и потюнить инструмент, который специально для этого предназначен
Спасибо, что очень внимательно прочитали статью. Ваш отзыв очень полезен.
Вы правильно заметили, что это не универсальная библиотека — это шаблон, который нужно адаптировать под конкретную реализацию.
Если ваше решение работает только с PostgreSQL и вам не нужна реализация проверок на слое backend, то в шаблоне нужно правильно реализовать слой DB.
Используйте максимально все возможности PostgreSQL. Библиотека jackc/pgx отлично поддерживает массовую вставку из срезов.
Очень может быть, что вам и Go не нужен — используйте стандартные возможности PostgreSQL.
Sign up to leave a comment.
Шаблон backend сервера на Golang — часть 2 (REST API)