ЁЯПл The SchoolтА║ЁЯУИ ScalingтА║ЁЯУЪ рдзрдбрд╛ 10 тАФ Read replicas + connection pools: рдиреЛрдВрджрд╡рд╣реАрдЪреНрдпрд╛ рдкреНрд░рддреА
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯУЪ рдзрдбрд╛ 10 тАФ Read replicas + connection pools: рдиреЛрдВрджрд╡рд╣реАрдЪреНрдпрд╛ рдкреНрд░рддреА

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 13 рдкреИрдХреА рдзрдбрд╛ 10 ┬╖ рдорд╛рдЧреАрд▓: lesson-09-caching-the-database ┬╖ рдкреБрдвреАрд▓: lesson-11-partitioning


ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ

рдзрдбреЗ 01тАУ09, рдЖрдгрд┐ database рдЪреА рджреЛрди рд╕рд╛рдзрдиреЗ: read replicas (reads рджреЗрдгрд╛рд▒реНрдпрд╛ рдЖрдгрд┐ рдереЛрдбреНрдпрд╛ рдорд╛рдЧреЗ рдЕрд╕рдгрд╛рд▒реНрдпрд╛ рдкреНрд░рддреА) рдЖрдгрд┐ рдПрдХ connection pool (RDS Proxy), рдЬреНрдпрд╛рдореБрд│реЗ рд╢реЗрдХрдбреЛ callers рдереЛрдбреЗ database connections рд╡рд╛рдЯреВрди рдШреЗрдК рд╢рдХрддрд╛рдд. scale/demo.py рдордзрд▓реЗ replicas() replica lag рдЖрдгрд┐ connection storm рджрд╛рдЦрд╡рддреЗ.

ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ

рдХрд╛рд░реНрдпрд╛рд▓рдпрд╛рдд рдПрдХрдЪ рдиреЛрдВрджрд╡рд╣реА ЁЯУТ рдЖрд╣реЗ. рд╢рд┐рдХреНрд╖рд┐рдХрд╛ рддреНрдпрд╛рдд grades рд▓рд┐рд╣рд┐рддрд╛рдд. рд╣рдЬрд╛рд░реЛ рдкрд╛рд▓рдХрд╛рдВрдирд╛ рддреА рд╡рд╛рдЪрд╛рдпрдЪреА рдЖрд╣реЗ. рдПрдХрд╛ рд╡реЗрд│реА рдПрдХрдЪ рд╡реНрдпрдХреНрддреА рдПрдЦрд╛рджреЗ рдкрд╛рди рдкрд╛рд╣реВ рд╢рдХрддреЗ.

рдореНрд╣рдгреВрди рджреАрдкрд┐рдХрд╛ рдиреЛрдВрджрд╡рд╣реАрдЪреНрдпрд╛ рдкреНрд░рддреА ЁЯУЪ рдмрдирд╡рддреЗ рдЖрдгрд┐ рддреНрдпрд╛ рдЗрддрд░ рдЯреЗрдмрд▓рд╛рдВрд╡рд░ рдареЗрд╡рддреЗ. рдкрд╛рд▓рдХ рдкреНрд░рддреА рд╡рд╛рдЪрддрд╛рдд. рд╢рд┐рдХреНрд╖рд┐рдХрд╛ рдЕрдЬреВрдирд╣реА рдлрдХреНрдд рдПрдХрд╛рдЪ рдЦрд▒реНрдпрд╛ рдиреЛрдВрджрд╡рд╣реАрдд рд▓рд┐рд╣рд┐рддрд╛рдд, рдЖрдгрд┐ рдПрдХ рдирд┐рд░реЛрдкреНрдпрд╛ рдкреНрд░рддреНрдпреЗрдХ рдирд╡реАрди grade рдкреНрд░рддреАрдВрдкрд░реНрдпрдВрдд рдкреЛрд╣реЛрдЪрд╡рддреЛ.

