Skip to content

metadata access counter; closes #3472 - #3475

Open
petrelharp wants to merge 3 commits into
tskit-dev:mainfrom
petrelharp:metadata_cache
Open

metadata access counter; closes #3472#3475
petrelharp wants to merge 3 commits into
tskit-dev:mainfrom
petrelharp:metadata_cache

Conversation

@petrelharp

Copy link
Copy Markdown
Contributor

As lengthily discussed in #3472, if top-level metadata is large then doing ts.metadata a lot of times can be verrrrry slow, because of both the decoding and making a copy of the data (in roughly equal measure). Seeing no straightforward way around this, I'm proposing this stopgap: if someone does TreeSequence.metadata or TableCollection.metadata 20 times then they get a Warning suggesting they do something else. The warning only occurs once.

@codecov

codecov Bot commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.68%. Comparing base (12b196a) to head (9592254).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3475   +/-   ##
=======================================
  Coverage   91.67%   91.68%           
=======================================
  Files          38       38           
  Lines       32188    32213   +25     
  Branches     5151     5157    +6     
=======================================
+ Hits        29509    29534   +25     
  Misses       2346     2346           
  Partials      333      333           
Flag Coverage Δ
C 82.23% <ø> (ø)
c-python 77.54% <ø> (ø)
python-tests 96.44% <100.00%> (+0.01%) ⬆️
python-tests-no-jit 33.20% <37.03%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
Python API 98.72% <100.00%> (+<0.01%) ⬆️
Python C interface 91.23% <ø> (ø)
C library 91.24% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@petrelharp
petrelharp requested a review from nspope August 7, 2026 17:32
@petrelharp
petrelharp marked this pull request as ready for review August 7, 2026 17:49

@nspope nspope left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks fine by me, some minor suggestions

Comment thread python/tskit/tables.py Outdated
def metadata(self):
if self._metadata_access_counter is not None:
self._metadata_access_counter += 1
if self._metadata_access_counter > 20:

@nspope nspope Aug 10, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than hardcoding 20 in multiple places, how about defining METADATA_WARNING_COUNT in __init__.py alongside the other constants, and importing it for use here (and in the tests)?

Comment thread python/tests/test_metadata.py Outdated
# should warn after 20 times and no more after that
md = t.metadata
for _ in range(19):
print(_, t._metadata_access_counter)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stray print?

Comment thread python/tskit/tables.py Outdated
def metadata(self):
if self._metadata_access_counter is not None:
# can't set things directly on this immutable class
builtins.object.__setattr__(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need builtins here? object exists without importing, so I think you can just do object.__setattr__ here (and elsewhere) and remove the import. (That's done elsewhere, for example with the _initialised member of the same class)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm: I did it that way because of this bit. Looks like you're right, though, don't need it.

@nspope

nspope commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Here's one thing which isn't wrong per se but is a bit messy: checking equality may raise the counters of the involved tree sequences/tables, to a degree that varies depending on what's compared. For example,

import msprime
import tskit

ts = msprime.sim_ancestry(3, sequence_length=1, random_seed=1024)
tables = ts.dump_tables()
tables.metadata_schema = tskit.MetadataSchema.permissive_json()
tables.metadata = {"a": "bcde", "x": [1, 2, 3, 4]}
ts = tables.tree_sequence()

# mutable vs mutable, same metadata (+0, +0)
t1, t2 = ts.dump_tables(), ts.dump_tables()
t1.assert_equals(t2)
print(t1._metadata_access_counter, t2._metadata_access_counter)  # (0, 0)

# mutable vs mutable, diff metadata (+2, +2)
t3, t4 = ts.dump_tables(), ts.dump_tables()
t4.metadata = {"a": "zzz"}
try:
    t3.assert_equals(t4)
except AssertionError:
    pass
print(t3._metadata_access_counter, t4._metadata_access_counter)  # (2, 2)

# immutable vs immutable, same metadata (+1, +1)
t5, t6 = ts.tables, ts.dump_tables().tree_sequence().tables
t5.assert_equals(t6)
print(t5._metadata_access_counter, t6._metadata_access_counter) # (1, 1)

I'm not sure there's an easy way to avoid this though (and it's just cosmetic, would just add some unavoidable noise when comparing things in a loop for instance)

@petrelharp

Copy link
Copy Markdown
Contributor Author

Good point about comparisons. I don't think I'm worried about it, though?

Thanks for the input! Will get on it.

@jeromekelleher jeromekelleher left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree this is a good least-bad option here given the dependence on the return value being a mutable copy in downstream code. I think we should make the interface "official" though and make it flexible, as there will be cases where this is really annoying and we want to turn it off.

Comment thread python/tskit/__init__.py
]

#: Threshhold for warning about multiple top-level metadata use
METADATA_WARNING_COUNT = 20

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this is part of the public API, I think we should give it a more descriptive name and document how to disable it, like

#: Threshhold for warning about multiple top-level metadata use. To disable, set to a large number
# e.g. ``tskit.METADATA_ACCESS_WARNING_THRESHOLD = 2**32``.
METADATA_ACCESS_WARNING_THRESHOLD = 20

We should add a short description in the docs somewhere also so that it's findable.


def test_warns_tree_sequence(self):
t = self.get_example().tree_sequence()
self.check_warns(t)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we need test_warnts_immutable_tables also?

Like

ts = self.get_example().tree_sequence()
self.check_warns(ts.tables)

Also we should test that setting the module variable works so that people can control this. There'll be pytest functionality for this (i.e., setting the module var temporarily)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants