Skip to content

Commit 2f3e62a

Browse files
slayerjainclaude
andauthored
fix(ps-cache-kotlin): correct README ports, DB config, and reproduction steps (#124)
- Fix app port: 8080 (not 8090) per application.properties - Fix DB config: testdb/postgres/postgres on port 5432 (not demodb/testpass on 5433) - Add Docker Compose option as the recommended approach - Document environment variables with their defaults - Fix scoring description: +5 tiebreaker bonus (not +50/-50) - Add note about variable test numbering - Quote curl URLs for shell safety Signed-off-by: slayerjain <shubhamkjain@outlook.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 5c6aeac commit 2f3e62a

1 file changed

Lines changed: 62 additions & 33 deletions

File tree

ps-cache-kotlin/README.md

Lines changed: 62 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ The JDBC driver (PostgreSQL JDBC + HikariCP) caches prepared statements per conn
1111
3. Sort-order prediction starts from 0 on a fresh connection, pointing to the wrong window's mocks
1212

1313
### Real-world impact
14-
This was reported by a customer running a Kotlin/Spring Boot app with Agoda's travel account service. Test-6 (member_id=31) returned Alice's data (member_id=19) instead of Charlie's — **silently returning the wrong customer's financial data**.
14+
This was reported by a customer running a Kotlin/Spring Boot app with Agoda's travel account service. The post-eviction test returned Alice's data (member_id=19) instead of Charlie's — **silently returning the wrong customer's financial data**.
1515

1616
## Architecture
1717

@@ -39,84 +39,113 @@ This was reported by a customer running a Kotlin/Spring Boot app with Agoda's tr
3939

4040
## How to Reproduce the Bug
4141

42-
### Prerequisites
42+
### Option A: Using Docker Compose (recommended)
43+
4344
```bash
44-
docker run -d --name pg-demo -e POSTGRES_PASSWORD=testpass -e POSTGRES_DB=demodb -p 5433:5432 postgres:16
45+
docker compose up --build
4546
```
4647

47-
### Pre-create the schema
48+
This starts PostgreSQL (with schema + seed data via `init.sql`) and the app on port 8080.
49+
50+
### Option B: Standalone
51+
52+
**Prerequisites:** Java 21, Maven, PostgreSQL running on localhost:5432.
53+
4854
```bash
49-
docker exec pg-demo psql -U postgres -d demodb -c "
50-
CREATE SCHEMA IF NOT EXISTS travelcard;
51-
CREATE TABLE IF NOT EXISTS travelcard.travel_account (
55+
# Start Postgres (if not already running)
56+
docker run -d --name pg-demo \
57+
-e POSTGRES_USER=postgres \
58+
-e POSTGRES_PASSWORD=postgres \
59+
-e POSTGRES_DB=testdb \
60+
-p 5432:5432 postgres:16
61+
62+
# Create the schema and seed data
63+
docker exec pg-demo psql -U postgres -d testdb -f- <<'SQL'
64+
CREATE SCHEMA IF NOT EXISTS travelcard;
65+
CREATE TABLE IF NOT EXISTS travelcard.travel_account (
5266
id SERIAL PRIMARY KEY, member_id INT NOT NULL UNIQUE,
5367
name TEXT NOT NULL, balance INT NOT NULL DEFAULT 0);
54-
INSERT INTO travelcard.travel_account (member_id, name, balance) VALUES
68+
INSERT INTO travelcard.travel_account (member_id, name, balance) VALUES
5569
(19, 'Alice', 1000), (23, 'Bob', 2500),
56-
(31, 'Charlie', 500), (42, 'Diana', 7500);"
57-
```
70+
(31, 'Charlie', 500), (42, 'Diana', 7500)
71+
ON CONFLICT (member_id) DO NOTHING;
72+
SQL
5873

59-
### Build the app
60-
```bash
74+
# Build
6175
mvn package -DskipTests -q
6276
```
6377

64-
### With the OLD keploy binary (demonstrates failure)
78+
### Record and replay
79+
6580
```bash
6681
# Record
6782
sudo keploy record -c "java -jar target/kotlin-app-1.0.0.jar"
68-
# Hit endpoints:
69-
curl http://localhost:8090/account?member=19
70-
curl http://localhost:8090/account?member=23
71-
curl http://localhost:8090/evict
72-
curl http://localhost:8090/account?member=31
73-
curl http://localhost:8090/account?member=42
74-
# Stop recording (Ctrl+C)
75-
76-
# Reset DB and replay
77-
docker exec pg-demo psql -U postgres -d demodb -c "TRUNCATE travelcard.travel_account; INSERT INTO ..."
83+
84+
# In another terminal, hit endpoints in order:
85+
curl "http://localhost:8080/account?member=19" # Alice (warms PS cache on Connection A)
86+
curl "http://localhost:8080/account?member=23" # Bob (cached PS, Bind-only)
87+
curl "http://localhost:8080/evict" # Force HikariCP to evict connections
88+
curl "http://localhost:8080/account?member=31" # Charlie (new Connection B, cold PS cache)
89+
curl "http://localhost:8080/account?member=42" # Diana (cached PS on Connection B)
90+
91+
# Or run the traffic script:
92+
# bash test.sh
93+
94+
# Stop recording (Ctrl+C), then replay:
7895
sudo keploy test -c "java -jar target/kotlin-app-1.0.0.jar" --skip-coverage
7996
```
8097

81-
**Expected failure (without fix):**
98+
**Expected failure (without fix):** The post-eviction `/account?member=31` test fails:
8299
```
83-
test-5 (/account?member=31):
84-
EXPECTED: {"memberId":31, "name":"Charlie", "balance":500}
85-
ACTUAL: {"memberId":19, "name":"Alice", "balance":1000} ← WRONG PERSON
100+
EXPECTED: {"id":3, "memberId":31, "name":"Charlie", "balance":500}
101+
ACTUAL: {"id":1, "memberId":19, "name":"Alice", "balance":1000} <- WRONG PERSON
86102
```
87103

104+
> **Note:** The exact test number that fails depends on how many health-check
105+
> requests keploy captures during recording (typically test-5 through test-7).
106+
88107
**With obfuscation enabled (worse):**
89108
```
90-
test-5: EXPECTED Charlie ACTUAL Alice
91-
test-6: EXPECTED Diana ACTUAL Bob TWO wrong results
109+
Post-eviction member=31: EXPECTED Charlie -> ACTUAL Alice
110+
Post-eviction member=42: EXPECTED Diana -> ACTUAL Bob <- TWO wrong results
92111
```
93112

94113
### With the FIXED keploy binary
95114
```bash
96-
# Same steps all tests pass, correct data for each member
115+
# Same steps -> all tests pass, correct data for each member
97116
```
98117

99118
## What the Fix Does
100119

101120
The fix adds **recording-connection affinity** to the Postgres mock matcher (see [keploy/integrations#121](https://github.com/keploy/integrations/pull/121)):
102121

103122
1. When the first `Bind` mock is consumed on a replay connection, its recording `connID` is stored
104-
2. Subsequent scoring applies +50/-50 bonus/penalty to prefer mocks from the same recording window
123+
2. Subsequent scoring applies a small tiebreaker bonus to prefer mocks from the same recording connection
105124
3. Only activates when 2+ distinct recording connections exist (zero impact on single-connection apps)
106125

107126
## Configuration
108127

109128
### application.properties
110129
| Property | Value | Purpose |
111130
|----------|-------|---------|
131+
| `server.port` | `8080` | HTTP server port |
112132
| `spring.datasource.hikari.maximum-pool-size` | `1` | Forces all requests through one connection |
113133
| `prepareThreshold=1` | JDBC URL param | Caches PS after first use |
114-
| `spring.sql.init.mode` | `never` | Schema created externally |
134+
| `spring.sql.init.mode` | `never` | Schema created externally (via init.sql) |
135+
136+
### Environment variables (defaults in parentheses)
137+
| Variable | Default | Description |
138+
|----------|---------|-------------|
139+
| `DB_HOST` | `localhost` | PostgreSQL host |
140+
| `DB_PORT` | `5432` | PostgreSQL port |
141+
| `DB_NAME` | `testdb` | Database name |
142+
| `DB_USER` | `postgres` | Database user |
143+
| `DB_PASSWORD` | `postgres` | Database password |
115144

116145
### Endpoints
117146

118147
| Endpoint | Description |
119148
|----------|-------------|
120149
| `GET /health` | Health check |
121-
| `GET /account?member=N` | Query travel_account by member_id (BEGIN SELECT COMMIT) |
150+
| `GET /account?member=N` | Query travel_account by member_id (BEGIN -> SELECT -> COMMIT) |
122151
| `GET /evict` | Soft-evict HikariCP connections (forces new PG connection) |

0 commit comments

Comments
 (0)