Files
DongHyeonkaandClaude Opus 5 b2963105a8 docs(keycloak-session-store): import the session-storage lab as a new project
The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.

Follows the import procedure in README.md.

  source/     the originating repository verbatim — 78 documents, 28 SVGs,
              8 manifests, plus .source-revision recording the commit
  final/      the SSOT
    document.md   729 lines written from the 29 experiment documents, not
                  concatenated: what was predicted, what was measured, and
                  where the measurement itself was wrong
    evidence/raw    125 outputs, flattened to <experiment>__<file> because
                    the originals collided (01-baseline.txt appeared three
                    times) and the audit only globs the top level
    evidence/meta   one per raw file; command and exitCode are null and the
                    README says why rather than inventing them
    evidence/browser  22 captures
    assets/       three diagrams through techviz
    .techviz/     their VizSpecs

A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.

Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.

verify-pipeline.py passes. audit-records.py reports no issues.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 22:51:59 +09:00

146 lines
9.4 KiB
Plaintext

# 프로파일이 선언하는 세 상한
* @param maxDepth deepest accepted nesting
* @param maxArrayElements most elements accepted in one array
* @param maxStringBytes longest accepted string
boolean rejectScalarCoercion,
int maxDepth,
int maxArrayElements,
int maxStringBytes) {
public static WebJsonProfile strict() {
return new WebJsonProfile(true, true, true, true, true, 64, 100_000, 1_048_576);
}
maxArrayElements() 를 부르는 자리 : (0)
# 같은 이름의 필드가 예산 타입에도 있다
22: * @param maxArrayElements most elements accepted in one JSON array
33: int maxArrayElements,
51: private static final int ABSOLUTE_ARRAY_ELEMENTS_MAX = 100_000;
59: requirePositive(maxArrayElements, "maxArrayElements");
85: requireAtMost(maxArrayElements, ABSOLUTE_ARRAY_ELEMENTS_MAX, "maxArrayElements");
110: ABSOLUTE_ARRAY_ELEMENTS_MAX,
123: && maxArrayElements <= other.maxArrayElements
프로덕션이 등록하는 값과 그 필드 순서 :
* @param maxExecutionTime longest this operation may run before its deadline expires
* @param maxResponseBytes largest response this operation may produce
*/
public record WebRequestBudget(
int maxUriBytes,
int maxHeaderBytes,
int maxQueryParameters,
long maxBodyBytes,
int maxJsonDepth,
int maxArrayElements,
int maxMultipartParts,
public static WebRequestBudget standard() {
return new WebRequestBudget(
2048, 8192, 64, 256L * 1024, 32, 1_000, 16, Duration.ofSeconds(10), 4L * 1024 * 1024);
}
절대 상한을 쓰는 술어를 프로덕션에서 부르는 자리 : test · WebRequestBudgetTest.java:43 assertThat(WebRequestBudget.standard().withinPlatformMaximum()).isTrue(); test · WebRequestBudgetTest.java:58 () -> catalog.registerOverride(new WebBudgetProfileName("orders.bulk"), base, tighter)) test · WebRequestBudgetTest.java:66 catalog.registerOverride(
그 예산 목록은 프로덕션 자동설정이 만들고, 등록하는 예산은 하나다 :
WebMvcPlatformAutoConfiguration.java:67 WebBudgetCatalog catalog = new WebBudgetCatalog();
WebMvcPlatformAutoConfiguration.java:68 catalog.register(WebBudgetProfileName.standard(), WebRequestBudget.standard());
WebFluxPlatformAutoConfiguration.java:85 WebBudgetCatalog catalog = new WebBudgetCatalog();
WebFluxPlatformAutoConfiguration.java:86 catalog.register(WebBudgetProfileName.standard(), WebRequestBudget.standard());
WebRequestBudget 의 공개 팩토리 : 1
# 그 예산을 강제하는 필터가 실제로 읽는 필드
budget.maxBodyBytes()
budget.maxHeaderBytes()
budget.maxQueryParameters()
budget.maxResponseBytes()
budget.maxUriBytes()
반응형 필터가 자기 파일에서 읽는 필드 :
budget.maxBodyBytes()
budget.maxHeaderBytes()
budget.maxQueryParameters()
budget.maxUriBytes()
나머지 둘은 이 감싼 교환에 넘겨 잰다 :
64: .filter(new BoundedServerWebExchange(exchange, budget))
budget.maxBodyBytes()
budget.maxResponseBytes()
두 필터를 등록하는 자리 :
webfluxContractTest · ReactiveBudgetFixtureApplication.java
testkit · BudgetFixtureApplication.java
# 유계 팩토리가 파서에 거는 것과 그 이유
/**
* Builds the reader whose limits are enforced before a document is materialised.
*
* <p>The constraints go on the {@code JsonFactory} rather than being checked after parsing, and
* that placement is the whole point. A depth limit applied to a parsed tree has already paid for
* the tree; a nesting bomb is cheap to send and expensive to hold, so the only limit that helps is
* one the streaming parser refuses to exceed.
JsonFactoryBuilder builder =
JsonFactory.builder()
.streamReadConstraints(
StreamReadConstraints.builder()
.maxNestingDepth(profile.maxDepth())
.maxStringLength(profile.maxStringBytes())
.build());
if (profile.rejectDuplicateKeys()) {
builder.enable(StreamReadFeature.STRICT_DUPLICATE_DETECTION);
}
return builder.build();
# 파서가 실제로 제공하는 상한 전부
jackson-core-3.1.5.jar
maxNestingDepth(int)
maxDocumentLength(long)
maxTokenCount(long)
maxNumberLength(int)
maxStringLength(int)
maxNameLength(int)
유계 팩토리를 부르는 자리와 그것을 부르는 자동설정 :
WebMvcPlatformAutoConfiguration.java:135 return WebObjectMapperFactory.jsonMapper(
WebFluxPlatformAutoConfiguration.java:129 return WebObjectMapperFactory.jsonMapper(WebJsonProfile.strict());
WebObjectMapperFactory.java:59 JsonMapper.builder(BoundedJsonFactory.create(profile))
그중 이 저장소가 이미 쓰는 것 :
JacksonMessageCodec.java:213 .maxDocumentLength(maxBytes)
StrictWebSocketJsonCodec.java:64 .maxDocumentLength(budget.maxMessageBytes())
WebSocketCborCodec.java:76 .maxDocumentLength(binary.maxMessageBytes())
WebCborMapperFactory.java:55 .maxDocumentLength(budget.maxBodyBytes())
WebXmlMapperFactory.java:74 .maxDocumentLength(budget.maxBodyBytes())
LocalJsonSchemaRegistry.java:623 .maxDocumentLength(limits.maximumEnvelopeBytes())
같은 어댑터의 두 자리가 받는 예산 타입과 그 출하값 :
WebCborMapperFactory.java:41 public static ObjectMapper create(CodecBudget budget) {
WebXmlMapperFactory.java:39 public static ObjectMapper create(CodecBudget budget) {
WebXmlMapperFactory.java:56 public static ObjectMapper create(CodecBudget budget, XMLInputFactory input) {
21: int maxBodyBytes,
43: public static CodecBudget conventional(WebRepresentation representation) {
여섯 자리를 실제로 만드는 곳 :
main · JacksonMessageCodec.java:66 public static JacksonMessageCodec of(Map<MessageContractKey, Class<?>> registry) {
main · JacksonMessageCodec.java:77 public static JacksonMessageCodec of(Map<MessageContractKey, Class<?>> registry, int maxBytes) {
main · MessagingCoreAutoConfiguration.java:364 dev.caskeleton.messaging.schema.json.JacksonMessageCodec.of(
test · BinaryCodecRoundTripTest.java:118 new WebSocketCborCodec(
test · BinaryCodecRoundTripTest.java:64 return new WebSocketCborCodec(
test · JsonContractRegistryTest.java:108 JacksonMessageCodec.of(Map.of(new MessageContractKey(text, V1), String.class), exact);
test · JsonContractRegistryTest.java:37 JacksonMessageCodec.of(
test · JsonContractRegistryTest.java:87 JacksonMessageCodec.of(Map.of(new MessageContractKey(largeList, V1), List.class), 1_024);
test · JsonSchemaIntegrationEventEncoderTest.java:229 new LocalJsonSchemaRegistry(
test · JsonSchemaIntegrationEventEncoderTest.java:242 return new LocalJsonSchemaRegistry.SchemaSource(path, bytes, sha256(bytes));
test · LocalJsonSchemaRegistryTest.java:119 new LocalJsonSchemaRegistry(
test · LocalJsonSchemaRegistryTest.java:122 new LocalJsonSchemaRegistry.SchemaSource(
test · LocalJsonSchemaRegistryTest.java:134 new LocalJsonSchemaRegistry.SchemaSource(
test · LocalJsonSchemaRegistryTest.java:136 assertThatThrownBy(() -> new LocalJsonSchemaRegistry(duplicate, LIMITS))
test · LocalJsonSchemaRegistryTest.java:346 return new LocalJsonSchemaRegistry(
test · LocalJsonSchemaRegistryTest.java:355 return new LocalJsonSchemaRegistry(
test · LocalJsonSchemaRegistryTest.java:356 Map.of(path, new LocalJsonSchemaRegistry.SchemaSource(path, bytes, sha256(bytes))), LIMITS);
test · LocalJsonSchemaRegistryTest.java:367 return new LocalJsonSchemaRegistry.SchemaSource(path, bytes, sha256(bytes));
test · MessagingEvidenceManifestSchemaValidator.java:48 new LocalJsonSchemaRegistry(
test · MessagingEvidenceManifestSchemaValidator.java:51 new LocalJsonSchemaRegistry.SchemaSource(
test · WebCodecMapperTest.java:109 WebXmlMapperFactory.create(
test · WebCodecMapperTest.java:163 ObjectMapper mapper = WebCborMapperFactory.create(shallow);
test · WebCodecMapperTest.java:38 return WebCborMapperFactory.create(CodecBudget.conventional(WebRepresentation.CBOR));
test · WebCodecMapperTest.java:42 return WebXmlMapperFactory.create(CodecBudget.conventional(WebRepresentation.XML));
test · WebCodecMapperTest.java:86 () -> WebCborMapperFactory.create(CodecBudget.conventional(WebRepresentation.XML)))
test · WebSocketJsonWireManifestTest.java:49 new StrictWebSocketJsonCodec(
그 백엔드 잠금 항목이 실린 구성 :
tools.jackson.dataformat:jackson-dataformat-cbor:3.1.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
tools.jackson.dataformat:jackson-dataformat-xml:3.1.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
# 표본 애플리케이션은 컬렉션 본문에 크기 제약을 건다
206- public record BatchCreateRequest(
207- @Valid
208: @Size(max = MAX_BATCH_SIZE, message = "batch may not exceed " + MAX_BATCH_SIZE + " items")