0

I have a Ruby on Rails application that uses both Postgres and Neo4j databases. For certain records, data is first saved to Postgres, and then a callback is triggered to update the Neo4j database. This usually works as expected, but occasionally, the Neo4j update doesn't happen, and I can't figure out why.

Here’s some additional context:

  • The Postgres-to-Neo4j update is handled inline in the Ruby code usually right after a Postgres update.

  • There’s no apparent pattern to when this issue occurs.

  • Sometimes, we call a "redundancy" sidekiq job to double save the data to Neo to increase the chance that it actually saves.

  • The condition is so intermittent that it's hard to replicate. But it happens often enough that it affects our users' experience in a detrimental way.

My questions are:

  • What are potential causes for this kind of behavior in a Rails application?

  • How can I debug or trace the root cause more effectively?

  • Are there any best practices for ensuring consistent synchronization between Postgres and Neo4j in a Rails application?

Here is some code...

# The space table gets updated on the postgres side
    @space.save
    
    #Neo gets called
    Neo4j::Space.new(@space).save


# The interface with Neo...

    def initialize(space)
    @space = space
    @neo = Neo4j::Db.instance
    end

    def save(send=true)
    return if ENV["NEO4J_NOTUSE"].present?
    return if space.nil?
    
    slug = space.slug
    parents_slugs = space.parents.pluck(:slug)
    linked_parents_slugs = space.linked_parents.pluck(:slug)
    first_parent = space.parents.first

    query = "MERGE (s:Space {slug: '#{slug}'}) " +
    "SET #{Neo4j::Utils.params_to_set_neo4j(data)} " +
    "WITH s " +
    "MATCH (u:User{auth_token: '#{space.creator.present? ? space.creator.authentication_token : SecureRandom.uuid}'}) " +
    "MERGE (s)<-[creator:CREATOR_OF]-(u) " + 
    "WITH s " +
    "OPTIONAL MATCH (parents:Space)-[pp:PARENT_OF]->(s) " +
    "OPTIONAL MATCH (link_parents:Space)-[lp:LINKED_PARENT_OF]->(s) " +
    "DELETE pp " +
    "DELETE lp "
    
    if @space.creator.nil?
      query = "MERGE (s:Space {slug: '#{slug}'}) " +
      "SET #{Neo4j::Utils.params_to_set_neo4j(data)} " +
      "WITH s " +
      "OPTIONAL MATCH (parents:Space)-[pp:PARENT_OF]->(s) " +
      "OPTIONAL MATCH (link_parents:Space)-[lp:LINKED_PARENT_OF]->(s) " +
      "DELETE pp " +
      "DELETE lp "
    end
    
    parents_slugs.each_with_index do |parent_slug, index|
      p = EditorJs::Circle.find_by_slug parent_slug
      _block = EditorJs::Block.find_by_circle_id(slug, p.blocks)
      priority = _block.present? ? _block.priority : 9999
      query += "WITH s "
      query += "MATCH (parent#{index}:Space {slug: '#{parent_slug}'}) " +
      "MERGE (parent#{index})-[rel#{index}:PARENT_OF]->(s) "+
      "ON CREATE SET rel#{index}.position = #{priority} "+
      "ON MATCH SET rel#{index}.position = #{priority} "
    end
    
    linked_parents_slugs.each_with_index do |parent_slug, index|
      query += "WITH s "
      query += "MATCH (link_parent#{index}:Space {slug: '#{parent_slug}'}) " +
      "MERGE (link_parent#{index})-[:LINKED_PARENT_OF]->(s) "
    end
    
    query += "RETURN s;"
    r = @neo.query(query, "write")
    
    space.skip_neo4j = true
    
    puts "neo4jed " * 100
    
    space.update_column :neo4j_updated_at, Time.now
    space.update_column :neo4j_version, ENV["NEO4J_SPACE_DB_VERSION"]

    if first_parent.present? && first_parent.visibility.present?
      space.update_column :visibility, first_parent.visibility
    end
    
    Neo4j::Server.new({action: "space", slug: slug}).send if send
    
    
  end

1
  • I'd start by adding some logging to the neo save method. There is at least one point it can exit silently. Simply tracking each call, the query after each state, and the return value of @neo.query(query, "write") will probably tell you what you need to know. If I were to bet, its likely some data issue that is creating a bad write query. Commented Dec 24, 2024 at 13:40

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.