рдирд┐рд░реЛрдкреНрдпрд╛рд▓рд╛ рдПрдХ рдХреНрд╖рдг рд▓рд╛рдЧрддреЛ. рдХрддрд░рд┐рдирд╛рдЪреА рд╢рд┐рдХреНрд╖рд┐рдХрд╛ 10:00:00 рд▓рд╛ "A+" рд▓рд┐рд╣рд┐рддреЗ. рдХрддрд░рд┐рдирд╛ рд▓рдЧреЗрдЪ рдПрдХрд╛ рдкреНрд░рддреАрдХрдбреЗ рдзрд╛рд╡рддреЗ рдЖрдгрд┐ рдкрд╛рд╣рддреЗ тАФ рдкреНрд░рддреАрдд рдЕрдЬреВрди рдХрд╛рд╣реАрдЪ рдирд╛рд╣реА! рдПрдХрд╛ рдХреНрд╖рдгрд╛рдиреЗ рдирд┐рд░реЛрдкреНрдпрд╛ рдкреЛрд╣реЛрдЪрддреЛ рдЖрдгрд┐ A+ рджрд┐рд╕реВ рд▓рд╛рдЧрддреЛ. рддреЛ рдХреНрд╖рдг рдореНрд╣рдгрдЬреЗ replica lag.

рджреБрд╕рд░реА рдЕрдбрдЪрдг: рдиреЛрдВрджрд╡рд╣реАрдЪреНрдпрд╛ рдЦреЛрд▓реАрдд 500 рдЦреБрд░реНрдЪреНрдпрд╛ рдЖрд╣реЗрдд. 800 рдорджрддрдиреАрд╕рд╛рдВрдирд╛ рдкреНрд░рддреНрдпреЗрдХреА рд╕реНрд╡рддрдГрдЪреА рдЦреБрд░реНрдЪреА рд╣рд╡реА рдЖрд╣реЗ тАФ 300 рдЬрдгрд╛рдВрдирд╛ рдкрд░рдд рдкрд╛рдард╡рд▓реЗ рдЬрд╛рддреЗ. рдореНрд╣рдгреВрди рджреАрдкрд┐рдХрд╛ рджрд╛рд░рд╛рд╡рд░ рдПрдХ рд╕реНрд╡рд╛рдЧрддрд┐рдХрд╛ (proxy) рдмрд╕рд╡рддреЗ, рдЬрд┐рдЪреНрдпрд╛рдХрдбреЗ 100 рдЦреБрд░реНрдЪреНрдпрд╛ рдЖрд╣реЗрдд. рдорджрддрдиреАрд╕ рддреНрдпрд╛ рдЦреБрд░реНрдЪреНрдпрд╛рдВрд╡рд░ рдЖрд│реАрдкрд╛рд│реАрдиреЗ рдмрд╕рддрд╛рдд. рдХреЛрдгрд╛рд▓рд╛рд╣реА рдкрд░рдд рдкрд╛рдард╡рд▓реЗ рдЬрд╛рдд рдирд╛рд╣реА.

ЁЯЧ║я╕П рдЖрдХреГрддреА

flowchart LR
    app["ЁЯН│ 800 Lambda environments"] --> px["ЁЯЫОя╕П RDS Proxy<br/>pool of 100 connections"]
    px -->|"writes + read-your-own-writes"| pri["ЁЯУТ primary<br/>max_connections 500"]
    px -->|"other reads"| rep1["ЁЯУЪ read replica 1"]
    px -->|"other reads"| rep2["ЁЯУЪ read replica 2"]
    pri -.->|"asynchronous copy<br/>lag 200 ms"| rep1
    pri -.->|"asynchronous copy"| rep2

ЁЯЧ║я╕П рд░реЗрдЦрд╛рдЯрд▓реЗрд▓реА рдЖрд╡реГрддреНрддреА + рдПрдХ lab: https://school-edh.pages.dev/scaling/lesson-diagrams.html#l10

