spring-projects / spring-boot

Spring Boot helps you to create Spring-powered, production-grade applications and services with absolute minimum fuss.
https://spring.io/projects/spring-boot
Apache License 2.0
75.17k stars 40.68k forks source link

Ensure that all data store auto-configurations have consistent default connection behaviour #11325

Open wilkinsona opened 6 years ago

wilkinsona commented 6 years ago

This was raised as part of a discussion on Twitter. The specific example given is Mongo which connects to localhost when it's on the classpath and Couchbase which doesn't connect to anything until your configure at least one bootstrap host.

There may well be good reasons for the apparent inconsistency, but it appears to be causing some confusion. It would be nice to align the behaviour across data stores if we can. If there are good reasons not to do that, it would be nice to see if we can somehow make the varying behaviour less surprising.

abelsromero commented 6 years ago

The confusion for us came because of two reasons One, we have been using Couchbase without issues, and when we migrated to MongoDB we had to exclude the autoconfigurer. That felt odd.

Two, how the Autoconfiguration starts and logs the MongoClient behauviour...

  1. if MongoClient is in the classpath and no configuration is present, it attempts to connect a localhost instance.
  2. If connection fails, it does not propagate the error, so the context starts and the app is working fine.
  3. However, the error is logged into the logs just after the "success"message. See attachment. mongoclient-springboot-autcoonfigurer-error.txt

Note: whe use a logback logging configuration importing springs defaults

    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>

Imho, all Autoconfigurators for components depend on some mid-complex configuration should check for properties. Simple things or when a default config is a common thing like Gson or Jackson are fine that they only check for Beans.

Btw, the reason why we have a custom connector and do not expose a MongoClient to the Spring context is because we have a multitennacy platform that manages several connections at once. We load the configurations from a config-service and then initialize all our MongoClients inside a custom bean. That bean is the exposed, but not the clients.

wilkinsona commented 6 years ago

Here's the error from the attachment:

2017-12-12 12:58:38.732  INFO [multitenancy-server,,,] 9520 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 9040 (http)
2017-12-12 12:58:38.741  INFO [multitenancy-server,,,] 9520 --- [           main] c.e.e.m.MultitenancyServerApplication    : Started MultitenancyServerApplication in 12.765 seconds (JVM running for 13.878)
2017-12-12 12:58:38.826  INFO [multitenancy-server,,,] 9520 --- [localhost:27017] org.mongodb.driver.cluster               : Exception in monitor thread while connecting to server localhost:27017
com.mongodb.MongoSocketOpenException: Exception opening socket
                at com.mongodb.connection.SocketStream.open(SocketStream.java:62)
                at com.mongodb.connection.InternalStreamConnection.open(InternalStreamConnection.java:107)
                at com.mongodb.connection.DefaultServerMonitor$ServerMonitorRunnable.run(DefaultServerMonitor.java:111)
                at java.lang.Thread.run(Thread.java:748) 
Caused by: java.net.ConnectException: Connection refused: connect
                at java.net.DualStackPlainSocketImpl.waitForConnect(Native Method)
                at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:85)
                at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
                at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
                at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
                at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172)
                at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
                at java.net.Socket.connect(Socket.java:589)
                at com.mongodb.connection.SocketStreamHelper.initialize(SocketStreamHelper.java:59)
                at com.mongodb.connection.SocketStream.open(SocketStream.java:57)
                ... 3 common frames omitted
wilkinsona commented 6 years ago

IMO, the main problem with the current situation is the inconsistency in behaviour across the different data stores for which auto-configuration is provided. The current behaviour when dependencies are available but no configuration is provided is described in the following table:

Store Result
JDBC Can't determine the driver class and fails to start.
Cassandra Tries to connect to localhost and fails to start.
Couchbase Auto-configuration backs off and application starts.
Elasticsearch Auto-configuration backs off and application starts.
Mongo Application starts but logs a ConnectException. Any calls to Mongo fail.
Redis Application starts cleanly. Any calls to Redis fail.
Solr Application starts cleanly. Any calls to Solr fail.

These results were gathered from deliberately minimal applications that didn't attempt to autowire or use any components that may or may not have been auto-configured.

@mbhave made a good point about a knock-on effect of this inconsistency with respect to actuator and the health endpoint. The health responses are shown in the following table:

Store Health
Couchbase Up
Elasticsearch Up
Mongo Down
Redis Down
Solr Down

The difference in health responses is determined by the auto-configuration either backing off or "successfully" configuring beans that can't actually communicate with the data store.

philwebb commented 6 years ago

Wondering if this should be in the backlog?

wilkinsona commented 6 years ago

It'd be nice to tackle it in 2.1 if we can.

yuranos commented 6 years ago

I just wanted to add that apart from different connectivity behaviour for different DBs, applications can also react very differently to a specific DB depending on applications content(business logic, configurations), which is sometimes very confusing.

Let me describe it with one example: I have 2 mongo db-based apps. I'm starting them to make sure everything is Ok. However, I forgot that I haven't started mongo server yet. And yet one app will fail to start and another one will print a connection error stacktrace but keep on running. It took me a while to figure out that the one that fails had a @Document with @Index in it, while the other one didn't. So, by just adding/deleting @Index annotation I can significantly impact the way Spring Boot react to mongo DB availability. It is reproducible on our apps but it can also be easily reproduced on the very minimal app with zero business logic or customization. The entity is as follows:

@Document(collection = "domain")
public class Domain {

    @Id
    private long id;

    //If I leave it like this, an app fails to start, if I comment the next line, it starts up successfully.
    @Indexed(unique = true)
    private String domain;

    private boolean displayAds;

}

That's all the code that is needed to reproduce it. This class might not be used anywhere at all. I haven't exposed it into any services or controllers. A properties file is also empty. It's as skinny as it gets. And yet, even with this minimal configuration I can reproduce a very confusing behavioral pattern that I'd prefer to be fixed.

upen4a3 commented 4 years ago

We are using Reactive Redis to cache web-client response.If Redis server is up and running application works fine.But App start up is failing ,if we stop redis. Expected behavior is app should still go ahead and start even though redis connection fails.Does spring handles connection exception?or do we have to handle this kind of exceptions?

wilkinsona commented 4 years ago

Neo4j is also affected in a similar way. An app will fail to start when Neo4j can't be reached. #23707 contains some details, including failures both with and without Actuator.