Skip to content

Commit 985000e

Browse files
Add skip_record_validation_percents_by_type indexer config for sampled backfill validation (#1315)
Add `skip_record_validation_percents_by_type` indexer config for sampled backfill validation ## Why During large backfills of already-validated data, per-record JSON schema validation is wasted work. Every record walks the full schema (regex, enum, min/max, format, abstract-type discriminators) even though the source has already been validated upstream. Today there's no way to trade that cost for throughput. This adds a config option that skips per-record validation for a chosen percentage of records, per GraphQL type, while keeping a sampled slice validated as a canary so schema drift still surfaces. It's a sibling of the existing `skip_derived_indexing_type_updates` backfill knob and follows the same shape. Design notes: - `skip_record_validation_percents_by_type` maps a type name to a percent in `[0, 100]`. `0` (or an absent key) validates everything, `100` skips everything, and values in between sample. The value is the percentage *skipped*, so `90` skips 90% and validates 10%. - The skip decision compares a `Zlib.crc32` of the event id (`type:id@vversion`) against the configured percentage of the CRC32 space. Same event id, same decision, so a retry never flips a record between validated and skipped, even across pods. `String#hash` won't do here: its seed is per-process, so two pods would disagree. The `<= 0` and `>= 100` guards keep both endpoints exact, so no float boundary error can make a `0` skip a record or a `100` validate one. - The event envelope is always validated. Only the per-record schema walk gets sampled. - Skipping isn't silent. `Processor` counts skipped records per batch and logs one `RecordValidationSkipped` line with per-type counts, the same way it logs `ElasticGraphIndexingLatencies`. Logging per record would drown a `100` percent backfill in log lines, so the count is aggregated per batch. - Isolation via re-validation, not an error taxonomy: with validation off, malformed data surfaces as an exception while building the event's operations, and there is no bounded list of error types to enumerate. So when validation was skipped, `Factory#build` rescues anything and then runs the validation it skipped. If the validator faults the record, the caller gets a `FailedEventError` carrying **the validator's own message** - the same one it would have gotten on the validated path, PII-sanitized - and the rest of the batch still indexes. If the validator is happy, the error was never about the data (a schema artifact defect, or a bug), so the original exception is re-raised untouched. This is why no dedicated error type is needed for the missing/unknown abstract-type `__typename` case: `Inventor` has `required: ["__typename"]` plus a `oneOf` whose branches each pin `__typename` with a `const`, so a missing one fails `required` and an unknown one fails every branch. - `::Kernel.raise` is used for the re-raise because `Operation::Factory` overrides `raise` to stop the class *originating* errors instead of returning a `BuildResult`. Propagating an error that already escaped a collaborator is the opposite case, so the guard is deliberately bypassed, with a comment at the site and a spec that asserts the original class and message. Asserting on both matters: a plain `raise exception` would substitute the guard's own error and still satisfy a bare `raise_error`. - Fixed on the way: `build_failed_result` could itself raise while building the operations it attaches to a `FailedEventError`, masking the malformation it was trying to report. An exception in a `rescue` body isn't caught by that same `rescue`, so this had to hold before the re-validation path above could route unvalidated records through it. It turns out to be a live bug on the *validated* path too: `Widget` requires `cost`, so a `Widget` without it fails validation, reaches `build_failed_result`, and dies with `KeyError` building the derived `WidgetCurrency` target, whose id comes from `cost.currency`. The batch dies with it and the malformation is never reported. It now falls back to no operations and logs `FailedEventOperationBuildingFailure`; `FailedEventError#operations` is already documented as sometimes empty for exactly this reason. - With validation off, one class of failure is still not isolated: malformations that surface only when an operation is serialized for the datastore. `Update#to_datastore_bulk` is lazy and memoized, so the rollover index suffix and custom routing value computed in `Update#metadata` are evaluated inside `router.bulk`, after `build` has returned - out of reach of any rescue here. Such a batch produces no partial-failure response, so the queue redelivers all of its events and the malformed record fails them again on each retry until it drains to the DLQ. The config documentation says this specifically rather than implying a broader guarantee. Happy to take it on in a follow-up. The field defaults to `{}`, so nothing changes unless you set it. Additive and minor-release-safe. ## What Config: ```yaml indexer: skip_record_validation_percents_by_type: Widget: 90 # skip validation for 90% of Widget records, validate 10% Component: 100 # skip validation for all Component records ``` - `config.rb`: new `skip_record_validation_percents_by_type` JSON schema property (object, per-type number in `[0, 100]`, `additionalProperties: false`, default `{}`); `convert_values` coerces percents to `Float`. The `description:` leads with what the setting does, then the indexing-CPU tradeoff, then what remains unisolated. - `operation/factory.rb`: new `skip_validation?(type, event)` and the `CRC32_SPACE_PER_PERCENT` constant; `build` branches on the skip decision into `build_success_result` or `build_success_result_isolating_malformed_records`; `build_failed_result` no longer lets a second failure mask the first. `BuildResult` gains `type_with_skipped_validation`. - `processor.rb`: aggregate `RecordValidationSkipped` log per batch when any record was skipped. - `indexer.rb`: wire `config.skip_record_validation_percents_by_type` into the factory. - `record_preparer.rb`: unchanged. It carried a `RecordPreparer::UnknownTypeError` in an earlier revision of this PR; that's gone, and the file no longer appears in the diff. - RBS signatures updated for all of the above. - `elasticgraph-local` `config_schema.yaml`: regenerated via `script/update_config_artifacts`. ## Verification - `script/run_specs` (COVERAGE=1, real Elasticsearch): 5307 examples, 0 failures. `elasticgraph-indexer` on its own is 264 examples, 0 failures, at 100% line (592/592) and 100% branch (135/135). - `script/type_check` (Steep): no type errors. - `script/lint` (Standard Ruby): 899 files, no offenses. - `script/spellcheck` (codespell): clean. - `script/ci_parts/run_misc_checks`: `config_schema.yaml is up-to-date`, so no artifact drift. - `bundle exec rake schema_artifacts:check`: up to date (runtime config only, no artifact changes). - `bundle exec rake site:validate`: HTML-Proofer clean over 171 files, 149 runs, 0 failures. - Generated configuration reference checked by hand: the new field, its text and its examples all appear, and the generated example config still validates against the schema. New tests: - `config_spec.rb`: integer YAML percents coerce to `Float` (`90` to `90.0`), and out-of-range percents (`100.5`, `-0.1`) are rejected at config load. - `operation/factory_spec.rb`: a skipped type builds operations without record validation; non-skipped types still fail on bad records; envelope validation still runs for skipped types; partial sampling (stubbed `Zlib.crc32` for both branches); retry stability; the derived-index path under skip; a non-coercible `amount_cents` and an unknown abstract-type `__typename` each reported as a `FailedEventError` carrying the validator's message; an error the validator has no opinion about re-raised with its class and message intact; the validated path still propagating, so the rescue is provably gated; and a malformed event whose operation building also fails still reporting the malformation, with a warn log for the error it discarded. - `processor_spec.rb`: a batch with skips logs one `RecordValidationSkipped` with the right `count`/`counts_by_type`; a batch with no skips logs none. --------- Co-authored-by: Ashit Verma <ashit.kumar.verma@toasttab.com>
1 parent 2b9463c commit 985000e

11 files changed

Lines changed: 484 additions & 18 deletions

File tree

elasticgraph-indexer/lib/elastic_graph/indexer.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ def operation_factory
8585
record_preparer_factory: record_preparer_factory,
8686
logger: datastore_core.logger,
8787
skip_derived_indexing_type_updates: config.skip_derived_indexing_type_updates,
88+
skip_record_validation_percents_by_type: config.skip_record_validation_percents_by_type,
8889
configure_record_validator: nil
8990
)
9091
end