тЭУ рдХрд╛рдп

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг рдирд┐рдХрд╛рд▓рд╛рдЪреНрдпрд╛ рджрд┐рд╡рд╢реА database рдЪреЗ рдмрд╣реБрддреЗрдХ рдХрд╛рдо рд╡рд╛рдЪрдгреНрдпрд╛рдЪреЗ рдЕрд╕рддреЗ, рдЖрдгрд┐ reads рдкреНрд░рддреАрдВрд╡рд░ рдкрд╕рд░рд╡рддрд╛ рдпреЗрддрд╛рдд. рдкрдг рдкреНрд░рддреА рдереЛрдбреНрдпрд╛ рдорд╛рдЧреЗ рдЕрд╕рддрд╛рдд тАФ рдпрд╛рдХрдбреЗ рджреБрд░реНрд▓рдХреНрд╖ рдХреЗрд▓реЗ рддрд░ рд╢рд┐рдХреНрд╖рд┐рдХреЗрдиреЗ рдирд╡реАрди grade save рдХреЗрд▓реНрдпрд╛рд╡рд░ рд▓рдЧреЗрдЪ рдкрд╛рд▓рдХрд╛рд▓рд╛ рдЬреБрдирд╛ grade рджрд┐рд╕рддреЛ. рдЖрдгрд┐ рдХрд╛рд░рдг serverless рдЖрдгрд┐ pods рдХрд╛рд╣реА рд╕реЗрдХрдВрджрд╛рдВрдд рд╢реЗрдХрдбреЛ рдкреНрд░рддреАрдВрдкрд░реНрдпрдВрдд scale рд╣реЛрддрд╛рдд (рдзрдбреЗ 07тАУ08); pool рдирд╕реЗрд▓ рддрд░ рдкреНрд░рддреНрдпреЗрдХ рдкреНрд░рддреАрдЪреЗ connection рдореНрд╣рдгрдЬреЗ рдард░рд╛рд╡реАрдХ рдЦреБрд░реНрдЪреНрдпрд╛ рдЕрд╕рд▓реЗрд▓реНрдпрд╛ рдЦреЛрд▓реАрддрд▓реА рдЖрдгрдЦреА рдПрдХ рдЦреБрд░реНрдЪреА.

ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)

scale/sim.py рдордзрд▓реЗ Replica(lag_ms) рдкреНрд░рддреНрдпреЗрдХ write рддреНрдпрд╛рдЪреНрдпрд╛ рд╡реЗрд│реЗрд╕рд╣ рд╕рд╛рдард╡рддреЗ; read(key, t_ms) рд╣реЗ t_ms рдЪреНрдпрд╛ рдХрд┐рдорд╛рди lag_ms рдЖрдзреА рд▓рд┐рд╣рд┐рд▓реЗрд▓реА рд╕рд░реНрд╡рд╛рдд рдирд╡реАрди value рдкрд░рдд рдХрд░рддреЗ, рдирд╛рд╣реАрддрд░ None. connections(functions, per_function, max_connections, proxy_pool=None) рд╣реЗ callers рдирд╛ рдХрд┐рддреА connections рд╣рд╡реЗ рдЖрд╣реЗрдд, database рдХрд┐рддреА рдЙрдШрдбрддреЛ, рдЖрдгрд┐ рдХрд┐рддреА рдирд╛рдХрд╛рд░рд▓реЗ рдЬрд╛рддрд╛рдд рд╣реЗ рдкрд░рдд рдХрд░рддреЗ тАФ proxy рдЕрд╕реЗрд▓ рддрд░ database рд▓рд╛ рдлрдХреНрдд pool рджрд┐рд╕рддреЛ.

ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛

