Задать вопрос
Ответы пользователя по тегу Go
  • Почему не запускаются миграции?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Обычно это означает, что была попытка миграции ранее и произошла ошибка во время миграции.
    Например у вас всего одна миграция (версия 1) с созданием таблицы `users` как в вашем случае.
    Вы запускаете команду up, а в файле с миграциями ошибка.
    В это случае таблица `users` в базе не создалась, а таблицу schema_migrations команда `migrate` уже создала.
    И не просто создала, а и записала туда, что у вас база соответствует версии 1 и установила флажок `dirty`.
    SELECT * FROM schema_migrations;
    +---------+-------+
    | version | dirty |
    +---------+-------+
    |     1   |   1   |
    +---------+-------+
    1 row in set (0.00 sec)


    По хорошему надо проверить какие миграции у вас выполнены, а какие нет.
    Т.е. нужно понять какой версии сейчас соответствует то, что в базе.

    После того, как придёт понимание - нужно будет указать версию, которой соответствует база.
    migrate -path $PATH_TO_YOUR_MIGRATIONS -database $YOUR_DATABASE_URL force $VERSION

    Где $VERSION - это номер версии, которой соответствует структура базы.
    Если у вас всего одна таблица и вы уже исправили ошибку в файле миграции - можете просто удалить таблицу `schema_migrations` и дальше не читать :)

    После выполнения команды с параметрами force $VERSION флаг "dirty" будет снят и можно будет вызвать опять вашу команду с `up` и она будет успешно выполнена.
    Ответ написан
  • Как в map добавить slice?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Нужно изменить тип map на map[string]interface{}
    Вот так
    message := map[string]interface{}{
        "cmd":       "subscribe",
        "auth_key":  "dfb496cca67e13b",
        "needed_bk": []string {"live"},
      }
    Ответ написан
    6 комментариев
  • Правильно ли делать сервис менеджер в Go?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    > В UserService может использоваться логика ProjectService'a. Для этого я передаю в userService.New(projectService ProjectService)

    По хорошему в UserService не должно быть логики ProjectService.
    Логику лучше не разносить по разным местам, такой код очень трудно потом поддерживать.
    Представьте, что через год придёт новый человек и внесёт правки в ProjectService, он может не знать/забыть внести нужные правки в UserService.

    Если без этого совсем никак, тогда лучше сделать 3й сервис, который в конструкторе примет сервисы Users и Project и будет вызывать нужные методы из каждого из них.

    В идеале нужно перестроиться и проектировать пакеты так, чтобы таких проблем не возникало.
    Но когда вы перестроитесь - результаты работы будут намного качественнее.
    Мне после Perl, Python понадобилось не мало времени чтобы перестроиться :))
    Но сейчас могу с уверенностью сказать оно того точно стоило!

    Подход как у вас с ServiceManager тоже используется, даже видел библиотеки готовые, но уже не помню как назывались. Я попробовал их, у меня не прижились эти библиотеки )

    Сейчас я использую такой подход:
    users/user.go
    тут описываю структуру Users, простая валидация полей, методы у структуры типа GetFullName и т.д.

    users/repo.go
    тут описываю интерфейсы:
    QueryRepo для извлечения данных
    CommandRepo для внесения изменения в данные пользователей
    репозитории используются только в рамках пакета users, другие пакеты используют интерфейсы сервисов

    users/querysvc.go
    тут описываю интерфейс QuerySvc (только для извлечения данных связанных с пользователей),
    он уже использует интерфейс QueryRepo
    этот пакет будут импортировать другие пакеты

    users/commandsvc.go
    тут описываю интерфейс CommandSvc (только для внесения изменений в данные пользователей), он тоже использует интерфейсы QueryRepo, CommandRepo
    этот пакет будут импортировать другие пакеты

    users/signupsvc.go
    это сервис регистрации пользователей, тут описываю интерфейс SignupSvc

    users/repos/mysql/query.go
    тут реализация интерфейса QueryRepo на MySQL
    этот пакет будет импортироваться только в пакете `app` (см. ниже)

    users/repos/mysql/command.go
    тут реализация интерфейса CommandRepo на MySQL
    этот пакет будет импортироваться только в пакете `app` (см. ниже)

    users/services/querysvc.go
    тут реализация интерфейса QuerySvc

    users/services/commandsvc.go
    тут реализация интерфейса CommandSvc

    users/services/signupsvc.go
    тут реализация интерфейса SignupSvc

    core/
    тут все константы, которые могут быть использованы в разных пакетах приложения (например коды ошибок)
    этот пакет могут включать любые пакеты, но он ничего не импортирует в рамках проекта

    app/
    тут иницализация всех репозиториев, сервисов, конфигов и т.д.
    здесь же происходит связывание всех пакетов.
    Т.е. что-то вроде
    usersQuerySvc := usersservices.NewQuerySvc(usersRepo, ...)
    usersSignupSvc := usersservices.NewSignupSvc(usersQuerySvc, usersCommandSvc)
    // например сервис для админов (с проверкой полномочий), методы которого уже можно использовать в endpoint'ax
    usersAdminSvc := usersservices.NewAdminSvc(usersQuerySvc, usersCommandSvc)
    
    projectSvc := projectservices.New(usersQuerySvc)

    Мне такой подход очень нравится тем, что нет скрытых зависимостей, всё видно как на ладони, кто и что использует.
    А если возникают сложности с применением такой схемы - для меня это верный признак того, что нужно спроектировать иначе, раз сложности уже возникли, значит в будущем их будет еще больше ))

    Ну и все пакеты как параметры принимают везде только интерфейсы.
    Хотя... с точки зрения Dependency Inversion, правильно бы было описывать под каждый пакет свой интерфейс, а не так, как я :), но я делаю интерфейсы очень маленькими, тогда получается очень гибко и, как мне кажется, после этого еще внутри других пакетов описывать интерфейсы уже избыточно в моём случае, работы больше, а толку не так много, по этому пока экономлю на этом время :)

    cmd/serve.go - импортирует app и запускает веб сервер например

    Если нужны будут еще какие то подробности - пишите, буду рад помочь
    Ответ написан
    Комментировать
  • Как правильно записать ListenAndServeTLS() с кастомным типом servemux?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Вы вызываете метод ListenAndServeTLS для структуры Server, у него всего два параметра.
    Четыре параметра у метода ListenAndServeTLS для пакета http.

    Вам нужно добавить параметр Handler: mux в структуру, и вызвать метод ListenAndServeTLS структуры Server. У вас должно получиться примерно так:
    func main() {
        var mux = http.NewServeMux()
        mux.HandleFunc("/", indexPage)
        var serv = &http.Server{
            // добавляете параметр
            Handler:      mux,
            Addr:         serverPort,
            ReadTimeout:  15 * time.Second,
            WriteTimeout: 15 * time.Second,
        }
        // вызываете с двумя параметрами
        log.Fatal(serv.ListenAndServeTLS(TLScert, TLSkey))
    }
    Ответ написан
  • Как корректно написать функцию getBlobReader?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Вам нужно реализовать свой io.Reader, и в Read уже вычитывать данные по очереди из частей файла.
    Вот работающий пример реализации
    package main
    
    import (
        "io"
        "log"
        "os"
        "path/filepath"
    )
    
    type BlobReader struct {
        keys       []string
        currentKey uint64
        reader     io.ReadCloser
    }
    
    func NewBlobReader(keys []string) *BlobReader {
        return &BlobReader{
            keys: keys,
        }
    }
    
    // Read реализация интерфейса io.Reader, чтобы можно было ваш reader использовать в io.Copy
    func (br *BlobReader) Read(p []byte) (int, error) {
        var err error
    
        // открываем каждый файл по очереди
        if br.reader == nil {
            filePath := filepath.Join(".", "blobs", br.keys[br.currentKey])
            br.reader, err = os.Open(filePath)
            if err != nil {
                return 0, err
            }
        }
    
        // читаем данные из текущего открытого файла, пока данные не закончатся
        n, err := br.reader.Read(p)
    
        // если данные в файле закончились, закрываем его
        if err == io.EOF {
            br.currentKey++
            br.reader.Close()
            br.reader = nil
    
            // io.EOF в err должно вернуть только у последнего файла, чтобы io.Copy считал все файлы и не завис на последнем.
            if br.currentKey < uint64(len(br.keys)) {
                err = nil
            }
        }
    
        return n, err
    }
    
    func main() {
        blobReader := NewBlobReader([]string{
            "2050-part1",
            "2050-part2",
            "2050-part3",
        })
    
        _, err := io.Copy(os.Stdout, blobReader)
        if err != nil {
            log.Fatalln(err)
        }
    
        log.Println("Done")
    }

    Еще нужно будет реализовать метод Close у вашего Reader'a чтобы небыло утечек файловых дескрипторов
    Ответ написан
    4 комментария
  • Как оповестить пачку горутин о завершении работы?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Попробуйте вот так
    package main
    
    import (
        "context"
        "fmt"
        "sync"
    )
    
    func worker(ctx context.Context, worderID int, data chan int, wg *sync.WaitGroup) {
        defer wg.Done()
    
        fmt.Printf("worker %d started\n", worderID)
        for {
            fmt.Printf("worker %d enter for\n", worderID)
            select {
            case <-ctx.Done():
                fmt.Printf("worker %d cancelled\n", worderID)
                return
            case v, ok := <-data:
                fmt.Printf("worker %d got data: %v, ok: %v\n", worderID, v, ok)
                if !ok {
                    fmt.Printf("worker %d data channel was closed\n", worderID)
                    return
                }
            }
        }
    }
    
    func main() {
        var wg sync.WaitGroup
        ctx, cancel := context.WithCancel(context.Background())
        channels := make([]chan int, 10)
    
        for i := 0; i < 10; i++ {
            wg.Add(1)
            channels[i] = make(chan int)
            go worker(ctx, i, channels[i], &wg)
        }
    
        for i := 0; i < 10; i++ {
            channels[i] <- i
        }
    
        cancel()
        wg.Wait()
    }
    Ответ написан
  • Почему нельзя вернуть так структуру?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    В реальной жизни, скорее всего, задачу придёться решать немного иначе.
    Обычно вам приходят данные в виде структур, например, когда вы извлекаете данные из базы.
    В момент получения данных вам нужно просто заполнить слайс интерфейсов этими структурами (которые реализуют методы интерфейса)

    Сделал пример того, как задача может быть решена

    https://play.golang.org/p/RKrCLIqakXg

    package main
    
    import "fmt"
    
    // объявляем интерфейс
    type Profile interface {
    	GetFullName() string
    }
    
    // структура данных, которая будет реализовывать интерфейс Profile
    type User struct {
        Name string
        LastName string
    }
    
    // реализация метода интерфейса Profile
    func (u User) GetFullName() string {
    	return u.Name + ` ` + u.LastName
    }
    
    // пример функции, которая извлекает/формирует данные и возвращает слайс интерфейсов
    func getProfiles() []Profile {
            // создаём слайс интерфейсов
    	var result = make([]Profile, 10)
    
    	for i := 0; i<10; i++ {
    	    // но заполняем слайс структурами, которые реализуют этот интерфейс
    	    result[i] = User{
    	        Name: fmt.Sprintf("Name%d", i),
    	        LastName: fmt.Sprintf("LastName%d", i),
    	    }
    	}
    
    	return result
    }
    
    // пример функции, которая работает со слайсом интерфейсов.
    func printProfiles(profiles []Profile) {
        for _, profile := range profiles {
            fmt.Println(profile.GetFullName())
        }
    }
    
    func main() {
    	printProfiles(getProfiles())
    }
    Ответ написан
    Комментировать
  • Golang vksdk, проблема с типом данных, как решить?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    вместо b.MessageIDs(obj.Message.ID) напишите b.MessageIDs([]int{obj.Message.ID})
    Ответ написан
  • Как структурировать JSON объект?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Так как вы делаете не получится :)

    Вы получаете данные, которые нужно преобразовать из JSON в структуру данных Go).
    и дальше с ними работать, а вы преобразовываете JSON (слайс байтов) в слайс байтов.

    Вот рабочий пример того, что вы хотите сделать
    package main
    
    import (
        "encoding/json"
        "fmt"
        "io/ioutil"
        "log"
        "net/http"
    )
    
    func main() {
        artist := "Madonna"
        token := "5936fb55e90cdd9938f8e7086c783c40"
        url := fmt.Sprintf(
            "http://ws.audioscrobbler.com/2.0/?method=artist.getinfo&artist=%s&api_key=%s&format=json", artist, token)
        res, err := http.Get(url)
        if err != nil {
            log.Fatal(err)
        }
        defer res.Body.Close()
    
        body, _ := ioutil.ReadAll(res.Body)
    
        var data map[string]interface{}
        // десериализуете JSON
        err = json.Unmarshal(body, &data)
        if err != nil {
            log.Fatal(err)
        }
    
        // сериализуете данные в JSON
        marshaledDataBytes, err := json.MarshalIndent(data, "", "\t")
        fmt.Println(string(marshaledDataBytes))
    }
    Ответ написан
    8 комментариев
  • Ошибка: package ....... is not in GOROOT. Как решить?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Лучше установить пакет так, как написано в его инструкции.
    Выполните в командной строке
    go get github.com/adshao/go-binance/v2

    Ну и импорт потом вот так
    import (
        "github.com/adshao/go-binance/v2"
    )
    Ответ написан
    2 комментария
  • Как установить pkg-config?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Скачайте бинарники + зависимости тут ftp.gnome.org/pub/gnome/binaries/win32/dependencies
    pkg-config_0.26-1_win32.zip
    glib_2.28.8-1_win32.zip
    gettext-runtime_0.18.1.1-2_win32.zip

    Распакуйте их в одну из папок, которая у вас $PATH прописана, или в отдельную папку и пропишите к ней путь в $PATH

    А лучше поставьте Cygwin https://cygwin.com/install.html, у вас отпадёт еще куча разных проблем
    Ответ написан
    Комментировать
  • Как сделать в Go, чтобы какая-то goroutine выполнялась циклично до тех пор, пока state не станет false?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    В вашей реализации надпись "I am doing this!" будет показана только один раз, т.к. вы пишите всего 1 раз в канал, а потом выполнение заблокируется на чтении данных из канала, т.к. там данных не будет.

    То, что вы хотите сделать можно сделать вот так:
    package main
    
    import (
        "log"
        "time"
    )
    
    func doSomethingEvery5Seconds(ch chan bool) {
        log.Println("Started!")
        for {
            select {
            case <-ch:
                return
            default:
                log.Println("I am doing this!")
                time.Sleep(1 * time.Second)
            }
        }
    }
    
    func main() {
        ch := make(chan bool, 1)
        go doSomethingEvery5Seconds(ch)
        time.Sleep(15 * time.Second)
        close(ch) // Останавливаем спустя 15 секунд
    }
    Ответ написан
    2 комментария
  • Почему db.Find сьедает одно из значений таблицы?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    99%, что дело в названиях полей
    Если из базы вы извлекаете данные в структуру Post - допишите там db:"userId", чтобы получилось примерно так
    UserId int `json:"userId" db:"userId"`
    или для gorm
    UserId int `json:"userId" gorm:"column:userId"`
    Ответ написан
    2 комментария
  • Можно ли как-то перехватить команды, которые исполняются через терминал в ubuntu?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Там храниться только история команд, которые были введены в bash.
    Если у вас будет другая shell - список команд в .bash_history вы не увидите.

    Например у меня может запускаться bash сразу после входа в систему, но я потом запущу, например, zsh.
    bash.history будет пустым.
    Это я к тому, что даже если вы сделаете свою shell, то её можно будет легко обойти.

    Знаю как минимум одно реальное решение, которое будет работать.
    Делаете модуль к ядру Linux, там перехватываете системный вызов exec и всё, что с этим связано и тогда обойти вас будет уже очень не просто.

    Я делал нечто подобное для Unix FreeBSD лет 20 назад :))
    Ответ написан
    Комментировать
  • Почему Возвращает букву?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Потому, что 65 - это код английской буквы "A".

    Если вы хотите чтобы отобразило 65, нужно использовать либо модуль strconv, либо fmt.
    package main
    
    import "fmt"
    import "strconv"
    
    func main() {
      var i int64 = 65
      fmt.Println(strconv.FormatInt(i, 10))
      // или так
      fmt.Printf("%d\n", i)
    }
    Ответ написан
    2 комментария
  • Как записать данные полученные в формате JSON в БД?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Обратите внимание, что вы запрашиваете один пост, но в ответ приходит массив.
    Это видно по скобкам вначале и конце ответа "[" .... "]",
    [
      {
        "userId": 1,
        "id": 1,
        "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
        "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
      }
    ]

    Т.е. декодировать ответ нужно как массив, а не отдельный пост

    Исправил ошибки и отрефакторил ваш код.
    Глобальные переменные тоже лучше не использовать.
    Надеюсь так станет немного понятнее :)
    Если будет что-то не понятно - пишите, постараюсь помочь.

    В общем вот так будет работать.
    package main
    
    import (
        "database/sql"
        "encoding/json"
        "log"
        "net/http"
    
        _ "github.com/go-sql-driver/mysql"
    )
    
    type Post struct {
        Userid int    `json:"userId"`
        ID     int    `json:"id"`
        Title  string `json:"title"`
        Body   string `json:"body"`
    }
    
    func main() {
        posts, err := fetchPosts("https://jsonplaceholder.typicode.com/posts?id=1")
        if err != nil {
            log.Fatal(err)
        }
    
        db, err := sql.Open("mysql", "root:password@/Posts")
        if err != nil {
            log.Fatal(err)
        }
    
        err = savePosts(db, posts)
        if err != nil {
            log.Fatal(err)
        }
    }
    
    func fetchPosts(url string) ([]Post, error) {
        resp, err := http.Get(url)
        if err != nil {
            return nil, err
        }
    
        var posts []Post
        err = json.NewDecoder(resp.Body).Decode(&posts)
        return posts, err
    }
    
    func savePosts(db *sql.DB, posts []Post) error {
        for _, post := range posts {
            _, err := db.Exec("insert into post (id, user_id, title, body) values(?, ?, ?, ?)",
                post.ID, post.Userid, post.Title, post.Body)
            if err != nil {
                return err
            }
        }
    
        return nil
    }
    Ответ написан
    22 комментария
  • Где можно использовать горутины в Go?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Самый простой пример - это HTTP сервер.
    Когда к вам приходит HTTP запрос - создаётся горутина и каждый запрос спокойно обрабатывается, никто никого не ждёт. Т.е. сервер может обрабатывать параллельно несколько запросов как раз за счёт горутин.

    Фактически, почти во всех сетевых сервисах есть смысл использовать горутины для распараллеливания обработки запросов (кроме epoll, kqueue и т.п.)

    Еще хороший пример.
    Вам нужно сделать парсер Авито :)) или индесатор сайтов.
    Т.е. задача сводится к тому, что нужно будет постоянно качать страницы с сайтов.
    Если вы будете качать одну страницу за другой - вы будете очень долго качать.
    А так вы можете сделать пул из нескольких горутин (как минимум по 1 на поток процессора) и получите параллельное скачивание страниц.

    Для ускорения работы вычислений (параллельные вычисления) есть смысл использовать горутины только по 1 на поток процессора). Т.е. в случаях, когда у вас огромный объём данных и нужно максимально ускорить его обработку. Например у вас файл 100Гб, и 8 ядер в процессоре - вы можете разбить файл на 8 частей и параллельно эти части обработать (в самом простом варианте, в реальной жизни нужно будет просто по частям считывать новые данные и скармливать свободной горутине)

    Еще когда нужно сделать чтобы какая нибудь задача выполнялась в "фоне".
    Например периодическая очистка кеша/устаревший записей в базе.

    Реальных примеров намного больше :)
    В целом их есть смысл использовать там, где нужно параллельное выполнение какой либо задачи или разных задач параллельно :)
    Ответ написан
    2 комментария
  • Как разбить GO проект на отдельные файлы, и потом подключать их в main?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Можно сделать через `go mod`, примерно вот так:

    1. Создаёте папку yourproject.com/ там, где вам удобно
    2. cd yourproject.com/ ; go mod init yourproject.com
    3. Создаёте пакет pkg1 внутри yourproject.com, т.е. yourproject.com/pkg1
    4. Создаёте main.go
    5. Запускаете командой go run .

    Структура проекта
    yourproject.com/main.go
    yourproject.com/go.mod (появится после выполнения команды go mod init)
    yourproject.com/pkg1/pkg1.go


    yourproject.com/main.go
    package main
    
    import "yourproject.com/pkg1"
    
    func main() {
        pkg1.SomeFunc()
    }


    yourproject.com/pkg1/pkg1.go
    package pkg1
    
    import "fmt"
    
    func SomeFunc() {
        fmt.Println(`SomeFunc called`)
    }
    Ответ написан
    6 комментариев
  • Как организовать тесты в пакетах вида адаптер?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    У меня в некоторых проектах схема очень похожа на вашу.
    Я выношу тесты и инициализацию в папку repo.
    Структура папок выглядит таким образом (беру пакет users для примера)
    users/repo.go - описание интерфейса Repo
    users/repo/repo.go - тут инициализация любого типа реализации в зависимости от конфига
    users/repo/repo_test.go - тут тесты
      (создаю все типы репозиториев (mongo, redis...)) и одними и теми же тестами прогоняю каждый тип. 
      Тут и инициализация репозиториев для тестов и зачистка после тестов.)
    users/repo/mongodb - реализация (импортирует пакет users при необходимости)
    users/repo/redis
    users/repo/sqlite

    В приложении (там, где идёт инициализация) импортируется только пакет "users/repo", он (на основе конфига) подключается базе нужного типа.
    А все остальные пакеты (если кому нужен интерфейс users.Repo) - импортируют пакет users
    Ответ написан
    5 комментариев
  • В каких приложениях Go существенно эффективнее чем Node.js и PHP?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Бенчмарки - это хорошо, но очень важно понимать что именно там меряли и почему результаты именно такие.

    Несколько лет назад я тоже делал бенчмарки Python, PHP, Node, Go.
    Для меня были важны две вещи:
    1 - скорость ответа сервера/кол-во запросов в секунду
    2 - объём сервиса в памяти, т.к. от этого зависит стоимость ресурсов

    На тесте, где сервисы не делали запросы в базу - из всех четверых лучше всего отработал Go с приличным отрывом, цифры, к сожалению, уже не помню.

    Но вся эта разница сошла на нет, как только добавился всего один простой SQL запрос в базу, в таблицу с 10 строками. И на этом фоне разница по скорости ответа была меньше 10%.

    Иными словами если ваш сервис работает с базой - критической разницы по скорости работы между Go/Rust/PHP/Node/Java, особо не будет.

    Другое дело если ваш сервис не будет делать запросы в базу, или будет кешировать результаты запросов, тогда вы почувствуете ощутимую разницу.

    Еще очень важно понимать сколько ваш скрипт потребляет ресурсов. Это становится критически важным, когда вы имеете дело с большими нагрузками.

    Один экземпляр Go занимал в памяти порядка 6мб RAM, при том, что Pytho+Django порядка 60мб.
    Node уже не помню сколько, но что-то близкое к Python'у.

    Вот тут уже, когда серверов у вас будет много - количество серверов с Go у вас будет в 10 раз меньше, соответственно расходы за эти сервера у вас будут в 10 раз меньше :)

    В крупных компаниях, где нужны специалисты по Go - там уже есть высокие нагрузки и переход на Go и поиск специалиста обусловлен необходимостью решить в первую очередь проблему с нагрузками, оптимизировать расходы на оборудование.

    Где-то читал статью, что у людей было API на порядка 40 серверов на Node, после переписывания на Go - серверов осталось два, из которых второй запасной :)
    Ответ написан
    13 комментариев