How to handle Many to Many relationship in mongoDB?
Asked Answered
T

2

9

I have a specific problem with many to many relationship implementations in MongoDB.

I have collections of Songs and Artists(Millions document). Here the song can be sung by Many Artists and an artist can sing Many songs. So I followed the approach of Document referencing in both collections. like this...

1. Songs Collection:-

{
  _id:ObjectId("dge547567hheheasfw3454dfg"),
   title:"xyz",
   artists:[ObjectId("xfvdg464654"), ...] //many artists // artists ids
}

2. Artist Collection:-

{
  _id:ObjectId("dge547567hheheasfw3454dfg"),
   title:"xyz",
   songs:[ObjectId("xfvdg464654"), ...] //many songs // songs Ids 
}

But here the problem is while deleting artist I have to delete an artist from an array of the artist in all document of the song which has an artist and vice versa. Which can cause the problem of Atomicity. How can I ensure atomicity here?

Secondly when the database will grow and songs are sung by the artist will increase thus resulting document growth of both collection and document size can reach to 16MB or greater(MAX DOC SIZE).

So what can be done here in this case?

Turbo answered 7/4, 2018 at 19:12 Comment(1)
But here the problem is while doing CRUD Operation on one collection I have to do CRUD operation on other collection. which can cause the problem of Atomicity -> MongoDB leaves this job to the programmer who had to ensure data consistency explicitly. MongoDB ensure atomicity in document level though. Secondly when the database will grow and songs are sung by the artist will increase thus resulting document growth of both collection and document size can reach to 16MB or greater -> You're not storing the entire songs/artist collection, just an Id, so ideally no problems here..Bethina
M
8

Let's start with detailing our Many-to-Many relationship for your case and try to understand what can be and cannot be done -

  • A song can be sung by as many as 10 or maybe 20 artists (assuming it isn't as complex/diverse to require maybe 100's of artists).

    In this case, bucketing artist's id inside of songs collection is perfectly fine and we can safely assume that even in the worst case scenario (storing a complex/diverse song sung by 100 artist) it will never force our song collection beyond 16 MBs.

  • An artist, however may very well sing upto 1000s of songs or maybe more in his enitre career. An ObjectId being 12 bytes long, in this case will grow a collection to a size of merely 12000 bytes which is way lesser than 16000000 bytes. You are still left with a lot of space. So no need to worry about hitting the cap of 16MB.

Approach - 1

Inter-bucketing works really well for relations expecting high reads.

Songs for some artists can be fetched in single query and even vice versa. And this would be even smoother with indexes sprinkled over these two collections.

But if we go around bucketing artists inside of songs and songs inside of artists then our updates are no more Atomic but for that we can still implement an application level Two-phase commit for both artists and songs CRUD, which even after being a little troublesome, solves the problem.

Approach - 2:

Why not bucket only artist id's inside of songs collection and have multikey index on that field.

List of artists who sang a song is way too short than the list of songs sung by an artist. So we only bucket artists inside of songs collection.

This way we will -

1. avoid the near to impossible possibility of hitting maximum size of artist's collection if we had bucketed songs inside of artists collection.

2. avoid writing 2P commits for atleast songs collections. All relational reads can be satisfied via songs collection only (here i am excluding the _id lookup for artist)

3. ensure fast data access in just a single query even when reverse querying on song collection for songs sung by an artist.

You will already be having some info(_id) of artist for which you need to fetch songs. You just draft a query like this -

 db.songs.find({ artists: 'your-artist-id' });

And when you explain this query, you find happiness when you realize that it utilizes your multi-key indexes. Great job there !

Now which approach to go for ?

I find the second approach a little more subtle for your use case as it reduces some of the complexity of managing 2P commits for atomicity and still provides a good read performance. First approach definitely is reads oriented so if you're sure that you will be recieving a lots and lots of reads on both the collection, go for first one otherwise second one should do the trick.

Maley answered 7/4, 2018 at 21:16 Comment(0)
M
2

I implemented many to many relationship in mongodb by taking third collection similar to what we do in sql.

Song Collection

{
  _id:ObjectId("dge547567hheheasfw3454df12"),
   title:"xyz",
   length : 123
}

Artist Collection

{
   _id:ObjectId("dge547567hheheasfw3454d32"),
   name:"abc",
}

SongArtist Collection

{
   _id:ObjectId("dge547567hheheasdfsdfsdfgdfga42"),
   artist: ObjectId("dge547567hheheasfw3454dfg32"),
   song: ObjectId("dge547567hheheasfw3454df12"),
}
  • Now when you do crud operations and if you want to delete the artist from a song you can do it in single query in SongArtist Collection.
  • It will never have any problem for exceeding document size
  • If you want to delete particular artist in particular song you have to query once
  • It will increase the number of records in a collection but mongodb can handle that very easily.
  • You can find all the song related to one artist in single query and vice versa.
Maramarabel answered 8/4, 2018 at 8:16 Comment(1)
@abhishek please tell if u have any other queries regarding my solutionMaramarabel

© 2022 - 2024 — McMap. All rights reserved.