Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dougqh/spring data support #1014

Merged
merged 52 commits into from
Oct 8, 2019
Merged

Dougqh/spring data support #1014

merged 52 commits into from
Oct 8, 2019

Conversation

dougqh
Copy link
Contributor

@dougqh dougqh commented Sep 24, 2019

Revised approach to adding spring-data support.

Similar to the prior approach this change uses Spring's AOP interceptors; however, rather than hooking into the TransactionInterceptor -- this change adds a Datadog specific MethodInterceptor into the Repository.

This is accomplished by instrumenting RepositoryFactorySupport constructor to add a post-processor. The post-processor then adds a MethodInterceptor at the beginning of the interceptor chain.

Ryan Fitzpatrick and others added 20 commits September 10, 2019 15:13
Tweak partially picked from signalfx -- that commit included same other unwanted changes in this file and others (see signalfx/signalfx-java-tracing@636c23e)
There are two main differences...
- "hsqldb" vs "spring-data" as the root test service name
- an extra seemingly spurious sql span in signalfx
Added some explanatory comments for each span -- might turn these into assertions later

Primary aim was to understand the differences from the signalfx fork.  They seem to stem deviations in the underlying JDBC integration.
Removing "Modified by SignalFx" comments, since our integration is current the same.

Added a comment to SpringJpaTest explaining the differences.
Removing dependencies rendered redundant by clean-up in datadog repo - as per review comment
After experimenting with RepositorySupport$QueryExecutorMethodInterceptor switched to TransactionInterceptor.

QueryExecutorMethodInterceptor was too narrowly scoped and didn't capture the UPDATE or DELETE statement.  TransactionInterceptor captures everything, but is still a bit more broadly applicable than I'd like.
Renaming advice class to more accurately reflect what is being instrumented
Switching muzzle to target spring-tx rather than spring-data (which is logical given the instrumentation point)

Also added extraDependency on aopalliance
This restricts the support the Spring 2.5 when spring-tx & spring-aop using aopalliance were introduced.
The SpringDataDecorator is really for Spring transactions including non-DB related transactions.

The SpringDataDecorator never really implemented DB or ORM support anyway, since most of the methods just returned null.

Changing the decorator to just extend ClientDecorator instead.  Also rename to reflect its true purpose.
This revised version targets spring-data RepositoryFactorySupport rather than spring-tx

This is accomplished by injecting a RepositoryProxyPostProcessor during construction that adds a datadog specific MethodIntercptor.

In the end, this functions similarly to the tx support in that it uses a widely scoped MethodInterceptor.

This required some changes to the test as well.  The old test set-up the repository before the instrumentation was fully-enabled.  Enabling the instrumentation earlier capture extra traces where Spring JPA performs metainfo queries.
Renamed transactionalMethod to just method, since this is no longer tied to spring-tx
@dougqh dougqh requested a review from a team as a code owner September 24, 2019 14:56
Moved spring to spring.jpa to make room for other spring data integrations.
RepositoryProxyPostProcessor has different method sigs depending on the version of spring-data-commons.

As stop gap implemented both signatures, but probably need to split spring-data support by version.
Copy link
Contributor

@tylerbenson tylerbenson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see below.

Rather than using a somewhat ugly solution of implementing both postProcess method signatures.

I'm restricting the integration to newer versions (1.9 - Sept 2014) of spring-data to start.
Removing unnecessary test code left over from initial port integration
Changing directory name to spring-data-1.9 to reflect version restrictions as per review comments
After seeing the existing elasticsearch 5.3 spring data test, I decided to extend that instead.

However, I do think we need to revisit the best place for tests involving multiple pieces of instrumentation.
@shared runs before the spring-data instrumentation is enabled.

Switching to more explicitly set-up code to change the timing; however, this change doesn't enabled spring-data instrumentation to keep this non-functional.
To shift the set-up timing without adding a lot of ugly code (as I did previously), I introduced a lazy dynamic proxy around the repository.

