-
Notifications
You must be signed in to change notification settings - Fork 509
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
288 additions
and
56 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,37 +1,18 @@ | ||
FROM node:lts AS base | ||
FROM golang:1.23-alpine AS builder | ||
|
||
RUN npm i -g pnpm turbo bun | ||
RUN corepack enable | ||
|
||
|
||
FROM base AS builder | ||
|
||
# Set working directory | ||
WORKDIR /unkey | ||
WORKDIR /go/src/github.com/unkeyed/unkey/apps/chproxy | ||
COPY go.mod ./ | ||
# COPY go.sum ./ | ||
# RUN go mod download | ||
|
||
COPY . . | ||
RUN turbo prune chproxy --docker | ||
|
||
# Add lockfile and package.json's of isolated subworkspace | ||
FROM base AS installer | ||
WORKDIR /unkey | ||
|
||
# First install dependencies (as they change less often) | ||
COPY .gitignore .gitignore | ||
COPY --from=builder /unkey/out/json/ . | ||
COPY --from=builder /unkey/out/pnpm-lock.yaml ./pnpm-lock.yaml | ||
RUN pnpm install | ||
|
||
# Build the project and its dependencies | ||
COPY --from=builder /unkey/out/full/ . | ||
COPY turbo.json turbo.json | ||
|
||
RUN pnpm turbo build --filter=chproxy... | ||
RUN go build -o bin/chproxy ./main.go | ||
|
||
FROM base AS runner | ||
WORKDIR /unkey | ||
|
||
COPY --from=installer /unkey . | ||
FROM golang:1.23-alpine | ||
WORKDIR /usr/local/bin | ||
COPY --from=builder /go/src/github.com/unkeyed/unkey/apps/chproxy/bin/chproxy . | ||
|
||
WORKDIR /unkey/apps/chproxy | ||
CMD bun run ./src/main.ts | ||
CMD [ "/usr/local/bin/chproxy"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
module github.com/unkeyed/unkey/apps/chproxy | ||
|
||
go 1.23.2 | ||
|
||
require ( | ||
github.com/influxdata/tdigest v0.0.1 // indirect | ||
github.com/josharian/intern v1.0.0 // indirect | ||
github.com/mailru/easyjson v0.7.7 // indirect | ||
github.com/tsenart/vegeta v12.7.0+incompatible // indirect | ||
golang.org/x/net v0.31.0 // indirect | ||
golang.org/x/text v0.20.0 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= | ||
github.com/influxdata/tdigest v0.0.1 h1:XpFptwYmnEKUqmkcDjrzffswZ3nvNeevbUSLPP/ZzIY= | ||
github.com/influxdata/tdigest v0.0.1/go.mod h1:Z0kXnxzbTC2qrx4NaIzYkE1k66+6oEDQTvL95hQFh5Y= | ||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= | ||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= | ||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= | ||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= | ||
github.com/tsenart/vegeta v12.7.0+incompatible h1:sGlrv11EMxQoKOlDuMWR23UdL90LE5VlhKw/6PWkZmU= | ||
github.com/tsenart/vegeta v12.7.0+incompatible/go.mod h1:Smz/ZWfhKRcyDDChZkG3CyTHdj87lHzio/HOCkbndXM= | ||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | ||
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= | ||
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= | ||
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= | ||
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= | ||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||
gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= | ||
gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"encoding/base64" | ||
"fmt" | ||
"io" | ||
"log" | ||
"net/http" | ||
"net/url" | ||
"os" | ||
"os/signal" | ||
"strings" | ||
"syscall" | ||
"time" | ||
) | ||
|
||
const ( | ||
MAX_BUFFER_SIZE = 50000 | ||
MAX_BATCH_SIZE = 10000 | ||
FLUSH_INTERVAL = time.Second * 3 | ||
) | ||
|
||
var ( | ||
CLICKHOUSE_URL string | ||
BASIC_AUTH string | ||
PORT string | ||
) | ||
|
||
func init() { | ||
|
||
CLICKHOUSE_URL = os.Getenv("CLICKHOUSE_URL") | ||
if CLICKHOUSE_URL == "" { | ||
panic("CLICKHOUSE_URL must be defined") | ||
} | ||
BASIC_AUTH = os.Getenv("BASIC_AUTH") | ||
if BASIC_AUTH == "" { | ||
panic("BASIC_AUTH must be defined") | ||
} | ||
PORT = os.Getenv("PORT") | ||
if PORT == "" { | ||
PORT = "7123" | ||
} | ||
} | ||
|
||
type Batch struct { | ||
Rows []string | ||
Params url.Values | ||
} | ||
|
||
func persist(batch *Batch) error { | ||
if len(batch.Rows) == 0 { | ||
return nil | ||
} | ||
|
||
u, err := url.Parse(CLICKHOUSE_URL) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
u.RawQuery = batch.Params.Encode() | ||
|
||
req, err := http.NewRequest("POST", u.String(), strings.NewReader(strings.Join(batch.Rows, "\n"))) | ||
if err != nil { | ||
return err | ||
} | ||
req.Header.Add("Content-Type", "text/plain") | ||
username := u.User.Username() | ||
password, ok := u.User.Password() | ||
if !ok { | ||
return fmt.Errorf("password not set") | ||
} | ||
req.SetBasicAuth(username, password) | ||
|
||
client := http.Client{} | ||
res, err := client.Do(req) | ||
if err != nil { | ||
return err | ||
} | ||
defer res.Body.Close() | ||
|
||
if res.StatusCode == http.StatusOK { | ||
log.Printf("GOLANG persisted %d rows for %s\n", len(batch.Rows), batch.Params.Get("query")) | ||
} else { | ||
body, err := io.ReadAll(res.Body) | ||
if err != nil { | ||
return err | ||
} | ||
fmt.Println("unable to persist", string(body)) | ||
} | ||
return nil | ||
} | ||
|
||
func main() { | ||
requiredAuthorization := "Basic " + base64.StdEncoding.EncodeToString([]byte(BASIC_AUTH)) | ||
|
||
buffer := make(chan *Batch, MAX_BUFFER_SIZE) | ||
// blocks until we've persisted everything and the process may stop | ||
done := make(chan bool) | ||
|
||
go func() { | ||
|
||
buffered := 0 | ||
|
||
batchesByParams := make(map[string]*Batch) | ||
|
||
ticker := time.NewTicker(FLUSH_INTERVAL) | ||
|
||
flushAndReset := func() { | ||
for _, batch := range batchesByParams { | ||
err := persist(batch) | ||
if err != nil { | ||
log.Println("Error flushing:", err.Error()) | ||
} | ||
} | ||
buffered = 0 | ||
batchesByParams = make(map[string]*Batch) | ||
ticker.Reset(FLUSH_INTERVAL) | ||
} | ||
for { | ||
select { | ||
case b, ok := <-buffer: | ||
if !ok { | ||
// channel closed | ||
flushAndReset() | ||
done <- true | ||
return | ||
} | ||
|
||
params := b.Params.Encode() | ||
batch, ok := batchesByParams[params] | ||
if !ok { | ||
batchesByParams[params] = b | ||
} else { | ||
|
||
batch.Rows = append(batch.Rows, b.Rows...) | ||
} | ||
|
||
buffered += len(b.Rows) | ||
|
||
if buffered >= MAX_BATCH_SIZE { | ||
log.Println("Flushing due to max size") | ||
flushAndReset() | ||
} | ||
case <-ticker.C: | ||
log.Println("Flushing from ticker") | ||
|
||
flushAndReset() | ||
} | ||
} | ||
}() | ||
|
||
http.HandleFunc("/v1/liveness", func(w http.ResponseWriter, r *http.Request) { | ||
w.Write([]byte("ok")) | ||
}) | ||
|
||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { | ||
if r.Header.Get("Authorization") != requiredAuthorization { | ||
log.Println("invaldu authorization header, expected", requiredAuthorization, r.Header.Get("Authorization")) | ||
http.Error(w, "unauthorized", http.StatusUnauthorized) | ||
return | ||
} | ||
|
||
query := r.URL.Query().Get("query") | ||
if query == "" || !strings.HasPrefix(strings.ToLower(query), "insert into") { | ||
http.Error(w, "wrong query", http.StatusBadRequest) | ||
return | ||
} | ||
|
||
params := r.URL.Query() | ||
params.Del("query_id") | ||
|
||
body, err := io.ReadAll(r.Body) | ||
if err != nil { | ||
http.Error(w, "cannot read body", http.StatusInternalServerError) | ||
} | ||
rows := strings.Split(string(body), "\n") | ||
|
||
buffer <- &Batch{ | ||
Params: params, | ||
Rows: rows, | ||
} | ||
|
||
w.Write([]byte("ok")) | ||
}) | ||
|
||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) | ||
defer stop() | ||
|
||
fmt.Println("listening on", PORT) | ||
if err := http.ListenAndServe(fmt.Sprintf(":%s", PORT), nil); err != nil { | ||
log.Fatalln("error starting server:", err) | ||
} | ||
|
||
<-ctx.Done() | ||
log.Println("shutting down") | ||
close(buffer) | ||
<-done | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.