jaegertracing/jaeger · error
service/operation reads require a searcher, but this storage
Error message
service/operation reads require a searcher, but this storage was constructed for write-only use
What it means
errNoSearcher is an unexported error returned by the read methods (getServices/getOperations) of ServiceOperationStorage when the storage was constructed without a searcher, i.e. in write-only mode as described on NewServiceOperationStorage. It converts what would otherwise be a nil-pointer panic on the searcher into a clear, actionable configuration error.
Source
Thrown at internal/storage/v2/elasticsearch/tracestore/core/service_operation.go:38
"github.com/jaegertracing/jaeger/internal/storage/v2/elasticsearch/tracestore/core/dbmodel"
)
const (
serviceName = "serviceName"
// serviceCacheTTLDefault is how long a written service:operation pair is
// remembered when the configuration does not say.
serviceCacheTTLDefault = 12 * time.Hour
operationsAggregation = "distinct_operations"
servicesAggregation = "distinct_services"
)
// errNoSearcher is returned by the read methods (getServices/getOperations) when
// the storage was constructed without a searcher — the write-only case described
// on NewServiceOperationStorage. It turns that misconfiguration into a clear
// error rather than a nil-pointer panic.
var errNoSearcher = errors.New("service/operation reads require a searcher, but this storage was constructed for write-only use")
// ServiceOperationStorage stores service to operation pairs.
type ServiceOperationStorage struct {
searcher esclient.Searcher
logger *zap.Logger
serviceCache cache.Cache
}
// NewServiceOperationStorage returns a new ServiceOperationStorage. searcher is
// used only by the read methods (getServices/getOperations); a write-only instance
// (the SpanWriter) may pass a nil searcher. The write side builds documents via
// toUpsertItem and commits the cache via commitToCache — it does not write directly.
// A cacheTTL of zero selects serviceCacheTTLDefault.
func NewServiceOperationStorage(
searcher esclient.Searcher,
logger *zap.Logger,
cacheTTL time.Duration,
) *ServiceOperationStorage {View on GitHub (pinned to 806f444784)
Solutions
- Construct the storage with a non-nil searcher if you need service/operation reads.
- Use a separate, read-capable storage instance for the query path.
- If the deployment is intentionally write-only, stop routing getServices/getOperations calls to this instance.
Example fix
// before so := core.NewServiceOperationStorage(client, logger, nil /* write-only */) services, err := so.GetServices(ctx) // errNoSearcher // after so := core.NewServiceOperationStorage(client, logger, searcher) services, err := so.GetServices(ctx)
Defensive patterns
Strategy: validation
Validate before calling
// Before exposing read APIs, confirm the storage was built with a searcher:
if so == nil || !readCapable {
return errors.New("service/operation reads require a searcher")
} Try / catch
services, err := soStorage.GetServices(ctx)
if err != nil && strings.Contains(err.Error(), "write-only use") {
// misconfigured instance: route reads to a read-capable storage
} Prevention
- Match storage construction to role: writers get a nil searcher, readers must supply one.
- Keep separate instances for read and write roles instead of sharing one.
- Assert searcher non-nil in tests for any component that exposes read endpoints.
When it happens
Trigger: Constructing the storage via NewServiceOperationStorage with a nil searcher (write-only use) and then calling getServices or getOperations (exposed through the read API). TestServiceOperationStorage_ReadWithoutSearcher covers exactly this path.
Common situations: Deploying a writer-only storage factory (e.g. remote-storage span-writer roles) and accidentally exposing it to a query component; sharing one storage instance between writer and reader roles when only one was wired with a searcher.
Related errors
- invalid version %q: expected one of es7, es8, es9, os1, os2,
- unsupported version %d: set 0 to auto-detect, or use 7/8/9 (
- deprecated ES rotation flags (%s) are no longer supported; m
- unrecognized poison_pill_handling %q: valid values are %q an
- invalid server URL %q: %w
AI-assisted analysis of jaegertracing/jaeger@806f444784 (2026-09-01).
Data as JSON: /api/errors/630b3781c16a8d44.
Report an issue: GitHub.