I'm not really happy with this, but I consider it better than the prior approach.  There is also probably a more "groovy" way to do this.

As before, this change is itself non-functional.  The subsequent test will enable spring-data instrumentation and alter the test.
Enabling the spring-data integration in this test

Mostly, this introduces an extra span for the repository; however, it also connects some previously separate traces under the repository action.

@Shared
DocRepository repo = applicationContext.getBean(DocRepository)
DocRepository repo = Proxy.newProxyInstance(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created this lazy proxy because otherwise Spring is configured before the spring-data instrumentation is available, but I don't particularly like it.

I could also change how the test set-up is done, but that was ugly in terms of refactoring the test.

Ultimately, I think it would be better if we enabled instrumentation before @shared set-up is run, but clearly, that would be larger change probably with significant ripple effects.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's kind of tricky because spock controls when @Shared variables are initialized and we already apply the instrumentation as early as possible. I believe any further changes would need to be done in the test runner.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If nothing else, I would think the instrumentation itself could be enabled via @Shared. I don't love that solution, but it is probably cleaner than what I currently resorted to doing.

class SpringJpaTest extends AgentTestRunner {
def "test CRUD"() {
// moved inside test -- otherwise, miss the opportunity to instrument
def context = new AnnotationConfigApplicationContext(JpaPersistenceConfig)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps, I should employ the same proxy approach here that I ended up using for elastic.
Here I was at least able to do something simple. Elastic required care to not set-up extra nodes; otherwise, the replication count tag value started to change.

DocRepository getOrCreateRepository() {
if ( repo != null ) return repo;

TEST_WRITER.clear()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a helper routine for this capture set-up traces and discard idiom might be worth adding.

@@ -0,0 +1,160 @@
// This file includes software developed at SignalFx
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the inconsistency of having the spring-data JPA / JDBC test in the spring-data directory while have the spring-data Elastic test in the elastic directory.

As mentioned in response to prior comments, I think we should consider have separate test-only sub-modules for such combination tests.

Copy link
Contributor

@tylerbenson tylerbenson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor change to rethrow the error and make sure the tests are passing.


boolean isRepositoryOp = Repository.class.isAssignableFrom(clazz);
// non-Repository methods including Object methods will also flow through
// this interceptor. Don't create spans for those.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this still accurate/needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is still needed. I might add a test to make sure it doesn't regress.

try {
result = methodInvocation.proceed();
} catch (Throwable t) {
DECORATOR.onError(scope, t);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rethrow t?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's what I asked in the other comment. I've done that.

I haven't looked carefully, but I didn't see any tests confirming our exception propagation. That seems like something that would be worth adding.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I don't disagree, I don't think there's a universal way to test this... It'd need to be done for each integration.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you'd either have to create a helper, test it once, and use it regularly. Or, yes, we'd need to test each time.

I don't think this is really a problem for simple advice where we can trust ByteBuddy to do the right thing. But in other cases, we have error propagation code that is specific to an integration that lack tests.

@@ -69,34 +114,23 @@ class Elasticsearch53SpringRepositoryTest extends AgentTestRunner {
repo.index(doc) == doc

and:
assertTraces(2) {
trace(0, 1) {
assertTraces(1) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rarely (<5%) of the time, I haven't seen index actions fail.
The reason appears to be that the RefreshAction happens early and is placed into a separate trace.

I'd like to address this is possible rather than introducing additional instability into the tests.

Added a test case that confirms that no span is creating for Object methods on the repository
Copy link
Contributor

@tylerbenson tylerbenson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

@dougqh dougqh merged commit 7dbad68 into master Oct 8, 2019
@dougqh dougqh deleted the dougqh/spring-data-support branch October 8, 2019 16:02
@tylerbenson tylerbenson added this to the 0.34.0 milestone Oct 11, 2019
dougqh added a commit to DataDog/documentation that referenced this pull request Oct 30, 2019
dougqh added a commit to DataDog/documentation that referenced this pull request Oct 30, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants