Luk*_*don 4 postgresql upsert json hashing google-cloud-sql
我想使用 Postgres(CloudSQL 中的 11)作为高效的键值存储。我有大约 200GB 的字典(平均大小为 10kB,结构可以不同并且可以嵌套)。我正在考虑利用改进的哈希索引。这是架构:
\n\n CREATE EXTENSION IF NOT EXISTS "uuid-ossp";\n\n CREATE TABLE IF NOT EXISTS key_val (\n id uuid DEFAULT uuid_generate_v4(),\n value jsonb,\n EXCLUDE using hash (id with =)\n );\n\n CREATE INDEX IF NOT EXISTS idx_key_val ON key_val USING hash (id);\n
Run Code Online (Sandbox Code Playgroud)\n\n获取、更新和插入非常简单,但我不知道如何实现高效的更新插入。
\n\nINSERT INTO key_val VALUES ($1, $2) ON CONFLICT ON CONSTRAINT key_val_id_excl DO UPDATE SET value = ($2)\n
Run Code Online (Sandbox Code Playgroud)\n\n结果是WrongObjectTypeError ON CONFLICT DO UPDATE not supported with exclusion constraints
可能的解决方案:
\n\n编辑
\n\n信息:\nMac 2.3 GHz Intel Core i9;16GB 内存\n(PostgreSQL) 11.4
\n\n\\d+\npublic | user_profiles | table | postgres | 16 GB\n# num of records\nSELECT COUNT(*) FROM user_profiles -> 3 095 348\n# hash index\nSELECT pg_table_size(\'idx_user_profiles\');\n87 334 912\nSELECT pg_table_size(\'idx_user_profiles_btree\')\n97 705 984\n
Run Code Online (Sandbox Code Playgroud)\n\n对于 B 树
\n\npostgres=# \\d user_profiles\n Table "public.user_profiles"\n Column | Type | Collation | Nullable | Default\n--------+-------+-----------+----------+--------------------\n key | uuid | | not null | uuid_generate_v4()\n value | jsonb | | |\nIndexes:\n "user_profiles_pkey" PRIMARY KEY, btree (key)\n\n\npostgres=# SELECT * FROM user_profiles WHERE key = \'2cfc4dbf-a1b9-46b3-8c15-a03f51dde890\';\nTime: 3.126 ms\n\nINSERT INTO user_profiles (value) VALUES (\'{"type": "_app_retail","user_id": "a628....... 0 ]}\');\nINSERT 0 1\nTime: 4.496 ms # with standard btree index, no optimization\n
Run Code Online (Sandbox Code Playgroud)\n\n哈希索引
\n\n\\d+ user_profiles\n Table "public.user_profiles"\n Column | Type | Collation | Nullable | Default | Storage | Stats target | Description\n--------+-------+-----------+----------+--------------------+----------+--------------+-------------\n key | uuid | | not null | uuid_generate_v4() | plain | |\n value | jsonb | | | | extended | |\nIndexes:\n "idx_user_profiles" hash (key)\n\n\nINSERT INTO user_profiles...);\nINSERT 0 1\nTime: 1.690 ms\n# doesnt exists\nSELECT * FROM user_profiles WHERE key = \'2cfc4dbf-a1b9-46b3-8c15-a03f51dde891\'; \nTime: 0.514 ms\n# exists\npostgres=# SELECT * FROM user_profiles WHERE key = \'2cfc4dbf-a1b9-46b3-8c15-a03f51dde890\';\nTime: 1.747 ms\n
Run Code Online (Sandbox Code Playgroud)\n\n为了确认我使用 asyncpg 制作了一个 python 脚本。
\n\nimport asyncio\nimport uuid\nfrom time import time\nimport os\n\nimport asyncpg\nfrom flask import json\n# pip(env) install flask asyncpg\n\n\nTABLE_NAME = "user_profiles"\nEXAMPLE_PROFILE = {\n "type": "whatever",\n "user_id": "8378c54f-3a39-41af-b4ab-5a514aa1b941",\n "same_action_count": 8,\n "active_time": 156.36,\n "is_premium": 0.0,\n "early_premium": 0.0,\n "referral_type": "empty",\n "age": 200,\n "age_is_minor": 0,\n "age_is_student": 0,\n "age_is_young_adult": 0,\n "age_is_mid": 1,\n "age_is_senior": 0,\n "integral_balance_w": 0.0,\n "average_balance": 0.0,\n "integral_balance": 0.0,\n "sum_total_in": 0.0,\n "third_party_company": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n}\n\nasync def create_connection():\n conn = await asyncpg.connect(\n user="postgres",\n password=os.environ.get("SECRET_DB_PASSWORD", "postgres"),\n database="postgres",\n host=os.environ.get("DB_HOST", "127.0.0.1"),\n )\n await conn.set_type_codec("json", encoder=json.dumps, decoder=json.loads, schema="pg_catalog")\n return conn\n\n\nasync def create_table(conn, table_name):\n await conn.execute(\n f"""\n CREATE EXTENSION IF NOT EXISTS "uuid-ossp";\n\n CREATE TABLE IF NOT EXISTS {table_name} (\n id uuid DEFAULT uuid_generate_v4(),\n value jsonb,\n EXCLUDE using hash (id with =)\n );\n\n CREATE INDEX IF NOT EXISTS idx_{table_name} ON {table_name} USING hash (id);\n """\n )\n\n\nasync def run():\n conn = await create_connection()\n await create_table(conn, TABLE_NAME)\n profile = EXAMPLE_PROFILE.copy()\n for i in range(100):\n profile["user_id"] = str(uuid.uuid4())\n hash_id = str(uuid.uuid4())\n str_profile = json.dumps(profile)\n tic = time()\n _ = await conn.execute(f"INSERT INTO {TABLE_NAME} VALUES ($1, $2)", hash_id, str_profile)\n toc = time()\n print(i, (toc - tic) * 1000)\n\n await conn.close()\n\n\nloop = asyncio.get_event_loop()\nloop.run_until_complete(run())\n
Run Code Online (Sandbox Code Playgroud)\n\n哈希索引平均插入时间为1.2 毫秒,其中 btree 大约为3 毫秒。
\n\n\n\n\n我们可以看到,hash 索引的性能比 btree 索引要好,性能差异在 10% 到 22% 之间。在其他一些工作负载中,我们看到了更好的性能,例如 varchar 列上的哈希索引,甚至在社区中,据报道,当哈希索引用于唯一索引列时,性能提高了 40-60%。
\n
http://amitkapila16.blogspot.com/2017/03/hash-indexes-are-faster-than-btree.html
\n\n\n\n\n这里是哈希索引开始发挥作用的地方,一个简单的表,有 2 列,一个序列列和一个文本列,有 319,894,674 条记录,表大小为 23 GB,序列列是主键(没有充分的理由,只是添加到它) PK 的大小为 6852 MB。\n sha1 列中没有索引的查询,执行时间为 4 分钟(感谢并行工作线程)。\n B-Tree 索引的大小:20 GB。哈希索引的大小:8192 MB (8 GB),比 B 树多一半:-),执行时间与 B 树大致相同。\n 较小索引的另一个优点是它们可以最好地适应在内存和较少的磁盘读取中,\xe2\x80\x9cBuffers:共享命中=2\xe2\x80\x9d vs \xe2\x80\x9cBuffers:共享命中=6\xe2\x80\x9d。
\n
https://medium.com/@jorsol/postgresql-10-features-hash-indexes-484f319db281
\n第一:如果你有很多s , PostgreSQL永远不会成为伟大的键值存储UPDATE
。具有许多UPDATE
s 的工作负载对于 PostgreSQL 的架构来说是很困难的。
确保使用fillfactor
低于 100 的方式创建表,以便可以利用 HOT 更新。这是您能够承受大量工作负载的唯一方法UPDATE
。确保上没有索引value
,否则无法工作。
另外,由于该表的 TOAST 表上会有相当大的变动,因此请确保将该表上的autovacuum_work_mem
和 设置为高toast.autovacuum_vacuum_cost_delay = 0
,以便 autovacuum 有机会跟上。
第二:不要使用哈希索引。我一点也不相信它们的速度是它的两到三倍。你必须对此进行基准测试才能说服我。
使用 B 树索引,您的问题就会消失:只需在id
.
第三:将字典存储为一个大的jsonb
意味着每当您更新jsonb
.
通过以关系方式对此进行建模可能会更好。当然,只有在jsonb
.