mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-02 04:32:42 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b3c0e937f | ||
|
|
180f267867 | ||
|
|
b21f599cde | ||
|
|
3fba0adc9d | ||
|
|
f8f162aa72 | ||
|
|
6d3e38f439 | ||
|
|
4a0dc883cf | ||
|
|
8ebbf37e8e | ||
|
|
008ac38ebc | ||
|
|
a5fe6e21bc | ||
|
|
3003c9e17e | ||
|
|
31d8907e77 | ||
|
|
7b0a37312d | ||
|
|
623fc3a4fc | ||
|
|
aa31c5422b | ||
|
|
6232041c64 | ||
|
|
72b14a451e | ||
|
|
3400962877 | ||
|
|
113c9ce6a8 | ||
|
|
88a1eee31c | ||
|
|
62c4980754 | ||
|
|
9a5d3e7b31 | ||
|
|
15fc58bfd9 | ||
|
|
706d1a8e22 | ||
|
|
90acfd9394 | ||
|
|
6161d0b05d | ||
|
|
98b519b113 | ||
|
|
efdebf712e | ||
|
|
dc784bf217 | ||
|
|
ffe908371d | ||
|
|
92366134e3 | ||
|
|
e2d30cc3bc | ||
|
|
d185e6beea | ||
|
|
f67b5b20ca | ||
|
|
a7973ed7d1 | ||
|
|
0f2c3648dc | ||
|
|
5bddf0c085 | ||
|
|
65fb8fad99 | ||
|
|
0060a2cf9c | ||
|
|
c29c912bdc | ||
|
|
ad6240f066 | ||
|
|
8802843511 | ||
|
|
3feb66d0a1 |
@@ -3,8 +3,8 @@ module github.com/seaweedfs/seaweedfs
|
||||
go 1.22.0
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.115.1 // indirect
|
||||
cloud.google.com/go/pubsub v1.43.0
|
||||
cloud.google.com/go v0.116.0 // indirect
|
||||
cloud.google.com/go/pubsub v1.45.1
|
||||
cloud.google.com/go/storage v1.45.0
|
||||
github.com/Azure/azure-pipeline-go v0.2.3
|
||||
github.com/Azure/azure-storage-blob-go v0.15.0
|
||||
@@ -32,7 +32,7 @@ require (
|
||||
github.com/go-redsync/redsync/v4 v4.13.0
|
||||
github.com/go-sql-driver/mysql v1.8.1
|
||||
github.com/go-zookeeper/zk v1.0.3 // indirect
|
||||
github.com/gocql/gocql v1.6.0
|
||||
github.com/gocql/gocql v1.7.0
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.4
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
@@ -56,7 +56,7 @@ require (
|
||||
github.com/klauspost/reedsolomon v1.12.4
|
||||
github.com/kurin/blazer v0.5.3
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/linxGnu/grocksdb v1.9.5
|
||||
github.com/linxGnu/grocksdb v1.9.7
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-ieproxy v0.0.11 // indirect
|
||||
@@ -96,30 +96,30 @@ require (
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.5.16
|
||||
go.etcd.io/etcd/client/v3 v3.5.17
|
||||
go.mongodb.org/mongo-driver v1.16.0
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
gocloud.dev v0.40.0
|
||||
gocloud.dev/pubsub/natspubsub v0.39.0
|
||||
gocloud.dev/pubsub/natspubsub v0.40.0
|
||||
gocloud.dev/pubsub/rabbitpubsub v0.40.0
|
||||
golang.org/x/crypto v0.28.0 // indirect
|
||||
golang.org/x/crypto v0.29.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
||||
golang.org/x/image v0.21.0
|
||||
golang.org/x/net v0.30.0
|
||||
golang.org/x/net v0.31.0
|
||||
golang.org/x/oauth2 v0.23.0 // indirect
|
||||
golang.org/x/sys v0.26.0
|
||||
golang.org/x/text v0.19.0 // indirect
|
||||
golang.org/x/tools v0.25.0
|
||||
golang.org/x/sys v0.27.0
|
||||
golang.org/x/text v0.20.0 // indirect
|
||||
golang.org/x/tools v0.27.0
|
||||
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
|
||||
google.golang.org/api v0.199.0
|
||||
google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||
google.golang.org/api v0.203.0
|
||||
google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53 // indirect
|
||||
google.golang.org/grpc v1.67.1
|
||||
google.golang.org/protobuf v1.35.1
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
modernc.org/b v1.0.0 // indirect
|
||||
modernc.org/mathutil v1.6.0
|
||||
modernc.org/memory v1.8.0 // indirect
|
||||
modernc.org/sqlite v1.33.1
|
||||
modernc.org/sqlite v1.34.1
|
||||
modernc.org/strutil v1.2.0
|
||||
modernc.org/token v1.1.0 // indirect
|
||||
)
|
||||
@@ -128,20 +128,20 @@ require (
|
||||
github.com/Jille/raft-grpc-transport v1.6.1
|
||||
github.com/arangodb/go-driver v1.6.4
|
||||
github.com/armon/go-metrics v0.4.1
|
||||
github.com/aws/aws-sdk-go-v2 v1.32.2
|
||||
github.com/aws/aws-sdk-go-v2/config v1.28.0
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.41
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2
|
||||
github.com/aws/aws-sdk-go-v2 v1.32.4
|
||||
github.com/aws/aws-sdk-go-v2/config v1.28.4
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.45
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.66.3
|
||||
github.com/cognusion/imaging v1.0.1
|
||||
github.com/fluent/fluent-logger-golang v1.9.0
|
||||
github.com/getsentry/sentry-go v0.29.1
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/google/flatbuffers/go v0.0.0-20230108230133-3b8644d32c50
|
||||
github.com/hanwen/go-fuse/v2 v2.6.2
|
||||
github.com/hanwen/go-fuse/v2 v2.6.1
|
||||
github.com/hashicorp/raft v1.7.1
|
||||
github.com/hashicorp/raft-boltdb/v2 v2.3.0
|
||||
github.com/orcaman/concurrent-map/v2 v2.0.1
|
||||
github.com/parquet-go/parquet-go v0.23.0
|
||||
github.com/parquet-go/parquet-go v0.23.1-0.20241011155651-6446d1d0d2fe
|
||||
github.com/rabbitmq/amqp091-go v1.10.0
|
||||
github.com/rclone/rclone v1.68.1
|
||||
github.com/rdleal/intervalst v1.4.0
|
||||
@@ -150,20 +150,20 @@ require (
|
||||
github.com/shirou/gopsutil/v3 v3.24.5
|
||||
github.com/tikv/client-go/v2 v2.0.7
|
||||
github.com/ydb-platform/ydb-go-sdk-auth-environ v0.5.0
|
||||
github.com/ydb-platform/ydb-go-sdk/v3 v3.89.2
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.16
|
||||
github.com/ydb-platform/ydb-go-sdk/v3 v3.91.0
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.17
|
||||
go.uber.org/atomic v1.11.0
|
||||
golang.org/x/sync v0.8.0
|
||||
golang.org/x/sync v0.9.0
|
||||
google.golang.org/grpc/security/advancedtls v1.0.0
|
||||
)
|
||||
|
||||
require (
|
||||
cel.dev/expr v0.16.1 // indirect
|
||||
cloud.google.com/go/auth v0.9.5 // indirect
|
||||
cloud.google.com/go/auth v0.9.9 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.5.2 // indirect
|
||||
cloud.google.com/go/iam v1.2.1 // indirect
|
||||
cloud.google.com/go/monitoring v1.21.0 // indirect
|
||||
cloud.google.com/go/monitoring v1.21.1 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 // indirect
|
||||
@@ -190,22 +190,22 @@ require (
|
||||
github.com/andybalholm/cascadia v1.3.2 // indirect
|
||||
github.com/appscode/go-querystring v0.0.0-20170504095604-0126cfb3f1dc // indirect
|
||||
github.com/arangodb/go-velocypack v0.0.0-20200318135517-5af53c29c67e // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.23 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.23 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.23 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sns v1.31.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sqs v1.34.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.32.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.0 // indirect
|
||||
github.com/aws/smithy-go v1.22.0 // indirect
|
||||
github.com/boltdb/bolt v1.3.1 // indirect
|
||||
github.com/bradenaw/juniper v0.15.2 // indirect
|
||||
@@ -243,7 +243,7 @@ require (
|
||||
github.com/go-resty/resty/v2 v2.11.0 // indirect
|
||||
github.com/gofrs/flock v0.8.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
|
||||
github.com/google/s2a-go v0.1.8 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
|
||||
@@ -309,7 +309,6 @@ require (
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/samber/lo v1.47.0 // indirect
|
||||
github.com/segmentio/encoding v0.4.0 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
|
||||
github.com/smartystreets/goconvey v1.8.1 // indirect
|
||||
@@ -336,7 +335,7 @@ require (
|
||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||
github.com/zeebo/errs v1.3.0 // indirect
|
||||
go.etcd.io/bbolt v1.3.10 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.5.16 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.5.17 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.29.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect
|
||||
@@ -347,11 +346,11 @@ require (
|
||||
go.opentelemetry.io/otel/trace v1.29.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/mod v0.21.0 // indirect
|
||||
golang.org/x/term v0.25.0 // indirect
|
||||
golang.org/x/time v0.6.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||
golang.org/x/mod v0.22.0 // indirect
|
||||
golang.org/x/term v0.26.0 // indirect
|
||||
golang.org/x/time v0.7.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect
|
||||
google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
|
||||
@@ -38,8 +38,8 @@ cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRY
|
||||
cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM=
|
||||
cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I=
|
||||
cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY=
|
||||
cloud.google.com/go v0.115.1 h1:Jo0SM9cQnSkYfp44+v+NQXHpcHqlnRJk2qxh6yvxxxQ=
|
||||
cloud.google.com/go v0.115.1/go.mod h1:DuujITeaufu3gL68/lOFIirVNJwQeyf5UXyi+Wbgknc=
|
||||
cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE=
|
||||
cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U=
|
||||
cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4=
|
||||
cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw=
|
||||
cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E=
|
||||
@@ -86,8 +86,8 @@ cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVo
|
||||
cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo=
|
||||
cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0=
|
||||
cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E=
|
||||
cloud.google.com/go/auth v0.9.5 h1:4CTn43Eynw40aFVr3GpPqsQponx2jv0BQpjvajsbbzw=
|
||||
cloud.google.com/go/auth v0.9.5/go.mod h1:Xo0n7n66eHyOWWCnitop6870Ilwo3PiZyodVkkH1xWM=
|
||||
cloud.google.com/go/auth v0.9.9 h1:BmtbpNQozo8ZwW2t7QJjnrQtdganSdmqeIBxHxNkEZQ=
|
||||
cloud.google.com/go/auth v0.9.9/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc=
|
||||
cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0=
|
||||
@@ -290,8 +290,8 @@ cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxs
|
||||
cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg=
|
||||
cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0=
|
||||
cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w=
|
||||
cloud.google.com/go/kms v1.19.0 h1:x0OVJDl6UH1BSX4THKlMfdcFWoE4ruh90ZHuilZekrU=
|
||||
cloud.google.com/go/kms v1.19.0/go.mod h1:e4imokuPJUc17Trz2s6lEXFDt8bgDmvpVynH39bdrHM=
|
||||
cloud.google.com/go/kms v1.20.0 h1:uKUvjGqbBlI96xGE669hcVnEMw1Px/Mvfa62dhM5UrY=
|
||||
cloud.google.com/go/kms v1.20.0/go.mod h1:/dMbFF1tLLFnQV44AoI2GlotbjowyUfgVwezxW291fM=
|
||||
cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic=
|
||||
cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI=
|
||||
cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE=
|
||||
@@ -330,8 +330,8 @@ cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJP
|
||||
cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk=
|
||||
cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4=
|
||||
cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w=
|
||||
cloud.google.com/go/monitoring v1.21.0 h1:EMc0tB+d3lUewT2NzKC/hr8cSR9WsUieVywzIHetGro=
|
||||
cloud.google.com/go/monitoring v1.21.0/go.mod h1:tuJ+KNDdJbetSsbSGTqnaBvbauS5kr3Q/koy3Up6r+4=
|
||||
cloud.google.com/go/monitoring v1.21.1 h1:zWtbIoBMnU5LP9A/fz8LmWMGHpk4skdfeiaa66QdFGc=
|
||||
cloud.google.com/go/monitoring v1.21.1/go.mod h1:Rj++LKrlht9uBi8+Eb530dIrzG/cU/lB8mt+lbeFK1c=
|
||||
cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA=
|
||||
cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o=
|
||||
cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM=
|
||||
@@ -383,8 +383,8 @@ cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjp
|
||||
cloud.google.com/go/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI=
|
||||
cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0=
|
||||
cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8=
|
||||
cloud.google.com/go/pubsub v1.43.0 h1:s3Qx+F96J7Kwey/uVHdK3QxFLIlOvvw4SfMYw2jFjb4=
|
||||
cloud.google.com/go/pubsub v1.43.0/go.mod h1:LNLfqItblovg7mHWgU5g84Vhza4J8kTxx0YqIeTzcXY=
|
||||
cloud.google.com/go/pubsub v1.45.1 h1:ZC/UzYcrmK12THWn1P72z+Pnp2vu/zCZRXyhAfP1hJY=
|
||||
cloud.google.com/go/pubsub v1.45.1/go.mod h1:3bn7fTmzZFwaUjllitv1WlsNMkqBgGUb3UdMhI54eCc=
|
||||
cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg=
|
||||
cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k=
|
||||
cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4=
|
||||
@@ -494,8 +494,8 @@ cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV6
|
||||
cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28=
|
||||
cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y=
|
||||
cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA=
|
||||
cloud.google.com/go/trace v1.11.0 h1:UHX6cOJm45Zw/KIbqHe4kII8PupLt/V5tscZUkeiJVI=
|
||||
cloud.google.com/go/trace v1.11.0/go.mod h1:Aiemdi52635dBR7o3zuc9lLjXo3BwGaChEjCa3tJNmM=
|
||||
cloud.google.com/go/trace v1.11.1 h1:UNqdP+HYYtnm6lb91aNA5JQ0X14GnxkABGlfz2PzPew=
|
||||
cloud.google.com/go/trace v1.11.1/go.mod h1:IQKNQuBzH72EGaXEodKlNJrWykGZxet2zgjtS60OtjA=
|
||||
cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs=
|
||||
cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg=
|
||||
cloud.google.com/go/translate v1.6.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos=
|
||||
@@ -648,46 +648,46 @@ github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJ
|
||||
github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
|
||||
github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=
|
||||
github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
|
||||
github.com/aws/aws-sdk-go-v2 v1.32.2 h1:AkNLZEyYMLnx/Q/mSKkcMqwNFXMAvFto9bNsHqcTduI=
|
||||
github.com/aws/aws-sdk-go-v2 v1.32.2/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 h1:70PVAiL15/aBMh5LThwgXdSQorVr91L127ttckI9QQU=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4/go.mod h1:/MQxMqci8tlqDH+pjmoLu1i0tbWCUP1hhyMRuFxpQCw=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.28.0 h1:FosVYWcqEtWNxHn8gB/Vs6jOlNwSoyOCA/g/sxyySOQ=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.28.0/go.mod h1:pYhbtvg1siOOg8h5an77rXle9tVG8T+BWLWAo7cOukc=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.41 h1:7gXo+Axmp+R4Z+AK8YFQO0ZV3L0gizGINCOWxSLY9W8=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.41/go.mod h1:u4Eb8d3394YLubphT4jLEwN1rLNq2wFOlT6OuxFwPzU=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17 h1:TMH3f/SCAWdNtXXVPPu5D6wrr4G5hI1rAxbcocKfC7Q=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17/go.mod h1:1ZRXLdTpzdJb9fwTMXiLipENRxkGMTn1sfKexGllQCw=
|
||||
github.com/aws/aws-sdk-go-v2 v1.32.4 h1:S13INUiTxgrPueTmrm5DZ+MiAo99zYzHEFh1UNkOxNE=
|
||||
github.com/aws/aws-sdk-go-v2 v1.32.4/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 h1:pT3hpW0cOHRJx8Y0DfJUEQuqPild8jRGmSFmBgvydr0=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6/go.mod h1:j/I2++U0xX+cr44QjHay4Cvxj6FUbnxrgmqN3H1jTZA=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.28.4 h1:qgD0MKmkIzZR2DrAjWJcI9UkndjR+8f6sjUQvXh0mb0=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.28.4/go.mod h1:LgnWnNzHZw4MLplSyEGia0WgJ/kCGD86zGCjvNpehJs=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.45 h1:DUgm5lFso57E7150RBgu1JpVQoF8fAPretiDStIuVjg=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.45/go.mod h1:dnBpENcPC1ekZrGpSWspX+ZRGzhkvqngT2Qp5xBR1dY=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.19 h1:woXadbf0c7enQ2UGCi8gW/WuKmE0xIzxBF/eD94jMKQ=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.19/go.mod h1:zminj5ucw7w0r65bP6nhyOd3xL6veAUMc3ElGMoLVb4=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.10 h1:zeN9UtUlA6FTx0vFSayxSX32HDw73Yb6Hh2izDSFxXY=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.10/go.mod h1:3HKuexPDcwLWPaqpW2UR/9n8N/u/3CKcGAzSs8p8u8g=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21 h1:UAsR3xA31QGf79WzpG/ixT9FZvQlh5HY1NRqSHBNOCk=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21/go.mod h1:JNr43NFf5L9YaG3eKTm7HQzls9J+A9YYcGI5Quh1r2Y=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21 h1:6jZVETqmYCadGFvrYEQfC5fAQmlo80CeL5psbno6r0s=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21/go.mod h1:1SR0GbLlnN3QUmYaflZNiH1ql+1qrSiB2vwcJ+4UM60=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.23 h1:A2w6m6Tmr+BNXjDsr7M90zkWjsu4JXHwrzPg235STs4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.23/go.mod h1:35EVp9wyeANdujZruvHiQUAo9E3vbhnIO1mTCAxMlY0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.23 h1:pgYW9FCabt2M25MoHYCfMrVY2ghiiBKYWUVXfwZs+sU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.23/go.mod h1:c48kLgzO19wAu3CPkDWC28JbaJ+hfQlsdl7I2+oqIbk=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17 h1:Roo69qTpfu8OlJ2Tb7pAYVuF0CpuUMB0IYWwYP/4DZM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17/go.mod h1:NcWPxQzGM1USQggaTVwz6VpqMZPX1CvDJLDh6jnOCa4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.23 h1:1SZBDiRzzs3sNhOMVApyWPduWYGAX0imGy06XiBnCAM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.23/go.mod h1:i9TkxgbZmHVh2S0La6CAXtnyFhlCX/pJ0JsOvBAS6Mk=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 h1:TToQNkvGguu209puTojY/ozlqy2d/SFNcoLIqTFi42g=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0/go.mod h1:0jp+ltwkf+SwG2fm/PKo8t4y8pJSgOCO4D8Lz3k0aHQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.19 h1:FLMkfEiRjhgeDTCjjLoc3URo/TBkgeQbocA78lfkzSI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.19/go.mod h1:Vx+GucNSsdhaxs3aZIKfSUjKVGsxN25nX2SRcdhuw08=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2 h1:s7NA1SOw8q/5c0wr8477yOPp0z+uBaXBnLE0XYb0POA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2/go.mod h1:fnjjWyAW/Pj5HYOxl9LJqWtEwS7W2qgcRLWP+uWbss0=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17 h1:u+EfGmksnJc/x5tq3A+OD7LrMbSSR/5TrKLvkdy/fhY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17/go.mod h1:VaMx6302JHax2vHJWgRo+5n9zvbacs3bLU/23DNQrTY=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2 h1:Kp6PWAlXwP1UvIflkIP6MFZYBNDCa4mFCGtxrpICVOg=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2/go.mod h1:5FmD/Dqq57gP+XwaUnd5WFPipAuzrf0HmupX27Gvjvc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.4 h1:aaPpoG15S2qHkWm4KlEyF01zovK1nW4BBbyXuHNSE90=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.4/go.mod h1:eD9gS2EARTKgGr/W5xwgY/ik9z/zqpW+m/xOQbVxrMk=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.4 h1:tHxQi/XHPK0ctd/wdOw0t7Xrc2OxcRCnVzv8lwWPu0c=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.4/go.mod h1:4GQbF1vJzG60poZqWatZlhP31y8PGCCVTvIGPdaaYJ0=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.4 h1:E5ZAVOmI2apR8ADb72Q63KqwwwdW1XcMeXIlrZ1Psjg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.4/go.mod h1:wezzqVUOVVdk+2Z/JzQT4NxAU0NbhRe5W8pIE72jsWI=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.66.3 h1:neNOYJl72bHrz9ikAEED4VqWyND/Po0DnEx64RW6YM4=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.66.3/go.mod h1:TMhLIyRIyoGVlaEMAt+ITMbwskSTpcGsCPDq91/ihY0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sns v1.31.3 h1:eSTEdxkfle2G98FE+Xl3db/XAXXVTJPNQo9K/Ar8oAI=
|
||||
github.com/aws/aws-sdk-go-v2/service/sns v1.31.3/go.mod h1:1dn0delSO3J69THuty5iwP0US2Glt0mx2qBBlI13pvw=
|
||||
github.com/aws/aws-sdk-go-v2/service/sqs v1.34.3 h1:Vjqy5BZCOIsn4Pj8xzyqgGmsSqzz7y/WXbN3RgOoVrc=
|
||||
github.com/aws/aws-sdk-go-v2/service/sqs v1.34.3/go.mod h1:L0enV3GCRd5iG9B64W35C4/hwsCB00Ib+DKVGTadKHI=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.2 h1:bSYXVyUzoTHoKalBmwaZxs97HU9DWWI3ehHSAMa7xOk=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.2/go.mod h1:skMqY7JElusiOUjMJMOv1jJsP7YUg7DrhgqZZWuzu1U=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2 h1:AhmO1fHINP9vFYUE0LHzCWg/LfUWUF+zFPEcY9QXb7o=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2/go.mod h1:o8aQygT2+MVP0NaV6kbdE1YnnIM8RRVQzoeUH45GOdI=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.32.2 h1:CiS7i0+FUe+/YY1GvIBLLrR/XNGZ4CtM1Ll0XavNuVo=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.32.2/go.mod h1:HtaiBI8CjYoNVde8arShXb94UbQQi9L4EMr6D+xGBwo=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.5 h1:HJwZwRt2Z2Tdec+m+fPjvdmkq2s9Ra+VR0hjF7V2o40=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.5/go.mod h1:wrMCEwjFPms+V86TCQQeOxQF/If4vT44FGIOFiMC2ck=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.4 h1:zcx9LiGWZ6i6pjdcoE9oXAB6mUdeyC36Ia/QEiIvYdg=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.4/go.mod h1:Tp/ly1cTjRLGBBmNccFumbZ8oqpZlpdhFf80SrRh4is=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.0 h1:s7LRgBqhwLaxcocnAniBJp7gaAB+4I4vHzqUqjH18yc=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.0/go.mod h1:9XEUty5v5UAsMiFOBJrNibZgwCeOma73jgGwwhgffa8=
|
||||
github.com/aws/smithy-go v1.22.0 h1:uunKnWlcoL3zO7q+gG2Pk53joueEOsnNB28QdMsmiMM=
|
||||
github.com/aws/smithy-go v1.22.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
@@ -945,8 +945,8 @@ github.com/go-zookeeper/zk v1.0.3/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL
|
||||
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/gocql/gocql v1.6.0 h1:IdFdOTbnpbd0pDhl4REKQDM+Q0SzKXQ1Yh+YZZ8T/qU=
|
||||
github.com/gocql/gocql v1.6.0/go.mod h1:3gM2c4D3AnkISwBxGnMMsS8Oy4y2lhbPRsH4xnJrHG8=
|
||||
github.com/gocql/gocql v1.7.0 h1:O+7U7/1gSN7QTEAaMEsJc1Oq2QHXvCWoF3DFK9HDHus=
|
||||
github.com/gocql/gocql v1.7.0/go.mod h1:vnlvXyFZeLBF0Wy+RS8hrOdbn0UWsWtdg07XJnFxZ+4=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
@@ -955,8 +955,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
@@ -1107,8 +1107,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4Zs
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w=
|
||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
|
||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
|
||||
github.com/hanwen/go-fuse/v2 v2.6.2 h1:Lm0ZDfKYCvdh9pGSv+l4YpuMeR+Q87ArPWh6hWpRPPQ=
|
||||
github.com/hanwen/go-fuse/v2 v2.6.2/go.mod h1:ugNaD/iv5JYyS1Rcvi57Wz7/vrLQJo10mmketmoef48=
|
||||
github.com/hanwen/go-fuse/v2 v2.6.1 h1:F3RUMbAuRhVTi3fvgf8HjMPvOm9xEv5wjuy/AXJtEwI=
|
||||
github.com/hanwen/go-fuse/v2 v2.6.1/go.mod h1:ugNaD/iv5JYyS1Rcvi57Wz7/vrLQJo10mmketmoef48=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
@@ -1243,8 +1243,8 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/linxGnu/grocksdb v1.9.5 h1:Pqx1DTR5bdJSZic8CJwc9UNZR60qoAOK09feMg4SoaI=
|
||||
github.com/linxGnu/grocksdb v1.9.5/go.mod h1:QYiYypR2d4v63Wj1adOOfzglnoII0gLj3PNh4fZkcFA=
|
||||
github.com/linxGnu/grocksdb v1.9.7 h1:Bp2r1Yti/IXxEobZZnDooXAui/Q+5gVqgQMenLWyDUw=
|
||||
github.com/linxGnu/grocksdb v1.9.7/go.mod h1:QYiYypR2d4v63Wj1adOOfzglnoII0gLj3PNh4fZkcFA=
|
||||
github.com/lpar/date v1.0.0 h1:bq/zVqFTUmsxvd/CylidY4Udqpr9BOFrParoP6p0x/I=
|
||||
github.com/lpar/date v1.0.0/go.mod h1:KjYe0dDyMQTgpqcUz4LEIeM5VZwhggjVx/V2dtc8NSo=
|
||||
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed h1:036IscGBfJsFIgJQzlui7nK1Ncm0tp2ktmPj8xO4N/0=
|
||||
@@ -1336,8 +1336,8 @@ github.com/orcaman/concurrent-map/v2 v2.0.1 h1:jOJ5Pg2w1oeB6PeDurIYf6k9PQ+aTITr/
|
||||
github.com/orcaman/concurrent-map/v2 v2.0.1/go.mod h1:9Eq3TG2oBe5FirmYWQfYO5iH1q0Jv47PLaNK++uCdOM=
|
||||
github.com/panjf2000/ants/v2 v2.9.1 h1:Q5vh5xohbsZXGcD6hhszzGqB7jSSc2/CRr3QKIga8Kw=
|
||||
github.com/panjf2000/ants/v2 v2.9.1/go.mod h1:7ZxyxsqE4vvW0M7LSD8aI3cKwgFhBHbxnlN8mDqHa1I=
|
||||
github.com/parquet-go/parquet-go v0.23.0 h1:dyEU5oiHCtbASyItMCD2tXtT2nPmoPbKpqf0+nnGrmk=
|
||||
github.com/parquet-go/parquet-go v0.23.0/go.mod h1:MnwbUcFHU6uBYMymKAlPPAw9yh3kE1wWl6Gl1uLdkNk=
|
||||
github.com/parquet-go/parquet-go v0.23.1-0.20241011155651-6446d1d0d2fe h1:oUJ5TPnrEK/z+/PeoLL+jCgfngAZIDMyhZASetRcYYg=
|
||||
github.com/parquet-go/parquet-go v0.23.1-0.20241011155651-6446d1d0d2fe/go.mod h1:OqBBRGBl7+llplCvDMql8dEKaDqjaFA/VAPw+OJiNiw=
|
||||
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
@@ -1470,8 +1470,6 @@ github.com/seaweedfs/raft v1.1.3 h1:5B6hgneQ7IuU4Ceom/f6QUt8pEeqjcsRo+IxlyPZCws=
|
||||
github.com/seaweedfs/raft v1.1.3/go.mod h1:9cYlEBA+djJbnf/5tWsCybtbL7ICYpi+Uxcg3MxjuNs=
|
||||
github.com/secsy/goftp v0.0.0-20200609142545-aa2de14babf4 h1:PT+ElG/UUFMfqy5HrxJxNzj3QBOf7dZwupeVC+mG1Lo=
|
||||
github.com/secsy/goftp v0.0.0-20200609142545-aa2de14babf4/go.mod h1:MnkX001NG75g3p8bhFycnyIjeQoOjGL6CEIsdE/nKSY=
|
||||
github.com/segmentio/encoding v0.4.0 h1:MEBYvRqiUB2nfR2criEXWqwdY6HJOUrCn5hboVOVmy8=
|
||||
github.com/segmentio/encoding v0.4.0/go.mod h1:/d03Cd8PoaDeceuhUUUQWjU0KhWjrmYrWPgtJHYZSnI=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||
@@ -1602,8 +1600,8 @@ github.com/ydb-platform/ydb-go-sdk-auth-environ v0.5.0 h1:/NyPd9KnCJgzrEXCArqk1T
|
||||
github.com/ydb-platform/ydb-go-sdk-auth-environ v0.5.0/go.mod h1:9YzkhlIymWaJGX6KMU3vh5sOf3UKbCXkG/ZdjaI3zNM=
|
||||
github.com/ydb-platform/ydb-go-sdk/v3 v3.44.0/go.mod h1:oSLwnuilwIpaF5bJJMAofnGgzPJusoI3zWMNb8I+GnM=
|
||||
github.com/ydb-platform/ydb-go-sdk/v3 v3.47.3/go.mod h1:bWnOIcUHd7+Sl7DN+yhyY1H/I61z53GczvwJgXMgvj0=
|
||||
github.com/ydb-platform/ydb-go-sdk/v3 v3.89.2 h1:yVubsh4bTmSk7bc1Gss8K4OUs59eWBfJhxmH/uDtv6g=
|
||||
github.com/ydb-platform/ydb-go-sdk/v3 v3.89.2/go.mod h1:ah+n4KaEcANFM+CBKnJ5VrmR0IvWfYFvkofW/56076c=
|
||||
github.com/ydb-platform/ydb-go-sdk/v3 v3.91.0 h1:dFD032XMymsL4byFye3KNt3pN0HiUVVE0YXsj2sd46c=
|
||||
github.com/ydb-platform/ydb-go-sdk/v3 v3.91.0/go.mod h1:ah+n4KaEcANFM+CBKnJ5VrmR0IvWfYFvkofW/56076c=
|
||||
github.com/ydb-platform/ydb-go-yc v0.12.1 h1:qw3Fa+T81+Kpu5Io2vYHJOwcrYrVjgJlT6t/0dOXJrA=
|
||||
github.com/ydb-platform/ydb-go-yc v0.12.1/go.mod h1:t/ZA4ECdgPWjAb4jyDe8AzQZB5dhpGbi3iCahFaNwBY=
|
||||
github.com/ydb-platform/ydb-go-yc-metadata v0.6.1 h1:9E5q8Nsy2RiJMZDNVy0A3KUrIMBPakJ2VgloeWbcI84=
|
||||
@@ -1636,12 +1634,12 @@ go.einride.tech/aip v0.68.0 h1:4seM66oLzTpz50u4K1zlJyOXQ3tCzcJN7I22tKkjipw=
|
||||
go.einride.tech/aip v0.68.0/go.mod h1:7y9FF8VtPWqpxuAxl0KQWqaULxW4zFIesD6zF5RIHHg=
|
||||
go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0=
|
||||
go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ=
|
||||
go.etcd.io/etcd/api/v3 v3.5.16 h1:WvmyJVbjWqK4R1E+B12RRHz3bRGy9XVfh++MgbN+6n0=
|
||||
go.etcd.io/etcd/api/v3 v3.5.16/go.mod h1:1P4SlIP/VwkDmGo3OlOD7faPeP8KDIFhqvciH5EfN28=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.16 h1:ZgY48uH6UvB+/7R9Yf4x574uCO3jIx0TRDyetSfId3Q=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.16/go.mod h1:V8acl8pcEK0Y2g19YlOV9m9ssUe6MgiDSobSoaBAM0E=
|
||||
go.etcd.io/etcd/client/v3 v3.5.16 h1:sSmVYOAHeC9doqi0gv7v86oY/BTld0SEFGaxsU9eRhE=
|
||||
go.etcd.io/etcd/client/v3 v3.5.16/go.mod h1:X+rExSGkyqxvu276cr2OwPLBaeqFu1cIl4vmRjAD/50=
|
||||
go.etcd.io/etcd/api/v3 v3.5.17 h1:cQB8eb8bxwuxOilBpMJAEo8fAONyrdXTHUNcMd8yT1w=
|
||||
go.etcd.io/etcd/api/v3 v3.5.17/go.mod h1:d1hvkRuXkts6PmaYk2Vrgqbv7H4ADfAKhyJqHNLJCB4=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.17 h1:XxnDXAWq2pnxqx76ljWwiQ9jylbpC4rvkAeRVOUKKVw=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.17/go.mod h1:4DqK1TKacp/86nJk4FLQqo6Mn2vvQFBmruW3pP14H/w=
|
||||
go.etcd.io/etcd/client/v3 v3.5.17 h1:o48sINNeWz5+pjy/Z0+HKpj/xSnBkuVhVvXkjEXbqZY=
|
||||
go.etcd.io/etcd/client/v3 v3.5.17/go.mod h1:j2d4eXTHWkT2ClBgnnEPm/Wuu7jsqku41v9DZ3OtjQo=
|
||||
go.mongodb.org/mongo-driver v1.16.0 h1:tpRsfBJMROVHKpdGyc1BBEzzjDUWjItxbVSZ8Ls4BQ4=
|
||||
go.mongodb.org/mongo-driver v1.16.0/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
@@ -1695,8 +1693,8 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
gocloud.dev v0.40.0 h1:f8LgP+4WDqOG/RXoUcyLpeIAGOcAbZrZbDQCUee10ng=
|
||||
gocloud.dev v0.40.0/go.mod h1:drz+VyYNBvrMTW0KZiBAYEdl8lbNZx+OQ7oQvdrFmSQ=
|
||||
gocloud.dev/pubsub/natspubsub v0.39.0 h1:T5fOoUtlnttE/NrggpJ0mutN8ui6qd6+ys2Dmq9h1L8=
|
||||
gocloud.dev/pubsub/natspubsub v0.39.0/go.mod h1:UsxWgxP2/Qv2U5pcN0Y3EHbbjuqUxuqeP7H8ALEnCrk=
|
||||
gocloud.dev/pubsub/natspubsub v0.40.0 h1:KUDqVV1i724df37v2F6EN+bg02WvekUNYFqLKhgBm94=
|
||||
gocloud.dev/pubsub/natspubsub v0.40.0/go.mod h1:yjUqZCV4qaLjoUlxapY4kja49uMr0L5mxug+3SEZDTk=
|
||||
gocloud.dev/pubsub/rabbitpubsub v0.40.0 h1:Gz2otW0qiYAA+A3gIgrrXXIyAiv7euoRmXB8LYCVKSs=
|
||||
gocloud.dev/pubsub/rabbitpubsub v0.40.0/go.mod h1:Z8fouI+E7u3sxuOX/Tq1FpRWl36SF5+iE361SJTjuGQ=
|
||||
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||
@@ -1723,8 +1721,8 @@ golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
|
||||
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -1789,8 +1787,8 @@ golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -1862,8 +1860,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||
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/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -1913,8 +1911,8 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
||||
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -2011,8 +2009,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
||||
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -2026,8 +2024,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
|
||||
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
|
||||
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
|
||||
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -2046,16 +2044,16 @@ golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
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/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -2126,8 +2124,8 @@ golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
|
||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
|
||||
golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
|
||||
golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o=
|
||||
golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -2201,8 +2199,8 @@ google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/
|
||||
google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=
|
||||
google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=
|
||||
google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI=
|
||||
google.golang.org/api v0.199.0 h1:aWUXClp+VFJmqE0JPvpZOK3LDQMyFKYIow4etYd9qxs=
|
||||
google.golang.org/api v0.199.0/go.mod h1:ohG4qSztDJmZdjK/Ar6MhbAmb/Rpi4JHOqagsh90K28=
|
||||
google.golang.org/api v0.203.0 h1:SrEeuwU3S11Wlscsn+LA1kb/Y5xT8uggJSkIhD08NAU=
|
||||
google.golang.org/api v0.203.0/go.mod h1:BuOVyCSYEPwJb3npWvDnNmFI92f3GeRnHNkETneT3SI=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
@@ -2336,12 +2334,12 @@ google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ
|
||||
google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA=
|
||||
google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw=
|
||||
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
|
||||
google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 h1:BulPr26Jqjnd4eYDVe+YvyR7Yc2vJGkO5/0UxD0/jZU=
|
||||
google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:hL97c3SYopEHblzpxRL4lSs523++l8DYxGM1FQiYmb4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||
google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53 h1:Df6WuGvthPzc+JiQ/G+m+sNX24kc0aTBqoDN/0yyykE=
|
||||
google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53/go.mod h1:fheguH3Am2dGp1LfXkrvwqC/KlFq8F0nLq3LryOMrrE=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
@@ -2502,8 +2500,8 @@ modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
||||
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
||||
modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4=
|
||||
modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM=
|
||||
modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k=
|
||||
modernc.org/sqlite v1.34.1 h1:u3Yi6M0N8t9yKRDwhXcyp1eS5/ErhPTBggxWFuR6Hfk=
|
||||
modernc.org/sqlite v1.34.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k=
|
||||
modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
|
||||
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
|
||||
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
apiVersion: v1
|
||||
description: SeaweedFS
|
||||
name: seaweedfs
|
||||
appVersion: "3.79"
|
||||
appVersion: "3.80"
|
||||
# Dev note: Trigger a helm chart release by `git tag -a helm-<version>`
|
||||
version: 4.0.379
|
||||
version: 4.0.380
|
||||
|
||||
@@ -32,9 +32,9 @@ spec:
|
||||
- name: WEED_CLUSTER_DEFAULT
|
||||
value: "sw"
|
||||
- name: WEED_CLUSTER_SW_MASTER
|
||||
value: "{{ template "seaweedfs.name" . }}-master.{{ .Release.Namespace }}:9333"
|
||||
value: "{{ template "seaweedfs.name" . }}-master.{{ .Release.Namespace }}:{{ .Values.master.port }}"
|
||||
- name: WEED_CLUSTER_SW_FILER
|
||||
value: "{{ template "seaweedfs.name" . }}-filer-client.{{ .Release.Namespace }}:8888"
|
||||
value: "{{ template "seaweedfs.name" . }}-filer-client.{{ .Release.Namespace }}:{{ .Values.filer.port }}"
|
||||
- name: POD_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
@@ -53,6 +53,26 @@ spec:
|
||||
- "/bin/sh"
|
||||
- "-ec"
|
||||
- |
|
||||
wait_for_service() {
|
||||
local url=$1
|
||||
local max_attempts=60 # 5 minutes total (5s * 60)
|
||||
local attempt=1
|
||||
|
||||
echo "Waiting for service at $url..."
|
||||
while [ $attempt -le $max_attempts ]; do
|
||||
if wget -q --spider "$url" >/dev/null 2>&1; then
|
||||
echo "Service at $url is up!"
|
||||
return 0
|
||||
fi
|
||||
echo "Attempt $attempt: Service not ready yet, retrying in 5s..."
|
||||
sleep 5
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
echo "Service at $url failed to become ready within 5 minutes"
|
||||
exit 1
|
||||
}
|
||||
wait_for_service "http://$WEED_CLUSTER_SW_MASTER{{ .Values.master.readinessProbe.httpGet.path }}"
|
||||
wait_for_service "http://$WEED_CLUSTER_SW_FILER{{ .Values.filer.readinessProbe.httpGet.path }}"
|
||||
{{- range $reg, $props := $.Values.filer.s3.createBuckets }}
|
||||
exec /bin/echo \
|
||||
"s3.bucket.create --name {{ $props.name }}" |\
|
||||
|
||||
@@ -26,8 +26,8 @@ spec:
|
||||
scrapeTimeout: 5s
|
||||
selector:
|
||||
matchLabels:
|
||||
app: {{ template "seaweedfs.name" . }}
|
||||
component: s3
|
||||
app.kubernetes.io/name: {{ template "seaweedfs.name" . }}
|
||||
app.kubernetes.io/component: s3
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
@@ -14,20 +14,23 @@ public class ReadChunks {
|
||||
points.add(new Point(chunk.getOffset(), chunk, true));
|
||||
points.add(new Point(chunk.getOffset() + chunk.getSize(), chunk, false));
|
||||
}
|
||||
|
||||
Collections.sort(points, new Comparator<Point>() {
|
||||
@Override
|
||||
public int compare(Point a, Point b) {
|
||||
int x = (int) (a.x - b.x);
|
||||
if (a.x != b.x) {
|
||||
return (int) (a.x - b.x);
|
||||
int xComparison = Long.compare(a.x, b.x);
|
||||
if (xComparison != 0) {
|
||||
return xComparison;
|
||||
}
|
||||
if (a.ts != b.ts) {
|
||||
return (int) (a.ts - b.ts);
|
||||
|
||||
// If x values are equal, compare ts
|
||||
int tsComparison = Long.compare(a.ts, b.ts);
|
||||
if (tsComparison != 0) {
|
||||
return tsComparison;
|
||||
}
|
||||
if (!a.isStart) {
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
|
||||
// If both x and ts are equal, prioritize start points
|
||||
return Boolean.compare(b.isStart, a.isStart); // b.isStart first to prioritize starts
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ import (
|
||||
"github.com/seaweedfs/seaweedfs/weed/operation"
|
||||
"github.com/seaweedfs/seaweedfs/weed/security"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||
"github.com/seaweedfs/seaweedfs/weed/wdclient"
|
||||
util_http "github.com/seaweedfs/seaweedfs/weed/util/http"
|
||||
"github.com/seaweedfs/seaweedfs/weed/wdclient"
|
||||
)
|
||||
|
||||
type BenchmarkOptions struct {
|
||||
@@ -242,7 +242,7 @@ func writeFiles(idChan chan int, fileIdLineChan chan string, s *stat) {
|
||||
DiskType: *b.diskType,
|
||||
}
|
||||
if assignResult, err := operation.Assign(b.masterClient.GetMaster, b.grpcDialOption, ar); err == nil {
|
||||
fp.Server, fp.Fid, fp.Collection = assignResult.Url, assignResult.Fid, *b.collection
|
||||
fp.Server, fp.Fid, fp.Pref.Collection = assignResult.Url, assignResult.Fid, *b.collection
|
||||
if !isSecure && assignResult.Auth != "" {
|
||||
isSecure = true
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/replication/source"
|
||||
"github.com/seaweedfs/seaweedfs/weed/security"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util/http"
|
||||
"google.golang.org/grpc"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -14,16 +17,18 @@ import (
|
||||
)
|
||||
|
||||
type FilerBackupOptions struct {
|
||||
isActivePassive *bool
|
||||
filer *string
|
||||
path *string
|
||||
excludePaths *string
|
||||
excludeFileName *string
|
||||
debug *bool
|
||||
proxyByFiler *bool
|
||||
doDeleteFiles *bool
|
||||
timeAgo *time.Duration
|
||||
retentionDays *int
|
||||
isActivePassive *bool
|
||||
filer *string
|
||||
path *string
|
||||
excludePaths *string
|
||||
excludeFileName *string
|
||||
debug *bool
|
||||
proxyByFiler *bool
|
||||
doDeleteFiles *bool
|
||||
disableErrorRetry *bool
|
||||
ignore404Error *bool
|
||||
timeAgo *time.Duration
|
||||
retentionDays *int
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -41,6 +46,8 @@ func init() {
|
||||
filerBackupOptions.debug = cmdFilerBackup.Flag.Bool("debug", false, "debug mode to print out received files")
|
||||
filerBackupOptions.timeAgo = cmdFilerBackup.Flag.Duration("timeAgo", 0, "start time before now. \"300ms\", \"1.5h\" or \"2h45m\". Valid time units are \"ns\", \"us\" (or \"µs\"), \"ms\", \"s\", \"m\", \"h\"")
|
||||
filerBackupOptions.retentionDays = cmdFilerBackup.Flag.Int("retentionDays", 0, "incremental backup retention days")
|
||||
filerBackupOptions.disableErrorRetry = cmdFilerBackup.Flag.Bool("disableErrorRetry", false, "disables errors retry, only logs will print")
|
||||
filerBackupOptions.ignore404Error = cmdFilerBackup.Flag.Bool("ignore404Error", true, "ignore 404 errors from filer")
|
||||
}
|
||||
|
||||
var cmdFilerBackup = &Command{
|
||||
@@ -130,7 +137,23 @@ func doFilerBackup(grpcDialOption grpc.DialOption, backupOption *FilerBackupOpti
|
||||
*backupOption.proxyByFiler)
|
||||
dataSink.SetSourceFiler(filerSource)
|
||||
|
||||
processEventFn := genProcessFunction(sourcePath, targetPath, excludePaths, reExcludeFileName, dataSink, *backupOption.doDeleteFiles, debug)
|
||||
var processEventFn func(*filer_pb.SubscribeMetadataResponse) error
|
||||
if *backupOption.ignore404Error {
|
||||
processEventFnGenerated := genProcessFunction(sourcePath, targetPath, excludePaths, reExcludeFileName, dataSink, *backupOption.doDeleteFiles, debug)
|
||||
processEventFn = func(resp *filer_pb.SubscribeMetadataResponse) error {
|
||||
err := processEventFnGenerated(resp)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if errors.Is(err, http.ErrNotFound) {
|
||||
glog.V(0).Infof("got 404 error, ignore it: %s", err.Error())
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
processEventFn = genProcessFunction(sourcePath, targetPath, excludePaths, reExcludeFileName, dataSink, *backupOption.doDeleteFiles, debug)
|
||||
}
|
||||
|
||||
processEventFnWithOffset := pb.AddOffsetFunc(processEventFn, 3*time.Second, func(counter int64, lastTsNs int64) error {
|
||||
glog.V(0).Infof("backup %s progressed to %v %0.2f/sec", sourceFiler, time.Unix(0, lastTsNs), float64(counter)/float64(3))
|
||||
@@ -154,6 +177,11 @@ func doFilerBackup(grpcDialOption grpc.DialOption, backupOption *FilerBackupOpti
|
||||
prefix = prefix + "/"
|
||||
}
|
||||
|
||||
eventErrorType := pb.RetryForeverOnError
|
||||
if *backupOption.disableErrorRetry {
|
||||
eventErrorType = pb.TrivialOnError
|
||||
}
|
||||
|
||||
metadataFollowOption := &pb.MetadataFollowOption{
|
||||
ClientName: "backup_" + dataSink.GetName(),
|
||||
ClientId: clientId,
|
||||
@@ -164,7 +192,7 @@ func doFilerBackup(grpcDialOption grpc.DialOption, backupOption *FilerBackupOpti
|
||||
DirectoriesToWatch: nil,
|
||||
StartTsNs: startFrom.UnixNano(),
|
||||
StopTsNs: 0,
|
||||
EventErrorType: pb.RetryForeverOnError,
|
||||
EventErrorType: eventErrorType,
|
||||
}
|
||||
|
||||
return pb.FollowMetadata(sourceFiler, grpcDialOption, metadataFollowOption, processEventFnWithOffset)
|
||||
|
||||
@@ -436,7 +436,7 @@ func genProcessFunction(sourcePath string, targetPath string, excludePaths []str
|
||||
}
|
||||
key := buildKey(dataSink, message, targetPath, sourceNewKey, sourcePath)
|
||||
if err := dataSink.CreateEntry(key, message.NewEntry, message.Signatures); err != nil {
|
||||
return fmt.Errorf("create entry1 : %v", err)
|
||||
return fmt.Errorf("create entry1 : %w", err)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
@@ -462,13 +462,13 @@ func genProcessFunction(sourcePath string, targetPath string, excludePaths []str
|
||||
|
||||
// not able to find old entry
|
||||
if err = dataSink.DeleteEntry(string(oldKey), message.OldEntry.IsDirectory, false, message.Signatures); err != nil {
|
||||
return fmt.Errorf("delete old entry %v: %v", oldKey, err)
|
||||
return fmt.Errorf("delete old entry %v: %w", oldKey, err)
|
||||
}
|
||||
}
|
||||
// create the new entry
|
||||
newKey := buildKey(dataSink, message, targetPath, sourceNewKey, sourcePath)
|
||||
if err := dataSink.CreateEntry(newKey, message.NewEntry, message.Signatures); err != nil {
|
||||
return fmt.Errorf("create entry2 : %v", err)
|
||||
return fmt.Errorf("create entry2 : %w", err)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
@@ -486,7 +486,7 @@ func genProcessFunction(sourcePath string, targetPath string, excludePaths []str
|
||||
// new key is in the watched directory
|
||||
key := buildKey(dataSink, message, targetPath, sourceNewKey, sourcePath)
|
||||
if err := dataSink.CreateEntry(key, message.NewEntry, message.Signatures); err != nil {
|
||||
return fmt.Errorf("create entry3 : %v", err)
|
||||
return fmt.Errorf("create entry3 : %w", err)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -248,6 +248,7 @@ func RunMount(option *MountOptions, umask os.FileMode) bool {
|
||||
Cipher: cipher,
|
||||
UidGidMapper: uidGidMapper,
|
||||
DisableXAttr: *option.disableXAttr,
|
||||
IsMacOs: runtime.GOOS == "darwin",
|
||||
})
|
||||
|
||||
// create mount root
|
||||
|
||||
@@ -49,6 +49,7 @@ copy_1 = 7 # create 1 x 7 = 7 actual volumes
|
||||
copy_2 = 6 # create 2 x 6 = 12 actual volumes
|
||||
copy_3 = 3 # create 3 x 3 = 9 actual volumes
|
||||
copy_other = 1 # create n x 1 = n actual volumes
|
||||
threshold = 0.9 # create threshold
|
||||
|
||||
# configuration flags for replication
|
||||
[master.replication]
|
||||
|
||||
+16
-2
@@ -97,7 +97,14 @@ func runUpload(cmd *Command, args []string) bool {
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
results, e := operation.SubmitFiles(func(_ context.Context) pb.ServerAddress { return pb.ServerAddress(*upload.master) }, grpcDialOption, parts, *upload.replication, *upload.collection, *upload.dataCenter, *upload.ttl, *upload.diskType, *upload.maxMB, *upload.usePublicUrl)
|
||||
results, e := operation.SubmitFiles(func(_ context.Context) pb.ServerAddress { return pb.ServerAddress(*upload.master) }, grpcDialOption, parts, operation.StoragePreference{
|
||||
Replication: *upload.replication,
|
||||
Collection: *upload.collection,
|
||||
DataCenter: *upload.dataCenter,
|
||||
Ttl: *upload.ttl,
|
||||
DiskType: *upload.diskType,
|
||||
MaxMB: *upload.maxMB,
|
||||
}, *upload.usePublicUrl)
|
||||
bytes, _ := json.Marshal(results)
|
||||
fmt.Println(string(bytes))
|
||||
if e != nil {
|
||||
@@ -119,7 +126,14 @@ func runUpload(cmd *Command, args []string) bool {
|
||||
fmt.Println(e.Error())
|
||||
return false
|
||||
}
|
||||
results, err := operation.SubmitFiles(func(_ context.Context) pb.ServerAddress { return pb.ServerAddress(*upload.master) }, grpcDialOption, parts, *upload.replication, *upload.collection, *upload.dataCenter, *upload.ttl, *upload.diskType, *upload.maxMB, *upload.usePublicUrl)
|
||||
results, err := operation.SubmitFiles(func(_ context.Context) pb.ServerAddress { return pb.ServerAddress(*upload.master) }, grpcDialOption, parts, operation.StoragePreference{
|
||||
Replication: *upload.replication,
|
||||
Collection: *upload.collection,
|
||||
DataCenter: *upload.dataCenter,
|
||||
Ttl: *upload.ttl,
|
||||
DiskType: *upload.diskType,
|
||||
MaxMB: *upload.maxMB,
|
||||
}, *upload.usePublicUrl)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return false
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package filer
|
||||
|
||||
import (
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
"sync"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
)
|
||||
|
||||
const SectionSize = 2 * 1024 * 1024 * 32 // 64MiB
|
||||
@@ -62,11 +63,6 @@ func removeGarbageChunks(section *FileChunkSection, garbageFileIds map[string]st
|
||||
}
|
||||
|
||||
func (section *FileChunkSection) setupForRead(group *ChunkGroup, fileSize int64) {
|
||||
if section.isPrepared {
|
||||
section.reader.fileSize = fileSize
|
||||
return
|
||||
}
|
||||
|
||||
section.lock.Lock()
|
||||
defer section.lock.Unlock()
|
||||
|
||||
|
||||
@@ -4,15 +4,16 @@ import (
|
||||
"container/heap"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util/log_buffer"
|
||||
"github.com/seaweedfs/seaweedfs/weed/wdclient"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"io"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util/log_buffer"
|
||||
"github.com/seaweedfs/seaweedfs/weed/wdclient"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||
)
|
||||
@@ -39,6 +40,19 @@ func (f *Filer) collectPersistedLogBuffer(startPosition log_buffer.MessagePositi
|
||||
|
||||
}
|
||||
|
||||
func (f *Filer) HasPersistedLogFiles(startPosition log_buffer.MessagePosition) (bool, error) {
|
||||
startDate := fmt.Sprintf("%04d-%02d-%02d", startPosition.Year(), startPosition.Month(), startPosition.Day())
|
||||
dayEntries, _, listDayErr := f.ListDirectoryEntries(context.Background(), SystemLogDir, startDate, true, 1, "", "", "")
|
||||
|
||||
if listDayErr != nil {
|
||||
return false, fmt.Errorf("fail to list log by day: %v", listDayErr)
|
||||
}
|
||||
if len(dayEntries) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// ----------
|
||||
type LogEntryItem struct {
|
||||
Entry *filer_pb.LogEntry
|
||||
@@ -103,7 +117,7 @@ func (o *OrderedLogVisitor) GetNext() (logEntry *filer_pb.LogEntry, err error) {
|
||||
if nextErr != nil {
|
||||
if nextErr == io.EOF {
|
||||
// do nothing since the filer has no more log entries
|
||||
}else {
|
||||
} else {
|
||||
return nil, fmt.Errorf("failed to get next log entry: %v", nextErr)
|
||||
}
|
||||
} else {
|
||||
@@ -230,7 +244,7 @@ func (c *LogFileEntryCollector) collectMore(v *OrderedLogVisitor) (err error) {
|
||||
if nextErr != nil {
|
||||
if nextErr == io.EOF {
|
||||
// do nothing since the filer has no more log entries
|
||||
}else {
|
||||
} else {
|
||||
return fmt.Errorf("failed to get next log entry for %v: %v", entryName, err)
|
||||
}
|
||||
} else {
|
||||
|
||||
+12
-6
@@ -97,6 +97,10 @@ func NewChunkReaderAtFromClient(readerCache *ReaderCache, chunkViews *IntervalLi
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ChunkReadAt) Size() int64 {
|
||||
return c.fileSize
|
||||
}
|
||||
|
||||
func (c *ChunkReadAt) Close() error {
|
||||
c.readerCache.destroy()
|
||||
return nil
|
||||
@@ -169,15 +173,14 @@ func (c *ChunkReadAt) doReadAt(p []byte, offset int64) (n int, ts int64, err err
|
||||
// zero the remaining bytes if a gap exists at the end of the last chunk (or a fully sparse file)
|
||||
if err == nil && remaining > 0 {
|
||||
var delta int64
|
||||
if c.fileSize > startOffset {
|
||||
if c.fileSize >= startOffset {
|
||||
delta = min(remaining, c.fileSize-startOffset)
|
||||
startOffset -= offset
|
||||
} else {
|
||||
delta = remaining
|
||||
startOffset = max(startOffset-offset, startOffset-remaining-offset)
|
||||
}
|
||||
glog.V(4).Infof("zero2 [%d,%d) of file size %d bytes", startOffset, startOffset+delta, c.fileSize)
|
||||
n += zero(p, startOffset, delta)
|
||||
if delta > 0 {
|
||||
glog.V(4).Infof("zero2 [%d,%d) of file size %d bytes", startOffset, startOffset+delta, c.fileSize)
|
||||
n += zero(p, startOffset, delta)
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil && offset+int64(len(p)) >= c.fileSize {
|
||||
@@ -216,6 +219,9 @@ func (c *ChunkReadAt) readChunkSliceAt(buffer []byte, chunkView *ChunkView, next
|
||||
}
|
||||
|
||||
func zero(buffer []byte, start, length int64) int {
|
||||
if length <= 0 {
|
||||
return 0
|
||||
}
|
||||
end := min(start+length, int64(len(buffer)))
|
||||
start = max(start, 0)
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ func (m *mockChunkCache) ReadChunkAt(data []byte, fileId string, offset uint64)
|
||||
func (m *mockChunkCache) SetChunk(fileId string, data []byte) {
|
||||
}
|
||||
|
||||
func (m *mockChunkCache) GetMaxFilePartSizeInCache() (uint64) {
|
||||
func (m *mockChunkCache) GetMaxFilePartSizeInCache() uint64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ func TestReaderAt(t *testing.T) {
|
||||
}
|
||||
|
||||
testReadAt(t, readerAt, 0, 10, 10, io.EOF, nil, nil)
|
||||
testReadAt(t, readerAt, 0, 12, 12, io.EOF, nil, nil)
|
||||
testReadAt(t, readerAt, 0, 12, 10, io.EOF, nil, nil)
|
||||
testReadAt(t, readerAt, 2, 8, 8, io.EOF, nil, nil)
|
||||
testReadAt(t, readerAt, 3, 6, 6, nil, nil, nil)
|
||||
|
||||
@@ -131,8 +131,8 @@ func TestReaderAt0(t *testing.T) {
|
||||
testReadAt(t, readerAt, 3, 16, 7, io.EOF, nil, nil)
|
||||
testReadAt(t, readerAt, 3, 5, 5, nil, nil, nil)
|
||||
|
||||
testReadAt(t, readerAt, 11, 5, 5, io.EOF, nil, nil)
|
||||
testReadAt(t, readerAt, 10, 5, 5, io.EOF, nil, nil)
|
||||
testReadAt(t, readerAt, 11, 5, 0, io.EOF, nil, nil)
|
||||
testReadAt(t, readerAt, 10, 5, 0, io.EOF, nil, nil)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package filer
|
||||
|
||||
const (
|
||||
TopicsDir = "/topics"
|
||||
SystemLogDir = TopicsDir + "/.system/log"
|
||||
TopicsDir = "/topics"
|
||||
SystemLogDir = TopicsDir + "/.system/log"
|
||||
TopicConfFile = "topic.conf"
|
||||
)
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
package filer_client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/seaweedfs/seaweedfs/weed/filer"
|
||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||
"github.com/seaweedfs/seaweedfs/weed/mq/topic"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/mq_pb"
|
||||
"google.golang.org/grpc"
|
||||
jsonpb "google.golang.org/protobuf/encoding/protojson"
|
||||
)
|
||||
|
||||
type FilerClientAccessor struct {
|
||||
@@ -22,41 +18,23 @@ func (fca *FilerClientAccessor) WithFilerClient(streamingMode bool, fn func(file
|
||||
return pb.WithFilerClient(streamingMode, 0, fca.GetFiler(), fca.GetGrpcDialOption(), fn)
|
||||
}
|
||||
|
||||
func (fca *FilerClientAccessor) SaveTopicConfToFiler(t *mq_pb.Topic, conf *mq_pb.ConfigureTopicResponse) error {
|
||||
func (fca *FilerClientAccessor) SaveTopicConfToFiler(t topic.Topic, conf *mq_pb.ConfigureTopicResponse) error {
|
||||
|
||||
glog.V(0).Infof("save conf for topic %v to filer", t)
|
||||
|
||||
// save the topic configuration on filer
|
||||
topicDir := fmt.Sprintf("%s/%s/%s", filer.TopicsDir, t.Namespace, t.Name)
|
||||
if err := fca.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
|
||||
var buf bytes.Buffer
|
||||
filer.ProtoToText(&buf, conf)
|
||||
return filer.SaveInsideFiler(client, topicDir, "topic.conf", buf.Bytes())
|
||||
}); err != nil {
|
||||
return fmt.Errorf("save topic to %s: %v", topicDir, err)
|
||||
}
|
||||
return nil
|
||||
return fca.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
|
||||
return t.WriteConfFile(client, conf)
|
||||
})
|
||||
}
|
||||
|
||||
func (fca *FilerClientAccessor) ReadTopicConfFromFiler(t topic.Topic) (conf *mq_pb.ConfigureTopicResponse, err error) {
|
||||
|
||||
glog.V(0).Infof("load conf for topic %v from filer", t)
|
||||
glog.V(1).Infof("load conf for topic %v from filer", t)
|
||||
|
||||
topicDir := fmt.Sprintf("%s/%s/%s", filer.TopicsDir, t.Namespace, t.Name)
|
||||
if err = fca.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
|
||||
data, err := filer.ReadInsideFiler(client, topicDir, "topic.conf")
|
||||
if err == filer_pb.ErrNotFound {
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("read topic.conf of %v: %v", t, err)
|
||||
}
|
||||
// parse into filer conf object
|
||||
conf = &mq_pb.ConfigureTopicResponse{}
|
||||
if err = jsonpb.Unmarshal(data, conf); err != nil {
|
||||
return fmt.Errorf("unmarshal topic %v conf: %v", t, err)
|
||||
}
|
||||
return nil
|
||||
conf, err = t.ReadConfFile(client)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -47,6 +47,8 @@ func (fh *FileHandle) readFromChunks(buff []byte, offset int64) (int64, int64, e
|
||||
if fileSize == 0 {
|
||||
glog.V(1).Infof("empty fh %v", fileFullPath)
|
||||
return 0, 0, io.EOF
|
||||
} else if offset == fileSize {
|
||||
return 0, 0, io.EOF
|
||||
} else if offset >= fileSize {
|
||||
glog.V(1).Infof("invalid read, fileSize %d, offset %d for %s", fileSize, offset, fileFullPath)
|
||||
return 0, 0, io.EOF
|
||||
|
||||
@@ -51,7 +51,7 @@ func (pw *PageWriter) FlushData() error {
|
||||
}
|
||||
|
||||
func (pw *PageWriter) ReadDirtyDataAt(data []byte, offset int64, tsNs int64) (maxStop int64) {
|
||||
glog.V(4).Infof("ReadDirtyDataAt %v [%d, %d)", pw.fh.fh, offset, offset+int64(len(data)))
|
||||
glog.V(4).Infof("ReadDirtyDataAt %v [%d, %d)", pw.fh.inode, offset, offset+int64(len(data)))
|
||||
|
||||
chunkIndex := offset / pw.chunkSize
|
||||
for i := chunkIndex; len(data) > 0; i++ {
|
||||
|
||||
@@ -46,6 +46,7 @@ type Option struct {
|
||||
Umask os.FileMode
|
||||
Quota int64
|
||||
DisableXAttr bool
|
||||
IsMacOs bool
|
||||
|
||||
MountUid uint32
|
||||
MountGid uint32
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
)
|
||||
|
||||
func (wfs *WFS) GetAttr(cancel <-chan struct{}, input *fuse.GetAttrIn, out *fuse.AttrOut) (code fuse.Status) {
|
||||
glog.V(4).Infof("GetAttr %v", input.NodeId)
|
||||
if input.NodeId == 1 {
|
||||
wfs.setRootAttr(out)
|
||||
return fuse.OK
|
||||
|
||||
@@ -2,6 +2,7 @@ package mount
|
||||
|
||||
import (
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -66,6 +67,14 @@ func (wfs *WFS) Open(cancel <-chan struct{}, in *fuse.OpenIn, out *fuse.OpenOut)
|
||||
if status == fuse.OK {
|
||||
out.Fh = uint64(fileHandle.fh)
|
||||
out.OpenFlags = in.Flags
|
||||
if wfs.option.IsMacOs {
|
||||
// remove the direct_io flag, as it is not well-supported on macOS
|
||||
// https://code.google.com/archive/p/macfuse/wikis/OPTIONS.wiki recommended to avoid the direct_io flag
|
||||
if in.Flags&fuse.FOPEN_DIRECT_IO != 0 {
|
||||
glog.V(4).Infof("macfuse direct_io mode %v => false\n", in.Flags&fuse.FOPEN_DIRECT_IO != 0)
|
||||
out.OpenFlags &^= fuse.FOPEN_DIRECT_IO
|
||||
}
|
||||
}
|
||||
// TODO https://github.com/libfuse/libfuse/blob/master/include/fuse_common.h#L64
|
||||
}
|
||||
return status
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||
"github.com/seaweedfs/seaweedfs/weed/mq/logstore"
|
||||
"github.com/seaweedfs/seaweedfs/weed/mq/pub_balancer"
|
||||
"github.com/seaweedfs/seaweedfs/weed/mq/topic"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb"
|
||||
@@ -26,7 +27,7 @@ func (b *MessageQueueBroker) AssignTopicPartitions(c context.Context, request *m
|
||||
} else {
|
||||
var localPartition *topic.LocalPartition
|
||||
if localPartition = b.localTopicManager.GetLocalPartition(t, partition); localPartition == nil {
|
||||
localPartition = topic.NewLocalPartition(partition, b.genLogFlushFunc(t, assignment.Partition), b.genLogOnDiskReadFunc(t, assignment.Partition))
|
||||
localPartition = topic.NewLocalPartition(partition, b.genLogFlushFunc(t, partition), logstore.GenMergedReadFunc(b, t, partition))
|
||||
b.localTopicManager.AddLocalPartition(t, localPartition)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ func (b *MessageQueueBroker) ConfigureTopic(ctx context.Context, request *mq_pb.
|
||||
resp.RecordType = request.RecordType
|
||||
|
||||
// save the topic configuration on filer
|
||||
if err := b.fca.SaveTopicConfToFiler(request.Topic, resp); err != nil {
|
||||
if err := b.fca.SaveTopicConfToFiler(t, resp); err != nil {
|
||||
return nil, fmt.Errorf("configure topic: %v", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package broker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/seaweedfs/seaweedfs/weed/filer"
|
||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||
"github.com/seaweedfs/seaweedfs/weed/mq/topic"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/mq_pb"
|
||||
@@ -93,9 +92,7 @@ func (b *MessageQueueBroker) PublishFollowMe(stream mq_pb.SeaweedMessaging_Publi
|
||||
time.Sleep(113 * time.Millisecond)
|
||||
}
|
||||
|
||||
topicDir := fmt.Sprintf("%s/%s/%s", filer.TopicsDir, t.Namespace, t.Name)
|
||||
partitionGeneration := time.Unix(0, p.UnixTimeNs).UTC().Format(topic.TIME_FORMAT)
|
||||
partitionDir := fmt.Sprintf("%s/%s/%04d-%04d", topicDir, partitionGeneration, p.RangeStart, p.RangeStop)
|
||||
partitionDir := topic.PartitionDir(t, p)
|
||||
|
||||
// flush the remaining messages
|
||||
inMemoryBuffers.CloseInput()
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/mq_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (b *MessageQueueBroker) SubscribeFollowMe(stream mq_pb.SeaweedMessaging_SubscribeFollowMeServer) (err error) {
|
||||
@@ -65,9 +64,7 @@ func (b *MessageQueueBroker) SubscribeFollowMe(stream mq_pb.SeaweedMessaging_Sub
|
||||
func (b *MessageQueueBroker) readConsumerGroupOffset(initMessage *mq_pb.SubscribeMessageRequest_InitMessage) (offset int64, err error) {
|
||||
t, p := topic.FromPbTopic(initMessage.Topic), topic.FromPbPartition(initMessage.PartitionOffset.Partition)
|
||||
|
||||
topicDir := fmt.Sprintf("%s/%s/%s", filer.TopicsDir, t.Namespace, t.Name)
|
||||
partitionGeneration := time.Unix(0, p.UnixTimeNs).UTC().Format(topic.TIME_FORMAT)
|
||||
partitionDir := fmt.Sprintf("%s/%s/%04d-%04d", topicDir, partitionGeneration, p.RangeStart, p.RangeStop)
|
||||
partitionDir := topic.PartitionDir(t, p)
|
||||
offsetFileName := fmt.Sprintf("%s.offset", initMessage.ConsumerGroup)
|
||||
|
||||
err = b.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
|
||||
@@ -86,9 +83,7 @@ func (b *MessageQueueBroker) readConsumerGroupOffset(initMessage *mq_pb.Subscrib
|
||||
|
||||
func (b *MessageQueueBroker) saveConsumerGroupOffset(t topic.Topic, p topic.Partition, consumerGroup string, offset int64) error {
|
||||
|
||||
topicDir := fmt.Sprintf("%s/%s/%s", filer.TopicsDir, t.Namespace, t.Name)
|
||||
partitionGeneration := time.Unix(0, p.UnixTimeNs).UTC().Format(topic.TIME_FORMAT)
|
||||
partitionDir := fmt.Sprintf("%s/%s/%04d-%04d", topicDir, partitionGeneration, p.RangeStart, p.RangeStop)
|
||||
partitionDir := topic.PartitionDir(t, p)
|
||||
offsetFileName := fmt.Sprintf("%s.offset", consumerGroup)
|
||||
|
||||
offsetBytes := make([]byte, 8)
|
||||
|
||||
@@ -3,6 +3,7 @@ package broker
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||
"github.com/seaweedfs/seaweedfs/weed/mq/logstore"
|
||||
"github.com/seaweedfs/seaweedfs/weed/mq/pub_balancer"
|
||||
"github.com/seaweedfs/seaweedfs/weed/mq/topic"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/mq_pb"
|
||||
@@ -40,7 +41,7 @@ func (b *MessageQueueBroker) genLocalPartitionFromFiler(t topic.Topic, partition
|
||||
self := b.option.BrokerAddress()
|
||||
for _, assignment := range conf.BrokerPartitionAssignments {
|
||||
if assignment.LeaderBroker == string(self) && partition.Equals(topic.FromPbPartition(assignment.Partition)) {
|
||||
localPartition = topic.NewLocalPartition(partition, b.genLogFlushFunc(t, assignment.Partition), b.genLogOnDiskReadFunc(t, assignment.Partition))
|
||||
localPartition = topic.NewLocalPartition(partition, b.genLogFlushFunc(t, partition), logstore.GenMergedReadFunc(b, t, partition))
|
||||
b.localTopicManager.AddLocalPartition(t, localPartition)
|
||||
isGenerated = true
|
||||
break
|
||||
@@ -55,7 +56,7 @@ func (b *MessageQueueBroker) ensureTopicActiveAssignments(t topic.Topic, conf *m
|
||||
hasChanges := pub_balancer.EnsureAssignmentsToActiveBrokers(b.PubBalancer.Brokers, 1, conf.BrokerPartitionAssignments)
|
||||
if hasChanges {
|
||||
glog.V(0).Infof("topic %v partition updated assignments: %v", t, conf.BrokerPartitionAssignments)
|
||||
if err = b.fca.SaveTopicConfToFiler(t.ToPbTopic(), conf); err != nil {
|
||||
if err = b.fca.SaveTopicConfToFiler(t, conf); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,24 +2,15 @@ package broker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/seaweedfs/seaweedfs/weed/filer"
|
||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||
"github.com/seaweedfs/seaweedfs/weed/mq/topic"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/mq_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util/log_buffer"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"math"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
util_http "github.com/seaweedfs/seaweedfs/weed/util/http"
|
||||
)
|
||||
|
||||
func (b *MessageQueueBroker) genLogFlushFunc(t topic.Topic, partition *mq_pb.Partition) log_buffer.LogFlushFuncType {
|
||||
topicDir := fmt.Sprintf("%s/%s/%s", filer.TopicsDir, t.Namespace, t.Name)
|
||||
partitionGeneration := time.Unix(0, partition.UnixTimeNs).UTC().Format(topic.TIME_FORMAT)
|
||||
partitionDir := fmt.Sprintf("%s/%s/%04d-%04d", topicDir, partitionGeneration, partition.RangeStart, partition.RangeStop)
|
||||
func (b *MessageQueueBroker) genLogFlushFunc(t topic.Topic, p topic.Partition) log_buffer.LogFlushFuncType {
|
||||
partitionDir := topic.PartitionDir(t, p)
|
||||
|
||||
return func(logBuffer *log_buffer.LogBuffer, startTime, stopTime time.Time, buf []byte) {
|
||||
if len(buf) == 0 {
|
||||
@@ -45,7 +36,6 @@ func (b *MessageQueueBroker) genLogFlushFunc(t topic.Topic, partition *mq_pb.Par
|
||||
|
||||
b.accessLock.Lock()
|
||||
defer b.accessLock.Unlock()
|
||||
p := topic.FromPbPartition(partition)
|
||||
if localPartition := b.localTopicManager.GetLocalPartition(t, p); localPartition != nil {
|
||||
localPartition.NotifyLogFlushed(logBuffer.LastFlushTsNs)
|
||||
}
|
||||
@@ -53,126 +43,3 @@ func (b *MessageQueueBroker) genLogFlushFunc(t topic.Topic, partition *mq_pb.Par
|
||||
glog.V(0).Infof("flushing at %d to %s size %d", logBuffer.LastFlushTsNs, targetFile, len(buf))
|
||||
}
|
||||
}
|
||||
|
||||
func (b *MessageQueueBroker) genLogOnDiskReadFunc(t topic.Topic, partition *mq_pb.Partition) log_buffer.LogReadFromDiskFuncType {
|
||||
topicDir := fmt.Sprintf("%s/%s/%s", filer.TopicsDir, t.Namespace, t.Name)
|
||||
partitionGeneration := time.Unix(0, partition.UnixTimeNs).UTC().Format(topic.TIME_FORMAT)
|
||||
partitionDir := fmt.Sprintf("%s/%s/%04d-%04d", topicDir, partitionGeneration, partition.RangeStart, partition.RangeStop)
|
||||
|
||||
lookupFileIdFn := func(fileId string) (targetUrls []string, err error) {
|
||||
return b.MasterClient.LookupFileId(fileId)
|
||||
}
|
||||
|
||||
eachChunkFn := func(buf []byte, eachLogEntryFn log_buffer.EachLogEntryFuncType, starTsNs, stopTsNs int64) (processedTsNs int64, err error) {
|
||||
for pos := 0; pos+4 < len(buf); {
|
||||
|
||||
size := util.BytesToUint32(buf[pos : pos+4])
|
||||
if pos+4+int(size) > len(buf) {
|
||||
err = fmt.Errorf("LogOnDiskReadFunc: read [%d,%d) from [0,%d)", pos, pos+int(size)+4, len(buf))
|
||||
return
|
||||
}
|
||||
entryData := buf[pos+4 : pos+4+int(size)]
|
||||
|
||||
logEntry := &filer_pb.LogEntry{}
|
||||
if err = proto.Unmarshal(entryData, logEntry); err != nil {
|
||||
pos += 4 + int(size)
|
||||
err = fmt.Errorf("unexpected unmarshal mq_pb.Message: %v", err)
|
||||
return
|
||||
}
|
||||
if logEntry.TsNs < starTsNs {
|
||||
pos += 4 + int(size)
|
||||
continue
|
||||
}
|
||||
if stopTsNs != 0 && logEntry.TsNs > stopTsNs {
|
||||
println("stopTsNs", stopTsNs, "logEntry.TsNs", logEntry.TsNs)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = eachLogEntryFn(logEntry); err != nil {
|
||||
err = fmt.Errorf("process log entry %v: %v", logEntry, err)
|
||||
return
|
||||
}
|
||||
|
||||
processedTsNs = logEntry.TsNs
|
||||
|
||||
pos += 4 + int(size)
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
eachFileFn := func(entry *filer_pb.Entry, eachLogEntryFn log_buffer.EachLogEntryFuncType, starTsNs, stopTsNs int64) (processedTsNs int64, err error) {
|
||||
if len(entry.Content) > 0 {
|
||||
// skip .offset files
|
||||
return
|
||||
}
|
||||
var urlStrings []string
|
||||
for _, chunk := range entry.Chunks {
|
||||
if chunk.Size == 0 {
|
||||
continue
|
||||
}
|
||||
if chunk.IsChunkManifest {
|
||||
glog.Warningf("this should not happen. unexpected chunk manifest in %s/%s", partitionDir, entry.Name)
|
||||
return
|
||||
}
|
||||
urlStrings, err = lookupFileIdFn(chunk.FileId)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("lookup %s: %v", chunk.FileId, err)
|
||||
return
|
||||
}
|
||||
if len(urlStrings) == 0 {
|
||||
err = fmt.Errorf("no url found for %s", chunk.FileId)
|
||||
return
|
||||
}
|
||||
|
||||
// try one of the urlString until util.Get(urlString) succeeds
|
||||
var processed bool
|
||||
for _, urlString := range urlStrings {
|
||||
// TODO optimization opportunity: reuse the buffer
|
||||
var data []byte
|
||||
if data, _, err = util_http.Get(urlString); err == nil {
|
||||
processed = true
|
||||
if processedTsNs, err = eachChunkFn(data, eachLogEntryFn, starTsNs, stopTsNs); err != nil {
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !processed {
|
||||
err = fmt.Errorf("no data processed for %s %s", entry.Name, chunk.FileId)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
return func(startPosition log_buffer.MessagePosition, stopTsNs int64, eachLogEntryFn log_buffer.EachLogEntryFuncType) (lastReadPosition log_buffer.MessagePosition, isDone bool, err error) {
|
||||
startFileName := startPosition.UTC().Format(topic.TIME_FORMAT)
|
||||
startTsNs := startPosition.Time.UnixNano()
|
||||
stopTime := time.Unix(0, stopTsNs)
|
||||
var processedTsNs int64
|
||||
err = b.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
|
||||
return filer_pb.SeaweedList(client, partitionDir, "", func(entry *filer_pb.Entry, isLast bool) error {
|
||||
if entry.IsDirectory {
|
||||
return nil
|
||||
}
|
||||
if stopTsNs != 0 && entry.Name > stopTime.UTC().Format(topic.TIME_FORMAT) {
|
||||
isDone = true
|
||||
return nil
|
||||
}
|
||||
if entry.Name < startPosition.UTC().Format(topic.TIME_FORMAT) {
|
||||
return nil
|
||||
}
|
||||
if processedTsNs, err = eachFileFn(entry, eachLogEntryFn, startTsNs, stopTsNs); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
}, startFileName, true, math.MaxInt32)
|
||||
})
|
||||
lastReadPosition = log_buffer.NewMessagePosition(processedTsNs, -2)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,12 @@ import (
|
||||
"github.com/seaweedfs/seaweedfs/weed/mq/schema"
|
||||
"github.com/seaweedfs/seaweedfs/weed/mq/topic"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/schema_pb"
|
||||
util_http "github.com/seaweedfs/seaweedfs/weed/util/http"
|
||||
"log"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
util_http "github.com/seaweedfs/seaweedfs/weed/util/http"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -25,11 +26,17 @@ var (
|
||||
namespace = flag.String("ns", "test", "namespace")
|
||||
t = flag.String("t", "test", "t")
|
||||
seedBrokers = flag.String("brokers", "localhost:17777", "seed brokers")
|
||||
|
||||
counter int32
|
||||
)
|
||||
|
||||
func doPublish(publisher *pub_client.TopicPublisher, id int) {
|
||||
startTime := time.Now()
|
||||
for i := 0; i < *messageCount / *concurrency; i++ {
|
||||
for {
|
||||
i := atomic.AddInt32(&counter, 1)
|
||||
if i > int32(*messageCount) {
|
||||
break
|
||||
}
|
||||
// Simulate publishing a message
|
||||
myRecord := genMyRecord(int32(i))
|
||||
if err := publisher.PublishRecord(myRecord.Key, myRecord.ToRecordValue()); err != nil {
|
||||
@@ -38,7 +45,7 @@ func doPublish(publisher *pub_client.TopicPublisher, id int) {
|
||||
}
|
||||
if *messageDelay > 0 {
|
||||
time.Sleep(*messageDelay)
|
||||
fmt.Printf("sent %+v\n", myRecord)
|
||||
fmt.Printf("sent %+v\n", string(myRecord.Key))
|
||||
}
|
||||
}
|
||||
if err := publisher.FinishPublish(); err != nil {
|
||||
|
||||
@@ -8,12 +8,12 @@ import (
|
||||
"github.com/seaweedfs/seaweedfs/weed/mq/topic"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/schema_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||
util_http "github.com/seaweedfs/seaweedfs/weed/util/http"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"strings"
|
||||
"time"
|
||||
util_http "github.com/seaweedfs/seaweedfs/weed/util/http"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -22,6 +22,7 @@ var (
|
||||
seedBrokers = flag.String("brokers", "localhost:17777", "seed brokers")
|
||||
maxPartitionCount = flag.Int("maxPartitionCount", 3, "max partition count")
|
||||
perPartitionConcurrency = flag.Int("perPartitionConcurrency", 1, "per partition concurrency")
|
||||
timeAgo = flag.Duration("timeAgo", 1*time.Hour, "start time before now. \"300ms\", \"1.5h\" or \"2h45m\". Valid time units are \"ns\", \"us\" (or \"µs\"), \"ms\", \"s\", \"m\", \"h\"")
|
||||
|
||||
clientId = flag.Uint("client_id", uint(util.RandomInt32()), "client id")
|
||||
)
|
||||
@@ -65,7 +66,7 @@ func main() {
|
||||
contentConfig := &sub_client.ContentConfiguration{
|
||||
Topic: topic.NewTopic(*namespace, *t),
|
||||
Filter: "",
|
||||
StartTime: time.Unix(1, 1),
|
||||
StartTime: time.Now().Add(-*timeAgo),
|
||||
}
|
||||
|
||||
brokers := strings.Split(*seedBrokers, ",")
|
||||
@@ -75,9 +76,13 @@ func main() {
|
||||
subscriber.SetEachMessageFunc(func(key, value []byte) error {
|
||||
counter++
|
||||
record := &schema_pb.RecordValue{}
|
||||
proto.Unmarshal(value, record)
|
||||
fmt.Printf("record: %v\n", record)
|
||||
time.Sleep(1300 * time.Millisecond)
|
||||
err := proto.Unmarshal(value, record)
|
||||
if err != nil {
|
||||
fmt.Printf("unmarshal record value: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf("%s %d: %v\n", string(key), len(value), record)
|
||||
}
|
||||
//time.Sleep(1300 * time.Millisecond)
|
||||
return nil
|
||||
})
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/mq_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||
"io"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func (sub *TopicSubscriber) onEachPartition(assigned *mq_pb.BrokerPartitionAssignment, stopCh chan struct{}) error {
|
||||
@@ -24,6 +25,11 @@ func (sub *TopicSubscriber) onEachPartition(assigned *mq_pb.BrokerPartitionAssig
|
||||
perPartitionConcurrency = 1
|
||||
}
|
||||
|
||||
var stopTsNs int64
|
||||
if !sub.ContentConfig.StopTime.IsZero() {
|
||||
stopTsNs = sub.ContentConfig.StopTime.UnixNano()
|
||||
}
|
||||
|
||||
if err = subscribeClient.Send(&mq_pb.SubscribeMessageRequest{
|
||||
Message: &mq_pb.SubscribeMessageRequest_Init{
|
||||
Init: &mq_pb.SubscribeMessageRequest_InitMessage{
|
||||
@@ -32,6 +38,8 @@ func (sub *TopicSubscriber) onEachPartition(assigned *mq_pb.BrokerPartitionAssig
|
||||
Topic: sub.ContentConfig.Topic.ToPbTopic(),
|
||||
PartitionOffset: &mq_pb.PartitionOffset{
|
||||
Partition: assigned.Partition,
|
||||
StartTsNs: sub.ContentConfig.StartTime.UnixNano(),
|
||||
StopTsNs: stopTsNs,
|
||||
StartType: mq_pb.PartitionOffsetStartType_EARLIEST_IN_MEMORY,
|
||||
},
|
||||
Filter: sub.ContentConfig.Filter,
|
||||
@@ -101,6 +109,10 @@ func (sub *TopicSubscriber) onEachPartition(assigned *mq_pb.BrokerPartitionAssig
|
||||
glog.V(2).Infof("subscriber %s received control from producer:%s isClose:%v", sub.SubscriberConfig.ConsumerGroup, m.Data.Ctrl.PublisherName, m.Data.Ctrl.IsClose)
|
||||
continue
|
||||
}
|
||||
if len(m.Data.Key) == 0 {
|
||||
fmt.Printf("empty key %+v, type %v\n", m, reflect.TypeOf(m))
|
||||
continue
|
||||
}
|
||||
executors.Execute(func() {
|
||||
processErr := sub.OnEachMessageFunc(m.Data.Key, m.Data.Value)
|
||||
if processErr == nil {
|
||||
|
||||
@@ -21,6 +21,7 @@ type ContentConfiguration struct {
|
||||
Topic topic.Topic
|
||||
Filter string
|
||||
StartTime time.Time
|
||||
StopTime time.Time
|
||||
}
|
||||
|
||||
type OnEachMessageFunc func(key, value []byte) (err error)
|
||||
|
||||
@@ -0,0 +1,454 @@
|
||||
package logstore
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"github.com/parquet-go/parquet-go"
|
||||
"github.com/parquet-go/parquet-go/compress/zstd"
|
||||
"github.com/seaweedfs/seaweedfs/weed/filer"
|
||||
"github.com/seaweedfs/seaweedfs/weed/mq/schema"
|
||||
"github.com/seaweedfs/seaweedfs/weed/mq/topic"
|
||||
"github.com/seaweedfs/seaweedfs/weed/operation"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/schema_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||
util_http "github.com/seaweedfs/seaweedfs/weed/util/http"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util/log_buffer"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
SW_COLUMN_NAME_TS = "_ts_ns"
|
||||
SW_COLUMN_NAME_KEY = "_key"
|
||||
)
|
||||
|
||||
func CompactTopicPartitions(filerClient filer_pb.FilerClient, t topic.Topic, timeAgo time.Duration, recordType *schema_pb.RecordType, preference *operation.StoragePreference) error {
|
||||
// list the topic partition versions
|
||||
topicVersions, err := collectTopicVersions(filerClient, t, timeAgo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("list topic files: %v", err)
|
||||
}
|
||||
|
||||
// compact the partitions
|
||||
for _, topicVersion := range topicVersions {
|
||||
partitions, err := collectTopicVersionsPartitions(filerClient, t, topicVersion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("list partitions %s/%s/%s: %v", t.Namespace, t.Name, topicVersion, err)
|
||||
}
|
||||
for _, partition := range partitions {
|
||||
err := compactTopicPartition(filerClient, t, timeAgo, recordType, partition, preference)
|
||||
if err != nil {
|
||||
return fmt.Errorf("compact partition %s/%s/%s/%s: %v", t.Namespace, t.Name, topicVersion, partition, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func collectTopicVersions(filerClient filer_pb.FilerClient, t topic.Topic, timeAgo time.Duration) (partitionVersions []time.Time, err error) {
|
||||
err = filer_pb.ReadDirAllEntries(filerClient, util.FullPath(t.Dir()), "", func(entry *filer_pb.Entry, isLast bool) error {
|
||||
t, err := topic.ParseTopicVersion(entry.Name)
|
||||
if err != nil {
|
||||
// skip non-partition directories
|
||||
return nil
|
||||
}
|
||||
if t.Unix() < time.Now().Unix()-int64(timeAgo/time.Second) {
|
||||
partitionVersions = append(partitionVersions, t)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func collectTopicVersionsPartitions(filerClient filer_pb.FilerClient, t topic.Topic, topicVersion time.Time) (partitions []topic.Partition, err error) {
|
||||
version := topicVersion.Format(topic.PartitionGenerationFormat)
|
||||
err = filer_pb.ReadDirAllEntries(filerClient, util.FullPath(t.Dir()).Child(version), "", func(entry *filer_pb.Entry, isLast bool) error {
|
||||
if !entry.IsDirectory {
|
||||
return nil
|
||||
}
|
||||
start, stop := topic.ParsePartitionBoundary(entry.Name)
|
||||
if start != stop {
|
||||
partitions = append(partitions, topic.Partition{
|
||||
RangeStart: start,
|
||||
RangeStop: stop,
|
||||
RingSize: topic.PartitionCount,
|
||||
UnixTimeNs: topicVersion.UnixNano(),
|
||||
})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func compactTopicPartition(filerClient filer_pb.FilerClient, t topic.Topic, timeAgo time.Duration, recordType *schema_pb.RecordType, partition topic.Partition, preference *operation.StoragePreference) error {
|
||||
partitionDir := topic.PartitionDir(t, partition)
|
||||
|
||||
// compact the partition directory
|
||||
return compactTopicPartitionDir(filerClient, t.Name, partitionDir, timeAgo, recordType, preference)
|
||||
}
|
||||
|
||||
func compactTopicPartitionDir(filerClient filer_pb.FilerClient, topicName, partitionDir string, timeAgo time.Duration, recordType *schema_pb.RecordType, preference *operation.StoragePreference) error {
|
||||
// read all existing parquet files
|
||||
minTsNs, maxTsNs, err := readAllParquetFiles(filerClient, partitionDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// read all log files
|
||||
logFiles, err := readAllLogFiles(filerClient, partitionDir, timeAgo, minTsNs, maxTsNs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(logFiles) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// divide log files into groups of 128MB
|
||||
logFileGroups := groupFilesBySize(logFiles, 128*1024*1024)
|
||||
|
||||
// write to parquet file
|
||||
parquetLevels, err := schema.ToParquetLevels(recordType)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ToParquetLevels failed %+v: %v", recordType, err)
|
||||
}
|
||||
|
||||
// create a parquet schema
|
||||
parquetSchema, err := schema.ToParquetSchema(topicName, recordType)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ToParquetSchema failed: %v", err)
|
||||
}
|
||||
|
||||
// TODO parallelize the writing
|
||||
for _, logFileGroup := range logFileGroups {
|
||||
if err = writeLogFilesToParquet(filerClient, partitionDir, recordType, logFileGroup, parquetSchema, parquetLevels, preference); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func groupFilesBySize(logFiles []*filer_pb.Entry, maxGroupSize int64) (logFileGroups [][]*filer_pb.Entry) {
|
||||
var logFileGroup []*filer_pb.Entry
|
||||
var groupSize int64
|
||||
for _, logFile := range logFiles {
|
||||
if groupSize+int64(logFile.Attributes.FileSize) > maxGroupSize {
|
||||
logFileGroups = append(logFileGroups, logFileGroup)
|
||||
logFileGroup = nil
|
||||
groupSize = 0
|
||||
}
|
||||
logFileGroup = append(logFileGroup, logFile)
|
||||
groupSize += int64(logFile.Attributes.FileSize)
|
||||
}
|
||||
if len(logFileGroup) > 0 {
|
||||
logFileGroups = append(logFileGroups, logFileGroup)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func readAllLogFiles(filerClient filer_pb.FilerClient, partitionDir string, timeAgo time.Duration, minTsNs, maxTsNs int64) (logFiles []*filer_pb.Entry, err error) {
|
||||
err = filer_pb.ReadDirAllEntries(filerClient, util.FullPath(partitionDir), "", func(entry *filer_pb.Entry, isLast bool) error {
|
||||
if strings.HasSuffix(entry.Name, ".parquet") {
|
||||
return nil
|
||||
}
|
||||
if entry.Attributes.Crtime > time.Now().Unix()-int64(timeAgo/time.Second) {
|
||||
return nil
|
||||
}
|
||||
logTime, err := time.Parse(topic.TIME_FORMAT, entry.Name)
|
||||
if err != nil {
|
||||
// glog.Warningf("parse log time %s: %v", entry.Name, err)
|
||||
return nil
|
||||
}
|
||||
if maxTsNs > 0 && logTime.UnixNano() <= maxTsNs {
|
||||
return nil
|
||||
}
|
||||
logFiles = append(logFiles, entry)
|
||||
return nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func readAllParquetFiles(filerClient filer_pb.FilerClient, partitionDir string) (minTsNs, maxTsNs int64, err error) {
|
||||
err = filer_pb.ReadDirAllEntries(filerClient, util.FullPath(partitionDir), "", func(entry *filer_pb.Entry, isLast bool) error {
|
||||
if !strings.HasSuffix(entry.Name, ".parquet") {
|
||||
return nil
|
||||
}
|
||||
if len(entry.Extended) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// read min ts
|
||||
minTsBytes := entry.Extended["min"]
|
||||
if len(minTsBytes) != 8 {
|
||||
return nil
|
||||
}
|
||||
minTs := int64(binary.BigEndian.Uint64(minTsBytes))
|
||||
if minTsNs == 0 || minTs < minTsNs {
|
||||
minTsNs = minTs
|
||||
}
|
||||
|
||||
// read max ts
|
||||
maxTsBytes := entry.Extended["max"]
|
||||
if len(maxTsBytes) != 8 {
|
||||
return nil
|
||||
}
|
||||
maxTs := int64(binary.BigEndian.Uint64(maxTsBytes))
|
||||
if maxTsNs == 0 || maxTs > maxTsNs {
|
||||
maxTsNs = maxTs
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func writeLogFilesToParquet(filerClient filer_pb.FilerClient, partitionDir string, recordType *schema_pb.RecordType, logFileGroups []*filer_pb.Entry, parquetSchema *parquet.Schema, parquetLevels *schema.ParquetLevels, preference *operation.StoragePreference) (err error) {
|
||||
|
||||
tempFile, err := os.CreateTemp(".", "t*.parquet")
|
||||
if err != nil {
|
||||
return fmt.Errorf("create temp file: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
tempFile.Close()
|
||||
os.Remove(tempFile.Name())
|
||||
}()
|
||||
|
||||
writer := parquet.NewWriter(tempFile, parquetSchema, parquet.Compression(&zstd.Codec{Level: zstd.DefaultLevel}))
|
||||
rowBuilder := parquet.NewRowBuilder(parquetSchema)
|
||||
|
||||
var startTsNs, stopTsNs int64
|
||||
|
||||
for _, logFile := range logFileGroups {
|
||||
fmt.Printf("compact %s/%s ", partitionDir, logFile.Name)
|
||||
var rows []parquet.Row
|
||||
if err := iterateLogEntries(filerClient, logFile, func(entry *filer_pb.LogEntry) error {
|
||||
|
||||
if startTsNs == 0 {
|
||||
startTsNs = entry.TsNs
|
||||
}
|
||||
stopTsNs = entry.TsNs
|
||||
|
||||
if len(entry.Key) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// write to parquet file
|
||||
rowBuilder.Reset()
|
||||
|
||||
record := &schema_pb.RecordValue{}
|
||||
if err := proto.Unmarshal(entry.Data, record); err != nil {
|
||||
return fmt.Errorf("unmarshal record value: %v", err)
|
||||
}
|
||||
|
||||
record.Fields[SW_COLUMN_NAME_TS] = &schema_pb.Value{
|
||||
Kind: &schema_pb.Value_Int64Value{
|
||||
Int64Value: entry.TsNs,
|
||||
},
|
||||
}
|
||||
record.Fields[SW_COLUMN_NAME_KEY] = &schema_pb.Value{
|
||||
Kind: &schema_pb.Value_BytesValue{
|
||||
BytesValue: entry.Key,
|
||||
},
|
||||
}
|
||||
|
||||
if err := schema.AddRecordValue(rowBuilder, recordType, parquetLevels, record); err != nil {
|
||||
return fmt.Errorf("add record value: %v", err)
|
||||
}
|
||||
|
||||
rows = append(rows, rowBuilder.Row())
|
||||
|
||||
return nil
|
||||
|
||||
}); err != nil {
|
||||
return fmt.Errorf("iterate log entry %v/%v: %v", partitionDir, logFile.Name, err)
|
||||
}
|
||||
|
||||
fmt.Printf("processed %d rows\n", len(rows))
|
||||
|
||||
if _, err := writer.WriteRows(rows); err != nil {
|
||||
return fmt.Errorf("write rows: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := writer.Close(); err != nil {
|
||||
return fmt.Errorf("close writer: %v", err)
|
||||
}
|
||||
|
||||
// write to parquet file to partitionDir
|
||||
parquetFileName := fmt.Sprintf("%s.parquet", time.Unix(0, startTsNs).UTC().Format("2006-01-02-15-04-05"))
|
||||
if err := saveParquetFileToPartitionDir(filerClient, tempFile, partitionDir, parquetFileName, preference, startTsNs, stopTsNs); err != nil {
|
||||
return fmt.Errorf("save parquet file %s: %v", parquetFileName, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func saveParquetFileToPartitionDir(filerClient filer_pb.FilerClient, sourceFile *os.File, partitionDir, parquetFileName string, preference *operation.StoragePreference, startTsNs, stopTsNs int64) error {
|
||||
uploader, err := operation.NewUploader()
|
||||
if err != nil {
|
||||
return fmt.Errorf("new uploader: %v", err)
|
||||
}
|
||||
|
||||
// get file size
|
||||
fileInfo, err := sourceFile.Stat()
|
||||
if err != nil {
|
||||
return fmt.Errorf("stat source file: %v", err)
|
||||
}
|
||||
|
||||
// upload file in chunks
|
||||
chunkSize := int64(4 * 1024 * 1024)
|
||||
chunkCount := (fileInfo.Size() + chunkSize - 1) / chunkSize
|
||||
entry := &filer_pb.Entry{
|
||||
Name: parquetFileName,
|
||||
Attributes: &filer_pb.FuseAttributes{
|
||||
Crtime: time.Now().Unix(),
|
||||
Mtime: time.Now().Unix(),
|
||||
FileMode: uint32(os.FileMode(0644)),
|
||||
FileSize: uint64(fileInfo.Size()),
|
||||
Mime: "application/vnd.apache.parquet",
|
||||
},
|
||||
}
|
||||
entry.Extended = make(map[string][]byte)
|
||||
minTsBytes := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(minTsBytes, uint64(startTsNs))
|
||||
entry.Extended["min"] = minTsBytes
|
||||
maxTsBytes := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(maxTsBytes, uint64(stopTsNs))
|
||||
entry.Extended["max"] = maxTsBytes
|
||||
|
||||
for i := int64(0); i < chunkCount; i++ {
|
||||
fileId, uploadResult, err, _ := uploader.UploadWithRetry(
|
||||
filerClient,
|
||||
&filer_pb.AssignVolumeRequest{
|
||||
Count: 1,
|
||||
Replication: preference.Replication,
|
||||
Collection: preference.Collection,
|
||||
TtlSec: 0, // TODO set ttl
|
||||
DiskType: preference.DiskType,
|
||||
Path: partitionDir + "/" + parquetFileName,
|
||||
},
|
||||
&operation.UploadOption{
|
||||
Filename: parquetFileName,
|
||||
Cipher: false,
|
||||
IsInputCompressed: false,
|
||||
MimeType: "application/vnd.apache.parquet",
|
||||
PairMap: nil,
|
||||
},
|
||||
func(host, fileId string) string {
|
||||
return fmt.Sprintf("http://%s/%s", host, fileId)
|
||||
},
|
||||
io.NewSectionReader(sourceFile, i*chunkSize, chunkSize),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("upload chunk %d: %v", i, err)
|
||||
}
|
||||
if uploadResult.Error != "" {
|
||||
return fmt.Errorf("upload result: %v", uploadResult.Error)
|
||||
}
|
||||
entry.Chunks = append(entry.Chunks, uploadResult.ToPbFileChunk(fileId, i*chunkSize, time.Now().UnixNano()))
|
||||
}
|
||||
|
||||
// write the entry to partitionDir
|
||||
if err := filerClient.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
|
||||
return filer_pb.CreateEntry(client, &filer_pb.CreateEntryRequest{
|
||||
Directory: partitionDir,
|
||||
Entry: entry,
|
||||
})
|
||||
}); err != nil {
|
||||
return fmt.Errorf("create entry: %v", err)
|
||||
}
|
||||
fmt.Printf("saved to %s/%s\n", partitionDir, parquetFileName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func iterateLogEntries(filerClient filer_pb.FilerClient, logFile *filer_pb.Entry, eachLogEntryFn func(entry *filer_pb.LogEntry) error) error {
|
||||
lookupFn := filer.LookupFn(filerClient)
|
||||
_, err := eachFile(logFile, lookupFn, func(logEntry *filer_pb.LogEntry) (isDone bool, err error) {
|
||||
if err := eachLogEntryFn(logEntry); err != nil {
|
||||
return true, err
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func eachFile(entry *filer_pb.Entry, lookupFileIdFn func(fileId string) (targetUrls []string, err error), eachLogEntryFn log_buffer.EachLogEntryFuncType) (processedTsNs int64, err error) {
|
||||
if len(entry.Content) > 0 {
|
||||
// skip .offset files
|
||||
return
|
||||
}
|
||||
var urlStrings []string
|
||||
for _, chunk := range entry.Chunks {
|
||||
if chunk.Size == 0 {
|
||||
continue
|
||||
}
|
||||
if chunk.IsChunkManifest {
|
||||
fmt.Printf("this should not happen. unexpected chunk manifest in %s", entry.Name)
|
||||
return
|
||||
}
|
||||
urlStrings, err = lookupFileIdFn(chunk.FileId)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("lookup %s: %v", chunk.FileId, err)
|
||||
return
|
||||
}
|
||||
if len(urlStrings) == 0 {
|
||||
err = fmt.Errorf("no url found for %s", chunk.FileId)
|
||||
return
|
||||
}
|
||||
|
||||
// try one of the urlString until util.Get(urlString) succeeds
|
||||
var processed bool
|
||||
for _, urlString := range urlStrings {
|
||||
var data []byte
|
||||
if data, _, err = util_http.Get(urlString); err == nil {
|
||||
processed = true
|
||||
if processedTsNs, err = eachChunk(data, eachLogEntryFn); err != nil {
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !processed {
|
||||
err = fmt.Errorf("no data processed for %s %s", entry.Name, chunk.FileId)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func eachChunk(buf []byte, eachLogEntryFn log_buffer.EachLogEntryFuncType) (processedTsNs int64, err error) {
|
||||
for pos := 0; pos+4 < len(buf); {
|
||||
|
||||
size := util.BytesToUint32(buf[pos : pos+4])
|
||||
if pos+4+int(size) > len(buf) {
|
||||
err = fmt.Errorf("reach each log chunk: read [%d,%d) from [0,%d)", pos, pos+int(size)+4, len(buf))
|
||||
return
|
||||
}
|
||||
entryData := buf[pos+4 : pos+4+int(size)]
|
||||
|
||||
logEntry := &filer_pb.LogEntry{}
|
||||
if err = proto.Unmarshal(entryData, logEntry); err != nil {
|
||||
pos += 4 + int(size)
|
||||
err = fmt.Errorf("unexpected unmarshal mq_pb.Message: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = eachLogEntryFn(logEntry); err != nil {
|
||||
err = fmt.Errorf("process log entry %v: %v", logEntry, err)
|
||||
return
|
||||
}
|
||||
|
||||
processedTsNs = logEntry.TsNs
|
||||
|
||||
pos += 4 + int(size)
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package logstore
|
||||
|
||||
import (
|
||||
"github.com/seaweedfs/seaweedfs/weed/mq/topic"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util/log_buffer"
|
||||
)
|
||||
|
||||
func GenMergedReadFunc(filerClient filer_pb.FilerClient, t topic.Topic, p topic.Partition) log_buffer.LogReadFromDiskFuncType {
|
||||
fromParquetFn := GenParquetReadFunc(filerClient, t, p)
|
||||
readLogDirectFn := GenLogOnDiskReadFunc(filerClient, t, p)
|
||||
return mergeReadFuncs(fromParquetFn, readLogDirectFn)
|
||||
}
|
||||
|
||||
func mergeReadFuncs(fromParquetFn, readLogDirectFn log_buffer.LogReadFromDiskFuncType) log_buffer.LogReadFromDiskFuncType {
|
||||
var exhaustedParquet bool
|
||||
var lastProcessedPosition log_buffer.MessagePosition
|
||||
return func(startPosition log_buffer.MessagePosition, stopTsNs int64, eachLogEntryFn log_buffer.EachLogEntryFuncType) (lastReadPosition log_buffer.MessagePosition, isDone bool, err error) {
|
||||
if !exhaustedParquet {
|
||||
// glog.V(4).Infof("reading from parquet startPosition: %v\n", startPosition.UTC())
|
||||
lastReadPosition, isDone, err = fromParquetFn(startPosition, stopTsNs, eachLogEntryFn)
|
||||
// glog.V(4).Infof("read from parquet: %v %v %v %v\n", startPosition, lastReadPosition, isDone, err)
|
||||
if isDone {
|
||||
isDone = false
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
lastProcessedPosition = lastReadPosition
|
||||
}
|
||||
exhaustedParquet = true
|
||||
|
||||
if startPosition.Before(lastProcessedPosition.Time) {
|
||||
startPosition = lastProcessedPosition
|
||||
}
|
||||
|
||||
// glog.V(4).Infof("reading from direct log startPosition: %v\n", startPosition.UTC())
|
||||
lastReadPosition, isDone, err = readLogDirectFn(startPosition, stopTsNs, eachLogEntryFn)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package logstore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/seaweedfs/seaweedfs/weed/filer"
|
||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||
"github.com/seaweedfs/seaweedfs/weed/mq/topic"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||
util_http "github.com/seaweedfs/seaweedfs/weed/util/http"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util/log_buffer"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GenLogOnDiskReadFunc(filerClient filer_pb.FilerClient, t topic.Topic, p topic.Partition) log_buffer.LogReadFromDiskFuncType {
|
||||
partitionDir := topic.PartitionDir(t, p)
|
||||
|
||||
lookupFileIdFn := filer.LookupFn(filerClient)
|
||||
|
||||
eachChunkFn := func(buf []byte, eachLogEntryFn log_buffer.EachLogEntryFuncType, starTsNs, stopTsNs int64) (processedTsNs int64, err error) {
|
||||
for pos := 0; pos+4 < len(buf); {
|
||||
|
||||
size := util.BytesToUint32(buf[pos : pos+4])
|
||||
if pos+4+int(size) > len(buf) {
|
||||
err = fmt.Errorf("GenLogOnDiskReadFunc: read [%d,%d) from [0,%d)", pos, pos+int(size)+4, len(buf))
|
||||
return
|
||||
}
|
||||
entryData := buf[pos+4 : pos+4+int(size)]
|
||||
|
||||
logEntry := &filer_pb.LogEntry{}
|
||||
if err = proto.Unmarshal(entryData, logEntry); err != nil {
|
||||
pos += 4 + int(size)
|
||||
err = fmt.Errorf("unexpected unmarshal mq_pb.Message: %v", err)
|
||||
return
|
||||
}
|
||||
if logEntry.TsNs < starTsNs {
|
||||
pos += 4 + int(size)
|
||||
continue
|
||||
}
|
||||
if stopTsNs != 0 && logEntry.TsNs > stopTsNs {
|
||||
println("stopTsNs", stopTsNs, "logEntry.TsNs", logEntry.TsNs)
|
||||
return
|
||||
}
|
||||
|
||||
// fmt.Printf(" read logEntry: %v, ts %v\n", string(logEntry.Key), time.Unix(0, logEntry.TsNs).UTC())
|
||||
if _, err = eachLogEntryFn(logEntry); err != nil {
|
||||
err = fmt.Errorf("process log entry %v: %v", logEntry, err)
|
||||
return
|
||||
}
|
||||
|
||||
processedTsNs = logEntry.TsNs
|
||||
|
||||
pos += 4 + int(size)
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
eachFileFn := func(entry *filer_pb.Entry, eachLogEntryFn log_buffer.EachLogEntryFuncType, starTsNs, stopTsNs int64) (processedTsNs int64, err error) {
|
||||
if len(entry.Content) > 0 {
|
||||
// skip .offset files
|
||||
return
|
||||
}
|
||||
var urlStrings []string
|
||||
for _, chunk := range entry.Chunks {
|
||||
if chunk.Size == 0 {
|
||||
continue
|
||||
}
|
||||
if chunk.IsChunkManifest {
|
||||
glog.Warningf("this should not happen. unexpected chunk manifest in %s/%s", partitionDir, entry.Name)
|
||||
return
|
||||
}
|
||||
urlStrings, err = lookupFileIdFn(chunk.FileId)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("lookup %s: %v", chunk.FileId, err)
|
||||
return
|
||||
}
|
||||
if len(urlStrings) == 0 {
|
||||
err = fmt.Errorf("no url found for %s", chunk.FileId)
|
||||
return
|
||||
}
|
||||
|
||||
// try one of the urlString until util.Get(urlString) succeeds
|
||||
var processed bool
|
||||
for _, urlString := range urlStrings {
|
||||
// TODO optimization opportunity: reuse the buffer
|
||||
var data []byte
|
||||
// fmt.Printf("reading %s/%s %s\n", partitionDir, entry.Name, urlString)
|
||||
if data, _, err = util_http.Get(urlString); err == nil {
|
||||
processed = true
|
||||
if processedTsNs, err = eachChunkFn(data, eachLogEntryFn, starTsNs, stopTsNs); err != nil {
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !processed {
|
||||
err = fmt.Errorf("no data processed for %s %s", entry.Name, chunk.FileId)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
return func(startPosition log_buffer.MessagePosition, stopTsNs int64, eachLogEntryFn log_buffer.EachLogEntryFuncType) (lastReadPosition log_buffer.MessagePosition, isDone bool, err error) {
|
||||
startFileName := startPosition.UTC().Format(topic.TIME_FORMAT)
|
||||
startTsNs := startPosition.Time.UnixNano()
|
||||
stopTime := time.Unix(0, stopTsNs)
|
||||
var processedTsNs int64
|
||||
err = filerClient.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
|
||||
return filer_pb.SeaweedList(client, partitionDir, "", func(entry *filer_pb.Entry, isLast bool) error {
|
||||
if entry.IsDirectory {
|
||||
return nil
|
||||
}
|
||||
if strings.HasSuffix(entry.Name, ".parquet") {
|
||||
return nil
|
||||
}
|
||||
// FIXME: this is a hack to skip the .offset files
|
||||
if strings.HasSuffix(entry.Name, ".offset") {
|
||||
return nil
|
||||
}
|
||||
if stopTsNs != 0 && entry.Name > stopTime.UTC().Format(topic.TIME_FORMAT) {
|
||||
isDone = true
|
||||
return nil
|
||||
}
|
||||
if entry.Name < startPosition.UTC().Format(topic.TIME_FORMAT) {
|
||||
return nil
|
||||
}
|
||||
if processedTsNs, err = eachFileFn(entry, eachLogEntryFn, startTsNs, stopTsNs); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
}, startFileName, true, math.MaxInt32)
|
||||
})
|
||||
lastReadPosition = log_buffer.NewMessagePosition(processedTsNs, -2)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package logstore
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"github.com/parquet-go/parquet-go"
|
||||
"github.com/seaweedfs/seaweedfs/weed/filer"
|
||||
"github.com/seaweedfs/seaweedfs/weed/mq/schema"
|
||||
"github.com/seaweedfs/seaweedfs/weed/mq/topic"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/mq_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util/chunk_cache"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util/log_buffer"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"io"
|
||||
"math"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
chunkCache = chunk_cache.NewChunkCacheInMemory(256) // 256 entries, 8MB max per entry
|
||||
)
|
||||
|
||||
func GenParquetReadFunc(filerClient filer_pb.FilerClient, t topic.Topic, p topic.Partition) log_buffer.LogReadFromDiskFuncType {
|
||||
partitionDir := topic.PartitionDir(t, p)
|
||||
|
||||
lookupFileIdFn := filer.LookupFn(filerClient)
|
||||
|
||||
// read topic conf from filer
|
||||
var topicConf *mq_pb.ConfigureTopicResponse
|
||||
var err error
|
||||
if err := filerClient.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
|
||||
topicConf, err = t.ReadConfFile(client)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil
|
||||
}
|
||||
recordType := topicConf.GetRecordType()
|
||||
recordType = schema.NewRecordTypeBuilder(recordType).
|
||||
WithField(SW_COLUMN_NAME_TS, schema.TypeInt64).
|
||||
WithField(SW_COLUMN_NAME_KEY, schema.TypeBytes).
|
||||
RecordTypeEnd()
|
||||
|
||||
parquetSchema, err := schema.ToParquetSchema(t.Name, recordType)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
parquetLevels, err := schema.ToParquetLevels(recordType)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// eachFileFn reads a parquet file and calls eachLogEntryFn for each log entry
|
||||
eachFileFn := func(entry *filer_pb.Entry, eachLogEntryFn log_buffer.EachLogEntryFuncType, starTsNs, stopTsNs int64) (processedTsNs int64, err error) {
|
||||
// create readerAt for the parquet file
|
||||
fileSize := filer.FileSize(entry)
|
||||
visibleIntervals, _ := filer.NonOverlappingVisibleIntervals(lookupFileIdFn, entry.Chunks, 0, int64(fileSize))
|
||||
chunkViews := filer.ViewFromVisibleIntervals(visibleIntervals, 0, int64(fileSize))
|
||||
readerCache := filer.NewReaderCache(32, chunkCache, lookupFileIdFn)
|
||||
readerAt := filer.NewChunkReaderAtFromClient(readerCache, chunkViews, int64(fileSize))
|
||||
|
||||
// create parquet reader
|
||||
parquetReader := parquet.NewReader(readerAt, parquetSchema)
|
||||
rows := make([]parquet.Row, 128)
|
||||
for {
|
||||
rowCount, readErr := parquetReader.ReadRows(rows)
|
||||
|
||||
for i := 0; i < rowCount; i++ {
|
||||
row := rows[i]
|
||||
// convert parquet row to schema_pb.RecordValue
|
||||
recordValue, err := schema.ToRecordValue(recordType, parquetLevels, row)
|
||||
if err != nil {
|
||||
return processedTsNs, fmt.Errorf("ToRecordValue failed: %v", err)
|
||||
}
|
||||
processedTsNs = recordValue.Fields[SW_COLUMN_NAME_TS].GetInt64Value()
|
||||
if processedTsNs < starTsNs {
|
||||
continue
|
||||
}
|
||||
if stopTsNs != 0 && processedTsNs >= stopTsNs {
|
||||
return processedTsNs, nil
|
||||
}
|
||||
|
||||
data, marshalErr := proto.Marshal(recordValue)
|
||||
if marshalErr != nil {
|
||||
return processedTsNs, fmt.Errorf("marshal record value: %v", marshalErr)
|
||||
}
|
||||
|
||||
logEntry := &filer_pb.LogEntry{
|
||||
Key: recordValue.Fields[SW_COLUMN_NAME_KEY].GetBytesValue(),
|
||||
TsNs: processedTsNs,
|
||||
Data: data,
|
||||
}
|
||||
|
||||
// fmt.Printf(" parquet entry %s ts %v\n", string(logEntry.Key), time.Unix(0, logEntry.TsNs).UTC())
|
||||
|
||||
if _, err = eachLogEntryFn(logEntry); err != nil {
|
||||
return processedTsNs, fmt.Errorf("process log entry %v: %v", logEntry, err)
|
||||
}
|
||||
}
|
||||
|
||||
if readErr != nil {
|
||||
if readErr == io.EOF {
|
||||
return processedTsNs, nil
|
||||
}
|
||||
return processedTsNs, readErr
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
return func(startPosition log_buffer.MessagePosition, stopTsNs int64, eachLogEntryFn log_buffer.EachLogEntryFuncType) (lastReadPosition log_buffer.MessagePosition, isDone bool, err error) {
|
||||
startFileName := startPosition.UTC().Format(topic.TIME_FORMAT)
|
||||
startTsNs := startPosition.Time.UnixNano()
|
||||
var processedTsNs int64
|
||||
|
||||
err = filerClient.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
|
||||
|
||||
return filer_pb.SeaweedList(client, partitionDir, "", func(entry *filer_pb.Entry, isLast bool) error {
|
||||
if entry.IsDirectory {
|
||||
return nil
|
||||
}
|
||||
if !strings.HasSuffix(entry.Name, ".parquet") {
|
||||
return nil
|
||||
}
|
||||
if len(entry.Extended) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// read minTs from the parquet file
|
||||
minTsBytes := entry.Extended["min"]
|
||||
if len(minTsBytes) != 8 {
|
||||
return nil
|
||||
}
|
||||
minTsNs := int64(binary.BigEndian.Uint64(minTsBytes))
|
||||
|
||||
// read max ts
|
||||
maxTsBytes := entry.Extended["max"]
|
||||
if len(maxTsBytes) != 8 {
|
||||
return nil
|
||||
}
|
||||
maxTsNs := int64(binary.BigEndian.Uint64(maxTsBytes))
|
||||
|
||||
if stopTsNs != 0 && stopTsNs <= minTsNs {
|
||||
isDone = true
|
||||
return nil
|
||||
}
|
||||
|
||||
if maxTsNs < startTsNs {
|
||||
return nil
|
||||
}
|
||||
|
||||
if processedTsNs, err = eachFileFn(entry, eachLogEntryFn, startTsNs, stopTsNs); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
}, startFileName, true, math.MaxInt32)
|
||||
})
|
||||
lastReadPosition = log_buffer.NewMessagePosition(processedTsNs, -2)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ func (bs *BrokerStats) UpdateStats(stats *mq_pb.BrokerStats) {
|
||||
}
|
||||
publisherCount += topicPartitionStats.PublisherCount
|
||||
subscriberCount += topicPartitionStats.SubscriberCount
|
||||
key := tps.TopicPartition.String()
|
||||
key := tps.TopicPartition.TopicPartitionId()
|
||||
bs.TopicPartitionStats.Set(key, tps)
|
||||
delete(currentTopicPartitions, key)
|
||||
}
|
||||
@@ -79,7 +79,7 @@ func (bs *BrokerStats) RegisterAssignment(t *mq_pb.Topic, partition *mq_pb.Parti
|
||||
PublisherCount: 0,
|
||||
SubscriberCount: 0,
|
||||
}
|
||||
key := tps.TopicPartition.String()
|
||||
key := tps.TopicPartition.TopicPartitionId()
|
||||
if isAdd {
|
||||
bs.TopicPartitionStats.SetIfAbsent(key, tps)
|
||||
} else {
|
||||
|
||||
@@ -19,10 +19,12 @@ type RecordTypeBuilder struct {
|
||||
recordType *schema_pb.RecordType
|
||||
}
|
||||
|
||||
// RecordTypeBegin creates a new RecordTypeBuilder, it should be followed by a series of WithField methods and RecordTypeEnd
|
||||
func RecordTypeBegin() *RecordTypeBuilder {
|
||||
return &RecordTypeBuilder{recordType: &schema_pb.RecordType{}}
|
||||
}
|
||||
|
||||
// RecordTypeEnd finishes the building of a RecordValue
|
||||
func (rtb *RecordTypeBuilder) RecordTypeEnd() *schema_pb.RecordType {
|
||||
// be consistent with parquet.node.go `func (g Group) Fields() []Field`
|
||||
sort.Slice(rtb.recordType.Fields, func(i, j int) bool {
|
||||
@@ -31,6 +33,11 @@ func (rtb *RecordTypeBuilder) RecordTypeEnd() *schema_pb.RecordType {
|
||||
return rtb.recordType
|
||||
}
|
||||
|
||||
// NewRecordTypeBuilder creates a new RecordTypeBuilder from an existing RecordType, it should be followed by a series of WithField methods and RecordTypeEnd
|
||||
func NewRecordTypeBuilder(recordType *schema_pb.RecordType) (rtb *RecordTypeBuilder) {
|
||||
return &RecordTypeBuilder{recordType: recordType}
|
||||
}
|
||||
|
||||
func (rtb *RecordTypeBuilder) WithField(name string, scalarType *schema_pb.Type) *RecordTypeBuilder {
|
||||
rtb.recordType.Fields = append(rtb.recordType.Fields, &schema_pb.Field{
|
||||
Name: name,
|
||||
|
||||
@@ -11,13 +11,6 @@ type PartitionConsumerMapping struct {
|
||||
prevMappings []*PartitionSlotToConsumerInstanceList
|
||||
}
|
||||
|
||||
func NewPartitionConsumerMapping(ringSize int32) *PartitionConsumerMapping {
|
||||
newVersion := time.Now().UnixNano()
|
||||
return &PartitionConsumerMapping{
|
||||
currentMapping: NewPartitionSlotToConsumerInstanceList(ringSize, newVersion),
|
||||
}
|
||||
}
|
||||
|
||||
// Balance goal:
|
||||
// 1. max processing power utilization
|
||||
// 2. allow one consumer instance to be down unexpectedly
|
||||
@@ -27,8 +20,7 @@ func (pcm *PartitionConsumerMapping) BalanceToConsumerInstances(partitionSlotToB
|
||||
if len(partitionSlotToBrokerList.PartitionSlots) == 0 || len(consumerInstances) == 0 {
|
||||
return
|
||||
}
|
||||
newVersion := time.Now().UnixNano()
|
||||
newMapping := NewPartitionSlotToConsumerInstanceList(partitionSlotToBrokerList.RingSize, newVersion)
|
||||
newMapping := NewPartitionSlotToConsumerInstanceList(partitionSlotToBrokerList.RingSize, time.Now())
|
||||
var prevMapping *PartitionSlotToConsumerInstanceList
|
||||
if len(pcm.prevMappings) > 0 {
|
||||
prevMapping = pcm.prevMappings[len(pcm.prevMappings)-1]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package sub_coordinator
|
||||
|
||||
import "github.com/seaweedfs/seaweedfs/weed/mq/topic"
|
||||
import "time"
|
||||
|
||||
type PartitionSlotToConsumerInstance struct {
|
||||
RangeStart int32
|
||||
@@ -17,17 +17,9 @@ type PartitionSlotToConsumerInstanceList struct {
|
||||
Version int64
|
||||
}
|
||||
|
||||
func NewPartitionSlotToConsumerInstanceList(ringSize int32, version int64) *PartitionSlotToConsumerInstanceList {
|
||||
func NewPartitionSlotToConsumerInstanceList(ringSize int32, version time.Time) *PartitionSlotToConsumerInstanceList {
|
||||
return &PartitionSlotToConsumerInstanceList{
|
||||
RingSize: ringSize,
|
||||
Version: version,
|
||||
Version: version.UnixNano(),
|
||||
}
|
||||
}
|
||||
|
||||
func ToPartitions(ringSize int32, slots []*PartitionSlotToConsumerInstance) []*topic.Partition {
|
||||
partitions := make([]*topic.Partition, 0, len(slots))
|
||||
for _, slot := range slots {
|
||||
partitions = append(partitions, topic.NewPartition(slot.RangeStart, slot.RangeStop, ringSize, slot.UnixTimeNs))
|
||||
}
|
||||
return partitions
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ func (manager *LocalTopicManager) CollectStats(duration time.Duration) *mq_pb.Br
|
||||
Topic: Topic{Namespace: localTopic.Namespace, Name: localTopic.Name},
|
||||
Partition: localPartition.Partition,
|
||||
}
|
||||
stats.Stats[topicPartition.String()] = &mq_pb.TopicPartitionStats{
|
||||
stats.Stats[topicPartition.TopicPartitionId()] = &mq_pb.TopicPartitionStats{
|
||||
Topic: &mq_pb.Topic{
|
||||
Namespace: string(localTopic.Namespace),
|
||||
Name: localTopic.Name,
|
||||
|
||||
@@ -34,6 +34,7 @@ type LocalPartition struct {
|
||||
}
|
||||
|
||||
var TIME_FORMAT = "2006-01-02-15-04-05"
|
||||
var PartitionGenerationFormat = "v2006-01-02-15-04-05"
|
||||
|
||||
func NewLocalPartition(partition Partition, logFlushFn log_buffer.LogFlushFuncType, readFromDiskFn log_buffer.LogReadFromDiskFuncType) *LocalPartition {
|
||||
lp := &LocalPartition{
|
||||
|
||||
@@ -3,6 +3,7 @@ package topic
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/mq_pb"
|
||||
"time"
|
||||
)
|
||||
|
||||
const PartitionCount = 4096
|
||||
@@ -89,6 +90,19 @@ func (partition Partition) String() string {
|
||||
return fmt.Sprintf("%04d-%04d", partition.RangeStart, partition.RangeStop)
|
||||
}
|
||||
|
||||
func ToString(partition *mq_pb.Partition) string {
|
||||
return fmt.Sprintf("%04d-%04d", partition.RangeStart, partition.RangeStop)
|
||||
func ParseTopicVersion(name string) (t time.Time, err error) {
|
||||
return time.Parse(PartitionGenerationFormat, name)
|
||||
}
|
||||
|
||||
func ParsePartitionBoundary(name string) (start, stop int32) {
|
||||
_, err := fmt.Sscanf(name, "%04d-%04d", &start, &stop)
|
||||
if err != nil {
|
||||
return 0, 0
|
||||
}
|
||||
return start, stop
|
||||
}
|
||||
|
||||
func PartitionDir(t Topic, p Partition) string {
|
||||
partitionGeneration := time.Unix(0, p.UnixTimeNs).UTC().Format(PartitionGenerationFormat)
|
||||
return fmt.Sprintf("%s/%s/%04d-%04d", t.Dir(), partitionGeneration, p.RangeStart, p.RangeStop)
|
||||
}
|
||||
|
||||
+39
-5
@@ -1,8 +1,13 @@
|
||||
package topic
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/seaweedfs/seaweedfs/weed/filer"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/mq_pb"
|
||||
jsonpb "google.golang.org/protobuf/encoding/protojson"
|
||||
)
|
||||
|
||||
type Topic struct {
|
||||
@@ -23,13 +28,42 @@ func FromPbTopic(topic *mq_pb.Topic) Topic {
|
||||
}
|
||||
}
|
||||
|
||||
func (tp Topic) ToPbTopic() *mq_pb.Topic {
|
||||
func (t Topic) ToPbTopic() *mq_pb.Topic {
|
||||
return &mq_pb.Topic{
|
||||
Namespace: tp.Namespace,
|
||||
Name: tp.Name,
|
||||
Namespace: t.Namespace,
|
||||
Name: t.Name,
|
||||
}
|
||||
}
|
||||
|
||||
func (tp Topic) String() string {
|
||||
return fmt.Sprintf("%s.%s", tp.Namespace, tp.Name)
|
||||
func (t Topic) String() string {
|
||||
return fmt.Sprintf("%s.%s", t.Namespace, t.Name)
|
||||
}
|
||||
|
||||
func (t Topic) Dir() string {
|
||||
return fmt.Sprintf("%s/%s/%s", filer.TopicsDir, t.Namespace, t.Name)
|
||||
}
|
||||
|
||||
func (t Topic) ReadConfFile(client filer_pb.SeaweedFilerClient) (*mq_pb.ConfigureTopicResponse, error) {
|
||||
data, err := filer.ReadInsideFiler(client, t.Dir(), filer.TopicConfFile)
|
||||
if errors.Is(err, filer_pb.ErrNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read topic.conf of %v: %v", t, err)
|
||||
}
|
||||
// parse into filer conf object
|
||||
conf := &mq_pb.ConfigureTopicResponse{}
|
||||
if err = jsonpb.Unmarshal(data, conf); err != nil {
|
||||
return nil, fmt.Errorf("unmarshal topic %v conf: %v", t, err)
|
||||
}
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
func (t Topic) WriteConfFile(client filer_pb.SeaweedFilerClient, conf *mq_pb.ConfigureTopicResponse) error {
|
||||
var buf bytes.Buffer
|
||||
filer.ProtoToText(&buf, conf)
|
||||
if err := filer.SaveInsideFiler(client, t.Dir(), filer.TopicConfFile, buf.Bytes()); err != nil {
|
||||
return fmt.Errorf("save topic %v conf: %v", t, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7,6 +7,6 @@ type TopicPartition struct {
|
||||
Partition
|
||||
}
|
||||
|
||||
func (tp *TopicPartition) String() string {
|
||||
func (tp *TopicPartition) TopicPartitionId() string {
|
||||
return fmt.Sprintf("%v.%v-%04d-%04d", tp.Namespace, tp.Topic, tp.RangeStart, tp.RangeStop)
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ func TestCreateNeedleFromRequest(t *testing.T) {
|
||||
{
|
||||
mockClient.needleHandling = func(n *needle.Needle, originalSize int, err error) {
|
||||
assert.Equal(t, nil, err, "upload: %v", err)
|
||||
assert.Equal(t, "", string(n.Mime), "mime detection failed: %v", string(n.Mime))
|
||||
assert.Equal(t, "text/plain; charset=utf-8", string(n.Mime), "mime detection failed: %v", string(n.Mime))
|
||||
assert.Equal(t, true, n.IsCompressed(), "this should be compressed")
|
||||
assert.Equal(t, true, util.IsGzippedContent(n.Data), "this should be gzip")
|
||||
fmt.Printf("needle: %v, originalSize: %d\n", n, originalSize)
|
||||
|
||||
+41
-39
@@ -19,19 +19,15 @@ import (
|
||||
)
|
||||
|
||||
type FilePart struct {
|
||||
Reader io.Reader
|
||||
FileName string
|
||||
FileSize int64
|
||||
MimeType string
|
||||
ModTime int64 //in seconds
|
||||
Replication string
|
||||
Collection string
|
||||
DataCenter string
|
||||
Ttl string
|
||||
DiskType string
|
||||
Server string //this comes from assign result
|
||||
Fid string //this comes from assign result, but customizable
|
||||
Fsync bool
|
||||
Reader io.Reader
|
||||
FileName string
|
||||
FileSize int64
|
||||
MimeType string
|
||||
ModTime int64 //in seconds
|
||||
Pref StoragePreference
|
||||
Server string //this comes from assign result
|
||||
Fid string //this comes from assign result, but customizable
|
||||
Fsync bool
|
||||
}
|
||||
|
||||
type SubmitResult struct {
|
||||
@@ -42,20 +38,29 @@ type SubmitResult struct {
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type StoragePreference struct {
|
||||
Replication string
|
||||
Collection string
|
||||
DataCenter string
|
||||
Ttl string
|
||||
DiskType string
|
||||
MaxMB int
|
||||
}
|
||||
|
||||
type GetMasterFn func(ctx context.Context) pb.ServerAddress
|
||||
|
||||
func SubmitFiles(masterFn GetMasterFn, grpcDialOption grpc.DialOption, files []FilePart, replication string, collection string, dataCenter string, ttl string, diskType string, maxMB int, usePublicUrl bool) ([]SubmitResult, error) {
|
||||
func SubmitFiles(masterFn GetMasterFn, grpcDialOption grpc.DialOption, files []*FilePart, pref StoragePreference, usePublicUrl bool) ([]SubmitResult, error) {
|
||||
results := make([]SubmitResult, len(files))
|
||||
for index, file := range files {
|
||||
results[index].FileName = file.FileName
|
||||
}
|
||||
ar := &VolumeAssignRequest{
|
||||
Count: uint64(len(files)),
|
||||
Replication: replication,
|
||||
Collection: collection,
|
||||
DataCenter: dataCenter,
|
||||
Ttl: ttl,
|
||||
DiskType: diskType,
|
||||
Replication: pref.Replication,
|
||||
Collection: pref.Collection,
|
||||
DataCenter: pref.DataCenter,
|
||||
Ttl: pref.Ttl,
|
||||
DiskType: pref.DiskType,
|
||||
}
|
||||
ret, err := Assign(masterFn, grpcDialOption, ar)
|
||||
if err != nil {
|
||||
@@ -73,12 +78,8 @@ func SubmitFiles(masterFn GetMasterFn, grpcDialOption grpc.DialOption, files []F
|
||||
if usePublicUrl {
|
||||
file.Server = ret.PublicUrl
|
||||
}
|
||||
file.Replication = replication
|
||||
file.Collection = collection
|
||||
file.DataCenter = dataCenter
|
||||
file.Ttl = ttl
|
||||
file.DiskType = diskType
|
||||
results[index].Size, err = file.Upload(maxMB, masterFn, usePublicUrl, ret.Auth, grpcDialOption)
|
||||
file.Pref = pref
|
||||
results[index].Size, err = file.Upload(pref.MaxMB, masterFn, usePublicUrl, ret.Auth, grpcDialOption)
|
||||
if err != nil {
|
||||
results[index].Error = err.Error()
|
||||
}
|
||||
@@ -88,8 +89,8 @@ func SubmitFiles(masterFn GetMasterFn, grpcDialOption grpc.DialOption, files []F
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func NewFileParts(fullPathFilenames []string) (ret []FilePart, err error) {
|
||||
ret = make([]FilePart, len(fullPathFilenames))
|
||||
func NewFileParts(fullPathFilenames []string) (ret []*FilePart, err error) {
|
||||
ret = make([]*FilePart, len(fullPathFilenames))
|
||||
for index, file := range fullPathFilenames {
|
||||
if ret[index], err = newFilePart(file); err != nil {
|
||||
return
|
||||
@@ -97,7 +98,8 @@ func NewFileParts(fullPathFilenames []string) (ret []FilePart, err error) {
|
||||
}
|
||||
return
|
||||
}
|
||||
func newFilePart(fullPathFilename string) (ret FilePart, err error) {
|
||||
func newFilePart(fullPathFilename string) (ret *FilePart, err error) {
|
||||
ret = &FilePart{}
|
||||
fh, openErr := os.Open(fullPathFilename)
|
||||
if openErr != nil {
|
||||
glog.V(0).Info("Failed to open file: ", fullPathFilename)
|
||||
@@ -121,7 +123,7 @@ func newFilePart(fullPathFilename string) (ret FilePart, err error) {
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (fi FilePart) Upload(maxMB int, masterFn GetMasterFn, usePublicUrl bool, jwt security.EncodedJwt, grpcDialOption grpc.DialOption) (retSize uint32, err error) {
|
||||
func (fi *FilePart) Upload(maxMB int, masterFn GetMasterFn, usePublicUrl bool, jwt security.EncodedJwt, grpcDialOption grpc.DialOption) (retSize uint32, err error) {
|
||||
fileUrl := "http://" + fi.Server + "/" + fi.Fid
|
||||
if fi.ModTime != 0 {
|
||||
fileUrl += "?ts=" + strconv.Itoa(int(fi.ModTime))
|
||||
@@ -145,13 +147,13 @@ func (fi FilePart) Upload(maxMB int, masterFn GetMasterFn, usePublicUrl bool, jw
|
||||
|
||||
var ret *AssignResult
|
||||
var id string
|
||||
if fi.DataCenter != "" {
|
||||
if fi.Pref.DataCenter != "" {
|
||||
ar := &VolumeAssignRequest{
|
||||
Count: uint64(chunks),
|
||||
Replication: fi.Replication,
|
||||
Collection: fi.Collection,
|
||||
Ttl: fi.Ttl,
|
||||
DiskType: fi.DiskType,
|
||||
Replication: fi.Pref.Replication,
|
||||
Collection: fi.Pref.Collection,
|
||||
Ttl: fi.Pref.Ttl,
|
||||
DiskType: fi.Pref.DiskType,
|
||||
}
|
||||
ret, err = Assign(masterFn, grpcDialOption, ar)
|
||||
if err != nil {
|
||||
@@ -159,13 +161,13 @@ func (fi FilePart) Upload(maxMB int, masterFn GetMasterFn, usePublicUrl bool, jw
|
||||
}
|
||||
}
|
||||
for i := int64(0); i < chunks; i++ {
|
||||
if fi.DataCenter == "" {
|
||||
if fi.Pref.DataCenter == "" {
|
||||
ar := &VolumeAssignRequest{
|
||||
Count: 1,
|
||||
Replication: fi.Replication,
|
||||
Collection: fi.Collection,
|
||||
Ttl: fi.Ttl,
|
||||
DiskType: fi.DiskType,
|
||||
Replication: fi.Pref.Replication,
|
||||
Collection: fi.Pref.Collection,
|
||||
Ttl: fi.Pref.Ttl,
|
||||
DiskType: fi.Pref.DiskType,
|
||||
}
|
||||
ret, err = Assign(masterFn, grpcDialOption, ar)
|
||||
if err != nil {
|
||||
|
||||
@@ -209,6 +209,7 @@ func (s3a *S3ApiServer) completeMultipartUpload(input *s3.CompleteMultipartUploa
|
||||
ModifiedTsNs: chunk.ModifiedTsNs,
|
||||
CipherKey: chunk.CipherKey,
|
||||
ETag: chunk.ETag,
|
||||
IsCompressed: chunk.IsCompressed,
|
||||
}
|
||||
finalParts = append(finalParts, p)
|
||||
offset += int64(chunk.Size)
|
||||
|
||||
@@ -2,11 +2,12 @@ package weed_server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/seaweedfs/seaweedfs/weed/stats"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/stats"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/filer"
|
||||
@@ -62,8 +63,19 @@ func (fs *FilerServer) SubscribeMetadata(req *filer_pb.SubscribeMetadataRequest,
|
||||
return nil
|
||||
}
|
||||
|
||||
glog.V(4).Infof("processed to %v: %v", clientName, processedTsNs)
|
||||
if processedTsNs != 0 {
|
||||
lastReadTime = log_buffer.NewMessagePosition(processedTsNs, -2)
|
||||
} else {
|
||||
nextDayTs := util.GetNextDayTsNano(lastReadTime.UnixNano())
|
||||
position := log_buffer.NewMessagePosition(nextDayTs, -2)
|
||||
found, err := fs.filer.HasPersistedLogFiles(position)
|
||||
if err != nil {
|
||||
return fmt.Errorf("checking persisted log files: %v", err)
|
||||
}
|
||||
if found {
|
||||
lastReadTime = position
|
||||
}
|
||||
}
|
||||
|
||||
glog.V(4).Infof("read in memory %v aggregated subscribe %s from %+v", clientName, req.PathPrefix, lastReadTime)
|
||||
@@ -72,10 +84,7 @@ func (fs *FilerServer) SubscribeMetadata(req *filer_pb.SubscribeMetadataRequest,
|
||||
fs.filer.MetaAggregator.ListenersLock.Lock()
|
||||
fs.filer.MetaAggregator.ListenersCond.Wait()
|
||||
fs.filer.MetaAggregator.ListenersLock.Unlock()
|
||||
if !fs.hasClient(req.ClientId, req.ClientEpoch) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
return fs.hasClient(req.ClientId, req.ClientEpoch)
|
||||
}, eachLogEntryFn)
|
||||
if readInMemoryLogErr != nil {
|
||||
if readInMemoryLogErr == log_buffer.ResumeFromDiskError {
|
||||
|
||||
@@ -100,14 +100,14 @@ func (fs *FilerServer) uploadReaderToChunks(reader io.Reader, startOffset int64,
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func(offset int64) {
|
||||
go func(offset int64, buf *bytes.Buffer) {
|
||||
defer func() {
|
||||
bufPool.Put(bytesBuffer)
|
||||
bufPool.Put(buf)
|
||||
<-bytesBufferLimitChan
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
chunks, toChunkErr := fs.dataToChunk(fileName, contentType, bytesBuffer.Bytes(), offset, so)
|
||||
chunks, toChunkErr := fs.dataToChunk(fileName, contentType, buf.Bytes(), offset, so)
|
||||
if toChunkErr != nil {
|
||||
uploadErrLock.Lock()
|
||||
if uploadErr == nil {
|
||||
@@ -124,7 +124,7 @@ func (fs *FilerServer) uploadReaderToChunks(reader io.Reader, startOffset int64,
|
||||
}
|
||||
fileChunksLock.Unlock()
|
||||
}
|
||||
}(chunkOffset)
|
||||
}(chunkOffset, bytesBuffer)
|
||||
|
||||
// reset variables for the next chunk
|
||||
chunkOffset = chunkOffset + dataSize
|
||||
@@ -138,6 +138,10 @@ func (fs *FilerServer) uploadReaderToChunks(reader io.Reader, startOffset int64,
|
||||
wg.Wait()
|
||||
|
||||
if uploadErr != nil {
|
||||
glog.V(0).Infof("upload file %s error: %v", fileName, uploadErr)
|
||||
for _, chunk := range fileChunks {
|
||||
glog.V(4).Infof("purging failed uploaded %s chunk %s [%d,%d)", fileName, chunk.FileId, chunk.Offset, chunk.Offset+int64(chunk.Size))
|
||||
}
|
||||
fs.filer.DeleteUncommittedChunks(fileChunks)
|
||||
return nil, md5Hash, 0, uploadErr, nil
|
||||
}
|
||||
|
||||
@@ -4,12 +4,13 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
"github.com/seaweedfs/seaweedfs/weed/cluster"
|
||||
"net"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/seaweedfs/seaweedfs/weed/cluster"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/stats"
|
||||
"github.com/seaweedfs/seaweedfs/weed/storage/backend"
|
||||
@@ -89,7 +90,7 @@ func (ms *MasterServer) SendHeartbeat(stream master_pb.Seaweed_SendHeartbeatServ
|
||||
glog.V(0).Infof("unregister disconnected volume server %s:%d", dn.Ip, dn.Port)
|
||||
ms.UnRegisterUuids(dn.Ip, dn.Port)
|
||||
|
||||
if len(message.DeletedVids) > 0 || len(message.DeletedEcVids) > 0 {
|
||||
if ms.Topo.IsLeader() && (len(message.DeletedVids) > 0 || len(message.DeletedEcVids) > 0) {
|
||||
ms.broadcastToClients(&master_pb.KeepConnectedResponse{VolumeLocation: message})
|
||||
}
|
||||
}
|
||||
@@ -338,8 +339,14 @@ func (ms *MasterServer) KeepConnected(stream master_pb.Seaweed_KeepConnectedServ
|
||||
|
||||
func (ms *MasterServer) broadcastToClients(message *master_pb.KeepConnectedResponse) {
|
||||
ms.clientChansLock.RLock()
|
||||
for _, ch := range ms.clientChans {
|
||||
ch <- message
|
||||
for client, ch := range ms.clientChans {
|
||||
select {
|
||||
case ch <- message:
|
||||
glog.V(4).Infof("send message to %s", client)
|
||||
default:
|
||||
stats.MasterBroadcastToFullErrorCounter.Inc()
|
||||
glog.Errorf("broadcastToClients %s message full", client)
|
||||
}
|
||||
}
|
||||
ms.clientChansLock.RUnlock()
|
||||
}
|
||||
|
||||
@@ -33,14 +33,14 @@
|
||||
{{ with .RaftServer }}
|
||||
<tr>
|
||||
<th>Leader</th>
|
||||
<td><a href="http://{{ .Leader }}">{{ .Leader }}</a></td>
|
||||
<td><a href="{{ url .Leader }}">{{ .Leader }}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Other Masters</th>
|
||||
<td class="col-sm-5">
|
||||
<ul class="list-unstyled">
|
||||
{{ range $k, $p := .Peers }}
|
||||
<li><a href="http://{{ $p.Name }}/ui/index.html">{{ $p.Name }}</a></li>
|
||||
<li><a href="{{ url $p.Name }}/ui/index.html">{{ $p.Name }}</a></li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
</td>
|
||||
@@ -88,9 +88,9 @@
|
||||
<tr>
|
||||
<td><code>{{ $dc.Id }}</code></td>
|
||||
<td>{{ $rack.Id }}</td>
|
||||
<td><a href="http://{{ $dn.Url }}/ui/index.html">{{ $dn.Url }}</a>
|
||||
<td><a href="{{ url $dn.Url }}/ui/index.html">{{ $dn.Url }}</a>
|
||||
{{ if ne $dn.PublicUrl $dn.Url }}
|
||||
/ <a href="http://{{ $dn.PublicUrl }}/ui/index.html">{{ $dn.PublicUrl }}</a>
|
||||
/ <a href="{{ url $dn.PublicUrl }}/ui/index.html">{{ $dn.PublicUrl }}</a>
|
||||
{{ end }}
|
||||
</td>
|
||||
<td>{{ $dn.Volumes }}</td>
|
||||
|
||||
@@ -3,6 +3,7 @@ package master_ui
|
||||
import (
|
||||
_ "embed"
|
||||
"html/template"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//go:embed master.html
|
||||
@@ -11,5 +12,17 @@ var masterHtml string
|
||||
//go:embed masterNewRaft.html
|
||||
var masterNewRaftHtml string
|
||||
|
||||
var StatusTpl = template.Must(template.New("status").Parse(masterHtml))
|
||||
var templateFunctions = template.FuncMap{
|
||||
"url": func(input string) string {
|
||||
|
||||
if !strings.HasPrefix(input, "http://") && !strings.HasPrefix(input, "https://") {
|
||||
return "http://" + input
|
||||
}
|
||||
|
||||
return input
|
||||
},
|
||||
}
|
||||
|
||||
var StatusTpl = template.Must(template.New("status").Funcs(templateFunctions).Parse(masterHtml))
|
||||
|
||||
var StatusNewRaftTpl = template.Must(template.New("status").Parse(masterNewRaftHtml))
|
||||
|
||||
@@ -4,12 +4,6 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/storage/erasure_coding"
|
||||
"github.com/seaweedfs/seaweedfs/weed/storage/needle"
|
||||
"github.com/seaweedfs/seaweedfs/weed/storage/types"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -103,7 +97,6 @@ func (c *commandEcBalance) HasTag(CommandTag) bool {
|
||||
}
|
||||
|
||||
func (c *commandEcBalance) Do(args []string, commandEnv *CommandEnv, writer io.Writer) (err error) {
|
||||
|
||||
balanceCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
|
||||
collection := balanceCommand.String("collection", "EACH_COLLECTION", "collection name, or \"EACH_COLLECTION\" for each collection")
|
||||
dc := balanceCommand.String("dataCenter", "", "only apply the balancing for this dataCenter")
|
||||
@@ -117,431 +110,16 @@ func (c *commandEcBalance) Do(args []string, commandEnv *CommandEnv, writer io.W
|
||||
return
|
||||
}
|
||||
|
||||
// collect all ec nodes
|
||||
allEcNodes, totalFreeEcSlots, err := collectEcNodes(commandEnv, *dc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if totalFreeEcSlots < 1 {
|
||||
return fmt.Errorf("no free ec shard slots. only %d left", totalFreeEcSlots)
|
||||
}
|
||||
|
||||
racks := collectRacks(allEcNodes)
|
||||
|
||||
var collections []string
|
||||
if *collection == "EACH_COLLECTION" {
|
||||
collections, err := ListCollectionNames(commandEnv, false, true)
|
||||
collections, err = ListCollectionNames(commandEnv, false, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("balanceEcVolumes collections %+v\n", len(collections))
|
||||
for _, c := range collections {
|
||||
fmt.Printf("balanceEcVolumes collection %+v\n", c)
|
||||
if err = balanceEcVolumes(commandEnv, c, allEcNodes, racks, *applyBalancing); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err = balanceEcVolumes(commandEnv, *collection, allEcNodes, racks, *applyBalancing); err != nil {
|
||||
return err
|
||||
}
|
||||
collections = append(collections, *collection)
|
||||
}
|
||||
fmt.Printf("balanceEcVolumes collections %+v\n", len(collections))
|
||||
|
||||
if err := balanceEcRacks(commandEnv, racks, *applyBalancing); err != nil {
|
||||
return fmt.Errorf("balance ec racks: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func collectRacks(allEcNodes []*EcNode) map[RackId]*EcRack {
|
||||
// collect racks info
|
||||
racks := make(map[RackId]*EcRack)
|
||||
for _, ecNode := range allEcNodes {
|
||||
if racks[ecNode.rack] == nil {
|
||||
racks[ecNode.rack] = &EcRack{
|
||||
ecNodes: make(map[EcNodeId]*EcNode),
|
||||
}
|
||||
}
|
||||
racks[ecNode.rack].ecNodes[EcNodeId(ecNode.info.Id)] = ecNode
|
||||
racks[ecNode.rack].freeEcSlot += ecNode.freeEcSlot
|
||||
}
|
||||
return racks
|
||||
}
|
||||
|
||||
func balanceEcVolumes(commandEnv *CommandEnv, collection string, allEcNodes []*EcNode, racks map[RackId]*EcRack, applyBalancing bool) error {
|
||||
|
||||
fmt.Printf("balanceEcVolumes %s\n", collection)
|
||||
|
||||
if err := deleteDuplicatedEcShards(commandEnv, allEcNodes, collection, applyBalancing); err != nil {
|
||||
return fmt.Errorf("delete duplicated collection %s ec shards: %v", collection, err)
|
||||
}
|
||||
|
||||
if err := balanceEcShardsAcrossRacks(commandEnv, allEcNodes, racks, collection, applyBalancing); err != nil {
|
||||
return fmt.Errorf("balance across racks collection %s ec shards: %v", collection, err)
|
||||
}
|
||||
|
||||
if err := balanceEcShardsWithinRacks(commandEnv, allEcNodes, racks, collection, applyBalancing); err != nil {
|
||||
return fmt.Errorf("balance within racks collection %s ec shards: %v", collection, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteDuplicatedEcShards(commandEnv *CommandEnv, allEcNodes []*EcNode, collection string, applyBalancing bool) error {
|
||||
// vid => []ecNode
|
||||
vidLocations := collectVolumeIdToEcNodes(allEcNodes, collection)
|
||||
// deduplicate ec shards
|
||||
for vid, locations := range vidLocations {
|
||||
if err := doDeduplicateEcShards(commandEnv, collection, vid, locations, applyBalancing); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func doDeduplicateEcShards(commandEnv *CommandEnv, collection string, vid needle.VolumeId, locations []*EcNode, applyBalancing bool) error {
|
||||
|
||||
// check whether this volume has ecNodes that are over average
|
||||
shardToLocations := make([][]*EcNode, erasure_coding.TotalShardsCount)
|
||||
for _, ecNode := range locations {
|
||||
shardBits := findEcVolumeShards(ecNode, vid)
|
||||
for _, shardId := range shardBits.ShardIds() {
|
||||
shardToLocations[shardId] = append(shardToLocations[shardId], ecNode)
|
||||
}
|
||||
}
|
||||
for shardId, ecNodes := range shardToLocations {
|
||||
if len(ecNodes) <= 1 {
|
||||
continue
|
||||
}
|
||||
sortEcNodesByFreeslotsAscending(ecNodes)
|
||||
fmt.Printf("ec shard %d.%d has %d copies, keeping %v\n", vid, shardId, len(ecNodes), ecNodes[0].info.Id)
|
||||
if !applyBalancing {
|
||||
continue
|
||||
}
|
||||
|
||||
duplicatedShardIds := []uint32{uint32(shardId)}
|
||||
for _, ecNode := range ecNodes[1:] {
|
||||
if err := unmountEcShards(commandEnv.option.GrpcDialOption, vid, pb.NewServerAddressFromDataNode(ecNode.info), duplicatedShardIds); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := sourceServerDeleteEcShards(commandEnv.option.GrpcDialOption, collection, vid, pb.NewServerAddressFromDataNode(ecNode.info), duplicatedShardIds); err != nil {
|
||||
return err
|
||||
}
|
||||
ecNode.deleteEcVolumeShards(vid, duplicatedShardIds)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func balanceEcShardsAcrossRacks(commandEnv *CommandEnv, allEcNodes []*EcNode, racks map[RackId]*EcRack, collection string, applyBalancing bool) error {
|
||||
// collect vid => []ecNode, since previous steps can change the locations
|
||||
vidLocations := collectVolumeIdToEcNodes(allEcNodes, collection)
|
||||
// spread the ec shards evenly
|
||||
for vid, locations := range vidLocations {
|
||||
if err := doBalanceEcShardsAcrossRacks(commandEnv, collection, vid, locations, racks, applyBalancing); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func doBalanceEcShardsAcrossRacks(commandEnv *CommandEnv, collection string, vid needle.VolumeId, locations []*EcNode, racks map[RackId]*EcRack, applyBalancing bool) error {
|
||||
|
||||
// calculate average number of shards an ec rack should have for one volume
|
||||
averageShardsPerEcRack := ceilDivide(erasure_coding.TotalShardsCount, len(racks))
|
||||
|
||||
// see the volume's shards are in how many racks, and how many in each rack
|
||||
rackToShardCount := groupByCount(locations, func(ecNode *EcNode) (id string, count int) {
|
||||
shardBits := findEcVolumeShards(ecNode, vid)
|
||||
return string(ecNode.rack), shardBits.ShardIdCount()
|
||||
})
|
||||
rackEcNodesWithVid := groupBy(locations, func(ecNode *EcNode) string {
|
||||
return string(ecNode.rack)
|
||||
})
|
||||
|
||||
// ecShardsToMove = select overflown ec shards from racks with ec shard counts > averageShardsPerEcRack
|
||||
ecShardsToMove := make(map[erasure_coding.ShardId]*EcNode)
|
||||
for rackId, count := range rackToShardCount {
|
||||
if count > averageShardsPerEcRack {
|
||||
possibleEcNodes := rackEcNodesWithVid[rackId]
|
||||
for shardId, ecNode := range pickNEcShardsToMoveFrom(possibleEcNodes, vid, count-averageShardsPerEcRack) {
|
||||
ecShardsToMove[shardId] = ecNode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for shardId, ecNode := range ecShardsToMove {
|
||||
rackId := pickOneRack(racks, rackToShardCount, averageShardsPerEcRack)
|
||||
if rackId == "" {
|
||||
fmt.Printf("ec shard %d.%d at %s can not find a destination rack\n", vid, shardId, ecNode.info.Id)
|
||||
continue
|
||||
}
|
||||
var possibleDestinationEcNodes []*EcNode
|
||||
for _, n := range racks[rackId].ecNodes {
|
||||
possibleDestinationEcNodes = append(possibleDestinationEcNodes, n)
|
||||
}
|
||||
err := pickOneEcNodeAndMoveOneShard(commandEnv, averageShardsPerEcRack, ecNode, collection, vid, shardId, possibleDestinationEcNodes, applyBalancing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rackToShardCount[string(rackId)] += 1
|
||||
rackToShardCount[string(ecNode.rack)] -= 1
|
||||
racks[rackId].freeEcSlot -= 1
|
||||
racks[ecNode.rack].freeEcSlot += 1
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func pickOneRack(rackToEcNodes map[RackId]*EcRack, rackToShardCount map[string]int, averageShardsPerEcRack int) RackId {
|
||||
|
||||
// TODO later may need to add some randomness
|
||||
|
||||
for rackId, rack := range rackToEcNodes {
|
||||
if rackToShardCount[string(rackId)] >= averageShardsPerEcRack {
|
||||
continue
|
||||
}
|
||||
|
||||
if rack.freeEcSlot <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
return rackId
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func balanceEcShardsWithinRacks(commandEnv *CommandEnv, allEcNodes []*EcNode, racks map[RackId]*EcRack, collection string, applyBalancing bool) error {
|
||||
// collect vid => []ecNode, since previous steps can change the locations
|
||||
vidLocations := collectVolumeIdToEcNodes(allEcNodes, collection)
|
||||
|
||||
// spread the ec shards evenly
|
||||
for vid, locations := range vidLocations {
|
||||
|
||||
// see the volume's shards are in how many racks, and how many in each rack
|
||||
rackToShardCount := groupByCount(locations, func(ecNode *EcNode) (id string, count int) {
|
||||
shardBits := findEcVolumeShards(ecNode, vid)
|
||||
return string(ecNode.rack), shardBits.ShardIdCount()
|
||||
})
|
||||
rackEcNodesWithVid := groupBy(locations, func(ecNode *EcNode) string {
|
||||
return string(ecNode.rack)
|
||||
})
|
||||
|
||||
for rackId, _ := range rackToShardCount {
|
||||
|
||||
var possibleDestinationEcNodes []*EcNode
|
||||
for _, n := range racks[RackId(rackId)].ecNodes {
|
||||
if _, found := n.info.DiskInfos[string(types.HardDriveType)]; found {
|
||||
possibleDestinationEcNodes = append(possibleDestinationEcNodes, n)
|
||||
}
|
||||
}
|
||||
sourceEcNodes := rackEcNodesWithVid[rackId]
|
||||
averageShardsPerEcNode := ceilDivide(rackToShardCount[rackId], len(possibleDestinationEcNodes))
|
||||
if err := doBalanceEcShardsWithinOneRack(commandEnv, averageShardsPerEcNode, collection, vid, sourceEcNodes, possibleDestinationEcNodes, applyBalancing); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func doBalanceEcShardsWithinOneRack(commandEnv *CommandEnv, averageShardsPerEcNode int, collection string, vid needle.VolumeId, existingLocations, possibleDestinationEcNodes []*EcNode, applyBalancing bool) error {
|
||||
|
||||
for _, ecNode := range existingLocations {
|
||||
|
||||
shardBits := findEcVolumeShards(ecNode, vid)
|
||||
overLimitCount := shardBits.ShardIdCount() - averageShardsPerEcNode
|
||||
|
||||
for _, shardId := range shardBits.ShardIds() {
|
||||
|
||||
if overLimitCount <= 0 {
|
||||
break
|
||||
}
|
||||
|
||||
fmt.Printf("%s has %d overlimit, moving ec shard %d.%d\n", ecNode.info.Id, overLimitCount, vid, shardId)
|
||||
|
||||
err := pickOneEcNodeAndMoveOneShard(commandEnv, averageShardsPerEcNode, ecNode, collection, vid, shardId, possibleDestinationEcNodes, applyBalancing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
overLimitCount--
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func balanceEcRacks(commandEnv *CommandEnv, racks map[RackId]*EcRack, applyBalancing bool) error {
|
||||
|
||||
// balance one rack for all ec shards
|
||||
for _, ecRack := range racks {
|
||||
if err := doBalanceEcRack(commandEnv, ecRack, applyBalancing); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func doBalanceEcRack(commandEnv *CommandEnv, ecRack *EcRack, applyBalancing bool) error {
|
||||
|
||||
if len(ecRack.ecNodes) <= 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var rackEcNodes []*EcNode
|
||||
for _, node := range ecRack.ecNodes {
|
||||
rackEcNodes = append(rackEcNodes, node)
|
||||
}
|
||||
|
||||
ecNodeIdToShardCount := groupByCount(rackEcNodes, func(ecNode *EcNode) (id string, count int) {
|
||||
diskInfo, found := ecNode.info.DiskInfos[string(types.HardDriveType)]
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
for _, ecShardInfo := range diskInfo.EcShardInfos {
|
||||
count += erasure_coding.ShardBits(ecShardInfo.EcIndexBits).ShardIdCount()
|
||||
}
|
||||
return ecNode.info.Id, count
|
||||
})
|
||||
|
||||
var totalShardCount int
|
||||
for _, count := range ecNodeIdToShardCount {
|
||||
totalShardCount += count
|
||||
}
|
||||
|
||||
averageShardCount := ceilDivide(totalShardCount, len(rackEcNodes))
|
||||
|
||||
hasMove := true
|
||||
for hasMove {
|
||||
hasMove = false
|
||||
slices.SortFunc(rackEcNodes, func(a, b *EcNode) int {
|
||||
return b.freeEcSlot - a.freeEcSlot
|
||||
})
|
||||
emptyNode, fullNode := rackEcNodes[0], rackEcNodes[len(rackEcNodes)-1]
|
||||
emptyNodeShardCount, fullNodeShardCount := ecNodeIdToShardCount[emptyNode.info.Id], ecNodeIdToShardCount[fullNode.info.Id]
|
||||
if fullNodeShardCount > averageShardCount && emptyNodeShardCount+1 <= averageShardCount {
|
||||
|
||||
emptyNodeIds := make(map[uint32]bool)
|
||||
if emptyDiskInfo, found := emptyNode.info.DiskInfos[string(types.HardDriveType)]; found {
|
||||
for _, shards := range emptyDiskInfo.EcShardInfos {
|
||||
emptyNodeIds[shards.Id] = true
|
||||
}
|
||||
}
|
||||
if fullDiskInfo, found := fullNode.info.DiskInfos[string(types.HardDriveType)]; found {
|
||||
for _, shards := range fullDiskInfo.EcShardInfos {
|
||||
if _, found := emptyNodeIds[shards.Id]; !found {
|
||||
for _, shardId := range erasure_coding.ShardBits(shards.EcIndexBits).ShardIds() {
|
||||
|
||||
fmt.Printf("%s moves ec shards %d.%d to %s\n", fullNode.info.Id, shards.Id, shardId, emptyNode.info.Id)
|
||||
|
||||
err := moveMountedShardToEcNode(commandEnv, fullNode, shards.Collection, needle.VolumeId(shards.Id), shardId, emptyNode, applyBalancing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ecNodeIdToShardCount[emptyNode.info.Id]++
|
||||
ecNodeIdToShardCount[fullNode.info.Id]--
|
||||
hasMove = true
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func pickOneEcNodeAndMoveOneShard(commandEnv *CommandEnv, averageShardsPerEcNode int, existingLocation *EcNode, collection string, vid needle.VolumeId, shardId erasure_coding.ShardId, possibleDestinationEcNodes []*EcNode, applyBalancing bool) error {
|
||||
|
||||
sortEcNodesByFreeslotsDescending(possibleDestinationEcNodes)
|
||||
skipReason := ""
|
||||
for _, destEcNode := range possibleDestinationEcNodes {
|
||||
|
||||
if destEcNode.info.Id == existingLocation.info.Id {
|
||||
continue
|
||||
}
|
||||
|
||||
if destEcNode.freeEcSlot <= 0 {
|
||||
skipReason += fmt.Sprintf(" Skipping %s because it has no free slots\n", destEcNode.info.Id)
|
||||
continue
|
||||
}
|
||||
if findEcVolumeShards(destEcNode, vid).ShardIdCount() >= averageShardsPerEcNode {
|
||||
skipReason += fmt.Sprintf(" Skipping %s because it %d >= avernageShards (%d)\n",
|
||||
destEcNode.info.Id, findEcVolumeShards(destEcNode, vid).ShardIdCount(), averageShardsPerEcNode)
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf("%s moves ec shard %d.%d to %s\n", existingLocation.info.Id, vid, shardId, destEcNode.info.Id)
|
||||
|
||||
err := moveMountedShardToEcNode(commandEnv, existingLocation, collection, vid, shardId, destEcNode, applyBalancing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
fmt.Printf("WARNING: Could not find suitable taget node for %d.%d:\n%s", vid, shardId, skipReason)
|
||||
return nil
|
||||
}
|
||||
|
||||
func pickNEcShardsToMoveFrom(ecNodes []*EcNode, vid needle.VolumeId, n int) map[erasure_coding.ShardId]*EcNode {
|
||||
picked := make(map[erasure_coding.ShardId]*EcNode)
|
||||
var candidateEcNodes []*CandidateEcNode
|
||||
for _, ecNode := range ecNodes {
|
||||
shardBits := findEcVolumeShards(ecNode, vid)
|
||||
if shardBits.ShardIdCount() > 0 {
|
||||
candidateEcNodes = append(candidateEcNodes, &CandidateEcNode{
|
||||
ecNode: ecNode,
|
||||
shardCount: shardBits.ShardIdCount(),
|
||||
})
|
||||
}
|
||||
}
|
||||
slices.SortFunc(candidateEcNodes, func(a, b *CandidateEcNode) int {
|
||||
return b.shardCount - a.shardCount
|
||||
})
|
||||
for i := 0; i < n; i++ {
|
||||
selectedEcNodeIndex := -1
|
||||
for i, candidateEcNode := range candidateEcNodes {
|
||||
shardBits := findEcVolumeShards(candidateEcNode.ecNode, vid)
|
||||
if shardBits > 0 {
|
||||
selectedEcNodeIndex = i
|
||||
for _, shardId := range shardBits.ShardIds() {
|
||||
candidateEcNode.shardCount--
|
||||
picked[shardId] = candidateEcNode.ecNode
|
||||
candidateEcNode.ecNode.deleteEcVolumeShards(vid, []uint32{uint32(shardId)})
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if selectedEcNodeIndex >= 0 {
|
||||
ensureSortedEcNodes(candidateEcNodes, selectedEcNodeIndex, func(i, j int) bool {
|
||||
return candidateEcNodes[i].shardCount > candidateEcNodes[j].shardCount
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
return picked
|
||||
}
|
||||
|
||||
func collectVolumeIdToEcNodes(allEcNodes []*EcNode, collection string) map[needle.VolumeId][]*EcNode {
|
||||
vidLocations := make(map[needle.VolumeId][]*EcNode)
|
||||
for _, ecNode := range allEcNodes {
|
||||
diskInfo, found := ecNode.info.DiskInfos[string(types.HardDriveType)]
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
for _, shardInfo := range diskInfo.EcShardInfos {
|
||||
// ignore if not in current collection
|
||||
if shardInfo.Collection == collection {
|
||||
vidLocations[needle.VolumeId(shardInfo.Id)] = append(vidLocations[needle.VolumeId(shardInfo.Id)], ecNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
return vidLocations
|
||||
return EcBalance(commandEnv, collections, *dc, *applyBalancing)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package shell
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||
"github.com/seaweedfs/seaweedfs/weed/operation"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb"
|
||||
@@ -13,7 +15,6 @@ import (
|
||||
"github.com/seaweedfs/seaweedfs/weed/storage/types"
|
||||
"golang.org/x/exp/slices"
|
||||
"google.golang.org/grpc"
|
||||
"math"
|
||||
)
|
||||
|
||||
func moveMountedShardToEcNode(commandEnv *CommandEnv, existingLocation *EcNode, collection string, vid needle.VolumeId, shardId erasure_coding.ShardId, destinationEcNode *EcNode, applyBalancing bool) (err error) {
|
||||
@@ -377,3 +378,424 @@ func groupBy(data []*EcNode, identifierFn func(*EcNode) (id string)) map[string]
|
||||
}
|
||||
return groupMap
|
||||
}
|
||||
|
||||
func collectRacks(allEcNodes []*EcNode) map[RackId]*EcRack {
|
||||
// collect racks info
|
||||
racks := make(map[RackId]*EcRack)
|
||||
for _, ecNode := range allEcNodes {
|
||||
if racks[ecNode.rack] == nil {
|
||||
racks[ecNode.rack] = &EcRack{
|
||||
ecNodes: make(map[EcNodeId]*EcNode),
|
||||
}
|
||||
}
|
||||
racks[ecNode.rack].ecNodes[EcNodeId(ecNode.info.Id)] = ecNode
|
||||
racks[ecNode.rack].freeEcSlot += ecNode.freeEcSlot
|
||||
}
|
||||
return racks
|
||||
}
|
||||
|
||||
func balanceEcVolumes(commandEnv *CommandEnv, collection string, allEcNodes []*EcNode, racks map[RackId]*EcRack, applyBalancing bool) error {
|
||||
|
||||
fmt.Printf("balanceEcVolumes %s\n", collection)
|
||||
|
||||
if err := deleteDuplicatedEcShards(commandEnv, allEcNodes, collection, applyBalancing); err != nil {
|
||||
return fmt.Errorf("delete duplicated collection %s ec shards: %v", collection, err)
|
||||
}
|
||||
|
||||
if err := balanceEcShardsAcrossRacks(commandEnv, allEcNodes, racks, collection, applyBalancing); err != nil {
|
||||
return fmt.Errorf("balance across racks collection %s ec shards: %v", collection, err)
|
||||
}
|
||||
|
||||
if err := balanceEcShardsWithinRacks(commandEnv, allEcNodes, racks, collection, applyBalancing); err != nil {
|
||||
return fmt.Errorf("balance within racks collection %s ec shards: %v", collection, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteDuplicatedEcShards(commandEnv *CommandEnv, allEcNodes []*EcNode, collection string, applyBalancing bool) error {
|
||||
// vid => []ecNode
|
||||
vidLocations := collectVolumeIdToEcNodes(allEcNodes, collection)
|
||||
// deduplicate ec shards
|
||||
for vid, locations := range vidLocations {
|
||||
if err := doDeduplicateEcShards(commandEnv, collection, vid, locations, applyBalancing); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func doDeduplicateEcShards(commandEnv *CommandEnv, collection string, vid needle.VolumeId, locations []*EcNode, applyBalancing bool) error {
|
||||
|
||||
// check whether this volume has ecNodes that are over average
|
||||
shardToLocations := make([][]*EcNode, erasure_coding.TotalShardsCount)
|
||||
for _, ecNode := range locations {
|
||||
shardBits := findEcVolumeShards(ecNode, vid)
|
||||
for _, shardId := range shardBits.ShardIds() {
|
||||
shardToLocations[shardId] = append(shardToLocations[shardId], ecNode)
|
||||
}
|
||||
}
|
||||
for shardId, ecNodes := range shardToLocations {
|
||||
if len(ecNodes) <= 1 {
|
||||
continue
|
||||
}
|
||||
sortEcNodesByFreeslotsAscending(ecNodes)
|
||||
fmt.Printf("ec shard %d.%d has %d copies, keeping %v\n", vid, shardId, len(ecNodes), ecNodes[0].info.Id)
|
||||
if !applyBalancing {
|
||||
continue
|
||||
}
|
||||
|
||||
duplicatedShardIds := []uint32{uint32(shardId)}
|
||||
for _, ecNode := range ecNodes[1:] {
|
||||
if err := unmountEcShards(commandEnv.option.GrpcDialOption, vid, pb.NewServerAddressFromDataNode(ecNode.info), duplicatedShardIds); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := sourceServerDeleteEcShards(commandEnv.option.GrpcDialOption, collection, vid, pb.NewServerAddressFromDataNode(ecNode.info), duplicatedShardIds); err != nil {
|
||||
return err
|
||||
}
|
||||
ecNode.deleteEcVolumeShards(vid, duplicatedShardIds)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func balanceEcShardsAcrossRacks(commandEnv *CommandEnv, allEcNodes []*EcNode, racks map[RackId]*EcRack, collection string, applyBalancing bool) error {
|
||||
// collect vid => []ecNode, since previous steps can change the locations
|
||||
vidLocations := collectVolumeIdToEcNodes(allEcNodes, collection)
|
||||
// spread the ec shards evenly
|
||||
for vid, locations := range vidLocations {
|
||||
if err := doBalanceEcShardsAcrossRacks(commandEnv, collection, vid, locations, racks, applyBalancing); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func doBalanceEcShardsAcrossRacks(commandEnv *CommandEnv, collection string, vid needle.VolumeId, locations []*EcNode, racks map[RackId]*EcRack, applyBalancing bool) error {
|
||||
|
||||
// calculate average number of shards an ec rack should have for one volume
|
||||
averageShardsPerEcRack := ceilDivide(erasure_coding.TotalShardsCount, len(racks))
|
||||
|
||||
// see the volume's shards are in how many racks, and how many in each rack
|
||||
rackToShardCount := groupByCount(locations, func(ecNode *EcNode) (id string, count int) {
|
||||
shardBits := findEcVolumeShards(ecNode, vid)
|
||||
return string(ecNode.rack), shardBits.ShardIdCount()
|
||||
})
|
||||
rackEcNodesWithVid := groupBy(locations, func(ecNode *EcNode) string {
|
||||
return string(ecNode.rack)
|
||||
})
|
||||
|
||||
// ecShardsToMove = select overflown ec shards from racks with ec shard counts > averageShardsPerEcRack
|
||||
ecShardsToMove := make(map[erasure_coding.ShardId]*EcNode)
|
||||
for rackId, count := range rackToShardCount {
|
||||
if count > averageShardsPerEcRack {
|
||||
possibleEcNodes := rackEcNodesWithVid[rackId]
|
||||
for shardId, ecNode := range pickNEcShardsToMoveFrom(possibleEcNodes, vid, count-averageShardsPerEcRack) {
|
||||
ecShardsToMove[shardId] = ecNode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for shardId, ecNode := range ecShardsToMove {
|
||||
rackId := pickOneRack(racks, rackToShardCount, averageShardsPerEcRack)
|
||||
if rackId == "" {
|
||||
fmt.Printf("ec shard %d.%d at %s can not find a destination rack\n", vid, shardId, ecNode.info.Id)
|
||||
continue
|
||||
}
|
||||
var possibleDestinationEcNodes []*EcNode
|
||||
for _, n := range racks[rackId].ecNodes {
|
||||
possibleDestinationEcNodes = append(possibleDestinationEcNodes, n)
|
||||
}
|
||||
err := pickOneEcNodeAndMoveOneShard(commandEnv, averageShardsPerEcRack, ecNode, collection, vid, shardId, possibleDestinationEcNodes, applyBalancing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rackToShardCount[string(rackId)] += 1
|
||||
rackToShardCount[string(ecNode.rack)] -= 1
|
||||
racks[rackId].freeEcSlot -= 1
|
||||
racks[ecNode.rack].freeEcSlot += 1
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func pickOneRack(rackToEcNodes map[RackId]*EcRack, rackToShardCount map[string]int, averageShardsPerEcRack int) RackId {
|
||||
|
||||
// TODO later may need to add some randomness
|
||||
|
||||
for rackId, rack := range rackToEcNodes {
|
||||
if rackToShardCount[string(rackId)] >= averageShardsPerEcRack {
|
||||
continue
|
||||
}
|
||||
|
||||
if rack.freeEcSlot <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
return rackId
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func balanceEcShardsWithinRacks(commandEnv *CommandEnv, allEcNodes []*EcNode, racks map[RackId]*EcRack, collection string, applyBalancing bool) error {
|
||||
// collect vid => []ecNode, since previous steps can change the locations
|
||||
vidLocations := collectVolumeIdToEcNodes(allEcNodes, collection)
|
||||
|
||||
// spread the ec shards evenly
|
||||
for vid, locations := range vidLocations {
|
||||
|
||||
// see the volume's shards are in how many racks, and how many in each rack
|
||||
rackToShardCount := groupByCount(locations, func(ecNode *EcNode) (id string, count int) {
|
||||
shardBits := findEcVolumeShards(ecNode, vid)
|
||||
return string(ecNode.rack), shardBits.ShardIdCount()
|
||||
})
|
||||
rackEcNodesWithVid := groupBy(locations, func(ecNode *EcNode) string {
|
||||
return string(ecNode.rack)
|
||||
})
|
||||
|
||||
for rackId, _ := range rackToShardCount {
|
||||
|
||||
var possibleDestinationEcNodes []*EcNode
|
||||
for _, n := range racks[RackId(rackId)].ecNodes {
|
||||
if _, found := n.info.DiskInfos[string(types.HardDriveType)]; found {
|
||||
possibleDestinationEcNodes = append(possibleDestinationEcNodes, n)
|
||||
}
|
||||
}
|
||||
sourceEcNodes := rackEcNodesWithVid[rackId]
|
||||
averageShardsPerEcNode := ceilDivide(rackToShardCount[rackId], len(possibleDestinationEcNodes))
|
||||
if err := doBalanceEcShardsWithinOneRack(commandEnv, averageShardsPerEcNode, collection, vid, sourceEcNodes, possibleDestinationEcNodes, applyBalancing); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func doBalanceEcShardsWithinOneRack(commandEnv *CommandEnv, averageShardsPerEcNode int, collection string, vid needle.VolumeId, existingLocations, possibleDestinationEcNodes []*EcNode, applyBalancing bool) error {
|
||||
|
||||
for _, ecNode := range existingLocations {
|
||||
|
||||
shardBits := findEcVolumeShards(ecNode, vid)
|
||||
overLimitCount := shardBits.ShardIdCount() - averageShardsPerEcNode
|
||||
|
||||
for _, shardId := range shardBits.ShardIds() {
|
||||
|
||||
if overLimitCount <= 0 {
|
||||
break
|
||||
}
|
||||
|
||||
fmt.Printf("%s has %d overlimit, moving ec shard %d.%d\n", ecNode.info.Id, overLimitCount, vid, shardId)
|
||||
|
||||
err := pickOneEcNodeAndMoveOneShard(commandEnv, averageShardsPerEcNode, ecNode, collection, vid, shardId, possibleDestinationEcNodes, applyBalancing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
overLimitCount--
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func balanceEcRacks(commandEnv *CommandEnv, racks map[RackId]*EcRack, applyBalancing bool) error {
|
||||
|
||||
// balance one rack for all ec shards
|
||||
for _, ecRack := range racks {
|
||||
if err := doBalanceEcRack(commandEnv, ecRack, applyBalancing); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func doBalanceEcRack(commandEnv *CommandEnv, ecRack *EcRack, applyBalancing bool) error {
|
||||
|
||||
if len(ecRack.ecNodes) <= 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var rackEcNodes []*EcNode
|
||||
for _, node := range ecRack.ecNodes {
|
||||
rackEcNodes = append(rackEcNodes, node)
|
||||
}
|
||||
|
||||
ecNodeIdToShardCount := groupByCount(rackEcNodes, func(ecNode *EcNode) (id string, count int) {
|
||||
diskInfo, found := ecNode.info.DiskInfos[string(types.HardDriveType)]
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
for _, ecShardInfo := range diskInfo.EcShardInfos {
|
||||
count += erasure_coding.ShardBits(ecShardInfo.EcIndexBits).ShardIdCount()
|
||||
}
|
||||
return ecNode.info.Id, count
|
||||
})
|
||||
|
||||
var totalShardCount int
|
||||
for _, count := range ecNodeIdToShardCount {
|
||||
totalShardCount += count
|
||||
}
|
||||
|
||||
averageShardCount := ceilDivide(totalShardCount, len(rackEcNodes))
|
||||
|
||||
hasMove := true
|
||||
for hasMove {
|
||||
hasMove = false
|
||||
slices.SortFunc(rackEcNodes, func(a, b *EcNode) int {
|
||||
return b.freeEcSlot - a.freeEcSlot
|
||||
})
|
||||
emptyNode, fullNode := rackEcNodes[0], rackEcNodes[len(rackEcNodes)-1]
|
||||
emptyNodeShardCount, fullNodeShardCount := ecNodeIdToShardCount[emptyNode.info.Id], ecNodeIdToShardCount[fullNode.info.Id]
|
||||
if fullNodeShardCount > averageShardCount && emptyNodeShardCount+1 <= averageShardCount {
|
||||
|
||||
emptyNodeIds := make(map[uint32]bool)
|
||||
if emptyDiskInfo, found := emptyNode.info.DiskInfos[string(types.HardDriveType)]; found {
|
||||
for _, shards := range emptyDiskInfo.EcShardInfos {
|
||||
emptyNodeIds[shards.Id] = true
|
||||
}
|
||||
}
|
||||
if fullDiskInfo, found := fullNode.info.DiskInfos[string(types.HardDriveType)]; found {
|
||||
for _, shards := range fullDiskInfo.EcShardInfos {
|
||||
if _, found := emptyNodeIds[shards.Id]; !found {
|
||||
for _, shardId := range erasure_coding.ShardBits(shards.EcIndexBits).ShardIds() {
|
||||
|
||||
fmt.Printf("%s moves ec shards %d.%d to %s\n", fullNode.info.Id, shards.Id, shardId, emptyNode.info.Id)
|
||||
|
||||
err := moveMountedShardToEcNode(commandEnv, fullNode, shards.Collection, needle.VolumeId(shards.Id), shardId, emptyNode, applyBalancing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ecNodeIdToShardCount[emptyNode.info.Id]++
|
||||
ecNodeIdToShardCount[fullNode.info.Id]--
|
||||
hasMove = true
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func pickOneEcNodeAndMoveOneShard(commandEnv *CommandEnv, averageShardsPerEcNode int, existingLocation *EcNode, collection string, vid needle.VolumeId, shardId erasure_coding.ShardId, possibleDestinationEcNodes []*EcNode, applyBalancing bool) error {
|
||||
|
||||
sortEcNodesByFreeslotsDescending(possibleDestinationEcNodes)
|
||||
skipReason := ""
|
||||
for _, destEcNode := range possibleDestinationEcNodes {
|
||||
|
||||
if destEcNode.info.Id == existingLocation.info.Id {
|
||||
continue
|
||||
}
|
||||
|
||||
if destEcNode.freeEcSlot <= 0 {
|
||||
skipReason += fmt.Sprintf(" Skipping %s because it has no free slots\n", destEcNode.info.Id)
|
||||
continue
|
||||
}
|
||||
if findEcVolumeShards(destEcNode, vid).ShardIdCount() >= averageShardsPerEcNode {
|
||||
skipReason += fmt.Sprintf(" Skipping %s because it %d >= avernageShards (%d)\n",
|
||||
destEcNode.info.Id, findEcVolumeShards(destEcNode, vid).ShardIdCount(), averageShardsPerEcNode)
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf("%s moves ec shard %d.%d to %s\n", existingLocation.info.Id, vid, shardId, destEcNode.info.Id)
|
||||
|
||||
err := moveMountedShardToEcNode(commandEnv, existingLocation, collection, vid, shardId, destEcNode, applyBalancing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
fmt.Printf("WARNING: Could not find suitable taget node for %d.%d:\n%s", vid, shardId, skipReason)
|
||||
return nil
|
||||
}
|
||||
|
||||
func pickNEcShardsToMoveFrom(ecNodes []*EcNode, vid needle.VolumeId, n int) map[erasure_coding.ShardId]*EcNode {
|
||||
picked := make(map[erasure_coding.ShardId]*EcNode)
|
||||
var candidateEcNodes []*CandidateEcNode
|
||||
for _, ecNode := range ecNodes {
|
||||
shardBits := findEcVolumeShards(ecNode, vid)
|
||||
if shardBits.ShardIdCount() > 0 {
|
||||
candidateEcNodes = append(candidateEcNodes, &CandidateEcNode{
|
||||
ecNode: ecNode,
|
||||
shardCount: shardBits.ShardIdCount(),
|
||||
})
|
||||
}
|
||||
}
|
||||
slices.SortFunc(candidateEcNodes, func(a, b *CandidateEcNode) int {
|
||||
return b.shardCount - a.shardCount
|
||||
})
|
||||
for i := 0; i < n; i++ {
|
||||
selectedEcNodeIndex := -1
|
||||
for i, candidateEcNode := range candidateEcNodes {
|
||||
shardBits := findEcVolumeShards(candidateEcNode.ecNode, vid)
|
||||
if shardBits > 0 {
|
||||
selectedEcNodeIndex = i
|
||||
for _, shardId := range shardBits.ShardIds() {
|
||||
candidateEcNode.shardCount--
|
||||
picked[shardId] = candidateEcNode.ecNode
|
||||
candidateEcNode.ecNode.deleteEcVolumeShards(vid, []uint32{uint32(shardId)})
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if selectedEcNodeIndex >= 0 {
|
||||
ensureSortedEcNodes(candidateEcNodes, selectedEcNodeIndex, func(i, j int) bool {
|
||||
return candidateEcNodes[i].shardCount > candidateEcNodes[j].shardCount
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
return picked
|
||||
}
|
||||
|
||||
func collectVolumeIdToEcNodes(allEcNodes []*EcNode, collection string) map[needle.VolumeId][]*EcNode {
|
||||
vidLocations := make(map[needle.VolumeId][]*EcNode)
|
||||
for _, ecNode := range allEcNodes {
|
||||
diskInfo, found := ecNode.info.DiskInfos[string(types.HardDriveType)]
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
for _, shardInfo := range diskInfo.EcShardInfos {
|
||||
// ignore if not in current collection
|
||||
if shardInfo.Collection == collection {
|
||||
vidLocations[needle.VolumeId(shardInfo.Id)] = append(vidLocations[needle.VolumeId(shardInfo.Id)], ecNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
return vidLocations
|
||||
}
|
||||
|
||||
func EcBalance(commandEnv *CommandEnv, collections []string, dc string, applyBalancing bool) (err error) {
|
||||
if len(collections) == 0 {
|
||||
return fmt.Errorf("no collections to balance")
|
||||
}
|
||||
|
||||
// collect all ec nodes
|
||||
allEcNodes, totalFreeEcSlots, err := collectEcNodes(commandEnv, dc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if totalFreeEcSlots < 1 {
|
||||
return fmt.Errorf("no free ec shard slots. only %d left", totalFreeEcSlots)
|
||||
}
|
||||
|
||||
racks := collectRacks(allEcNodes)
|
||||
for _, c := range collections {
|
||||
if err = balanceEcVolumes(commandEnv, c, allEcNodes, racks, applyBalancing); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := balanceEcRacks(commandEnv, racks, applyBalancing); err != nil {
|
||||
return fmt.Errorf("balance ec racks: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -237,7 +237,10 @@ func parallelCopyEcShardsFromSource(grpcDialOption grpc.DialOption, targetServer
|
||||
fmt.Printf("unmount aborted shards %d.%v on %s: %v\n", volumeId, allocatedEcShardIds, server.info.Id, err)
|
||||
}
|
||||
if err := sourceServerDeleteEcShards(grpcDialOption, collection, volumeId, pb.NewServerAddressFromDataNode(server.info), allocatedEcShardIds); err != nil {
|
||||
fmt.Printf("remove aborted shards %d.%v on %s: %v\n", volumeId, allocatedEcShardIds, server.info.Id, err)
|
||||
fmt.Printf("remove aborted shards %d.%v on target server %s: %v\n", volumeId, allocatedEcShardIds, server.info.Id, err)
|
||||
}
|
||||
if err := sourceServerDeleteEcShards(grpcDialOption, collection, volumeId, existingLocation.ServerAddress(), allocatedEcShardIds); err != nil {
|
||||
fmt.Printf("remove aborted shards %d.%v on existing server %s: %v\n", volumeId, allocatedEcShardIds, existingLocation.ServerAddress(), err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,7 +327,7 @@ func collectVolumeIdsForEcEncode(commandEnv *CommandEnv, selectedCollection stri
|
||||
}
|
||||
} else {
|
||||
if diskInfo.FreeVolumeCount < 2 {
|
||||
glog.V(0).Infof("skip %d %d on %s, no free disk", v.Collection, v.Id, dn.Id)
|
||||
glog.V(0).Infof("skip %s %d on %s, no free disk", v.Collection, v.Id, dn.Id)
|
||||
vidMap[v.Id] = false
|
||||
} else {
|
||||
vidMap[v.Id] = true
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package shell
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"github.com/seaweedfs/seaweedfs/weed/filer_client"
|
||||
"github.com/seaweedfs/seaweedfs/weed/mq/logstore"
|
||||
"github.com/seaweedfs/seaweedfs/weed/mq/schema"
|
||||
"github.com/seaweedfs/seaweedfs/weed/mq/topic"
|
||||
"github.com/seaweedfs/seaweedfs/weed/operation"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb"
|
||||
"google.golang.org/grpc"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Commands = append(Commands, &commandMqTopicCompact{})
|
||||
}
|
||||
|
||||
type commandMqTopicCompact struct {
|
||||
}
|
||||
|
||||
func (c *commandMqTopicCompact) Name() string {
|
||||
return "mq.topic.compact"
|
||||
}
|
||||
|
||||
func (c *commandMqTopicCompact) Help() string {
|
||||
return `compact the topic storage into parquet format
|
||||
|
||||
Example:
|
||||
mq.topic.compact -namespace <namespace> -topic <topic_name> -timeAgo <time_ago>
|
||||
|
||||
`
|
||||
}
|
||||
|
||||
func (c *commandMqTopicCompact) HasTag(tag CommandTag) bool {
|
||||
return ResourceHeavy == tag
|
||||
}
|
||||
|
||||
func (c *commandMqTopicCompact) Do(args []string, commandEnv *CommandEnv, writer io.Writer) error {
|
||||
|
||||
// parse parameters
|
||||
mqCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
|
||||
namespace := mqCommand.String("namespace", "", "namespace name")
|
||||
topicName := mqCommand.String("topic", "", "topic name")
|
||||
timeAgo := mqCommand.Duration("timeAgo", 2*time.Minute, "start time before now. \"300ms\", \"1.5h\" or \"2h45m\". Valid time units are \"ns\", \"us\" (or \"µs\"), \"ms\", \"s\", \"m\", \"h\"")
|
||||
replication := mqCommand.String("replication", "", "replication type")
|
||||
collection := mqCommand.String("collection", "", "optional collection name")
|
||||
dataCenter := mqCommand.String("dataCenter", "", "optional data center name")
|
||||
diskType := mqCommand.String("disk", "", "[hdd|ssd|<tag>] hard drive or solid state drive or any tag")
|
||||
maxMB := mqCommand.Int("maxMB", 4, "split files larger than the limit")
|
||||
|
||||
if err := mqCommand.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
storagePreference := &operation.StoragePreference{
|
||||
Replication: *replication,
|
||||
Collection: *collection,
|
||||
DataCenter: *dataCenter,
|
||||
DiskType: *diskType,
|
||||
MaxMB: *maxMB,
|
||||
}
|
||||
|
||||
// read topic configuration
|
||||
fca := &filer_client.FilerClientAccessor{
|
||||
GetFiler: func() pb.ServerAddress {
|
||||
return commandEnv.option.FilerAddress
|
||||
},
|
||||
GetGrpcDialOption: func() grpc.DialOption {
|
||||
return commandEnv.option.GrpcDialOption
|
||||
},
|
||||
}
|
||||
t := topic.NewTopic(*namespace, *topicName)
|
||||
topicConf, err := fca.ReadTopicConfFromFiler(t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get record type
|
||||
recordType := topicConf.GetRecordType()
|
||||
recordType = schema.NewRecordTypeBuilder(recordType).
|
||||
WithField(logstore.SW_COLUMN_NAME_TS, schema.TypeInt64).
|
||||
WithField(logstore.SW_COLUMN_NAME_KEY, schema.TypeBytes).
|
||||
RecordTypeEnd()
|
||||
|
||||
// compact the topic partition versions
|
||||
if err = logstore.CompactTopicPartitions(commandEnv, t, *timeAgo, recordType, storagePreference); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb"
|
||||
@@ -32,7 +33,7 @@ func (c *commandVolumeBalance) Name() string {
|
||||
func (c *commandVolumeBalance) Help() string {
|
||||
return `balance all volumes among volume servers
|
||||
|
||||
volume.balance [-collection ALL_COLLECTIONS|EACH_COLLECTION|<collection_name>] [-force] [-dataCenter=<data_center_name>]
|
||||
volume.balance [-collection ALL_COLLECTIONS|EACH_COLLECTION|<collection_name>] [-force] [-dataCenter=<data_center_name>] [-racks=rack_name_one,rack_name_two] [-nodes=192.168.0.1:8080,192.168.0.2:8080]
|
||||
|
||||
Algorithm:
|
||||
|
||||
@@ -73,23 +74,30 @@ func (c *commandVolumeBalance) Do(args []string, commandEnv *CommandEnv, writer
|
||||
balanceCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
|
||||
collection := balanceCommand.String("collection", "ALL_COLLECTIONS", "collection name, or use \"ALL_COLLECTIONS\" across collections, \"EACH_COLLECTION\" for each collection")
|
||||
dc := balanceCommand.String("dataCenter", "", "only apply the balancing for this dataCenter")
|
||||
racks := balanceCommand.String("racks", "", "only apply the balancing for this racks")
|
||||
nodes := balanceCommand.String("nodes", "", "only apply the balancing for this nodes")
|
||||
noLock := balanceCommand.Bool("noLock", false, "do not lock the admin shell at one's own risk")
|
||||
applyBalancing := balanceCommand.Bool("force", false, "apply the balancing plan.")
|
||||
if err = balanceCommand.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
infoAboutSimulationMode(writer, *applyBalancing, "-force")
|
||||
|
||||
if err = commandEnv.confirmIsLocked(args); err != nil {
|
||||
return
|
||||
if *noLock {
|
||||
commandEnv.noLock = true
|
||||
} else {
|
||||
if err = commandEnv.confirmIsLocked(args); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// collect topology information
|
||||
topologyInfo, _, err := collectTopologyInfo(commandEnv, 15*time.Second)
|
||||
topologyInfo, _, err := collectTopologyInfo(commandEnv, 5*time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
volumeServers := collectVolumeServersByDc(topologyInfo, *dc)
|
||||
volumeServers := collectVolumeServersByDcRackNode(topologyInfo, *dc, *racks, *nodes)
|
||||
volumeReplicas, _ := collectVolumeReplicaLocations(topologyInfo)
|
||||
diskTypes := collectVolumeDiskTypes(topologyInfo)
|
||||
|
||||
@@ -142,13 +150,19 @@ func balanceVolumeServersByDiskType(commandEnv *CommandEnv, diskType types.DiskT
|
||||
return nil
|
||||
}
|
||||
|
||||
func collectVolumeServersByDc(t *master_pb.TopologyInfo, selectedDataCenter string) (nodes []*Node) {
|
||||
func collectVolumeServersByDcRackNode(t *master_pb.TopologyInfo, selectedDataCenter string, selectedRacks string, selectedNodes string) (nodes []*Node) {
|
||||
for _, dc := range t.DataCenterInfos {
|
||||
if selectedDataCenter != "" && dc.Id != selectedDataCenter {
|
||||
continue
|
||||
}
|
||||
for _, r := range dc.RackInfos {
|
||||
if selectedRacks != "" && !strings.Contains(selectedRacks, r.Id) {
|
||||
continue
|
||||
}
|
||||
for _, dn := range r.DataNodeInfos {
|
||||
if selectedNodes != "" && !strings.Contains(selectedNodes, dn.Id) {
|
||||
continue
|
||||
}
|
||||
nodes = append(nodes, &Node{
|
||||
info: dn,
|
||||
dc: dc.Id,
|
||||
@@ -325,7 +339,6 @@ func attemptToMoveOneVolume(commandEnv *CommandEnv, volumeReplicas map[uint32][]
|
||||
}
|
||||
|
||||
func maybeMoveOneVolume(commandEnv *CommandEnv, volumeReplicas map[uint32][]*VolumeReplica, fullNode *Node, candidateVolume *master_pb.VolumeInformationMessage, emptyNode *Node, applyChange bool) (hasMoved bool, err error) {
|
||||
|
||||
if !commandEnv.isLocked() {
|
||||
return false, fmt.Errorf("lock is lost")
|
||||
}
|
||||
|
||||
@@ -251,7 +251,7 @@ func TestIsGoodMove(t *testing.T) {
|
||||
|
||||
func TestBalance(t *testing.T) {
|
||||
topologyInfo := parseOutput(topoData)
|
||||
volumeServers := collectVolumeServersByDc(topologyInfo, "")
|
||||
volumeServers := collectVolumeServersByDcRackNode(topologyInfo, "", "", "")
|
||||
volumeReplicas, _ := collectVolumeReplicaLocations(topologyInfo)
|
||||
diskTypes := collectVolumeDiskTypes(topologyInfo)
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ func (c *commandVolumeServerEvacuate) volumeServerEvacuate(commandEnv *CommandEn
|
||||
|
||||
func (c *commandVolumeServerEvacuate) evacuateNormalVolumes(commandEnv *CommandEnv, volumeServer string, skipNonMoveable, applyChange bool, writer io.Writer) error {
|
||||
// find this volume server
|
||||
volumeServers := collectVolumeServersByDc(c.topologyInfo, "")
|
||||
volumeServers := collectVolumeServersByDcRackNode(c.topologyInfo, "", "", "")
|
||||
thisNodes, otherNodes := c.nodesOtherThan(volumeServers, volumeServer)
|
||||
if len(thisNodes) == 0 {
|
||||
return fmt.Errorf("%s is not found in this cluster", volumeServer)
|
||||
@@ -124,7 +124,7 @@ func (c *commandVolumeServerEvacuate) evacuateNormalVolumes(commandEnv *CommandE
|
||||
fmt.Fprintf(writer, "update topologyInfo %v", err)
|
||||
} else {
|
||||
_, otherNodesNew := c.nodesOtherThan(
|
||||
collectVolumeServersByDc(topologyInfo, ""), volumeServer)
|
||||
collectVolumeServersByDcRackNode(topologyInfo, "", "", ""), volumeServer)
|
||||
if len(otherNodesNew) > 0 {
|
||||
otherNodes = otherNodesNew
|
||||
c.topologyInfo = topologyInfo
|
||||
|
||||
@@ -35,6 +35,7 @@ type CommandEnv struct {
|
||||
MasterClient *wdclient.MasterClient
|
||||
option *ShellOptions
|
||||
locker *exclusive_locks.ExclusiveLocker
|
||||
noLock bool
|
||||
}
|
||||
|
||||
func NewCommandEnv(options *ShellOptions) *CommandEnv {
|
||||
@@ -42,6 +43,7 @@ func NewCommandEnv(options *ShellOptions) *CommandEnv {
|
||||
env: make(map[string]string),
|
||||
MasterClient: wdclient.NewMasterClient(options.GrpcDialOption, *options.FilerGroup, pb.AdminShellClient, "", "", "", *pb.ServerAddresses(*options.Masters).ToServiceDiscovery()),
|
||||
option: options,
|
||||
noLock: false,
|
||||
}
|
||||
ce.locker = exclusive_locks.NewExclusiveLocker(ce.MasterClient, "shell")
|
||||
return ce
|
||||
@@ -79,6 +81,9 @@ func (ce *CommandEnv) isLocked() bool {
|
||||
if ce == nil {
|
||||
return true
|
||||
}
|
||||
if ce.noLock {
|
||||
return false
|
||||
}
|
||||
return ce.locker.IsLocked()
|
||||
}
|
||||
|
||||
|
||||
@@ -94,6 +94,14 @@ var (
|
||||
Help: "Counter of master pick for write error",
|
||||
})
|
||||
|
||||
MasterBroadcastToFullErrorCounter = prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Namespace: Namespace,
|
||||
Subsystem: "master",
|
||||
Name: "broadcast_to_full",
|
||||
Help: "Counter of master broadcast send to full message channel err",
|
||||
})
|
||||
|
||||
MasterLeaderChangeCounter = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Namespace: Namespace,
|
||||
@@ -314,6 +322,7 @@ func init() {
|
||||
Gather.MustRegister(MasterReplicaPlacementMismatch)
|
||||
Gather.MustRegister(MasterVolumeLayoutWritable)
|
||||
Gather.MustRegister(MasterVolumeLayoutCrowded)
|
||||
Gather.MustRegister(MasterBroadcastToFullErrorCounter)
|
||||
|
||||
Gather.MustRegister(FilerRequestCounter)
|
||||
Gather.MustRegister(FilerHandlerCounter)
|
||||
|
||||
@@ -43,19 +43,8 @@ func ParseUpload(r *http.Request, sizeLimit int64, bytesBuffer *bytes.Buffer) (p
|
||||
}
|
||||
}
|
||||
|
||||
if r.Method == http.MethodPost {
|
||||
contentType := r.Header.Get("Content-Type")
|
||||
e = parseUpload(r, sizeLimit, pu)
|
||||
|
||||
// If content-type is explicitly set, upload the file without parsing form-data
|
||||
if contentType != "" && !strings.Contains(contentType, "form-data") {
|
||||
e = parseRawPost(r, sizeLimit, pu)
|
||||
} else {
|
||||
e = parseMultipart(r, sizeLimit, pu)
|
||||
}
|
||||
|
||||
} else {
|
||||
e = parsePut(r, sizeLimit, pu)
|
||||
}
|
||||
if e != nil {
|
||||
return
|
||||
}
|
||||
@@ -108,87 +97,136 @@ func ParseUpload(r *http.Request, sizeLimit int64, bytesBuffer *bytes.Buffer) (p
|
||||
return
|
||||
}
|
||||
|
||||
func parsePut(r *http.Request, sizeLimit int64, pu *ParsedUpload) error {
|
||||
pu.IsGzipped = r.Header.Get("Content-Encoding") == "gzip"
|
||||
// pu.IsZstd = r.Header.Get("Content-Encoding") == "zstd"
|
||||
pu.MimeType = r.Header.Get("Content-Type")
|
||||
pu.FileName = ""
|
||||
dataSize, err := pu.bytesBuffer.ReadFrom(io.LimitReader(r.Body, sizeLimit+1))
|
||||
if err == io.EOF || dataSize == sizeLimit+1 {
|
||||
io.Copy(io.Discard, r.Body)
|
||||
}
|
||||
pu.Data = pu.bytesBuffer.Bytes()
|
||||
r.Body.Close()
|
||||
return nil
|
||||
}
|
||||
func parseUpload(r *http.Request, sizeLimit int64, pu *ParsedUpload) (e error) {
|
||||
|
||||
func parseMultipart(r *http.Request, sizeLimit int64, pu *ParsedUpload) (e error) {
|
||||
defer func() {
|
||||
if e != nil && r.Body != nil {
|
||||
io.Copy(io.Discard, r.Body)
|
||||
r.Body.Close()
|
||||
}
|
||||
}()
|
||||
form, fe := r.MultipartReader()
|
||||
if fe != nil {
|
||||
glog.V(0).Infoln("MultipartReader [ERROR]", fe)
|
||||
e = fe
|
||||
return
|
||||
}
|
||||
|
||||
// first multi-part item
|
||||
part, fe := form.NextPart()
|
||||
if fe != nil {
|
||||
glog.V(0).Infoln("Reading Multi part [ERROR]", fe)
|
||||
e = fe
|
||||
return
|
||||
}
|
||||
|
||||
pu.FileName = part.FileName()
|
||||
if pu.FileName != "" {
|
||||
pu.FileName = path.Base(pu.FileName)
|
||||
}
|
||||
|
||||
contentType := r.Header.Get("Content-Type")
|
||||
var dataSize int64
|
||||
dataSize, e = pu.bytesBuffer.ReadFrom(io.LimitReader(part, sizeLimit+1))
|
||||
if e != nil {
|
||||
glog.V(0).Infoln("Reading Content [ERROR]", e)
|
||||
return
|
||||
}
|
||||
if dataSize == sizeLimit+1 {
|
||||
e = fmt.Errorf("file over the limited %d bytes", sizeLimit)
|
||||
return
|
||||
}
|
||||
pu.Data = pu.bytesBuffer.Bytes()
|
||||
|
||||
// if the filename is empty string, do a search on the other multi-part items
|
||||
for pu.FileName == "" {
|
||||
part2, fe := form.NextPart()
|
||||
if r.Method == http.MethodPost && (contentType == "" || strings.Contains(contentType, "form-data")) {
|
||||
form, fe := r.MultipartReader()
|
||||
|
||||
if fe != nil {
|
||||
break // no more or on error, just safely break
|
||||
glog.V(0).Infoln("MultipartReader [ERROR]", fe)
|
||||
e = fe
|
||||
return
|
||||
}
|
||||
|
||||
fName := part2.FileName()
|
||||
|
||||
// found the first <file type> multi-part has filename
|
||||
if fName != "" {
|
||||
pu.bytesBuffer.Reset()
|
||||
dataSize2, fe2 := pu.bytesBuffer.ReadFrom(io.LimitReader(part2, sizeLimit+1))
|
||||
if fe2 != nil {
|
||||
glog.V(0).Infoln("Reading Content [ERROR]", fe2)
|
||||
e = fe2
|
||||
return
|
||||
}
|
||||
if dataSize2 == sizeLimit+1 {
|
||||
e = fmt.Errorf("file over the limited %d bytes", sizeLimit)
|
||||
return
|
||||
}
|
||||
|
||||
// update
|
||||
pu.Data = pu.bytesBuffer.Bytes()
|
||||
pu.FileName = path.Base(fName)
|
||||
break
|
||||
// first multi-part item
|
||||
part, fe := form.NextPart()
|
||||
if fe != nil {
|
||||
glog.V(0).Infoln("Reading Multi part [ERROR]", fe)
|
||||
e = fe
|
||||
return
|
||||
}
|
||||
|
||||
pu.FileName = part.FileName()
|
||||
if pu.FileName != "" {
|
||||
pu.FileName = path.Base(pu.FileName)
|
||||
}
|
||||
|
||||
dataSize, e = pu.bytesBuffer.ReadFrom(io.LimitReader(part, sizeLimit+1))
|
||||
if e != nil {
|
||||
glog.V(0).Infoln("Reading Content [ERROR]", e)
|
||||
return
|
||||
}
|
||||
if dataSize == sizeLimit+1 {
|
||||
e = fmt.Errorf("file over the limited %d bytes", sizeLimit)
|
||||
return
|
||||
}
|
||||
pu.Data = pu.bytesBuffer.Bytes()
|
||||
|
||||
contentType = part.Header.Get("Content-Type")
|
||||
|
||||
// if the filename is empty string, do a search on the other multi-part items
|
||||
for pu.FileName == "" {
|
||||
part2, fe := form.NextPart()
|
||||
if fe != nil {
|
||||
break // no more or on error, just safely break
|
||||
}
|
||||
|
||||
fName := part2.FileName()
|
||||
|
||||
// found the first <file type> multi-part has filename
|
||||
if fName != "" {
|
||||
pu.bytesBuffer.Reset()
|
||||
dataSize2, fe2 := pu.bytesBuffer.ReadFrom(io.LimitReader(part2, sizeLimit+1))
|
||||
if fe2 != nil {
|
||||
glog.V(0).Infoln("Reading Content [ERROR]", fe2)
|
||||
e = fe2
|
||||
return
|
||||
}
|
||||
if dataSize2 == sizeLimit+1 {
|
||||
e = fmt.Errorf("file over the limited %d bytes", sizeLimit)
|
||||
return
|
||||
}
|
||||
|
||||
// update
|
||||
pu.Data = pu.bytesBuffer.Bytes()
|
||||
pu.FileName = path.Base(fName)
|
||||
contentType = part.Header.Get("Content-Type")
|
||||
part = part2
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
pu.IsGzipped = part.Header.Get("Content-Encoding") == "gzip"
|
||||
// pu.IsZstd = part.Header.Get("Content-Encoding") == "zstd"
|
||||
|
||||
} else {
|
||||
disposition := r.Header.Get("Content-Disposition")
|
||||
|
||||
if strings.Contains(disposition, "name=") {
|
||||
|
||||
if !strings.HasPrefix(disposition, "inline") && !strings.HasPrefix(disposition, "attachment") {
|
||||
disposition = "attachment; " + disposition
|
||||
}
|
||||
|
||||
_, mediaTypeParams, err := mime.ParseMediaType(disposition)
|
||||
|
||||
if err == nil {
|
||||
dpFilename, hasFilename := mediaTypeParams["filename"]
|
||||
dpName, hasName := mediaTypeParams["name"]
|
||||
|
||||
if hasFilename {
|
||||
pu.FileName = dpFilename
|
||||
} else if hasName {
|
||||
pu.FileName = dpName
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
pu.FileName = ""
|
||||
}
|
||||
|
||||
if pu.FileName != "" {
|
||||
pu.FileName = path.Base(pu.FileName)
|
||||
} else {
|
||||
pu.FileName = path.Base(r.URL.Path)
|
||||
}
|
||||
|
||||
dataSize, e = pu.bytesBuffer.ReadFrom(io.LimitReader(r.Body, sizeLimit+1))
|
||||
|
||||
if e != nil {
|
||||
glog.V(0).Infoln("Reading Content [ERROR]", e)
|
||||
return
|
||||
}
|
||||
if dataSize == sizeLimit+1 {
|
||||
e = fmt.Errorf("file over the limited %d bytes", sizeLimit)
|
||||
return
|
||||
}
|
||||
|
||||
pu.Data = pu.bytesBuffer.Bytes()
|
||||
pu.MimeType = contentType
|
||||
pu.IsGzipped = r.Header.Get("Content-Encoding") == "gzip"
|
||||
// pu.IsZstd = r.Header.Get("Content-Encoding") == "zstd"
|
||||
}
|
||||
|
||||
pu.IsChunkedFile, _ = strconv.ParseBool(r.FormValue("cm"))
|
||||
@@ -197,81 +235,19 @@ func parseMultipart(r *http.Request, sizeLimit int64, pu *ParsedUpload) (e error
|
||||
|
||||
dotIndex := strings.LastIndex(pu.FileName, ".")
|
||||
ext, mtype := "", ""
|
||||
|
||||
if dotIndex > 0 {
|
||||
ext = strings.ToLower(pu.FileName[dotIndex:])
|
||||
mtype = mime.TypeByExtension(ext)
|
||||
}
|
||||
contentType := part.Header.Get("Content-Type")
|
||||
if contentType != "" && contentType != "application/octet-stream" && mtype != contentType {
|
||||
pu.MimeType = contentType // only return mime type if not deducible
|
||||
mtype = contentType
|
||||
}
|
||||
|
||||
}
|
||||
pu.IsGzipped = part.Header.Get("Content-Encoding") == "gzip"
|
||||
// pu.IsZstd = part.Header.Get("Content-Encoding") == "zstd"
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func parseRawPost(r *http.Request, sizeLimit int64, pu *ParsedUpload) (e error) {
|
||||
|
||||
defer func() {
|
||||
if e != nil && r.Body != nil {
|
||||
io.Copy(io.Discard, r.Body)
|
||||
r.Body.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
pu.FileName = r.Header.Get("Content-Disposition")
|
||||
|
||||
if pu.FileName != "" && strings.Contains(pu.FileName, "filename=") {
|
||||
parts := strings.Split(pu.FileName, "filename=")
|
||||
parts = strings.Split(parts[1], "\"")
|
||||
|
||||
pu.FileName = parts[1]
|
||||
} else {
|
||||
pu.FileName = ""
|
||||
}
|
||||
|
||||
if pu.FileName != "" {
|
||||
pu.FileName = path.Base(pu.FileName)
|
||||
} else {
|
||||
pu.FileName = path.Base(r.URL.Path)
|
||||
}
|
||||
|
||||
var dataSize int64
|
||||
dataSize, e = pu.bytesBuffer.ReadFrom(io.LimitReader(r.Body, sizeLimit+1))
|
||||
|
||||
if e != nil {
|
||||
glog.V(0).Infoln("Reading Content [ERROR]", e)
|
||||
return
|
||||
}
|
||||
if dataSize == sizeLimit+1 {
|
||||
e = fmt.Errorf("file over the limited %d bytes", sizeLimit)
|
||||
return
|
||||
}
|
||||
pu.Data = pu.bytesBuffer.Bytes()
|
||||
pu.IsChunkedFile, _ = strconv.ParseBool(r.FormValue("cm"))
|
||||
|
||||
if !pu.IsChunkedFile {
|
||||
|
||||
dotIndex := strings.LastIndex(pu.FileName, ".")
|
||||
ext, mtype := "", ""
|
||||
if dotIndex > 0 {
|
||||
ext = strings.ToLower(pu.FileName[dotIndex:])
|
||||
mtype = mime.TypeByExtension(ext)
|
||||
}
|
||||
contentType := r.Header.Get("Content-Type")
|
||||
|
||||
if contentType != "" && contentType != "application/octet-stream" && mtype != contentType {
|
||||
pu.MimeType = contentType // only return mime type if not deducible
|
||||
mtype = contentType
|
||||
} else if mtype != "" && pu.MimeType == "" && mtype != "application/octet-stream" {
|
||||
pu.MimeType = mtype
|
||||
}
|
||||
|
||||
}
|
||||
pu.IsGzipped = r.Header.Get("Content-Encoding") == "gzip"
|
||||
// pu.IsZstd = r.Header.Get("Content-Encoding") == "zstd"
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -74,7 +74,9 @@ func (n *Needle) ReadBytes(bytes []byte, offset int64, size Size, version Versio
|
||||
checksum := util.BytesToUint32(bytes[NeedleHeaderSize+size : NeedleHeaderSize+size+NeedleChecksumSize])
|
||||
newChecksum := NewCRC(n.Data)
|
||||
if checksum != newChecksum.Value() && checksum != uint32(newChecksum) {
|
||||
// the crc.Value() function is to be deprecated. this double checking is for backward compatible.
|
||||
// the crc.Value() function is to be deprecated. this double checking is for backward compatibility
|
||||
// with seaweed version using crc.Value() instead of uint32(crc), which appears in commit 056c480eb
|
||||
// and switch appeared in version 3.09.
|
||||
stats.VolumeServerHandlerCounter.WithLabelValues(stats.ErrorCRC).Inc()
|
||||
return errors.New("CRC error! Data On Disk Corrupted")
|
||||
}
|
||||
|
||||
@@ -164,7 +164,10 @@ func (v *Volume) readNeedleDataInto(n *needle.Needle, readOption *ReadOption, wr
|
||||
toWrite := min(count, int(offset+size-x))
|
||||
if toWrite > 0 {
|
||||
crc = crc.Update(buf[0:toWrite])
|
||||
if offset == 0 && size == int64(n.DataSize) && int64(count) == size && (n.Checksum != crc) {
|
||||
// the crc.Value() function is to be deprecated. this double checking is for backward compatibility
|
||||
// with seaweed version using crc.Value() instead of uint32(crc), which appears in commit 056c480eb
|
||||
// and switch appeared in version 3.09.
|
||||
if offset == 0 && size == int64(n.DataSize) && int64(count) == size && (n.Checksum != crc && uint32(n.Checksum) != crc.Value()) {
|
||||
// This check works only if the buffer is big enough to hold the whole needle data
|
||||
// and we ask for all needle data.
|
||||
// Otherwise we cannot check the validity of partially aquired data.
|
||||
@@ -187,7 +190,9 @@ func (v *Volume) readNeedleDataInto(n *needle.Needle, readOption *ReadOption, wr
|
||||
}
|
||||
}
|
||||
if offset == 0 && size == int64(n.DataSize) && (n.Checksum != crc && uint32(n.Checksum) != crc.Value()) {
|
||||
// the crc.Value() function is to be deprecated. this double checking is for backward compatible.
|
||||
// the crc.Value() function is to be deprecated. this double checking is for backward compatibility
|
||||
// with seaweed version using crc.Value() instead of uint32(crc), which appears in commit 056c480eb
|
||||
// and switch appeared in version 3.09.
|
||||
stats.VolumeServerHandlerCounter.WithLabelValues(stats.ErrorCRC).Inc()
|
||||
return fmt.Errorf("ReadNeedleData checksum %v expected %v for Needle: %v,%v", crc, n.Checksum, v.Id, n)
|
||||
}
|
||||
|
||||
@@ -5,11 +5,31 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
_ ChunkCache = &ChunkCacheInMemory{}
|
||||
)
|
||||
|
||||
// a global cache for recently accessed file chunks
|
||||
type ChunkCacheInMemory struct {
|
||||
cache *ccache.Cache
|
||||
}
|
||||
|
||||
func (c *ChunkCacheInMemory) ReadChunkAt(data []byte, fileId string, offset uint64) (n int, err error) {
|
||||
return c.readChunkAt(data, fileId, offset)
|
||||
}
|
||||
|
||||
func (c *ChunkCacheInMemory) IsInCache(fileId string, lockNeeded bool) (answer bool) {
|
||||
item := c.cache.Get(fileId)
|
||||
if item == nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *ChunkCacheInMemory) GetMaxFilePartSizeInCache() (answer uint64) {
|
||||
return 8 * 1024 * 1024
|
||||
}
|
||||
|
||||
func NewChunkCacheInMemory(maxEntries int64) *ChunkCacheInMemory {
|
||||
pruneCount := maxEntries >> 3
|
||||
if pruneCount <= 0 {
|
||||
|
||||
@@ -8,7 +8,7 @@ const HttpStatusCancelled = 499
|
||||
|
||||
var (
|
||||
MAJOR_VERSION = int32(3)
|
||||
MINOR_VERSION = int32(79)
|
||||
MINOR_VERSION = int32(80)
|
||||
VERSION_NUMBER = fmt.Sprintf("%d.%02d", MAJOR_VERSION, MINOR_VERSION)
|
||||
VERSION = sizeLimit + " " + VERSION_NUMBER
|
||||
COMMIT = ""
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util/mem"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util/mem"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -16,6 +16,8 @@ import (
|
||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||
)
|
||||
|
||||
var ErrNotFound = fmt.Errorf("not found")
|
||||
|
||||
func Post(url string, values url.Values) ([]byte, error) {
|
||||
r, err := GetGlobalHttpClient().PostForm(url, values)
|
||||
if err != nil {
|
||||
@@ -311,7 +313,10 @@ func ReadUrlAsStreamAuthenticated(fileUrl, jwt string, cipherKey []byte, isConte
|
||||
}
|
||||
defer CloseResponse(r)
|
||||
if r.StatusCode >= 400 {
|
||||
retryable = r.StatusCode == http.StatusNotFound || r.StatusCode >= 499
|
||||
if r.StatusCode == http.StatusNotFound {
|
||||
return true, fmt.Errorf("%s: %s: %w", fileUrl, r.Status, ErrNotFound)
|
||||
}
|
||||
retryable = r.StatusCode >= 499
|
||||
return retryable, fmt.Errorf("%s: %s", fileUrl, r.Status)
|
||||
}
|
||||
|
||||
@@ -477,4 +482,4 @@ func RetriedFetchChunkData(buffer []byte, urlStrings []string, cipherKey []byte,
|
||||
|
||||
return n, err
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package log_buffer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/mq_pb"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -11,11 +10,12 @@ import (
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/mq_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||
)
|
||||
|
||||
const BufferSize = 4 * 1024 * 1024
|
||||
const PreviousBufferCount = 3
|
||||
const BufferSize = 8 * 1024 * 1024
|
||||
const PreviousBufferCount = 32
|
||||
|
||||
type dataToFlush struct {
|
||||
startTime time.Time
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func GetNextDayTsNano(curTs int64) int64 {
|
||||
curTime := time.Unix(0, curTs)
|
||||
nextDay := curTime.AddDate(0, 0, 1).Truncate(24 * time.Hour)
|
||||
nextDayNano := nextDay.UnixNano()
|
||||
|
||||
return nextDayNano
|
||||
}
|
||||
Reference in New Issue
Block a user