/
githubmirror
/
etcd
Обзор
Документация
Войти
/
githubmirror
/
etcd
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
server/etcdserver/txn/range.go
234 строки
7 KB
Nont
Range with --keys-only retrieves from memory when possible
25 май 2026, 18:00
25 май 2026, 18:00
7624a8a
Код
Авторство
О чём код?
// Copyright 2025 The etcd Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package txn import ( "bytes" "context" "math" "sort" "time" "go.uber.org/zap" pb "go.etcd.io/etcd/api/v3/etcdserverpb" "go.etcd.io/etcd/api/v3/mvccpb" "go.etcd.io/etcd/pkg/v3/traceutil" "go.etcd.io/etcd/server/v3/storage/mvcc" ) // Count returns the number of keys in [key, rangeEnd) at the given revision. // A revision of 0 reads at the latest revision. func Count(ctx context.Context, lg *zap.Logger, kv mvcc.KV, key, rangeEnd []byte, revision int64) (int64, error) { resp, _, err := Range(ctx, lg, kv, &pb.RangeRequest{ Key: key, RangeEnd: rangeEnd, Revision: revision, CountOnly: true, }, false) if err != nil { return 0, err } return resp.Count, nil } func Range(ctx context.Context, lg *zap.Logger, kv mvcc.KV, r *pb.RangeRequest, withTotalCount bool) (resp *pb.RangeResponse, trace *traceutil.Trace, err error) { ctx, trace = traceutil.EnsureTrace(ctx, lg, "range") defer func(start time.Time) { success := err == nil RangeSecObserve(success, time.Since(start)) }(time.Now()) txnRead := kv.Read(mvcc.ConcurrentReadTxMode, trace) defer txnRead.End() resp, err = executeRange(ctx, lg, txnRead, r, withTotalCount) return resp, trace, err } func executeRange(ctx context.Context, lg *zap.Logger, txnRead mvcc.TxnRead, r *pb.RangeRequest, withTotalCount bool) (*pb.RangeResponse, error) { trace := traceutil.Get(ctx) limit := rangeLimit(r) ro := mvcc.RangeOptions{ Limit: limit, Rev: r.Revision, CountOnly: r.CountOnly, FastKeysOnly: r.KeysOnly && r.SortTarget != pb.RangeRequest_VALUE, WithTotalCount: withTotalCount, } rr, err := txnRead.Range(ctx, r.Key, mkGteRange(r.RangeEnd), ro) if err != nil { return nil, err } filterRangeResults(rr, r) sortRangeResults(rr, r, lg) trace.Step("filter and sort the key-value pairs") resp := asembleRangeResponse(rr, r) trace.Step("assemble the response") return resp, nil } func rangeLimit(r *pb.RangeRequest) int64 { limit := r.Limit if !IsDefaultOrdering(r.SortTarget, r.SortOrder) || HasRevisionFilters(r) { limit = 0 } if limit > 0 && limit < math.MaxInt64 { limit = limit + 1 } return limit } func IsDefaultOrdering(sortTarget pb.RangeRequest_SortTarget, sortOrder pb.RangeRequest_SortOrder) bool { // Since current mvcc.Range implementation returns results // sorted by keys in lexiographically ascending order, // don't re-sort when target is 'KEY' and order is ASCEND return sortOrder == pb.RangeRequest_NONE || (sortTarget == pb.RangeRequest_KEY && sortOrder == pb.RangeRequest_ASCEND) } func HasRevisionFilters(r *pb.RangeRequest) bool { return r.MinModRevision != 0 || r.MaxModRevision != 0 || r.MinCreateRevision != 0 || r.MaxCreateRevision != 0 } func filterRangeResults(rr *mvcc.RangeResult, r *pb.RangeRequest) { if r.MaxModRevision != 0 { pruneKVs(rr, func(kv *mvccpb.KeyValue) bool { return kv.ModRevision > r.MaxModRevision }) } if r.MinModRevision != 0 { pruneKVs(rr, func(kv *mvccpb.KeyValue) bool { return kv.ModRevision < r.MinModRevision }) } if r.MaxCreateRevision != 0 { pruneKVs(rr, func(kv *mvccpb.KeyValue) bool { return kv.CreateRevision > r.MaxCreateRevision }) } if r.MinCreateRevision != 0 { pruneKVs(rr, func(kv *mvccpb.KeyValue) bool { return kv.CreateRevision < r.MinCreateRevision }) } } func sortRangeResults(rr *mvcc.RangeResult, r *pb.RangeRequest, lg *zap.Logger) { sortOrder := r.SortOrder if r.SortTarget != pb.RangeRequest_KEY && sortOrder == pb.RangeRequest_NONE { sortOrder = pb.RangeRequest_ASCEND } if !IsDefaultOrdering(r.SortTarget, sortOrder) { var sorter sort.Interface switch { case r.SortTarget == pb.RangeRequest_KEY: sorter = &kvSortByKey{&kvSort{rr.KVs}} case r.SortTarget == pb.RangeRequest_VERSION: sorter = &kvSortByVersion{&kvSort{rr.KVs}} case r.SortTarget == pb.RangeRequest_CREATE: sorter = &kvSortByCreate{&kvSort{rr.KVs}} case r.SortTarget == pb.RangeRequest_MOD: sorter = &kvSortByMod{&kvSort{rr.KVs}} case r.SortTarget == pb.RangeRequest_VALUE: sorter = &kvSortByValue{&kvSort{rr.KVs}} default: lg.Panic("unexpected sort target", zap.Int32("sort-target", int32(r.SortTarget))) } switch { case sortOrder == pb.RangeRequest_ASCEND: sort.Sort(sorter) case sortOrder == pb.RangeRequest_DESCEND: sort.Sort(sort.Reverse(sorter)) } } } func asembleRangeResponse(rr *mvcc.RangeResult, r *pb.RangeRequest) *pb.RangeResponse { resp := &pb.RangeResponse{Header: &pb.ResponseHeader{}} if r.Limit > 0 && len(rr.KVs) > int(r.Limit) { rr.KVs = rr.KVs[:r.Limit] resp.More = true } resp.Header.Revision = rr.Rev resp.Count = int64(rr.Count) resp.Kvs = make([]*mvccpb.KeyValue, len(rr.KVs)) for i := range rr.KVs { if r.KeysOnly { rr.KVs[i].Value = nil } resp.Kvs[i] = rr.KVs[i] } return resp } func checkRange(rv mvcc.ReadView, req *pb.RangeRequest) error { switch { case req.Revision == 0: return nil case req.Revision > rv.Rev(): return mvcc.ErrFutureRev case req.Revision < rv.FirstRev(): return mvcc.ErrCompacted } return nil } func pruneKVs(rr *mvcc.RangeResult, isPrunable func(*mvccpb.KeyValue) bool) { j := 0 for i := range rr.KVs { rr.KVs[j] = rr.KVs[i] if !isPrunable(rr.KVs[i]) { j++ } } rr.KVs = rr.KVs[:j] } type kvSort struct{ kvs []*mvccpb.KeyValue } func (s *kvSort) Swap(i, j int) { t := s.kvs[i] s.kvs[i] = s.kvs[j] s.kvs[j] = t } func (s *kvSort) Len() int { return len(s.kvs) } type kvSortByKey struct{ *kvSort } func (s *kvSortByKey) Less(i, j int) bool { return bytes.Compare(s.kvs[i].Key, s.kvs[j].Key) < 0 } type kvSortByVersion struct{ *kvSort } func (s *kvSortByVersion) Less(i, j int) bool { return (s.kvs[i].Version - s.kvs[j].Version) < 0 } type kvSortByCreate struct{ *kvSort } func (s *kvSortByCreate) Less(i, j int) bool { return (s.kvs[i].CreateRevision - s.kvs[j].CreateRevision) < 0 } type kvSortByMod struct{ *kvSort } func (s *kvSortByMod) Less(i, j int) bool { return (s.kvs[i].ModRevision - s.kvs[j].ModRevision) < 0 } type kvSortByValue struct{ *kvSort } func (s *kvSortByValue) Less(i, j int) bool { return bytes.Compare(s.kvs[i].Value, s.kvs[j].Value) < 0 }