Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

RavenDB – Coolblue Behind the Scenes

422 views

Published on

An introduction to RavenDB, presented at Coolblue's Behind the Scenes event April 21st, 2016

Published in: Technology
  • Be the first to comment

RavenDB – Coolblue Behind the Scenes

  1. 1. Behind the Scenes – April 2016 Michiel van Oosterhout <m.vanoosterhout@coolblue.nl>
  2. 2. Bird’s eye view
  3. 3. Low-level stuff
  4. 4. RavenDB ● Open Source ● Document database ● Written in .NET ● “It just works” ● “Zero-friction development” ● “Fast writes, fast reads, and world peace” — Oren Eini, developer
  5. 5. Act 1
  6. 6. Working with documents
  7. 7. Plain old C# object var question = new Question { Title = "Who played Private Hudson in the 1986 movie Aliens?", Body = @"You may remember him from this quote: ""Game over man, GAME OVER!""", Tags = new[] {"identify-this-actor", "aliens"}, Score = 12, Asker = new Asker { Id = 3, Name = "Michiel", Reputation = 42 }, Answers = null };
  8. 8. Simple properties var question = new Question { Title = "Who played Private Hudson in the 1986 movie Aliens?", Body = @"You may remember him from this quote: ""Game over man, GAME OVER!""", Tags = new[] {"identify-this-actor", "aliens"}, Score = 12, Asker = new Asker { Id = 3, Name = "Michiel", Reputation = 42 }, Answers = null };
  9. 9. Complex properties var question = new Question { Title = "Who played Private Hudson in the 1986 movie Aliens?", Body = @"You may remember him from this quote: ""Game over man, GAME OVER!""", Tags = new[] {"identify-this-actor", "aliens"}, Score = 12, Asker = new Asker { Id = 3, Name = "Michiel", Reputation = 42 }, Answers = null };
  10. 10. Collection properties var question = new Question { Title = "Who played Private Hudson in the 1986 movie Aliens?", Body = @"You may remember him from this quote: ""Game over man, GAME OVER!""", Tags = new[] {"identify-this-actor", "aliens"}, Score = 12, Asker = new Asker { Id = 3, Name = "Michiel", Reputation = 42 }, Answers = null };
  11. 11. A question var question = new Question { Title = "Who played Private Hudson in the 1986 movie Aliens?", Body = @"You may remember him from this quote: ""Game over man, GAME OVER!""", Tags = new[] {"identify-this-actor", "aliens"}, Score = 12, Asker = new Asker { Id = 3, Name = "Michiel", Reputation = 42 }, Answers = null };
  12. 12. The answer var question = new Question { Title = "Who played Private Hudson in the 1986 movie Aliens?", Body = @"You may remember him from this quote: ""Game over man, GAME OVER!""", Tags = new[] {"identify-this-actor", "aliens"}, Score = 12, Asker = new Asker { Id = 3, Name = "Michiel", Reputation = 42 }, Answers = new[] { new Answer { Body = "Bill Paxton", Score = 1, Accepted = true, Answerer = new Answerer { Id = 5, Name = "Dries", Reputation = 232 } } } };
  13. 13. An entity var question = new Question { Title = "Who played Private Hudson in the 1986 movie Aliens?", Body = @"You may remember him from this quote: ""Game over man, GAME OVER!""", Tags = new[] {"identify-this-actor", "aliens"}, Score = 12, Asker = new Asker { Id = 3, Name = "Michiel", Reputation = 42 }, Answers = new[] { new Answer { Body = "Bill Paxton", Score = 1, Accepted = true, Answerer = new Answerer { Id = 5, Name = "Dries", Reputation = 232 } } } };
  14. 14. The stored document { "Title": "Who played Private Hudson in the 1986 movie Aliens?", "Body": "You may remember him from this quote: "Game over man, GAME OVER!"", "Tags": ["identify-this-actor", "aliens"], "Score": 12, "Asker": { "Id": 3, "Name": "Michiel", "Reputation": 42 }, "Answers": [ { "Body": "Bill Paxton", "Score": 1, "Accepted": true, "Answerer": { "Id": 5, "Name": "Dries", "Reputation": 232} } ] }
  15. 15. Connecting to a database using (var store = new DocumentStore()) { store.ConnectionStringName = "RavenDB"; store.Initialize(); store.DatabaseCommands.GlobalAdmin.EnsureDatabaseExists(store.DefaultDatabase); }
  16. 16. Opening a session using (var store = new DocumentStore()) { store.ConnectionStringName = "RavenDB"; store.Initialize(); store.DatabaseCommands.GlobalAdmin.EnsureDatabaseExists(store.DefaultDatabase); using (var session = store.OpenSession()) { } }
  17. 17. Saving a document using (var store = new DocumentStore()) { store.ConnectionStringName = "RavenDB"; store.Initialize(); store.DatabaseCommands.GlobalAdmin.EnsureDatabaseExists(store.DefaultDatabase); using (var session = store.OpenSession()) { var question = ... session.Store(question); session.SaveChanges(); } }
  18. 18. Retrieving a document using (var store = new DocumentStore()) { store.ConnectionStringName = "RavenDB"; store.Initialize(); store.DatabaseCommands.GlobalAdmin.EnsureDatabaseExists(store.DefaultDatabase); using (var session = store.OpenSession()) { var question = session.Load<Question>(42); } }
  19. 19. Retrieving a document using (var store = new DocumentStore()) { store.ConnectionStringName = "RavenDB"; store.Initialize(); store.DatabaseCommands.GlobalAdmin.EnsureDatabaseExists(store.DefaultDatabase); using (var session = store.OpenSession()) { var question = session.Load<Question>(42); RenderQuestion(question); } }
  20. 20. Querying
  21. 21. Language integrated queries var query = session .Query<Question>() .Where(question => question.Tags.Contains(tag)) .Where( question => question.Answers == null || question.Answers.Any(answer => answer.Accepted) == false ) .OrderByDescending(question => question.Score) .Take(3);
  22. 22. Create index public class Questions_ByHasAcceptedAnswerAndTag : AbstractIndexCreationTask<Question> { public Questions_ByHasAcceptedAnswerAndTag() { Map = questions => questions .Select(question => new { HasAcceptedAnswer = question.Answers == null || question.Answers.Any(answer => answer.Accepted) == false, Score = question.Score, Tags = question.Tags }); } }
  23. 23. Create index public class Questions_ByHasAcceptedAnswerAndTag : AbstractIndexCreationTask<Question> { public Questions_ByHasAcceptedAnswerAndTag() { Map = questions => questions .Select(question => new { HasAcceptedAnswer = question.Answers == null || question.Answers.Any(answer => answer.Accepted) == false, Score = question.Score, Tags = question.Tags }); } } new Questions_ByHasAcceptedAnswerAndTag().Execute()
  24. 24. Act 2
  25. 25. We have to go deeper
  26. 26. Behind the scenes
  27. 27. Documents Database ESENT
  28. 28. Documents Databasesession.Store()
  29. 29. Documents Databasesession.Store() session.Load()
  30. 30. Documents Database ACID session.Load() session.Store()
  31. 31. Documents Database
  32. 32. Documents Database
  33. 33. Documents Indexes Database Lucene
  34. 34. Documents Indexes Databasesession.Store()
  35. 35. Documents Indexes Database Indexing… 🕓
  36. 36. Documents Indexes Database session.Query() Indexing… 🕓
  37. 37. Documents Indexes Database session.Query()
  38. 38. ACID BASE Documents Indexes Database
  39. 39. BASE
  40. 40. BASE
  41. 41. BASE
  42. 42. BASE
  43. 43. Eventual consistency
  44. 44. Querying a possibly stale index var query = session .Query<Question>() .Where(question => question.Tags.Contains(tag)) .Where( question => question.Answers == null || question.Answers.Any(answer => answer.Accepted) == false ) .OrderByDescending(question => question.Score) .Take(3);
  45. 45. Dealing with stale results RavenQueryStatistics statistics; var query = session .Query<Question>() .Statistics(out statistics) .Where(question => question.Tags.Contains(tag)) .Where( question => question.Answers == null || question.Answers.Any(answer => answer.Accepted) == false ) .OrderByDescending(question => question.Score) .Take(3); bool stale = statistics.IsStale;
  46. 46. Waiting for the non-stale results var query = session .Query<Question>() .Customize(x => x.WaitForNonStaleResultsAsOfLastWrite()) .Where(question => question.Tags.Contains(tag)) .Where( question => question.Answers == null || question.Answers.Any(answer => answer.Accepted) == false ) .OrderByDescending(question => question.Score) .Take(3);
  47. 47. Final act
  48. 48. Result set size var query = session .Query<Question>() .Statistics(out statistics) .Where(question => question.HasAcceptedAnswer == false) .OrderByDescending(question => question.Score) .Take(size);
  49. 49. Result set size var query = session .Query<Question>() .Statistics(out statistics) .Where(question => question.HasAcceptedAnswer == false) .OrderByDescending(question => question.Score) .Take(size);
  50. 50. Result set size var query = session .Query<Question>() .Statistics(out statistics) .Where(question => question.HasAcceptedAnswer == false) .OrderByDescending(question => question.Score) .Take(size); // Max 1024
  51. 51. Result set size var query = session .Query<Question>() .Statistics(out statistics) .Where(question => question.HasAcceptedAnswer == false) .OrderByDescending(question => question.Score) .Take(size); // Max 1024
  52. 52. Paging const int pageSize = 1000; int count = 0; while (count < size) { var query = session .Query<Question>() .Statistics(out statistics) .Where(question => question.HasAcceptedAnswer == false) .OrderByDescending(question => question.Score) .Take(pageSize); count += query.Count; // ...
  53. 53. Paging const int pageSize = 1000; int count = 0; while (count < size) { var query = session .Query<Question>() .Statistics(out statistics) .Where(question => question.HasAcceptedAnswer == false) .OrderByDescending(question => question.Score) .Take(pageSize); count += query.Count; // ...
  54. 54. Streaming using (var session = store.OpenSession()) { var query = session .Query<Question>("Questions/ByHasAcceptedAnswerAndTag") .OrderByDescending(question => question.Score); var results = session.Advanced.Stream(query); while (results.MoveNext()) { var question = results.Current.Document; // ... }
  55. 55. Streaming using (var session = store.OpenSession()) { var query = session .Query<Question>("Questions/ByHasAcceptedAnswerAndTag") .OrderByDescending(question => question.Score); var results = session.Advanced.Stream(query); while (results.MoveNext()) { var question = results.Current.Document; // ... }
  56. 56. Leaky LINQ
  57. 57. Query var query = session .Query<Question>() .Where(question => question.Tags.Contains(tag)) .Where( question => question.Answers == null || question.Answers.Any(answer => answer.Accepted) == false ) .OrderByDescending(question => question.Score) .Take(3);
  58. 58. Refactor var query = session .Query<Question>() .Where(question => question.Tags.Contains(tag)) .Where(question => question.HasAcceptedAnswer() == false) .OrderByDescending(question => question.Score) .Take(3);
  59. 59. Cause of bug public class Question { public string Id { get; set; } public string Title { get; set; } public string Body { get; set; } public Asker Asker { get; set; } public IEnumerable<Answer> Answers { get; set; } public bool HasAcceptedAnswer() { return Answers != null && Answers.Any(answer => answer.Accepted); } }
  60. 60. Convert to property public class Question { public string Id { get; set; } public string Title { get; set; } public string Body { get; set; } public Asker Asker { get; set; } public IEnumerable<Answer> Answers { get; set; } public bool HasAcceptedAnswer { get; set; } }
  61. 61. Refactor var query = session .Query<Question>() .Where(question => question.Tags.Contains(tag)) .Where(question => question.HasAcceptedAnswer() == false) .OrderByDescending(question => question.Score) .Take(3);
  62. 62. Refactor var query = session .Query<Question>() .Where(question => question.Tags.Contains(tag)) .Where(question => question.HasAcceptedAnswer == false) .OrderByDescending(question => question.Score) .Take(3);
  63. 63. Data is missing { "Title": "Who played Private Hudson in the 1986 movie Aliens?", "Body": "You may remember him from this quote: "Game over man, GAME OVER!"", "Tags": ["identify-this-actor", "aliens"], "Score": 12, "Asker": { "Id": 3, "Name": "Michiel", "Reputation": 42 }, "Answers": [ { "Body": "Bill Paxton", "Score": 3, "Accepted": true, "Answerer": { "Id": 5, "Name": "Dries", "Reputation": 232 } } ] }
  64. 64. Migrations
  65. 65. Before { "Title": "Who played Private Hudson in the 1986 movie Aliens?", "Body": "You may remember him from this quote: "Game over man, GAME OVER!"", "Tags": ["identify-this-actor", "aliens"], "Score": 12, "Asker": { "Id": 3, "Name": "Michiel", "Reputation": 42 }, "Answers": [ { "Body": "Bill Paxton", "Score": 3, "Accepted": true, "Answerer": { "Id": 5, "Name": "Dries", "Reputation": 232 } } ] }
  66. 66. After { "Title": "Who played Private Hudson in the 1986 movie Aliens?", "Body": "You may remember him from this quote: "Game over man, GAME OVER!"", "Tags": ["identify-this-actor", "aliens"], "Score": 12, "Asker": { "Id": 3, "Name": "Michiel", "Reputation": 42 }, "Answers": [ { "Body": "Bill Paxton", "Score": 1, "Accepted": true, "Answerer": { "Id": 5, "Name": "Dries", "Reputation": 232 } } ], "HasAcceptedAnswer": true }
  67. 67. Client-side migration int start = 0; while (true) { var batch = session.Query<Question>().Take(100).Skip(start).ToList(); start += batch.Count; if (batch.Count == 0) break; foreach (var question in batch) { question.HasAcceptedAnswer = question.Answers != null && question.Answers.Any(answer => answer.Accepted); session.Store(question); } session.SaveChanges(); }
  68. 68. Questions?
  69. 69. Behind the Scenes – April 2016 Michiel van Oosterhout <m.vanoosterhout@coolblue.nl>

×