Replicated MongoDB + spring-data-mongodb

There is a replicated mongodb (mongodb-1 - primary, mongodb-2 - secondary, mongodb-3 - secondary). The app runs through spring-boot-starter-data-mongodb.

Service:

public class FooBarService {
  private FooBarRepository repository;

  public FooBar method1() {
    return repository.someQuery();
  }
  
  public FooBar method2() {
    return repository.someQuery();
  }
}

Repository:

public interface FooBarRepository extends MongoRepository<FooBar, String> {
  FooBar someQuery();
}

My question is, how nice to make it so that method1 reads from the primary participant in the mongo replica set, and method2 reads from the secondary participant in the mongo replica set?

Would like to find some way to manage this at the service level (Something like @Transactional, but to select a mongo replica set member).

Can you advise me on any solutions in this regard?

Upvotes: 0

Views: 2138

Answers (2)

William Goff
William Goff

Reputation: 11

This has been changed somewhat. If you are using Spring's Repo interface you can just use the @ReadPreference. Like so:

public interface SearchRepository extends MongoRepository<SearchItem, String>
{
    @ReadPreference("secondaryPreferred")
    public Optional<SearchItem> findById(String id);

If you are using Spring's MongoOperations you can do it like this.

    AggregationOptions aggOps = AggregationOptions.builder().readPreference(
        com.mongodb.ReadPreference.secondaryPreferred()).build();
                
    Aggregation agg = Aggregation.newAggregation(match, buildRuntimeProjection()).
        withOptions(aggOps);

If you want all reads from your code to use a specific read preference you can just append it to your url string. Ie "readPreference=secondaryPreferred". Ie Something like the following in your applications.properties file.

spring.data.mongodb.uri=mongodb://localhost:27017,localhost:27018,.../dbName?authSource=admin&readPreference=secondaryPreferred

Upvotes: 0

gmeiner.m
gmeiner.m

Reputation: 827

Solution 1: @Meta Annotation

If you want to continue using Repository Interfaces, you can annotate the query method definition with the @Meta annotation which lets you pass flags to indicate to read from a secondary mongodb member.

public interface FooBarRepository extends MongoRepository<FooBar, String> {

  @Query("{}")
  @Meta(flags = Meta.CursorOption.SECONDARY_READS)
  FooBar someQuery();
}

But you cannot control this flag from the service level. You would have to create 2 query methods: One with the flag and one without it. Like someQueryFromSecondary() and someQueryFromPrimary().

Solution 2: Using MongoTemplate

Another option would be to use MongoTemplate directly and set the flag on the Query.

public void someQuery(boolean readFromSecondary) {
    var query = Query.query(Criteria.where("someKey").is("1"));

    if (readFromSecondary) {
        query.allowSecondaryReads();
    }

    return mongoTemplate.findOne(query, FooBar.class);
}

Regardless which solution you choose: be aware of that reading from secondary members can lead to retrieving stale data. Consider taking a look at the mongodb docs.

Upvotes: 3

Related Questions