python3 scale/demo.py replicas
python3 - <<'EOF'
import sys; sys.path.insert(0, "scale"); from sim import Replica, connections
for lag in (20, 200, 2000):
    r = Replica(lag_ms=lag); r.write("grade:katrina", "A+", 1000)
    print(f"lag {lag:>4} ms тЖТ read at t=1100: {r.read('grade:katrina', 1100)} ┬╖ at t=3500: {r.read('grade:katrina', 3500)}")
r = Replica(200); r.write("grade:katrina", "B", 1000); r.write("grade:katrina", "A+", 2000)
print("regraded at t=2000; replica at t=2100 says:", r.read("grade:katrina", 2100))
for fns, per, pool in ((300, 1, None), (800, 2, None), (800, 2, 100), (800, 2, 600)):
    print(f"{fns} ├Ч {per} connection(s), max 500, pool {pool} тЖТ", connections(fns, per, 500, pool))
EOF

тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ

replicas рд╣реЗ рдЫрд╛рдкрддреЗ:

   read from the replica at t=1050 ms тЖТ None
   read from the replica at t=1150 ms тЖТ None
   read from the replica at t=1250 ms тЖТ A+

рдордЧ тФАтФА 800 Lambda environments ├Ч 1 connection, max_connections 500, proxy pool тАФ тЖТ opened 500, refused 300 рдЖрдгрд┐ тФАтФА 800 Lambda environments ├Ч 1 connection, max_connections 500, proxy pool 100 тЖТ opened 100, refused 0.

рддреБрдордЪрд╛ snippet рд╣реЗ рдЫрд╛рдкрддреЛ:

lag   20 ms тЖТ read at t=1100: A+ ┬╖ at t=3500: A+
lag  200 ms тЖТ read at t=1100: None ┬╖ at t=3500: A+
lag 2000 ms тЖТ read at t=1100: None ┬╖ at t=3500: A+
regraded at t=2000; replica at t=2100 says: B
300 ├Ч 1 connection(s), max 500, pool None тЖТ {'wanted': 300, 'opened': 300, 'refused': 0}
800 ├Ч 2 connection(s), max 500, pool None тЖТ {'wanted': 1600, 'opened': 500, 'refused': 1100}
800 ├Ч 2 connection(s), max 500, pool 100 тЖТ {'wanted': 1600, 'opened': 100, 'refused': 0}
800 ├Ч 2 connection(s), max 500, pool 600 тЖТ {'wanted': 1600, 'opened': 500, 'refused': 100}

ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ

Lag рдореНрд╣рдгрдЬреЗ рдлрдХреНрдд "рдЕрдЬреВрди рдХрд╛рд╣реАрдЪ рдирд╛рд╣реА" рдЕрд╕реЗ рдирд╛рд╣реА тАФ рддреА рдЬреБрдиреА value рд╕реБрджреНрдзрд╛ рдЕрд╕реВ рд╢рдХрддреЗ: A+ рдордзреНрдпреЗ regrade рдЭрд╛рд▓реНрдпрд╛рдирдВрддрд░ 100 ms рдиреА replica рдиреЗ B рд╕рд╛рдВрдЧрд┐рддрд▓реЗ, рдЬреЗ рдЬрд╛рд╕реНрдд рд╡рд╛рдИрдЯ рдЖрд╣реЗ. Proxy storm рд╕реБрдзрд╛рд░рддреЛ, рдкрдг рдлрдХреНрдд рддреНрдпрд╛рдЪрд╛ pool max_connections рдкреЗрдХреНрд╖рд╛ рд▓рд╣рд╛рди рдЕрд╕реЗрд▓ рддрд░рдЪ: 600 рдЪреНрдпрд╛ pool рдордзреНрдпреЗ рдЕрдЬреВрдирд╣реА 100 рдирд╛рдХрд╛рд░рд▓реЗ рдЬрд╛рддрд╛рдд.

тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд

