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>
113 lines
3.8 KiB
Plaintext
113 lines
3.8 KiB
Plaintext
### 여러 키를 읽어 하나에 쓰는 연산 일곱의 서명
|
|
<V> Set<V> difference(
|
|
Collection<SetKey<V>> keys,
|
|
AdvancedOperationPermit permit,
|
|
MultiKeyPermit multiKeyPermit,
|
|
OperationBudget budget);
|
|
<V> Set<V> intersection(
|
|
Collection<SetKey<V>> keys,
|
|
AdvancedOperationPermit permit,
|
|
MultiKeyPermit multiKeyPermit,
|
|
OperationBudget budget);
|
|
<V> Set<V> union(
|
|
Collection<SetKey<V>> keys,
|
|
AdvancedOperationPermit permit,
|
|
MultiKeyPermit multiKeyPermit,
|
|
OperationBudget budget);
|
|
long bitOperation(
|
|
BitmapOperation operation,
|
|
BitmapKey destination,
|
|
Collection<BitmapKey> sources,
|
|
MultiKeyPermit permit,
|
|
OperationBudget budget);
|
|
<V> long searchStore(
|
|
GeoKey<V> source,
|
|
GeoKey<V> destination,
|
|
GeoSearchRequest<V> request,
|
|
MultiKeyPermit permit,
|
|
OperationBudget budget);
|
|
long count(Collection<? extends HyperLogLogKey<?>> keys, MultiKeyPermit permit);
|
|
void merge(
|
|
HyperLogLogKey<?> destination,
|
|
Collection<? extends HyperLogLogKey<?>> sources,
|
|
MultiKeyPermit permit);
|
|
|
|
### 정책 카탈로그의 위험 등급
|
|
SDIFF:
|
|
risk: R2
|
|
support: ADVANCED_TYPED
|
|
read-only: true
|
|
key-spec: "1 -1 1"
|
|
required-policy: set-algebra
|
|
SINTER:
|
|
risk: R2
|
|
support: ADVANCED_TYPED
|
|
read-only: true
|
|
key-spec: "1 -1 1"
|
|
required-policy: set-algebra
|
|
SUNION:
|
|
risk: R2
|
|
support: ADVANCED_TYPED
|
|
read-only: true
|
|
key-spec: "1 -1 1"
|
|
required-policy: set-algebra
|
|
BITOP:
|
|
risk: R2
|
|
support: ADVANCED_TYPED
|
|
key-spec: "2 -1 1"
|
|
required-policy: multi-key-write
|
|
PFCOUNT:
|
|
risk: R2
|
|
support: ADVANCED_TYPED
|
|
read-only: true
|
|
key-spec: "1 -1 1"
|
|
required-policy: multi-key-read
|
|
PFMERGE:
|
|
risk: R2
|
|
support: ADVANCED_TYPED
|
|
key-spec: "1 -1 1"
|
|
required-policy: multi-key-write
|
|
GEOSEARCHSTORE:
|
|
risk: R2
|
|
support: ADVANCED_TYPED
|
|
key-spec: "1 2 1"
|
|
required-policy: multi-key-write
|
|
|
|
### R2 요청에 budget이 비어 있으면
|
|
}
|
|
if (request.budget().isEmpty()) {
|
|
throw new RedisCommandRejectedException(
|
|
"R2 command requires permit and budget", metadata(policy, OptionalInt.empty()));
|
|
}
|
|
|
|
### 그래서 HyperLogLog 요청은 budget을 스스로 만든다
|
|
69: Optional.of(context.collectionBudget(rendered.qualified().size(), rendered.requestBytes())),
|
|
93: Optional.of(context.collectionBudget(qualified.size(), size)),
|
|
long replyCeiling = (long) elements * limits.maxReplyBytesPerElement();
|
|
return new OperationBudget(
|
|
elements, Math.max(1L, requestBytes), replyCeiling, limits.collectionTimeout());
|
|
|
|
### 같은 식을 쓰는 다른 두 메서드
|
|
313: elements, Math.max(1L, requestBytes), replyCeiling, limits.collectionTimeout());
|
|
336: Math.max(1L, requestBytes),
|
|
349: 1, Math.max(1L, requestBytes), limits.maxReplyBytesPerElement(), limits.scriptTimeout());
|
|
|
|
### 그 설계를 적어 둔 곳
|
|
/**
|
|
* The ceilings the typed operations apply when the public signature does not carry a budget.
|
|
*
|
|
* <p>Design section 10 gives some R2 methods a caller-supplied {@code OperationBudget} and others a
|
|
* caller-supplied permit, but {@code CommandPolicyGuard} requires both for every R2 command. These
|
|
* limits are what the SDK fills in for the half the signature omits, so an R2 command is never
|
|
* admitted with an unbounded cost. The caller-supplied half always wins; this only supplies what
|
|
* the caller had no way to pass.
|
|
*
|
|
|
|
### 같은 패키지에서 반대로 적어 둔 곳
|
|
/**
|
|
* Explicit bound a caller accepts for one advanced operation.
|
|
*
|
|
* <p>Every R2 API requires a budget. The budget is never optional and never defaulted, because the
|
|
* whole point is that the caller states the cost it is prepared to pay before Redis is asked.
|
|
*/
|