elasticgraph-indexer/lib/elastic_graph/indexer/config.rb

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
module ElasticGraph
1313
class Indexer
14-
class Config < Support::Config.define(:latency_slo_thresholds_by_timestamp_in_ms, :skip_derived_indexing_type_updates, :extension_modules)
14+
class Config < Support::Config.define(:latency_slo_thresholds_by_timestamp_in_ms, :skip_derived_indexing_type_updates, :skip_record_validation_percents_by_type, :extension_modules)
1515
json_schema at: "indexer",
1616
optional: false,
1717
description: "Configuration for indexing operations and metrics used by `elasticgraph-indexer`.",
@@ -43,15 +43,45 @@ class Config < Support::Config.define(:latency_slo_thresholds_by_timestamp_in_ms
4343
{"WidgetWorkspace" => ["ABC12345678"]}
4444
]
4545
},
46+
skip_record_validation_percents_by_type: {
47+
description: "Map of GraphQL type names to the percentage of records of that type whose per-record " \
48+
"JSON schema validation should be skipped. `0` (or an absent key) validates every record of the " \
49+
"type; `100` skips every record; values in between sample, and may be fractional. The decision is " \
50+
"deterministic per event id (`type:id@vversion`), so the same event makes the same choice on every " \
51+
"retry and on every indexer pod. The event envelope (op, id, type, version, json_schema_version, " \
52+
"latency_timestamps) is always validated, regardless of this setting.\n\n" \
53+
"With a large schema the per-record schema walk consumes a significant share of indexing CPU: every " \
54+
"record is checked against every regex, enum, min/max, format, and abstract-type discriminator " \
55+
"defined for its type. Skipping it trades that check for throughput, which is worthwhile when " \
56+
"backfilling data that was already validated upstream. Leaving a percentage of records validated " \
57+
"keeps a canary in place so schema drift still surfaces.\n\n" \
58+
"Note: skipping validation makes malformed-data detection later and less precise. A malformation " \
59+
"found while building an event's operations is still reported as an isolated event failure, carrying " \
60+
"the message validation itself would have produced. But one found only while serializing an " \
61+
"operation for the datastore (an unparsable rollover index timestamp, or a missing custom routing " \
62+
"field) raises an error that fails the entire batch, including the well-formed events in it. Since " \
63+
"such a batch produces no partial-failure response, the queue redelivers all of its events, and the " \
64+
"malformed record fails them again on each retry until it is drained to the dead letter queue. Leave " \
65+
"this empty for live-traffic ingestion.",
66+
type: "object",
67+
patternProperties: {/^[A-Z]\w*$/.source => {type: "number", minimum: 0, maximum: 100}},
68+
additionalProperties: false,
69+
default: {}, # : untyped
70+
examples: [
71+
{}, # : untyped
72+
{"Widget" => 90, "Component" => 100}
73+
]
74+
},
4675
extension_modules: Support::Config::EXTENSION_MODULE_SCHEMA
4776
}
4877

