-
Notifications
You must be signed in to change notification settings - Fork 328
Generate Request IDs (if not specified); Return Request ID as a Header #2602
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
Generate Request IDs (if not specified); Return Request ID as a Header #2602
Conversation
| if (requestId == null) { | ||
| requestId = UUID.randomUUID().toString(); | ||
| } | ||
| MDC.put(REQUEST_ID_KEY, requestId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm admittedly nit-picking a bit 😅 but I'd argue that the MDC.put statement should stay in LoggingMDCFilter since it's about logging.
What should be moved here is just the rc.setProperty call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I can agree to that :) Will adjust in this revision.
|
|
||
| @Provider | ||
| public class RequestIdResponseFilter implements ContainerResponseFilter { | ||
| private static final String REQUEST_ID_HEADER = "X-Request-Id"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The X- prefix is deprecated, FYI. See:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers
But more importantly, why not use the header name used for incoming requests, Polaris-Request-Id?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TIL "X-" is deprecated! Thanks!
We can - I thought this was more of a standardized name prior. Let me change this.
runtime/service/src/main/java/org/apache/polaris/service/tracing/RequestIdFilter.java
Show resolved
Hide resolved
runtime/service/src/main/java/org/apache/polaris/service/tracing/RequestIdFilter.java
Show resolved
Hide resolved
| return Map.of( | ||
| "polaris.log.request-id-header-name", | ||
| REQUEST_ID_HEADER, | ||
| "polaris.bootstrap.credentials", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't look right. There is no Quarkus configuration named polaris.bootstrap.credentials.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The README is correct, but the other test is wrong indeed.
| public void filter(ContainerRequestContext rc) { | ||
| var requestId = rc.getHeaderString(loggingConfiguration.requestIdHeaderName()); | ||
| if (requestId == null) { | ||
| requestId = UUID.randomUUID().toString(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is exhausting the randomness pool a concern? @snazy : WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. Also: UUID.randomUUID() is in theory a blocking call (happens only when the entropy source is empty though), and therefore should be avoided in filters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the request ID does not have to be a UUID (I'm a bit out-of-date on this) it may be worth using a per-node (or per-thread) UUID (allocated one per restart) plus a simple counter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few suggestions:
- Use
org.apache.polaris.ids.impl.SnowflakeIdGeneratorImpl#idToTimeUuid - Use a simple
AtomicLongcounter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also UUID v7 may be worth considering (optional, for follow-up): https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-04.html#name-uuid-version-7
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added the RequestIdGenerator, which is a very, very simple ID generator - since we don't require the level of complexity that has been made in the SnowflakeIdGenerator. It's just a quick implementation of @dimas-b's suggestion above:
If the request ID does not have to be a UUID (I'm a bit out-of-date on this) it may be worth using a per-node (or per-thread) UUID (allocated one per restart) plus a simple counter.
I'm very hesitant to introduce a complete dependency on the NoSql Persistence for the Service module and so creating the RequestIdGenerator is my minor effort to avoid doing that. I'm sure there may be a better reason for introducing this dependency in the future, but I don't want to sidetrack the simple goal (and simple requirements) of this PR with whether we should introduce this dependency or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are we sure that UUID.randomUUID() is blocking call and in what chance will it exhaust the system entropy?
I've never got a satisfying answer for that question 😄
Most commenters stress the fact that dev/urandom never blocks.
This is certainly true, but doesn't address the fact that the entropy pool might get exhausted at some point, in which case your UUIDs will have very poor randomness.
Furthermore, the UUID.randomUUID() call is explicitly considered blocking by BlockHound:
I think that this is due to the fact that new SecureRandom() does some magic to select the random numbers provider, and even if the default provider doesn't block (it plugs into /dev/urandom), you still can configure your JVM with a different provider that could block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here’s a post that shows how to work around BlockHound: https://stackoverflow.com/a/75687886/933856.
That said, we probably shouldn’t make decisions based on a single anecdote or assumptions about JVM configurations. What if a user runs Polaris on their own JVM and it breaks? That scenario is very likely. And what if a future JVM introduces a breaking change?
Do we need to worry about that now? Probably not. It feels like a premature optimization at this stage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Quarkus doesn't use BlockHound – I cited BlockHound as an example. But Quarkus does have a mechanism to detect blocking calls in non-blocking contexts, we already had the problem with RealmContextFilter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I posted that link to demonstrate that people disagree with BlockHound on whether it is blocking call. Does Quarkus consider also UUID.randomUUID() as a blocking-call? If not, we got another reason to optimize it later.
|
|
||
| @Test | ||
| public void testRequestIdHeaderNotSpecifiedAndCounterExhausted() { | ||
| requestIdGenerator.setCounter(Long.MAX_VALUE / 2 + 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test is actually testing the internals of RequestIdGenerator. I'd prefer to move this to a new RequestIdGeneratorTest class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in the next revision!
| assertThat(currentRequestIdBase).isNotEqualTo(uuidBase); | ||
| } | ||
|
|
||
| private boolean isValidDefaultUUID(String str) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, I'm not sure we need to verify that request IDs have any particular structure. For all intents and purposes, the ID is an opaque string and it doesn't matter how it's generated, as long as it is unique.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it - I'm still testing the format in RequestIdGenerator (just to make sure that what we expect to happen is still happening), but removed it from here.
| @Priority(FilterPriorities.REQUEST_ID_FILTER) | ||
| @Provider | ||
| public class RequestIdFilter implements ContainerRequestFilter { | ||
| @Inject RequestIdGenerator requestIdGenerator; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: move this line down to line 40
|
|
||
| @ApplicationScoped | ||
| public class RequestIdGenerator { | ||
| private static String BASE_DEFAULT_UUID = UUID.randomUUID().toString(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| private static String BASE_DEFAULT_UUID = UUID.randomUUID().toString(); | |
| private static String baseDefaultUuid = UUID.randomUUID().toString(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TIL, thanks!
|
|
||
| public String generateRequestId() { | ||
| String requestId = BASE_DEFAULT_UUID + "_" + COUNTER.incrementAndGet(); | ||
| if (COUNTER.get() >= COUNTER_SOFT_MAX) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are many issues here:
COUNTER.get()will not necessarily return whatCOUNTER.incrementAndGet()returned in the previous line.- Many threads may enter this if block in parallel, and will all try to assign the
BASE_DEFAULT_UUIDfield. BASE_DEFAULT_UUIDis not volatile nor atomic, so the value write on line 38 may not be immediately visible to other threads.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, you are right and I'm embarrassed that I didn't catch these. I've fixed this in the next revision using an AtomicBoolean as a way to track whether a reset is in progress. Please let me know if I've missed anything else.
adnanhemani
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Responded to comments from @adutra . Please let me know if there is anything else!
|
|
||
| @Test | ||
| public void testRequestIdHeaderNotSpecifiedAndCounterExhausted() { | ||
| requestIdGenerator.setCounter(Long.MAX_VALUE / 2 + 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in the next revision!
| assertThat(currentRequestIdBase).isNotEqualTo(uuidBase); | ||
| } | ||
|
|
||
| private boolean isValidDefaultUUID(String str) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it - I'm still testing the format in RequestIdGenerator (just to make sure that what we expect to happen is still happening), but removed it from here.
|
|
||
| @ApplicationScoped | ||
| public class RequestIdGenerator { | ||
| private static String BASE_DEFAULT_UUID = UUID.randomUUID().toString(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TIL, thanks!
|
|
||
| public String generateRequestId() { | ||
| String requestId = BASE_DEFAULT_UUID + "_" + COUNTER.incrementAndGet(); | ||
| if (COUNTER.get() >= COUNTER_SOFT_MAX) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, you are right and I'm embarrassed that I didn't catch these. I've fixed this in the next revision using an AtomicBoolean as a way to track whether a reset is in progress. Please let me know if I've missed anything else.
| import jakarta.ws.rs.Priorities; | ||
|
|
||
| public final class FilterPriorities { | ||
| public static final int REQUEST_ID_FILTER = Priorities.AUTHENTICATION - 101; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: -> REALM_CONTEXT_FILTER - 1 to make it more readable?
flyrain
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
flyrain
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 Thanks @adnanhemani for the change!
runtime/service/src/main/java/org/apache/polaris/service/tracing/RequestIdGenerator.java
Show resolved
Hide resolved
| } | ||
|
|
||
| State increment() { | ||
| return counter >= COUNTER_SOFT_MAX ? new State() : new State(uuid, counter + 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why COUNTER_SOFT_MAX ? There is no risk of overflowing here.
|
|
||
| @Test | ||
| void testCounterIncrementsSequentially() { | ||
| // requestIdGenerator.setCounter(0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: dangling comment.
| } | ||
|
|
||
| @Test | ||
| void testSetCounterChangesNextGeneratedId() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure this test has any value, setCounter is not meant to be called outside of tests, and besides, I think we can remove that method.
| } | ||
|
|
||
| @VisibleForTesting | ||
| public void setCounter(long counter) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need this method, RequestIdGenerator.state is already package-private.
| } | ||
|
|
||
| @Test | ||
| void testGenerateRequestId_ReturnsUniqueIds() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test could be made multi-threaded.
|
In order to move things forward, and since @flyrain already approved, I'm going to merge this PR now even if my latest comments are still open. I will address them myself in a follow-up PR. |
Summary of changes: 1. Extracted an interface from `RequestIdGenerator`. 2. The `generateRequestId` method now returns a `CompletionStage<String>` in case custom implementations need to perform I/O or other blocking calls during request ID generation. 3. Also addressed comments in apache#2602.
Summary of changes: 1. Extracted an interface from `RequestIdGenerator`. 2. The `generateRequestId` method now returns a `Uni<String>` in case custom implementations need to perform I/O or other blocking calls during request ID generation. 3. Also addressed comments in apache#2602.
Summary of changes: 1. Extracted an interface from `RequestIdGenerator`. 2. The `generateRequestId` method now returns a `Uni<String>` in case custom implementations need to perform I/O or other blocking calls during request ID generation. 3. Also addressed comments in apache#2602.
Summary of changes: 1. Extracted an interface from `RequestIdGenerator`. 2. The `generateRequestId` method now returns a `Uni<String>` in case custom implementations need to perform I/O or other blocking calls during request ID generation. 3. Also addressed comments in #2602.
This PR makes two changes:
ContainerRequestContextdownstream.I'm having some trouble with the naming of the classes, please do help suggest better names!