Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[utils] Implement ShardedMap #248

Merged
merged 8 commits into from
Nov 23, 2020
Merged

[utils] Implement ShardedMap #248

merged 8 commits into from
Nov 23, 2020

Conversation

patrick-ogrady
Copy link
Contributor

@patrick-ogrady patrick-ogrady commented Nov 23, 2020

Related: https://github.com/coinbase/rosetta-sdk-go/tree/patrick/priority-lock

This PR adds support for a ShardedMap that enables the caller to concurrently write to a "map-like" data structure (where the caller gets a granular lock based on key).

Changes

  • Implement ShardedMap
  • Add tests for ShardedMap
  • Migrate MutexMap to use ShardedMap

Future Work

As @bobg mentioned, this ShardedMap could be converted to a ShardedContainer to allow the caller to store anything they want in a particular shard.

@coveralls
Copy link

coveralls commented Nov 23, 2020

Pull Request Test Coverage Report for Build 12095

  • 41 of 42 (97.62%) changed or added relevant lines in 2 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage increased (+0.04%) to 79.049%

Changes Missing Coverage Covered Lines Changed/Added Lines %
utils/mutex_map.go 15 16 93.75%
Totals Coverage Status
Change from base Build 12029: 0.04%
Covered Lines: 7761
Relevant Lines: 9818

💛 - Coveralls

// Lock acquires the lock for a shard that could contain
// the key. This syntax allows the caller to perform multiple
// operations while holding the lock for a single shard.
func (m *ShardedMap) Lock(key string, priority bool) map[string]interface{} {
Copy link

Choose a reason for hiding this comment

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

I observe that nothing about this package uses the resulting map[string]interface{} as a map. This could conceivably be a more general ShardedContainer API, where Lock returns an interface{} and it's up to the caller to type-assert it to whatever kind of container they're sharding.

This means you'd also have to parameterize the constructor to construct caller-specified container shards rather than assuming they're maps.

One nice benefit of this approach is that (apart from the need to type-assert the result of Lock) it's more typesafe, in that the caller can say it's a map[string]uint64 instead of having to settle for a map[string]interface{}.

Copy link
Contributor Author

@patrick-ogrady patrick-ogrady Nov 23, 2020

Choose a reason for hiding this comment

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

I observe that nothing about this package uses the resulting map[string]interface{} as a map. This could conceivably be a more general ShardedContainer API, where Lock returns an interface{} and it's up to the caller to type-assert it to whatever kind of container they're sharding.

With the current API, the only thing that we would probably want to be asserted is that interface{} is a pointer (like json.Unmarshal). Otherwise, any modifications to the returned object would be ineffectual (or we return a pointer to what is stored, which may get hairy).

One nice benefit of this approach is that (apart from the need to type-assert the result of Lock) it's more typesafe, in that the caller can say it's a map[string]uint64 instead of having to settle for a map[string]interface{}.

Because the caller (me in MutexMap) doesn't need to parse the value in the map if it doesn't exist, this API allows the caller to avoid unnecessary reflection. However, I see your point about why a ShardedContainer would be cool.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this is a change we could make in a future PR once we have a clear use case in mind (likely just wrapping a ShardedContainer implementation). For now, I'd like to keep this optimized for the use case I have in mind (optimization of the reconciler and storage packages).

@patrick-ogrady patrick-ogrady changed the title [utils] Add ShardedMap [utils] Implement ShardedMap Nov 23, 2020
@@ -309,7 +309,7 @@ func TestBadgerTrain_Limit(t *testing.T) {
namespace,
newDir,
dictionaryPath,
10,
50,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Increased this to stop a flaky test 😢

Copy link

@bobg bobg left a comment

Choose a reason for hiding this comment

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

One non-blocking suggestion, otherwise LGTM.

})

time.Sleep(1 * time.Second)
assert.NoError(t, g.Wait())
Copy link

Choose a reason for hiding this comment

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

It looks like none of the goroutines in g actually need to return an error, which means you could simplify a bit by using sync.WaitGroup instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think adding sync.WaitGroup may take more lines/be slightly more complex than just returning nil. I think we would need a wg.Add before spawning each goroutine and would need to call wg.Done at the end of each goroutine. Going to just leave as is for now!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Appreciate the careful review!

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

Successfully merging this pull request may close these issues.

3 participants