рдЦрд▒реНрдпрд╛ account рд╡рд░ тАФ рдПрдХ read replica рдЬреЛрдбрд╛ рдЖрдгрд┐ рддрд┐рдЪрд╛ lag рдкрд╛рд╣рд╛:

aws rds create-db-instance-read-replica --db-instance-identifier school-db-replica-1 \
    --source-db-instance-identifier school-db --db-instance-class db.r7g.large
aws cloudwatch get-metric-statistics --namespace AWS/RDS --metric-name ReplicaLag \
    --dimensions Name=DBInstanceIdentifier,Value=school-db-replica-1 \
    --statistics Maximum --period 60 \
    --start-time 2026-05-20T03:00:00Z --end-time 2026-05-20T05:00:00Z

Primary рд╕рдореЛрд░ RDS Proxy рддрдпрд╛рд░ рдХрд░рд╛ (secret рдордзреНрдпреЗ database user рдЖрдгрд┐ password рдЕрд╕рддреЛ; role рдореБрд│реЗ proxy рддреЛ рд╡рд╛рдЪреВ рд╢рдХрддреЛ):

aws rds create-db-proxy --db-proxy-name school-proxy --engine-family POSTGRESQL \
    --auth '[{"AuthScheme":"SECRETS","SecretArn":"arn:aws:secretsmanager:ap-south-1:111122223333:secret:school-db-app-AbCdEf","IAMAuth":"REQUIRED"}]' \
    --role-arn arn:aws:iam::111122223333:role/school-proxy-secrets \
    --vpc-subnet-ids subnet-0aaa1111bbbb2222c subnet-0ddd3333eeee4444f \
    --vpc-security-group-ids sg-0123456789abcdef0 --require-tls
aws rds register-db-proxy-targets --db-proxy-name school-proxy --db-instance-identifiers school-db
aws rds modify-db-proxy-target-group --db-proxy-name school-proxy --target-group-name default \
    --connection-pool-config MaxConnectionsPercent=50

PostgreSQL рд╡рд░ рдЦрд░реА рдорд░реНрдпрд╛рджрд╛ рддрдкрд╛рд╕рд╛:

SHOW max_connections;
SELECT count(*) FROM pg_stat_activity;

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: code рдордзреНрдпреЗ рд╣реЗрддреВрдиреБрд╕рд╛рд░ route рдХрд░рд╛ тАФ db.write, db.read (replica) рдЖрдгрд┐ db.read_fresh (primary) тАФ рдореНрд╣рдгрдЬреЗ рдХреЛрдгреАрд╣реА рдЪреБрдХреВрди read-after-write replica рдХрдбреЗ рдкрд╛рдард╡рдд рдирд╛рд╣реА. рдкрд╣рд┐рд▓рд╛ Lambda database рд╢реА рдмреЛрд▓рдгреНрдпрд╛рдЖрдзреАрдЪ рддреНрдпрд╛рдЪреНрдпрд╛рд╕рдореЛрд░ proxy рдареЗрд╡рд╛.

тПня╕П рдкреБрдвреЗ

рдкреНрд░рддреА reads рд▓рд╛ рдорджрдд рдХрд░рддрд╛рдд. рдкрдг writes рдПрдХрд╛ primary рд▓рд╛ рдкреБрд░реВрди рдЙрд░рддрд╛рдд, рдХрд┐рдВрд╡рд╛ data рдПрдХрд╛ machine рд▓рд╛ рдкреБрд░реВрди рдЙрд░рддреЛ, рддреЗрд╡реНрд╣рд╛ рдиреЛрдВрджрд╡рд╣реАрдЪреА рд╡рд┐рднрд╛рдЧрдгреА рдХрд░рд╛рд╡реАрдЪ рд▓рд╛рдЧрддреЗ. рдкреБрдвреЗ: partitioning рдЖрдгрд┐ DynamoDB.

git checkout lesson-11-partitioning

ЁЯУЪ Lesson 10 тАФ Read replicas + connection pools: copies of the register

