Удивительно высокие задержки для выбора / вставки в spanner

#google-cloud-spanner

#google-облако-spanner

Вопрос:

Я получаю задержки около 50-100 мс для простых запросов в spanner (обновления или выбор по первичному ключу). Подключение к spanner из того же проекта / региона. Это ожидаемое поведение? Я ожидал гораздо меньших задержек для них.

Комментарии:

1. Не могли бы вы опубликовать пример запроса? Кроме того, каково время запроса в консоли Google.

Ответ №1:

Нет, задержка для простого выбора с использованием первичного ключа должна быть намного ниже.

Я провел быстрый тест на основе информации, предоставленной вами выше, используя следующую простую программу:

 package main

import (
    "context"
    "fmt"
    "math/rand"
    "time"

    "cloud.google.com/go/spanner"
    "github.com/montanaflynn/stats"
    "google.golang.org/api/iterator"
)

func main() {
    fmt.Printf("Simple Spanner benchmarking...n")
    source := rand.NewSource(time.Now().UnixNano())
    rnd := rand.New(source)
    client, err := spanner.NewClient(context.Background(), "projects/my-project/instances/my-instance/databases/my-database")
    if err != nil {
        fmt.Printf("Client creation failed: %v", err)
        return
    }
    var times stats.Float64Data
    for i := 0; i < 25; i   {
        id := rnd.Int63n(1000)   100000
        statement := spanner.NewStatement("SELECT * FROM Singers WHERE SingerId=@id")
        statement.Params["id"] = id
        start := time.Now()
        iter := client.Single().Query(context.Background(), statement)
        for {
            row, err := iter.Next()
            if err == iterator.Done {
                break
            }
            if err != nil {
                fmt.Printf("Query failure: %v", err)
                break
            }
            var fullName string
            row.ColumnByName("FullName", amp;fullName)
            fmt.Printf("Singer name: %sn", fullName)
            elapsed := time.Since(start)
            fmt.Printf("Time: %vn", elapsed)
            times = append(times, float64(elapsed.Milliseconds()))
        }
        iter.Stop()
    }
    median, _ := stats.Median(times)
    avg, _ := stats.Mean(times)
    p90, _ := stats.Percentile(times, 90)
    fmt.Printf("Median: %vn", median)
    fmt.Printf("P90: %vn", p90)
    fmt.Printf("Avg: %vn", avg)
}

 

Приложение было выполнено на минимально возможной виртуальной машине Google Cloud Compute Engine, расположенной в том же регионе, что и экземпляр Spanner. Результаты были:

 Simple Spanner benchmarking...
Singer name: FirstName LastName 100960
Time: 374.627846ms
Singer name: FirstName LastName 100865
Time: 4.102019ms
Singer name: FirstName LastName 100488
Time: 3.479059ms

...

Singer name: FirstName LastName 100542
Time: 3.986866ms
Singer name: FirstName LastName 100822
Time: 3.978838ms
Singer name: FirstName LastName 100235
Time: 4.511711ms
Singer name: FirstName LastName 100020
Time: 3.476673ms
Singer name: FirstName LastName 100234
Time: 3.191529ms
Singer name: FirstName LastName 100219
Time: 4.451639ms

Median: 3
P90: 4
Avg: 18.44
 

Таким образом, ваше время выполнения около 50-100 мс звучит как много. Обычное время выполнения в этом (простом) тестовом примере составляет около 3-4 мс для однорядного выбора (за исключением первого запроса, поскольку это также инициализирует резервный пул сеансов).

  • Может ли быть так, что ваша таблица имеет первичный ключ, который использует монотонно возрастающее значение? Это может создать горячие точки в индексе поддержки первичного ключа.
  • Может быть, вы закрываете и создаете нового клиента между каждым запросом? Это потребовало бы повторной инициализации пула сеансов для каждого нового запроса?
  • Используете ли вы одноразовую транзакцию только для чтения для своих запросов? Или вы используете какой-то другой тип транзакции для чтения данных?

Не могли бы вы предоставить некоторые дополнительные сведения о том, как именно вы выполняете запрос (желательно с примером кода)?

  • Вы используете клиентскую библиотеку? Если да, то какой? (Java, Node, Go, …?)
  • Вы измеряете только самый первый запрос, который выполняете после запуска вашего приложения? Самый первый запрос будет выполняться медленнее, чем последующие запросы, поскольку клиентской библиотеке потребуется сначала создать сеанс, а затем выполнить запрос.
  • Вы пишете, что подключаетесь из одного и того же проекта / региона. Означает ли это, что ваш клиентский код выполняется на виртуальной машине Google Cloud или аналогичной ей?

Комментарии:

1. Использование библиотеки golang. Измерение всех запросов (это долго работающее приложение / сервис). Да, он работает на старомодном вычислительном экземпляре.

2. Я получаю более точные данные об этом (более случайные). Похоже, когда я извлекаю жестко закодированный первичный ключ (который никогда не меняется), он должен быть кэширован, и в этом случае задержки колеблются около 20 мс. Это то, что люди обычно видят? Я меняю его на выбор случайных клавиш, чтобы увидеть эффекты без возможного кэширования.

3. Спасибо за подробный ответ! Я пытаюсь выделить хороший пример из рабочего приложения, немного позже опубликую здесь автономный пример. Чтобы ответить на дополнительные вопросы: Я пытался избежать ситуации с горячими точками, но позвольте мне получить конкретный запрос и структуру таблицы. Почти уверен, что этот клиент используется повторно.