4978
private
5079

51-
def convert_values(skip_derived_indexing_type_updates:, latency_slo_thresholds_by_timestamp_in_ms:, extension_modules:)
80+
def convert_values(skip_derived_indexing_type_updates:, latency_slo_thresholds_by_timestamp_in_ms:, skip_record_validation_percents_by_type:, extension_modules:)
5281
{
5382
skip_derived_indexing_type_updates: skip_derived_indexing_type_updates.transform_values(&:to_set),
5483
latency_slo_thresholds_by_timestamp_in_ms: latency_slo_thresholds_by_timestamp_in_ms,
84+
skip_record_validation_percents_by_type: skip_record_validation_percents_by_type.transform_values(&:to_f),
5585
extension_modules: SchemaArtifacts::RuntimeMetadata::ExtensionLoader.load_component_extensions(extension_modules)
5686
}
5787
end

elasticgraph-indexer/lib/elastic_graph/indexer/operation/factory.rb

Lines changed: 80 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
require "elastic_graph/indexer/record_preparer"
1414
require "elastic_graph/support/json_schema/validator_factory"
1515
require "elastic_graph/support/memoizable_data"
16+
require "zlib"
1617

1718
module ElasticGraph
1819
class Indexer
@@ -23,6 +24,7 @@ class Factory < Support::MemoizableData.define(
2324
:record_preparer_factory,
2425
:logger,
2526
:skip_derived_indexing_type_updates,
27+
:skip_record_validation_percents_by_type,
2628
:configure_record_validator
2729
)
2830
def build(event)
@@ -40,15 +42,44 @@ def build(event)
4042
return build_failed_result(event, "event payload", error_message)
4143
end
4244

43-
failed_result = validate_record_returning_failure(event, selected_json_schema_version)
44-
failed_result || BuildResult.success(build_all_operations_for(
45-
event,
46-
record_preparer_factory.for_json_schema_version(selected_json_schema_version)
47-
))
45+
graphql_type_name = event.fetch("type")
46+
47+
if skip_validation?(graphql_type_name, event)
48+
build_success_result_isolating_malformed_records(event, graphql_type_name, selected_json_schema_version)
49+
else
50+
validate_record_returning_failure(event, graphql_type_name, selected_json_schema_version) ||
51+
build_success_result(event, selected_json_schema_version, type_with_skipped_validation: nil)
52+
end
4853
end
4954

5055
private
5156

