在我从代码创建它之后,Golang 代码没有插入到 BigQuery 的表中

标签 go google-bigquery gcloud

我有一个具有此架构的 BigQuery 表:

name    STRING  NULLABLE    
age     INTEGER NULLABLE    
amount  INTEGER NULLABLE

我可以用这段代码成功地插入到表中:

ctx := context.Background()
client, err := bigquery.NewClient(ctx, projectID)
if err != nil {
    log.Fatal(err)
}

u := client.Dataset(dataSetID).Table("test_user").Uploader()

savers := []*bigquery.StructSaver{
    {Struct: test{Name: "Sylvanas", Age: 23, Amount: 123}},
}

if err := u.Put(ctx, savers); err != nil {
    log.Fatal(err)
}
fmt.Printf("rows inserted!!")

这很好用,因为表已经在 bigquery 上创建了,我现在想做的是删除表(如果存在)并通过代码重新创建它:

type test struct {
    Name   string
    Age    int
    Amount int
}

if err := client.Dataset(dataSetID).Table("test_user").Delete(ctx); err != nil {
    log.Fatal(err)
}

fmt.Printf("table deleted")

t := client.Dataset(dataSetID).Table("test_user")

// Infer table schema from a Go type.
schema, err := bigquery.InferSchema(test{})

if err := t.Create(ctx,
    &bigquery.TableMetadata{
        Name:           "test_user",
        Schema:         schema,
    }); err != nil {
    log.Fatal(err)
}

fmt.Printf("table created with the test schema")

这也非常有效,因为删除表并使用从我的结构测试中推断出的模式创建它。

当我尝试在删除/创建过程之后执行上述插入时,问题就来了。没有错误被抛出,但它没有插入数据(如果我评论删除/创建部分,插入工作正常)。

我做错了什么? 我是否需要以某种方式提交创建表事务才能插入,或者我是否需要关闭 DDBB 连接?

最佳答案

根据这个old answer ,最多可能需要 2 分钟才能将 BigQuery 流缓冲区正确附加到已删除并立即重新创建的表。

我已经运行了一些测试,在我的情况下,只需要几秒钟就可以使用表格,而不是在其他问题上报告的 2~5 分钟。生成的代码与您的完全不同,但概念应该适用。

我尝试的是,不是直接插入行,而是将它们添加到缓冲 channel 上,然后等到您可以验证当前表是否正确保存了值,然后再开始发送它们。

我使用了一个非常简单的结构来运行我的测试(因此编写代码更容易):

type Row struct {
    ByteField []byte
}

我按以下方式生成我的行:

func generateRows(rows chan<- *Row) {
    for {
            randBytes := make([]byte, 100)
            _, _ = rand.Read(randBytes)
            rows <- &row{randBytes}
            time.Sleep(time.Millisecond * 500) // use whatever frequency you need to insert rows at
    }
}

请注意我是如何将行发送到 channel 的。您无需生成它们,只需从数据源中获取它们即可。

下一部分是找到一种方法来检查表是否正确保存了行。我所做的是尝试将缓冲行之一插入表中,恢复该行,并验证是否一切正常。如果该行未正确返回,则将其发送回缓冲区。

func unreadyTable(rows chan *row) bool {
    client, err := bigquery.NewClient(context.Background(), project)
    if err != nil {return true}

    r := <-rows // get a row to try to insert       
    uploader := client.Dataset(dataset).Table(table).Uploader()
    if err := uploader.Put(context.Background(), r); err != nil {rows <- r;return true}

    i, err := client.Query(fmt.Sprintf("select * from `%s.%s.%s`", project, dataset, table)).Read(context.Background())
    if err != nil {rows <- r; return true}
    var testRow []bigquery.Value
    if err := i.Next(&testRow); err != nil {rows <- r;return true}
    if reflect.DeepEqual(&row{testrow[0].([]byte)}, r) {return false} // there's probably a better way to check if it's equal
    rows <- r;return true
}

有了这样的功能,我们只需要添加for ; unreadyTable(rows); time.Sleep(time.Second) {}阻塞直到可以安全地插入行。

最后,我们把所有东西放在一起:

func main() {

    // initialize a channel where the rows will be sent
    rows := make(chan *row, 1000) // make it big enough to hold several minutes of rows

    // start generating rows to be inserted
    go generateRows(rows)

    // create the BigQuery client
    client, err := bigquery.NewClient(context.Background(), project)
    if err != nil {/* handle error */}

    // delete the previous table
    if err := client.Dataset(dataset).Table(table).Delete(context.Background()); err != nil {/* handle error */}

    // create the new table
    schema, err := bigquery.InferSchema(row{})
    if err != nil {/* handle error */}
    if err := client.Dataset(dataset).Table(table).Create(context.Background(), &bigquery.TableMetadata{Schema: schema}); err != nil {/* handle error */}

    // wait for the table to be ready
    for ; unreadyTable(rows); time.Sleep(time.Second) {}

    // once it's ready, upload indefinitely
    for {
            if len(rows) > 0 { // if there are uninserted rows, create a batch and insert them
                    uploader := client.Dataset(dataset).Table(table).Uploader()
                    insert := make([]*row, min(500, len(rows))) // create a batch of all the rows on buffer, up to 500
                    for i := range insert {insert[i] = <-rows}
                    go func(insert []*row) { // do the actual insert async
                            if err := uploader.Put(context.Background(), insert); err != nil {/* handle error */}
                    }(insert)
            } else { // if there are no rows waiting to be inserted, wait and check again
                    time.Sleep(time.Second)
            }
    }
}

注意:自 math.Min()不喜欢整数,我不得不包括func min(a,b int)int{if a<b{return a};return b} .

这是我的 full working example .

关于在我从代码创建它之后,Golang 代码没有插入到 BigQuery 的表中,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51022028/

相关文章:

python - Google Cloud Platform 配额监控(警报)

bash - 无法在 Mac 上安装 Delve Go 调试器

Go区分大小写吗?

shell - 不输入该如何输入Golang中Stdin的输入字段?

google-bigquery - 如何在 Big Query Web UI 中显示创建表

python - windows 10下安排python脚本加载数据到BigQuery

python - 如何将base64图像上传到GCloud存储

string - 在 go 中使用从 []byte 到 string 的不安全转换可能产生的后果是什么?

google-analytics - google.com 中的缺失值 :analytics-bigquery:LondonCycleHelmet. ga_sessions_20130910

sockets - gcloud 计算 : configure firewall for external traffic