I have a table with about 5 million records in my Postgres database for real estate listings in my area. The table has 95 columns, ~75 which are used for filtering and searching for listings the user may be interested in.
I'm having trouble speeding up the query. A query using 5 filters that returns ~7k results takes about 7 seconds to complete.
I've searched around and the common advice is to create indexes on the most filtered columns, however in my case they seem to be used pretty much evenly, with only a few used less than others.
My question is, how should I create the indexes? Should I create indexes on each filterable column? Or 2 multi-column indexes with all the columns?
Any advice would be appreciated, thanks.
EDIT:
Listing table Schema:
"MlsId" text NOT NULL,
"StreetNumber" text NOT NULL,
"StreetDirPrefix" text NULL,
"StreetName" text NOT NULL,
"StreetSuffix" text NULL,
"Unit" text NOT NULL,
"Zip" int4 NOT NULL,
"City" text NOT NULL,
"State" text NOT NULL,
"Status" text NOT NULL,
"Beds" int2 NULL,
"BathsFull" int2 NULL,
"BathsHalf" int2 NULL,
"Sqft" int4 NULL,
"Price" money NOT NULL,
"LastUpdated" timestamp NOT NULL,
"PropertyType" text NOT NULL,
"PreviewMedia" text NULL,
"CondoBuildingId" int4 NULL,
"PIN" text NULL,
"AttachedType" text NOT NULL,
"Coords" public.geometry(point, 4326) NOT NULL,
"Data" jsonb NOT NULL,
"ActionDate" timestamptz NULL,
"ParkingPrice" int4 NOT NULL,
"ShortSale" text NULL,
"ParkingGarage" int2 NOT NULL DEFAULT 0,
"ParkingTotal" int2 NOT NULL DEFAULT 0,
"Address" public."citext" NULL,
"LaundryTypes" _text NULL,
"Basement" _text NULL,
"AssociationAmenities" _text NULL,
"UnitFloorLevel" varchar NULL,
"AssociationFee" numeric NULL DEFAULT 0,
"TaxAnnualAmount" numeric NULL DEFAULT 0,
"BasementDescript" text NULL,
"PetWeight" int2 NULL,
"Appliances" _text NULL,
"Cooling" _text NULL,
"Heat" _text NULL,
"Garage" text NULL,
"GarageSpaces" numeric NULL,
"ParkingIncluded" bool NULL,
"Waterfront" bool NULL,
"TotalUnits" int2 NULL,
"UnitNumber" text NULL,
"MasterBedroomLevel" text NULL,
"MasterBedroomBath" bool NULL,
"Ownership" text NULL,
"BuyerFinancing" _text NULL,
"NewConstruction" bool NULL,
"ExteriorType" text NULL,
"ExteriorFeatures" _text NULL,
"InteriorFeatures" _text NULL,
"FireplacesTotal" numeric NULL,
"FireplaceFeatures" _text NULL,
"FireplaceLocation" text NULL,
"WaterSource" _text NULL,
"Furnished" bool NULL,
"LotSizeAcres" numeric NULL,
"ListingContractDate" date NULL,
"PurchaseContractDate" date NULL,
"ListAgentFirstName" text NULL,
"ListAgentLastName" text NULL,
"ListAgentKey" text NULL,
"ListOfficeKey" text NULL,
"ListOfficeName" text NULL,
"BuyerAgentFirstName" text NULL,
"BuyerAgentLastName" text NULL,
"BuyerAgentKey" text NULL,
"BuyerOfficeKey" text NULL,
"BuyerOfficeName" text NULL,
"InvestoryFriendly" int2 NULL,
"OwnerOccupancy" int2 NULL,
"Stories" numeric NULL,
"PublicRemarks" text NULL,
"YearBuilt" int2 NULL,
"B78" bool NULL,
"AssociationFeeIncludes" _text NULL,
"Pets" _text NULL,
"Tags" _text NULL,
"LastCoordUpdated" date NULL,
county text NULL,
As for a typical query, like I said there really isn't a typical query. Users can filter by any combination of the columns. I guess every query will most likely include "Status" and "LastUpdated" but other than that, everyone searches differently.
EDIT 2:
So I read all the replies and messed around some. So in the end, I will require the Status and LastUpdated filter for every search. Created a multi index for status,lastupdated.
That reduced the same query ~150ms. Huge difference:
QUERY PLAN |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
Sort (cost=3004.15..3004.15 rows=1 width=201) (actual time=54.283..54.314 rows=369 loops=1) |
Sort Key: "LastUpdated" DESC |
Sort Method: quicksort Memory: 123kB |
-> Index Scan using ah_mlslisting_status_lu_q_idx on "ah_MlsListing" a (cost=0.43..3004.14 rows=1 width=201) (actual time=2.118..53.899 rows=369 loops=1) |
Index Cond: (("Status" = ANY ('{Active,New,PriceChange}'::text[])) AND ("LastUpdated" >= '2023-04-20 00:00:00'::timestamp without time zone)) |
Filter: (("Sqft" > 900) AND ("GarageSpaces" > '0'::numeric) AND ("City" ~~* 'Chicago'::text) AND ("PropertyType" = 'Residential Lease'::text) AND ("Price" >= (3000)::money) AND ("Price" <= (5000)::money))|
Rows Removed by Filter: 25121 |
Planning Time: 0.529 ms |
Execution Time: 54.448 ms |
Thanks for all the input.