57+
def build_success_result(event, selected_json_schema_version, type_with_skipped_validation:)
58+
BuildResult.success(
59+
build_all_operations_for(event, record_preparer_factory.for_json_schema_version(selected_json_schema_version)),
60+
type_with_skipped_validation: type_with_skipped_validation
61+
)
62+
end
63+
64+
# Builds the operations for an event whose per-record validation we skipped.
65+
#
66+
# Skipping validation means malformed data the schema walk would have rejected surfaces instead as
67+
# an exception while we build the event's operations, and there is no bounded list of error types to
68+
# enumerate. So we rescue anything and then run the validation we skipped: the validator tells us
69+
# whether the data was actually bad, and if it was, hands the caller the same pinpointed message it
70+
# would have gotten had we validated up front. A clean bill of health from the validator means the
71+
# error was never about the data (a schema artifact defect, or a bug) and must not be swallowed.
72+
def build_success_result_isolating_malformed_records(event, graphql_type_name, selected_json_schema_version)
73+
build_success_result(event, selected_json_schema_version, type_with_skipped_validation: graphql_type_name)
74+
rescue => exception
75+
failed_result = validate_record_returning_failure(event, graphql_type_name, selected_json_schema_version)
76+
# `raise` is overridden below to stop this class from *originating* an error instead of returning a
77+
# `BuildResult`. Here we propagate one that already escaped a collaborator, which is exactly what
78+
# happens without this rescue, so we deliberately bypass that guard.
79+
::Kernel.raise(exception) unless failed_result
80+
failed_result
81+
end
82+
5283
def select_json_schema_version(event)
5384
available_json_schema_versions = schema_artifacts.available_json_schema_versions
5485

@@ -117,23 +148,57 @@ def prepare_event(event)
117148
event.merge("record" => event["record"].merge("id" => event.fetch("id")))
118149
end
119150

120-
def validate_record_returning_failure(event, selected_json_schema_version)
151+
def validate_record_returning_failure(event, graphql_type_name, selected_json_schema_version)
121152
record = event.fetch("record")
122-
graphql_type_name = event.fetch("type")
123153
validator = validator(graphql_type_name, selected_json_schema_version)
124154

125155
if (error_message = validator.validate_with_error_message(record))
126156
build_failed_result(event, "#{graphql_type_name} record", error_message)
127157
end
128158
end
129159

160+
# `Zlib.crc32` returns a value in `[0, 2**32)`. Pre-dividing that space by 100 lets us test a
161+
# configured percent with a single multiply instead of dividing on every event.
162+
CRC32_SPACE_PER_PERCENT = (1 << 32) / 100.0
163+
164+
# Decides whether to skip per-record validation for `event` of `type`. The decision is
165+
# deterministic per event id: a stable `Zlib.crc32` of `EventID#to_s` maps each event to a
166+
# point in the CRC32 space, and we skip validation for the configured percentage of that
167+
# space. Same event id => same decision across pods and retries, so retries never flip a
168+
# record between validated and skipped. `String#hash` is unsuitable here, as `RUBY_HASH_SEED`
169+
# is per-process. The `<= 0` and `>= 100` guards keep the endpoints exact, so no float
170+
# boundary error can make a `0` percent skip a record or a `100` percent validate one.
171+
def skip_validation?(type, event)
172+
percent = skip_record_validation_percents_by_type[type]
173+
return false if percent.nil? || percent <= 0
174+
return true if percent >= 100
175+
::Zlib.crc32(EventID.from_event(event).to_s) < percent * CRC32_SPACE_PER_PERCENT
176+
end
177+
130178
def build_failed_result(event, payload_description, validation_message)
131179
message = "Malformed #{payload_description}. #{validation_message}"
132180

133181
# Here we use the `RecordPreparer::Identity` record preparer because we may not have a valid JSON schema
134182
# version number in this case (which is usually required to get a `RecordPreparer` from the factory), and
135183
# we won't wind up using the record preparer for real on these operations, anyway.
136-
operations = build_all_operations_for(event, RecordPreparer::Identity)
184+
#
185+
# Building operations for an event we already know is malformed can itself fail--for example, when the
186+
# record omits a field an update target derives its id from. Reporting what was malformed matters more
187+
# than reporting the operations we would have run, and `FailedEventError#operations` is documented to
188+
# sometimes be empty for exactly this reason, so we fall back to no operations rather than let a second
189+
# failure mask the first.
190+
operations = begin
191+
build_all_operations_for(event, RecordPreparer::Identity)
192+
rescue => exception
193+
logger.warn({
194+
"message_type" => "FailedEventOperationBuildingFailure",
195+
"message_id" => event["message_id"],
196+
"event_id" => EventID.from_event(event).to_s,
197+
"error_class" => exception.class.name,
198+
"error_message" => exception.message
199+
})
200+
[] # : ::Array[_Operation]
201+
end
137202

