Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 16 additions & 112 deletions misc/python/materialize/data_ingest/data_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,7 @@ def random_value(
rng.randint(-100, 100),
]
)
return literal(str(result)) if in_query else str(result)
# Fails: unterminated dollar-quoted string
# chars = string.printable
chars = string.ascii_letters + string.digits
Expand Down Expand Up @@ -1190,120 +1191,23 @@ def name(backend: Backend = Backend.MATERIALIZE) -> str:
# Sort to keep determinism for reproducible runs with specific seed
DATA_TYPES = sorted(list(all_subclasses(DataType)), key=repr)

# fastavro._schema_common.UnknownType: record
# bytea requires Python bytes type instead of str
DATA_TYPES_FOR_AVRO = sorted(
list(
set(DATA_TYPES)
- {
TextTextMap,
Jsonb,
Bytea,
Boolean,
UUID,
Interval,
IntList,
IntArray,
Time,
Date,
Timestamp,
TimestampTz,
MzTimestamp,
Oid,
Numeric,
Numeric383,
UInt2,
UInt4,
UInt8,
Float,
Double,
Char,
VarChar,
Int4Range,
Int8Range,
NumRange,
DateRange,
TsRange,
TsTzRange,
}
),
key=repr,
)

DATA_TYPES_FOR_MYSQL = sorted(
list(
set(DATA_TYPES)
- {
IntList,
IntArray,
UUID,
TextTextMap,
Interval,
Oid,
Jsonb,
Bytea,
Boolean,
Numeric,
Numeric383,
UInt2,
UInt4,
UInt8,
Char,
VarChar,
Int4Range,
Int8Range,
NumRange,
DateRange,
TsRange,
TsTzRange,
}
),
key=repr,
)

DATA_TYPES_FOR_SQL_SERVER = sorted(
list(
set(DATA_TYPES)
- {
IntList,
IntArray,
UUID,
TextTextMap,
Interval,
Oid,
Jsonb,
Bytea,
Boolean,
Numeric,
Numeric383,
UInt2,
UInt4,
UInt8,
Date,
Time,
Timestamp,
TimestampTz,
MzTimestamp,
Float,
Double,
Char,
VarChar,
Int4Range,
Int8Range,
NumRange,
DateRange,
TsRange,
TsTzRange,
}
),
key=repr,
)
# Explicit allowlists so that the actually exercised types are visible at a
# glance. A type belongs in one of these lists only if it maps to a native
# type on the backend, generated values fit the backend's ranges, and values
# roundtrip identically for the cross-backend result comparison.

# Types the Kafka executor can encode in Avro directly, no records, bytes or
# logical types.
DATA_TYPES_FOR_AVRO = [Int, Long, SmallInt, Text]

# Timestamp and Date are excluded because random_value generates years outside
# MySQL's supported ranges. MzTimestamp and TimestampTz have no MySQL type.
DATA_TYPES_FOR_MYSQL = [Double, Float, Int, Long, SmallInt, Text, Time]

DATA_TYPES_FOR_SQL_SERVER = [Int, Long, SmallInt, Text]

# MySQL doesn't support keys of unlimited size
DATA_TYPES_FOR_KEY = sorted(
list(set(DATA_TYPES_FOR_AVRO) - {Text, Bytea, IntList, IntArray, Float, Double}),
key=repr,
)
DATA_TYPES_FOR_KEY = [Int, Long, SmallInt]

NUMBER_TYPES = [
SmallInt,
Expand Down
35 changes: 14 additions & 21 deletions misc/python/materialize/data_ingest/definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,6 @@ class Keyspace(Enum):
EXISTING = 3


class Target(Enum):
KAFKA = 1
POSTGRES = 2
PRINT = 3


class Definition:
def generate(self, fields: list[Field]) -> Iterator[RowList]:
raise NotImplementedError
Expand All @@ -48,7 +42,6 @@ class Insert(Definition):
def __init__(self, count: Records, record_size: RecordSize):
self.count = count.value
self.record_size = record_size
self.current_key = 0

def max_key(self) -> int:
if self.count < 1:
Expand All @@ -63,19 +56,17 @@ def generate(self, fields: list[Field]) -> Iterator[RowList]:
f'Unexpected count {self.count}, doesn\'t make sense to generate "ALL" values'
)

for i in range(self.count):
if self.current_key >= self.count:
break

# Keys restart at 0 on every call so that a workload cycling through
# insert and delete phases inserts the same keys again in each cycle.
for key in range(self.count):
values = [
(
field.data_type.numeric_value(self.current_key)
field.data_type.numeric_value(key)
if field.is_key
else field.data_type.random_value(rng, self.record_size)
)
for field in fields
]
self.current_key += 1

