mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 10:54:59 +00:00
* removes unused filesize function * removes ioutil usage * updates ioutil.ReadAll to io.ReadAll * updates ioutil.TempFile to os.CreateTemp * updates ioutil.TempDir to os.MkdirTemp * updates ioutil.ReadAll to os.ReadAll * update ioutil.WriteFile to os.WriteFile * updates ioutil.Discard to io.Discard * updates ioutil.ReadDir to os.ReadDir where applicable * removes unused code in idk * creates type to use for context value keys * replaces assert.Nil with assert.NoError for error checks
253 lines
5.4 KiB
Go
253 lines
5.4 KiB
Go
// Copyright 2022 Molecula Corp. (DBA FeatureBase).
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
package main
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bytes"
|
|
"compress/gzip"
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
gohttp "net/http"
|
|
"net/url"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
pilosa "github.com/featurebasedb/featurebase/v3"
|
|
"github.com/featurebasedb/featurebase/v3/encoding/proto"
|
|
pnet "github.com/featurebasedb/featurebase/v3/net"
|
|
"github.com/featurebasedb/featurebase/v3/vprint"
|
|
)
|
|
|
|
// slurp: slurp is a load-tester for importing bulk data.
|
|
// It allows us to measure write performance.
|
|
|
|
type stateMachine struct {
|
|
viewData map[string][]byte
|
|
lastIndex string
|
|
lastField string
|
|
lastShard uint64
|
|
state string
|
|
client *pilosa.InternalClient
|
|
start time.Time
|
|
|
|
profile string
|
|
host string
|
|
}
|
|
|
|
func (r *stateMachine) NewHeader(h *tar.Header, tr *tar.Reader) error {
|
|
parts := strings.Split(h.Name, "/")
|
|
switch parts[0] {
|
|
case "roaring":
|
|
index := parts[1]
|
|
field := parts[2]
|
|
view := parts[4]
|
|
shard, err := strconv.ParseUint(parts[6], 10, 64)
|
|
vprint.PanicOn(err)
|
|
if index != r.lastIndex || field != r.lastField || shard != r.lastShard {
|
|
err := r.Upload()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
roaringData, err := io.ReadAll(tr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, already := r.viewData[view]; already {
|
|
panic(fmt.Sprintf("view '%v' already present!", view))
|
|
}
|
|
r.viewData[view] = roaringData
|
|
r.lastIndex = index
|
|
r.lastField = field
|
|
r.lastShard = shard
|
|
case "bolt":
|
|
if r.state == "roaring" {
|
|
err := r.Upload()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
vprint.VV("Finished import %v", time.Since(r.start))
|
|
|
|
if r.profile != "" {
|
|
stopProfile(r.host, r.profile)
|
|
}
|
|
}
|
|
//
|
|
uri := GetImportRoaringURI(r.lastIndex, r.lastShard)
|
|
|
|
switch v := parts[len(parts)-1]; v {
|
|
case "keys":
|
|
index := parts[1]
|
|
fieldName := parts[2]
|
|
if fieldName == "_keys" {
|
|
//skip index keys are not not real fields so will have no need for field keys
|
|
return nil
|
|
|
|
}
|
|
|
|
byteData, err := io.ReadAll(tr)
|
|
vprint.PanicOn(err)
|
|
readerFunc := func() (io.Reader, error) {
|
|
return bytes.NewReader(byteData), nil
|
|
}
|
|
err = r.client.ImportFieldKeys(context.Background(), uri, index, fieldName, false, readerFunc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
vprint.VV("%v", h.Name)
|
|
index := parts[1]
|
|
partition, err := strconv.ParseUint(v, 10, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
byteData, err := io.ReadAll(tr)
|
|
vprint.PanicOn(err)
|
|
readerFunc := func() (io.Reader, error) {
|
|
return bytes.NewReader(byteData), nil
|
|
}
|
|
|
|
err = r.client.ImportIndexKeys(context.Background(), uri, index, int(partition), false, readerFunc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
r.state = parts[0]
|
|
return nil
|
|
}
|
|
func (r *stateMachine) Upload() error {
|
|
if len(r.viewData) > 0 {
|
|
request := &pilosa.ImportRoaringRequest{
|
|
Views: r.viewData,
|
|
}
|
|
uri := GetImportRoaringURI(r.lastIndex, r.lastShard)
|
|
err := r.client.ImportRoaring(context.Background(), uri, r.lastIndex, r.lastField, r.lastShard, false, request)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r.viewData = make(map[string][]byte)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func UploadTar(srcFile string, client *pilosa.InternalClient, profile, host string) error {
|
|
f, err := os.Open(srcFile)
|
|
if err != nil {
|
|
return (err)
|
|
}
|
|
defer f.Close()
|
|
var tarReader *tar.Reader
|
|
if strings.HasSuffix(srcFile, "gz") {
|
|
gzf, err := gzip.NewReader(f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tarReader = tar.NewReader(gzf)
|
|
} else {
|
|
tarReader = tar.NewReader(f)
|
|
}
|
|
runner := &stateMachine{
|
|
viewData: make(map[string][]byte),
|
|
start: time.Now(),
|
|
profile: profile,
|
|
host: host,
|
|
}
|
|
runner.client = client
|
|
for {
|
|
header, err := tarReader.Next()
|
|
if err == io.EOF {
|
|
_ = runner.Upload()
|
|
break
|
|
}
|
|
if err != nil {
|
|
vprint.PanicOn(err)
|
|
}
|
|
err = runner.NewHeader(header, tarReader)
|
|
vprint.PanicOn(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func main() {
|
|
var host string
|
|
var profile string
|
|
var tarSrcPath string
|
|
flag.StringVar(&host, "host", "127.0.0.1:10101", "host to import into")
|
|
flag.StringVar(&profile, "profile", "", "profile and save a cpu profile of the import to this file")
|
|
flag.StringVar(&tarSrcPath, "src", "q2.tar.gz", "data to import")
|
|
flag.Parse()
|
|
|
|
uri, err := pnet.NewURIFromAddress(host)
|
|
vprint.PanicOn(err)
|
|
globURI = uri
|
|
|
|
h := &gohttp.Client{}
|
|
if profile != "" {
|
|
startProfile(host)
|
|
}
|
|
c, err := pilosa.NewInternalClient(host, h, pilosa.WithSerializer(proto.Serializer{}))
|
|
vprint.PanicOn(err)
|
|
|
|
t0 := time.Now()
|
|
println("uploading", tarSrcPath)
|
|
vprint.PanicOn(UploadTar(tarSrcPath, c, profile, host))
|
|
vprint.VV("total elapsed '%v'", time.Since(t0))
|
|
}
|
|
|
|
func startProfile(host string) {
|
|
cli := &gohttp.Client{}
|
|
req := &gohttp.Request{
|
|
Method: "GET",
|
|
URL: &url.URL{
|
|
Scheme: "http",
|
|
Host: host,
|
|
Path: "/cpu-profile/start",
|
|
},
|
|
}
|
|
resp, err := cli.Do(req)
|
|
if err != nil {
|
|
if resp != nil {
|
|
resp.Body.Close()
|
|
}
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func stopProfile(host, outfile string) {
|
|
cli := &gohttp.Client{}
|
|
req := &gohttp.Request{
|
|
Method: "GET",
|
|
URL: &url.URL{
|
|
Scheme: "http",
|
|
Host: host,
|
|
Path: "/cpu-profile/stop",
|
|
},
|
|
}
|
|
resp, err := cli.Do(req)
|
|
if err != nil {
|
|
if resp != nil {
|
|
resp.Body.Close()
|
|
}
|
|
panic(err)
|
|
}
|
|
|
|
fd, err := os.Create(outfile)
|
|
vprint.PanicOn(err)
|
|
defer fd.Close()
|
|
_, err = io.Copy(fd, resp.Body)
|
|
vprint.PanicOn(err)
|
|
|
|
}
|
|
|
|
var globURI *pnet.URI
|
|
|
|
// get correct node to go to.
|
|
func GetImportRoaringURI(index string, shard uint64) *pnet.URI {
|
|
return globURI
|
|
}
|