ЁЯУН You are here: Lesson 10 of 13 ┬╖ Previous: lesson-09-caching-the-database ┬╖ Next: lesson-11-partitioning


ЁЯУж What's in this branch

Lessons 01тАУ09, plus two database tools: read replicas (copies that serve reads and lag a little behind) and a connection pool (RDS Proxy) that lets hundreds of callers share a small number of database connections. replicas() in scale/demo.py shows replica lag and a connection storm.

ЁЯзТ Explain like I'm 5

The office has one register ЁЯУТ. Teachers write grades in it. Thousands of parents want to read it. Only one person can look at a page at a time.

So Dipika makes copies of the register ЁЯУЪ and puts them on other tables. Parents read the copies. Teachers still write only in the one real register, and a runner carries each new grade to the copies.

The runner needs a moment. Katrina's teacher writes "A+" at 10:00:00. Katrina runs to a copy at once and looks тАФ the copy still shows nothing! A moment later, the runner arrives and the A+ appears. That moment is replica lag.

A second problem: the register room has 500 chairs. 800 helpers each want their own chair тАФ 300 are sent away. So Dipika puts a receptionist at the door (the proxy) with 100 chairs. Helpers take turns on those chairs. Nobody is sent away.

ЁЯЧ║я╕П Diagram

flowchart LR
    app["ЁЯН│ 800 Lambda environments"] --> px["ЁЯЫОя╕П RDS Proxy<br/>pool of 100 connections"]
    px -->|"writes + read-your-own-writes"| pri["ЁЯУТ primary<br/>max_connections 500"]
    px -->|"other reads"| rep1["ЁЯУЪ read replica 1"]
    px -->|"other reads"| rep2["ЁЯУЪ read replica 2"]
    pri -.->|"asynchronous copy<br/>lag 200 ms"| rep1
    pri -.->|"asynchronous copy"| rep2

ЁЯЧ║я╕П Drawn version + a lab: https://school-edh.pages.dev/scaling/lesson-diagrams.html#l10

тЭУ What

ЁЯдФ Why

Because on results day most database work is reading, and reads can be spread over copies. But copies are a little behind тАФ ignore that and a parent sees an old grade right after a teacher saved the new one. And because serverless and pods scale to hundreds of copies in seconds (lessons 07тАУ08); without a pool, each copy's connection is one more chair in a room with a fixed number of chairs.

ЁЯФз How (in this repo)

Replica(lag_ms) in scale/sim.py stores each write with its time; read(key, t_ms) returns the latest value written at least lag_ms before t_ms, or None. connections(functions, per_function, max_connections, proxy_pool=None) returns how many connections the callers want, how many the database opens, and how many are refused тАФ with a proxy, the database only sees the pool.

ЁЯзк Try it

python3 scale/demo.py replicas
python3 - <<'EOF'
import sys; sys.path.insert(0, "scale"); from sim import Replica, connections
for lag in (20, 200, 2000):
    r = Replica(lag_ms=lag); r.write("grade:katrina", "A+", 1000)
    print(f"lag {lag:>4} ms тЖТ read at t=1100: {r.read('grade:katrina', 1100)} ┬╖ at t=3500: {r.read('grade:katrina', 3500)}")
r = Replica(200); r.write("grade:katrina", "B", 1000); r.write("grade:katrina", "A+", 2000)
print("regraded at t=2000; replica at t=2100 says:", r.read("grade:katrina", 2100))
for fns, per, pool in ((300, 1, None), (800, 2, None), (800, 2, 100), (800, 2, 600)):
    print(f"{fns} ├Ч {per} connection(s), max 500, pool {pool} тЖТ", connections(fns, per, 500, pool))
EOF

тЬЕ Verify тАФ what you should see

replicas prints:

   read from the replica at t=1050 ms тЖТ None
   read from the replica at t=1150 ms тЖТ None
   read from the replica at t=1250 ms тЖТ A+