138203
BuildResult.failure(FailedEventError.new(event: event, operations: operations.to_set, main_message: message))
139204
end
@@ -192,14 +257,17 @@ def raise(*args)
192257
# Return value from `build` that indicates what happened.
193258
# - If it was successful, `operations` will be a non-empty array of operations and `failed_event_error` will be nil.
194259
# - If there was a validation issue, `operations` will be an empty array and `failed_event_error` will be non-nil.
195-
BuildResult = ::Data.define(:operations, :failed_event_error) do
260+
# - `type_with_skipped_validation` names the event's GraphQL type when per-record validation was skipped
261+
# (via `skip_record_validation_percents_by_type`), and is nil otherwise. `Processor` aggregates this
262+
# for observability.
263+
BuildResult = ::Data.define(:operations, :failed_event_error, :type_with_skipped_validation) do
196264
# @implements BuildResult
197-
def self.success(operations)
198-
new(operations, nil)
265+
def self.success(operations, type_with_skipped_validation: nil)
266+
new(operations, nil, type_with_skipped_validation)
199267
end
200268

201269
def self.failure(failed_event_error)
202-
new([], failed_event_error)
270+
new([], failed_event_error, nil)
203271
end
204272
end
205273
end

elasticgraph-indexer/lib/elastic_graph/indexer/processor.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ def process_returning_failures(events, refresh_indices: false)
4545

4646
factory_results = factory_results_by_event.values
4747

48+
log_skipped_record_validations(factory_results)
49+
4850
bulk_result = @datastore_router.bulk(factory_results.flat_map(&:operations), refresh: refresh_indices)
4951
successful_operations = bulk_result.successful_operations(check_failures: false)
5052

@@ -62,6 +64,26 @@ def process_returning_failures(events, refresh_indices: false)
6264

6365
private
6466

67+
# Emits a single aggregate log line per batch when any records had their per-record validation
68+
# skipped (via `skip_record_validation_percents_by_type`). Skipping a safety check should never be
69+
# silent, but per-record logging would be untenable at backfill scale (a `100` percent is one line
70+
# per record), so we tally by type and log once. Mirrors the batch-level
71+
# `ElasticGraphIndexingLatencies` log.
72+
#
73+
# A skipped record that then failed is deliberately absent from the tally: `Operation::Factory`
74+
# re-runs the skipped validation when building its operations raises, so such a record ended up
75+
# validated after all, and is already reported as a failed event.
76+
def log_skipped_record_validations(factory_results)
77+
counts_by_type = factory_results.filter_map(&:type_with_skipped_validation).tally
78+
return if counts_by_type.empty?
79+
80+
@logger.info({
81+
"message_type" => "RecordValidationSkipped",
82+
"count" => counts_by_type.values.sum,
83+
"counts_by_type" => counts_by_type
84+
})
85+
end
86+
6587
def categorize_failures(failures, events)
6688
source_event_versions_by_cluster_by_op = @datastore_router.source_event_versions_in_index(
6789
failures.flat_map { |f| f.versioned_operations.to_a }

elasticgraph-indexer/sig/elastic_graph/indexer/config.rbs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,19 @@ module ElasticGraph
55

66
attr_reader latency_slo_thresholds_by_timestamp_in_ms: ::Hash[::String, ::Integer]
77
attr_reader skip_derived_indexing_type_updates: ::Hash[::String, ::Set[::String]]
8+
attr_reader skip_record_validation_percents_by_type: ::Hash[::String, ::Float]
89
attr_reader extension_modules: ::Array[::Module]
910

1011
def initialize: (
1112
?latency_slo_thresholds_by_timestamp_in_ms: ::Hash[::String, ::Integer],
1213
?skip_derived_indexing_type_updates: ::Hash[::String, ::Set[::String]],
14+
?skip_record_validation_percents_by_type: ::Hash[::String, ::Float],
1315
?extension_modules: ::Array[::Module]) -> void
1416

1517
def with: (
1618
?latency_slo_thresholds_by_timestamp_in_ms: ::Hash[::String, ::Integer],
1719
?skip_derived_indexing_type_updates: ::Hash[::String, ::Set[::String]],
20+
?skip_record_validation_percents_by_type: ::Hash[::String, ::Float],
1821
?extension_modules: ::Array[::Module]) -> Config
1922

2023
def self.members: () -> ::Array[::Symbol]
@@ -26,6 +29,7 @@ module ElasticGraph
2629
def convert_values: (
2730
latency_slo_thresholds_by_timestamp_in_ms: untyped,
2831
skip_derived_indexing_type_updates: untyped,
32+
skip_record_validation_percents_by_type: untyped,
2933
extension_modules: untyped
3034
) -> ::Hash[::Symbol, untyped]
3135

elasticgraph-indexer/sig/elastic_graph/indexer/operation/factory.rbs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ module ElasticGraph
1111
attr_reader record_preparer_factory: RecordPreparer::Factory
1212
attr_reader logger: ::Logger
1313
attr_reader skip_derived_indexing_type_updates: ::Hash[::String, ::Set[::String]]
14+
attr_reader skip_record_validation_percents_by_type: ::Hash[::String, ::Float]
1415
attr_reader configure_record_validator: (^(validatorFactory) -> validatorFactory)?
1516

1617
def initialize: (
@@ -19,6 +20,7 @@ module ElasticGraph
1920
record_preparer_factory: RecordPreparer::Factory,
2021
logger: ::Logger,
2122
skip_derived_indexing_type_updates: ::Hash[::String, ::Set[::String]],
23+
skip_record_validation_percents_by_type: ::Hash[::String, ::Float],
2224
configure_record_validator: (^(validatorFactory) -> validatorFactory)?
2325
) -> void
2426

@@ -28,6 +30,7 @@ module ElasticGraph
2830
?record_preparer_factory: RecordPreparer::Factory,
2931
?logger: ::Logger,
3032
?skip_derived_indexing_type_updates: ::Hash[::String, ::Set[::String]],
33+
?skip_record_validation_percents_by_type: ::Hash[::String, ::Float],
3134
?configure_record_validator: (^(validatorFactory) -> validatorFactory)?
3235
) -> instance
3336
end
@@ -44,7 +47,11 @@ module ElasticGraph
4447

