remove unnecessary index in CoinStore & add additional benchmarks (#8641)

* simplify and extend the coin store benchmark to include lookups by coin ID and get_coin_removed_at_height

* remove coin_spent index from coin store table. It's not necessary and slows down updates (and caused one mistaken query plan)
This commit is contained in:
Arvid Norberg 2021-10-12 18:27:47 +02:00 committed by GitHub
parent 653a11476c
commit bbc2f76a21
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 117 additions and 58 deletions

View File

@ -4,7 +4,7 @@ import secrets
from time import time
from pathlib import Path
from chia.full_node.coin_store import CoinStore
from typing import List
from typing import List, Tuple
import os
import sys
@ -14,11 +14,14 @@ from chia.consensus.coinbase import create_farmer_coin, create_pool_coin
from chia.consensus.default_constants import DEFAULT_CONSTANTS
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.blockchain_format.coin import Coin
from chia.util.ints import uint64
from chia.util.ints import uint64, uint32
NUM_ITERS = 200
# farmer puzzle hash
ph = bytes32(b"a" * 32)
async def setup_db() -> DBWrapper:
db_filename = Path("coin-store-benchmark.db")
@ -40,46 +43,57 @@ def make_coin() -> Coin:
return Coin(rand_hash(), rand_hash(), uint64(1))
def make_coins(num: int) -> Tuple[List[Coin], List[bytes32]]:
additions: List[Coin] = []
hashes: List[bytes32] = []
for i in range(num):
c = make_coin()
additions.append(c)
hashes.append(c.get_hash())
return additions, hashes
def rewards(height: uint32) -> Tuple[Coin, Coin]:
farmer_coin = create_farmer_coin(height, ph, uint64(250000000), DEFAULT_CONSTANTS.GENESIS_CHALLENGE)
pool_coin = create_pool_coin(height, ph, uint64(1750000000), DEFAULT_CONSTANTS.GENESIS_CHALLENGE)
return farmer_coin, pool_coin
async def run_new_block_benchmark():
db_wrapper: DBWrapper = await setup_db()
try:
coin_store = await CoinStore.create(db_wrapper)
# farmer puzzle hash
ph = bytes32(b"a" * 32)
all_added: List[bytes32] = []
all_unspent: List[bytes32] = []
all_coins: List[bytes32] = []
block_height = 1
timestamp = 1631794488
print("Building database ", end="")
for height in range(block_height, block_height + NUM_ITERS):
additions = []
removals = []
# add some new coins
for i in range(2000):
c = make_coin()
additions.append(c)
all_added.append(c.get_hash())
additions, hashes = make_coins(2000)
# farm rewards
farmer_coin = create_farmer_coin(height, ph, 250000000, DEFAULT_CONSTANTS.GENESIS_CHALLENGE)
pool_coin = create_pool_coin(height, ph, 1750000000, DEFAULT_CONSTANTS.GENESIS_CHALLENGE)
reward_coins = [pool_coin, farmer_coin]
all_added += [pool_coin.name(), farmer_coin.name()]
farmer_coin, pool_coin = rewards(height)
all_coins += hashes
all_unspent += hashes
all_unspent += [pool_coin.name(), farmer_coin.name()]
# remove some coins we've added previously
random.shuffle(all_added)
removals = all_added[:100]
all_added = all_added[100:]
random.shuffle(all_unspent)
removals = all_unspent[:100]
all_unspent = all_unspent[100:]
await coin_store.new_block(
height,
timestamp,
set(reward_coins),
set([pool_coin, farmer_coin]),
additions,
removals,
)
@ -97,33 +111,28 @@ async def run_new_block_benchmark():
total_remove = 0
print("\nProfiling mostly additions ", end="")
for height in range(block_height, block_height + NUM_ITERS):
additions = []
removals = []
# add some new coins
for i in range(2000):
c = make_coin()
additions.append(c)
all_added.append(c.get_hash())
additions, hashes = make_coins(2000)
total_add += 2000
farmer_coin = create_farmer_coin(height, ph, 250000000, DEFAULT_CONSTANTS.GENESIS_CHALLENGE)
pool_coin = create_pool_coin(height, ph, 1750000000, DEFAULT_CONSTANTS.GENESIS_CHALLENGE)
reward_coins = [pool_coin, farmer_coin]
all_added += [pool_coin.name(), farmer_coin.name()]
farmer_coin, pool_coin = rewards(height)
all_coins += hashes
all_unspent += hashes
all_unspent += [pool_coin.name(), farmer_coin.name()]
total_add += 2
# remove some coins we've added previously
random.shuffle(all_added)
removals = all_added[:100]
all_added = all_added[100:]
random.shuffle(all_unspent)
removals = all_unspent[:100]
all_unspent = all_unspent[100:]
total_remove += 100
start = time()
await coin_store.new_block(
height,
timestamp,
set(reward_coins),
set([pool_coin, farmer_coin]),
additions,
removals,
)
@ -147,31 +156,29 @@ async def run_new_block_benchmark():
total_time = 0
for height in range(block_height, block_height + NUM_ITERS):
additions = []
removals = []
# add one new coins
c = make_coin()
additions.append(c)
all_added.append(c.get_hash())
total_add += 1
farmer_coin = create_farmer_coin(height, ph, 250000000, DEFAULT_CONSTANTS.GENESIS_CHALLENGE)
pool_coin = create_pool_coin(height, ph, 1750000000, DEFAULT_CONSTANTS.GENESIS_CHALLENGE)
reward_coins = [pool_coin, farmer_coin]
all_added += [pool_coin.name(), farmer_coin.name()]
farmer_coin, pool_coin = rewards(height)
all_coins += [c.get_hash()]
all_unspent += [c.get_hash()]
all_unspent += [pool_coin.name(), farmer_coin.name()]
total_add += 2
# remove some coins we've added previously
random.shuffle(all_added)
removals = all_added[:700]
all_added = all_added[700:]
random.shuffle(all_unspent)
removals = all_unspent[:700]
all_unspent = all_unspent[700:]
total_remove += 700
start = time()
await coin_store.new_block(
height,
timestamp,
set(reward_coins),
set([pool_coin, farmer_coin]),
additions,
removals,
)
@ -195,33 +202,28 @@ async def run_new_block_benchmark():
total_remove = 0
total_time = 0
for height in range(block_height, block_height + NUM_ITERS):
additions = []
removals = []
# add some new coins
for i in range(2000):
c = make_coin()
additions.append(c)
all_added.append(c.get_hash())
additions, hashes = make_coins(2000)
total_add += 2000
farmer_coin = create_farmer_coin(height, ph, 250000000, DEFAULT_CONSTANTS.GENESIS_CHALLENGE)
pool_coin = create_pool_coin(height, ph, 1750000000, DEFAULT_CONSTANTS.GENESIS_CHALLENGE)
reward_coins = [pool_coin, farmer_coin]
all_added += [pool_coin.name(), farmer_coin.name()]
farmer_coin, pool_coin = rewards(height)
all_coins += hashes
all_unspent += hashes
all_unspent += [pool_coin.name(), farmer_coin.name()]
total_add += 2
# remove some coins we've added previously
random.shuffle(all_added)
removals = all_added[:2000]
all_added = all_added[2000:]
random.shuffle(all_unspent)
removals = all_unspent[:2000]
all_unspent = all_unspent[2000:]
total_remove += 2000
start = time()
await coin_store.new_block(
height,
timestamp,
set(reward_coins),
set([pool_coin, farmer_coin]),
additions,
removals,
)
@ -235,8 +237,62 @@ async def run_new_block_benchmark():
print(".", end="")
sys.stdout.flush()
block_height += NUM_ITERS
print(f"\nFULLBLOCKS, time: {total_time:0.4f}s additions: {total_add} removals: {total_remove}")
print("profiling get_coin_records_by_names, include_spent ", end="")
total_time = 0
found_coins = 0
for i in range(NUM_ITERS):
lookup = random.sample(all_coins, 200)
start = time()
records = await coin_store.get_coin_records_by_names(True, lookup)
total_time += time() - start
assert len(records) == 200
found_coins += len(records)
print(".", end="")
sys.stdout.flush()
print(
f"\nGET RECORDS BY NAMES with spent, time: {total_time:0.4f}s {NUM_ITERS} "
f"lookups found {found_coins} coins in total"
)
print("profiling get_coin_records_by_names, without spent coins ", end="")
total_time = 0
found_coins = 0
for i in range(NUM_ITERS):
lookup = random.sample(all_coins, 200)
start = time()
records = await coin_store.get_coin_records_by_names(False, lookup)
total_time += time() - start
assert len(records) <= 200
found_coins += len(records)
print(".", end="")
sys.stdout.flush()
print(
f"\nGET RECORDS BY NAMES without spent, time: {total_time:0.4f}s {NUM_ITERS} "
f"lookups found {found_coins} coins in total"
)
print("profiling get_coin_removed_at_height ", end="")
total_time = 0
found_coins = 0
for i in range(1, block_height):
start = time()
records = await coin_store.get_coins_removed_at_height(i)
total_time += time() - start
found_coins += len(records)
print(".", end="")
sys.stdout.flush()
print(
f"\nGET COINS REMOVED AT HEIGHT, time: {total_time:0.4f}s {block_height-1} blocks, "
f"found {found_coins} coins in total"
)
finally:
await db_wrapper.db.close()

View File

@ -55,7 +55,10 @@ class CoinStore:
await self.coin_record_db.execute("CREATE INDEX IF NOT EXISTS coin_spent_index on coin_record(spent_index)")
await self.coin_record_db.execute("CREATE INDEX IF NOT EXISTS coin_spent on coin_record(spent)")
# earlier versions of chia created this index despite no lookups needing
# it. For now, just don't create it for new installs. In the future we
# may remove the index from existing installations as well
# await self.coin_record_db.execute("DROP INDEX IF EXISTS coin_spent")
await self.coin_record_db.execute("CREATE INDEX IF NOT EXISTS coin_puzzle_hash on coin_record(puzzle_hash)")