then тФАтФА 800 Lambda environments ├Ч 1 connection, max_connections 500, proxy pool тАФ тЖТ opened 500, refused 300 and тФАтФА 800 Lambda environments ├Ч 1 connection, max_connections 500, proxy pool 100 тЖТ opened 100, refused 0.

Your snippet prints:

lag   20 ms тЖТ read at t=1100: A+ ┬╖ at t=3500: A+
lag  200 ms тЖТ read at t=1100: None ┬╖ at t=3500: A+
lag 2000 ms тЖТ read at t=1100: None ┬╖ at t=3500: A+
regraded at t=2000; replica at t=2100 says: B
300 ├Ч 1 connection(s), max 500, pool None тЖТ {'wanted': 300, 'opened': 300, 'refused': 0}
800 ├Ч 2 connection(s), max 500, pool None тЖТ {'wanted': 1600, 'opened': 500, 'refused': 1100}
800 ├Ч 2 connection(s), max 500, pool 100 тЖТ {'wanted': 1600, 'opened': 100, 'refused': 0}
800 ├Ч 2 connection(s), max 500, pool 600 тЖТ {'wanted': 1600, 'opened': 500, 'refused': 100}

ЁЯПБ What you just proved

Lag is not only "nothing yet" тАФ it can be the old value: the replica said B 100 ms after the regrade to A+, which is worse. A proxy fixes the storm, but only if its pool is smaller than max_connections: a pool of 600 still gets 100 refused.

тЪая╕П Common mistakes

ЁЯПн In production

On a real account тАФ add a read replica and watch its lag:

aws rds create-db-instance-read-replica --db-instance-identifier school-db-replica-1 \
    --source-db-instance-identifier school-db --db-instance-class db.r7g.large
aws cloudwatch get-metric-statistics --namespace AWS/RDS --metric-name ReplicaLag \
    --dimensions Name=DBInstanceIdentifier,Value=school-db-replica-1 \
    --statistics Maximum --period 60 \
    --start-time 2026-05-20T03:00:00Z --end-time 2026-05-20T05:00:00Z

Create RDS Proxy in front of the primary (the secret holds the database user and password; the role lets the proxy read it):

aws rds create-db-proxy --db-proxy-name school-proxy --engine-family POSTGRESQL \
    --auth '[{"AuthScheme":"SECRETS","SecretArn":"arn:aws:secretsmanager:ap-south-1:111122223333:secret:school-db-app-AbCdEf","IAMAuth":"REQUIRED"}]' \
    --role-arn arn:aws:iam::111122223333:role/school-proxy-secrets \
    --vpc-subnet-ids subnet-0aaa1111bbbb2222c subnet-0ddd3333eeee4444f \
    --vpc-security-group-ids sg-0123456789abcdef0 --require-tls
aws rds register-db-proxy-targets --db-proxy-name school-proxy --db-instance-identifiers school-db
aws rds modify-db-proxy-target-group --db-proxy-name school-proxy --target-group-name default \
    --connection-pool-config MaxConnectionsPercent=50

Check the real limit on PostgreSQL:

SHOW max_connections;
SELECT count(*) FROM pg_stat_activity;

ЁЯПн Why this matters in production: route by intent in code тАФ db.write, db.read (replica) and db.read_fresh (primary) тАФ so nobody sends a read-after-write to a replica by accident. Put a proxy in front of the database before the first Lambda talks to it.

тПня╕П Next

Copies help reads. But when writes outgrow one primary, or the data outgrows one machine, you must split the register. Next: partitioning and DynamoDB.

git checkout lesson-11-partitioning
тЖР Previouscaching the databaseNext тЖТpartitioning

This page is the lesson's README from the lesson-10-replicas-and-pools branch, shown here so the whole School stays on one site. Code files open on GitHub at the same branch.