yield RowList(
[
Expand All @@ -90,6 +81,8 @@ def generate(self, fields: list[Field]) -> Iterator[RowList]:

class Upsert(Definition):
def __init__(self, keyspace: Keyspace, count: Records, record_size: RecordSize):
if keyspace not in (Keyspace.SINGLE_VALUE, Keyspace.LARGE):
raise ValueError(f"Unsupported keyspace {keyspace}")
self.keyspace = keyspace
self.count = count.value
self.record_size = record_size
Expand All @@ -101,14 +94,14 @@ def generate(self, fields: list[Field]) -> Iterator[RowList]:
)

for i in range(self.count):
values = [
(
field.data_type.numeric_value(0)
if field.is_key
else field.data_type.random_value(rng, self.record_size)
)
for field in fields
]
values = []
for field in fields:
if not field.is_key:
values.append(field.data_type.random_value(rng, self.record_size))
elif self.keyspace == Keyspace.SINGLE_VALUE:
values.append(field.data_type.numeric_value(0))
else:
values.append(field.data_type.random_value(rng, self.record_size))

yield RowList(
[
Expand Down
15 changes: 14 additions & 1 deletion misc/python/materialize/data_ingest/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,14 @@ def execute(self, cur: psycopg.Cursor | pymysql.cursors.Cursor, query: str) -> N
else cur.execute(query)
)
except OperationalError:
# Can happen after Mz disruptions if we are running queries against Mz
# Can happen after Mz disruptions if we are running queries against
# Mz. Only queries against Mz can be retried this way, a query on
# an upstream connection must not be redirected to Mz.
if (
not isinstance(cur, psycopg.Cursor)
or cur.connection is not self.mz_conn
):
raise
print("Network error, retrying")
time.sleep(0.01)
self.reconnect()
Expand Down Expand Up @@ -136,6 +143,12 @@ def execute_with_retry_on_error(


class PrintExecutor(Executor):
def reconnect(self) -> None:
# Only prints transactions, needs no Materialize connection. The
# random service pick in the base implementation could also hit a
# service that is not running yet.
pass

def create(self, logging_exe: Any | None = None) -> None:
pass

Expand Down
18 changes: 11 additions & 7 deletions misc/python/materialize/data_ingest/workload.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,13 +162,14 @@ def __init__(
)
# Delete all records in a single transaction
delete_phase = TransactionDef(
[
size=TransactionSize.HUGE,
operations=[
Delete(
number_of_records=Records.ALL,
record_size=RecordSize.SMALL,
num=insert.max_key(),
)
]
],
)
self.cycle = [
insert_phase,
Expand All @@ -195,13 +196,14 @@ def __init__(
)
# Delete all records in a single transaction
delete_phase = TransactionDef(
[
size=TransactionSize.HUGE,
operations=[
Delete(
number_of_records=Records.ALL,
record_size=RecordSize.SMALL,
num=insert.max_key(),
)
]
],
)
self.cycle = [
insert_phase,
Expand Down Expand Up @@ -235,13 +237,14 @@ def __init__(
)
# Delete all records in a single transaction
delete_phase = TransactionDef(
[
size=TransactionSize.HUGE,
operations=[
Delete(
number_of_records=Records.ALL,
record_size=RecordSize.SMALL,
num=insert.max_key(),
)
]
],
)
self.cycle = [
insert_phase,
Expand All @@ -266,7 +269,8 @@ def __init__(
# ]


WORKLOADS = all_subclasses(Workload)
# Sort to keep determinism for reproducible runs with specific seed
WORKLOADS = sorted(all_subclasses(Workload), key=repr)


def execute_workload(
Expand Down
4 changes: 4 additions & 0 deletions test/data-ingest/mzcompose.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import time

from materialize import buildkite
from materialize.data_ingest import definition
from materialize.data_ingest.executor import (
KafkaExecutor,
MySqlExecutor,
Expand Down Expand Up @@ -191,6 +192,9 @@ def workflow_default(c: Composition, parser: WorkflowArgumentParser) -> None:

for i, workload_class in enumerate(workloads):
random.seed(args.seed)
# Row values come from a separate rng, seed it too so that runs
# with the same seed are fully reproducible.
definition.rng.seed(args.seed)
print(f"--- Testing workload {workload_class.__name__}")
workload = workload_class(args.azurite, c, mz_service, deploy_generation)
execute_workload(
Expand Down
Loading