4548
def select_json_schema_version: (event) { (BuildResult) -> bot } -> (::Integer | bot)
4649
def prepare_event: (event) -> event
47-
def validate_record_returning_failure: (event, ::Integer) -> BuildResult?
50+
def validate_record_returning_failure: (event, ::String, ::Integer) -> BuildResult?
51+
def build_success_result: (event, ::Integer, type_with_skipped_validation: ::String?) -> BuildResult
52+
def build_success_result_isolating_malformed_records: (event, ::String, ::Integer) -> BuildResult
53+
CRC32_SPACE_PER_PERCENT: ::Float
54+
def skip_validation?: (::String, event) -> bool
4855
def build_failed_result: (event, ::String, ::String) -> BuildResult
4956
def build_all_operations_for: (event, _RecordPreparer) -> ::Array[_Operation]
5057
def index_definitions_for: (::String) -> ::Array[DatastoreCore::_IndexDefinition]
@@ -53,15 +60,17 @@ module ElasticGraph
5360
class BuildResult
5461
attr_reader operations: ::Array[_Operation]
5562
attr_reader failed_event_error: FailedEventError?
63+
attr_reader type_with_skipped_validation: ::String?
5664

57-
def initialize: (::Array[_Operation], FailedEventError?) -> void
65+
def initialize: (::Array[_Operation], FailedEventError?, ::String?) -> void
5866

5967
def with: (
6068
?operations: ::Array[_Operation],
61-
?failed_event_error: FailedEventError?
69+
?failed_event_error: FailedEventError?,
70+
?type_with_skipped_validation: ::String?
6271
) -> BuildResult
6372

64-
def self.success: (::Array[_Operation]) -> BuildResult
73+
def self.success: (::Array[_Operation], ?type_with_skipped_validation: ::String?) -> BuildResult
6574
def self.failure: (FailedEventError) -> BuildResult
6675
end
6776
end

elasticgraph-indexer/sig/elastic_graph/indexer/processor.rbs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ module ElasticGraph
2020
@indexing_latency_slo_thresholds_by_timestamp_in_ms: ::Hash[::String, ::Integer]
2121
@clock: singleton(::Time)
2222

23+
def log_skipped_record_validations: (::Array[Operation::Factory::BuildResult]) -> void
2324
def categorize_failures: (::Array[FailedEventError], ::Array[event]) -> ::Array[FailedEventError]
2425
def calculate_latency_metrics: (::Array[_Operation], ::Array[Operation::Result]) -> void
2526
end

0 commit comments

Comments
 (0)